#include "EngineGLFW.h"
#include "ofAppGlutWindow.h"

#if !defined(TARGET_OPENGLES) && !defined(OF_TARGET_API_VULKAN) && defined(FORCE_APPGLUT)
#include "ofLog.h"

//#include "gl/freeglut.h"
#include <GL/glut.h>    /* Header File For The GLUT Library */  

// @RemoteImgui begin
#include "imgui_remote.h"
#define VCANVAS_WIDTH  8192
#define VCANVAS_HEIGHT 8192
// @RemoteImgui end

namespace ofxImGui
{
	GLuint EngineGLUT::g_FontTexture = 0;

	static void SetClipboardText(void* user_data, const char* text)
	{
		//ofGetWindowPtr()->setClipboardString(std::string(text));
	}

	static const char* GetClipboardText(void* user_data)
	{
		//static std::string clipBuffer;
		//clipBuffer = ofGetWindowPtr()->getClipboardString();
		//return clipBuffer.c_str();
		std::cerr << "ImGui_ImplGLUT_GetClipboardText not implemented\n";
		return "";
	}

	//--------------------------------------------------------------
	void EngineGLUT::setup(bool autoDraw, const char* glsl_version)
	{
		if (isSetup) return;

		ImGuiIO& io = ImGui::GetIO();

		io.KeyMap[ImGuiKey_Tab] = 9;
		io.KeyMap[ImGuiKey_LeftArrow] = 57356;
		io.KeyMap[ImGuiKey_RightArrow] = 57358;
		io.KeyMap[ImGuiKey_UpArrow] = 57357;
		io.KeyMap[ImGuiKey_DownArrow] = 57359;
		io.KeyMap[ImGuiKey_PageUp] = GLUT_KEY_PAGE_UP;
		io.KeyMap[ImGuiKey_PageDown] = GLUT_KEY_PAGE_DOWN;
		io.KeyMap[ImGuiKey_Home] = GLUT_KEY_HOME;
		io.KeyMap[ImGuiKey_End] = GLUT_KEY_END;
		io.KeyMap[ImGuiKey_Insert] = GLUT_KEY_INSERT;
		io.KeyMap[ImGuiKey_Delete] = 127;
		io.KeyMap[ImGuiKey_Backspace] = 8;
		//io.KeyMap[ImGuiKey_Space] = GLUT_KEY_SPACE;
		io.KeyMap[ImGuiKey_Enter] = 13;
		io.KeyMap[ImGuiKey_Escape] = 22;
		io.KeyMap[ImGuiKey_A] = 1;				// ctrl-A
		io.KeyMap[ImGuiKey_C] = 3;				// ctrl-c
		io.KeyMap[ImGuiKey_V] = 22;				// ctrl-V
		io.KeyMap[ImGuiKey_X] = 24;				// ctrl-X
		io.KeyMap[ImGuiKey_Y] = 25;				// ctrl-Y
		io.KeyMap[ImGuiKey_Z] = 26;				// ctrl-Z

		io.RenderDrawListsFn = fixedDrawData;

		io.SetClipboardTextFn = SetClipboardText;
		io.GetClipboardTextFn = GetClipboardText;

		if (autoDraw)
		{
			createDeviceObjects();
		}

		// Override listeners
		ofAddListener(ofEvents().mousePressed, this, &EngineGLUT::onMousePressed);
		ofAddListener(ofEvents().mouseReleased, this, &EngineGLUT::onMouseReleased);
		ofAddListener(ofEvents().keyReleased, this, &EngineGLUT::onKeyReleased);
		ofAddListener(ofEvents().keyPressed, this, &EngineGLUT::onKeyPressed);

		// BaseEngine listeners
		ofAddListener(ofEvents().mouseDragged, (BaseEngine*)this, &BaseEngine::onMouseDragged);
		ofAddListener(ofEvents().mouseScrolled, (BaseEngine*)this, &BaseEngine::onMouseScrolled);
		ofAddListener(ofEvents().windowResized, (BaseEngine*)this, &BaseEngine::onWindowResized);

		isSetup = true;
    // @RemoteImgui begin
    ImGui::RemoteInit("0.0.0.0", 7002); // local host, local port
    //ImGui::GetStyle().WindowRounding = 0.f; // no rounding uses less bandwidth
    io.DisplaySize = ImVec2((float)VCANVAS_WIDTH, (float)VCANVAS_HEIGHT);
    // @RemoteImgui end

	}

	//--------------------------------------------------------------
	void EngineGLUT::exit()
	{
		if (!isSetup) return;

		// Override listeners
		ofRemoveListener(ofEvents().mousePressed, this, &EngineGLUT::onMousePressed);
		ofRemoveListener(ofEvents().mouseReleased, this, &EngineGLUT::onMouseReleased);
		ofRemoveListener(ofEvents().keyReleased, this, &EngineGLUT::onKeyReleased);
		ofRemoveListener(ofEvents().keyPressed, this, &EngineGLUT::onKeyPressed);

		// Base class listeners
		ofRemoveListener(ofEvents().mouseDragged, (BaseEngine*)this, &BaseEngine::onMouseDragged);
		ofRemoveListener(ofEvents().mouseScrolled, (BaseEngine*)this, &BaseEngine::onMouseScrolled);
		ofRemoveListener(ofEvents().windowResized, (BaseEngine*)this, &BaseEngine::onWindowResized);

		invalidateDeviceObjects();

		isSetup = false;
	}

	//--------------------------------------------------------------
	void EngineGLUT::draw()
	{
		fixedDrawData(ImGui::GetDrawData());
	}

	//--------------------------------------------------------------
	void remapToGLFWConvention(int& button)
	{
		switch (button)
		{

		case 0:
		{
			break;
		}
		case 1:
		{
			button = 2;
			break;
		}
		case 2:
		{
			button = 1;
			break;
		}
		}
	}

	//--------------------------------------------------------------
	void EngineGLUT::onMousePressed(ofMouseEventArgs& event)
	{
		int button = event.button;
		if (button >= 0 && button < 5)
		{
			remapToGLFWConvention(button);
			mousePressed[button] = true;
		}
	}

	//--------------------------------------------------------------
	void EngineGLUT::onMouseReleased(ofMouseEventArgs& event)
	{
		int button = event.button;
		if (button >= 0 && button < 5)
		{
			remapToGLFWConvention(button);
			mousePressed[button] = false;
		}
	}

	//--------------------------------------------------------------
	void EngineGLUT::fixedDrawData(ImDrawData * draw_data)
	{
		if(draw_data == nullptr) return;
		// Avoid rendering when minimized, scale coordinates for retina displays (screen coordinates != framebuffer coordinates)
		ImGuiIO& io = ImGui::GetIO();
		int fb_width = (int)(io.DisplaySize.x * io.DisplayFramebufferScale.x);
		int fb_height = (int)(io.DisplaySize.y * io.DisplayFramebufferScale.y);
		if (fb_width == 0 || fb_height == 0)
			return;
		draw_data->ScaleClipRects(io.DisplayFramebufferScale);

		// We are using the OpenGL fixed pipeline to make the example code simpler to read!
		// Setup render state: alpha-blending enabled, no face culling, no depth testing, scissor enabled, vertex/texcoord/color pointers, polygon fill.
		GLint last_texture; glGetIntegerv(GL_TEXTURE_BINDING_2D, &last_texture);
		GLint last_polygon_mode[2]; glGetIntegerv(GL_POLYGON_MODE, last_polygon_mode);
		GLint last_viewport[4]; glGetIntegerv(GL_VIEWPORT, last_viewport);
		GLint last_scissor_box[4]; glGetIntegerv(GL_SCISSOR_BOX, last_scissor_box);
		glPushAttrib(GL_ENABLE_BIT | GL_COLOR_BUFFER_BIT | GL_TRANSFORM_BIT);
		glEnable(GL_BLEND);
		glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
		glDisable(GL_CULL_FACE);
		glDisable(GL_DEPTH_TEST);
		glEnable(GL_SCISSOR_TEST);
		glEnableClientState(GL_VERTEX_ARRAY);
		glEnableClientState(GL_TEXTURE_COORD_ARRAY);
		glEnableClientState(GL_COLOR_ARRAY);
		glEnable(GL_TEXTURE_2D);
		glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
		//glUseProgram(0); // You may want this if using this code in an OpenGL 3+ context where shaders may be bound

		// Setup viewport, orthographic projection matrix
		glViewport(0, 0, (GLsizei)fb_width, (GLsizei)fb_height);
		glMatrixMode(GL_PROJECTION);
		glPushMatrix();
		glLoadIdentity();
		glOrtho(0.0f, io.DisplaySize.x, io.DisplaySize.y, 0.0f, -1.0f, +1.0f);
		glMatrixMode(GL_MODELVIEW);
		glPushMatrix();
		glLoadIdentity();

		// Render command lists
		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;
			glVertexPointer(2, GL_FLOAT, sizeof(ImDrawVert), (const GLvoid*)((const char*)vtx_buffer + IM_OFFSETOF(ImDrawVert, pos)));
			glTexCoordPointer(2, GL_FLOAT, sizeof(ImDrawVert), (const GLvoid*)((const char*)vtx_buffer + IM_OFFSETOF(ImDrawVert, uv)));
			glColorPointer(4, GL_UNSIGNED_BYTE, sizeof(ImDrawVert), (const GLvoid*)((const char*)vtx_buffer + IM_OFFSETOF(ImDrawVert, col)));

			for (int cmd_i = 0; cmd_i < cmd_list->CmdBuffer.Size; cmd_i++)
			{
				const ImDrawCmd* pcmd = &cmd_list->CmdBuffer[cmd_i];
				if (pcmd->UserCallback)
				{
					pcmd->UserCallback(cmd_list, pcmd);
				}
				else
				{
					glBindTexture(GL_TEXTURE_2D, (GLuint)(intptr_t)pcmd->TextureId);
					glScissor((int)pcmd->ClipRect.x, (int)(fb_height - pcmd->ClipRect.w), (int)(pcmd->ClipRect.z - pcmd->ClipRect.x), (int)(pcmd->ClipRect.w - pcmd->ClipRect.y));
					glDrawElements(GL_TRIANGLES, (GLsizei)pcmd->ElemCount, sizeof(ImDrawIdx) == 2 ? GL_UNSIGNED_SHORT : GL_UNSIGNED_INT, idx_buffer);
				}
				idx_buffer += pcmd->ElemCount;
			}
		}

		// Restore modified state
		glDisableClientState(GL_COLOR_ARRAY);
		glDisableClientState(GL_TEXTURE_COORD_ARRAY);
		glDisableClientState(GL_VERTEX_ARRAY);
		glBindTexture(GL_TEXTURE_2D, (GLuint)last_texture);
		glMatrixMode(GL_MODELVIEW);
		glPopMatrix();
		glMatrixMode(GL_PROJECTION);
		glPopMatrix();
		glPopAttrib();
		glPolygonMode(GL_FRONT, (GLenum)last_polygon_mode[0]); glPolygonMode(GL_BACK, (GLenum)last_polygon_mode[1]);
		glViewport(last_viewport[0], last_viewport[1], (GLsizei)last_viewport[2], (GLsizei)last_viewport[3]);
		glScissor(last_scissor_box[0], last_scissor_box[1], (GLsizei)last_scissor_box[2], (GLsizei)last_scissor_box[3]);
	}

	//--------------------------------------------------------------
	void EngineGLUT::onKeyReleased(ofKeyEventArgs& event)
	{
		int key = event.keycode;
		ofLogVerbose(__FUNCTION__) << key;
		ofLogVerbose(__FUNCTION__) << event.keycode;

		//std::cout << key;

		ImGuiIO& io = ImGui::GetIO();
		io.KeysDown[key] = false;

		//io.KeyCtrl = io.KeysDown[GLUT_KEY_LEFT_CONTROL] || io.KeysDown[GLUT_KEY_RIGHT_CONTROL];
		//io.KeyShift = io.KeysDown[GLUT_KEY_LEFT_SHIFT] || io.KeysDown[GLUT_KEY_RIGHT_SHIFT];
		io.KeyCtrl = glutGetModifiers() & GLUT_ACTIVE_CTRL;
		io.KeyShift = glutGetModifiers() & GLUT_ACTIVE_SHIFT;
		//io.KeyAlt = io.KeysDown[GLUT_KEY_LEFT_ALT] || io.KeysDown[GLUT_KEY_RIGHT_ALT];
		//io.KeySuper = io.KeysDown[GLUT_KEY_LEFT_SUPER] || io.KeysDown[GLUT_KEY_RIGHT_SUPER]
	}

	//--------------------------------------------------------------
	void EngineGLUT::onKeyPressed(ofKeyEventArgs& event)
	{
		int key = event.keycode;
		ofLogVerbose(__FUNCTION__) << key;
		ofLogVerbose(__FUNCTION__) << event.keycode;

		ImGuiIO& io = ImGui::GetIO();
		io.KeysDown[key] = true;

		//io.KeyCtrl = io.KeysDown[GLUT_KEY_LEFT_CONTROL] || io.KeysDown[GLUT_KEY_RIGHT_CONTROL];
		//io.KeyShift = io.KeysDown[GLUT_KEY_LEFT_SHIFT] || io.KeysDown[GLUT_KEY_RIGHT_SHIFT];
		io.KeyCtrl = glutGetModifiers() & GLUT_ACTIVE_CTRL;
		io.KeyShift = glutGetModifiers() & GLUT_ACTIVE_SHIFT;
		//io.KeyAlt = io.KeysDown[GLUT_KEY_LEFT_ALT] || io.KeysDown[GLUT_KEY_RIGHT_ALT];
		//io.KeySuper = io.KeysDown[GLUT_KEY_LEFT_SUPER] || io.KeysDown[GLUT_KEY_RIGHT_SUPER];

		//bool isNumericalKey = (key >= GLUT_KEY_KP_0) && (key <= GLUT_KEY_KP_EQUAL);
		//if (key < 22 || isNumericalKey)
		//{
			io.AddInputCharacter((unsigned short)event.codepoint);
		//}
		glutPostRedisplay();
	}

	//--------------------------------------------------------------
	bool EngineGLUT::createDeviceObjects()
	{
		// Build texture atlas
		ImGuiIO& io = ImGui::GetIO();
		unsigned char* pixels;
		int width, height;
		io.Fonts->GetTexDataAsAlpha8(&pixels, &width, &height);
		
		// Upload texture to graphics system
		GLint last_texture;
		glGetIntegerv(GL_TEXTURE_BINDING_2D, &last_texture);
		glGenTextures(1, &g_FontTexture);
		glBindTexture(GL_TEXTURE_2D, g_FontTexture);
		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
		glTexImage2D(GL_TEXTURE_2D, 0, GL_ALPHA, width, height, 0, GL_ALPHA, GL_UNSIGNED_BYTE, pixels);
		
		// Store our identifier
		io.Fonts->TexID = (void *)(intptr_t)g_FontTexture;
		
		// Restore state
		glBindTexture(GL_TEXTURE_2D, last_texture);
		
		return true;
	}

	//--------------------------------------------------------------
	void EngineGLUT::invalidateDeviceObjects()
	{
		if (g_FontTexture)
		{
			glDeleteTextures(1, &g_FontTexture);
			ImGui::GetIO().Fonts->TexID = 0;
			g_FontTexture = 0;
		}
	}
}

#endif
