#include "ImGui_DeviceAGK.h"
#include <ShellScalingApi.h>

int keymap[] = {
	0,0,0,0,0,0,0,0,ImGuiKey_Backspace,ImGuiKey_Tab,0,0,0,ImGuiKey_Enter,
	0,0,ImGuiKey_ModShift,ImGuiKey_ModCtrl,ImGuiKey_ModAlt,ImGuiKey_Pause,
	ImGuiKey_CapsLock,0,0,0,0,0,0,ImGuiKey_Escape,0,0,0,0,ImGuiKey_Space,
	ImGuiKey_PageUp,ImGuiKey_PageDown,ImGuiKey_End,ImGuiKey_Home,
	ImGuiKey_LeftArrow,ImGuiKey_UpArrow,ImGuiKey_RightArrow,ImGuiKey_DownArrow,
	0,0,0,0,ImGuiKey_Insert,ImGuiKey_Delete,0,ImGuiKey_0,ImGuiKey_1,
	ImGuiKey_2,ImGuiKey_3,ImGuiKey_4,ImGuiKey_5,ImGuiKey_6,ImGuiKey_7,
	ImGuiKey_8,ImGuiKey_9,0,0,0,0,0,0,0,ImGuiKey_A,ImGuiKey_B,ImGuiKey_C,
	ImGuiKey_D,ImGuiKey_E,ImGuiKey_F,ImGuiKey_G,ImGuiKey_H,ImGuiKey_I,ImGuiKey_J,
	ImGuiKey_K,ImGuiKey_L,ImGuiKey_M,ImGuiKey_N,ImGuiKey_O,ImGuiKey_P,ImGuiKey_Q,
	ImGuiKey_R,ImGuiKey_S,ImGuiKey_T,ImGuiKey_U,ImGuiKey_V,ImGuiKey_W,ImGuiKey_X,
	ImGuiKey_Y,ImGuiKey_Z,ImGuiKey_LeftSuper,ImGuiKey_RightSuper,0,0,0,
	ImGuiKey_Keypad0,ImGuiKey_Keypad1,ImGuiKey_Keypad2,ImGuiKey_Keypad3,ImGuiKey_Keypad4,
	ImGuiKey_Keypad5,ImGuiKey_Keypad6,ImGuiKey_Keypad7,ImGuiKey_Keypad8,ImGuiKey_Keypad9,
	ImGuiKey_KeypadMultiply,ImGuiKey_KeypadAdd,0,ImGuiKey_KeypadSubtract,
	ImGuiKey_KeypadDecimal,ImGuiKey_KeypadDivide,ImGuiKey_F1,ImGuiKey_F2,ImGuiKey_F3,
	ImGuiKey_F4,ImGuiKey_F5,ImGuiKey_F6,ImGuiKey_F7,ImGuiKey_F8,ImGuiKey_F9,ImGuiKey_F10,
	ImGuiKey_F11,ImGuiKey_F12,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,ImGuiKey_NumLock,
	ImGuiKey_ScrollLock,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
	0,0,0,0,0,0,0,0,0,0,0,0,0,ImGuiKey_Semicolon,ImGuiKey_Equal,ImGuiKey_Comma,ImGuiKey_Minus,
	ImGuiKey_Period,ImGuiKey_Slash,ImGuiKey_Apostrophe,ImGuiKey_LeftBracket,ImGuiKey_Backslash,
	ImGuiKey_RightBracket,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
	ImGuiKey_LeftShift,ImGuiKey_RightShift,ImGuiKey_LeftCtrl,ImGuiKey_RightCtrl,
	ImGuiKey_LeftAlt,ImGuiKey_RightAlt,ImGuiKey_0,ImGuiKey_1,ImGuiKey_2,ImGuiKey_3,ImGuiKey_4,
	ImGuiKey_5,ImGuiKey_6,ImGuiKey_7,ImGuiKey_8,ImGuiKey_9
};

ImGui_DeviceAGK::ImGui_DeviceAGK() : ImGui_Device()
{
	if (!ImGuiDevice) {
		ImGui::StyleColorsDark();
		Init();

		ImGuiDevice = this;
	}
}

ImGui_DeviceAGK::~ImGui_DeviceAGK()
{
}

bool ImGui_DeviceAGK::IsMouseMoved()
{
	static float lastx = 0, lasty = 0;
	float curx = agk::GetRawMouseX();
	float cury = agk::GetRawMouseY();
	bool result = false;

	if (lastx != curx) { lastx = curx; result = true; }
	if (lasty != cury) { lasty = cury; result = true; }

	return result;
}

bool ImGui_DeviceAGK::IsMouseWheel()
{
	static float lastw;
	float curw = agk::GetRawMouseWheel();

	if (lastw != curw) { lastw = curw; return true; }

	return false;
}

void ImGui_DeviceAGK::Init()
{
	ImGuiIO& io = ImGui::GetIO();
	IM_ASSERT(io.BackendPlatformUserData == nullptr && "Already initialized a platform backend!");

	io.BackendPlatformUserData = this;
	io.BackendPlatformName = "imgui_impl_agk2_cpp";
	//io.BackendFlags |= ImGuiBackendFlags_HasMouseCursors;       // We can honor GetMouseCursor() values (optional)
	io.BackendFlags |= ImGuiBackendFlags_HasSetMousePos;        // We can honor io.WantSetMousePos requests (optional, rarely used)

	io.BackendRendererUserData = (void*)this;
	io.BackendRendererName = "imgui_impl_agk2_renderer_cpp";
	//io.BackendFlags |= ImGuiBackendFlags_RendererHasVtxOffset;  // We can honor the ImDrawCmd::VtxOffset field, allowing for large meshes.

	m_pRenderer = new cShape();
	m_pRenderer->CreateVertices(0x2000, 0x6000);
	m_pRenderer->SetPosition(0, 0);

}

void ImGui_DeviceAGK::NewFrame()
{
	ImGuiIO& io = ImGui::GetIO();

	if (!m_pFontTexture)
		CreateDeviceObjects();

	// Setup display size (every frame to accommodate for window resizing)
	//int display_w, display_h;
	int w = agk::GetVirtualWidth();
	int h = agk::GetVirtualHeight();

	io.DisplaySize = ImVec2((float)w, (float)h);
	io.DisplayFramebufferScale = ImVec2(1, 1);

	io.DeltaTime = agk::GetFrameTime();

	UpdateMouseData();
	UpdateMouseCursor();

	UpdateGamepads();

	ImGui::NewFrame();
}

void ImGui_DeviceAGK::Render()
{
	ImGui::Render();
	DrawData(ImGui::GetDrawData());
}

void ImGui_DeviceAGK::DrawData(ImDrawData* draw_data)
{
	ImVec2 clip_off = draw_data->DisplayPos;
	ImVec2 render_scale(m_fScaleUI, m_fScaleUI);
	ImVec2 clip_scale = render_scale;

	int fb_width = (int)(draw_data->DisplaySize.x * render_scale.x);
	int fb_height = (int)(draw_data->DisplaySize.y * render_scale.y);
	if (fb_width == 0 || fb_height == 0)
		return;

	agk::SetScissor(0, 0, 0, 0);
	for (int n = 0; n < draw_data->CmdListsCount; n++) {
		const ImDrawList* cmd_list = draw_data->CmdLists[n];
		const ImDrawVert* vtx_buffer = cmd_list->VtxBuffer.Data;
		const ImDrawIdx* idx_buffer = cmd_list->IdxBuffer.Data;

		// Bind texture, Draw
		int numV = cmd_list->VtxBuffer.Size;
		int numI = cmd_list->IdxBuffer.Size;
		m_pRenderer->CreateVertices(numV, numI);
		ImVec2* vtx = new ImVec2[numV];
		ImVec2* uv = new ImVec2[numV];
		ImVec4* col = new ImVec4[numV];
		for (int i = 0; i < numV; i++) {
			BYTE* color = (BYTE*)&vtx_buffer[i].col;
			vtx[i] = vtx_buffer[i].pos;
			uv[i] = vtx_buffer[i].uv;
			col[i] = ImVec4(
				(float)color[0] / 255.0f,
				(float)color[1] / 255.0f,
				(float)color[2] / 255.0f,
				(float)color[3] / 255.0f);
		}

		for (int cmd_i = 0; cmd_i < cmd_list->CmdBuffer.Size; cmd_i++)
		{
			const ImDrawCmd* pcmd = &cmd_list->CmdBuffer[cmd_i];
			if (pcmd->UserCallback) {
				// User callback, registered via ImDrawList::AddCallback()
				// (ImDrawCallback_ResetRenderState is a special callback value used by the user to request the renderer to reset render state.)
				if (pcmd->UserCallback == ImDrawCallback_ResetRenderState)
					agk::SetScissor(0, 0, 0, 0);
				else
					pcmd->UserCallback(cmd_list, pcmd);
			}
			else {
				// Project scissor/clipping rectangles into framebuffer space
				ImVec2 clip_min((pcmd->ClipRect.x - clip_off.x) * clip_scale.x, (pcmd->ClipRect.y - clip_off.y) * clip_scale.y);
				ImVec2 clip_max((pcmd->ClipRect.z - clip_off.x) * clip_scale.x, (pcmd->ClipRect.w - clip_off.y) * clip_scale.y);
				if (clip_min.x < 0.0f) { clip_min.x = 0.0f; }
				if (clip_min.y < 0.0f) { clip_min.y = 0.0f; }
				if (clip_max.x > (float)fb_width) { clip_max.x = (float)fb_width; }
				if (clip_max.y > (float)fb_height) { clip_max.y = (float)fb_height; }
				if (clip_max.x <= clip_min.x || clip_max.y <= clip_min.y)
					continue;

				m_pRenderer->LoadFromMemory(
					cmd_list->VtxBuffer.Size,
					(float*)vtx, (float*)col, (float*)uv,
					pcmd->ElemCount,
					(USHORT*)idx_buffer + pcmd->IdxOffset);

				agk::SetScissor(clip_min.x, clip_min.y, clip_max.x, clip_max.y);

				m_pRenderer->SetImage((cImage*)pcmd->GetTexID());
				m_pRenderer->SetPosition(0, 0);
				m_pRenderer->SetScale(m_fScaleUI, m_fScaleUI);
				m_pRenderer->Update();
				m_pRenderer->Draw();
			}
		}

		delete[] vtx;
		delete[] uv;
		delete[] col;
	}
	agk::SetScissor(0, 0, 0, 0);
}

bool ImGui_DeviceAGK::ProcessInputs()
{
	ImGuiIO& io = ImGui::GetIO();
	char* char_buf = agk::GetCharBuffer();
	int last_key = agk::GetRawLastKey();

	if (IsMouseMoved()) {
		io.AddMouseSourceEvent(ImGuiMouseSource_Mouse);
		io.AddMousePosEvent(agk::GetRawMouseX()/m_fScaleUI, agk::GetRawMouseY() / m_fScaleUI);
	}

	//if (IsMouseWheel()) 
	{
		io.AddMouseSourceEvent(ImGuiMouseSource_Mouse);
		io.AddMouseWheelEvent(0.0f, agk::GetRawMouseWheelDelta());
	}

	if (agk::GetRawMouseLeftPressed() || agk::GetRawMouseLeftReleased()) {
		io.AddMouseSourceEvent(ImGuiMouseSource_Mouse);
		io.AddMouseButtonEvent(ImGuiMouseButton_Left, agk::GetRawMouseLeftPressed());
	}

	if (agk::GetRawMouseRightPressed() || agk::GetRawMouseRightReleased()) {
		io.AddMouseSourceEvent(ImGuiMouseSource_Mouse);
		io.AddMouseButtonEvent(ImGuiMouseButton_Right, agk::GetRawMouseRightPressed());
	}

	if (agk::GetRawMouseMiddlePressed() || agk::GetRawMouseMiddleReleased()) {
		io.AddMouseSourceEvent(ImGuiMouseSource_Mouse);
		io.AddMouseButtonEvent(ImGuiMouseButton_Middle, agk::GetRawMouseMiddlePressed());
	}

	if (agk::GetRawMouseFourthPressed() || agk::GetRawMouseFourthReleased()) {
		io.AddMouseSourceEvent(ImGuiMouseSource_Mouse);
		io.AddMouseButtonEvent(3, agk::GetRawMouseFourthPressed());
	}

	if (agk::GetRawMouseFifthPressed() || agk::GetRawMouseFifthReleased()) {
		io.AddMouseSourceEvent(ImGuiMouseSource_Mouse);
		io.AddMouseButtonEvent(4, agk::GetRawMouseFifthPressed());
	}

	if (char_buf[0] != 0) {
		io.AddInputCharactersUTF8(char_buf);
	}

	if (last_key > 0) {
		ImGuiKey key = (ImGuiKey)keymap[last_key];
		if (agk::GetRawKeyPressed(last_key))	io.AddKeyEvent(key, 1);
		if (agk::GetRawKeyReleased(last_key))	io.AddKeyEvent(key, 0);
	}

	if (agk::GetRawKeyPressed(16))		io.AddKeyEvent((ImGuiKey)keymap[16], 1);
	if (agk::GetRawKeyReleased(16))		io.AddKeyEvent((ImGuiKey)keymap[16], 0);
	if (agk::GetRawKeyPressed(17))		io.AddKeyEvent((ImGuiKey)keymap[17], 1);
	if (agk::GetRawKeyReleased(17))		io.AddKeyEvent((ImGuiKey)keymap[17], 0);
	if (agk::GetRawKeyPressed(18))		io.AddKeyEvent((ImGuiKey)keymap[18], 1);
	if (agk::GetRawKeyReleased(18))		io.AddKeyEvent((ImGuiKey)keymap[18], 0);

	delete[] char_buf;

	return false;
}

bool ImGui_DeviceAGK::CreateDeviceObjects()
{
	return CreateFontsTexture();
}

bool ImGui_DeviceAGK::CreateFontsTexture()
{
	// Build texture atlas
	ImGuiIO& io = ImGui::GetIO();
	unsigned char* pixels;
	int width, height;
	io.Fonts->GetTexDataAsRGBA32(&pixels, &width, &height);   // Load as RGBA 32-bit (75% of the memory is wasted, but default font is so small) because it is more likely to be compatible with user's existing shaders. If your ImTextureId represent a higher-level concept than just a GL texture id, consider calling GetTexDataAsAlpha8() instead to save on GPU memory.

	// Upload texture to graphics system
	// (Bilinear sampling is required by default. Set 'io.Fonts->Flags |= ImFontAtlasFlags_NoBakedLines' or 'style.AntiAliasedLinesUseTex = false' to allow point/nearest sampling)
	m_pFontTexture = new cImage();
	m_pFontTexture->LoadFromData(width, height, (UINT*)pixels);
	m_pFontTexture->SetMinFilter(1);
	m_pFontTexture->SetMagFilter(1);

	io.Fonts->SetTexID(m_pFontTexture);

	return true;
}

void ImGui_DeviceAGK::SetClipboardText(void* user_data, const char* text)
{
	agk::SetClipboardText(text);
}

const char* ImGui_DeviceAGK::GetClipboardText(void* user_data)
{
	return agk::GetClipboardText();
}
