//Copyright (C) 2007 SiegeLord
//See ReadMe.txt and License.txt for copying information

#include "../include/engine/gfx.h"
#include "../include/engine/error.h"
#include "../include/engine/font.h"

void SetModelMatrix(const transform2d& trans)
{
	GLfloat matrix[16];

	//memset(matrix, 0, sizeof(GLfloat) * 16);

	matrix[0] = trans.vals[0][0];
	matrix[1] = trans.vals[1][0];
	matrix[4] = trans.vals[0][1];
	matrix[5] = trans.vals[1][1];

	matrix[12] = trans.vals[0][2];
	matrix[13] = trans.vals[1][2];

	matrix[10] = 1;
	matrix[15] = 1;

	matrix[2] = matrix[3] = matrix[6] = matrix[7] = matrix[8] = matrix[9] = matrix[11] = matrix[14] = 0;

	glMatrixMode(GL_MODELVIEW);
	glLoadMatrixf(matrix);
}

void SetCameraMatrix(const transform2d& trans)
{
	glMatrixMode(GL_PROJECTION);
	glLoadIdentity();
	glOrtho(0, SCREEN_W, SCREEN_H, 0, 1, 10000);

	GLfloat matrix[16];

	//memset(matrix, 0, sizeof(GLfloat) * 16);

	matrix[0] = trans.vals[0][0];
	matrix[1] = trans.vals[1][0];
	matrix[4] = trans.vals[0][1];
	matrix[5] = trans.vals[1][1];

	matrix[12] = trans.vals[0][2];
	matrix[13] = trans.vals[1][2];

	matrix[10] = 1;
	matrix[15] = 1;

	matrix[2] = matrix[3] = matrix[6] = matrix[7] = matrix[8] = matrix[9] = matrix[11] = matrix[14] = 0;

	glMultMatrixf(matrix);
}

void InitGfx()
{
	install_allegro_gl();


	//init allegro gl stuff
	allegro_gl_clear_settings();
	allegro_gl_use_alpha_channel(true);
	allegro_gl_set(AGL_COLOR_DEPTH, 32);
	allegro_gl_set(AGL_Z_DEPTH, 16);
	allegro_gl_set(AGL_WINDOWED, TRUE);
	allegro_gl_set(AGL_DOUBLEBUFFER, 1);
	allegro_gl_set(AGL_RENDERMETHOD, 1);
	allegro_gl_set(AGL_SUGGEST, AGL_Z_DEPTH | AGL_WINDOWED);

	allegro_gl_set(AGL_SUGGEST, AGL_RENDERMETHOD | AGL_COLOR_DEPTH | AGL_DOUBLEBUFFER);

	if(set_gfx_mode(GFX_OPENGL_FULLSCREEN, 1024, 768, 0, 0) != 0)
	{
		Throw("Unable to set any graphic mode...\n", allegro_error);
	}

	//init opengl stuff
	glEnable(GL_TEXTURE_2D);

	glShadeModel(GL_SMOOTH);
	glEnable(GL_LINE_SMOOTH);

	glViewport(0, 0, SCREEN_W, SCREEN_H);
	glMatrixMode(GL_PROJECTION);
	glLoadIdentity();
	glOrtho(0, SCREEN_W, SCREEN_H, 0, 1, 10000);
	glClearDepth(1.0f);
	glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST);
	glHint(GL_LINE_SMOOTH_HINT, GL_NICEST);

	/* Set culling mode - not that we have anything to cull */
	glDisable(GL_CULL_FACE);
	glEnable(GL_COLOR_MATERIAL);

	//glEnable(GL_DEPTH_TEST);
	//glDepthFunc(GL_LESS);

	//glEnable(GL_COLOR_MATERIAL);
	glEnable(GL_LIGHTING);

	GLfloat LightAmbient[]= { 1.0f, 1.0f, 1.0f, 1.0f };
	//GLfloat LightPosition[]= { 100.0f, 100.0f, -10.0f, 1.0f };
	glLightModelfv(GL_LIGHT_MODEL_AMBIENT, LightAmbient);

	//GLfloat LightMiss[]= { 0, 0, 0, 1.0f };
	//glMaterialfv(GL_FRONT, GL_EMISSION, LightMiss);
	//glLightfv(GL_LIGHT1, GL_AMBIENT, LightAmbient);
	//glLightfv(GL_LIGHT1, GL_POSITION,LightPosition);

	//glEnable(GL_LIGHT1);
	glFrontFace(GL_CCW);
}

void Present()
{
	allegro_gl_set_allegro_mode();
	allegro_gl_unset_allegro_mode();
	allegro_gl_flip();

	glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
}

void DrawRect(float x1, float y1, float x2, float y2, float depth, zColor color)
{
	SetColor(color);

	glBegin(GL_LINE_LOOP);

	glVertex3f(x2, y2, depth);
	glVertex3f(x1, y2, depth);
	glVertex3f(x1, y1, depth);
	glVertex3f(x2, y1, depth);

	glEnd();

	glColor4f(1.0f, 1.0f, 1.0f, 1.0f);
}

void DrawFilledRect(float x1, float y1, float x2, float y2, float depth, zColor color)
{
	SetColor(color);

	glBegin(GL_QUADS);

	glVertex3f(x2, y2, depth);
	glVertex3f(x1, y2, depth);
	glVertex3f(x1, y1, depth);
	glVertex3f(x2, y1, depth);

	glEnd();

	glColor4f(1.0f, 1.0f, 1.0f, 1.0f);
}

void DrawLine(float x1, float y1, float x2, float y2, float depth, zColor color)
{
	SetColor(color);

	glBegin(GL_LINES);

	glVertex3f(x1, y1, depth);
	glVertex3f(x2, y2, depth);

	glEnd();

	glColor4f(1.0f, 1.0f, 1.0f, 1.0f);
}

void DrawPolyLine(vector2d* pts, float depth, int num, zColor color, bool closed)
{
	SetColor(color);

	glBegin(GL_LINE_STRIP);

	FOR1(num)
	{
		vector2d* pt = &(pts[ii]);
		glVertex3f(pt->x, pt->y, depth);
	}

	if(closed)
	{
		vector2d* pt = &(pts[0]);
		glVertex3f(pt->x, pt->y, depth);
	}

	glEnd();

	glColor4f(1.0f, 1.0f, 1.0f, 1.0f);
}

//segments per full circle
void DrawCircle(float _x, float _y, float _r, float depth, zColor color, int segments)
{
	int real_segments = segments;
	if(real_segments < 0)
	{
		real_segments = (int)(_r / 15.0f);
	}

	SetColor(color);

	float theta = 2 * AL_PI / float(real_segments);
	float tangential_factor = tanf(theta);
	float radial_factor = 1 - cosf(theta);

	float x = _x + _r;
	float y = _y;

	glBegin(GL_LINE_LOOP);
	FOR1(real_segments)
	{
		glVertex3f(x, y, depth);

		float tx = -(y - _y);
		float ty = x - _x;

		x += tx * tangential_factor;
		y += ty * tangential_factor;

		float rx = _x - x;
		float ry = _y - y;

		x += rx * radial_factor;
		y += ry * radial_factor;
	}
	glEnd();

	glColor4f(1.0f, 1.0f, 1.0f, 1.0f);
}

//segments are segments per full circle!
//d_angle determines the direction and the ammount of the arc to draw
void DrawArc(float _x, float _y, float _r, float depth, float start_angle, float d_angle, zColor color, int segments)
{
	int real_segments = segments;
	if(real_segments < 0)
	{
		real_segments = (int)(_r / 15.0f);
	}

	SetColor(color);

	real_segments = int(fabsf(d_angle) / (2 * AL_PI) * (float)real_segments) + 1;

	float theta = d_angle / float(real_segments);
	float tangential_factor = tanf(theta);
	float radial_factor = 1 - cosf(theta);

	float x = _x + _r * cosf(start_angle);
	float y = _y + _r * sinf(start_angle);

	glBegin(GL_LINE_STRIP);
	FOR1(real_segments + 1)
	{
		glVertex3f(x, y, depth);

		float tx = -(y - _y);
		float ty = x - _x;

		x += tx * tangential_factor;
		y += ty * tangential_factor;

		float rx = _x - x;
		float ry = _y - y;

		x += rx * radial_factor;
		y += ry * radial_factor;
	}
	glEnd();

	glColor4f(1.0f, 1.0f, 1.0f, 1.0f);
}

void DrawModeMask()
{
	glDisable(GL_BLEND);
	glEnable(GL_ALPHA_TEST);
	glAlphaFunc(GL_GREATER, 0.9);
}

void DrawModeAlpha()
{
	glDisable(GL_ALPHA_TEST);
	glEnable(GL_BLEND);
	glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
}

void DrawModeAdd()
{
	glDisable(GL_ALPHA_TEST);
	glEnable(GL_BLEND);
	glBlendFunc(GL_ONE,GL_ONE);
}

void DrawModeAlphaAdd()
{
	glDisable(GL_ALPHA_TEST);
	glEnable(GL_BLEND);
	glBlendFunc(GL_SRC_ALPHA,GL_ONE);
}

void DrawModeSolid()
{
	glDisable(GL_BLEND);
	glDisable(GL_ALPHA_TEST);
}

void DrawBitmap(const SBitmap* bmp, float depth)
{
	DrawBitmap(bmp, 0, 0, depth);
}

void DrawBitmap(const SBitmap* bmp, float x, float y, float depth)
{
	_ASSERT(bmp);

	//glMatrixMode(GL_MODELVIEW);
	//glLoadIdentity();

	glBindTexture(GL_TEXTURE_2D, bmp->handle);

	glBegin(GL_QUADS);

	glTexCoord2f(0, 0);
	glVertex3f(x, y, depth);

	glTexCoord2f(1, 0);
	glVertex3f(x + bmp->w, y, depth);

	glTexCoord2f(1, 1);
	glVertex3f(x + bmp->w, y + bmp->h, depth);

	glTexCoord2f(0, 1);
	glVertex3f(x, y + bmp->h, depth);

	glEnd();

	glBindTexture(GL_TEXTURE_2D, 0);
}

void DrawBitmapStretched(const SBitmap* bmp, float x, float y, float depth, float w, float h)
{
	_ASSERT(bmp);

	glBindTexture(GL_TEXTURE_2D, bmp->handle);
	glBegin(GL_QUADS);

	glTexCoord2f(0, 0);
	glVertex3f(x, y, depth);

	glTexCoord2f(1, 0);
	glVertex3f(x + w, y, depth);

	glTexCoord2f(1, 1);
	glVertex3f(x + w, y + h, depth);

	glTexCoord2f(0, 1);
	glVertex3f(x, y + h, depth);

	glEnd();

	glBindTexture(GL_TEXTURE_2D, 0);
}

void DrawBitmapTiled(const SBitmap* bmp, float x, float y, float depth, float w, float h)
{
	_ASSERT(bmp);

	glPolygonMode(GL_FRONT, GL_FILL);

	float u = (float)w / (float)bmp->w;
	float v = (float)h / (float)bmp->h;

	glBindTexture(GL_TEXTURE_2D, bmp->handle);
	glBegin(GL_QUADS);

	glTexCoord2f(0, 0);
	glVertex3f(x, y, depth);

	glTexCoord2f(u, 0);
	glVertex3f(x + w, y, depth);

	glTexCoord2f(u, v);
	glVertex3f(x + w, y + h, depth);

	glTexCoord2f(0, v);
	glVertex3f(x, y + h, depth);

	glEnd();

	glBindTexture(GL_TEXTURE_2D, 0);
}

void DrawBitmapTiled(const SBitmap* bmp, float x, float y, float depth, float w, float h, float offset_x, float offset_y)
{
	_ASSERT(bmp);

	glPolygonMode(GL_FRONT, GL_FILL);

	float uo = offset_x / (float)bmp->w;
	float vo = offset_y / (float)bmp->h;

	float u = (float)w / (float)bmp->w;
	float v = (float)h / (float)bmp->h;

	glBindTexture(GL_TEXTURE_2D, bmp->handle);
	glBegin(GL_QUADS);

	glTexCoord2f(uo, vo);
	glVertex3f(x, y, depth);

	glTexCoord2f(u + uo, vo);
	glVertex3f(x + w, y, depth);

	glTexCoord2f(u + uo, v + vo);
	glVertex3f(x + w, y + h, depth);

	glTexCoord2f(uo, v + vo);
	glVertex3f(x, y + h, depth);

	glEnd();

	glBindTexture(GL_TEXTURE_2D, 0);
}

void EnableDepthTesting(bool enable)
{
	if(enable)
		glEnable(GL_DEPTH_TEST);
	else
		glDisable(GL_DEPTH_TEST);
}

void ClearZBuffer()
{
	glClear(GL_DEPTH_BUFFER_BIT);
}

void SetClearColor(zColor color)
{
	float r,g,b,a;

	r = float(getr32(color)) / 255.0f;
	g = float(getg32(color)) / 255.0f;
	b = float(getb32(color)) / 255.0f;
	a = float(geta32(color)) / 255.0f;

	glClearColor(r,g,b,a);
}

float SetLineThickness(float thickness)
{
	static float old_thick = 1.0f;

	float ret = old_thick;
	old_thick = thickness;

	glLineWidth(thickness);

	return ret;
}

void SetColor(zColor color)
{
	union
	{
		GLubyte bytes[4];
		zColor color;
	} color2;

	color2.color = color;
//	float r,g,b,a;
//
//	r = float(getr32(color)) / 255.0f;
//	g = float(getg32(color)) / 255.0f;
//	b = float(getb32(color)) / 255.0f;
//	a = float(geta32(color)) / 255.0f;

	glColor4ubv(color2.bytes);
}

//I lifted the following function from
//http://ridiculousx.spaces.live.com/blog/cns!4DB53BAAB9349F0B!247.entry
//Supposedly I was allowed to 'jank' it at my leisure
int BlendColors(zColor color1, zColor color2, float factor)
{
    const unsigned char BlendWeight = (const unsigned char)(factor * 255.0f);
    const unsigned char InverseBlendWeight = 255 - BlendWeight;
    zColor Result;

	 Result = ( ( ( color1 >> 24 ) * InverseBlendWeight ) + ( ( color2 >> 24 ) * BlendWeight ) ) / 255;    // alpha channel
    Result <<= 8;
    Result |= ( ( ( ( color1 & 0x00FF0000 ) >> 16 ) * InverseBlendWeight ) + ( ( ( color2 & 0x00FF0000 ) >> 16 ) * BlendWeight ) ) / 255;    // red channel
    Result <<= 8;
    Result |= ( ( ( ( color1 & 0x0000FF00 ) >> 8 ) * InverseBlendWeight ) + ( ( ( color2 & 0x0000FF00 ) >> 8 ) * BlendWeight ) ) / 255;    // green channel
    Result <<= 8;
    Result |= ( ( ( color1 & 0x000000FF ) * InverseBlendWeight ) + ( ( color2 & 0x000000FF ) * BlendWeight ) ) / 255;    // blue channel

    return Result;
}

void DrawQuadBuffer(SBitmap* bmp, int num_entities, float* vertices, float* tcoords, int* colors)
{
	glEnableClientState(GL_VERTEX_ARRAY);
	glEnableClientState(GL_TEXTURE_COORD_ARRAY );
	glEnableClientState(GL_COLOR_ARRAY);

	glBindTexture(GL_TEXTURE_2D, bmp->handle);

	glTexCoordPointer(2, GL_FLOAT, 0, tcoords);
	glVertexPointer(3, GL_FLOAT, 0, vertices);
	glColorPointer(4, GL_UNSIGNED_BYTE, 0, colors);

	glDrawArrays(GL_QUADS, 0, num_entities * 4);

	glDisableClientState(GL_VERTEX_ARRAY);
	glDisableClientState(GL_TEXTURE_COORD_ARRAY );
	glDisableClientState(GL_COLOR_ARRAY );

	glColor4f(1.0f, 1.0f, 1.0f, 1.0f);

	glBindTexture(GL_TEXTURE_2D, 0);
}

void DrawQuadStripBuffer(SBitmap* bmp, int num_entities, float* vertices, float* tcoords, int* colors)
{
	glEnableClientState(GL_VERTEX_ARRAY);
	glEnableClientState(GL_TEXTURE_COORD_ARRAY );
	glEnableClientState(GL_COLOR_ARRAY);

	glBindTexture(GL_TEXTURE_2D, bmp->handle);

	glTexCoordPointer(2, GL_FLOAT, 0, tcoords);
	glVertexPointer(3, GL_FLOAT, 0, vertices);
	glColorPointer(4, GL_UNSIGNED_BYTE, 0, colors);

	glDrawArrays(GL_QUAD_STRIP, 0, num_entities * 2);

	glDisableClientState(GL_VERTEX_ARRAY);
	glDisableClientState(GL_TEXTURE_COORD_ARRAY );
	glDisableClientState(GL_COLOR_ARRAY );

	glColor4f(1.0f, 1.0f, 1.0f, 1.0f);

	glBindTexture(GL_TEXTURE_2D, 0);
}

//2 uv's, 1 color,  3 positions
void DrawQuadStripInterleaved(SBitmap* bmp, int num_entities, void* ptr, int stride)
{
	glBindTexture(GL_TEXTURE_2D, bmp->handle);

	glInterleavedArrays(GL_T2F_C4UB_V3F, stride, ptr);

	glDrawArrays(GL_QUAD_STRIP, 0, num_entities * 2);

	glColor4f(1.0f, 1.0f, 1.0f, 1.0f);

	glBindTexture(GL_TEXTURE_2D, 0);
}
