#include "FLSearch.h"

#include <ctype.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>

#include "WimpLib:PtrArray.h"
#include "WimpLib:CIcon.h"
#include "WimpLib:Desktop.h"
#include "WimpLib:Display.h"
#include "WimpLib:Exception.h"
#include "WimpLib:Hourglass.h"
#include "WimpLib:Mem.h"
#include "WimpLib:Menu.h"
#include "WimpLib:Message.h"
#include "WimpLib:Task.h"
#include "WimpLib:Template.h"
#include "WimpLib:Window.h"

#include "DigitalCD.h"
#include "PListFiles.h"
#include "PListRadio.h"
#include "FLTrack.h"
#include "Options.h"

typedef enum
{ cmp_LT = 0
, cmp_LE
, cmp_EQ
, cmp_GE
, cmp_GT
} cmpop;

typedef enum
{ Action_List = 0
, Action_AddMark
, Action_RemoveMark
, Action_AddPlayable
, Action_RemovePlayable
, Action_AddSelect
, Action_RemoveSelect
} Action;

typedef enum
{ Scope_AllPlaylists
, Scope_Playlist
, Scope_Marked
, Scope_Unmarked
, Scope_Playable
, Scope_NotPlayable
, Scope_NeverPlay
, Scope_Selected
, Scope_NotSelected
, Scope_Playing
, Scope_Played
, Scope_NotPlayed
, Scope_FailedToPlay
, Scope_Invalidated
} Scope;

static struct
{
	void*       m_pView;
	FLSearch_GetTrackCount GetTrackCount;
	FLSearch_GetTrack      GetTrack;
	FLSearch_GetState      GetState;
	FLSearch_SetState      SetState;
	FLSearch_ShowTrack     ShowTrack;
	HWind       m_wnd;
	HMenu       m_menuaction;
	HMenu       m_menuscope;
	HMenu       m_menucmp;
	cmpop       m_cmpop;
	CSpriteHdr* m_pclue;
	Action      m_action;
	Scope       m_scope;
} FLSearch = {NULL, NULL, NULL, NULL, NULL, NULL, HWind_None, NULL, NULL, NULL, cmp_LT, NULL, Action_List, Scope_Playlist};

#define Icon_Search             0
#define Icon_Cancel             1
#define Icon_Action             3
#define Icon_ActionMenu         4
#define Icon_Scope              6
#define Icon_ScopeMenu          7
#define Icon_HideStart          8
#define Icon_FieldFilename     11
#define Icon_FieldTitle        12
#define Icon_FieldSearchString 14
#define Icon_RateUnrated       17
#define Icon_RateRated         18
#define Icon_RateOp            19
#define Icon_RateOpMenu        20
#define Icon_RateValue         21
#define Icon_FieldClue         22
#define Icon_Clue              23
#define Icon_ClueMenu          24
#define Icon_HideEnd           24
#define Icon_FieldAll          25

static EListenerAction FLSearch_EventHandler(void* handle, const Event* e);


/**
 * Returns where the pointer should be located in the list of pointers
 * if it is/should be present, that is in range [0, count].
 *
 * @param  pi      Pointer to locate.
 * @param  bExact  if true return -1 if not present.
 *
 * @returns  Index in pointers array or -1.
 */
static int CollectionMatch_Find(const PtrArray* pArray, const void* pi, bool bExact)
{
	int i = 0;
	int k = PtrArray_Count(pArray) - 1;
	int j = (i + k) / 2;

	if (k >= 0)
	{
		int val = ((char*) PtrArray_Get(pArray, j)) - ((char*) pi);

		while (i < j)
		{
			if (val >= 0) k = j; // Allow for duplicates
			else i = j;

			j = (i + k) / 2;
			val = ((char*) PtrArray_Get(pArray, j)) - ((char*) pi);
		}

		if (bExact)
		{
			if (!val) return j;

			val = ((char*) PtrArray_Get(pArray, k)) - ((char*) pi);
			if (!val) return k;

			return -1;
		}
		else
		{
			if (val >= 0) return j;

			val = ((char*) PtrArray_Get(pArray, k)) - ((char*) pi);
			if (val >= 0) return k;

			return k + 1;
		}
	}

	return bExact ? -1 : 0;
}

static void FLSearch_SetPoints(int points)
{
	if (points < 0) points = 0;
	if (points > Options()->PlayLists.MaxPoints) points = Options()->PlayLists.MaxPoints;

	if ((points > 0)
	||  *Icon_GetData(FLSearch.m_wnd, Icon_RateValue))
		Icon_Printf(FLSearch.m_wnd, Icon_RateValue, "%d", points);
}

void FLSearch_Open(void* pView
		, FLSearch_GetTrackCount GetTrackCount
		, FLSearch_GetTrack GetTrack
		, FLSearch_GetState GetState
		, FLSearch_SetState SetState
		, FLSearch_ShowTrack ShowTrack)
{
	int i;

	if (FLSearch.m_wnd == HWind_None)
	{
		try
		{
			FLSearch.m_wnd = throw_Window_CreateFrom("FLSearch", "FLSrcH");
			throw_Window_RegisterEventHandler(FLSearch.m_wnd, FLSearch_EventHandler, &FLSearch, false);
		}
		catch
		{
			Window_Delete(FLSearch.m_wnd);
			App_ReportException();
			return;
		}
		catch_end
	}

	if (!FLSearch.m_menucmp)
	{
		try
		{
			FLSearch.m_pclue = NULL;
			FLSearch.m_menucmp = throw_New_Menu_Empty(" ");
			FLSearch.m_menuaction = throw_New_Menu(Msg_Lookup("Action"), Msg_Lookup("FLScAMenu"));
			FLSearch.m_menuscope = throw_New_Menu(Msg_Lookup("Scope"), Msg_Lookup("FLScSMenu"));
			throw_Menu_InsertItem(FLSearch.m_menucmp, "<", -1);
			throw_Menu_InsertItem(FLSearch.m_menucmp, "<=", -1);
			throw_Menu_InsertItem(FLSearch.m_menucmp, "==", -1);
			throw_Menu_InsertItem(FLSearch.m_menucmp, ">=", -1);
			throw_Menu_InsertItem(FLSearch.m_menucmp, ">", -1);

			Icon_SetData(FLSearch.m_wnd, Icon_FieldSearchString, NULL);
			Icon_SetHighlight(FLSearch.m_wnd, Icon_FieldAll, false);
			Icon_SetHighlight(FLSearch.m_wnd, Icon_FieldClue, false);
			Icon_SetData(FLSearch.m_wnd, Icon_Action, Menu_ItemGetText(FLSearch.m_menuaction, FLSearch.m_action));
			Icon_SetData(FLSearch.m_wnd, Icon_Scope, Menu_ItemGetText(FLSearch.m_menuscope, FLSearch.m_scope));
			Icon_SetData(FLSearch.m_wnd, Icon_RateOp, Menu_ItemGetText(FLSearch.m_menucmp, FLSearch.m_cmpop));
			FLSearch_SetPoints(Options()->PlayLists.MaxPoints);
			Icon_SetDimmed(FLSearch.m_wnd, Icon_Action, false);
			Icon_SetDimmed(FLSearch.m_wnd, Icon_ActionMenu, false);
			for (i = Icon_HideStart; i <= Icon_HideEnd; i++)
				Icon_SetDimmed(FLSearch.m_wnd, i, 0);
		}
		catch
		{
			App_ReportException();
			return;
		}
		catch_end
	}

	FLSearch.m_pView = pView;
	FLSearch.GetTrackCount = GetTrackCount;
	FLSearch.GetTrack = GetTrack;
	FLSearch.GetState = GetState;
	FLSearch.SetState = SetState;
	FLSearch.ShowTrack = ShowTrack;

	Window_OpenBehind(FLSearch.m_wnd, -1);
	Icon_SetFocus(FLSearch.m_wnd, Icon_FieldSearchString, -1);
}

void FLSearch_Close(void* pView)
{
	if (FLSearch.m_pView == pView)
		Window_Close(FLSearch.m_wnd);
}

void FLSearch_NotFLSearch(void)
{
	if (FLSearch.m_wnd != HWind_None)
	{
		Window_DeRegisterEventHandler(FLSearch.m_wnd, FLSearch_EventHandler, &FLSearch);
		Window_Delete(FLSearch.m_wnd);
		FLSearch.m_wnd = HWind_None;
	}
	Delete_Menu(FLSearch.m_menuaction, false);
	FLSearch.m_menuaction = NULL;
	Delete_Menu(FLSearch.m_menuscope, false);
	FLSearch.m_menuscope = NULL;
	Delete_Menu(FLSearch.m_menucmp, false);
	FLSearch.m_menucmp = NULL;
}

static bool IsSelected(HIcon i)
{
	return (Icon_GetState(FLSearch.m_wnd, i, EIcon_Selected) == EIcon_Selected);
}

static bool Str_Find(const char* in, const char* ref)
{
	int lin = strlen(in);
	int lref = strlen(ref);
	const char* ein = in + (lin - lref);
	int i;

	for (; in <= ein; in++)
	{
		for (i = 0; i < lref; i++)
		{
			if (toupper(in[i]) != toupper(ref[i]))
				break;
		}

		if (i == lref)
			return true;
	}

	return false;
}

#define fieldflag_filename  0x01
#define fieldflag_metadata  0x02
#define fieldflag_unrated   0x04
#define fieldflag_rated     0x08
#define fieldflag_clue      0x10

static bool FLSearch_IsWithinScope(const FLTrack* pTrack, Scope scope)
{
	switch(scope)
	{
		case Scope_AllPlaylists:
		case Scope_Playlist:
		{
			return true;
		}
		break;
		case Scope_Marked:
		{
			return ((FLSearch.GetState(FLSearch.m_pView, pTrack) & EWItem_Marked) != 0);
		}
		break;
		case Scope_Unmarked:
		{
			return ((FLSearch.GetState(FLSearch.m_pView, pTrack) & EWItem_Marked) == 0);
		}
		break;
		case Scope_Playable:
		{
			return ((FLTrack_GetFlags(pTrack) & (FLTrack_MustNotPlay | FLTrack_NeverPlay)) == 0);
		}
		break;
		case Scope_NotPlayable:
		{
			return ((FLTrack_GetFlags(pTrack) & (FLTrack_MustNotPlay | FLTrack_NeverPlay)) == FLTrack_MustNotPlay);
		}
		break;
		case Scope_NeverPlay:
		{
			return ((FLTrack_GetFlags(pTrack) & FLTrack_NeverPlay) != 0);
		}
		break;
		case Scope_Selected:
		{
			return ((FLSearch.GetState(FLSearch.m_pView, pTrack) & EWItem_Selected) != 0);
		}
		break;
		case Scope_NotSelected:
		{
			return ((FLSearch.GetState(FLSearch.m_pView, pTrack) & EWItem_Selected) == 0);
		}
		break;
		case Scope_Playing:
		{
			if ((PListFiles_GetPlayedTrack(PListFiles_Get()) == pTrack)
			||  (PListRadio_GetPlayedTrack(PListRadio_Get()) == pTrack))
				return true;

			return false;
		}
		break;
		case Scope_Played:
		{
			return ((FLTrack_GetFlags(pTrack) & (FLTrack_Played | FLTrack_Invalidated)) == FLTrack_Played);
		}
		break;
		case Scope_NotPlayed:
		{
			return ((FLTrack_GetFlags(pTrack) & (FLTrack_Played | FLTrack_Invalidated | FLTrack_NeverPlay)) == 0);
		}
		break;
		case Scope_FailedToPlay:
		{
			return ((FLTrack_GetFlags(pTrack) & FLTrack_FailedToPlay) != 0);
		}
		break;
		case Scope_Invalidated:
		{
			return ((FLTrack_GetFlags(pTrack) & FLTrack_Invalidated) != 0);
		}
		break;
	}

	return false;
}

static const char* FLSearch_GetMetaText(const FLTrack* pTrack, EMetaId id)
{
	const MetaData* pmeta = MetaList_Find(FLTrack_GetMeta(pTrack), id);

	if (pmeta && pmeta->data)
		return pmeta->data;

	return &nil;
}

static bool FLSearch_IsMatch(FLTrack* pTrack, unsigned int flags, const char* psearch, int rate, PtrArray* pMatchCol)
{
	bool bsel = false;

	if (IsSelected(Icon_FieldAll))
		bsel = true;
	else
	{
		if (*psearch)
		{
			// Search matching strings
			if (flags & fieldflag_filename)
			{
				if (Str_Find(FLTrack_GetFilename(pTrack), psearch))
					bsel = true;
			}
			if (flags & fieldflag_metadata)
			{
				if (Str_Find(FLSearch_GetMetaText(pTrack, EMetaId_StreamTitle), psearch)
				||  Str_Find(FLSearch_GetMetaText(pTrack, EMetaId_StreamStation), psearch)
				||  Str_Find(FLTrack_GetInfo(pTrack), psearch)
				||  (CollectionMatch_Find(pMatchCol, FLTrack_GetMetaString(pTrack, EMetaId_StreamCollective), true) >= 0)
				||  (CollectionMatch_Find(pMatchCol, FLTrack_GetMetaString(pTrack, EMetaId_StreamArtist), true) >= 0)
				||  (CollectionMatch_Find(pMatchCol, FLTrack_GetMetaString(pTrack, EMetaId_StreamBox), true) >= 0)
				||  (CollectionMatch_Find(pMatchCol, FLTrack_GetMetaString(pTrack, EMetaId_StreamAlbum), true) >= 0)
				||  (CollectionMatch_Find(pMatchCol, FLTrack_GetMetaString(pTrack, EMetaId_StreamBroadcaster), true) >= 0)
				   )
					bsel = true;
			}
		}

		if (flags & fieldflag_clue)
		{
			if (FLTrack_GetClue(pTrack) == FLSearch.m_pclue)
				bsel = true;
		}

		if (flags & fieldflag_unrated)
		{
			if (FLTrack_GetPoints(pTrack) == -1)
				bsel = true;
		}

		if ((flags & fieldflag_rated)
		&&  (FLTrack_GetPoints(pTrack) != -1))
		{
			switch(FLSearch.m_cmpop)
			{
				case cmp_LT:
				{
					if (FLTrack_GetPoints(pTrack) < rate)
						bsel = true;
				}
				break;
				case cmp_LE:
				{
					if (FLTrack_GetPoints(pTrack) <= rate)
						bsel = true;
				}
				break;
				case cmp_EQ:
				{
					if (FLTrack_GetPoints(pTrack) == rate)
						bsel = true;
				}
				break;
				case cmp_GE:
				{
					if (FLTrack_GetPoints(pTrack) >= rate)
						bsel = true;
				}
				break;
				case cmp_GT:
				{
					if (FLTrack_GetPoints(pTrack) > rate)
						bsel = true;
				}
				break;
			}
		}
	}

	return bsel;
}

static bool FLSearch_Act(FLTrack* pTrack, Action action)
{
	bool bret = false;

	switch(action)
	{
		case Action_AddMark:
		{
			FLSearch.SetState(FLSearch.m_pView, pTrack, EWItem_Marked, EWItem_Marked);
		}
		break;
		case Action_RemoveMark:
		{
			FLSearch.SetState(FLSearch.m_pView, pTrack, 0, EWItem_Marked);
		}
		break;
		case Action_AddPlayable:
		{
			if (FLTrack_SetFlags(pTrack, 0, FLTrack_MustNotPlay))
				bret = true;
		}
		break;
		case Action_RemovePlayable:
		{
			if (FLTrack_SetFlags(pTrack, FLTrack_MustNotPlay, FLTrack_MustNotPlay))
				bret = true;
		}
		break;
		case Action_AddSelect:
		{
			FLSearch.SetState(FLSearch.m_pView, pTrack, EWItem_Selected, EWItem_Selected);
		}
		break;
		case Action_RemoveSelect:
		{
			FLSearch.SetState(FLSearch.m_pView, pTrack, 0, EWItem_Selected);
		}
		break;
	}

	return bret;
}

static void FLSearch_OnSearch(void)
{
	char* psearch = Icon_GetData(FLSearch.m_wnd, Icon_FieldSearchString);
	int rate = atoi(Icon_GetData(FLSearch.m_wnd, Icon_RateValue));
	unsigned int flags = 0;
	unsigned int i, j;
	FLTrack* pTrack;
	const FLTrack* pShowTrack = NULL;
	bool bdone = false;
	FileList* volatile pResultList = NULL;
	PtrArray* volatile MatchCol = NULL;
	bool off = !WLib_Hourglass_IsOn();

	try
	{
		OrderedSet* pSet;
		OrderedNode* pNode;
		const char* pData;
		int pos;

	   	MatchCol = New_PtrArray(256);

		if (IsSelected(Icon_FieldFilename)) flags |= fieldflag_filename;
		if (IsSelected(Icon_FieldTitle))    flags |= fieldflag_metadata;
		if (IsSelected(Icon_RateUnrated))   flags |= fieldflag_unrated;
		if (IsSelected(Icon_RateRated))     flags |= fieldflag_rated;
		if (IsSelected(Icon_FieldClue))     flags |= fieldflag_clue;

		if (FLSearch.m_scope == Scope_AllPlaylists)
			FLSearch.m_action = Action_List;

		// Create view if required
		if (FLSearch.m_action == Action_List)
		{
			pResultList = throw_New_FileList_Search();
			if (off) WLib_Hourglass_On();
			FileList_StartUpdate(pResultList);
		}

		// Locate matches in text collections for faster searchs
	    if (flags & fieldflag_metadata)
    	{
    		pSet = DigitalCD.pCollectivesCol;
		    for ( pNode = OrderedSet_GetSuccessor(pSet, NULL)
    		    ; pNode
		        ; pNode = OrderedSet_GetSuccessor(pSet, pNode)
    		    )
		    {
		    	pData = OrderedSet_GetNodeData(pSet, pNode);

				if (Str_Find(pData, psearch))
				{
					pos = CollectionMatch_Find(MatchCol, pData, false);

					PtrArray_Insert(MatchCol, pos, (char*) pData);
				}
		    }

    		pSet = DigitalCD.pArtistsCol;
		    for ( pNode = OrderedSet_GetSuccessor(pSet, NULL)
    		    ; pNode
		        ; pNode = OrderedSet_GetSuccessor(pSet, pNode)
    		    )
		    {
		    	pData = OrderedSet_GetNodeData(pSet, pNode);

				if (Str_Find(pData, psearch))
				{
					pos = CollectionMatch_Find(MatchCol, pData, false);

					PtrArray_Insert(MatchCol, pos, (char*) pData);
				}
		    }

    		pSet = DigitalCD.pBoxesCol;
		    for ( pNode = OrderedSet_GetSuccessor(pSet, NULL)
    		    ; pNode
		        ; pNode = OrderedSet_GetSuccessor(pSet, pNode)
    		    )
		    {
		    	pData = OrderedSet_GetNodeData(pSet, pNode);

				if (Str_Find(pData, psearch))
				{
					pos = CollectionMatch_Find(MatchCol, pData, false);

					PtrArray_Insert(MatchCol, pos, (char*) pData);
				}
		    }

    		pSet = DigitalCD.pAlbumsCol;
		    for ( pNode = OrderedSet_GetSuccessor(pSet, NULL)
    		    ; pNode
		        ; pNode = OrderedSet_GetSuccessor(pSet, pNode)
    		    )
		    {
		    	pData = OrderedSet_GetNodeData(pSet, pNode);

				if (Str_Find(pData, psearch))
				{
					pos = CollectionMatch_Find(MatchCol, pData, false);

					PtrArray_Insert(MatchCol, pos, (char*) pData);
				}
		    }

    		pSet = DigitalCD.pBroadcastersCol;
		    for ( pNode = OrderedSet_GetSuccessor(pSet, NULL)
    		    ; pNode
		        ; pNode = OrderedSet_GetSuccessor(pSet, pNode)
    		    )
		    {
		    	pData = OrderedSet_GetNodeData(pSet, pNode);

				if (Str_Find(pData, psearch))
				{
					pos = CollectionMatch_Find(MatchCol, pData, false);

					PtrArray_Insert(MatchCol, pos, (char*) pData);
				}
		    }
    	}

		if (FLSearch.m_scope == Scope_AllPlaylists)
		{
			for (j = 0; j < DocList_Count(throw_DocList_Get()); j++)
			{
				Document* pDoc = DocList_GetDocument(throw_DocList_Get(), j);
				if (!FileList_IsFileList(pDoc))
					continue;

				FileList* pList = (FileList*) pDoc;

				switch (FileList_GetType(pList))
				{
					case FileList_TypeNormal:
					case FileList_TypeMain:
					case FileList_TypeQueue:
					case FileList_TypeExtern:
					case FileList_TypeDir:
					{
						for (i = 0; i < FileList_TrackCount(pList); i++)
						{
							pTrack = FileList_GetTrack(pList, i);

							if (FLSearch_IsMatch(pTrack, flags, psearch, rate, MatchCol))
							{
								pTrack = throw_New_FLTrack_Link(pResultList, pTrack);
								throw_FileList_InsertTrackAt(pResultList, -1, pTrack);
							}
						}
					}
				}
			}
		}
		else
		{
			for (i = 0; i < FLSearch.GetTrackCount(FLSearch.m_pView); i++)
			{
				pTrack = FLSearch.GetTrack(FLSearch.m_pView, i);

				if ((FLSearch_IsWithinScope(pTrack, FLSearch.m_scope))
				&&  (FLSearch_IsMatch(pTrack, flags, psearch, rate, MatchCol)))
				{
					if (pResultList)
					{
						pTrack = throw_New_FLTrack_Link(pResultList, pTrack);
						throw_FileList_InsertTrackAt(pResultList, -1, pTrack);
					}
					else
					{
						if (!pShowTrack) pShowTrack = pTrack;

						if (FLSearch_Act(pTrack, FLSearch.m_action))
						{
							FLTrack_RefreshViews(pTrack, &FLSearch, false, false);
							bdone = true;
						}
					}
				}
			}
		}
	}
	catch
	{
		App_ReportException();
	}
	catch_end

	if (pResultList)
	{
		FileList_EndUpdate(pResultList, false, true);
		FileList_Show(pResultList, -1);

		if (off) WLib_Hourglass_Off();
	}
	else
	{
		FLSearch.ShowTrack(FLSearch.m_pView, pShowTrack);

		if (bdone)
		{
			if ((FLSearch.m_action == Action_AddPlayable)
			||  (FLSearch.m_action == Action_RemovePlayable))
			{
				PListFiles_RefreshTrackList(PListFiles_Get(), NULL);
				PListRadio_RefreshTrackList(PListRadio_Get());
			}
		}
	}

  	Delete_PtrArray(MatchCol);
}

static void FLSearch_OnCancel(void)
{
	Window_Close(FLSearch.m_wnd);
}

static void FLSearch_Drawer(void* handle, const CWindRedraw* pdraw)
{
	CIcon icon;
	CPoint pt;
	CRect box;

	IGNORE(handle);

	Icon_GetInfo(FLSearch.m_wnd, Icon_Clue, &icon);

	Desktop_SetStdColour(0);
	box = RectToScreen(&icon.box, &pdraw->cvt);
	RoundRectForPlot(&box);
	Display_RectangleFill(&box);

	pt.x = box.x0 + 10;
	pt.y = box.y0 + 10;
	Desktop_PlotSprite(FLSearch.m_pclue, pt, NULL);
}

static EListenerAction FLSearch_ActionMenuHandler(void* handle, const int* hit, const Event* e)
{
	IGNORE(handle);

	switch(e->Type)
	{
		case EEvent_Menu:
		{
			FLSearch.m_action = (Action) hit[0];
			Icon_SetData(FLSearch.m_wnd, Icon_Action, Menu_ItemGetText(FLSearch.m_menuaction, FLSearch.m_action));

			return EListenerAction_StopEvent;
		}
		break;
	}

	return EListenerAction_ContinueEvent;
}

static EListenerAction FLSearch_ScopeMenuHandler(void* handle, const int* hit, const Event* e)
{
	IGNORE(handle);

	switch(e->Type)
	{
		case EEvent_Menu:
		{
			bool dim;

			FLSearch.m_scope = (Scope) hit[0];
			Icon_SetData(FLSearch.m_wnd, Icon_Scope, Menu_ItemGetText(FLSearch.m_menuscope, FLSearch.m_scope));

			dim = (FLSearch.m_scope == Scope_AllPlaylists);
			Icon_SetDimmed(FLSearch.m_wnd, Icon_Action, dim);
			Icon_SetDimmed(FLSearch.m_wnd, Icon_ActionMenu, dim);

			return EListenerAction_StopEvent;
		}
		break;
	}

	return EListenerAction_ContinueEvent;
}

static EListenerAction FLSearch_CmpMenuHandler(void* handle, const int* hit, const Event* e)
{
	IGNORE(handle);

	switch(e->Type)
	{
		case EEvent_Menu:
		{
			FLSearch.m_cmpop = (cmpop) hit[0];
			Icon_SetData(FLSearch.m_wnd, Icon_RateOp, Menu_ItemGetText(FLSearch.m_menucmp, FLSearch.m_cmpop));

			return EListenerAction_StopEvent;
		}
		break;
	}

	return EListenerAction_ContinueEvent;
}

static EListenerAction FLSearch_EventHandler(void* handle, const Event* e)
{
	IGNORE(handle);

	switch(e->Type)
	{
		case EEvent_WindowRedraw:
		{
			Window_Redraw(FLSearch.m_wnd, FLSearch_Drawer, handle);

			return EListenerAction_StopEvent;
		}
		break;
		case EEvent_Mouse:
		{
			const Mouse* m = e->pData;

			if (m->but & EBut_Menu)
				return EListenerAction_StopEvent;

			switch(m->i)
			{
				case Icon_Search:
				{
					FLSearch_OnSearch();
				}
				break;
				case Icon_Cancel:
				{
					FLSearch_OnCancel();
				}
				break;
				case Icon_ActionMenu:
				{
					Menu_Popup(FLSearch.m_wnd, Icon_ActionMenu, FLSearch.m_menuaction, FLSearch_ActionMenuHandler, handle);
				}
				break;
				case Icon_ScopeMenu:
				{
					Menu_Popup(FLSearch.m_wnd, Icon_ScopeMenu, FLSearch.m_menuscope, FLSearch_ScopeMenuHandler, handle);
				}
				break;
				case Icon_RateOpMenu:
				{
					Menu_Popup(FLSearch.m_wnd, Icon_RateOpMenu, FLSearch.m_menucmp, FLSearch_CmpMenuHandler, handle);
				}
				break;
				case Icon_ClueMenu:
				{
					// Open menu
					WList_SetParent(&DigitalCD.m_ClueList, FLSearch.m_wnd);
					WList_SetItemsState(&DigitalCD.m_ClueList, 0, EWItem_Selected, EWItem_Selected, EWItem_Selected);
					WList_Popup(&DigitalCD.m_ClueList, FLSearch.m_wnd, Icon_ClueMenu);
				}
				break;
				case Icon_FieldAll:
				{
					bool dim = IsSelected(Icon_FieldAll);
					int i;

					for (i = Icon_HideStart; i <= Icon_HideEnd; i++)
						Icon_SetDimmed(FLSearch.m_wnd, i, dim);
				}
				break;
			}

			return EListenerAction_StopEvent;
		}
		break;
		case EEvent_Key:
		{
			const Event_Key* pkey = e->pData;

			switch(pkey->code)
			{
				case 0x1b: // Esc
				{
					FLSearch_OnCancel();
					return EListenerAction_StopEvent;
				}
				break;
				case 0x0d: // Return
				{
					FLSearch_OnSearch();
					return EListenerAction_StopEvent;
				}
				break;
				case 0x181: // F1
				{
					App_StrongHelp("Playlist_Search");
					return EListenerAction_StopEvent;
				}
				break;
				default:
				{
					if (pkey->caret.pos.i == Icon_RateValue)
					{
						const char* pdata = Icon_GetData(FLSearch.m_wnd, Icon_RateValue);

						FLSearch_SetPoints(atoi(pdata));
					}
				}
			}
		}
		break;
		case EEvent_SelectionChange:
		{
			const Event_SelectionChange* ev = e->pData;
			const Mouse* m = Mouse_Get();

			FLSearch.m_pclue = WList_Get(&DigitalCD.m_ClueList, ev->Index);
			Icon_Redraw(FLSearch.m_wnd, Icon_Clue);

			if (m->but == EBut_Select)
				Menu_Close();

			return EListenerAction_StopEvent;
		}
		break;
	}

	return EListenerAction_ContinueEvent;
}
