

#define USE_OPENGL2
#ifdef USE_OPENGL2
#include "OpenGLWindow/SimpleOpenGL2App.h"
typedef SimpleOpenGL2App SimpleOpenGLApp;

#else
#include "OpenGLWindow/SimpleOpenGL3App.h"
typedef SimpleOpenGL3App SimpleOpenGLApp;

#endif  //USE_OPENGL2

#include "Bullet3Common/b3Quaternion.h"
#include "Bullet3Common/b3CommandLineArgs.h"
#include "assert.h"
#include <stdio.h>

char* gVideoFileName = 0;
char* gPngFileName = 0;

static b3WheelCallback sOldWheelCB = 0;
static b3ResizeCallback sOldResizeCB = 0;
static b3MouseMoveCallback sOldMouseMoveCB = 0;
static b3MouseButtonCallback sOldMouseButtonCB = 0;
static b3KeyboardCallback sOldKeyboardCB = 0;
//static b3RenderCallback sOldRenderCB = 0;

float gWidth = 1024;
float gHeight = 768;
SimpleOpenGLApp* app = 0;
float gMouseX = 0;
float gMouseY = 0;
float g_MouseWheel = 0.0f;
int g_MousePressed[3] = {0};
int g_MousePressed2[3] = {0};

#define B3_USE_IMGUI
#ifdef B3_USE_IMGUI
#include "OpenGLWindow/OpenGLInclude.h"
#include "ThirdPartyLibs/imgui/imgui.h"
static GLuint g_FontTexture = 0;

void ImGui_ImplBullet_CreateDeviceObjects()
{
	{
		GLint err = glGetError();
		assert(err == GL_NO_ERROR);
	}

	ImGuiIO& io = ImGui::GetIO();

	unsigned char* pixels;
	int width, height;
	io.Fonts->GetTexDataAsRGBA32(&pixels, &width, &height);  // Load as RGBA 32-bits (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
	GLint last_texture;
	glGetIntegerv(GL_TEXTURE_BINDING_2D, &last_texture);
	glGenTextures(1, &g_FontTexture);
	{
		GLint err = glGetError();
		assert(err == GL_NO_ERROR);
	}
	glBindTexture(GL_TEXTURE_2D, g_FontTexture);
	{
		GLint err = glGetError();
		assert(err == GL_NO_ERROR);
	}
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
	{
		GLint err = glGetError();
		assert(err == GL_NO_ERROR);
	}
	glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, pixels);

	// Store our identifier
	io.Fonts->TexID = (void*)(intptr_t)g_FontTexture;

	// Restore state
	glBindTexture(GL_TEXTURE_2D, last_texture);

	{
		GLint err = glGetError();
		assert(err == GL_NO_ERROR);
	}
}

void ImGui_ImplBullet_RenderDrawLists(ImDrawData* draw_data)
{
	{
		GLint err = glGetError();
		assert(err == GL_NO_ERROR);
	}

	glEnable(GL_COLOR_MATERIAL);
	// 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.
	GLint last_texture;
	glGetIntegerv(GL_TEXTURE_BINDING_2D, &last_texture);
	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);
	glUseProgram(0);  // You may want this if using this code in an OpenGL 3+ context

	{
		GLint err = glGetError();
		assert(err == GL_NO_ERROR);
	}

	// 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();

	{
		GLint err = glGetError();
		assert(err == GL_NO_ERROR);
	}

// Render command lists
#define OFFSETOF(TYPE, ELEMENT) ((size_t) & (((TYPE*)0)->ELEMENT))
	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 + OFFSETOF(ImDrawVert, pos)));
		glTexCoordPointer(2, GL_FLOAT, sizeof(ImDrawVert), (const GLvoid*)((const char*)vtx_buffer + OFFSETOF(ImDrawVert, uv)));
		glColorPointer(4, GL_UNSIGNED_BYTE, sizeof(ImDrawVert), (const GLvoid*)((const char*)vtx_buffer + 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;
		}
	}
#undef OFFSETOF

	// 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();
	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]);
}
#endif  //B3_USE_IMGUI

void MyWheelCallback(float deltax, float deltay)
{
	g_MouseWheel += deltax + deltay;
	if (sOldWheelCB)
		sOldWheelCB(deltax, deltay);
}
void MyResizeCallback(float width, float height)
{
	gWidth = width;
	gHeight = height;

	if (sOldResizeCB)
		sOldResizeCB(width, height);
}
void MyMouseMoveCallback(float x, float y)
{
	printf("Mouse Move: %f, %f\n", x, y);
	gMouseX = x;
	gMouseY = y;

	if (sOldMouseMoveCB)
		sOldMouseMoveCB(x, y);
}
void MyMouseButtonCallback(int button, int state, float x, float y)
{
	gMouseX = x;
	gMouseY = y;
	{
		if (button >= 0 && button < 3)
		{
			if (state)
			{
				g_MousePressed[button] = state;
			}
			g_MousePressed2[button] = state;
		}
	}

	if (sOldMouseButtonCB)
		sOldMouseButtonCB(button, state, x, y);
}

void MyKeyboardCallback(int keycode, int state)
{
	//keycodes are in examples/CommonInterfaces/CommonWindowInterface.h
	//for example B3G_ESCAPE for escape key
	//state == 1 for pressed, state == 0 for released.
	// use app->m_window->isModifiedPressed(...) to check for shift, escape and alt keys
	printf("MyKeyboardCallback received key:%c in state %d\n", keycode, state);
	if (sOldKeyboardCB)
		sOldKeyboardCB(keycode, state);
}

bool ImGui_ImplGlfw_Init()
{
#if 0
    ImGuiIO& io = ImGui::GetIO();
    io.KeyMap[ImGuiKey_Tab] = GLFW_KEY_TAB;                     // Keyboard mapping. ImGui will use those indices to peek into the io.KeyDown[] array.
    io.KeyMap[ImGuiKey_LeftArrow] = GLFW_KEY_LEFT;
    io.KeyMap[ImGuiKey_RightArrow] = GLFW_KEY_RIGHT;
    io.KeyMap[ImGuiKey_UpArrow] = GLFW_KEY_UP;
    io.KeyMap[ImGuiKey_DownArrow] = GLFW_KEY_DOWN;
    io.KeyMap[ImGuiKey_PageUp] = GLFW_KEY_PAGE_UP;
    io.KeyMap[ImGuiKey_PageDown] = GLFW_KEY_PAGE_DOWN;
    io.KeyMap[ImGuiKey_Home] = GLFW_KEY_HOME;
    io.KeyMap[ImGuiKey_End] = GLFW_KEY_END;
    io.KeyMap[ImGuiKey_Delete] = GLFW_KEY_DELETE;
    io.KeyMap[ImGuiKey_Backspace] = GLFW_KEY_BACKSPACE;
    io.KeyMap[ImGuiKey_Enter] = GLFW_KEY_ENTER;
    io.KeyMap[ImGuiKey_Escape] = GLFW_KEY_ESCAPE;
    io.KeyMap[ImGuiKey_A] = GLFW_KEY_A;
    io.KeyMap[ImGuiKey_C] = GLFW_KEY_C;
    io.KeyMap[ImGuiKey_V] = GLFW_KEY_V;
    io.KeyMap[ImGuiKey_X] = GLFW_KEY_X;
    io.KeyMap[ImGuiKey_Y] = GLFW_KEY_Y;
    io.KeyMap[ImGuiKey_Z] = GLFW_KEY_Z;

    io.RenderDrawListsFn = ImGui_ImplGlfw_RenderDrawLists;      // Alternatively you can set this to NULL and call ImGui::GetDrawData() after ImGui::Render() to get the same ImDrawData pointer.
    io.SetClipboardTextFn = ImGui_ImplGlfw_SetClipboardText;
    io.GetClipboardTextFn = ImGui_ImplGlfw_GetClipboardText;
    io.ClipboardUserData = g_Window;
#ifdef _WIN32
    io.ImeWindowHandle = glfwGetWin32Window(g_Window);
#endif

#endif

	return true;
}

int main(int argc, char* argv[])
{
	{
		b3CommandLineArgs myArgs(argc, argv);

		app = new SimpleOpenGLApp("SimpleOpenGLApp", gWidth, gHeight);

		{
			GLint err = glGetError();
			assert(err == GL_NO_ERROR);
		}

		ImGui::CreateContext();

		{
			GLint err = glGetError();
			assert(err == GL_NO_ERROR);
		}

		app->m_renderer->getActiveCamera()->setCameraDistance(13);
		app->m_renderer->getActiveCamera()->setCameraPitch(0);
		app->m_renderer->getActiveCamera()->setCameraTargetPosition(0, 0, 0);

		{
			GLint err = glGetError();
			assert(err == GL_NO_ERROR);
		}

		sOldKeyboardCB = app->m_window->getKeyboardCallback();
		app->m_window->setKeyboardCallback(MyKeyboardCallback);
		sOldMouseMoveCB = app->m_window->getMouseMoveCallback();
		app->m_window->setMouseMoveCallback(MyMouseMoveCallback);
		sOldMouseButtonCB = app->m_window->getMouseButtonCallback();
		app->m_window->setMouseButtonCallback(MyMouseButtonCallback);
		sOldWheelCB = app->m_window->getWheelCallback();
		app->m_window->setWheelCallback(MyWheelCallback);
		sOldResizeCB = app->m_window->getResizeCallback();
		app->m_window->setResizeCallback(MyResizeCallback);

		myArgs.GetCmdLineArgument("mp4_file", gVideoFileName);
		if (gVideoFileName)
			app->dumpFramesToVideo(gVideoFileName);

		myArgs.GetCmdLineArgument("png_file", gPngFileName);
		char fileName[1024];

		int textureWidth = 128;
		int textureHeight = 128;

		unsigned char* image = new unsigned char[textureWidth * textureHeight * 4];

		int textureHandle = app->m_renderer->registerTexture(image, textureWidth, textureHeight);

		int cubeIndex = app->registerCubeShape(1, 1, 1);

		b3Vector3 pos = b3MakeVector3(0, 0, 0);
		b3Quaternion orn(0, 0, 0, 1);
		b3Vector3 color = b3MakeVector3(1, 0, 0);
		b3Vector3 scaling = b3MakeVector3(1, 1, 1);
		{
			GLint err = glGetError();
			assert(err == GL_NO_ERROR);
		}

		app->m_renderer->registerGraphicsInstance(cubeIndex, pos, orn, color, scaling);
		app->m_renderer->writeTransforms();

		{
			bool dark = false;
			ImGuiStyle& style = ImGui::GetStyle();
			if (dark)
			{
				ImGui::StyleColorsDark(&style);
			}
			else
			{
				ImGui::StyleColorsLight(&style);
			}
		}

		{
			GLint err = glGetError();
			assert(err == GL_NO_ERROR);
		}
		do
		{
			static int frameCount = 0;
			frameCount++;
			if (gPngFileName)
			{
				printf("gPngFileName=%s\n", gPngFileName);

				sprintf(fileName, "%s%d.png", gPngFileName, frameCount++);
				app->dumpNextFrameToPng(fileName);
			}

			{
				GLint err = glGetError();
				assert(err == GL_NO_ERROR);
			}

			//update the texels of the texture using a simple pattern, animated using frame index
			for (int y = 0; y < textureHeight; ++y)
			{
				const int t = (y + frameCount) >> 4;
				unsigned char* pi = image + y * textureWidth * 3;
				for (int x = 0; x < textureWidth; ++x)
				{
					const int s = x >> 4;
					const unsigned char b = 180;
					unsigned char c = b + ((s + (t & 1)) & 1) * (255 - b);
					pi[0] = pi[1] = pi[2] = pi[3] = c;
					pi += 3;
				}
			}

			{
				GLint err = glGetError();
				assert(err == GL_NO_ERROR);
			}

			app->m_renderer->activateTexture(textureHandle);
			app->m_renderer->updateTexture(textureHandle, image);

			{
				GLint err = glGetError();
				assert(err == GL_NO_ERROR);
			}

			//float color[4] = { 255, 1, 1, 1 };
			//app->m_primRenderer->drawTexturedRect(100, 200, gWidth / 2 - 50, gHeight / 2 - 50, color, 0, 0, 1, 1, true);

			app->m_renderer->init();
			app->m_renderer->updateCamera(1);

			{
				GLint err = glGetError();
				assert(err == GL_NO_ERROR);
			}

			app->m_renderer->renderScene();
			{
				GLint err = glGetError();
				assert(err == GL_NO_ERROR);
			}
			//app->drawGrid();
			{
				GLint err = glGetError();
				assert(err == GL_NO_ERROR);
			}

			char bla[1024];
			sprintf(bla, "Simple test frame %d", frameCount);

			//app->drawText(bla, 10, 10);

#ifdef B3_USE_IMGUI
			{
				bool show_test_window = true;
				bool show_another_window = false;
				ImVec4 clear_color = ImColor(114, 144, 154);

				// Start the frame
				ImGuiIO& io = ImGui::GetIO();
				if (!g_FontTexture)
					ImGui_ImplBullet_CreateDeviceObjects();

				io.DisplaySize = ImVec2((float)gWidth, (float)gHeight);
				io.DisplayFramebufferScale = ImVec2(gWidth > 0 ? ((float)1.) : 0, gHeight > 0 ? ((float)1.) : 0);
				io.DeltaTime = (float)(1.0f / 60.0f);
				io.MousePos = ImVec2((float)gMouseX, (float)gMouseY);
				io.RenderDrawListsFn = ImGui_ImplBullet_RenderDrawLists;

				for (int i = 0; i < 3; i++)
				{
					io.MouseDown[i] = g_MousePressed[i] | g_MousePressed2[i];
					g_MousePressed[i] = false;
				}

				io.MouseWheel = g_MouseWheel;

				ImGui::NewFrame();

				{
					GLint err = glGetError();
					assert(err == GL_NO_ERROR);
				}

				{
					{static float f = 0.0f;
				static int counter = 0;
				ImGui::Text("Hello, world!");                            // Display some text (you can use a format string too)
				ImGui::SliderFloat("float", &f, 0.0f, 1.0f);             // Edit 1 float using a slider from 0.0f to 1.0f
				ImGui::ColorEdit3("clear color", (float*)&clear_color);  // Edit 3 floats representing a color

				//ImGui::Checkbox("Demo Window", &show_demo_window);      // Edit bools storing our windows open/close state
				ImGui::Checkbox("Another Window", &show_another_window);

				if (ImGui::Button("Button"))  // Buttons return true when clicked (NB: most widgets return true when edited/activated)
					counter++;
				ImGui::SameLine();
				ImGui::Text("counter = %d", counter);

				ImGui::Text("Application average %.3f ms/frame (%.1f FPS)", 1000.0f / ImGui::GetIO().Framerate, ImGui::GetIO().Framerate);
			}
		}
		//ImGui::ShowTestWindow();
		//ImGui::ShowMetricsWindow();
		{
			GLint err = glGetError();
			assert(err == GL_NO_ERROR);
		}

#if 0
				static float f = 0.0f;
				ImGui::Text("Hello, world!");
				ImGui::SliderFloat("float", &f, 0.0f, 1.0f);
				ImGui::ColorEdit3("clear color", (float*)&clear_color);
				if (ImGui::Button("Test Window")) show_test_window ^= 1;
				if (ImGui::Button("Another Window")) show_another_window ^= 1;
				ImGui::Text("Application average %.3f ms/frame (%.1f FPS)", 1000.0f / ImGui::GetIO().Framerate, ImGui::GetIO().Framerate);
#endif
		ImGui::Render();
	}
#endif  //B3_USE_IMGUI

	{
		GLint err = glGetError();
		assert(err == GL_NO_ERROR);
	}

	app->swapBuffer();
}
while (!app->m_window->requestedExit())
	;

ImGui::DestroyContext();

delete app;

delete[] image;
}
return 0;
}
