#include "glfuncs.h"
#include "Screen.h"


#include <iostream>

using namespace std;

glfuncs* glfuncs::_instance = NULL;

glfuncs::glfuncs()
{
	init_glfuncs();

	GLfloat coords[4];
	SDL_Rect rect;
	_bigFont = loadTexture("fonts/bigFont.png",coords,rect);
	_smallFont = loadTexture("fonts/smallFont.png",coords,rect);

	_bigBase = buildFont(1,16,32,_bigFont);
	_smallBase = buildFont(1,8,16,_smallFont);
	//_base = buildFont(24);
	//_small = buildFont(12);
}

glfuncs* glfuncs::instance()
{
	if(!_instance)
		_instance = new glfuncs();
	return _instance;
}

void* glfuncs::get_funcaddr(const char* p)
{
	void* f=SDL_GL_GetProcAddress(p);
	if (f)
	{
		return f;
	}
	else
	{
		printf("Unable to get function pointer for %s\n",p);
		exit(1);
	}
}

void glfuncs::init_glfuncs()
{
	glBegin=(void(APIENTRY*)(GLenum))get_funcaddr("glBegin");
	glEnd=(void(APIENTRY*)())get_funcaddr("glEnd");
	glVertex3f=(void(APIENTRY*)(GLfloat, GLfloat, GLfloat))get_funcaddr("glVertex3f");
	glClearColor=(void(APIENTRY*)(GLfloat, GLfloat, GLfloat, GLfloat))get_funcaddr("glClearColor");
	glClear=(void(APIENTRY*)(GLbitfield))get_funcaddr("glClear");
	glDisable=(void(APIENTRY*)(GLenum))get_funcaddr("glDisable");
	glEnable=(void(APIENTRY*)(GLenum))get_funcaddr("glEnable");
	glColor4ub=(void(APIENTRY*)(GLubyte,GLubyte,GLubyte,GLubyte))get_funcaddr("glColor4ub");
	glPointSize=(void(APIENTRY*)(GLfloat))get_funcaddr("glPointSize");
	glHint=(void(APIENTRY*)(GLenum,GLenum))get_funcaddr("glHint");
	glBlendFunc=(void(APIENTRY*)(GLenum,GLenum))get_funcaddr("glBlendFunc");
	glMatrixMode=(void(APIENTRY*)(GLenum))get_funcaddr("glMatrixMode");
	glLoadIdentity=(void(APIENTRY*)())get_funcaddr("glLoadIdentity");
	glOrtho=(void(APIENTRY*)(GLdouble,GLdouble,GLdouble,GLdouble,GLdouble,GLdouble))get_funcaddr("glOrtho");
	glRotatef=(void(APIENTRY*)(GLfloat,GLfloat,GLfloat,GLfloat))get_funcaddr("glRotatef");
	glViewport=(void(APIENTRY*)(GLint,GLint,GLsizei,GLsizei))get_funcaddr("glViewport");
	glFogf=(void(APIENTRY*)(GLenum,GLfloat))get_funcaddr("glFogf");
	glGenTextures=(void(APIENTRY*)(GLsizei,GLuint*))get_funcaddr("glGenTextures");
	glBindTexture=(void(APIENTRY*)(GLenum,GLuint))get_funcaddr("glBindTexture");
	glTexParameteri=(void(APIENTRY*)(GLenum,GLenum,GLint))get_funcaddr("glTexParameteri");
	glTexImage2D=(void(APIENTRY*)(GLenum,GLint,GLint,GLsizei,GLsizei,GLint,GLenum,GLenum,const GLvoid *))get_funcaddr("glTexImage2D");
	glColor3f=(void(APIENTRY*)(GLfloat,GLfloat,GLfloat))get_funcaddr("glColor3f");
	glVertex2f=(void(APIENTRY*)(GLfloat,GLfloat))get_funcaddr("glVertex2f");
	glTexCoord2f=(void(APIENTRY*)(GLfloat,GLfloat))get_funcaddr("glTexCoord2f");
	glTexEnvf=(void(APIENTRY*)(GLenum,GLenum,GLfloat))get_funcaddr("glTexEnvf");
	glColor4f=(void(APIENTRY*)(GLfloat,GLfloat,GLfloat,GLfloat))get_funcaddr("glColor4f");
	glDepthMask=(void(APIENTRY*)(GLboolean))get_funcaddr("glDepthMask");
	glClearDepth=(void(APIENTRY*)(GLclampd))get_funcaddr("glClearDepth");
	glDepthFunc=(void(APIENTRY*)(GLenum))get_funcaddr("glDepthFunc");
	glShadeModel=(void(APIENTRY*)(GLenum))get_funcaddr("glShadeModel");
	glFinish=(void(APIENTRY*)())get_funcaddr("glFinish");

	glGenLists=(GLuint(APIENTRY*)(GLsizei))get_funcaddr("glGenLists");
	glDeleteLists=(void(APIENTRY*)(GLuint,GLsizei))get_funcaddr("glDeleteLists");
	glPopAttrib=(void(APIENTRY*)())get_funcaddr("glPopAttrib");
	glCallLists=(void(APIENTRY*)(GLsizei,GLenum,const GLvoid*))get_funcaddr("glCallLists");
	glListBase=(void(APIENTRY*)(GLuint))get_funcaddr("glListBase");
	glPushAttrib=(void(APIENTRY*)(GLbitfield))get_funcaddr("glPushAttrib");

	glRasterPos2f=(void(APIENTRY*)(GLfloat,GLfloat))get_funcaddr("glRasterPos2f");
	glGetBooleanv=(void(APIENTRY*)(GLenum, GLboolean *))get_funcaddr("glGetBooleanv");

	glEndList=(void(APIENTRY*)())get_funcaddr("glEndList");
	glTranslated=(void(APIENTRY*)(GLdouble,GLdouble,GLdouble))get_funcaddr("glTranslated");
	glNewList=(void(APIENTRY*)(GLuint,GLenum))get_funcaddr("glNewList");
	glVertex2i=(void(APIENTRY*)(GLint,GLint))get_funcaddr("glVertex2i");
	glPushMatrix=(void(APIENTRY*)())get_funcaddr("glPushMatrix");
	glPopMatrix=(void(APIENTRY*)())get_funcaddr("glPopMatrix");
	
	glDeleteTextures=(void(APIENTRY*)(GLsizei, const GLuint *))get_funcaddr("glDeleteTextures");

	wglUseFontBitmapsA=(void(APIENTRY*)(HDC,long,long,long))get_funcaddr("wglUseFontBitmapsA");
}

/* Quick utility function for texture creation */
int glfuncs::power_of_two(int input)
{
    int value = 1;

    while ( value < input ) {
        value <<= 1;
    }
    return value;
}

GLuint glfuncs::SDL_GL_LoadTexture(SDL_Surface *surface, GLfloat *texcoord)
{
    GLuint texture;
    int w, h;
    SDL_Surface *image;
    SDL_Rect area;
    Uint32 saved_flags;
    Uint8  saved_alpha;

    /* Use the surface width and height expanded to powers of 2 */
    w = power_of_two(surface->w);
    h = power_of_two(surface->h);
    texcoord[0] = 0.0f;         /* Min X */
    texcoord[1] = 0.0f;         /* Min Y */
    texcoord[2] = (GLfloat)surface->w / w;  /* Max X */
    texcoord[3] = (GLfloat)surface->h / h;  /* Max Y */

    image = SDL_CreateRGBSurface(
            SDL_SWSURFACE,
            w, h,
            32,
#if SDL_BYTEORDER == SDL_LIL_ENDIAN /* OpenGL RGBA masks */
            0x000000FF,
            0x0000FF00,
            0x00FF0000,
            0xFF000000
#else
            0xFF000000,
            0x00FF0000,
            0x0000FF00,
            0x000000FF
#endif
               );
    if ( image == NULL ) {
        return 0;
    }

    /* Save the alpha blending attributes */
    saved_flags = surface->flags&(SDL_SRCALPHA|SDL_RLEACCELOK);
    saved_alpha = surface->format->alpha;
    if ( (saved_flags & SDL_SRCALPHA) == SDL_SRCALPHA ) {
        SDL_SetAlpha(surface, 0, 0);
    }

    /* Copy the surface into the GL texture image */
    area.x = 0;
    area.y = 0;
    area.w = surface->w;
    area.h = surface->h;
    SDL_BlitSurface(surface, &area, image, &area);

    /* Restore the alpha blending attributes */
    if ( (saved_flags & SDL_SRCALPHA) == SDL_SRCALPHA ) {
        SDL_SetAlpha(surface, saved_flags, saved_alpha);
    }

    /* Create an OpenGL texture for the image */
    glGenTextures(1, &texture);
    glBindTexture(GL_TEXTURE_2D, texture);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
    glTexImage2D(GL_TEXTURE_2D,
             0,
             GL_RGBA,
             w, h,
             0,
             GL_RGBA,
             GL_UNSIGNED_BYTE,
             image->pixels);
    SDL_FreeSurface(image); /* No longer needed */

    return texture;
}

SDL_Surface *glfuncs::load_image( std::string filename ) 
{ 
	//The image that's loaded 
	SDL_Surface* loadedImage = NULL; 
	
	//The optimized image that will be used 
	SDL_Surface* optimizedImage = NULL; 
	
	//Load the image using SDL_image 
	loadedImage = IMG_Load( filename.c_str() ); 
	
	//If the image loaded 
	if( loadedImage != NULL ) 
	{ 
		//Create an optimized image 
		optimizedImage = SDL_DisplayFormatAlpha( loadedImage ); 
		//Free the old image 
		SDL_FreeSurface( loadedImage ); 
	} 
	
	//Return the optimized image 
	return optimizedImage; 
}

/* Taken from SDL Tutorial Lesson 13
 * http://lazyfooproductions.com/SDL_tutorials/lesson13/index.php
 * 
 * Highly modified
 */

bool glfuncs::intersect(SDL_Rect &A, SDL_Rect &B, int* x , int* y)
{
	return intersectX(A,B,x) && intersectY(A,B,y);
}

bool glfuncs::intersectX(SDL_Rect &A, SDL_Rect &B, int* x)
{
	bool returnme;
	//The sides of the rectangles
    int leftA, leftB;
    int rightA, rightB;
	
	//Calculate the sides of rect A
    leftA = A.x;
    rightA = A.x + A.w;

	//Calculate the sides of rect B
    leftB = B.x;
    rightB = B.x + B.w;

	//If any of the sides from A are outside of B
    if( rightA <= leftB || leftA >= rightB )
		returnme = false;
	else 
		returnme = true;

	if(x != NULL)
	{
		int centerAx = A.x + A.w/2;
		int centerBx = B.x + B.w/2;

		// this corresponds to the direction 
		// b must be moved
		bool left;

		if(returnme)
			left = (centerAx >= centerBx);
		else
			left = (centerAx <= centerBx);
		
		//this is the amount b needs to be moved
		*x = min(abs(leftB-rightA),abs(leftA-rightB));
		
		// make sure the sign is right
		if(left)
			*x = -(*x);
	}
	return returnme;

}
bool glfuncs::intersectY(SDL_Rect &A, SDL_Rect &B, int* y)
{
	bool returnme;
	//The sides of the rectangles
	int topA, topB;
    int bottomA, bottomB;

	//Calculate the sides of rect A
	topA = A.y;
    bottomA = A.y + A.h;
        
    //Calculate the sides of rect B
    topB = B.y;
    bottomB = B.y + B.h;


	//If any of the sides from A are outside of B
    if( bottomA <= topB || topA >= bottomB )
		returnme = false;
	else 
		returnme = true;

	if(y != NULL)
	{
		
		int centerAy = A.y + A.h/2;
		int centerBy = B.y + B.h/2;

		// this corresponds to the direction
		// b must be moved
		bool up;
		
		if(returnme)
			up = (centerAy >= centerBy);
		else
			up = (centerAy <= centerBy);

		//this is the amount b needs to be moved
		*y = min(abs(bottomB-topA),abs(bottomA-topB));
		
		// make sure the sign is right
		if(up)
			*y = -(*y);

		
	}

	

	return returnme;
}

/* Taken from http://nehe.gamedev.net/data/lessons/lesson.asp?lesson=17 */
GLuint glfuncs::buildFont(double scale, double width, double height, int fontTexture)			// Build Our Bitmap Font
{
	GLuint base;

	double	cx;											// Holds Our X Character Coord
	double	cy;											// Holds Our Y Character Coord

	base=glGenLists(96);								// Creating 256 Display Lists
	glBindTexture(GL_TEXTURE_2D, fontTexture);			// Select Our Font Texture

	double invW = 0.0625;
	double invH = 0.125;

	for (int loop=0; loop<96; loop++)						// Loop Through All 256 Lists
	{
		cx=(loop%16)*invW;						// X Position Of Current Character
		cy=(loop/16)*invH;						// Y Position Of Current Character

		glNewList(base+loop,GL_COMPILE);				// Start Building A List
			glBegin(GL_QUADS);							// Use A Quad For Each Character
				glTexCoord2f(cx,cy+invH);			// Texture Coord (Bottom Left)
				glVertex2i(0,height*scale);						// Vertex Coord (Bottom Left)
				glTexCoord2f(cx+invW,cy+invH);	// Texture Coord (Bottom Right)
				glVertex2i(width*scale,height*scale);						// Vertex Coord (Bottom Right)
				glTexCoord2f(cx+invW,cy);			// Texture Coord (Top Right)
				glVertex2i(width*scale,0);						// Vertex Coord (Top Right)
				glTexCoord2f(cx,cy);					// Texture Coord (Top Left)
				glVertex2i(0,0);						// Vertex Coord (Top Left)
			glEnd();									// Done Building Our Quad (Character)
			glTranslated(width*scale-1,0,0);						// Move To The Right Of The Character
		glEndList();									// Done Building The Display List
	}													// Loop Until All 256 Are Built

	return base;

	/*
	SDL_SysWMinfo info;
	SDL_VERSION(&info.version);
	SDL_GetWMInfo(&info);

	HWND hWnd = info.window;

	HDC hDC = GetDC(hWnd); 

	HFONT	font;										// Windows Font ID
	HFONT	oldfont;									// Used For Good House Keeping

	GLuint base = glGenLists(96);				// Storage For 96 Characters

	font = CreateFont(	-1*height,						// Height Of Font
						0,								// Width Of Font
						0,								// Angle Of Escapement
						0,								// Orientation Angle
						FW_BOLD,						// Font Weight
						FALSE,							// Italic
						FALSE,							// Underline
						FALSE,							// Strikeout
						ANSI_CHARSET,					// Character Set Identifier
						OUT_TT_PRECIS,					// Output Precision
						CLIP_DEFAULT_PRECIS,			// Clipping Precision
						ANTIALIASED_QUALITY,			// Output Quality
						FF_DONTCARE|DEFAULT_PITCH,		// Family And Pitch
						"Courier New");					// Font Name

	oldfont = (HFONT)SelectObject(hDC, font);           // Selects The Font We Want
	wglUseFontBitmaps(hDC, 32, 96, base);				// Builds 96 Characters Starting At Character 32
	SelectObject(hDC, oldfont);							// Selects The Font We Want
	DeleteObject(font);									// Delete The Font

	return base;
	*/
}

/* Taken from http://nehe.gamedev.net/data/lessons/lesson.asp?lesson=17 */
GLvoid glfuncs::killFont()									// Delete The Font List
{

	
	glDeleteLists(_bigBase, 96);							// Delete All 96 Characters
	glDeleteLists(_smallBase, 96);
}

/* Taken from http://nehe.gamedev.net/data/lessons/lesson.asp?lesson=17 */
GLvoid glfuncs::glPrint(GLint x, GLint y, std::string text, bool small)
{
	// FIXME: HACK!!!!!!
	int height;
	int width;

	if(small) {
		glBindTexture(GL_TEXTURE_2D, _smallFont);				// Select Our Font Texture
		height = Screen::height();
		width = Screen::width();
	}
	else {
		glBindTexture(GL_TEXTURE_2D, _bigFont);
		height = Screen::height();
		width = Screen::width();
	}

	glDisable(GL_DEPTH_TEST);							// Disables Depth Testing
	glMatrixMode(GL_PROJECTION);						// Select The Projection Matrix
	glPushMatrix();										// Store The Projection Matrix
	glLoadIdentity();									// Reset The Projection Matrix
	glOrtho(0,width,height,0,-1,1);						// Set Up An Ortho Screen
	glMatrixMode(GL_MODELVIEW);							// Select The Modelview Matrix
	glPushMatrix();										// Store The Modelview Matrix
	glLoadIdentity();									// Reset The Modelview Matrix

	glTranslated(x,y,0);									// Position The Text (0,0 - Bottom Left)

	if(small)
		glListBase(_smallBase-32);
	else
		glListBase(_bigBase-32);

	glCallLists(strlen(text.c_str()),GL_UNSIGNED_BYTE,text.c_str());	// Write The Text To The Screen

	glMatrixMode(GL_PROJECTION);						// Select The Projection Matrix
	glPopMatrix();										// Restore The Old Projection Matrix
	glMatrixMode(GL_MODELVIEW);							// Select The Modelview Matrix
	glPopMatrix();										// Restore The Old Projection Matrix
	glEnable(GL_DEPTH_TEST);							// Enables Depth Testing

	

	/*

	if (text == "")									// If There's No Text
		return;											// Do Nothing

	glPushAttrib(GL_LIST_BIT);							// Pushes The Display List Bits

	if(small)
		glListBase(_small - 32);
	else
		glListBase(_base - 32);								// Sets The Base Character to 32

	glCallLists(strlen(text.c_str()), GL_UNSIGNED_BYTE, text.c_str());	// Draws The Display List Text
	glPopAttrib();										// Pops The Display List Bits
	*/
}


int glfuncs::loadTexture(std::string filename, GLfloat coords[4], SDL_Rect & rect)
{
	SDL_Surface *surface = load_image(filename);
	rect.x = 0;
    rect.y = 0;
    rect.w = surface->w;
    rect.h = surface->h;
	int texture = SDL_GL_LoadTexture(surface,coords);
	SDL_FreeSurface(surface);
	return texture;
}