/*
	GWEN
	Copyright (c) 2010 Facepunch Studios
	See license in Gwen.h
*/

#include "Gwen/InputHandler.h"
#include "Gwen/Controls/Base.h"
#include "Gwen/DragAndDrop.h"
#include "Gwen/Hook.h"
#include "Gwen/Platform.h"

#define DOUBLE_CLICK_SPEED 0.5f
#define MAX_MOUSE_BUTTONS 5

using namespace Gwen;

struct Action
{
	unsigned char type;

	int x, y;
	Gwen::UnicodeChar chr;
};

static const float KeyRepeatRate = 0.03f;
static const float KeyRepeatDelay = 0.3f;

struct t_KeyData
{
	t_KeyData()
	{
		for (int i = 0; i < Gwen::Key::Count; i++)
		{
			KeyState[i] = false;
			NextRepeat[i] = 0;
		}

		Target = NULL;
		LeftMouseDown = false;
		RightMouseDown = false;
	}

	bool KeyState[Gwen::Key::Count];
	float NextRepeat[Gwen::Key::Count];
	Controls::Base* Target;
	bool LeftMouseDown;
	bool RightMouseDown;

} KeyData;

Gwen::Point MousePosition;

static float g_fLastClickTime[MAX_MOUSE_BUTTONS];
static Gwen::Point g_pntLastClickPos;

enum
{
	ACT_MOUSEMOVE,
	ACT_MOUSEBUTTON,
	ACT_CHAR,
	ACT_MOUSEWHEEL,
	ACT_KEYPRESS,
	ACT_KEYRELEASE,
	ACT_MESSAGE
};

void UpdateHoveredControl(Controls::Base* pInCanvas)
{
	Controls::Base* pHovered = pInCanvas->GetControlAt(MousePosition.x, MousePosition.y);

	if (Gwen::HoveredControl && pHovered != Gwen::HoveredControl)
	{
		Gwen::HoveredControl->OnMouseLeave();

		pInCanvas->Redraw();
	}

	if (pHovered != Gwen::HoveredControl)
	{
		Gwen::HoveredControl = pHovered;

		if (Gwen::HoveredControl)
			Gwen::HoveredControl->OnMouseEnter();

		pInCanvas->Redraw();
	}

	if (Gwen::MouseFocus && Gwen::MouseFocus->GetCanvas() == pInCanvas)
	{
		Gwen::HoveredControl = Gwen::MouseFocus;
	}
}

void FindKeyboardFocus(Controls::Base* pControl)
{
	if (!pControl) return;
	if (pControl->GetKeyboardInputEnabled())
	{
		//Make sure none of our children have keyboard focus first - todo recursive
		for (Controls::Base::List::iterator iter = pControl->Children.begin(); iter != pControl->Children.end(); ++iter)
		{
			Controls::Base* pChild = *iter;
			if (pChild == Gwen::KeyboardFocus)
				return;
		}

		pControl->Focus();
		return;
	}

	return FindKeyboardFocus(pControl->GetParent());
}

Gwen::Point Gwen::Input::GetMousePosition()
{
	return MousePosition;
}

void Gwen::Input::OnCanvasThink(Controls::Base* pControl)
{
	if (Gwen::MouseFocus && !Gwen::MouseFocus->Visible())
		Gwen::MouseFocus = NULL;

	if (Gwen::KeyboardFocus)
	{
		bool isVisible = Gwen::KeyboardFocus->Visible();
		bool isEnabled = KeyboardFocus->GetKeyboardInputEnabled();

		if (!isVisible || !isEnabled)
			Gwen::KeyboardFocus = NULL;
	}

	if (!KeyboardFocus) return;
	if (KeyboardFocus->GetCanvas() != pControl) return;

	float fTime = Gwen::Platform::GetTimeInSeconds();

	//
	// Simulate Key-Repeats
	//
	for (int i = 0; i < Gwen::Key::Count; i++)
	{
		if (KeyData.KeyState[i] && KeyData.Target != KeyboardFocus)
		{
			KeyData.KeyState[i] = false;
			continue;
		}

		if (KeyData.KeyState[i] && fTime > KeyData.NextRepeat[i])
		{
			KeyData.NextRepeat[i] = Gwen::Platform::GetTimeInSeconds() + KeyRepeatRate;

			if (KeyboardFocus)
			{
				KeyboardFocus->OnKeyPress(i);
			}
		}
	}
}

bool Gwen::Input::IsKeyDown(int iKey)
{
	return KeyData.KeyState[iKey];
}

bool Gwen::Input::IsLeftMouseDown()
{
	return KeyData.LeftMouseDown;
}

bool Gwen::Input::IsRightMouseDown()
{
	return KeyData.RightMouseDown;
}

void Gwen::Input::OnMouseMoved(Controls::Base* pCanvas, int x, int y, int /*deltaX*/, int /*deltaY*/)
{
	MousePosition.x = x;
	MousePosition.y = y;

	UpdateHoveredControl(pCanvas);
}

bool Gwen::Input::OnMouseClicked(Controls::Base* pCanvas, int iMouseButton, bool bDown)
{
	// If we click on a control that isn't a menu we want to close
	// all the open menus. Menus are children of the canvas.
	if (bDown && (!Gwen::HoveredControl || !Gwen::HoveredControl->IsMenuComponent()))
	{
		pCanvas->CloseMenus();
	}

	if (!Gwen::HoveredControl) return false;
	if (Gwen::HoveredControl->GetCanvas() != pCanvas) return false;
	if (!Gwen::HoveredControl->Visible()) return false;
	if (Gwen::HoveredControl == pCanvas) return false;

	if (iMouseButton > MAX_MOUSE_BUTTONS)
		return false;

	if (iMouseButton == 0)
		KeyData.LeftMouseDown = bDown;
	else if (iMouseButton == 1)
		KeyData.RightMouseDown = bDown;

	// Double click.
	// Todo: Shouldn't double click if mouse has moved significantly
	bool bIsDoubleClick = false;

	if (bDown &&
		g_pntLastClickPos.x == MousePosition.x &&
		g_pntLastClickPos.y == MousePosition.y &&
		(Gwen::Platform::GetTimeInSeconds() - g_fLastClickTime[iMouseButton]) < DOUBLE_CLICK_SPEED)
	{
		bIsDoubleClick = true;
	}

	if (bDown && !bIsDoubleClick)
	{
		g_fLastClickTime[iMouseButton] = Gwen::Platform::GetTimeInSeconds();
		g_pntLastClickPos = MousePosition;
	}

	if (bDown)
	{
		FindKeyboardFocus(Gwen::HoveredControl);
	}

	Gwen::HoveredControl->UpdateCursor();

	// This tells the child it has been touched, which
	// in turn tells its parents, who tell their parents.
	// This is basically so that Windows can pop themselves
	// to the top when one of their children have been clicked.
	if (bDown)
		Gwen::HoveredControl->Touch();

#ifdef GWEN_HOOKSYSTEM
	if (bDown)
	{
		if (Hook::CallHook(&Hook::BaseHook::OnControlClicked, Gwen::HoveredControl, MousePosition.x, MousePosition.y))
			return true;
	}
#endif

	switch (iMouseButton)
	{
		case 0:
		{
			if (DragAndDrop::OnMouseButton(Gwen::HoveredControl, MousePosition.x, MousePosition.y, bDown))
				return true;

			if (bIsDoubleClick)
				Gwen::HoveredControl->OnMouseDoubleClickLeft(MousePosition.x, MousePosition.y);
			else
				Gwen::HoveredControl->OnMouseClickLeft(MousePosition.x, MousePosition.y, bDown);
			return true;
		}

		case 1:
		{
			if (bIsDoubleClick)
				Gwen::HoveredControl->OnMouseDoubleClickRight(MousePosition.x, MousePosition.y);
			else
				Gwen::HoveredControl->OnMouseClickRight(MousePosition.x, MousePosition.y, bDown);
			return true;
		}
	}

	return false;
}

bool Gwen::Input::HandleAccelerator(Controls::Base* pCanvas, Gwen::UnicodeChar chr)
{
	//Build the accelerator search string
	Gwen::UnicodeString accelString;
	if (Gwen::Input::IsControlDown())
		accelString += L"Ctrl + ";
	if (Gwen::Input::IsShiftDown())
		accelString += L"Shift + ";

	accelString += chr;

	//Debug::Msg("Accelerator string :%S\n", accelString.c_str());

	if (Gwen::KeyboardFocus && Gwen::KeyboardFocus->HandleAccelerator(accelString))
		return true;

	if (Gwen::MouseFocus && Gwen::MouseFocus->HandleAccelerator(accelString))
		return true;

	if (pCanvas->HandleAccelerator(accelString))
		return true;

	return false;
}

bool Gwen::Input::DoSpecialKeys(Controls::Base* pCanvas, Gwen::UnicodeChar chr)
{
	if (!Gwen::KeyboardFocus) return false;
	if (Gwen::KeyboardFocus->GetCanvas() != pCanvas) return false;
	if (!Gwen::KeyboardFocus->Visible()) return false;
	if (!Gwen::Input::IsControlDown()) return false;

	if (chr == L'C' || chr == L'c')
	{
		Gwen::KeyboardFocus->OnCopy(NULL);
		return true;
	}

	if (chr == L'V' || chr == L'v')
	{
		Gwen::KeyboardFocus->OnPaste(NULL);
		return true;
	}

	if (chr == L'X' || chr == L'x')
	{
		Gwen::KeyboardFocus->OnCut(NULL);
		return true;
	}

	if (chr == L'A' || chr == L'a')
	{
		Gwen::KeyboardFocus->OnSelectAll(NULL);
		return true;
	}

	return false;
}

bool Gwen::Input::OnKeyEvent(Controls::Base* pCanvas, int iKey, bool bDown)
{
	if (!Gwen::KeyboardFocus) return false;
	if (Gwen::KeyboardFocus->GetCanvas() != pCanvas) return false;
	if (!Gwen::KeyboardFocus->Visible()) return false;

	if (bDown)
	{
		if (!KeyData.KeyState[iKey])
		{
			KeyData.KeyState[iKey] = true;
			KeyData.NextRepeat[iKey] = Gwen::Platform::GetTimeInSeconds() + KeyRepeatDelay;
			KeyData.Target = KeyboardFocus;

			return KeyboardFocus->OnKeyPress(iKey);
		}
	}
	else
	{
		if (KeyData.KeyState[iKey])
		{
			KeyData.KeyState[iKey] = false;

			// BUG BUG. This causes shift left arrow in textboxes
			// to not work. What is disabling it here breaking?
			//KeyData.Target = NULL;

			return KeyboardFocus->OnKeyRelease(iKey);
		}
	}

	return false;
}