//	Application.cpp - Class handling the various application bits and pieces.
//  ----------------------------------------------------------------------------
//	This file is part of 'GameTemplate', a game.
//	Copyright (C) 2009  Niall Moody
//	
//	This program is free software: you can redistribute it and/or modify
//	it under the terms of the GNU General Public License as published by
//	the Free Software Foundation, either version 3 of the License, or
//	(at your option) any later version.
//
//	This program is distributed in the hope that it will be useful,
//	but WITHOUT ANY WARRANTY; without even the implied warranty of
//	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
//	GNU General Public License for more details.
//
//	You should have received a copy of the GNU General Public License
//	along with this program.  If not, see <http://www.gnu.org/licenses/>.
//	----------------------------------------------------------------------------

#ifdef DEBUG
#pragma warning(disable : 4786) //Stupid MS headers...
#endif

#include "Application.h"
#include "FatalError.h"
#include "ConfigLoader.h"
#include "NiallsDrawingLib/OpenGLDrawer.h"
#include "NiallsDrawingLib/ImageLoader.h"
#include "NiallsDrawingLib/Drawer.h"
#include "sound/Sounds.h"
#include "Random.h"
#include "Fonts.h"

#include "states/IntroState.h"
#include "states/GamePlay.h"

#include "SDL_image.h"
#include <iostream>
#include <iomanip>
#include <sstream>
#include <cmath>

using namespace std;

//------------------------------------------------------------------------------
Application::Application(const CmdLineParams &commandLine):
state(0),
timeToQuit(false),
lastRepaint(0),
width(0),
height(0),
noSound(false),
noMusic(false),
offlineTime(0),
joystick(0)
{
	int aaLevel = 0;
	bool antialias = false;
	CmdLineParams::const_iterator it;
	stringstream tempConverter;
	ConfigLoader config;
	bool fullscreen = false;
	bool sdl = false;

	//Initialise SDL.
	if(SDL_Init(SDL_INIT_TIMER | SDL_INIT_VIDEO | SDL_INIT_JOYSTICK) < 0)
	{
		ostringstream err("Could not initialise SDL: ");
		err << SDL_GetError();
		throw FatalError(err.str());
	}

	//Set window name.
	SDL_WM_SetCaption("The Future Will Only be Dystopian if Capitalism Remains Undefeated", 0);

	if(commandLine.size() > 0)
	{
		//Determine whether to print the help from the command line.
		for(it=commandLine.begin();it!= commandLine.end();++it)
		{
			if((*it) == "--help")
			{
				cout << endl;
				cout << "The Future Will Only be Dystopian if Capitalism Remains Undefeated";
				cout << endl << endl;
				cout << "Usage:" << endl;
#ifdef WIN32
				cout << "TheFutureWillOnlyBeDystopianIfCapitalismRemainsUndefeated.exe -w [width] -h [height] -a [val] -f -ns -nm -s";
#else
				cout << "TheFutureWillOnlyBeDystopianIfCapitalismRemainsUndefeated -w [width] -h [height] -a [val] -f -ns -nm -s";
#endif
				cout << endl << endl;
				cout << "Flags:" << endl;
				cout << "--width [val] | -w [val]:\tSet the width of the window.";
				cout << endl;
				cout << "--height [val] | -h [val]:\tSet the height of the window.";
				cout << endl;
				cout << "--antialias [val] | -a [val]:\tSets the antialiasing level (2, 4, or 6).";
				cout << endl;
				cout << "--fullscreen | -f:\t\tSets whether to go fullscreen or not.";
				cout << endl;
				cout << "--nosound | -ns:\t\tSwitches off the sound output.";
				cout << endl;
				cout << "--nomusic | -nm:\t\tSwitches off the background music.";
				cout << endl;
				cout << "--sdl | -s:\t\tSwitches to the SDL drawing backend in favour of the OpenGL one.";
				cout << endl;
				cout << "--help:\t\t\t\tDisplay this help.";
				cout << endl;
				throw FatalError("\n");
			}
		}

		//Determine width from command line.
		for(it=commandLine.begin();it!= commandLine.end();++it)
		{
			if(((*it) == "--width") || ((*it) == "-w"))
			{
				++it;
				if(it != commandLine.end())
				{
					tempConverter << *it;
					tempConverter >> width;
					tempConverter.clear();
				}
				else
					break;
			}
		}
		if(!width)
		{
			width = 800;
			cout << "Using default width = 800" << endl;
		}

		//Determine height from command line.
		for(it=commandLine.begin();it!= commandLine.end();++it)
		{
			if(((*it) == "--height") || ((*it) == "-h"))
			{
				++it;
				if(it != commandLine.end())
				{
					tempConverter << *it;
					tempConverter >> height;
					tempConverter.clear();
				}
				else
					break;
			}
		}
		if(!height)
		{
			height = 600;
			cout << "Using default height = 600" << endl;
		}

		//Determine whether to go fullscreen from command line.
		for(it=commandLine.begin();it!= commandLine.end();++it)
		{
			if(((*it) == "--fullscreen") || ((*it) == "-f"))
				fullscreen = true;
		}

		//Determine whether to switch off sound from command line.
		for(it=commandLine.begin();it!= commandLine.end();++it)
		{
			if(((*it) == "--nosound") || ((*it) == "-ns"))
				noSound = true;
		}

		//Determine whether to switch off sound from command line.
		for(it=commandLine.begin();it!= commandLine.end();++it)
		{
			if(((*it) == "--nomusic") || ((*it) == "-nm"))
				noMusic = true;
		}

		//Determine whether to use the SDLDrawer from command line.
		for(it=commandLine.begin();it!= commandLine.end();++it)
		{
			if(((*it) == "--sdl") || ((*it) == "-s"))
				sdl = true;
		}

		//Determine whether to use antialiasing from command line.
		for(it=commandLine.begin();it!= commandLine.end();++it)
		{
			if(((*it) == "--antialias") || ((*it) == "-a"))
			{
				antialias = true;
				++it;
				if(it != commandLine.end())
				{
					tempConverter << *it;
					tempConverter >> aaLevel;
					tempConverter.clear();
				}
				else
					break;
			}
		}
		if(antialias && (aaLevel < 2))
		{
			aaLevel = 2;
			cout << "Using 2x antialiasing." << endl;
		}
	}
	else
	{
		width = config.getScreenWidth();
		height = config.getScreenHeight();
		if(config.getAntialiasLevel() > 0)
		{
			antialias = true;
			aaLevel = config.getAntialiasLevel();
		}
		if(config.getFullscreen())
			fullscreen = true;
		if(config.getNoSound())
			noSound = true;
		if(config.getNoMusic())
			noMusic = true;
	}

	//Create the drawer.
	/*if(sdl)
		drawer = new SDLDrawer();
	else*/
		drawer = new OpenGLDrawer();

	drawer->initialise(width, height, fullscreen, aaLevel);
	loadGlyphs();

	//Create the intro state.
	state = new IntroState(this);

	if((offlineFilename == "") && (offlineTime == 0))
	{
		//Enable joystick, if available.
		if(SDL_NumJoysticks() > 0)
		{
			joystick = SDL_JoystickOpen(0);
			SDL_JoystickEventState(SDL_ENABLE);
		}

		//Start audio.
		portAudioDevice.setCallback(this);
		portAudioDevice.setNumChannels(0, 2);
		portAudioDevice.setSamplerate(44100.0);
		if(!noSound)
		{
			portAudioDevice.start();
			//if(portAudioDevice.getSamplerate() != 44100.0)
				//GameManager::getInstance().setSamplerate(portAudioDevice.getSamplerate());

			//Initialise Sounds.
			Sounds::getInstance();
		}
	}

	//Hide the cursor.
	SDL_ShowCursor(SDL_DISABLE);
}

//------------------------------------------------------------------------------
Application::~Application()
{
	if((offlineFilename == "") && (offlineTime == 0))
	{
		if(!noSound)
			portAudioDevice.stop();

		if(joystick)
			SDL_JoystickClose(joystick);
	}

	delete drawer;

	if(state)
		delete state;

	SDL_Quit();
}

//------------------------------------------------------------------------------
void Application::getAudio(float *input, float *output, int numSamples)
{
	int i;

	//Clear the buffers first.
	for(i=0;i<numSamples;++i)
	{
		output[i*2] = 0.0f;
		output[(i*2)+1] = 0.0f;
	}

	//Add the background music.
	if(!noMusic)
		music.audioBlock(output, numSamples);

	//Add any sounds.
	Sounds::getInstance().getAudio(output, numSamples);
}

//------------------------------------------------------------------------------
void Application::eventLoop()
{
	Uint32 tempint;
	SDL_Event event;
	SDL_keysym tempKey;

	while(1)
	{
		//Handle events.
		while(SDL_PollEvent(&event))
		{
			switch(event.type)
			{
				case SDL_KEYDOWN:
					keyDown(event.key.keysym);
					break;
				case SDL_KEYUP:
					keyUp(event.key.keysym);
					break;
				case SDL_MOUSEBUTTONDOWN:
					mouseDown(event.button.button,
							  event.button.x,
							  event.button.y);
					break;
				case SDL_MOUSEBUTTONUP:
					mouseUp(event.button.button,
							event.button.x,
							event.button.y);
					break;
				case SDL_MOUSEMOTION:
					mouseMove(event.motion.x, event.motion.y);
					break;
				case SDL_QUIT:
					timeToQuit = true;
					break;
				case SDL_JOYAXISMOTION:
					if(event.jaxis.axis == 0) //X axis.
					{
						if(event.jaxis.value < -128)
						{
							if(joyRightIsDown)
							{
								tempKey.sym = SDLK_RIGHT;
								keyUp(tempKey);
								joyRightIsDown = false;
							}
							tempKey.sym = SDLK_LEFT;
							keyDown(tempKey);
							joyXMove(false);
							joyLeftIsDown = true;
						}
						else if(event.jaxis.value > 128)
						{
							if(joyLeftIsDown)
							{
								tempKey.sym = SDLK_LEFT;
								keyUp(tempKey);
								joyLeftIsDown = false;
							}
							tempKey.sym = SDLK_RIGHT;
							keyDown(tempKey);
							joyXMove(true);
							joyRightIsDown = true;
						}
						else
						{
							tempKey.sym = SDLK_RIGHT;
							keyUp(tempKey);
							tempKey.sym = SDLK_LEFT;
							keyUp(tempKey);
							joyRightIsDown = false;
							joyLeftIsDown = false;
						}
					}
					else if(event.jaxis.axis == 1) //Y axis.
					{
						if(event.jaxis.value > 128)
						{
							if(joyUpIsDown)
							{
								tempKey.sym = SDLK_UP;
								keyUp(tempKey);
								joyUpIsDown = false;
							}
							tempKey.sym = SDLK_DOWN;
							keyDown(tempKey);
							joyYMove(false);
							joyDownIsDown = true;
						}
						else if(event.jaxis.value < -128)
						{
							if(joyDownIsDown)
							{
								tempKey.sym = SDLK_UP;
								keyUp(tempKey);
								joyDownIsDown = false;
							}
							tempKey.sym = SDLK_UP;
							keyDown(tempKey);
							joyYMove(true);
							joyUpIsDown = true;
						}
						else
						{
							tempKey.sym = SDLK_DOWN;
							keyUp(tempKey);
							tempKey.sym = SDLK_UP;
							keyUp(tempKey);
							joyUpIsDown = false;
							joyDownIsDown = false;
						}
					}
					break;
				case SDL_JOYBUTTONDOWN:
					switch(event.jbutton.button)
					{
						case 0:
							tempKey.sym = SDLK_z;
							break;
						case 1:
							tempKey.sym = SDLK_x;
							break;
						default:
							tempKey.sym = SDLK_c;
					}
					keyDown(tempKey);
					joyDown(event.jbutton.button);
					break;
			}
		}

		if(timeToQuit)
			break;

		//Repaint the screen, or wait a bit first.
		//We aim for a 30fps frame rate here.
		tempint = lastRepaint;
		lastRepaint = SDL_GetTicks();
		if((lastRepaint - tempint) < 30)
			SDL_Delay(30 - (lastRepaint - tempint));

		//Do the drawing.
		drawer->beginDrawing();
		state->draw(drawer);
		drawer->endDrawing();
	}
}

//------------------------------------------------------------------------------
void Application::nextState(const std::string s)
{
	if(s == "GamePlay")
	{
		delete state;
		state = new GamePlay(this);
	}
	else
		quit();
}

//------------------------------------------------------------------------------
void Application::quit()
{
	timeToQuit = true;
}

//------------------------------------------------------------------------------
void Application::keyDown(const SDL_keysym& keyPressed)
{
	state->keyDown(keyPressed);
}

//------------------------------------------------------------------------------
void Application::keyUp(const SDL_keysym& keyPressed)
{
	state->keyUp(keyPressed);
}

//------------------------------------------------------------------------------
void Application::mouseDown(int button, int posX, int posY)
{
	float x, y;
	float offset;
	const float aspect = (float)height/(float)width;

	x = (float)posX/(float)width;
	x *= 1024.0f;
	y = (float)posY/(float)height;
	y *= 768.0f;

	if(aspect < 0.75f)
	{
		offset = (float)width - (((float)height/768.0f) * 1024.0f);
		offset *= 0.5f;
	}
	else if(aspect > 0.75f)
	{
		offset = (float)height - (((float)width/1024.0f) * 768.0f);
		offset *= 0.5f;
	}

	state->mouseDown(button, x, y);
}

//------------------------------------------------------------------------------
void Application::mouseUp(int button, int posX, int posY)
{
	float x, y;
	float offset;
	const float aspect = (float)height/(float)width;

	x = (float)posX/(float)width;
	x *= 1024.0f;
	y = (float)posY/(float)height;
	y *= 768.0f;

	if(aspect < 0.75f)
	{
		offset = (float)width - (((float)height/768.0f) * 1024.0f);
		offset *= 0.5f;
	}
	else if(aspect > 0.75f)
	{
		offset = (float)height - (((float)width/1024.0f) * 768.0f);
		offset *= 0.5f;
	}

	state->mouseUp(button, x, y);
}

//------------------------------------------------------------------------------
void Application::mouseMove(int posX, int posY)
{
	float x, y;
	float offset;
	const float aspect = (float)height/(float)width;

	x = (float)posX/(float)width;
	x *= 1024.0f;
	y = (float)posY/(float)height;
	y *= 768.0f;

	if(aspect < 0.75f)
	{
		offset = (float)width - (((float)height/768.0f) * 1024.0f);
		offset *= 0.5f;
	}
	else if(aspect > 0.75f)
	{
		offset = (float)height - (((float)width/1024.0f) * 768.0f);
		offset *= 0.5f;
	}

	state->mouseMove(x, y);
}

//------------------------------------------------------------------------------
void Application::joyYMove(bool dir)
{
	
}

//------------------------------------------------------------------------------
void Application::joyXMove(bool dir)
{
	
}

//------------------------------------------------------------------------------
void Application::joyDown(int button)
{
	
}

//------------------------------------------------------------------------------
void Application::loadGlyphs()
{
	ImageLoader imLoad;
	ImageData data;

	data = imLoad.loadImage(Fonts::fonta_png, Fonts::fonta_pngSize);
	drawer->loadGlyph(Drawer::a, data.data, data.width);

	data = imLoad.loadImage(Fonts::fontb_png, Fonts::fontb_pngSize);
	drawer->loadGlyph(Drawer::b, data.data, data.width);

	data = imLoad.loadImage(Fonts::fontc_png, Fonts::fontc_pngSize);
	drawer->loadGlyph(Drawer::c, data.data, data.width);

	data = imLoad.loadImage(Fonts::fontd_png, Fonts::fontd_pngSize);
	drawer->loadGlyph(Drawer::d, data.data, data.width);

	data = imLoad.loadImage(Fonts::fonte_png, Fonts::fonte_pngSize);
	drawer->loadGlyph(Drawer::e, data.data, data.width);

	data = imLoad.loadImage(Fonts::fontf_png, Fonts::fontf_pngSize);
	drawer->loadGlyph(Drawer::f, data.data, data.width);

	data = imLoad.loadImage(Fonts::fontg_png, Fonts::fontg_pngSize);
	drawer->loadGlyph(Drawer::g, data.data, data.width);

	data = imLoad.loadImage(Fonts::fonth_png, Fonts::fonth_pngSize);
	drawer->loadGlyph(Drawer::h, data.data, data.width);

	data = imLoad.loadImage(Fonts::fonti_png, Fonts::fonti_pngSize);
	drawer->loadGlyph(Drawer::i, data.data, data.width);

	data = imLoad.loadImage(Fonts::fontj_png, Fonts::fontj_pngSize);
	drawer->loadGlyph(Drawer::j, data.data, data.width);

	data = imLoad.loadImage(Fonts::fontk_png, Fonts::fontk_pngSize);
	drawer->loadGlyph(Drawer::k, data.data, data.width);

	data = imLoad.loadImage(Fonts::fontl_png, Fonts::fontl_pngSize);
	drawer->loadGlyph(Drawer::l, data.data, data.width);

	data = imLoad.loadImage(Fonts::fontm_png, Fonts::fontm_pngSize);
	drawer->loadGlyph(Drawer::m, data.data, data.width);

	data = imLoad.loadImage(Fonts::fontn_png, Fonts::fontn_pngSize);
	drawer->loadGlyph(Drawer::n, data.data, data.width);

	data = imLoad.loadImage(Fonts::fonto_png, Fonts::fonto_pngSize);
	drawer->loadGlyph(Drawer::o, data.data, data.width);

	data = imLoad.loadImage(Fonts::fontp_png, Fonts::fontp_pngSize);
	drawer->loadGlyph(Drawer::p, data.data, data.width);

	data = imLoad.loadImage(Fonts::fontq_png, Fonts::fontq_pngSize);
	drawer->loadGlyph(Drawer::q, data.data, data.width);

	data = imLoad.loadImage(Fonts::fontr_png, Fonts::fontr_pngSize);
	drawer->loadGlyph(Drawer::r, data.data, data.width);

	data = imLoad.loadImage(Fonts::fonts_png, Fonts::fonts_pngSize);
	drawer->loadGlyph(Drawer::s, data.data, data.width);

	data = imLoad.loadImage(Fonts::fontt_png, Fonts::fontt_pngSize);
	drawer->loadGlyph(Drawer::t, data.data, data.width);

	data = imLoad.loadImage(Fonts::fontu_png, Fonts::fontu_pngSize);
	drawer->loadGlyph(Drawer::u, data.data, data.width);

	data = imLoad.loadImage(Fonts::fontv_png, Fonts::fontv_pngSize);
	drawer->loadGlyph(Drawer::v, data.data, data.width);

	data = imLoad.loadImage(Fonts::fontw_png, Fonts::fontw_pngSize);
	drawer->loadGlyph(Drawer::w, data.data, data.width);

	data = imLoad.loadImage(Fonts::fontx_png, Fonts::fontx_pngSize);
	drawer->loadGlyph(Drawer::x, data.data, data.width);

	data = imLoad.loadImage(Fonts::fonty_png, Fonts::fonty_pngSize);
	drawer->loadGlyph(Drawer::y, data.data, data.width);

	data = imLoad.loadImage(Fonts::fontz_png, Fonts::fontz_pngSize);
	drawer->loadGlyph(Drawer::z, data.data, data.width);

	data = imLoad.loadImage(Fonts::font0_png, Fonts::font0_pngSize);
	drawer->loadGlyph(Drawer::Num0, data.data, data.width);

	data = imLoad.loadImage(Fonts::font1_png, Fonts::font1_pngSize);
	drawer->loadGlyph(Drawer::Num1, data.data, data.width);

	data = imLoad.loadImage(Fonts::font2_png, Fonts::font2_pngSize);
	drawer->loadGlyph(Drawer::Num2, data.data, data.width);

	data = imLoad.loadImage(Fonts::font3_png, Fonts::font3_pngSize);
	drawer->loadGlyph(Drawer::Num3, data.data, data.width);

	data = imLoad.loadImage(Fonts::font4_png, Fonts::font4_pngSize);
	drawer->loadGlyph(Drawer::Num4, data.data, data.width);

	data = imLoad.loadImage(Fonts::font5_png, Fonts::font5_pngSize);
	drawer->loadGlyph(Drawer::Num5, data.data, data.width);

	data = imLoad.loadImage(Fonts::font6_png, Fonts::font6_pngSize);
	drawer->loadGlyph(Drawer::Num6, data.data, data.width);

	data = imLoad.loadImage(Fonts::font7_png, Fonts::font7_pngSize);
	drawer->loadGlyph(Drawer::Num7, data.data, data.width);

	data = imLoad.loadImage(Fonts::font8_png, Fonts::font8_pngSize);
	drawer->loadGlyph(Drawer::Num8, data.data, data.width);

	data = imLoad.loadImage(Fonts::font9_png, Fonts::font9_pngSize);
	drawer->loadGlyph(Drawer::Num9, data.data, data.width);

	data = imLoad.loadImage(Fonts::fontperiod_png, Fonts::fontperiod_pngSize);
	drawer->loadGlyph(Drawer::Period, data.data, data.width);

	data = imLoad.loadImage(Fonts::fontcomma_png, Fonts::fontcomma_pngSize);
	drawer->loadGlyph(Drawer::Comma, data.data, data.width);

	data = imLoad.loadImage(Fonts::fontplus_png, Fonts::fontplus_pngSize);
	drawer->loadGlyph(Drawer::Plus, data.data, data.width);

	data = imLoad.loadImage(Fonts::fontminus_png, Fonts::fontminus_pngSize);
	drawer->loadGlyph(Drawer::Minus, data.data, data.width);

	data = imLoad.loadImage(Fonts::fontexclaim_png, Fonts::fontexclaim_pngSize);
	drawer->loadGlyph(Drawer::Exclaim, data.data, data.width);

	data = imLoad.loadImage(Fonts::fontquestion_png, Fonts::fontquestion_pngSize);
	drawer->loadGlyph(Drawer::Question, data.data, data.width);
}
