#include "WimpLib:WFiler.h"

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

#include "WimpLib:mem.h"
#include "WimpLib:Display.h"
#include "WimpLib:Desktop.h"
#include "WimpLib:DragDrop.h"
#include "WimpLib:Exception.h"
#include "WimpLib:Keyboard.h"
#include "WimpLib:Task.h"
#include "WimpLib:Window.h"

typedef struct
{
	WSelector   sel;
	CRect       box;
    int         rot;
    bool        bShow;
} WGridSelect;

typedef struct
{
	WSelector   sel;
	int         startpos;
	int         curpos;
} WListSelect;

/*-------------------------------------------------------------------------*
 *--- GridSelect ----------------------------------------------------------*
 *-------------------------------------------------------------------------*/

/**
 * Draws a selection box during redraw/update loop
 *
 * @param pdraw  Pointer to the redraw parameters.
 */

static void WGridSelect_Draw(WSelector* pSel, const CWindRedraw* r)
{
	WGridSelect* This = (WGridSelect*) pSel;
	const unsigned int pattern = 0x3f3f3f3f;

	CRect box = RectToScreen(&This->box, &r->cvt);

	// Set dash-line pattern
	Display_SetDashPattern(8
					, (pattern >> This->rot) & 0xff
					, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc);

	// plot dashed rectangle
	Display_Plot( 4, box.x0, box.y0);
	Display_Plot(22, box.x0, box.y1);
	Display_Plot(54, box.x1, box.y1);
	Display_Plot(54, box.x1, box.y0);
	Display_Plot(62, box.x0, box.y0);
}

/**
 * Rotate an unmodified selection box during an update loop
 *
 * @param pdraw  Pointer to the redraw parameters.
 */

static void WGridSelect_DrawUpdate(WSelector* pSel, const CWindRedraw* r)
{
	WGridSelect* This = (WGridSelect*) pSel;
	const unsigned int pattern = 0x41414141;

	CRect box = RectToScreen(&This->box, &r->cvt);

	// Set dash-line pattern
	Display_SetDashPattern(8
					, (pattern >> This->rot) & 0xff
					, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc, 0xcc);

	// plot dashed rectangle
	Display_Plot( 4, box.x0, box.y0);
	Display_Plot(22, box.x0, box.y1);
	Display_Plot(54, box.x1, box.y1);
	Display_Plot(54, box.x1, box.y0);
	Display_Plot(62, box.x0, box.y0);
}

/**
 * Updates the window to show or hide the selection box.
 *
 * @param bShow  0 Hide, 1 Show, 2 Update.
 */

static void WGridSelect_Plot(WGridSelect* This, int Show)
{
	HWind w = WListCore_GetWindow(This->sel.pList);
	CRect rect;

	RoundRect(&This->box);

	rect = This->box;
	if (This->box.x0 > This->box.x1)
	{
		rect.x0 = This->box.x1;
		rect.x1 = This->box.x0;
	}
	if (This->box.y0 > This->box.y1)
	{
		rect.y0 = This->box.y1;
		rect.y1 = This->box.y0;
	}
	rect.x1++;
	rect.y1++;

	RoundUpRect(&rect);

	if (Show > 1)
	{
		Window_Update(w, &rect
				, (Window_Drawer) WGridSelect_DrawUpdate
				, This);
	}
	else
	{
		Window_Update(w, &rect
				, (Window_Drawer) WGridSelect_Draw
				, This);
	}

	This->bShow = (Show != 0);
}

/**
 * Checks changes in the pointer to update the selection state,
 * scroll the list if necessary and redraw the selection box.
 */

static void WGridSelect_Check(WSelector* pSel)
{
	WGridSelect* This = (WGridSelect*) pSel;
	CWindCvt info = Window_GetPosInfo(WListCore_GetWindow(This->sel.pList));
	const Mouse* m = Mouse_Get();
	CPoint pt;

	// Scroll the window if the mouse get near the window's borders
	Window_ScrollIfMouseOnBorder(WListCore_GetWindow(This->sel.pList), This->sel.pList->m_Size.cx, This->sel.pList->m_Size.cy);

	// Update the selection if the selection box was modified
	pt = PointToWindow(&m->pt, &info);

	if (((pt.x != This->box.x1) || (pt.y != This->box.y1)))
	{
		CRect box = This->box;

		// Hide the selection box
		if (This->bShow) WGridSelect_Plot(This, 0);

		This->box.x1 = pt.x;
		This->box.y1 = pt.y;
		WListCore_BoxActivation(This->sel.pList, &box, &This->box);

		// Plot the box again with a rotation in the dash pattern
		This->rot++;
		if (This->rot >= 8) This->rot = 0;
		WGridSelect_Plot(This, 1);
	}
	else
	{
		// Plot the box again with a rotation in the dash pattern
		This->rot++;
		if (This->rot >= 8) This->rot = 0;
		WGridSelect_Plot(This, 2);
	}
}

/**
 * Starts the selection process.
 * Shows the selection box.
 */

static void WGridSelect_Start(WSelector* pSel)
{
	WGridSelect* This = (WGridSelect*) pSel;

	WGridSelect_Plot(This, 1);
}

/**
 * Stops the selection process.
 * Hides the selection box.
 * Deletes the selector.
 */

static void WGridSelect_Stop(WSelector* pSel)
{
	WGridSelect* This = (WGridSelect*) pSel;

	if (This->bShow) WGridSelect_Plot(This, 0);

	mem_free(This);
}

/**
 * Creates a Selector using a mouse controlled selection box.
 * All items whose active area is at least partially covered by the selection
 * become active.
 *
 * @param pList  A pointer to the list in which items are to be selected.
 * @param pMouse A pointer to the pointer information which is to be used
 *               as a start point to the selection process.
 */

WSelector* throw_New_WGridSelect(WListCore* pList, const Mouse* m)
{
	WGridSelect* This = throw_mem_alloc(sizeof(*This));
	CWindCvt info = Window_GetPosInfo(WListCore_GetWindow(pList));

	This->sel.pList = pList;
	This->sel.pFStart = WGridSelect_Start;
	This->sel.pFStop  = WGridSelect_Stop;
	This->sel.pFCheck = WGridSelect_Check;
	This->sel.pFDraw  = WGridSelect_Draw;
	This->box.x0 = This->box.x1 = m->pt.x;
	This->box.y0 = This->box.y1 = m->pt.y;
	This->box = RectToWindow(&This->box, &info);
	This->rot = 0;

	return &This->sel;
}

/*-------------------------------------------------------------------------*
 *--- GridKeySelect -------------------------------------------------------*
 *-------------------------------------------------------------------------*/

WSelector* throw_New_WGridKeySelect(WListCore* pList, int index)
{
	WGridSelect* This = throw_mem_alloc(sizeof(*This));
	CPoint pt = WListCore_IndexToRowCol(pList, index);

	This->sel.pList = pList;
//	This->sel.pFStart = WGridKeySelect_Start;
//	This->sel.pFStop  = WGridKeySelect_Stop;
//	This->sel.pFCheck = WGridKeySelect_Check;
	This->sel.pFDraw  = NULL;
	This->box.x0 = This->box.x1 = pt.x;
	This->box.y0 = This->box.y1 = pt.y;

	return &This->sel;
}

/*-------------------------------------------------------------------------*
 *--- ListSelect ----------------------------------------------------------*
 *-------------------------------------------------------------------------*/

/**
 * Updates the activation status of a range of items in a list.
 *
 * @param start  Pointer to the list.
 * @param start  Index of the start point item of the selection.
 * @param cur    Index of the old current item of the selection.
 * @param pos    Index of the new current item of the selection.
 */

static void WListSelect_Activate(WListCore* pList, int start, int cur, int pos)
{
	int min, max, i;

	min = max = start;
	if (min > cur) min = cur;
	if (min > pos) min = pos;
	if (max < cur) max = cur;
	if (max < pos) max = pos;

	if (start < pos)
	{
		for (i = min; i < start; i++)
			WListCore_SetItemState(pList, i, 0, EWItem_Active);

		for (; i <= pos; i++)
			WListCore_SetItemState(pList, i, EWItem_Active, EWItem_Active);

		for (; i <= max; i++)
			WListCore_SetItemState(pList, i, 0, EWItem_Active);
	}
	else
	{
		for (i = min; i < pos; i++)
			WListCore_SetItemState(pList, i, 0, EWItem_Active);

		for (; i <= start; i++)
			WListCore_SetItemState(pList, i, EWItem_Active, EWItem_Active);

		for (; i <= max; i++)
			WListCore_SetItemState(pList, i, 0, EWItem_Active);
	}
}

/**
 * Checks changes in the pointer to update the selection state
 * and scroll the list if necessary.
 */

static void WListSelect_Check(WSelector* pSel)
{
	WListSelect* This = (WListSelect*) pSel;
	const Mouse* m = Mouse_Get();

	// Scroll the window if the mouse get near the window's borders
	Window_ScrollIfMouseOnBorder(WListCore_GetWindow(This->sel.pList), This->sel.pList->m_Size.cx, This->sel.pList->m_Size.cy);

	// Find index corresponding to mouse position
	int pos = WListCore_ItemFromScreenPt(This->sel.pList, &m->pt, EWListPtrMode_InRange);

	// Update activation status and focus position
	if (pos != This->curpos)
	{
		WListCore_SetFocus(This->sel.pList, pos, true);
		WListSelect_Activate(This->sel.pList, This->startpos, This->curpos, pos);
		This->curpos = pos;
	}
}

/**
 * Starts the selection process.
 */

static void WListSelect_Start(WSelector* pSel)
{
	WListSelect* This = (WListSelect*) pSel;

	// Move focus to curpos and activate that item.
	WListCore_SetFocus(This->sel.pList, This->curpos, true);
	WListSelect_Activate(This->sel.pList, This->startpos, This->curpos, This->curpos);
}

/**
 * Stops the selection process.
 * Deletes the selector.
 */

static void WListSelect_Stop(WSelector* pSel)
{
	WListSelect* This = (WListSelect*) pSel;

	mem_free(This);
}

/**
 * Creates a Selector using the mouse to control the selection.
 * All items between the start point and the current mouse position
 * become active.
 *
 * @param pList  A pointer to the list in which items are to be selected.
 * @param item   The index of the item to start the selection from.
 */

WSelector* throw_New_WListSelect(WListCore* pList, int index)
{
	WListSelect* This = throw_mem_alloc(sizeof(*This));

	This->sel.pList = pList;
	This->sel.pFStart = WListSelect_Start;
	This->sel.pFStop  = WListSelect_Stop;
	This->sel.pFCheck = WListSelect_Check;
	This->sel.pFDraw  = NULL;
	This->startpos = This->curpos = index;

	return &This->sel;
}

/*-------------------------------------------------------------------------*
 *--- ListKeySelect -------------------------------------------------------*
 *-------------------------------------------------------------------------*/

/**
 * Checks changes in the focus to update the selection state.
 */

static void WListKeySelect_Check(WSelector* pSel)
{
	WListSelect* This = (WListSelect*) pSel;
	int pos = WListCore_GetFocus(This->sel.pList);

	// Update activation states if focus was moved
	if (pos != This->curpos)
	{
		WListSelect_Activate(This->sel.pList, This->startpos, This->curpos, pos);
		This->curpos = pos;
	}
}

/**
 * Starts the selection process.
 */

static void WListKeySelect_Start(WSelector* pSel)
{
	WListSelect* This = (WListSelect*) pSel;

	// Activate item at curpos
	WListSelect_Activate(This->sel.pList, This->startpos, This->curpos, This->curpos);
}

/**
 * Stops the selection process.
 * Deletes the selector.
 */

static void WListKeySelect_Stop(WSelector* pSel)
{
	WListSelect* This = (WListSelect*) pSel;

	mem_free(This);
}

/**
 * Creates a Selector using the keyboard to control the selection.
 * All items between the start point and the current mouse position
 * become active.
 *
 * @param pList  A pointer to the list in which items are to be selected.
 * @param item   The index of the item to start the selection from.
 */

WSelector* throw_New_WListKeySelect(WListCore* pList, int index)
{
	WListSelect* This = throw_mem_alloc(sizeof(*This));

	This->sel.pList = pList;
	This->sel.pFStart = WListKeySelect_Start;
	This->sel.pFStop  = WListKeySelect_Stop;
	This->sel.pFCheck = WListKeySelect_Check;
	This->sel.pFDraw  = NULL;
	This->startpos = This->curpos = index;

	return &This->sel;
}
