#define WIN32_LEAN_AND_MEAN
#include <Windows.h>
#include <assert.h>
#include <glad/glad.h>
#include <GL/wglext.h>

#include <utils/logger.h>
#include <utils/win32_keys.h>

#include <render/render.h>
#include <render/renderdevice.h>
#include <render/texturesmanager.h>
#include <render/shadersystem.h>
#include <render/modelmanager.h>
#include <render/ui.h>
#include <render/debugrender.h>

#include <game/game_app.h>

#pragma comment(lib, "OpenGL32.lib")

View g_renderView;
Render* g_render = NULL;
HDC g_hDC = NULL;
HGLRC g_openglContext = NULL;

GLuint g_vao = 0;

void CreateOpenGL( HWND hWnd )
{
	PIXELFORMATDESCRIPTOR pfd =
	{
		sizeof(PIXELFORMATDESCRIPTOR),
		1,
		PFD_DRAW_TO_WINDOW | PFD_SUPPORT_OPENGL | PFD_DOUBLEBUFFER,    // Flags
		PFD_TYPE_RGBA,        // The kind of framebuffer. RGBA or palette.
		32,                   // Colordepth of the framebuffer.
		0, 0, 0, 0, 0, 0,
		0,
		0,
		0,
		0, 0, 0, 0,
		24,                   // Number of bits for the depthbuffer
		8,                    // Number of bits for the stencilbuffer
		0,                    // Number of Aux buffers in the framebuffer.
		PFD_MAIN_PLANE,
		0,
		0, 0, 0
	};
	
	g_hDC = GetDC( hWnd );
	int pixelFormat = ChoosePixelFormat( g_hDC, &pfd );
	SetPixelFormat( g_hDC, pixelFormat, &pfd );

	HGLRC tempContext = wglCreateContext( g_hDC );
	wglMakeCurrent( g_hDC, tempContext );

	PFNWGLCREATECONTEXTATTRIBSARBPROC wglCreateContextAttribsARB = (PFNWGLCREATECONTEXTATTRIBSARBPROC)wglGetProcAddress("wglCreateContextAttribsARB");
	assert(wglCreateContextAttribsARB);

	int attribs[] =
	{
		WGL_CONTEXT_MAJOR_VERSION_ARB, 3,
		WGL_CONTEXT_MINOR_VERSION_ARB, 2,
		WGL_CONTEXT_FLAGS_ARB, 0,
		0
	};

	g_openglContext = wglCreateContextAttribsARB( g_hDC, 0, attribs );
	wglMakeCurrent( NULL, NULL );
	wglDeleteContext( tempContext );
	wglMakeCurrent( g_hDC, g_openglContext );
}

Render::Render() : m_window(nullptr)
{
}

Render::~Render()
{
}

void Render::Init()
{
	Msg("Initializing renderer ...");

	m_window = WND_CreateWindow(0, 0, 1280, 720, "Game");

	CreateOpenGL( (HWND)m_window );

	gladLoadGL();

	int width = 0, height = 0;
	WND_GetWindowSize(m_window, &width, &height);

	g_renderView.width = width;
	g_renderView.height = height;

	// Create render device
	g_renderDevice = new RenderDevice();

	// Create texture manager
	g_texturesManager = new TexturesManager();
	g_texturesManager->Init();

	// Create shader system
	g_shaderSystem = new ShaderSystem();
	g_shaderSystem->Init();

	// Create model manager
	g_modelManager = new ModelManager();

	// Create debug render
	g_debugRender = new DebugRender();
	g_debugRender->Initialize();

	g_drawDebug = true;

	// Create UI System
	uiInit();

	// Create global vertex array
	glGenVertexArrays(1, &g_vao);
	glBindVertexArray(g_vao);
}

void Render::Shutdown()
{
	glBindVertexArray(0);
	glDeleteVertexArrays(1, &g_vao);

	uiShutdown();

	g_debugRender->Shutdown();
	delete g_debugRender;
	g_debugRender = nullptr;

	delete g_modelManager;
	g_modelManager = nullptr;

	g_shaderSystem->Shutdown();
	delete g_shaderSystem;
	g_shaderSystem = nullptr;

	g_texturesManager->Shutdown();
	delete g_texturesManager;
	g_texturesManager = nullptr;

	delete g_renderDevice;
	g_renderDevice = nullptr;
}

void Render::BeginFrame()
{
	BuildRenderView();

	int width = 0, height = 0;
	WND_GetWindowSize(m_window, &width, &height);

	glViewport( 0, 0, width, height );

	glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT );
	glClearColor( 0.5f, 0.5f, 0.5f, 1.0f );

	g_renderDevice->SetBackfaceCull(true);
}

void Render::EndFrame()
{
	// Flush debug render
	g_debugRender->RenderFrame();

	SwapBuffers( g_hDC );
}

void Render::DrawFrame()
{

}

void* Render::GetRenderWindow()
{
	return m_window;
}

void Render::BuildRenderView()
{
	if (g_renderView.fov <= 0.0f)
	{
		Msg("Render::BuildRenderView: FOV is zero!");
		g_renderView.fov = 0.1f;//70.0f;
	}

	int width = 0, height = 0;
	WND_GetWindowSize(m_window, &width, &height);

	if (width < 1) width = 1;
	if (height < 1) height = 1;

	float aspect = (float)width / (float)height;
	g_renderView.proj = glm::perspectiveFov(glm::radians(g_renderView.fov), (float)width, (float)height, 0.1f, 1000.0f);

	ShowCursor(FALSE);
}

// Windowing

static int s_should_wrap_mouse = FALSE;
static bool s_wndclass_registered = FALSE;
static const char* s_wndclass_name = "DumbWindowClass";

int g_mousex = 0;
int g_mousey = 0;
RECT g_rect = { 0, 0, 0, 0 };

// Should be implemented by library user.
void WND_Callback_Keyboard(void* window, KeyboardKeys key, int nativeKey, int action)
{
	if (action)
		g_inputSystem->OnKeyDown(key);
	else
		g_inputSystem->OnKeyUp(key);
}

mouseButton_t GetMouseButton(UINT mouseButton)
{
	switch (mouseButton)
	{
	case MK_LBUTTON:
		return mouseButton_Left;
	case MK_MBUTTON:
		return mouseButton_Middle;
	case MK_RBUTTON:
		return mouseButton_Right;
	default:
		break;
	}

	// #TODO: nameless buttons ???
	return GetNamelessMouseButtonIndex(1);
}

LRESULT CALLBACK WND_WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
	switch (uMsg)
	{
	case WM_CLOSE:
		PostQuitMessage(0);
		return 0;

	case WM_KEYDOWN:
		WND_Callback_Keyboard(hwnd, GetKeyFromWin32((Win32Keys::Keys)wParam), (int)wParam, PRESSED);
		break;

	case WM_KEYUP:
		WND_Callback_Keyboard(hwnd, GetKeyFromWin32((Win32Keys::Keys)wParam), (int)wParam, UNPRESSED);
		break;

	case WM_LBUTTONDOWN:
		g_inputSystem->OnMouseKeyDown(mouseButton_Left);
		break;
	case WM_RBUTTONDOWN:
		g_inputSystem->OnMouseKeyDown(mouseButton_Right);
		break;


	case WM_LBUTTONUP:
		g_inputSystem->OnMouseKeyUp(mouseButton_Left);
		break;
	case WM_RBUTTONUP:
		g_inputSystem->OnMouseKeyUp(mouseButton_Right);
		break;

	case WM_MOUSEHOVER:
		ShowCursor(FALSE);
		break;

	case WM_MOUSELEAVE:
		ShowCursor(TRUE);
		break;

	case WM_MOUSEMOVE:
	{
		if (s_should_wrap_mouse)
		{
			GetClientRect(hwnd, &g_rect); // Get window dimension

			g_rect.right -= 12; // right offset
			g_rect.left += 12; // left offset

			g_mousex = LOWORD(lParam); // Get mouse X pos
			g_mousey = HIWORD(lParam); // Get mouse Y pos

			if (g_mousex >= g_rect.right)
			{
				SetCursorPos(g_rect.left, g_mousey);
			}
			else if (g_mousex <= g_rect.left)
			{
				SetCursorPos(g_rect.right, g_mousey);
			}
			g_inputSystem->OnMouseMove(g_mousex, g_mousey);
		}
		else
		{
			g_mousex = LOWORD(lParam); // Get mouse X pos
			g_mousey = HIWORD(lParam); // Get mouse Y pos
			g_inputSystem->OnMouseMove(g_mousex, g_mousey);
		}
	}
	break;
	default:
		break;
	}

	return DefWindowProcA(hwnd, uMsg, wParam, lParam);
}


void* WND_CreateWindow(int x, int y, int width, int height, const char* title)
{
	if (!s_wndclass_registered)
	{
		WNDCLASSA wc;
		memset(&wc, 0, sizeof(wc));
	//	wc.hIcon = LoadIconA(GetModuleHandleA(0), MAKEINTRESOURCEA(IDI_ICON1));
		wc.lpfnWndProc = WND_WindowProc;
		wc.hInstance = GetModuleHandleA(0);
		wc.lpszClassName = s_wndclass_name;
		RegisterClassA(&wc);

		s_wndclass_registered = TRUE;
	}

	// Window Style
	DWORD dwWindowStyle = WS_OVERLAPPEDWINDOW ^ (WS_THICKFRAME | WS_MAXIMIZEBOX);

	// Rect
	RECT rc = { 0, 0, width, height };
	AdjustWindowRect(&rc, dwWindowStyle, FALSE);

	HWND hWnd = CreateWindowA(s_wndclass_name, title, dwWindowStyle,
		x, y, (rc.right - rc.left), (rc.bottom - rc.top),
		NULL, NULL, GetModuleHandleA(0), NULL);

	assert(hWnd != INVALID_HANDLE_VALUE);

	ShowWindow(hWnd, SW_SHOW);
	UpdateWindow(hWnd);

	return hWnd;
}

void WND_DestroyWindow(void* window)
{
	DestroyWindow((HWND)window);
}

void WND_GetWindowSize(void* window, int* x, int* y)
{
	RECT rc;
	memset(&rc, 0, sizeof(rc));

	GetClientRect((HWND)window, &rc);

	*x = (rc.right - rc.left);
	*y = (rc.bottom - rc.top);
}

bool WND_GetWindowGrab(void* window)
{
	return GetFocus() == (HWND)window;
}

void HELPER_GetWindowPos(HWND hWnd, int* x, int* y)
{
	RECT rect = { NULL };
	if (GetWindowRect(hWnd, &rect))
	{
		*x = rect.left;
		*y = rect.top;
	}
}

void WND_SetWindowSize(void* window, int x, int y)
{
	int posX = 0, posY = 0;
	HELPER_GetWindowPos((HWND)window, &posX, &posY);
	MoveWindow((HWND)window, posX, posY, x, y, TRUE);
	UpdateWindow((HWND)window);
}

void WND_SetRelativeMouseMode(int value)
{
	s_should_wrap_mouse = value;
}
