//
// License: GPL
// Date: 25.02.2006
// Author: André Schnabel (thefrogs@web.de)
// http://www.masterspace.biz/andreschnabel/
//

// Framework.cpp

#include "Framework.h"
#include "State.h"

#ifdef DREAMCAST
	#include <SDL/SDL_dreamcast.h>
#endif

Framework *framework = NULL;

//
// Framework
// Constructor
//
Framework::Framework(FrameworkSettings *settings) : maxFps(25), drawFps(false), noRedraw(false)
{
	int result;
	
	#ifdef DREAMCAST
		Uint32 scrFlags = SDL_SWSURFACE;
	#else
		Uint32 scrFlags = SDL_HWSURFACE;
	#endif
	
	char vidInfo[MAX_STR_LENGTH], audioInfo[MAX_STR_LENGTH], keyInfo[MAX_STR_LENGTH];

	Output("Initializing framework...");

	// Prevent from using two instances
	if(framework)
	{
		framework->Error("Two instances of the framework aren't allowed!");
		delete this;
	}

	// Copy given settings if necessary
	if(settings)
		memcpy(&this->settings, settings, sizeof(FrameworkSettings));

	// Initialize SDL
	result = SDL_Init(SDL_INIT_VIDEO|SDL_INIT_AUDIO|SDL_INIT_JOYSTICK);
	if(result == -1)
		Error("Can't init SDL!");

	// Init video stuff
	sprintf_s(vidInfo,	"===================\n"
						"Video informations:\n"
						"===================\n"
						"Screen width = %d\n"
						"Screen height = %d\n"
						"Screen bits per pixel = %d\n"
						"Double buffering = %d\n"
						"Fullscreen = %d\n"
						"Max frames per second = %d\n"
						"Caption = \"%s\"",
						this->settings.scrW,
						this->settings.scrH,
						this->settings.scrBpp,
						this->settings.doubleBuffering,
						this->settings.fullscreen,
						maxFps,
						this->settings.caption.c_str());
	Output(vidInfo);
	ShowDisplayModes();

	#ifndef DREAMCAST
		if(this->settings.doubleBuffering)
			scrFlags += SDL_DOUBLEBUF;

		if(this->settings.fullscreen)
			scrFlags += SDL_FULLSCREEN;
	#endif

	// Set video mode
	screen = SDL_SetVideoMode(this->settings.scrW, this->settings.scrH, this->settings.scrBpp, scrFlags);
	if(!screen)
		Error("Can't set video mode!");

	Output("Video mode set!");

	this->settings.caption += " - Build: ";
	this->settings.caption += __DATE__ " " __TIME__;

	SDL_WM_SetCaption(this->settings.caption.c_str(), NULL);
	SDL_ShowCursor(SDL_FALSE);

	// Init font stuff
	result = TTF_Init();
	if(result == -1)
		Error("Can't init SDL_ttf!");

	// Init audio stuff
	sprintf_s(audioInfo,	"===================\n"
							"Audio informations:\n"
							"===================\n"
							"Frequency = %d\n"
							"Channels = %d\n"
							"Chunk size = %d",
							this->settings.audioFrequency,
							this->settings.audioChannels,
							this->settings.audioChunkSize);

	Output(audioInfo);

	audioDisabled = this->settings.audioDisabled;
	audioInitialized = false;

	if(!audioDisabled)
	{
		result = Mix_OpenAudio(this->settings.audioFrequency, this->settings.audioFormat,
								this->settings.audioChannels, this->settings.audioChunkSize);
		if(result == -1)
			Error("Can't init audio!");

		Output("Audio initialized!");

		audioInitialized = true;
	}

	// Init input stuff
	#ifdef DREAMCAST
  
  SDL_JoystickOpen(0);
  SDL_JoystickOpen(1);
  SDL_JoystickOpen(2);
  SDL_JoystickOpen(3);
  
  SDL_DC_MapKey(0,SDL_DC_START,SDLK_RETURN); //START button
  SDL_DC_MapKey(0,SDL_DC_UP,SDLK_UP);
  SDL_DC_MapKey(0,SDL_DC_DOWN,SDLK_DOWN);
  SDL_DC_MapKey(0,SDL_DC_LEFT,SDLK_LEFT);
  SDL_DC_MapKey(0,SDL_DC_RIGHT,SDLK_RIGHT);
  SDL_DC_MapKey(0,SDL_DC_A,SDLK_RETURN); //a button
  SDL_DC_MapKey(0,SDL_DC_R,SDLK_ESCAPE); //L button
  
  SDL_DC_MapKey(1,SDL_DC_UP,SDLK_w);
  SDL_DC_MapKey(1,SDL_DC_DOWN,SDLK_s);
  SDL_DC_MapKey(1,SDL_DC_LEFT,SDLK_a);
  SDL_DC_MapKey(1,SDL_DC_RIGHT,SDLK_d);
  
  SDL_DC_MapKey(2,SDL_DC_UP,SDLK_t);
  SDL_DC_MapKey(2,SDL_DC_DOWN,SDLK_g);
  SDL_DC_MapKey(2,SDL_DC_LEFT,SDLK_f);
  SDL_DC_MapKey(2,SDL_DC_RIGHT,SDLK_h);
  
  SDL_DC_MapKey(3,SDL_DC_UP,SDLK_i);
  SDL_DC_MapKey(3,SDL_DC_DOWN,SDLK_k);
  SDL_DC_MapKey(3,SDL_DC_LEFT,SDLK_j);
  SDL_DC_MapKey(3,SDL_DC_RIGHT,SDLK_l);
  
  	//Dreamcast delay...
  	SDL_Delay(20);
  	SDL_Event event;
  	while(SDL_PollEvent(&event))
   		SDL_Delay(20);
	#endif
	
	keys = SDL_GetKeyState(&numKeys);
	sprintf_s(keyInfo, "Number of keys: %d", numKeys);
	Output(keyInfo);
	oldKeys = new Uint8[numKeys];

	// Make us global...
	framework = this;

	Output("Framework initialized!");
}

//
// ~Framework
// Destructor
//
Framework::~Framework()
{
	list<State *>::iterator it;
	State *curState;

	Output("Shutting down framework...");

	// delete all states in the heap...
	for(it = states.begin(); it != states.end(); it++)
	{
		curState = &**it;
		curState->Kill();
	}

	// ...and empty the list of pointers
	states.clear();

	delete [] oldKeys;

	// Shutdown the audio part
	if(audioInitialized)
		Mix_CloseAudio();

	// Shutdown ttf stuff
	TTF_Quit();

	// Shutdown the SDL
	SDL_Quit();

	Output("Framework shut down!");
}

//
// Output
// For various output messages
//
void Framework::Output(string msg) const
{
	cout << msg << endl;
}

//
// Error
// Call this when critical errors occur
//
void Framework::Error(string msg)
{
#ifdef _WIN32
	char mboxStr[MAX_STR_LENGTH];
	strcpy_s(mboxStr, msg.c_str());
	MessageBox(NULL, mboxStr, "Error", MB_ICONEXCLAMATION | MB_OK);
#endif

	cout << "Error:" << endl << msg << endl;
	delete this;
	exit(1);
}

//
// AddState
// Pushes a state to the back of the vector holding all the states
// The state at the beginning of the vector is going to be the first that's executed
//
void Framework::AddState(State *state)
{
	Output("State added!");
	states.push_back(state);
}

//
// ProcessEvents
// Only be sure to quit, if the user presses the closing button of the window
//
bool Framework::ProcessEvents() const
{
	SDL_Event event;
	while(SDL_PollEvent(&event))
		if(event.type == SDL_QUIT)
			return false;
	return true;
}

//
// Run
// Executes all states in the order they're placed in the states-vector
//
void Framework::Run()
{
	list<State *>::iterator it;
	State *curState;
	Uint32 lTime, lReset = GetTicks();
	fps = 0, curFps = 0;
	bool drawnOnce = false;

	for(it = states.begin(); it != states.end(); it++)
	{
		curState = &**it;

		// Init this state. Most states are allocating memory for resources then
		curState->Init();
		curState->SetDone(false);

		// Main-loop of our current state
		while(!curState->GetDone())
		{
			fps++;
			lTime = GetTicks();

			// Update the array of old key states
			memcpy(oldKeys, keys, numKeys);
			
			// Shutdown this state if the user is pressing the close-button
			if(!ProcessEvents())
				break;

			// Update the array of new key states
			SDL_PumpEvents();

			// Input processing
			curState->Input();

			// Game logic
			curState->Update();

			if(drawnOnce && noRedraw)
				continue;

			do {
				// Scene drawing
				curState->Draw();

				// Show fps counter, if necessary
				if(drawFps)
					DrawFPS();

				// Make everything drawn visible
				Flip();

				drawnOnce = true;
			
			} while(GetTicks() - lTime < 1000 / maxFps);

			// Update fps count
			if(GetTicks() - lReset >= 1000)
			{
				curFps = fps;
				fps = 0;
				lReset = GetTicks();
			}
		}

		// Shutdown this state. Most states are deallocating memory for resources then
		curState->Quit();
	}

	delete this;
}

//
// LoadImage
// Loads an image from a given file into a surface and converts it to the screen format.
// If useAlpha is true, the colorkey is also set to magenta (255, 0, 255).
//
SDL_Surface *Framework::LoadImage(string filename, bool useAlpha)
{
	SDL_Surface *temp, *image;
	// Use magenta for the colorkey
	Uint32 magenta = SDL_MapRGB(screen->format, Color::magenta.r, Color::magenta.g, Color::magenta.b);
	string okMsg;

	// Load the image out of the given file
	temp = IMG_Load(filename.c_str());
	if(!temp)
	{
		// Something went wrong...
		string errStr = "Can't load image: ";
		errStr += filename;
		errStr += "!";
		Error(errStr);
	}

	// Everything is working fine...
	okMsg = "Loaded image: ";
	okMsg += filename;
	okMsg += "!";
	
	Output(okMsg);

	// Convert it to screen format
	image = SDL_DisplayFormat(temp);
	FreeImage(temp);

	// Add the colorkey if necessary
	if(useAlpha)
		SDL_SetColorKey(image, SDL_SRCCOLORKEY, magenta);

	return image;
}

//
// LoadSound
//
Mix_Chunk *Framework::LoadSound(string filename)
{
	Mix_Chunk *sound;
	string okMsg;

	if(audioDisabled || !audioInitialized)
		return NULL;

	// Load the sound out of a given file
	sound = Mix_LoadWAV(filename.c_str());
	if(!sound)
	{
		// Something went wrong...
		string errStr = "Can't load sound: ";
		errStr += filename;
		errStr += "!";
		Error(errStr);
	}

	// Everything worked out correct...
	okMsg = "Loaded sound: ";
	okMsg += filename;
	okMsg += "!";

	Output(okMsg);

	return sound;
}

//
// LoadMusic
//
Mix_Music *Framework::LoadMusic(string filename)
{
	Mix_Music *music;
	string okMsg;

	if(audioDisabled || !audioInitialized)
		return NULL;

	// Load the music out of a given file
	music = Mix_LoadMUS(filename.c_str());
	if(!music)
	{
		// Something went wrong...
		string errStr = "Can't load music: ";
		errStr += filename;
		errStr += "!";
		Error(errStr);
	}

	// Everything worked out correct...
	okMsg = "Loaded music: ";
	okMsg += filename;
	okMsg += "!";

	Output(okMsg);

	return music;
}

//
// LoadFont
//
TTF_Font *Framework::LoadFont(string filename, int ptSize)
{
	string okMsg;
	TTF_Font *font;

	// Load font out of given file
	font = TTF_OpenFont(filename.c_str(), ptSize);
	if(!font)
	{
		// Something went wrong....
		string errStr = "Can't load font: ";
		errStr += filename;
		errStr += "!";
		Error(errStr);
	}

	// Everything worked out correct...
	okMsg = "Loaded font: ";
	okMsg += filename;
	okMsg += "!";

	Output(okMsg);

	return font;
}

//
// FreeImage
//
void Framework::FreeImage(SDL_Surface *image) const
{
	SDL_FreeSurface(image);
}

//
// FreeSound
//
void Framework::FreeSound(Mix_Chunk *sound) const
{
	if(audioInitialized)
		Mix_FreeChunk(sound);
}

//
// FreeMusic
//
void Framework::FreeMusic(Mix_Music *music) const
{
	if(audioInitialized)
		Mix_FreeMusic(music);
}

//
// FreeFont
//
void Framework::FreeFont(TTF_Font *font) const
{
	TTF_CloseFont(font);
}

//
// SetAlpha
//
void Framework::SetAlpha(SDL_Surface *image, Uint8 alpha) const
{
	SDL_SetAlpha(image, SDL_SRCALPHA | SDL_RLEACCEL, alpha);
}

//
// Draw
// Draws a given surface to the screen at a given position
//
void Framework::Draw(SDL_Surface *img, int x, int y)
{
	SDL_Rect rect = { x, y, img->w, img->h };
	SDL_BlitSurface(img, NULL, screen, &rect);
}

//
// Draw
//
void Framework::Draw(SDL_Surface *img, SDL_Rect *destRect)
{
	SDL_BlitSurface(img, NULL, screen, destRect);
}

//
// Draw
//
void Framework::Draw(SDL_Surface *img, SDL_Rect *srcRect, SDL_Rect *destRect)
{
	SDL_BlitSurface(img, srcRect, screen, destRect);
}

//
// FillRect
//
void Framework::FillRect(SDL_Rect *rect, SDL_Color color)
{
	Uint32 convColor = SDL_MapRGB(screen->format, color.r, color.g, color.b);
	SDL_FillRect(screen, rect, convColor);
}

//
// Flip
//
void Framework::Flip()
{
	if(settings.doubleBuffering)
		SDL_Flip(screen);
	else
		SDL_UpdateRect(screen, 0, 0, screen->w, screen->h);
}

//
// DrawText
//
void Framework::DrawText(string text, int x, int y, TTF_Font *font, SDL_Color color, bool blended)
{
	SDL_Surface *textSurf;
	SDL_Rect rect = { x, y, 0, 0 };

	if(blended)
		textSurf = TTF_RenderText_Blended(font, text.c_str(), color);
	else
		textSurf = TTF_RenderText_Solid(font, text.c_str(), color);

	rect.w = textSurf->w;
	rect.h = textSurf->h;

	SDL_BlitSurface(textSurf, NULL, screen, &rect);
	FreeImage(textSurf);
}

//
// DrawShadowedText
//
void Framework::DrawShadowedText(string text, int x, int y, TTF_Font *font, SDL_Color color, bool blended)
{
	DrawText(text, x - 2, y - 2, font, Color::black, blended);
	DrawText(text, x, y, font, color, blended);
}

//
// PlaySound
//
void Framework::PlaySound(Mix_Chunk *snd) const
{
	if(audioDisabled || !audioInitialized)
		return;

	Mix_PlayChannel(-1, snd, 0);
}

//
// PlayMusic
//
void Framework::PlayMusic(Mix_Music *music, bool loop) const
{
	if(audioDisabled || !audioInitialized)
		return;

	int loops = 0;
	if(loop)
		loops = -1;
	Mix_PlayMusic(music, loops);
}

//
// HaltMusic
//
void Framework::HaltMusic() const
{
	if(audioInitialized)
		Mix_HaltMusic();
}

//
// GetTicks
//
Uint32 Framework::GetTicks() const
{
	return SDL_GetTicks();
}

//
// GetMaxFps
//
int Framework::GetMaxFps() const
{
	return maxFps;
}

//
// GetScreen
//
SDL_Surface *Framework::GetScreen()
{
	return screen;
}

//
// GetScreenW
//
int Framework::GetScreenW() const
{
	return screen->w;
}

//
// GetScreenH
//
int Framework::GetScreenH() const
{
	return screen->h;
}

//
// SetCaption
//
void Framework::SetCaption(string caption) const
{
	SDL_WM_SetCaption(caption.c_str(), NULL);
}

//
// KeyIsPressed
//
bool Framework::KeyIsPressed(int keyNum, bool wasReleased) const
{
	if(wasReleased)
		return (keys[keyNum] && !oldKeys[keyNum]) != 0;
	else
		return keys[keyNum] != 0;
}

//
// GetNumKeys
//
int Framework::GetNumKeys() const
{
	return numKeys;
}

//
// RelativeMouseState
//
void Framework::RelativeMouseState(int *x, int *y) const
{
	SDL_GetRelativeMouseState(x, y);
}

//
// Coll
// Test whether two given rectangles are colliding
//
bool Framework::Coll(SDL_Rect *rect1, SDL_Rect *rect2) const
{
	return (rect1->x + rect1->w > rect2->x &&
			rect1->x < rect2->x + rect2->w &&
			rect1->y + rect1->h > rect2->y &&
			rect1->y < rect2->y + rect2->h);
}

//
// Cos
//
template<class Type>
Type Framework::Cos(Type alpha) const
{
	return (Type)cos(((long double)alpha * PI) / 180.0f);
}

//
// Sin
//
template<class Type>
Type Framework::Sin(Type alpha) const
{
	return (Type)sin(((long double)alpha * PI) / 180.0f);
}

//
// GetVersion
//
string Framework::GetVersion() const
{
	return settings.version;
}

//
// MakeScreenshot
//
void Framework::MakeScreenshot(string prefix) const
{
	int i;
	char filename[MAX_STR_LENGTH];

	for(i=0; i<MAX_SCRSHOTS; i++)
	{
		sprintf_s(filename, "%s%d.bmp", prefix.c_str(), i + 1);
		if(!FileExists(filename))
		{
			Output("Screenshot made!");
			SDL_SaveBMP(screen, filename);
			break;
		}
	}
}

//
// FileExists
//
bool Framework::FileExists(string filename) const
{
	ifstream inputStream;

	inputStream.open(filename.c_str());

	if(!inputStream.good())
		return false;

	inputStream.close();
	return true;
}

//
// SetDrawFps
//
void Framework::SetDrawFps(bool value, TTF_Font *font)
{
	drawFps = value;
	fpsFont = font;
}

//
// DrawFPS
//
void Framework::DrawFPS()
{
	char fpsStr[MAX_STR_LENGTH];
	sprintf_s(fpsStr, "Fps: %d", curFps);

	DrawText(fpsStr, 10, 10, fpsFont);
}

//
// IsFullscreen
//
bool Framework::IsFullscreen() const
{
	return settings.fullscreen;
}

//
// SetFullscreen
//
void Framework::SetFullscreen(bool value)
{
	settings.fullscreen = value;
	VidRestart();
	SDL_WM_ToggleFullScreen(screen);
}

//
// ToggleFullscreen
//
void Framework::ToggleFullscreen()
{
	SetFullscreen(!settings.fullscreen);
}

//
// VidRestart
//
void Framework::VidRestart()
{
	Uint32 scrFlags = SDL_HWSURFACE;

	if(settings.doubleBuffering)
		scrFlags += SDL_DOUBLEBUF;

	if(settings.fullscreen)
		scrFlags += SDL_FULLSCREEN;

	screen = SDL_SetVideoMode(	settings.scrW, settings.scrH,
								settings.scrBpp, scrFlags);
	if(!screen)
		Error("Can't restart video!");
}

//
// Delay
//
void Framework::Delay(Uint32 length) const
{
	SDL_Delay(length);
}

//
// SetGamma
//
void Framework::SetGamma(float red, float green, float blue)
{
	int result;

	result = SDL_SetGamma(red, green, blue);
	if(result == -1)
		Output("Warning: Setting gamma failed!");

	curGamma.Set(red, green, blue);
}

//
// IncGamma
//
void Framework::IncGamma(float value)
{
	curGamma.Inc(value);
	SetGamma(curGamma.red, curGamma.green, curGamma.blue);
}

//
// DecGamma
//
void Framework::DecGamma(float value)
{
	curGamma.Dec(value);
	SetGamma(curGamma.red, curGamma.green, curGamma.blue);
}

//
// ShowDisplayModes
//
void Framework::ShowDisplayModes() const
{
	SDL_Rect **rects;
	Uint32 flags = SDL_HWSURFACE;

	if(settings.fullscreen)
		flags += SDL_FULLSCREEN;

	if(settings.doubleBuffering)
		flags += SDL_DOUBLEBUF;

	rects = SDL_ListModes(NULL, flags);
	if(rects == NULL)
		Output("Warning: Failed to list video modes!");
	else if(rects == (SDL_Rect **)-1)
		Output("All video modes are available!");
	else
	{
		char modeStr[MAX_STR_LENGTH];
		int i;

		Output("Available video modes:");

		for(i=0; rects[i]; i++)
		{
			sprintf_s(modeStr, "Mode Nr. %d: width=%d height=%d", i + 1, rects[i]->w, rects[i]->h);
			Output(modeStr);
		}
	}
}

//
// GetAudioDisabled
//
bool Framework::GetAudioDisabled() const
{
	return audioDisabled;
}

//
// SetAudioDisabled
//
void Framework::SetAudioDisabled(bool value)
{
	audioDisabled = value;

	if(value)
		HaltMusic();
	else
	{
		if(!audioInitialized)
		{
			int result;

			result = Mix_OpenAudio(	settings.audioFrequency, settings.audioFormat,
									settings.audioChannels, settings.audioChunkSize);
			if(result == -1)
				Error("Can't open audio!");

			Output("Audio initialized!");

			audioInitialized = true;
		}
	}
}

//
// ToggleAudioDisabled
//
void Framework::ToggleAudioDisabled()
{
	SetAudioDisabled(!audioDisabled);
}

//
// Pow
//
template<class Type>
Type Framework::Pow(Type num, int exponent) const
{
	int i, base;

	if(exponent == 0)
		return (Type)1;

	for(i=1, base=num; i<exponent; i++, num *= base);
	return num;
}

//
// Sqrt
//
template<class Type>
Type Framework::Sqrt(Type num) const
{
	return (Type)sqrt((long double)num);
}

//
// Rand
//
int Framework::Rand(int min, int max)
{
	bool randInitialized = false;

	if(!randInitialized)
	{
		srand(GetTicks());
		randInitialized = true;
	}

	return rand() % (max + 1) + min;
}

//
// SetNoRedraw
//
void Framework::SetNoRedraw(bool value)
{
	noRedraw = value;
}

//
// LockScreen
//
void Framework::LockScreen()
{
	if(SDL_MUSTLOCK(screen))
		SDL_LockSurface(screen);
}

//
// UnlockScreen
//
void Framework::UnlockScreen()
{
	if(SDL_MUSTLOCK(screen))
		SDL_UnlockSurface(screen);
}

//
// GetPixelColor
//
Uint32 Framework::GetPixelColor(int x, int y)
{
	Uint8 *ptr = (Uint8 *)screen->pixels + screen->pitch * y + x * (settings.scrBpp / 8);

	if(settings.scrBpp == 16)
		return *(Uint16 *)ptr;
	else if(settings.scrBpp == 32)
		return *(Uint32 *)ptr;
	else
		Error("Can't get pixel: Wrong bits per pixel for screen surface!");
}

//
// SetPixelColor
//
void Framework::SetPixelColor(int x, int y, Uint32 color)
{
	Uint8 *ptr = (Uint8 *)screen->pixels + screen->pitch * y + x * (settings.scrBpp / 8);

	if(x > screen->w || x < 0 || y > screen->h || y < 0)
		Error("Can't set pixel: Drawing out of screen surface!");

	if(settings.scrBpp == 16)
		*(Uint16 *)ptr = color;
	else if(settings.scrBpp == 32)
		*(Uint32 *)ptr = color;
	else
		Error("Can't set pixel: Wrong bits per pixel for screen surface!");
}

//
// SDLColToUint32Col
//
Uint32 Framework::SDLColToUint32Col(SDL_Color color) const
{
	return SDL_MapRGB(screen->format, color.r, color.g, color.b);
}

//
// CountLinesInFile
//
int Framework::CountLinesInFile(string filename, bool noEmptyLines)
{
	ifstream inputStream;
	char curLine[MAX_STR_LENGTH];
	int numLines = 0;

	inputStream.open(filename.c_str());

	if(!inputStream.good())
	{
		string errStr = "Can't load file: ";
		errStr += filename;
		errStr += "!";

		Error(errStr);

		return -1;
	}

	while(!inputStream.eof())
	{
		inputStream.getline(curLine, MAX_STR_LENGTH);

		if(noEmptyLines)
		{
			if(isalpha(curLine[0]))
				numLines++;
		}
		else
			numLines++;
	}

	inputStream.close();

	return numLines;
}
