//	SDL1.2Application.cpp - An implementation of Application for SDL 1.2.
//  ----------------------------------------------------------------------------
//	This file is part of 'NiallsAVLib', base code for any kind of audiovisual
//	apps.
//	Copyright (C) 2012  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/>.
//	----------------------------------------------------------------------------

#include "SDL1.2Application.h"
#include "drawing/ScreenRes.h"
#include "drawing/Drawer.h"
#include "sound/Sounder.h"
#include "LoadingScreen.h"
#include "DebugHeaders.h"
#include "GlobalData.h"
#include "UTF8File.h"

#ifdef USE_PORTAUDIO
#include "sound/PortAudioDevice.h"
#endif
#include "SDL1.2AudioDevice.h"

#include "SDL_image.h"
#include "SDL.h"
#include <iostream>
#include <sstream>
#include <cassert>

#ifdef WIN32
#pragma warning(disable: 4996)
#endif

using namespace std;

//------------------------------------------------------------------------------
SDL1_2Application::SDL1_2Application():
sounder(0),
timeToQuit(false),
lastRepaint(0),
cmdWidth(-1),
cmdHeight(-1),
cmdWindow(false),
cmdMute(false),
cmdAudioAPI(L"SDL"),
cmdHelp(false),
vSync(true)
{
	
}

//------------------------------------------------------------------------------
SDL1_2Application::~SDL1_2Application()
{
	unsigned int i;

	if(audioDevice)
		audioDevice->stop();

	for(i=0;i<joysticks.size();++i)
		SDL_JoystickClose(joysticks[i]);

	if(sounder)
		delete sounder;

	IMG_Quit();
	SDL_Quit();
}

//------------------------------------------------------------------------------
void SDL1_2Application::initialise(CmdLineParams& cmdLine)
{
	int i;
	uint32_t sdlFlags = SDL_INIT_TIMER | SDL_INIT_VIDEO | SDL_INIT_JOYSTICK;

	//Register our command line switch callbacks.
	cmdLine.registerCallback(L"width", L"--width", L"-w", true, L"-1", this);
	cmdLine.registerCallback(L"height", L"--height", L"-h", true, L"-1", this);
	cmdLine.registerCallback(L"window", L"--window", L"-win", false, L"", this);
	cmdLine.registerCallback(L"mute", L"--mute", L"-m", false, L"", this);
	cmdLine.registerCallback(L"audioAPI", L"--audio-api", L"-a", true, L"SDL", this);
	cmdLine.registerCallback(L"help", L"--help", L"-help", false, L"", this);

	//Initialise our AppSettings.
	appSettings.initialise(cmdLine, this, keyManager, helpText);
	DebugStatement(L"SDL1.2Application: Initialised AppSettings.");

	//By this point we've registered all our GameStates, and one of them must
	//have been flagged as the start state.

	//If you hit this assert it means you haven't flagged any GameStates as
	//the start state.
	assert(startState != L"");

	//Handle any command line flags.
	cmdLine.handleParameters();
	//If the user passed in '--help' on the command line, we need to quit
	//immediately.
	if(cmdHelp)
		return;
	DebugStatement(L"SDL1.2Application: Handled any command line params.");

	//Construct the app's GlobalData.
	globalData = appSettings.getGlobalData();

	//If the user hasn't passed in the screen dimensions, set it to match
	//their desktop resolution.
	if(cmdWidth == -1)
		cmdWidth = ScreenResolution::getScreenWidth();
	if(cmdHeight == -1)
		cmdHeight = ScreenResolution::getScreenHeight();
	DebugStatement(L"SDL1.2Application: Screen width = " << cmdWidth << "; Screen height = " << cmdHeight);

	//Initialise SDL.
#ifdef USE_PORTAUDIO
	if(cmdAudioAPI == L"SDL")
#endif
		sdlFlags |= SDL_INIT_AUDIO;
	if(SDL_Init(sdlFlags) < 0)
	{
		DebugStatement(L"SDL1.2Application: Could not initialise SDL.");
		return;
	}
	DebugStatement("Application: SDL Initialised");

	///	Enable unicode.
	SDL_EnableUNICODE(1);

	//Set window name.
	{
		string tempstr;

		UTF8File::wstringToChar(AppSettings::applicationName, tempstr);
		SDL_WM_SetCaption(tempstr.c_str(), 0);
	}
	DebugStatement("Application: Window name set");

	//Handle any SDL-specific window creation stuff here.
	{
		int flags = SDL_OPENGL;

		//Try and accommodate 16-bit displays.
		if(is16Bit())
		{
			DebugStatement(L"SDL1.2Application: Display is 16bit.");
			if(SDL_GL_SetAttribute(SDL_GL_RED_SIZE, 5) < 0)
				DebugStatement(L"SDL1.2Application: Could not set red pixel size to 5...");
			if(SDL_GL_SetAttribute(SDL_GL_GREEN_SIZE, 5) < 0)
				DebugStatement(L"SDL1.2Application: Could not set green pixel size to 5...");
			if(SDL_GL_SetAttribute(SDL_GL_BLUE_SIZE, 5) < 0)
				DebugStatement(L"SDL1.2Application: Could not set blue pixel size to 5...");
		}
		else
		{
			DebugStatement(L"SDL1.2Application: Display is 32bit.");
			if(SDL_GL_SetAttribute(SDL_GL_RED_SIZE, 8) < 0)
				DebugStatement(L"SDL1.2Application: Could not set red pixel size to 8...");
			if(SDL_GL_SetAttribute(SDL_GL_GREEN_SIZE, 8) < 0)
				DebugStatement(L"SDL1.2Application: Could not set green pixel size to 8...");
			if(SDL_GL_SetAttribute(SDL_GL_BLUE_SIZE, 8) < 0)
				DebugStatement(L"SDL1.2Application: Could not set blue pixel size to 8...");
			if(SDL_GL_SetAttribute(SDL_GL_ALPHA_SIZE, 8) < 0)
				DebugStatement(L"SDL1.2Application: Could not set alpha pixel size to 8...");
		}
		//Set the depth buffer size.
		if(SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, 16) < 0)
			DebugStatement(L"SDL1.2Application: Could not set depth size to 16...");
		//Turn on double-buffering.
		if(SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1) < 0)
			DebugStatement(L"SDL1.2Application: Could not set double buffering...");
		//Turn on v-sync.
		if(vSync)
		{
			if(SDL_GL_SetAttribute(SDL_GL_SWAP_CONTROL, 1) < 0)
				DebugStatement(L"SDL1.2Application: Could not set vertical sync...");
		}

		//Make the window fullscreen, or centre it if not.
		if(!cmdWindow)
			flags |= SDL_FULLSCREEN;
		else
		{
			char tempstr[] = "SDL_VIDEO_CENTERED=1";
			putenv(tempstr); 
		}

		//Construct the actual window.
		if(SDL_SetVideoMode(cmdWidth, cmdHeight, 0, flags) == 0)
		{
			DebugStatement(L"SDL1.2Application: Could not set video mode: " << SDL_GetError());

			quit();
			return;
		}
	}

	//Construct and initialise the Drawer for this game.
	drawer = appSettings.getDrawer();
	drawer->initialise(cmdWidth, cmdHeight, appSettings);

	//Initialise any joysticks.
	SDL_JoystickEventState(SDL_ENABLE);

	for(i=0;i<SDL_NumJoysticks();++i)
		joysticks.push_back(SDL_JoystickOpen(i));

	//Initialise audio.
#ifdef USE_PORTAUDIO
	if(cmdAudioAPI == L"SDL")
#endif
	{
		audioDevice = AudioDevicePtr(new SDL1_2AudioDevice());
		DebugStatement(L"SDL1.2Application: Using SDL1.2AudioDevice for sound");
	}
#ifdef USE_PORTAUDIO
	else
	{
		audioDevice = AudioDevicePtr(new PortAudioDevice());
		DebugStatement(L"SDL1.2Application: Using PortAudioDevice for sound");
	}
#endif
	audioDevice->setAPI(cmdAudioAPI);
	audioDevice->setCallback(this);

	//Construct and initialise the Sounder for this game.
	sounder = appSettings.getSounder();
	sounder->setNumChannels(audioDevice->getNumInputs(),
							audioDevice->getNumOutputs());
	sounder->setSamplerate(audioDevice->getSamplerate());

	//Start the audio.
	if(!audioDevice->start())
		DebugStatement(L"SDL1.2Application: Could not start audio device.");

	//Frustratingly, SDL may not give us the I/O and samplerate we requested, so
	//we need to update our Sounder accordingly.
	if(cmdAudioAPI == L"SDL")
	{
		sounder->setNumChannels(audioDevice->getNumInputs(),
								audioDevice->getNumOutputs());
		sounder->setSamplerate(audioDevice->getSamplerate());
	}

	//Hide the cursor.
	SDL_ShowCursor(SDL_DISABLE);

	//Initialise SDL_image.
	IMG_Init(IMG_INIT_JPG|IMG_INIT_PNG);

	//Create the LoadingScreen state.
	currentState = LoadingScreen::creator.createInstance(this, sounder);

	DebugStatement(L"SDL1.2Application: Initialisation complete.");
}

//------------------------------------------------------------------------------
void SDL1_2Application::getAudio(float *input, float *output, int numSamples)
{
	if(sounder)
		sounder->getAudio(input, output, numSamples);
}

//------------------------------------------------------------------------------
void SDL1_2Application::eventLoop()
{
	uint32_t tempint;
	SDL_Event event;
	unsigned int i;

	while(1)
	{
		if(timeToQuit)
			break;

		//Handle events.
		while(SDL_PollEvent(&event))
		{
			switch(event.type)
			{
				case SDL_KEYDOWN:
					keyManager.getActions(event.key.keysym, mappingsVector);

					for(i=0;i<mappingsVector.size();++i)
					{
						currentState->keyDown(mappingsVector[i],
											  wstring(1, wchar_t(event.key.keysym.unicode)),
											  keyManager.getModVal(event.key.keysym.mod));
					}
					if(!mappingsVector.size())
					{
						currentState->keyDown(L"",
											  wstring(1, wchar_t(event.key.keysym.unicode)),
											  keyManager.getModVal(event.key.keysym.mod));
					}
					break;
				case SDL_KEYUP:
					keyManager.getActions(event.key.keysym, mappingsVector);

					for(i=0;i<mappingsVector.size();++i)
					{
						currentState->keyUp(mappingsVector[i],
											wstring(1, wchar_t(event.key.keysym.unicode)),
											keyManager.getModVal(event.key.keysym.mod));
					}
					if(!mappingsVector.size())
					{
						currentState->keyUp(L"",
											wstring(1, wchar_t(event.key.keysym.unicode)),
											keyManager.getModVal(event.key.keysym.mod));
					}
					break;
				case SDL_MOUSEBUTTONDOWN:
					switch(event.button.button)
					{
						case SDL_BUTTON_WHEELUP:
							currentState->mouseWheel(false);
							break;
						case SDL_BUTTON_WHEELDOWN:
							currentState->mouseWheel(true);
							break;
						default:
							currentState->mouseDown(event.button.button,
													mouseToDrawer(TwoFloats(event.motion.x,
																			event.motion.y)));
					}
					break;
				case SDL_MOUSEBUTTONUP:
					currentState->mouseUp(event.button.button,
										  mouseToDrawer(TwoFloats(event.motion.x,
																  event.motion.y)));
					break;
				case SDL_MOUSEMOTION:
					currentState->mouseMove(mouseToDrawer(TwoFloats(event.motion.x,
																	event.motion.y)));
					break;
				case SDL_QUIT:
					timeToQuit = true;
					break;
				case SDL_JOYAXISMOTION:
					keyManager.getActions(event.jaxis, mappingsVector);

					for(i=0;i<mappingsVector.size();++i)
					{
						currentState->joyMove(mappingsVector[i],
											  (float)(event.jaxis.value)/32768.0f,
											  event.jaxis.which,
											  event.jaxis.axis);
					}
					if(!mappingsVector.size())
					{
						currentState->joyMove(L"",
											  (float)(event.jaxis.value)/32768.0f,
											  event.jaxis.which,
											  event.jaxis.axis);
					}
					break;
				case SDL_JOYBUTTONDOWN:
					{
						wstring tempstr = keyManager.getJoyButtonText(event.jbutton);
						keyManager.getActions(event.jbutton, mappingsVector);

						for(i=0;i<mappingsVector.size();++i)
						{
							currentState->keyDown(mappingsVector[i],
												  tempstr,
												  0);
						}
						if(!mappingsVector.size())
							currentState->keyDown(L"", tempstr, 0);
					}
					break;
				case SDL_JOYBUTTONUP:
					{
						wstring tempstr = keyManager.getJoyButtonText(event.jbutton);
						keyManager.getActions(event.jbutton, mappingsVector);

						for(i=0;i<mappingsVector.size();++i)
						{
							currentState->keyUp(mappingsVector[i],
												tempstr,
												0);
						}
						if(!mappingsVector.size())
							currentState->keyUp(L"", tempstr, 0);
					}
					break;
				case SDL_JOYHATMOTION:
					{
						wstring tempstr = keyManager.getJoyHatText(event.jhat);
						keyManager.getActions(event.jhat, mappingsVector);

						for(i=0;i<mappingsVector.size();++i)
						{
							currentState->keyUp(mappingsVector[i],
												tempstr,
												0);
						}
						if(!mappingsVector.size())
							currentState->keyUp(L"", tempstr, 0);
					}
					break;
			}
		}

		//Repaint the screen, or wait a bit first.
		//We aim for a 60fps frame rate here.
		//Why does this not match framerates between Release and Debug builds on
		//Windows? (Release: ~104fps; Debug: ~68fps)
		if(!vSync)
		{
			tempint = lastRepaint;
			lastRepaint = SDL_GetTicks();
			tempint = lastRepaint - tempint;
			if(tempint < 16)
				SDL_Delay(16 - tempint);
		}

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

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

//------------------------------------------------------------------------------
void SDL1_2Application::setMousePos(const TwoFloats& pos)
{
	SDL_WarpMouse((int)pos.x, (int)pos.y);
}

//------------------------------------------------------------------------------
void SDL1_2Application::nextState(const wstring newState, const wstring extra)
{
	wstring tempState;

	if(newState == L"-start state-")
		tempState = startState;
	else
		tempState = newState;

	//Check if the newState exists.
	if(stateCreators.find(tempState) != stateCreators.end())
	{
		//Delete the current state.
		currentState.reset();

		//Construct the new state.
		currentState = stateCreators[tempState]->createInstance(this, sounder);
		DebugStatement(L"SDL1_2Application: New state created: " << tempState);
	}
	else
		DebugStatement(L"SDL1_2Application: Could not find a GameStateCreator for " << tempState);
}

//------------------------------------------------------------------------------
void SDL1_2Application::registerGameState(const wstring& stateName,
										  const GameStateCreatorBase *stateCreator,
										  bool isStartState)
{
	stateCreators.insert(make_pair(stateName, stateCreator));
	if(isStartState)
		startState = stateName;
}

//------------------------------------------------------------------------------
void SDL1_2Application::cmdLineParameterFound(const std::wstring& name,
										      const std::wstring& value)
{
	wstringstream tempstr;

	if(name == L"width")
	{
		tempstr << value;
		tempstr >> cmdWidth;
	}
	else if(name == L"height")
	{
		tempstr << value;
		tempstr >> cmdHeight;
	}
	else if(name == L"window")
		cmdWindow = true;
	else if(name == L"mute")
		cmdMute = true;
	else if(name == L"audioAPI")
		cmdAudioAPI = value;
	else if(name == L"help")
	{
		//We write the help text to the command line in CmdLineParams.
		cmdHelp = true;
		quit();
	}
}

//------------------------------------------------------------------------------
TwoFloats SDL1_2Application::mouseToDrawer(const TwoFloats& pos)
{
	TwoFloats retval;
	const TwoFloats& drawSize = drawer->getDrawingAreaSize();
	const TwoFloats& screenSize = drawer->getWindowSize();
	//const float dAspect = drawSize.y/drawSize.x;
	//const float aspect = screenSize.y/screenSize.x;

	retval.x = pos.x/screenSize.x;
	retval.x *= drawSize.x;
	retval.y = pos.y/screenSize.y;
	retval.y *=  drawSize.y;

	return retval;
}

//------------------------------------------------------------------------------
bool SDL1_2Application::is16Bit()
{
	bool retval = false;
	const SDL_VideoInfo *info = SDL_GetVideoInfo();

	if(info->vfmt->BitsPerPixel == 16)
		retval = true;

	return retval;
}

