
#include <SDL/SDL.h>
#include <SDL/SDL_opengl.h>
#include <SDL/SDL_image.h>

#include "include/main.h"
#include "include/ArcBall.h"
#include "include/object.h"

struct ObjectLocal
{
	Matrix3fT LastRot;
	Matrix3fT ThisRot;
	Matrix4fT rot_matrix;
	Vector3fT EnVec, StVec;
	struct Texture *first_texture;
	GLfloat lightAmbient[4];
	GLfloat lightDiffuse[4];
	GLfloat lightDiffusePos[4];
}local;


GLfloat *obj_getLightDiffusePos()
{
	return local.lightDiffusePos;
}
GLfloat *obj_getLightDiffuse()
{
	return local.lightDiffuse;
}

GLfloat *obj_getLightAmbient()
{
	return local.lightAmbient;
}
Matrix3fT *obj_getLastRot()
{
	return &local.LastRot;
}

Matrix4fT *obj_getRotMatrix()
{
	return &local.rot_matrix;
}

struct Texture *obj_getFirstTexture()
{
	return local.first_texture;
}

void obj_setFirstTexture(struct Texture *t)
{
	local.first_texture = t;
}

/**
 * draw pyramid
 */
void obj_pyramidDraw()
{
	struct Texture *cur = local.first_texture;
		
	if(cur)
	{
		glEnable(GL_TEXTURE_2D);
		glBindTexture(GL_TEXTURE_2D, cur->id);
		glBegin( GL_TRIANGLES );
		  glColor4f(   1,  1,  1, 0.5 );
		  glTexCoord2f(0,0);
		  glTexCoord2f(1,0);
		  glVertex3f(  0,  1,  0 ); 
		  glTexCoord2f(1,1);
		  glVertex3f( -1, -1,  1 ); 
		  glTexCoord2f(0,1);
		  glVertex3f(  1, -1,  1 ); 
		glEnd();
		  
		if(cur->next)
		{
			cur = cur->next;
			glBindTexture(GL_TEXTURE_2D, cur->id);
		}
		else
			cur = local.first_texture;

		glBegin(GL_TRIANGLES);
		  glColor4f(   1,  1,  1, 0.5 );
		  glTexCoord2f(0,0);
		  glTexCoord2f(1,0);
		  glVertex3f(  0,  1,  0 ); 
		  glTexCoord2f(1,1);
		  glVertex3f(  1, -1,  1 ); 
		  glTexCoord2f(0,1);
		  glVertex3f(  1, -1, -1 ); 
		glEnd( );		

		if(cur->next)
		{
			cur = cur->next;
			glBindTexture(GL_TEXTURE_2D, cur->id);
		}
		else
			cur = local.first_texture;
		
		glBegin(GL_TRIANGLES);
          glColor4f(   1,  1,  1, 0.5 );		
		  glTexCoord2f(0,0);
		  glTexCoord2f(1,0);
		  glVertex3f(  0,  1,  0 ); 
		  glTexCoord2f(1,1);
		  glVertex3f(  1, -1, -1 ); 
		  glTexCoord2f(0,1);
		  glVertex3f( -1, -1, -1 ); 
		glEnd( );
		
		if(cur->next)
		{
			cur = cur->next;
			glBindTexture(GL_TEXTURE_2D, cur->id);
		}
		else
			cur = local.first_texture;
		
		glBegin(GL_TRIANGLES);
		  glColor4f(   1,  1,  1, 0.5 );
		  glTexCoord2f(0,0);
		  glTexCoord2f(1,0);
		  glVertex3f(  0,  1,  0 ); 
		  glTexCoord2f(1,1);
		  glVertex3f( -1, -1, -1 ); 
		  glTexCoord2f(0,1);
		  glVertex3f( -1, -1,  1 ); 
		glEnd( );                   

		if(cur->next)
		{
			cur = cur->next;
			glBindTexture(GL_TEXTURE_2D, cur->id);
		}
		else
			cur = local.first_texture;
		
		/* draw bottom */
		glBegin(GL_QUADS);
		    glColor4f(   1,  1,  1, 0.5 );
		    glTexCoord2f(0,0);
			glVertex3f(  -1,  -1,  1 ); 
		    glTexCoord2f(0,1);
			glVertex3f( -1, -1, -1 ); 
		    glTexCoord2f(1,1);
			glVertex3f( 1, -1,  -1 ); 
		    glTexCoord2f(1,0);
			glVertex3f( 1, -1,   1 );
		glEnd();
	}
	else
	{
		glDisable(GL_TEXTURE_2D);
		
		glBegin( GL_TRIANGLES );      
		  glColor4f(   1,  0,  0, 0.5 ); 
		  glVertex3f(  0,  1,  0 ); 
		  glColor4f(   0,  1,  0, 0.5 ); 
		  glVertex3f( -1, -1,  1 ); 
		  glColor4f(   0,  0,  1, 0.5 ); 
		  glVertex3f(  1, -1,  1 ); 
		
		  glColor4f(   1,  0,  0, 0.5 ); 
		  glVertex3f(  0,  1,  0 ); 
		  glColor4f(   0,  0,  1, 0.5 ); 
		  glVertex3f(  1, -1,  1 ); 
		  glColor4f(   0,  1,  0, 0.5 ); 
		  glVertex3f(  1, -1, -1 ); 
		
		  glColor4f(   1,  0,  0, 0.5 ); 
		  glVertex3f(  0,  1,  0 ); 
		  glColor4f(   0,  1,  0, 0.5 ); 
		  glVertex3f(  1, -1, -1 ); 
		  glColor4f(   0,  0,  1, 0.5 ); 
		  glVertex3f( -1, -1, -1 ); 
		
		  glColor4f(   1,  0,  0, 0.5 ); 
		  glVertex3f(  0,  1,  0 ); 
		  glColor4f(   0,  0,  1, 0.5 ); 
		  glVertex3f( -1, -1, -1 ); 
		  glColor4f(   0,  1,  0, 0.5 ); 
		  glVertex3f( -1, -1,  1 ); 
		glEnd( );                   
		
		/* draw bottom */
		glBegin(GL_QUADS);
			glColor4f(   1,  0,  0, 0.5 ); 
			glVertex3f(  -1,  -1,  1 );
			glColor4f(   0,  0,  1, 0.5 ); 
			glVertex3f( -1, -1, -1 ); 
			glColor4f(   0,  1,  0, 0.5 ); 
			glVertex3f( 1, -1,  -1 ); 
			glColor4f(   1,  1,  0, 0.5 ); 
			glVertex3f( 1, -1,   1 );
		glEnd();
	}
}

/**
 * draw cube
 */
void obj_cubeDraw()
{

	struct Texture *cur = local.first_texture;

	
	if(cur)
	{
		glEnable(GL_TEXTURE_2D);
		
		/* TOP-surface */
		glBindTexture(GL_TEXTURE_2D, cur->id);
		glBegin(GL_QUADS);
		    glNormal3f( 0, 1,0);
		    glColor4f(   1,  1,  1, 0.5 );
			glTexCoord2f(0,1);
			glVertex3f(  1,  1, -1 ); 
			glTexCoord2f(1,1);
			glVertex3f( -1,  1, -1 ); 
			glTexCoord2f(1,0);
			glVertex3f( -1,  1,  1 ); 
			glTexCoord2f(0,0);
			glVertex3f(  1,  1,  1 ); 
		glEnd();
		
		if(cur->next)
		{
			cur = cur->next;
		}
		else
			cur = local.first_texture;

		/* LEFT-surface */
		glBindTexture(GL_TEXTURE_2D, cur->id);		
		glBegin(GL_QUADS);
			glNormal3f( -1, 0, 0);
		    glColor4f(   1,  1,  1, 0.5 );
			glTexCoord2f(0,1);
			glVertex3f( -1,  1,  1 ); 
			glTexCoord2f(1,1);
			glVertex3f( -1,  1, -1 ); 
			glTexCoord2f(1,0);
			glVertex3f( -1, -1, -1 ); 
			glTexCoord2f(0,0);
			glVertex3f( -1, -1,  1 ); 
		glEnd();
			
		if(cur->next)
		{
			cur = cur->next;
		}
		else
			cur = local.first_texture;
		
		/* BOTTOM-surface */
		glBindTexture(GL_TEXTURE_2D, cur->id);
		glBegin(GL_QUADS);
			glNormal3f( 0.0f,-1.0f, 0.0f);
			glColor4f(   1,  1,  1, 0.5 );
			glTexCoord2f(1,1);
			glVertex3f(  1, -1,  1 ); 
			glTexCoord2f(1,0);
			glVertex3f( -1, -1,  1 ); 
			glTexCoord2f(0,0);
			glVertex3f( -1, -1, -1 ); 
			glTexCoord2f(0,1);
			glVertex3f(  1, -1, -1 ); 
		glEnd();
			
		if(cur->next)
		{
			cur = cur->next;
		}
		else
			cur = local.first_texture;

		/* FRONT-surface */
		glBindTexture(GL_TEXTURE_2D, cur->id);		
		glBegin(GL_QUADS);
			glNormal3f( 0, 0, 1);
		    glColor4f(   1,  1,  1, 0.5 );
			glTexCoord2f(1,0);
			glVertex3f(  1,  1,  1 ); 
			glTexCoord2f(0,0);
			glVertex3f( -1,  1,  1 ); 
			glTexCoord2f(0,1);
			glVertex3f( -1, -1,  1 ); 
			glTexCoord2f(1,1);
			glVertex3f(  1, -1,  1 ); 
		glEnd();
			
		if(cur->next)
		{
			cur = cur->next;
		}
		else
			cur = local.first_texture;

		/* RIGHT-surface */
		glBindTexture(GL_TEXTURE_2D, cur->id);		
		glBegin(GL_QUADS);
			glNormal3f( 1, 0, 0);
			glColor4f(   1,  1,  1, 0.5 );
			glTexCoord2f(0,0);
			glVertex3f(  1,  1, -1 ); 
			glTexCoord2f(0,1);
			glVertex3f(  1,  1,  1 ); 
			glTexCoord2f(1,1);
			glVertex3f(  1, -1,  1 ); 
			glTexCoord2f(1,0);
			glVertex3f(  1, -1, -1 ); 
		glEnd();
			
		if(cur->next)
		{
			cur = cur->next;
		}
		else
			cur = local.first_texture;
		
		/* REAR-surface */
		glBindTexture(GL_TEXTURE_2D, cur->id);		
		glBegin(GL_QUADS);
			glNormal3f(0, 0, -1);	
		    glColor4f(   1,  1,  1, 0.5 );
			glTexCoord2f(0,0);
			glVertex3f(  1, -1, -1 ); 
			glTexCoord2f(-1,0);
			glVertex3f( -1, -1, -1 ); 
			glTexCoord2f(-1,1);
			glVertex3f( -1,  1, -1 ); 
			glTexCoord2f(0,1);
			glVertex3f(  1,  1, -1 ); 
		glEnd();
	}
	else
	{
		glDisable(GL_TEXTURE_2D);
		
		/* Set The Color To Blue One Time Only */
		glColor3f( 0.5f, 0.5f, 1.0f);
		
		glBegin( GL_QUADS );        
		  glNormal3f( 0, 1,0);
		  glColor3f(   0,  1,  0 ); 
		  glVertex3f(  1,  1, -1 ); /* Top Right Of The Quad (Top)      */
		  glVertex3f( -1,  1, -1 ); /* Top Left Of The Quad (Top)       */
		  glVertex3f( -1,  1,  1 ); /* Bottom Left Of The Quad (Top)    */
		  glVertex3f(  1,  1,  1 ); /* Bottom Right Of The Quad (Top)   */
		
		  glNormal3f( 0,-1, 0);
		  glColor3f(   1,  0.5f,  0 ); /* Set The Color To Orange          */
		  glVertex3f(  1, -1,  1 ); /* Top Right Of The Quad (Botm)     */
		  glVertex3f( -1, -1,  1 ); /* Top Left Of The Quad (Botm)      */
		  glVertex3f( -1, -1, -1 ); /* Bottom Left Of The Quad (Botm)   */
		  glVertex3f(  1, -1, -1 ); /* Bottom Right Of The Quad (Botm)  */
		
		  glNormal3f( 0, 0, 1);
		  glColor3f(   1,  0,  0 ); /* Set The Color To Red             */
		  glVertex3f(  1,  1,  1 ); /* Top Right Of The Quad (Front)    */
		  glVertex3f( -1,  1,  1 ); /* Top Left Of The Quad (Front)     */
		  glVertex3f( -1, -1,  1 ); /* Bottom Left Of The Quad (Front)  */
		  glVertex3f(  1, -1,  1 ); /* Bottom Right Of The Quad (Front) */
		
		  glNormal3f(0, 0, -1);
		  glColor3f(   1,  1,  0 ); /* Set The Color To Yellow          */
		  glVertex3f(  1, -1, -1 ); /* Bottom Left Of The Quad (Back)   */
		  glVertex3f( -1, -1, -1 ); /* Bottom Right Of The Quad (Back)  */
		  glVertex3f( -1,  1, -1 ); /* Top Right Of The Quad (Back)     */
		  glVertex3f(  1,  1, -1 ); /* Top Left Of The Quad (Back)      */
		
		  glNormal3f( -1, 0, 0);
		  glColor3f(   0,  0,  1 ); /* Set The Color To Blue            */
		  glVertex3f( -1,  1,  1 ); /* Top Right Of The Quad (Left)     */
		  glVertex3f( -1,  1, -1 ); /* Top Left Of The Quad (Left)      */
		  glVertex3f( -1, -1, -1 ); /* Bottom Left Of The Quad (Left)   */
		  glVertex3f( -1, -1,  1 ); /* Bottom Right Of The Quad (Left)  */
		
		  glNormal3f( 1, 0, 0);	
		  glColor3f(   1,  0,  1 ); /* Set The Color To Violet          */
		  glVertex3f(  1,  1, -1 ); /* Top Right Of The Quad (Right)    */
		  glVertex3f(  1,  1,  1 ); /* Top Left Of The Quad (Right)     */
		  glVertex3f(  1, -1,  1 ); /* Bottom Left Of The Quad (Right)  */
		  glVertex3f(  1, -1, -1 ); /* Bottom Right Of The Quad (Right) */
		glEnd( );                            /* Done Drawing The Quad            */
	}	

}

/**
 * rotate object according to mouse-movement
 */
void obj_rotate()
{
	Quat4fT ThisQuat;
	
			   
	
	if(m_getMouseClicked())
	{
		Matrix4fSetRotationFromMatrix3f(&local.rot_matrix, &local.ThisRot);	
	}
	
	if((m_getMouseDragged()))
	{
		if(m_getMouseClicked())
		{
			arc_drag(m_getMouseX(), m_getMouseY(), 
						&ThisQuat, &local.EnVec, &local.StVec);
			Matrix3fSetRotationFromQuat4f(&local.ThisRot, &ThisQuat);
			Matrix3fMulMatrix3f(&local.ThisRot, &local.LastRot);
			Matrix4fSetRotationFromMatrix3f(&local.rot_matrix, &local.ThisRot);
		}
	}
	else
	{
		if(m_getMouseClicked())
		{
			local.LastRot = local.ThisRot;
			arc_map2sphere(m_getMouseX(), m_getMouseY(), &local.StVec);
		}
	}
	
}

/**
 * count how much bits are set in integer
 */
static unsigned int countBits(int v)
{
	unsigned int c; // c accumulates the total bits set in v
	
	for (c = 0; v; c++)
	{
	  v &= v - 1; // clear the least significant bit set
	}
	
	return c;
}


/**
 * load image as GL texture
 */
int obj_textureLoad(const char *filename, GLuint *id)
{
    int result = -1;				/* Status indicator */
    SDL_Surface *t = NULL; 	/* Create storage space for the texture */
	GLenum format;

	
	printf("Loading: \"%s\"\n", filename);
	
    /* Load The Bitmap, Check For Errors, If Bitmap's Not Found Quit */
    if((id) && (t = IMG_Load(filename)))
	{

	    /* Set the status to true */
	    result = 0;

	    /* Create The Texture */
	    glGenTextures( 1, id );
	
		GL_CHECKERROR;
		
	    /* Typical Texture Generation Using Data From The Bitmap */
	    glBindTexture( GL_TEXTURE_2D, *id );
		GL_CHECKERROR;
		
		/* Pixel-format */
		if(t->format->Rmask == 255)
		{
			if(t->flags & SDL_SRCALPHA)
				format = GL_RGBA;
			else
				format = GL_RGB;
		}
		else
		{
			if(t->flags & SDL_SRCALPHA)
				format = GL_BGRA;
			else
				format = GL_BGR;
		}
		
		/* width power of 2? */
		if(countBits(t->w) != 1)
		{
			printf("ERROR: Texture width must be a power of 2!\n");
			result = -1;						
		}
		
		/* height power of 2? */
		if(countBits(t->h) != 1)
		{
			printf("ERROR: Texture height must be a power of 2!\n");
			result = -1;			
		}
		
		if(result >= 0)
		{
			
			/* Generate The Texture */
			glTexImage2D( GL_TEXTURE_2D, 0, 3, t->w, t->h, 0, format,
				  GL_UNSIGNED_BYTE, t->pixels );
	
			GL_CHECKERROR;
			
			/* Linear Filtering */
			glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR );
			glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR );
		
			GL_CHECKERROR;
		}
		
		/* Free up any memory we may have used */		
		SDL_FreeSurface( t );
    }
	else
	{
		id = NULL;
	}

    return result;
}

/**
 * flash ambient light
 */
void obj_lightAmbientFlash()
{
	local.lightAmbient[0] = LIGHT_FLASH_R;
	local.lightAmbient[1] = LIGHT_FLASH_G;
	local.lightAmbient[2] = LIGHT_FLASH_B;
	local.lightAmbient[3] = LIGHT_FLASH_A;
	glLightfv(GL_LIGHT1, GL_AMBIENT, local.lightAmbient);
}

/**
 * initialize OpenGL
 */
int obj_gl_init(int screen_width, int screen_height)
{
	/* protect against division by zero */
	if ( screen_height == 0 )
	screen_height = 1;
	
	/* Setup our viewport. */
	glViewport(0,0, (GLsizei) screen_width, (GLsizei) screen_height);
	GL_CHECKERROR;
	
	/* change to the projection matrix and set our viewing volume. */
	glMatrixMode( GL_PROJECTION );
	GL_CHECKERROR;
	glLoadIdentity( );

	/* Set our perspective */
	gluPerspective( 45.0f, 
				((GLfloat) screen_width / (GLfloat) screen_height),
				0.1f, 100.0f );
	GL_CHECKERROR;
	
	/* Make sure we're chaning the model view and not the projection */
	glMatrixMode( GL_MODELVIEW );
	GL_CHECKERROR;
	
	/* Reset The View */
	glLoadIdentity();
	GL_CHECKERROR;
	
	if(glGetError() != GL_NO_ERROR)
		return -1;
	else
		return 0;
}

/**
 * initialize this module
 */
int obj_init()
{
	int result = 0;
	
	printf("Initializing OpenGL...\n");
	
	local.rot_matrix.s.M00 = 1;
	local.rot_matrix.s.M11 = 1;
	local.rot_matrix.s.M22 = 1;
	local.rot_matrix.s.M33 = 1;
	
	/* initialize light */
	local.lightAmbient[0] = LIGHT_AMBIENT_R;
	local.lightAmbient[1] = LIGHT_AMBIENT_G;
	local.lightAmbient[2] = LIGHT_AMBIENT_B;
	local.lightAmbient[3] = LIGHT_AMBIENT_A;
	local.lightDiffuse[0] = LIGHT_DIFFUSE_R;
	local.lightDiffuse[1] = LIGHT_DIFFUSE_G;
	local.lightDiffuse[2] = LIGHT_DIFFUSE_B;
	local.lightDiffuse[3] = LIGHT_DIFFUSE_A;
	local.lightDiffusePos[0] = 0;
	local.lightDiffusePos[1] = 0;
	local.lightDiffusePos[2] = 2;
	local.lightDiffusePos[3] = 1;
	
	Matrix3fSetIdentity(&local.LastRot);
	Matrix3fSetIdentity(&local.ThisRot);
	
	/* Enable textures */
	glEnable(GL_TEXTURE_2D);
	GL_CHECKERROR;
	
	/* Enable smooth shading */
	glShadeModel( GL_SMOOTH );
	GL_CHECKERROR;
	
	/* init openGL */
	glClearColor(0,0,0,0.5);
	GL_CHECKERROR;
	
	/* Depth buffer setup */
	glClearDepth( 1.0f );
	GL_CHECKERROR;
	
	/* Enables Depth Testing */
	glEnable( GL_DEPTH_TEST );
	GL_CHECKERROR;
	
	/* The Type Of Depth Test To Do */
	glDepthFunc( GL_LEQUAL );
	GL_CHECKERROR;
	
	/* Really Nice Perspective Calculations */
	glHint( GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST );
	GL_CHECKERROR;
	
	if(!local.first_texture)
	{
		glEnable(GL_COLOR_MATERIAL);
		GL_CHECKERROR;
	}
	

	if(obj_gl_init(m_getScreenWidth(), m_getScreenHeight()) < 0)
	{
		printf("OpenGL init error!\n");
		result = -1;
	}
	
				
	glLightfv(GL_LIGHT1, GL_AMBIENT, local.lightAmbient);
	GL_CHECKERROR;
	glLightfv(GL_LIGHT2, GL_DIFFUSE, local.lightDiffuse);
	GL_CHECKERROR;
	glLightfv(GL_LIGHT2, GL_POSITION, local.lightDiffusePos);
	GL_CHECKERROR;
	glEnable(GL_LIGHT1);
	GL_CHECKERROR;
	glEnable(GL_LIGHT2);
	GL_CHECKERROR;

	
	return result;
}


/**
 * cleanup object-data
 */
void obj_cleanup()
{
	struct Texture *tmp, *tmp_f;
		
	/* free textures */
	for(tmp = local.first_texture; tmp; tmp = tmp_f)
	{
		free(tmp->filename);
		tmp_f = tmp->next;
		free(tmp);
	}	
	
	local.first_texture = NULL;
}
