#define WIN32_LEAN_AND_MEAN             // Exclude rarely-used stuff from Windows headers
#include <windows.h>
#include <cstdio>
#include <cmath>
#include "GL/ImmediateMode.h"


BEGIN_NAMESPACE


#define DECLARE_GL_FUNCTION(type, name) \
		type name;
DECLARE_ALL_GL_FUNCTIONS
#undef DECLARE_GL_FUNCTION

// Now load the various GL extension functions we use
#define DECLARE_GL_FUNCTION(type, name) \
			name = (type)wglGetProcAddress(#name); \
			if (glGetError() != GL_NO_ERROR) { \
				printf("GL Error getting proc address for %s\n", #name); \
			}

void glLoadExtensions()
{
	DECLARE_ALL_GL_FUNCTIONS
}

#undef DECLARE_GL_FUNCTION


void glCheckErrors()
{
	GLenum result = glGetError();
	if (result != GL_NO_ERROR)
	{
		printf("GL Error\n");
	}
}


void glSetColor(uint32_t a_color)
{
	glColor4ub((a_color >> 16) & 0xff, (a_color >> 8) & 0xff, a_color & 0xff, (a_color >> 24) & 0xff);
}


static void glLineHelper(float x1, float y1, float x2, float y2)
{
	glVertex2f(x1, y1);
	glVertex2f(x2, y2);
}


void glDrawLine(float x1, float y1, float x2, float y2, uint32_t a_color)
{
	glSetColor(a_color);
	glBegin(GL_LINES);
	glLineHelper(x1, y1, x2, y2);
	glEnd();
	glCheckErrors();
}


static void glBezierCurveHelper(float p1[2], float cp1[2], float cp2[2], float p2[2])
{
	const int subDivisions = 32;
	/*
	int mid = (x1 + x2) / 2;
	Mat4x4f coeffients = { { 1,-3,3,-1, 0,3,-6,3, 0,0,3,-3, 0,0,0,1 } };
	//Mat4x4f coeffients = { { 1,0,0,0, -3,3,0,0, 3,-6,3,0,  -1,3,-3,1 } };
	Mat4x4f points = { { x1,y1,0,0, mid,y1,0,0, mid,y2,0,0, x2,y2,0,0 } };
	Mat4x4f factors = MatrixMult(points, coeffients);
	*/
	for (int i = 0; i < subDivisions; ++i)
	{
		float t[2] = { float(i) / subDivisions, float(i+1) / subDivisions };
		/*
		Vec4f ts = { { 1,t,t*t,t*t*t } };
		Vec4f res = Transform(factors, ts);
		Vec4f t2s = { { 1,t2,t2*t2,t2*t2*t2 } };
		Vec4f res2 = Transform(factors, t2s);
		glDrawLine(res.v[0], res.v[1], res2.v[0], res2.v[1], a_color);
		*/
		float b[2][2];		
		for (int j = 0; j < 2; ++j)
			for (int c = 0; c < 2; ++c)
				b[j][c] = p1[c]*(1-t[j])*(1-t[j])*(1-t[j])+
						  cp1[c]*3*(1-t[j])*(1-t[j])*t[j]+
						  cp2[c]*3*(1-t[j])*t[j]*t[j]+
						  p2[c]*t[j]*t[j]*t[j];
		glLineHelper(b[0][0], b[0][1], b[1][0], b[1][1]);
	}
}


void glDrawBezierCurve(float p1[2], float cp1[2], float cp2[2], float p2[2], uint32_t a_color)
{
	glSetColor(a_color);
	glBegin(GL_LINE_STRIP);
	glBezierCurveHelper(p1, cp1, cp2, p2);
	glEnd();
	glCheckErrors();
}


void glDrawCurvedLine(float x1, float y1, float x2, float y2, uint32_t a_color)
{
	float halfW = fabs((x2 - x1) / 2.0f);
	float p1[2] = { x1, y1 }, cp1[2] = { x1+halfW, y1 }, cp2[2] = { x2-halfW, y2 }, p2[2] = { x2, y2 };
	glDrawBezierCurve(p1, cp1, cp2, p2, a_color);
}


void glDrawRect(float x1, float y1, float x2, float y2, uint32_t a_color)
{
	glSetColor(a_color);
	glBegin(GL_LINES);
	glLineHelper(x1, y1, x2, y1);
	glLineHelper(x2, y1, x2, y2);
	glLineHelper(x2, y2, x1, y2);
	glLineHelper(x1, y2, x1, y1);
	glEnd();
	glCheckErrors();
}


void glDrawFillRect(float x1, float y1, float x2, float y2, uint32_t a_color)
{
	glSetColor(a_color);
	glBegin(GL_QUADS);
    glVertex3f(x1, y1, 0.0f);
    glVertex3f(x2, y1, 0.0f);
    glVertex3f(x2, y2, 0.0f);
    glVertex3f(x1, y2, 0.0f);
    glEnd();
	glCheckErrors();
}

static void glDrawCircleHelper(GLenum mode, float x1, float y1, float size, uint32_t a_color)
{
	//glDrawRect(x1, y1, x1+size, y1+size, a_color);
	glSetColor(a_color);
	glBegin(mode);
	const int subDivisions = 64;
	float centerX = x1 + size * 0.5f;
	float centerY = y1 + size * 0.5f;
	float pi = 2.0f*acos(0.0f);
	for (int i = 0; i < subDivisions; ++i)
	{
		float x1 = centerX + size*0.5f*cos((i*2.0f*pi)/subDivisions) + 0.5f;
		float y1 = centerY + size*0.5f*sin((i*2.0f*pi)/subDivisions) + 0.5f;
		float x2 = centerX + size*0.5f*cos(((i+1)*2.0f*pi)/subDivisions) + 0.5f;
		float y2 = centerY + size*0.5f*sin(((i+1)*2.0f*pi)/subDivisions) + 0.5f;
		glLineHelper(x1, y1, x2, y2);
	}
	glEnd();
	glCheckErrors();
}


void glDrawCircle(float x1, float y1, float size, uint32_t a_color)
{
	glDrawCircleHelper(GL_LINES, x1, y1, size, a_color);
}


void glDrawFilledCircle(float x1, float y1, float size, uint32_t a_color)
{
	glDrawCircleHelper(GL_TRIANGLE_FAN, x1, y1, size, a_color);
}


static void glDrawRoundedRectHelper(float x1, float y1, float x2, float y2, float radius)
{
	/*
	float p[4][4][2] = {
		{ { x1, y1+radius }, { x1, y1 }, { x1, y1 }, { x1+radius, y1 } },
		{ { x2, y1+radius }, { x2, y1 }, { x2, y1 }, { x2-radius, y1 } },
		{ { x1, y2-radius }, { x1, y2 }, { x1, y2 }, { x1+radius, y2 } },
		{ { x2, y2-radius }, { x2, y2 }, { x2, y2 }, { x2-radius, y2 } },
	};
	*/
	float p[4][4][2] = {
		{ { x1+0.5f, y1+radius }, { x1, y1 }, { x1, y1 }, { x1+radius, y1+0.5f } },
		{ { x2-radius, y1+0.5f }, { x2, y1 }, { x2, y1 }, { x2-0.5f, y1+radius } },
		{ { x2-0.5f, y2-radius }, { x2, y2 }, { x2, y2 }, { x2-radius, y2-0.5f } },
		{ { x1+radius, y2-0.5f }, { x1, y2 }, { x1, y2 }, { x1+0.5f, y2-radius } },
	};

	for (int i = 0; i < 4; ++i) {
		glBezierCurveHelper(p[i][0], p[i][1], p[i][2], p[i][3]);
		glLineHelper(p[i][3][0], p[i][3][1], p[(i+1)&3][0][0], p[(i+1)&3][0][1]);
	}
}


void glDrawRoundedRect(float x1, float y1, float x2, float y2, float radius, uint32_t a_color)
{
	glSetColor(a_color);
	glBegin(GL_LINES);
	glDrawRoundedRectHelper(x1, y1, x2, y2, radius);
    glEnd();
	glCheckErrors();
}


void glDrawFillRoundedRect(float x1, float y1, float x2, float y2, float radius, uint32_t a_color)
{
	glSetColor(a_color);
	glBegin(GL_TRIANGLE_FAN);
	glDrawRoundedRectHelper(x1, y1, x2, y2, radius);
    glEnd();
	glCheckErrors();
}


void glDrawFillTitleBar(float x1, float y1, float x2, float y2, float radius, uint32_t a_color)
{
	glSetColor(a_color);
	glBegin(GL_TRIANGLE_FAN);

	float p[2][4][2] = {
		{ { x1, y1+radius }, { x1, y1 }, { x1, y1 }, { x1+radius, y1 } },
		{ { x2-radius, y1 }, { x2, y1 }, { x2, y1 }, { x2, y1+radius } },
	};

	//for (int i = 0; i < 4; ++i)
	int i = 0;
	glBezierCurveHelper(p[i][0], p[i][1], p[i][2], p[i][3]);
	glLineHelper(x1+radius, y1, x2-radius, y1);
	i = 1;
	glBezierCurveHelper(p[i][0], p[i][1], p[i][2], p[i][3]);
	
	glLineHelper(x2, y1+radius, x2, y2);
	glLineHelper(x2, y2, x1, y2);
	glLineHelper(x1, y2, x1, y1+radius);

    glEnd();
	glCheckErrors();
}


// Needed for textures with OpenGL
unsigned nextPowerOfTwo(unsigned int a_value)
{
	for (unsigned i = 0; i < 32; ++i)
	{
		unsigned powerOfTwo = 1 << i;
		if (powerOfTwo >= a_value)
			return powerOfTwo;
	}
	return 1 << 31;
}


void glDrawTarget(float x, float y, CUTE::CUTE_PaintTarget a_target, GLuint a_texture)
{
	int w = a_target.m_strideBytes/4;
	int h = nextPowerOfTwo(a_target.m_height);
#if 1
	glEnable(GL_TEXTURE_2D);
	glCheckErrors();
	glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_DECAL);
	glCheckErrors();
	glBindTexture(GL_TEXTURE_2D, a_texture);
	glCheckErrors();
	glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, w, h, GL_BGRA_EXT, GL_UNSIGNED_BYTE, a_target.m_pixels);
	glCheckErrors();
	glBegin(GL_QUADS);
	float x2 = float(a_target.m_width) / w;
	float y2 = float(a_target.m_height) / h;
    glTexCoord2f(0.0f, 0.0f); glVertex3f(x, y, 0.0f);
    glTexCoord2f(  x2, 0.0f); glVertex3f(x+a_target.m_width, y, 0.0f);
    glTexCoord2f(  x2,   y2); glVertex3f(x+a_target.m_width, y+a_target.m_height, 0.0f);
    glTexCoord2f(0.0f,   y2); glVertex3f(x, y+a_target.m_height, 0.0f);
    glEnd();
	glCheckErrors();
	//glFlush();
	//glCheckErrors();
	glDisable(GL_TEXTURE_2D);
#else
	//glPixelZoom(g_zoomFactor, -g_zoomFactor);
	glRasterPos2f(x, y);
	glDrawPixels(w, h, GL_BGRA_EXT, GL_UNSIGNED_BYTE, a_target.m_pixels);
#endif	
	glCheckErrors();
}


void glDrawShadowText(int x, int y, const char* text)
{
	glDrawText(x+1, y, text, 0xff000000, 0xffffffff, 17, false);
}


void glDrawDot(float x, float y)
{
	glDrawFilledCircle(x-7, y, 14, 0xf06060f0);
	glDrawCircle(x-7, y, 14, 0xa0202020);
}


void glDrawStyledCurvedLine(float x1, float y1, float x2, float y2, float scale, bool highlighted)
{
	// TODO: fix the line joins - eg miter, bevel etc join types.
	// Need to turn lines in to triangle strips or something like that. Probably only the bezier case matters at the moment.
	glLineWidth(5.0f * scale);
	glDrawCurvedLine(x1, y1, x2, y2, 0x40000000);
	uint32_t col = highlighted ? 0x50ff901a : 0x4ff0f0f0;
	glLineWidth(2.0f * scale);
	glDrawCurvedLine(x1, y1, x2, y2, col);
	glLineWidth(1.0f);
	glCheckErrors();
}


END_NAMESPACE
