#include <string.h>
#include "WimpLib:WStringList.h"
#include "WimpLib:exception.h"
#include "WimpLib:mem.h"
#include "WimpLib:Caret.h"
#include "WimpLib:Utils.h"
#include "WimpLib:Task.h"
#include "WimpLib:Template.h"
#include "WimpLib:WList.h"
#include "WimpLib:Log.h"

struct WStringList
{
	WList*      m_pWList;
	StrCol*     m_pStrCol;
	const char* m_pStartsWith;
	HWind       m_ParentWindow;
	HIcon       m_ParentIcon;
};

static const char* WStringList_GetItemText(void* pHandle, const void* pItemData)
{
	WStringList* This = pHandle;
	const OrderedNode* pNode = pItemData;

	return OrderedSet_GetNodeData(This->m_pStrCol, pNode);
}

static void WStringList_Clear(WStringList* This)
{
	for (int i = WList_Count(This->m_pWList) - 1; i >= 0; i--)
	{
		OrderedSet_RemoveNode(This->m_pStrCol, WList_Get(This->m_pWList, i));
	}

	WList_Clear(This->m_pWList);
}

static void throw_WStringList_Populate(WStringList* This)
{
	// Clean before checking bounds, cf remove node in clear
	WStringList_Clear(This);

	OrderedNode* pMinNode = OrderedSet_LowerBound(This->m_pStrCol, StrCol_Match, This->m_pStartsWith);
	OrderedNode* pMaxNode = OrderedSet_UpperBound(This->m_pStrCol, StrCol_Match, This->m_pStartsWith);
	OrderedNode* pNode;

	for (pNode = pMinNode; pNode; pNode = OrderedSet_GetSuccessor(This->m_pStrCol, pNode))
	{
		// Ensure that string won't disapear from the set while the list is in use
		OrderedSet_AddNode(This->m_pStrCol, pNode);

		throw_WList_Insert(This->m_pWList, -1, pNode);

		if (pNode == pMaxNode) break;
	}
}

static void WStringList_Select(WStringList* This, int index)
{
	if (index != -1)
	{
		const char* pText = WStringList_GetItemText(This, WList_Get(This->m_pWList, index));
		WStringList_SetIconData(This, pText);
	}
	Window_Close(WList_GetWindow(This->m_pWList));
	Icon_SetFocus(This->m_ParentWindow, This->m_ParentIcon, -1);
	// Trick to move to end that will detected by icon content change monitors
	Event_Key ek;
	Caret_Get(&ek.caret);
	ek.code = 0x1ad; // ctrl right arrow
	Event e = {NULL, EEvent_Key, &ek};
	// Strictly speaking should call Task_ProcessEvent but unused keys are passed to Wimp
	// which messes it and sends it back to our list instead
	Window_ProcessEvent(This->m_ParentWindow, &e);
}

static void throw_WStringList_SetFilter(WStringList* This, const char* pStartsWith)
{
	HWind w = WList_GetWindow(This->m_pWList);

	throw_mem_setstring(&This->m_pStartsWith, pStartsWith);
	throw_WStringList_Populate(This);

	if ((WList_Count(This->m_pWList) == 0)
	&&  Window_IsOpen(w))
		Window_Close(w);
}

static void throw_WStringList_Open(WStringList* This)
{
	CIcon data;

	Icon_GetInfo(This->m_ParentWindow, This->m_ParentIcon, &data);
	data.box.y1 = data.box.y0;
	data.box.y0 -= 200;

	throw_WList_SetPane(This->m_pWList, This->m_ParentWindow, &data.box);
}

static EListenerAction WStringList_NullHandler(void* handle, const Event* e)
{
	WStringList* This = handle;
	HWind w = WList_GetWindow(This->m_pWList);
	CCaret caret;

	IGNORE(e);

	try
	{
		Caret_Get(&caret);

		if ((caret.pos.w == This->m_ParentWindow)
		&&  (caret.pos.i == This->m_ParentIcon))
		{
			const char* pfilter = Icon_GetData(This->m_ParentWindow, This->m_ParentIcon);

			if (strcmp(This->m_pStartsWith, pfilter))
			{
				throw_WStringList_SetFilter(This, pfilter);
				if ((WList_Count(This->m_pWList) > 0)
				&&  !Window_IsOpen(w))
					throw_WStringList_Open(This);
			}
		}
		else if ((caret.pos.w != w) && Window_IsOpen(w))
		{
			Window_Close(w);
		}
	}
	catch
	{
		Task_ReportException();
	}
	catch_end

	return EListenerAction_ContinueEvent;
}

static EListenerAction WStringList_ParentEventHandler(void* handle, const Event* e)
{
	WStringList* This = handle;
	HWind w = WList_GetWindow(This->m_pWList);

	switch(e->Type)
	{
		case EEvent_Key:
		{
			const Event_Key* pkey = e->pData;

			if (pkey->caret.pos.i != This->m_ParentIcon)
				return EListenerAction_ContinueEvent;

			switch(pkey->code)
			{
				case 0x1b: // Esc
				{
					if (Window_IsOpen(w))
					{
						Window_Close(w);
						return EListenerAction_StopEvent;
					}
				}
				break;
				case 0x18e: // down arrow
				{
					if (Window_IsOpen(w) && (WList_Count(This->m_pWList) > 0))
					{
						// If down arrow is pressed move from icon to list
						WList_SetFocus(This->m_pWList, 0, true);
						return EListenerAction_StopEvent;
					}
				}
				break;
				case 0x18f: // up arrow
				{
					int count = WList_Count(This->m_pWList);

					if (Window_IsOpen(w) && (count > 0))
					{
						// If down arrow is pressed move from icon to list
						WList_SetFocus(This->m_pWList, count - 1, true);
						return EListenerAction_StopEvent;
					}
				}
				break;
			}
		}
		break;
	}

	return EListenerAction_ContinueEvent;
}

static EListenerAction WStringList_EventHandler(void* handle, const Event* e)
{
	WStringList* This = handle;
	HWind w = WList_GetWindow(This->m_pWList);

	switch(e->Type)
	{
		case EEvent_WindowClose:
		{
			try
			{
				throw_WList_SetPane(This->m_pWList, HWind_None, NULL);
			}
			catch
			{
				Task_ReportException();
			}
			catch_end

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

			// Ignore key presses in icons
			if (pkey->caret.pos.i != HIcon_None)
				return EListenerAction_ContinueEvent;

			switch(pkey->code)
			{
				case 0x00d: // Return
				{
					WStringList_Select(This, WList_GetFocus(This->m_pWList));
					return EListenerAction_StopEvent;
				}
				break;
				case 0x01b: // Esc
				{
					Window_Close(w);
					Icon_SetFocus(This->m_ParentWindow, This->m_ParentIcon, -1);
					return EListenerAction_StopEvent;
				}
				break;
				case 0x18e: // down arrow
				{
					int count = WList_Count(This->m_pWList) - 1;

					if (WList_GetFocus(This->m_pWList) >= count)
					{
						Icon_SetFocus(This->m_ParentWindow, This->m_ParentIcon, -1);
						return EListenerAction_StopEvent;
					}
				}
				break;
				case 0x18f: // up arrow
				{
					if (WList_GetFocus(This->m_pWList) <= 0)
					{
						Icon_SetFocus(This->m_ParentWindow, This->m_ParentIcon, -1);
						return EListenerAction_StopEvent;
					}
				}
				break;
			}
		}
		break;
		case EEvent_SelectionChange:
		{
			const Event_SelectionChange* ev = e->pData;

			WStringList_Select(This, ev->Index);
			return EListenerAction_StopEvent;
		}
		break;
	}

	return EListenerAction_ContinueEvent;
}

WStringList* throw_New_WStringList(StrCol* pStrCol, HWind parentwnd, HIcon parenticon)
{
	WStringList* This = throw_mem_alloc(sizeof(*This));
	CTemplate* volatile t = NULL;

	This->m_pWList = NULL;
	This->m_pStrCol = pStrCol;
	This->m_pStartsWith = (char*) &nil;
	This->m_ParentWindow = parentwnd;
	This->m_ParentIcon = parenticon;

	try
	{
		throw_Templates_Blank(256, EWind_Moveable | EWind_Pane | EWind_VScroll, 0, 0);

		This->m_pWList = throw_mem_alloc(sizeof(*This->m_pWList));
		throw_WList_WList(This->m_pWList, EWList_SelModePopup, t, This, WStringList_GetItemText);
		WList_SetParent(This->m_pWList, WList_GetWindow(This->m_pWList));
		throw_Task_AddListener(EEvent_Null, WStringList_NullHandler, This, false);
		throw_Window_RegisterEventHandler(This->m_ParentWindow, WStringList_ParentEventHandler, This, true);
		throw_Window_RegisterEventHandler(WList_GetWindow(This->m_pWList), WStringList_EventHandler, This, true);
		throw_WStringList_Populate(This);
	}
	catch
	{
		Delete_WStringList(This);
		throw_current();
	}
	catch_end

	return This;
}

void Delete_WStringList(WStringList* This)
{
	if (!This) return;

	if (This->m_pWList)
	{
		HWind w = WList_GetWindow(This->m_pWList);

		if (w != HWind_None)
		{
			Window_Close(w);
			Window_DeRegisterEventHandler(w, WStringList_EventHandler, This);
		}
		Window_DeRegisterEventHandler(This->m_ParentWindow, WStringList_ParentEventHandler, This);
		Task_RemoveListener(EEvent_Null, WStringList_NullHandler, This);
		WList_NotWList(This->m_pWList);
		mem_free(This->m_pWList);
	}
	mem_free(This->m_pStartsWith);
	mem_free(This);
}

void WStringList_SetIconData(WStringList* This, const char* pText)
{
	if (!Icon_SetData(This->m_ParentWindow, This->m_ParentIcon, pText)) return;

	try
	{
		throw_WStringList_SetFilter(This, Icon_GetData(This->m_ParentWindow, This->m_ParentIcon));
	}
	catch
	{
		Task_ReportException();
	}
	catch_end
}
