#include "BstmKernel.h"

namespace Brainstem
{
	Kernel::Kernel(const Kernel_Params& params) { Parameters = params; }

	Kernel::~Kernel() { }

	char Kernel::GetKey(WPARAM wParam)
	{
		HKL kbLayout = GetKeyboardLayout(0); 

		BYTE kbState[256] = {0};
		USHORT szKey[5] = {0};
		UINT nChar = (UINT)wParam;
		UINT nScanCode = MapVirtualKeyEx(nChar, 2, kbLayout);

		GetKeyboardState(kbState);
		UINT ok = ToAsciiEx(nChar, nScanCode, kbState, szKey, 0, kbLayout);

		if (szKey[0] != 0)
			return char(szKey[0]);

		return NULL;
	}

	LRESULT CALLBACK Kernel::StaticProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
	{
		Kernel* BstmKernel = (Kernel*)GetWindowLong(hWnd, GWL_USERDATA);

		if (BstmKernel)
			return BstmKernel->WndProc(hWnd,msg,wParam,lParam);

		return DefWindowProc(hWnd,msg,wParam,lParam);
	}

	bool Kernel::Init()
	{
		if (!Parameters.ShowConsole)
			SetConsoleState(SW_HIDE);
		else
			SetConsoleState(SW_MAXIMIZE);

		WRITELN("Welcome to Brainstem" << std::endl << "********************" << std::endl);

		if (Parameters.hInstance == NULL)
			Parameters.hInstance = GetModuleHandle(0);

		char szAppPath[MAX_PATH] = "";
		::GetModuleFileName(0, szAppPath, sizeof(szAppPath) - 1);
		Directory = szAppPath;
		Directory = Directory.substr(0, Directory.rfind("\\")) + "\\";
		SetCurrentDirectory(Directory.c_str());

		WRITELN("Registering Application..");

		WNDCLASSEX newWc = { sizeof(WNDCLASSEX), CS_CLASSDC, StaticProc, 0L, 0L, 
						  Parameters.hInstance, NULL, NULL, NULL, NULL,
						  "Brainstem", NULL };
		wClass = newWc;

		RegisterClassEx(&wClass);

		WRITELN("Creating Window.." << std::endl);
		
		if (Parameters.hWnd == NULL)
			Parameters.hWnd = CreateWindow("Brainstem", "Brainstem", 
				WS_OVERLAPPED | WS_CAPTION | WS_SYSMENU | WS_MINIMIZEBOX, 100, 100, Parameters.Size.Width,  Parameters.Size.Height,
								  GetDesktopWindow(), NULL, wClass.hInstance, NULL);

		SetWindowLong(Parameters.hWnd, GWL_USERDATA, LONG(this));

		WRITELN("hWND: " << Parameters.hWnd);
		WRITELN("hInstance: " << Parameters.hInstance);
		WRITELN("Window Size: [" << Parameters.Size.Width << "x" << Parameters.Size.Height << "]");

		Dimension2<int> screenResolution = GetResolution();
		WRITELN("Resolution: [" << screenResolution.Width << "x" << screenResolution.Height << "]" << std::endl);

		SetProcessorAffinity();

		ShowWindow(Parameters.hWnd, SW_SHOWDEFAULT);
		UpdateWindow(Parameters.hWnd);

		SetMousePosition(GetWindowCenter());

		HMODULE instance = reinterpret_cast<HMODULE>(&__ImageBase);
		HRSRC fontResource = FindResource(instance, MAKEINTRESOURCE(ID_CONSOLE_FONT), RT_FONT);
		HANDLE fontHandle;

		if (fontResource) 
		{
			 HGLOBAL mem = LoadResource(instance, fontResource);
			 void *data = LockResource(mem);
			 size_t len = SizeofResource(instance, fontResource);

			 DWORD nFonts;
			 fontHandle = AddFontMemResourceEx(data, len, NULL, &nFonts);
		}

		CoreFont = fontHandle;

		WRITELN("Starting Task Manager.." << std::endl);
		Tasks = new TaskManager();

		kDevice = new BstmDevice(this, Parameters.hWnd, Parameters.Size.Width, Parameters.Size.Height, Parameters.Fullscreen);
		kDirector = new BstmDirector(this);

		Tasks->AddTask(kDevice);
		Tasks->AddTask(kDirector);

		isPaused = false;

		ResetTimer();
		return (Tasks->StartTasks());
	}

	void Kernel::SetProcessorAffinity()
	{
		DWORD_PTR dwProcessAffinityMask = 0;
		DWORD_PTR dwSystemAffinityMask = 0;
		HANDLE hCurrentProcess = GetCurrentProcess();

		if (!GetProcessAffinityMask(hCurrentProcess, &dwProcessAffinityMask, &dwSystemAffinityMask))
			return;

		if (dwProcessAffinityMask)
		{
			DWORD_PTR dwAffinityMask = (dwProcessAffinityMask & ((~dwProcessAffinityMask) + 1));

			HANDLE hCurrentThread = GetCurrentThread();

			if (hCurrentThread != INVALID_HANDLE_VALUE)
			{
				SetThreadAffinityMask(hCurrentThread, dwAffinityMask);
				CloseHandle(hCurrentThread);
			}
		}

		CloseHandle(hCurrentProcess);
	}

	IDirector* Kernel::GetDirector() { return kDirector; }
	IDevice* Kernel::GetDevice() { return kDevice; }

	bool Kernel::Run()
	{
		if (!isPaused)
		{
			TotalFrames++;

			TimeFPSElapsed = TimeCurrent - TimeFPSLastTick;
			TimeDelta = TimeCurrent - TimeLastUpdate;

			TimeLastUpdate = TimeCurrent;
			TimeCurrent = timeGetTime();

			if (TimeFPSElapsed > 1000.0f)
			{
				TimeFPS = (TotalFrames * 1000.0f) / TimeFPSElapsed;
				TimeFPSLastTick = TimeCurrent;
				TotalFrames = 0;
			}

			Tasks->Update();
		}

		MSG msg; 
		ZeroMemory(&msg, sizeof(msg));

		while(msg.message != WM_QUIT)
		{
			if(PeekMessage(&msg, NULL, 0U, 0U, PM_REMOVE))
			{
				TranslateMessage(&msg);
				DispatchMessage(&msg);
			}
			else
			{
				return true;
			}
		}

		return false;
	}
	
	void Kernel::ResetTimer()
	{
		TimeCurrent = timeGetTime();
		TimeLastUpdate = TimeCurrent;
		TimeDelta = 0;
		TotalFrames = 0;
		TimeFPS = 0;
	}

	float Kernel::GetDeltaTime() { return TimeDelta; }
	float Kernel::GetFPS() { return TimeFPS; }
	long Kernel::GetFrameIndex() { return TimeCurrent; }

	void Kernel::SetPause(bool pause) { isPaused = pause; }
	bool Kernel::IsPaused() { return isPaused; }

	void Kernel::CenterWindow (HWND hWnd)
	{
		Dimension2<int> screenResolution = GetResolution();
		Dimension2<int> windowSize = GetWindowSize();

		int xNew = (screenResolution.Width/2) - (windowSize.Width/2);
		int yNew = (screenResolution.Height/2) - (windowSize.Height/2);
		SetWindowPosition(Vector2<int>(xNew, yNew));
	}
	Vector2<int> Kernel::GetWindowCenter()
	{
		Vector2<int> windowPosition = GetWindowPosition();
		Dimension2<int> windowSize = GetWindowSize();

		return Vector2<int>(windowPosition.X + (windowSize.Width/2),
		windowPosition.Y + (windowSize.Height/2));
	}
	void Kernel::SetEventManager(IEventManager* eventManager)
	{
		EventManager = eventManager;
	}
	void Kernel::SetWindowTitle(const char* title)
	{
		SetWindowText(Parameters.hWnd, title);
	}
	void Kernel::SetMousePosition(Vector2<int> position)
	{
		SetCursorPos(position.X, position.Y);
	}
	Dimension2<int> Kernel::GetResolution()
	{
		RECT rc;
		GetWindowRect(GetDesktopWindow(), &rc);

		return Dimension2<int>(rc.right, rc.bottom);
	}
	Vector2<int> Kernel::GetMousePosition()
	{
		POINT position;
		GetCursorPos(&position);

		return Vector2<int>(position.x, position.y);
	}
	void Kernel::SetWindowPosition(Vector2<int> position)
	{
		SetWindowPos(Parameters.hWnd, NULL, position.X, position.Y, 0, 0, SWP_NOSIZE | SWP_NOZORDER);
	}
	Vector2<int> Kernel::GetWindowPosition()
	{
		RECT rc;
		GetWindowRect(Parameters.hWnd, &rc);

		return Vector2<int>(rc.left, rc.top);
	}
	Dimension2<int> Kernel::GetWindowSize()
	{
		RECT rc;
		GetWindowRect(Parameters.hWnd, &rc);

		return Dimension2<int>(rc.right - rc.left, rc.bottom - rc.top);
	}

	const char* Kernel::GetWorkingDirectory()
	{
		return Directory.c_str();
	}

	void Kernel::SetMouseVisible(bool visible)
	{
		ShowCursor(visible);
	}
	void Kernel::SetConsoleState(int state)
	{
		ShowWindow(GetConsoleWindow(), state);
	}

	void Kernel::Release()
	{
		WRITELN(std::endl << "Shutting Down Kernel.." << std::endl);

		Tasks->KillAll();
		IObject::CollectRemaining();

		if (CoreFont != NULL)
			RemoveFontMemResourceEx(CoreFont);

		WRITELN(std::endl << "Unregistering Brainstem.." << std::endl);
		UnregisterClass("Brainstem", wClass.hInstance);

		WRITELN("Complete..");
	}

	LRESULT Kernel::WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
	{
		char inKey;
		int scrollDelta;

		switch(msg)
		{
			case WM_DESTROY:
				PostQuitMessage(0);
				return 0;
		}

		if (EventManager != NULL)
		{
			Event tEvent;

			switch(msg)
			{
				case WM_KEYDOWN:
				case WM_KEYUP:
					tEvent.Region = EVENT_KB;
					break;
				case WM_LBUTTONDOWN:
				case WM_RBUTTONDOWN:
				case WM_MBUTTONDOWN:
				case WM_LBUTTONUP:
				case WM_RBUTTONUP:
				case WM_MBUTTONUP:
				case WM_LBUTTONDBLCLK:
				case WM_RBUTTONDBLCLK:
				case WM_MBUTTONDBLCLK:
				case WM_MOUSEWHEEL:
				case WM_MOUSEMOVE:
					tEvent.Region = EVENT_MOUSE;
					break;
			}

			switch(tEvent.Region)
			{
				case EVENT_KB:

					inKey = GetKey(wParam);

					tEvent.Key.Char = inKey;
					tEvent.Key.Code = (USHORT)inKey;
					break;
				case EVENT_MOUSE:
					Vector2<int> mousePosition = GetMousePosition();

					tEvent.Mouse.X = mousePosition.X;
					tEvent.Mouse.Y = mousePosition.Y;
					break;
			}

			switch(msg)
			{
				case WM_KEYDOWN:
					tEvent.Type = EVENT_KEYDOWN;
					break;
				case WM_KEYUP:
					tEvent.Type = EVENT_KEYUP;
					break;
				case WM_LBUTTONDOWN:
					tEvent.Type = EVENT_MOUSEDOWN;
					tEvent.Mouse.Button = BTN_LEFT;
					break;
				case WM_RBUTTONDOWN:
					tEvent.Type = EVENT_MOUSEDOWN;
					tEvent.Mouse.Button = BTN_RIGHT;
					break;
				case WM_MBUTTONDOWN:
					tEvent.Type = EVENT_MOUSEDOWN;
					tEvent.Mouse.Button = BTN_MIDDLE;
					break;
				case WM_LBUTTONUP:
					tEvent.Type = EVENT_MOUSEUP;
					tEvent.Mouse.Button = BTN_LEFT;
					break;
				case WM_RBUTTONUP:
					tEvent.Type = EVENT_MOUSEUP;
					tEvent.Mouse.Button = BTN_RIGHT;
					break;
				case WM_MBUTTONUP:
					tEvent.Type = EVENT_MOUSEUP;
					tEvent.Mouse.Button = BTN_MIDDLE;
					break;
				case WM_LBUTTONDBLCLK:
					tEvent.Type = EVENT_MOUSEDBLCLICK;
					tEvent.Mouse.Button = BTN_LEFT;
					break;
				case WM_RBUTTONDBLCLK:
					tEvent.Type = EVENT_MOUSEDBLCLICK;
					tEvent.Mouse.Button = BTN_RIGHT;
					break;
				case WM_MBUTTONDBLCLK:
					tEvent.Type = EVENT_MOUSEDBLCLICK;
					tEvent.Mouse.Button = BTN_MIDDLE;
					break;
				case WM_MOUSEWHEEL:
					tEvent.Type = EVENT_MOUSESCROLL;
					scrollDelta = (short) HIWORD (wParam);

					if (scrollDelta > 0)
						tEvent.Mouse.Button = BTN_WHEELUP;
					else
						tEvent.Mouse.Button = BTN_WHEELDOWN;

					break;
				case WM_MOUSEMOVE:
					tEvent.Type = EVENT_MOUSEMOVE;
					tEvent.Mouse.Button = BTN_NULL;
					break;
			}

			if (tEvent.Type != NULL)
				EventManager->OnEvent(tEvent);
		}

		return DefWindowProc(hWnd, msg, wParam, lParam);
	}
}