/*
* This code is released under the GNU General Public License.  See COPYING for 
* details.  Copyright 2003 John Spray: spray_john@users.sourceforge.net
*/

//Visual::SDL_GL_LoadTexture(SDL_Surface*, float*) is Copyright Sam Lantinga.

#include <SDL_opengl.h>
#define GL_MIRRORED_REPEAT 0x8370
#include <SDL.h>
#include <string.h>
#include <string>
using namespace std;
#include <physfs.h>
#include <SDL_image.h>
#include <math.h>
#ifndef M_2PI
#define M_2PI 6.28318
#endif

#include "Visual.h"
#include "LList.h"
#include "Particle.h"
#include "Texture.h"
#include "Camera.h"
#include "Config.h"



extern Config GLOB_conf;

Visual::~Visual()
{
	LListItem<Texture>* texitem;

	texitem=texturelist.head;
	while(texitem){
		glDeleteTextures(1,(GLuint*)&texitem->data.glid);
		texitem=texitem->next;
	}

	cameralist.Flush();

	glFinish();

	if(scorefont){
		TTF_CloseFont(scorefont);
		if(verbose)
			printf("Visual::~Visual: Closing scorefont\n");
	}

	if(menufont){
		TTF_CloseFont(menufont);
		if(verbose)
			printf("Visual::~Visual: Closing menufont\n");
	}

}

Camera* Visual::GetMainCam()
{
	      return &cameralist.head->data;
}

Visual::Visual(Game* newgame)
{
	string fontpath,datadir,fontname;
	
	fontname="helmetr.ttf";
	datadir=DATADIR;
	fontpath=datadir+fontname;
	
	game=newgame;

	texerrors=0;

	verbose=newgame->verbose;
	
	score=newgame->score;

	scorefont=NULL;
	if(verbose)
		printf("Visual::Visual: opening scorefont\n");
	scorefont=TTF_OpenFont(fontpath.c_str(), 34);
	if(!scorefont){
			printf("Visual::Visual: failed to open scorefont with error %s\n",SDL_GetError());
			return;
	}

	menufont=NULL;
	if(verbose)
		printf("Visual::Visual: opening menufont\n");
	menufont=TTF_OpenFont(fontpath.c_str(),40);
	if(!menufont){
		printf("Visual::Visual: failed to open menufont with error %s\n",SDL_GetError());
		return;
	}

	strcpy(curtex,"NOTHING");

	ResetCams();
}

void Visual::ResetCams()
{

	cameralist.Flush();

	Camera* newcam=new Camera();
	if(!GLOB_conf.twoplayer){
  newcam->scorefont=scorefont;
	newcam->visual=this;
	newcam->viewportwidth=game->screen->w;
	newcam->viewportheight=game->screen->h;
	newcam->viewporty=0;
	newcam->targetplayer=game->player;
	cameralist.Add(newcam);
	}
	else{
  newcam->scorefont=scorefont;
	newcam->visual=this;
	newcam->viewportwidth=game->screen->w;
	newcam->viewportheight=game->screen->h/2;
	newcam->viewporty=0;
	newcam->targetplayer=game->player;
	cameralist.Add(newcam);
	newcam->viewporty=game->screen->h/2;
	newcam->targetplayer=game->player2;
	cameralist.Add(newcam);
	}
	delete newcam;

}

void Visual::Draw()
{
	if(score<game->score){
			score+=(int)game->dtf;
			if(score>game->score)
				score=game->score;
		}

	glClear(GL_COLOR_BUFFER_BIT);

	LListItem<Camera>* item=cameralist.head;
	while(item){
		item->data.Animate();
		item->data.Draw();
		item=item->next;
	}

	SDL_GL_SwapBuffers();
}

void Visual::UpdateParticles()
{
	LListItem<Particle>* item;

	item=particlelist.head;
	while(item){
		item->data.Animate(game->dtf);
		if(item->data.life<=0){
			item=particlelist.Del(item);
			continue;
		}
		if(item->data.collide) if(item->data.Collide(game)){
			item=particlelist.Del(item);
			continue;
		}
		item=item->next;
	}
}



int Visual::LoadMesh(char* meshfile)
{
	LListItem<Mesh>* item;
	Mesh* newmesh;

	item=meshlist.head;
	while(item){
		if(!strcmp(meshfile,item->data.filename)){
			printf("Visual::LoadMesh: mesh %s already loaded!\n",meshfile);
			return 0; 
		}
		item=item->next;
	}

	newmesh=new Mesh();
	newmesh->visual=this;
	if(newmesh->Load(meshfile)){
					printf("Visual::LoadMesh: mesh %s could not be loaded\n",meshfile);
					return -1;
	}
	
	meshlist.Add(newmesh);
	return 0;
	//deleting it segfaults!
	//delete newmesh;
}

void Visual::DrawMesh(char* meshfile)
{
	LListItem<Mesh>* item;

	item=meshlist.head;
	while(item){
		if(!strcmp(meshfile,item->data.filename)){
			item->data.Draw();
			return;
		}
		item=item->next;
	}

	fprintf(stderr,"Visual::DrawMesh: %s is not loaded!\n",meshfile);
}

int Visual::LoadTexture(char* filename)
{

	LListItem<Texture>* item;

	item=texturelist.head;
	while(item){
		if(!strcmp(filename,item->data.filename)){
			printf("Visual::LoadTexture: texture %s already loaded!\n",filename);
			return 0;
		}
		item=item->next;
	}

	Texture newtex;
	SDL_Surface *TextureImage;
	SDL_RWops *RW;
	PHYSFS_file* filehandle;
	void* filedata;
	long filesize=0;



	strcpy(newtex.filename,filename);

	filehandle=PHYSFS_openRead(filename);

	if(!filehandle) {printf("Visual::LoadTexture: PHYSFS_openRead failed on %s with error %s!\n",filename,PHYSFS_getLastError()); return 1;}

	filesize=PHYSFS_fileLength(filehandle);

	filedata=malloc(filesize);
	if(!filedata) {printf("Visual::LoadTexture: malloc failed!  Out of memory?\n"); return 1;}
	if(PHYSFS_read(filehandle,filedata,1,filesize)!=filesize){
		printf("Visual::LoadTexture: PHYSFS_read and PHYSFS_fileLength disagree!  Aborting.\n");
		free(filedata);
		if(!PHYSFS_close(filehandle))
			printf("ScoreBoard::Load: PHYSFS_close failed with error %s\n",PHYSFS_getLastError());
		return 1;
	}

	RW=SDL_RWFromMem(filedata,filesize);
	if(!RW){
		printf("Visual::LoadTexture: SDL_RWFromMem failed with error %s\n",SDL_GetError());
		free(filedata);
		if(!PHYSFS_close(filehandle))
			printf("ScoreBoard::Load: PHYSFS_close failed with error %s\n",PHYSFS_getLastError());
		return 1;
	}

	TextureImage=IMG_LoadPNG_RW(RW);
	if(!TextureImage){
		printf("Visual::LoadTexture: IMG_LoadPNG_RW failed with error %s\n",IMG_GetError());
		if(!PHYSFS_close(filehandle))
			printf("ScoreBoard::Load: PHYSFS_close failed with error %s\n",PHYSFS_getLastError());
		SDL_FreeRW(RW);
		free(filedata);
		return 1;
	}

	free(filedata);
	if(!PHYSFS_close(filehandle))
		printf("ScoreBoard::Load: PHYSFS_close failed with error %s\n",PHYSFS_getLastError());
	SDL_FreeRW(RW);



	int x;
	int y;
	unsigned char* pixels=(unsigned char*)TextureImage->pixels;
	unsigned char* pixels2=(unsigned char*)malloc(TextureImage->w*TextureImage->h*4);
	memcpy(pixels2,pixels,TextureImage->w*TextureImage->h*4);
	SDL_LockSurface(TextureImage);	
	//all incoming surfaces get flipped in y:
	for(x=0;x<4*(TextureImage->w);x+=4){
	for(y=0;y<4*(TextureImage->h);y+=4){
		pixels[x+TextureImage->w*y+0]=pixels2[x+TextureImage->w*(TextureImage->h*4-y-4)+0];
		pixels[x+TextureImage->w*y+1]=pixels2[x+TextureImage->w*(TextureImage->h*4-y-4)+1];
		pixels[x+TextureImage->w*y+2]=pixels2[x+TextureImage->w*(TextureImage->h*4-y-4)+2];
		pixels[x+TextureImage->w*y+3]=pixels2[x+TextureImage->w*(TextureImage->h*4-y-4)+3];
		
	}
	}
	int scalefactor=GLOB_conf.texscale;
	//and scaled down if scalefactor says so
	if(scalefactor!=1){
		memcpy(pixels2,pixels,TextureImage->w*TextureImage->h*4);
		for(x=0;x<4*(TextureImage->w/scalefactor);x+=4){
		for(y=0;y<4*(TextureImage->h/scalefactor);y+=4){
			pixels[x+(TextureImage->w/scalefactor)*y+0]=pixels2[(x*scalefactor)+TextureImage->w*(y*scalefactor)+0];
			pixels[x+(TextureImage->w/scalefactor)*y+1]=pixels2[(x*scalefactor)+TextureImage->w*(y*scalefactor)+1];
			pixels[x+(TextureImage->w/scalefactor)*y+2]=pixels2[(x*scalefactor)+TextureImage->w*(y*scalefactor)+2];
			pixels[x+(TextureImage->w/scalefactor)*y+3]=pixels2[(x*scalefactor)+TextureImage->w*(y*scalefactor)+3];
		}
		}
  }
	free(pixels2);
	SDL_UnlockSurface(TextureImage);


	glGenTextures( 1, (GLuint*)&newtex.glid );


	glBindTexture(GL_TEXTURE_2D,newtex.glid);

	glTexImage2D( GL_TEXTURE_2D, 0, 4, TextureImage->w/scalefactor,
		TextureImage->h/scalefactor, 0, GL_RGBA,
		GL_UNSIGNED_BYTE, TextureImage->pixels );


	gluBuild2DMipmaps( GL_TEXTURE_2D, 4,TextureImage->w/scalefactor,
			  TextureImage->h/scalefactor, GL_RGBA,
			  GL_UNSIGNED_BYTE, TextureImage->pixels );

	glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR );
	glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR );
	glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_MIRRORED_REPEAT);
	glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_MIRRORED_REPEAT);

	SDL_FreeSurface( TextureImage );

	texturelist.Add(&newtex);

	int errornumber;
	while((errornumber=glGetError())!=GL_NO_ERROR){
		printf("Visual::LoadTexture: OpenGL error: %s while loading %s\n",HandleGlError(errornumber),filename);
	}

	if(verbose)
		printf("Visual::LoadTexture: Successfully loaded %s into glTexture %d\n",newtex.filename,newtex.glid);

	return 0;
}

void Visual::NewParticle(Particle* newpart)
{
	newpart->startlife=newpart->life;
	newpart->spinoffset=((float)rand()/(float)RAND_MAX)*180;
	particlelist.Add(newpart);
}

int Visual::InitGL()
{
	GLfloat backcolor[4]={0.0f,1.0f,0.0f,0.1f};
//	GLfloat fogcolor[4]={0.8f,0.8f,0.8f,0.1f};

	if(GLOB_conf.verbose)
		printf("Visual::InitGL: SDL_Surface w=%d,h=%d,bpp=%d\n",game->screen->w,game->screen->h,game->screen->format->BitsPerPixel);

	glShadeModel( GL_SMOOTH );

	glEnable(GL_TEXTURE_2D);

	glCullFace( GL_BACK );
	glFrontFace( GL_CCW );
	glEnable( GL_CULL_FACE );
	glEnable(GL_DEPTH_TEST);
	glDepthFunc(GL_LEQUAL);

	glClearColor( backcolor[0],backcolor[1],backcolor[2],backcolor[3] );

  /*glFogi(GL_FOG_MODE, GL_LINEAR);
	glFogfv(GL_FOG_COLOR, fogcolor);
	glFogf(GL_FOG_DENSITY, 0.35f);
	glHint(GL_FOG_HINT, GL_NICEST);
	glFogf(GL_FOG_START, 50);
	glFogf(GL_FOG_END, 100);
	glEnable(GL_FOG);*/

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

	glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

	return 0;
}

int Visual::UseTexture(char* filename)
{
	LListItem<Texture>* item=NULL;

	if(!strcmp(filename,curtex))
		return 0;

	item=texturelist.head;
	while(item){
		if(!strcmp(filename,item->data.filename)){
			glBindTexture(GL_TEXTURE_2D,item->data.glid);
			strcpy(curtex,filename);
			return 0;
		}
		item=item->next;
	}

#ifndef RELEASE

	if(texerrors>50)
		return -1;
	texerrors++;

	printf("Visual::UseTexture: %s not loaded!  Loading.\n",filename);
	if(LoadTexture(filename))
		return -1;
	return UseTexture(filename);

#else

	printf("ERROR: Visual::UseTexture: %s not loaded!\n",filename);
  return -1;

#endif //RELEASE
}

int Visual::UnLoadTexture(char* filename)
{
	LListItem<Texture>* item;

	item=texturelist.head;
	while(item){
		if(!strcmp(filename,item->data.filename)){
			glDeleteTextures(1,(GLuint*)&item->data.glid);
			if(!strcmp(curtex,filename))
				strcpy(curtex,"NOTHING");
			item=texturelist.Del(item);
			if(verbose) printf("Unloaded texture %s successfully\n",filename);
			return 0;
		}
		item=item->next;
	}

	printf("Visual::UnLoadTexture: Can't!  %s not loaded!\n",filename);
	  return -1;

}

int Visual::UnLoadMesh(char* filename)
{
	LListItem<Mesh>* item;

	item=meshlist.head;
	while(item){
		if(!strcmp(filename,item->data.filename)){
			item=meshlist.Del(item);
			if(verbose) printf("Unloaded mesh %s successfully\n",filename);
			return 0;
		}
		item=item->next;
	}

	printf("Visual::UnLoadMesh: Can't! %s not loaded!\n",filename);
	  return -1;

}

char* Visual::HandleGlError(int errornumber)
{
	//FIXME:  waste of memory.  Do this properly.
	static char errorstring[128];

	switch(errornumber){
			case GL_INVALID_VALUE:
				strcpy(errorstring,"GL_INVALID_VALUE");
				break;
			case GL_INVALID_ENUM:
				strcpy(errorstring,"GL_INVALID_ENUM");
				break;
			case GL_INVALID_OPERATION:
				strcpy(errorstring,"GL_INVALID_OPERATION");
				break;
			case GL_STACK_OVERFLOW:
				strcpy(errorstring,"GL_STACK_OVERFLOW");
				break;
			case GL_STACK_UNDERFLOW:
				strcpy(errorstring,"GL_STACK_UNDERFLOW");
				break;
			case GL_OUT_OF_MEMORY:
				strcpy(errorstring,"GL_OUT_OF_MEMORY");
				break;
			case GL_TABLE_TOO_LARGE:
				strcpy(errorstring,"GL_TABLE_TOO_LARGE");
				break;
			default:
				strcpy(errorstring,"Unknown error code");
				break;
		}
	return errorstring;
}

unsigned int Visual::MakeHash(char* source){
	unsigned int hash=0;
	int i=0;
	while(source[i]!=0){
		hash^=source[i++];
	}
	return hash;
}


int power_of_two(int target)
{
	int x=1;
	while(x<target)
		x*=2;
	return x;
}

//This function from SDL_ttf example programs, (c) Sam Lantinga
int Visual::SDL_GL_LoadTexture(SDL_Surface *surface, float *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_LINEAR);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
	glTexImage2D(GL_TEXTURE_2D,
		     0,
		     GL_RGBA,
		     w, h,
		     0,
		     GL_RGBA,
		     GL_UNSIGNED_BYTE,
		     image->pixels);
	SDL_FreeSurface(image); /* No longer needed */

	strcpy(curtex,"NOTHING");

	return texture;
}

void Visual::ShowLoading(float howfar)
{
	glDisable(GL_DEPTH_TEST);
	glDisable(GL_LIGHTING);
	glMatrixMode(GL_PROJECTION);
	glPushMatrix();
	glLoadIdentity();
	gluOrtho2D(0,game->screen->w,0,game->screen->h);
	glMatrixMode(GL_MODELVIEW);
	glPushMatrix();
	glLoadIdentity();

	glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

	LoadTexture("loading.png");
	if(UseTexture("loading.png")==-1){
		printf("Visual::ShowLoading: UseTexture returned -1\n");
		glPopMatrix();
		glMatrixMode(GL_PROJECTION);
		glPopMatrix();
		return;
	}

	glColor4f(1.0f,1.0f,1.0f,1.0f);
	glBegin(GL_QUADS);
		glTexCoord2f(0.0f,0.0f);
		glVertex2f(0.0f,0.0f);
		glTexCoord2f(1.0f,0.0f);
		glVertex2f(game->screen->w,0.0f);
		glTexCoord2f(1.0f,1.0f);
		glVertex2f(game->screen->w,game->screen->h);
		glTexCoord2f(0.0f,1.0f);
		glVertex2f(0,game->screen->h);
	glEnd();

	if(howfar!=-1.0f){
		glDisable(GL_TEXTURE_2D);
		glBegin(GL_QUADS);
			glColor4f(1.0f,0.0f,0.0f,0.7f);
			glVertex2f(game->screen->w/4,game->screen->h*0.2f);
			glVertex2f(game->screen->w/4,game->screen->h*0.17f);
			glColor4f(0.0f,howfar,0.0f,0.7f);
			glVertex2f(game->screen->w/4+(game->screen->w/2)*howfar,game->screen->h*0.17f);
			glVertex2f(game->screen->w/4+(game->screen->w/2)*howfar,game->screen->h*0.2f);
		glEnd();
		glEnable(GL_TEXTURE_2D);
	}

	glPopMatrix();

	SDL_GL_SwapBuffers();

	UnLoadTexture("loading.png");

	glMatrixMode(GL_PROJECTION);
	glPopMatrix();
	glMatrixMode(GL_MODELVIEW);
	glEnable(GL_DEPTH_TEST);
	glEnable(GL_LIGHTING);
}

