#ifndef NO_OPENGL3

#include "SimpleOpenGL3App.h"
#include "ShapeData.h"

#ifdef BT_USE_EGL
#include "EGLOpenGLWindow.h"
#else
#endif  //BT_USE_EGL

#ifdef B3_USE_GLFW
#include "GLFWOpenGLWindow.h"
#else
#ifdef __APPLE__
#include "MacOpenGLWindow.h"
#else

#ifdef _WIN32
#include "Win32OpenGLWindow.h"
#else
//let's cross the fingers it is Linux/X11
#include "X11OpenGLWindow.h"
#define BT_USE_X11  // for runtime backend selection, move to build?
#endif              //_WIN32
#endif              //__APPLE__
#endif              //B3_USE_GLFW

#include <stdio.h>

#include "GLPrimitiveRenderer.h"
#include "GLInstancingRenderer.h"

#include "Bullet3Common/b3Vector3.h"
#include "Bullet3Common/b3Logging.h"

#include "fontstash.h"
#include "TwFonts.h"
#include "opengl_fontstashcallbacks.h"
#include <assert.h>
#include "GLRenderToTexture.h"
#include "Bullet3Common/b3Quaternion.h"
#include <string.h>  //memset
#ifdef _WIN32
#define popen _popen
#define pclose _pclose
#endif  // _WIN32

struct SimpleInternalData
{
	GLuint m_fontTextureId;
	GLuint m_largeFontTextureId;
	struct sth_stash* m_fontStash;
	struct sth_stash* m_fontStash2;

	RenderCallbacks* m_renderCallbacks;
	RenderCallbacks* m_renderCallbacks2;

	int m_droidRegular;
	int m_droidRegular2;
	int m_textureId;

	const char* m_frameDumpPngFileName;
	FILE* m_ffmpegFile;
	GLRenderToTexture* m_renderTexture;
	void* m_userPointer;
	int m_upAxis;  //y=1 or z=2 is supported
	int m_customViewPortWidth;
	int m_customViewPortHeight;
	int m_mp4Fps;

	SimpleInternalData()
		: m_fontTextureId(0),
		  m_largeFontTextureId(0),
		  m_fontStash(0),
		  m_fontStash2(0),
		  m_renderCallbacks(0),
		  m_renderCallbacks2(0),
		  m_droidRegular(0),
		  m_droidRegular2(0),
		  m_textureId(-1),
		  m_frameDumpPngFileName(0),
		  m_ffmpegFile(0),
		  m_renderTexture(0),
		  m_userPointer(0),
		  m_upAxis(1),
		  m_customViewPortWidth(-1),
		  m_customViewPortHeight(-1),
		  m_mp4Fps(60)
	{
	}
};

static SimpleOpenGL3App* gApp = 0;

static void SimpleResizeCallback(float widthf, float heightf)
{
	int width = (int)widthf;
	int height = (int)heightf;
	if (gApp && gApp->m_instancingRenderer)
		gApp->m_instancingRenderer->resize(width, height);

	if (gApp && gApp->m_primRenderer)
		gApp->m_primRenderer->setScreenSize(width, height);
}

static void SimpleKeyboardCallback(int key, int state)
{
	if (key == B3G_ESCAPE && gApp && gApp->m_window)
	{
		gApp->m_window->setRequestExit();
	}
	else
	{
		//gApp->defaultKeyboardCallback(key,state);
	}
}

void SimpleMouseButtonCallback(int button, int state, float x, float y)
{
	gApp->defaultMouseButtonCallback(button, state, x, y);
}
void SimpleMouseMoveCallback(float x, float y)
{
	gApp->defaultMouseMoveCallback(x, y);
}

void SimpleWheelCallback(float deltax, float deltay)
{
	gApp->defaultWheelCallback(deltax, deltay);
}

static GLuint BindFont(const CTexFont* _Font)
{
	GLuint TexID = 0;
	glGenTextures(1, &TexID);
	glBindTexture(GL_TEXTURE_2D, TexID);
	glPixelStorei(GL_UNPACK_SWAP_BYTES, GL_FALSE);
	glPixelStorei(GL_UNPACK_LSB_FIRST, GL_FALSE);
	glPixelStorei(GL_UNPACK_ROW_LENGTH, 0);
	glPixelStorei(GL_UNPACK_SKIP_ROWS, 0);
	glPixelStorei(GL_UNPACK_SKIP_PIXELS, 0);
	glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
	glTexImage2D(GL_TEXTURE_2D, 0, GL_RED, _Font->m_TexWidth, _Font->m_TexHeight, 0, GL_RED, GL_UNSIGNED_BYTE, _Font->m_TexBytes);
	glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
	glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
	glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
	glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
	glBindTexture(GL_TEXTURE_2D, 0);

	return TexID;
}

//static unsigned int s_indexData[INDEX_COUNT];
//static GLuint s_indexArrayObject, s_indexBuffer;
//static GLuint s_vertexArrayObject,s_vertexBuffer;

extern unsigned char OpenSansData[];

struct MyRenderCallbacks : public RenderCallbacks
{
	GLInstancingRenderer* m_instancingRenderer;

	b3AlignedObjectArray<unsigned char> m_rgbaTexture;
	float m_color[4];
	float m_worldPosition[3];
	float m_worldOrientation[4];

	int m_textureIndex;

	MyRenderCallbacks(GLInstancingRenderer* instancingRenderer)
		: m_instancingRenderer(instancingRenderer),
		  m_textureIndex(-1)
	{
		for (int i = 0; i < 4; i++)
		{
			m_color[i] = 1;
			m_worldOrientation[i] = 0;
		}
		m_worldPosition[0] = 0;
		m_worldPosition[1] = 0;
		m_worldPosition[2] = 0;

		m_worldOrientation[0] = 0;
		m_worldOrientation[1] = 0;
		m_worldOrientation[2] = 0;
		m_worldOrientation[3] = 1;
	}
	virtual ~MyRenderCallbacks()
	{
		m_rgbaTexture.clear();
	}

	virtual void setWorldPosition(float pos[3])
	{
		for (int i = 0; i < 3; i++)
		{
			m_worldPosition[i] = pos[i];
		}
	}

	virtual void setWorldOrientation(float orn[4])
	{
		for (int i = 0; i < 4; i++)
		{
			m_worldOrientation[i] = orn[i];
		}
	}

	virtual void setColorRGBA(float color[4])
	{
		for (int i = 0; i < 4; i++)
		{
			m_color[i] = color[i];
		}
	}
	virtual void updateTexture(sth_texture* texture, sth_glyph* glyph, int textureWidth, int textureHeight)
	{
		if (glyph)
		{
			m_rgbaTexture.resize(textureWidth * textureHeight * 3);
			for (int i = 0; i < textureWidth * textureHeight; i++)
			{
				m_rgbaTexture[i * 3 + 0] = texture->m_texels[i];
				m_rgbaTexture[i * 3 + 1] = texture->m_texels[i];
				m_rgbaTexture[i * 3 + 2] = texture->m_texels[i];
			}
			bool flipPixelsY = false;
			m_instancingRenderer->updateTexture(m_textureIndex, &m_rgbaTexture[0], flipPixelsY);
		}
		else
		{
			if (textureWidth && textureHeight)
			{
				texture->m_texels = (unsigned char*)malloc(textureWidth * textureHeight);
				memset(texture->m_texels, 0, textureWidth * textureHeight);
				if (m_textureIndex < 0)
				{
					m_rgbaTexture.resize(textureWidth * textureHeight * 3);
					bool flipPixelsY = false;
					m_textureIndex = m_instancingRenderer->registerTexture(&m_rgbaTexture[0], textureWidth, textureHeight, flipPixelsY);

					int strideInBytes = 9 * sizeof(float);
					int numVertices = sizeof(cube_vertices_textured) / strideInBytes;
					int numIndices = sizeof(cube_indices) / sizeof(int);

					float halfExtentsX = 1;
					float halfExtentsY = 1;
					float halfExtentsZ = 1;
					float textureScaling = 4;

					b3AlignedObjectArray<GfxVertexFormat1> verts;
					verts.resize(numVertices);
					for (int i = 0; i < numVertices; i++)
					{
						verts[i].x = halfExtentsX * cube_vertices_textured[i * 9];
						verts[i].y = halfExtentsY * cube_vertices_textured[i * 9 + 1];
						verts[i].z = halfExtentsZ * cube_vertices_textured[i * 9 + 2];
						verts[i].w = cube_vertices_textured[i * 9 + 3];
						verts[i].nx = cube_vertices_textured[i * 9 + 4];
						verts[i].ny = cube_vertices_textured[i * 9 + 5];
						verts[i].nz = cube_vertices_textured[i * 9 + 6];
						verts[i].u = cube_vertices_textured[i * 9 + 7] * textureScaling;
						verts[i].v = cube_vertices_textured[i * 9 + 8] * textureScaling;
					}

					int shapeId = m_instancingRenderer->registerShape(&verts[0].x, numVertices, cube_indices, numIndices, B3_GL_TRIANGLES, m_textureIndex);
					b3Vector3 pos = b3MakeVector3(0, 0, 0);
					b3Quaternion orn(0, 0, 0, 1);
					b3Vector4 color = b3MakeVector4(1, 1, 1, 1);
					b3Vector3 scaling = b3MakeVector3(.1, .1, .1);
					//m_instancingRenderer->registerGraphicsInstance(shapeId, pos, orn, color, scaling);
					m_instancingRenderer->writeTransforms();
				}
				else
				{
					b3Assert(0);
				}
			}
			else
			{
				delete texture->m_texels;
				texture->m_texels = 0;
				//there is no m_instancingRenderer->freeTexture (yet), all textures are released at reset/deletion of the renderer
			}
		}
	}
	virtual void render(sth_texture* texture)
	{
		int index = 0;

		float width = 1;
		b3AlignedObjectArray<unsigned int> indices;
		indices.resize(texture->nverts);
		for (int i = 0; i < indices.size(); i++)
		{
			indices[i] = i;
		}

		m_instancingRenderer->drawTexturedTriangleMesh(m_worldPosition, m_worldOrientation, &texture->newverts[0].position.p[0], texture->nverts, &indices[0], indices.size(), m_color, m_textureIndex);
	}
};

static void printGLString(const char* name, GLenum s)
{
	const char* v = (const char*)glGetString(s);
	printf("%s = %s\n", name, v);
}

bool sOpenGLVerbose = true;

SimpleOpenGL3App::SimpleOpenGL3App(const char* title, int width, int height, bool allowRetina, int windowType, int renderDevice, int maxNumObjectCapacity, int maxShapeCapacityInBytes)
{
	gApp = this;

	m_data = new SimpleInternalData;

	if (windowType == 0)
	{
		m_window = new b3gDefaultOpenGLWindow();
	}
	else if (windowType == 1)
	{
#ifdef BT_USE_X11
		m_window = new X11OpenGLWindow();
#else
		b3Warning("X11 requires Linux. Loading default window instead. \n");
		m_window = new b3gDefaultOpenGLWindow();
#endif
	}
	else if (windowType == 2)
	{
#ifdef BT_USE_EGL
		m_window = new EGLOpenGLWindow();
#else
		b3Warning("EGL window requires compilation with BT_USE_EGL.\n");
		b3Warning("Loading default window instead. \n");
		m_window = new b3gDefaultOpenGLWindow();
#endif
	}
	else
	{
		b3Warning("Unknown window type %d must be (0=default, 1=X11, 2=EGL).\n", windowType);
		b3Warning("Loading default window instead. \n");
		m_window = new b3gDefaultOpenGLWindow();
	}

	m_window->setAllowRetina(allowRetina);

	b3gWindowConstructionInfo ci;
	ci.m_title = title;
	ci.m_width = width;
	ci.m_height = height;
	ci.m_renderDevice = renderDevice;
	m_window->createWindow(ci);

	m_window->setWindowTitle(title);

	b3Assert(glGetError() == GL_NO_ERROR);

	{
		printGLString("Version", GL_VERSION);
		printGLString("Vendor", GL_VENDOR);
		printGLString("Renderer", GL_RENDERER);
	}

	glClearColor(m_backgroundColorRGB[0],
				 m_backgroundColorRGB[1],
				 m_backgroundColorRGB[2],
				 1.f);

	m_window->startRendering();
	width = m_window->getWidth();
	height = m_window->getHeight();

	b3Assert(glGetError() == GL_NO_ERROR);

	//gladLoadGLLoader((GLADloadproc) glfwGetProcAddress);

#ifdef USE_GLEW
#ifndef __APPLE__
#ifndef _WIN32
#ifndef B3_USE_GLFW
	//some Linux implementations need the 'glewExperimental' to be true
	glewExperimental = GL_TRUE;
#endif  //B3_USE_GLFW
#endif  //_WIN32

#ifndef B3_USE_GLFW
	if (glewInit() != GLEW_OK)
		exit(1);            // or handle the error in a nicer way
	if (!GLEW_VERSION_2_1)  // check that the machine supports the 2.1 API.
		exit(1);            // or handle the error in a nicer way
#endif                      //B3_USE_GLFW
#endif                      //__APPLE__
#endif                      //USE_GLEW

	glGetError();  //don't remove this call, it is needed for Ubuntu

	b3Assert(glGetError() == GL_NO_ERROR);

	m_parameterInterface = 0;

	b3Assert(glGetError() == GL_NO_ERROR);

	m_instancingRenderer = new GLInstancingRenderer(maxNumObjectCapacity, maxShapeCapacityInBytes);

	m_primRenderer = new GLPrimitiveRenderer(width, height);

	m_renderer = m_instancingRenderer;
	m_window->setResizeCallback(SimpleResizeCallback);

	m_instancingRenderer->init();
	m_instancingRenderer->resize(width, height);
	m_primRenderer->setScreenSize(width, height);
	b3Assert(glGetError() == GL_NO_ERROR);

	m_instancingRenderer->InitShaders();

	m_window->setMouseMoveCallback(SimpleMouseMoveCallback);
	m_window->setMouseButtonCallback(SimpleMouseButtonCallback);
	m_window->setKeyboardCallback(SimpleKeyboardCallback);
	m_window->setWheelCallback(SimpleWheelCallback);

	TwGenerateDefaultFonts();
	m_data->m_fontTextureId = BindFont(g_DefaultNormalFont);
	m_data->m_largeFontTextureId = BindFont(g_DefaultLargeFont);

	{
		m_data->m_renderCallbacks = new OpenGL2RenderCallbacks(m_primRenderer);
		m_data->m_renderCallbacks2 = new MyRenderCallbacks(m_instancingRenderer);
		m_data->m_fontStash2 = sth_create(512, 512, m_data->m_renderCallbacks2);
		m_data->m_fontStash = sth_create(512, 512, m_data->m_renderCallbacks);  //256,256);//,1024);//512,512);

		b3Assert(glGetError() == GL_NO_ERROR);

		if (!m_data->m_fontStash)
		{
			b3Warning("Could not create stash");
			//fprintf(stderr, "Could not create stash.\n");
		}

		if (!m_data->m_fontStash2)
		{
			b3Warning("Could not create fontStash2");
		}

		unsigned char* data2 = OpenSansData;
		unsigned char* data = (unsigned char*)data2;
		if (!(m_data->m_droidRegular = sth_add_font_from_memory(m_data->m_fontStash, data)))
		{
			b3Warning("error!\n");
		}
		if (!(m_data->m_droidRegular2 = sth_add_font_from_memory(m_data->m_fontStash2, data)))
		{
			b3Warning("error!\n");
		}

		b3Assert(glGetError() == GL_NO_ERROR);
	}
}

struct sth_stash* SimpleOpenGL3App::getFontStash()
{
	return m_data->m_fontStash;
}

void SimpleOpenGL3App::drawText3D(const char* txt, float position[3], float orientation[4], float color[4], float size, int optionFlag)
{
	B3_PROFILE("SimpleOpenGL3App::drawText3D");
	float viewMat[16];
	float projMat[16];
	CommonCameraInterface* cam = m_instancingRenderer->getActiveCamera();

	cam->getCameraViewMatrix(viewMat);
	cam->getCameraProjectionMatrix(projMat);

	float camPos[4];
	cam->getCameraPosition(camPos);
	//b3Vector3 cp= b3MakeVector3(camPos[0],camPos[2],camPos[1]);
	//b3Vector3 p = b3MakeVector3(worldPosX,worldPosY,worldPosZ);
	//float dist = (cp-p).length();
	//float dv = 0;//dist/1000.f;
	//
	//printf("str = %s\n",unicodeText);

	float dx = 0;

	//int measureOnly=0;

	glEnable(GL_BLEND);
	glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
	int viewport[4] = {0, 0, m_instancingRenderer->getScreenWidth(), m_instancingRenderer->getScreenHeight()};

	float posX = position[0];
	float posY = position[1];
	float posZ = position[2];
	float winx, winy, winz;

	if (optionFlag & CommonGraphicsApp::eDrawText3D_OrtogonalFaceCamera)
	{
		if (!projectWorldCoordToScreen(position[0], position[1], position[2], viewMat, projMat, viewport, &winx, &winy, &winz))
		{
			return;
		}
		posX = winx;
		posY = m_instancingRenderer->getScreenHeight() / 2 + (m_instancingRenderer->getScreenHeight() / 2) - winy;
		posZ = 0.f;
	}

	if (optionFlag & CommonGraphicsApp::eDrawText3D_TrueType)
	{
		bool measureOnly = false;

		float fontSize = 64;  //512;//128;

		if (optionFlag & CommonGraphicsApp::eDrawText3D_OrtogonalFaceCamera)
		{
			sth_draw_text(m_data->m_fontStash,
						  m_data->m_droidRegular, fontSize, posX, posY,
						  txt, &dx, this->m_instancingRenderer->getScreenWidth(), this->m_instancingRenderer->getScreenHeight(), measureOnly, m_window->getRetinaScale(), color);
			sth_end_draw(m_data->m_fontStash);
			sth_flush_draw(m_data->m_fontStash);
		}
		else
		{
			glEnable(GL_BLEND);
			glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);

			m_data->m_renderCallbacks2->setColorRGBA(color);

			m_data->m_renderCallbacks2->setWorldPosition(position);
			m_data->m_renderCallbacks2->setWorldOrientation(orientation);

			sth_draw_text3D(m_data->m_fontStash2,
							m_data->m_droidRegular2, fontSize, 0, 0, 0,
							txt, &dx, size, color, 0);
			sth_end_draw(m_data->m_fontStash2);
			sth_flush_draw(m_data->m_fontStash2);
			glDisable(GL_BLEND);
		}
	}
	else
	{
		//float width = 0.f;
		int pos = 0;
		//float color[]={0.2f,0.2,0.2f,1.f};
		glActiveTexture(GL_TEXTURE0);
		glBindTexture(GL_TEXTURE_2D, m_data->m_largeFontTextureId);

		//float width = r.x;
		//float extraSpacing = 0.;

		float startX = posX;
		float startY = posY + g_DefaultLargeFont->m_CharHeight * size;
		float z = position[2];  //2.f*winz-1.f;//*(far

		if (optionFlag & CommonGraphicsApp::eDrawText3D_OrtogonalFaceCamera)
		{
			posX = winx;
			posY = m_instancingRenderer->getScreenHeight() / 2 + (m_instancingRenderer->getScreenHeight() / 2) - winy;
			z = 2.f * winz - 1.f;
			startY = posY - g_DefaultLargeFont->m_CharHeight * size;
		}

		while (txt[pos])
		{
			int c = txt[pos];
			//r.h = g_DefaultNormalFont->m_CharHeight;
			//r.w = g_DefaultNormalFont->m_CharWidth[c]+extraSpacing;
			float endX = startX + g_DefaultLargeFont->m_CharWidth[c] * size;
			if (optionFlag & CommonGraphicsApp::eDrawText3D_OrtogonalFaceCamera)
			{
				endX = startX + g_DefaultLargeFont->m_CharWidth[c] * size;
			}
			float endY = posY;

			//float currentColor[]={1.f,1.,0.2f,1.f};

			//	m_primRenderer->drawTexturedRect(startX, startY, endX, endY, currentColor,g_DefaultLargeFont->m_CharU0[c],g_DefaultLargeFont->m_CharV0[c],g_DefaultLargeFont->m_CharU1[c],g_DefaultLargeFont->m_CharV1[c]);
			float u0 = g_DefaultLargeFont->m_CharU0[c];
			float u1 = g_DefaultLargeFont->m_CharU1[c];
			float v0 = g_DefaultLargeFont->m_CharV0[c];
			float v1 = g_DefaultLargeFont->m_CharV1[c];
			//float color[4] = {currentColor[0],currentColor[1],currentColor[2],currentColor[3]};
			float x0 = startX;
			float x1 = endX;
			float y0 = startY;
			float y1 = endY;
			int screenWidth = m_instancingRenderer->getScreenWidth();
			int screenHeight = m_instancingRenderer->getScreenHeight();

			float identity[16] = {1, 0, 0, 0,
								  0, 1, 0, 0,
								  0, 0, 1, 0,
								  0, 0, 0, 1};
			if (optionFlag & CommonGraphicsApp::eDrawText3D_OrtogonalFaceCamera)
			{
				PrimVertex vertexData[4] = {
					PrimVertex(PrimVec4(-1.f + 2.f * x0 / float(screenWidth), 1.f - 2.f * y0 / float(screenHeight), z, 1.f), PrimVec4(color[0], color[1], color[2], color[3]), PrimVec2(u0, v0)),
					PrimVertex(PrimVec4(-1.f + 2.f * x0 / float(screenWidth), 1.f - 2.f * y1 / float(screenHeight), z, 1.f), PrimVec4(color[0], color[1], color[2], color[3]), PrimVec2(u0, v1)),
					PrimVertex(PrimVec4(-1.f + 2.f * x1 / float(screenWidth), 1.f - 2.f * y1 / float(screenHeight), z, 1.f), PrimVec4(color[0], color[1], color[2], color[3]), PrimVec2(u1, v1)),
					PrimVertex(PrimVec4(-1.f + 2.f * x1 / float(screenWidth), 1.f - 2.f * y0 / float(screenHeight), z, 1.f), PrimVec4(color[0], color[1], color[2], color[3]), PrimVec2(u1, v0))};
				m_primRenderer->drawTexturedRect3D(vertexData[0], vertexData[1], vertexData[2], vertexData[3], identity, identity, false);
			}
			else
			{
				PrimVertex vertexData[4] = {
					PrimVertex(PrimVec4(x0, y0, z, 1.f), PrimVec4(color[0], color[1], color[2], color[3]), PrimVec2(u0, v0)),
					PrimVertex(PrimVec4(x0, y1, z, 1.f), PrimVec4(color[0], color[1], color[2], color[3]), PrimVec2(u0, v1)),
					PrimVertex(PrimVec4(x1, y1, z, 1.f), PrimVec4(color[0], color[1], color[2], color[3]), PrimVec2(u1, v1)),
					PrimVertex(PrimVec4(x1, y0, z, 1.f), PrimVec4(color[0], color[1], color[2], color[3]), PrimVec2(u1, v0))};

				m_primRenderer->drawTexturedRect3D(vertexData[0], vertexData[1], vertexData[2], vertexData[3], viewMat, projMat, false);
			}
			//DrawTexturedRect(0,r,g_DefaultNormalFont->m_CharU0[c],g_DefaultNormalFont->m_CharV0[c],g_DefaultNormalFont->m_CharU1[c],g_DefaultNormalFont->m_CharV1[c]);
			//	DrawFilledRect(r);

			startX = endX;
			//startY = endY;

			pos++;
		}
		glBindTexture(GL_TEXTURE_2D, 0);
	}

	glDisable(GL_BLEND);
}

void SimpleOpenGL3App::drawText3D(const char* txt, float worldPosX, float worldPosY, float worldPosZ, float size1)
{
	float position[3] = {worldPosX, worldPosY, worldPosZ};
	float orientation[4] = {0, 0, 0, 1};
	float color[4] = {0, 0, 0, 1};
	int optionFlags = CommonGraphicsApp::eDrawText3D_OrtogonalFaceCamera;
	drawText3D(txt, position, orientation, color, size1, optionFlags);
}

void SimpleOpenGL3App::drawText(const char* txt, int posXi, int posYi, float size, float colorRGBA[4])
{
	float posX = (float)posXi;
	float posY = (float)posYi;

	//
	//printf("str = %s\n",unicodeText);

	float dx = 0;

	//int measureOnly=0;

	glEnable(GL_BLEND);
	glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);

	if (1)  //m_useTrueTypeFont)
	{
		bool measureOnly = false;

		float fontSize = 64 * size;  //512;//128;
		sth_draw_text(m_data->m_fontStash,
					  m_data->m_droidRegular, fontSize, posX, posY,
					  txt, &dx, this->m_instancingRenderer->getScreenWidth(),
					  this->m_instancingRenderer->getScreenHeight(),
					  measureOnly,
					  m_window->getRetinaScale(), colorRGBA);

		sth_end_draw(m_data->m_fontStash);
		sth_flush_draw(m_data->m_fontStash);
	}
	else
	{
		//float width = 0.f;
		int pos = 0;
		//float color[]={0.2f,0.2,0.2f,1.f};
		glActiveTexture(GL_TEXTURE0);
		glBindTexture(GL_TEXTURE_2D, m_data->m_largeFontTextureId);

		//float width = r.x;
		//float extraSpacing = 0.;

		float startX = posX;
		float startY = posY;

		while (txt[pos])
		{
			int c = txt[pos];
			//r.h = g_DefaultNormalFont->m_CharHeight;
			//r.w = g_DefaultNormalFont->m_CharWidth[c]+extraSpacing;
			float endX = startX + g_DefaultLargeFont->m_CharWidth[c];
			float endY = startY + g_DefaultLargeFont->m_CharHeight;

			float currentColor[] = {0.2f, 0.2, 0.2f, 1.f};

			m_primRenderer->drawTexturedRect(startX, startY, endX, endY, currentColor, g_DefaultLargeFont->m_CharU0[c], g_DefaultLargeFont->m_CharV0[c], g_DefaultLargeFont->m_CharU1[c], g_DefaultLargeFont->m_CharV1[c]);

			//DrawTexturedRect(0,r,g_DefaultNormalFont->m_CharU0[c],g_DefaultNormalFont->m_CharV0[c],g_DefaultNormalFont->m_CharU1[c],g_DefaultNormalFont->m_CharV1[c]);
			//	DrawFilledRect(r);

			startX = endX;
			//startY = endY;

			pos++;
		}
		glBindTexture(GL_TEXTURE_2D, 0);
	}

	glDisable(GL_BLEND);
}

void SimpleOpenGL3App::drawTexturedRect(float x0, float y0, float x1, float y1, float color[4], float u0, float v0, float u1, float v1, int useRGBA)
{
	glEnable(GL_BLEND);
	glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);

	m_primRenderer->drawTexturedRect(x0, y0, x1, y1, color, u0, v0, u1, v1, useRGBA);
	glDisable(GL_BLEND);
}

int SimpleOpenGL3App::registerCubeShape(float halfExtentsX, float halfExtentsY, float halfExtentsZ, int textureIndex, float textureScaling)
{
	int strideInBytes = 9 * sizeof(float);
	int numVertices = sizeof(cube_vertices_textured) / strideInBytes;
	int numIndices = sizeof(cube_indices) / sizeof(int);

	b3AlignedObjectArray<GfxVertexFormat1> verts;
	verts.resize(numVertices);
	for (int i = 0; i < numVertices; i++)
	{
		verts[i].x = halfExtentsX * cube_vertices_textured[i * 9];
		verts[i].y = halfExtentsY * cube_vertices_textured[i * 9 + 1];
		verts[i].z = halfExtentsZ * cube_vertices_textured[i * 9 + 2];
		verts[i].w = cube_vertices_textured[i * 9 + 3];
		verts[i].nx = cube_vertices_textured[i * 9 + 4];
		verts[i].ny = cube_vertices_textured[i * 9 + 5];
		verts[i].nz = cube_vertices_textured[i * 9 + 6];
		verts[i].u = cube_vertices_textured[i * 9 + 7] * textureScaling;
		verts[i].v = cube_vertices_textured[i * 9 + 8] * textureScaling;
	}

	int shapeId = m_instancingRenderer->registerShape(&verts[0].x, numVertices, cube_indices, numIndices, B3_GL_TRIANGLES, textureIndex);
	return shapeId;
}

void SimpleOpenGL3App::registerGrid(int cells_x, int cells_z, float color0[4], float color1[4])
{
	b3Vector3 cubeExtents = b3MakeVector3(0.5, 0.5, 0.5);
	double halfHeight = 0.1;
	cubeExtents[m_data->m_upAxis] = halfHeight;
	int cubeId = registerCubeShape(cubeExtents[0], cubeExtents[1], cubeExtents[2]);
	b3Quaternion orn(0, 0, 0, 1);
	b3Vector3 center = b3MakeVector3(0, 0, 0, 1);
	b3Vector3 scaling = b3MakeVector3(1, 1, 1, 1);

	for (int i = 0; i < cells_x; i++)
	{
		for (int j = 0; j < cells_z; j++)
		{
			float* color = 0;
			if ((i + j) % 2 == 0)
			{
				color = (float*)color0;
			}
			else
			{
				color = (float*)color1;
			}
			if (this->m_data->m_upAxis == 1)
			{
				center = b3MakeVector3((i + 0.5f) - cells_x * 0.5f, -halfHeight, (j + 0.5f) - cells_z * 0.5f);
			}
			else
			{
				center = b3MakeVector3((i + 0.5f) - cells_x * 0.5f, (j + 0.5f) - cells_z * 0.5f, -halfHeight);
			}
			m_instancingRenderer->registerGraphicsInstance(cubeId, center, orn, color, scaling);
		}
	}
}

int SimpleOpenGL3App::registerGraphicsUnitSphereShape(EnumSphereLevelOfDetail lod, int textureId)
{

	int red = 255;
	int green = 0;
	int blue = 128;
	if (textureId<0)
	{
		if (m_data->m_textureId < 0)
		{
			int texWidth = 1024;
			int texHeight = 1024;
			b3AlignedObjectArray<unsigned char> texels;
			texels.resize(texWidth * texHeight * 3);
			for (int i = 0; i < texWidth * texHeight * 3; i++)
				texels[i] = 255;

			for (int i = 0; i < texWidth; i++)
			{
				for (int j = 0; j < texHeight; j++)
				{
					int a = i < texWidth / 2 ? 1 : 0;
					int b = j < texWidth / 2 ? 1 : 0;

					if (a == b)
					{
						texels[(i + j * texWidth) * 3 + 0] = red;
						texels[(i + j * texWidth) * 3 + 1] = green;
						texels[(i + j * texWidth) * 3 + 2] = blue;
						//					texels[(i+j*texWidth)*4+3] = 255;
					}
					/*else
					{
					texels[i*3+0+j*texWidth] = 255;
					texels[i*3+1+j*texWidth] = 255;
					texels[i*3+2+j*texWidth] = 255;
					}
					*/
				}
			}

			m_data->m_textureId = m_instancingRenderer->registerTexture(&texels[0], texWidth, texHeight);
			
		}
		textureId = m_data->m_textureId;
	}

	int strideInBytes = 9 * sizeof(float);

	int graphicsShapeIndex = -1;

	switch (lod)
	{
		case SPHERE_LOD_POINT_SPRITE:
		{
			int numVertices = sizeof(point_sphere_vertices) / strideInBytes;
			int numIndices = sizeof(point_sphere_indices) / sizeof(int);
			graphicsShapeIndex = m_instancingRenderer->registerShape(&point_sphere_vertices[0], numVertices, point_sphere_indices, numIndices, B3_GL_POINTS, textureId);
			break;
		}

		case SPHERE_LOD_LOW:
		{
			int numVertices = sizeof(low_sphere_vertices) / strideInBytes;
			int numIndices = sizeof(low_sphere_indices) / sizeof(int);
			graphicsShapeIndex = m_instancingRenderer->registerShape(&low_sphere_vertices[0], numVertices, low_sphere_indices, numIndices, B3_GL_TRIANGLES, textureId);
			break;
		}
		case SPHERE_LOD_MEDIUM:
		{
			int numVertices = sizeof(textured_detailed_sphere_vertices) / strideInBytes;
			int numIndices = sizeof(textured_detailed_sphere_indices) / sizeof(int);
			graphicsShapeIndex = m_instancingRenderer->registerShape(&textured_detailed_sphere_vertices[0], numVertices, textured_detailed_sphere_indices, numIndices, B3_GL_TRIANGLES, textureId);
			break;
		}
		case SPHERE_LOD_HIGH:
		default:
		{
			int numVertices = sizeof(textured_detailed_sphere_vertices) / strideInBytes;
			int numIndices = sizeof(textured_detailed_sphere_indices) / sizeof(int);
			graphicsShapeIndex = m_instancingRenderer->registerShape(&textured_detailed_sphere_vertices[0], numVertices, textured_detailed_sphere_indices, numIndices, B3_GL_TRIANGLES, textureId);
			break;
		}
	};
	return graphicsShapeIndex;
}

void SimpleOpenGL3App::drawGrid(DrawGridData data)
{
	int gridSize = data.gridSize;
	float upOffset = data.upOffset;
	int upAxis = data.upAxis;
	float gridColor[4];
	gridColor[0] = data.gridColor[0];
	gridColor[1] = data.gridColor[1];
	gridColor[2] = data.gridColor[2];
	gridColor[3] = data.gridColor[3];

	int sideAxis = -1;
	int forwardAxis = -1;

	switch (upAxis)
	{
		case 1:
			forwardAxis = 2;
			sideAxis = 0;
			break;
		case 2:
			forwardAxis = 1;
			sideAxis = 0;
			break;
		default:
			b3Assert(0);
	};
	//b3Vector3 gridColor = b3MakeVector3(0.5,0.5,0.5);

	b3AlignedObjectArray<unsigned int> indices;
	b3AlignedObjectArray<b3Vector3> vertices;
	int lineIndex = 0;
	for (int i = -gridSize; i <= gridSize; i++)
	{
		{
			b3Assert(glGetError() == GL_NO_ERROR);
			b3Vector3 from = b3MakeVector3(0, 0, 0);
			from[sideAxis] = float(i);
			from[upAxis] = upOffset;
			from[forwardAxis] = float(-gridSize);
			b3Vector3 to = b3MakeVector3(0, 0, 0);
			to[sideAxis] = float(i);
			to[upAxis] = upOffset;
			to[forwardAxis] = float(gridSize);
			vertices.push_back(from);
			indices.push_back(lineIndex++);
			vertices.push_back(to);
			indices.push_back(lineIndex++);
			// m_instancingRenderer->drawLine(from,to,gridColor);
		}

		b3Assert(glGetError() == GL_NO_ERROR);
		{
			b3Assert(glGetError() == GL_NO_ERROR);
			b3Vector3 from = b3MakeVector3(0, 0, 0);
			from[sideAxis] = float(-gridSize);
			from[upAxis] = upOffset;
			from[forwardAxis] = float(i);
			b3Vector3 to = b3MakeVector3(0, 0, 0);
			to[sideAxis] = float(gridSize);
			to[upAxis] = upOffset;
			to[forwardAxis] = float(i);
			vertices.push_back(from);
			indices.push_back(lineIndex++);
			vertices.push_back(to);
			indices.push_back(lineIndex++);
			// m_instancingRenderer->drawLine(from,to,gridColor);
		}
	}

	m_instancingRenderer->drawLines(&vertices[0].x,
									gridColor,
									vertices.size(), sizeof(b3Vector3), &indices[0], indices.size(), 1);

	m_instancingRenderer->drawLine(b3MakeVector3(0, 0, 0), b3MakeVector3(1, 0, 0), b3MakeVector3(1, 0, 0), 3);
	m_instancingRenderer->drawLine(b3MakeVector3(0, 0, 0), b3MakeVector3(0, 1, 0), b3MakeVector3(0, 1, 0), 3);
	m_instancingRenderer->drawLine(b3MakeVector3(0, 0, 0), b3MakeVector3(0, 0, 1), b3MakeVector3(0, 0, 1), 3);

	//	void GLInstancingRenderer::drawPoints(const float* positions, const float color[4], int numPoints, int pointStrideInBytes, float pointDrawSize)

	//we don't use drawPoints because all points would have the same color
	//	b3Vector3 points[3] = { b3MakeVector3(1, 0, 0), b3MakeVector3(0, 1, 0), b3MakeVector3(0, 0, 1) };
	//	m_instancingRenderer->drawPoints(&points[0].x, b3MakeVector3(1, 0, 0), 3, sizeof(b3Vector3), 6);

	m_instancingRenderer->drawPoint(b3MakeVector3(1, 0, 0), b3MakeVector3(1, 0, 0), 6);
	m_instancingRenderer->drawPoint(b3MakeVector3(0, 1, 0), b3MakeVector3(0, 1, 0), 6);
	m_instancingRenderer->drawPoint(b3MakeVector3(0, 0, 1), b3MakeVector3(0, 0, 1), 6);
}

void SimpleOpenGL3App::setBackgroundColor(float red, float green, float blue)
{
	CommonGraphicsApp::setBackgroundColor(red, green, blue);
	glClearColor(m_backgroundColorRGB[0], m_backgroundColorRGB[1], m_backgroundColorRGB[2], 1.f);
}

SimpleOpenGL3App::~SimpleOpenGL3App()
{
	delete m_instancingRenderer;
	delete m_primRenderer;
	sth_delete(m_data->m_fontStash);
	delete m_data->m_renderCallbacks;

	sth_delete(m_data->m_fontStash2);
	delete m_data->m_renderCallbacks2;

	TwDeleteDefaultFonts();
	m_window->closeWindow();

	delete m_window;
	delete m_data;
}

void SimpleOpenGL3App::setViewport(int width, int height)
{
	m_data->m_customViewPortWidth = width;
	m_data->m_customViewPortHeight = height;
	if (width >= 0)
	{
		glViewport(0, 0, width, height);
	}
	else
	{
		glViewport(0, 0, m_window->getRetinaScale() * m_instancingRenderer->getScreenWidth(), m_window->getRetinaScale() * m_instancingRenderer->getScreenHeight());
	}
}

void SimpleOpenGL3App::getScreenPixels(unsigned char* rgbaBuffer, int bufferSizeInBytes, float* depthBuffer, int depthBufferSizeInBytes)
{
	int width = m_data->m_customViewPortWidth >= 0 ? m_data->m_customViewPortWidth : (int)m_window->getRetinaScale() * m_instancingRenderer->getScreenWidth();
	int height = m_data->m_customViewPortHeight >= 0 ? m_data->m_customViewPortHeight : (int)m_window->getRetinaScale() * m_instancingRenderer->getScreenHeight();

	b3Assert((width * height * 4) == bufferSizeInBytes);
	if ((width * height * 4) == bufferSizeInBytes)
	{
		glReadPixels(0, 0, width, height, GL_RGBA, GL_UNSIGNED_BYTE, rgbaBuffer);
		int glstat;
		glstat = glGetError();
		b3Assert(glstat == GL_NO_ERROR);
	}
	b3Assert((width * height * sizeof(float)) == depthBufferSizeInBytes);
	if ((width * height * sizeof(float)) == depthBufferSizeInBytes)
	{
		glReadPixels(0, 0, width, height, GL_DEPTH_COMPONENT, GL_FLOAT, depthBuffer);
		int glstat;
		glstat = glGetError();
		b3Assert(glstat == GL_NO_ERROR);
	}
}

//#define STB_IMAGE_WRITE_IMPLEMENTATION
#include "stb_image/stb_image_write.h"
static void writeTextureToFile(int textureWidth, int textureHeight, const char* fileName, FILE* ffmpegVideo)
{
	int numComponents = 4;
	//glPixelStorei(GL_PACK_ALIGNMENT,1);

	b3Assert(glGetError() == GL_NO_ERROR);
	//glReadBuffer(GL_BACK);//COLOR_ATTACHMENT0);

	float* orgPixels = (float*)malloc(textureWidth * textureHeight * numComponents * 4);
	glReadPixels(0, 0, textureWidth, textureHeight, GL_RGBA, GL_FLOAT, orgPixels);
	//it is useful to have the actual float values for debugging purposes

	//convert float->char
	char* pixels = (char*)malloc(textureWidth * textureHeight * numComponents);
	assert(glGetError() == GL_NO_ERROR);

	for (int j = 0; j < textureHeight; j++)
	{
		for (int i = 0; i < textureWidth; i++)
		{
			pixels[(j * textureWidth + i) * numComponents] = char(orgPixels[(j * textureWidth + i) * numComponents] * 255.f);
			pixels[(j * textureWidth + i) * numComponents + 1] = char(orgPixels[(j * textureWidth + i) * numComponents + 1] * 255.f);
			pixels[(j * textureWidth + i) * numComponents + 2] = char(orgPixels[(j * textureWidth + i) * numComponents + 2] * 255.f);
			pixels[(j * textureWidth + i) * numComponents + 3] = char(orgPixels[(j * textureWidth + i) * numComponents + 3] * 255.f);
		}
	}

	if (ffmpegVideo)
	{
		fwrite(pixels, textureWidth * textureHeight * numComponents, 1, ffmpegVideo);
		//fwrite(pixels, 100,1,ffmpegVideo);//textureWidth*textureHeight*numComponents, 1, ffmpegVideo);
	}
	else
	{
		if (1)
		{
			//swap the pixels
			unsigned char tmp;

			for (int j = 0; j < textureHeight / 2; j++)
			{
				for (int i = 0; i < textureWidth; i++)
				{
					for (int c = 0; c < numComponents; c++)
					{
						tmp = pixels[(j * textureWidth + i) * numComponents + c];
						pixels[(j * textureWidth + i) * numComponents + c] =
							pixels[((textureHeight - j - 1) * textureWidth + i) * numComponents + c];
						pixels[((textureHeight - j - 1) * textureWidth + i) * numComponents + c] = tmp;
					}
				}
			}
		}
		stbi_write_png(fileName, textureWidth, textureHeight, numComponents, pixels, textureWidth * numComponents);
	}

	free(pixels);
	free(orgPixels);
}

void SimpleOpenGL3App::swapBuffer()
{
	if (m_data->m_frameDumpPngFileName)
	{
		int width = (int)m_window->getRetinaScale() * m_instancingRenderer->getScreenWidth();
		int height = (int)m_window->getRetinaScale() * this->m_instancingRenderer->getScreenHeight();
		writeTextureToFile(width,
						   height, m_data->m_frameDumpPngFileName,
						   m_data->m_ffmpegFile);
		m_data->m_renderTexture->disable();
		if (m_data->m_ffmpegFile == 0)
		{
			m_data->m_frameDumpPngFileName = 0;
		}
	}
	m_window->endRendering();
	m_window->startRendering();
}

void SimpleOpenGL3App::setMp4Fps(int fps)
{
	m_data->m_mp4Fps = fps;
}

// see also http://blog.mmacklin.com/2013/06/11/real-time-video-capture-with-ffmpeg/
void SimpleOpenGL3App::dumpFramesToVideo(const char* mp4FileName)
{
	if (mp4FileName)
	{
		int width = (int)m_window->getRetinaScale() * m_instancingRenderer->getScreenWidth();
		int height = (int)m_window->getRetinaScale() * m_instancingRenderer->getScreenHeight();
		char cmd[8192];

		sprintf(cmd,
				"ffmpeg -r %d -f rawvideo -pix_fmt rgba -s %dx%d -i - "
				"-threads 0 -y -b:v 50000k   -c:v libx264 -preset slow -crf 22 -an   -pix_fmt yuv420p -vf vflip %s",
			m_data->m_mp4Fps, width, height, mp4FileName);


		if (m_data->m_ffmpegFile)
		{
			pclose(m_data->m_ffmpegFile);
		}
		if (mp4FileName)
		{
			m_data->m_ffmpegFile = popen(cmd, "w");

			m_data->m_frameDumpPngFileName = mp4FileName;
		}
	}
	else
	{
		if (m_data->m_ffmpegFile)
		{
			fflush(m_data->m_ffmpegFile);
			pclose(m_data->m_ffmpegFile);
			m_data->m_frameDumpPngFileName = 0;
		}
		m_data->m_ffmpegFile = 0;
	}
}
void SimpleOpenGL3App::dumpNextFrameToPng(const char* filename)
{
	// open pipe to ffmpeg's stdin in binary write mode

	m_data->m_frameDumpPngFileName = filename;

	//you could use m_renderTexture to allow to render at higher resolutions, such as 4k or so
	if (!m_data->m_renderTexture)
	{
		m_data->m_renderTexture = new GLRenderToTexture();
		GLuint renderTextureId;
		glGenTextures(1, &renderTextureId);

		// "Bind" the newly created texture : all future texture functions will modify this texture
		glBindTexture(GL_TEXTURE_2D, renderTextureId);

		// Give an empty image to OpenGL ( the last "0" )
		//glTexImage2D(GL_TEXTURE_2D, 0,GL_RGB, g_OpenGLWidth,g_OpenGLHeight, 0,GL_RGBA, GL_UNSIGNED_BYTE, 0);
		//glTexImage2D(GL_TEXTURE_2D, 0,GL_RGBA32F, g_OpenGLWidth,g_OpenGLHeight, 0,GL_RGBA, GL_FLOAT, 0);
		glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA32F,
					 m_instancingRenderer->getScreenWidth() * m_window->getRetinaScale(), m_instancingRenderer->getScreenHeight() * m_window->getRetinaScale(), 0, GL_RGBA, GL_FLOAT, 0);

		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
		//glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
		//glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);

		m_data->m_renderTexture->init(m_instancingRenderer->getScreenWidth() * m_window->getRetinaScale(), this->m_instancingRenderer->getScreenHeight() * m_window->getRetinaScale(), renderTextureId, RENDERTEXTURE_COLOR);
	}

	m_data->m_renderTexture->enable();
}

void SimpleOpenGL3App::setUpAxis(int axis)
{
	b3Assert((axis == 1) || (axis == 2));  //only Y or Z is supported at the moment
	m_data->m_upAxis = axis;
}
int SimpleOpenGL3App::getUpAxis() const
{
	return m_data->m_upAxis;
}
#endif  //#ifndef NO_OPENGL3
