/*
   Copyright (C) 2004 by James Gregory
   Part of the GalaxyHack project
 
   This program is free software; you can redistribute it and/or modify
   it under the terms of the GNU General Public License.
   This program is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY.
 
   See the COPYING file for more details.
*/

#include "Globals.h"
#include "Group.h"
#include "MainMenu.h"
#include "SetupBattle.h"
#include "PreBattle.h"
#include "RTS.h"
#include "Score.h"
#include "ForceSelect.h"
#include "LookupTables.h"
#include "Stuff.h"

#include <sstream>
#include <iostream>
#include <stdexcept>
#include <iterator>
#include <boost/filesystem/operations.hpp>

using std::cout;
using std::endl;
using std::ofstream;
using std::runtime_error;
using std::terminate;
using std::istringstream;
using std::istream_iterator;
using std::find;
using std::getline;

//Global just to this file:
GameState* game = 0;
int fpsState = 0;
string userHomePath;
int lastSwitchTime; //set in init

// Function declarations //////////////////////////////////
void FindHomePath();
bool DealWithArgs(int argc, char* argv[], vector<string>& preloadSides);
void LoadSettings(char* argv[]);
void SaveSettings();
void GameInit(char* argv[]);
bool GameMain();
void PollInput();
void DealWithEvent(SDL_Event& event);
void FPSCounter();
void GameShutdown(bool saveSettings = true);
void ExceptionShutdown(runtime_error e);

int main(int argc, char* argv[]) {
	vector<string> preloadSides;

	try {
		if (!DealWithArgs(argc, argv, preloadSides))
			return 0;

		GameInit(argv);

		if (preloadSides.size()) {
			for (int i = 0; i != preloadSides.size(); ++i) {
				sides.push_back(Side(preloadSides[i]));
				sides[i].FilesToSDStruct();
				sides[i].LoadData(false);
			}
			gsTo = GST_PreBattle;
			PreBattle::pbState = PBS_PreBattle;
		}
	} catch(runtime_error e) {
		const char* error = e.what();
		WriteLog(error);
		GameShutdown(false);
		terminate();
	}
	
	try {
		while (GameMain());
	} catch(runtime_error e) {
		ExceptionShutdown(e);
	}

	GameShutdown();

	return 0;
}

void FindHomePath() {
#ifndef WIN32
	char *home = 0;

	home = getenv("HOME");

	if (home) {
		userHomePath = home;
		userHomePath += "/.galaxyhack/";
	}
#endif
}

bool DealWithArgs(int argc, char* argv[], vector<string>& preloadSides) {
	if (argc > 1) {
		bool validArgs = 1;

		for (int i = 1; i != argc; ++i) {
			string theArg = argv[i];

			if (theArg == "-driver" && argc > i + 1) {
					globalSettings.videoDriver = argv[i+1];
					cout << "Ignoring settings file and using video driver " + globalSettings.videoDriver << endl << endl;
					++i;
			} else if (theArg == "-x" && argc > i + 2) {
					preloadSides.push_back(argv[i+1]);
					preloadSides.push_back(argv[i+2]);
					i +=2;
			} else if (theArg == "-b" && argc > i + 1) {
					globalSettings.batch = true;
					string framesString = argv[i+1];
					string::const_iterator begin = framesString.begin();
					string::const_iterator end = framesString.end();
					globalSettings.maxFrames = IterToInt(begin, end);
					#ifndef WIN32
					cout << "Batch mode initiated, max frames set to " + globalSettings.maxFrames << endl << endl;
					#endif
					++i;
			} else if (theArg == "-u" && argc > i + 1) {
					string speedString = argv[i+1];
					string::const_iterator begin = speedString.begin();
					string::const_iterator end = speedString.end();
					worldUpdateInterval = IterToInt(begin, end);
					#ifndef WIN32
					cout << "World update interval set to" + worldUpdateInterval << endl << endl;
					#endif
					++i;
			} else if (theArg == "-r" && argc > i + 1) {
					string randomString = argv[i+1];
					string::const_iterator begin = randomString.begin();
					string::const_iterator end = randomString.end();
					globalSettings.randomSeed = IterToInt(begin, end);					
					#ifndef WIN32
					cout << "Random seed set to" + globalSettings.randomSeed << endl << endl;
					#endif
					++i;
			} else
				validArgs = 0;
		}

		if (validArgs == 0) {
			cout << "Usage: ./galaxyhack [OPTIONS]" << endl << endl;

			cout << "GalaxyHack - AI script based computer game" << endl << endl;
			
			cout << "Options:" << endl;
			cout << "  -driver <videodriver>: use the specified video driver" << endl << endl;
			
			cout << "  \"x11\" is the default but runs rather slowly." << endl;
			cout << "  \"dga\" will make the game run faster, but requires root permissions (run rootperms.sh) and only works with certain video cards." << endl << endl;
			
			cout << "  -x <fleet1> <fleet2>: skip menu screen and immediately start a battle between the two fleets given" << endl;
			cout << "  -b <number of frames>: batch mode, see manual for details" << endl;
			cout << "  -u <number of frames>: world update interval, see manual for details" << endl;
			cout << "  -r <seed>: set the seed for the random number generator" << endl << endl;
			
			cout << "A number of other options can be changed by editing ~/.galaxyhack/settings.dat" << endl << endl;

			return false;
		}
	}

	return true;
}

void LoadSettings(char* argv[]) {
#ifndef WIN32
	string settingsPath = userHomePath + "settings.dat";
	if (!DoesFileExist(settingsPath))
		settingsPath = "settings.dat";
#else
	string settingsPath = "settings.dat";
#endif

	if (!DoesFileExist(settingsPath)) {
#ifndef WIN32
		string error = "Couldn't find settings.dat in $HOME/.galaxyhack or in present working directory";
#else
		string error = "Couldn't find settings.dat";
#endif
		throw runtime_error(error.c_str());
	}

	string inputStr;
	FileToString(settingsPath, inputStr);
	istringstream input(inputStr);

	istream_iterator<char> iter = input;
	istream_iterator<char> fileEnd = istream_iterator<char>();
	
	#ifndef WIN32
		cout << "Using " + settingsPath + ":" << endl << endl;
		cout << inputStr;
	#endif

	//commander
	iter = find(iter, fileEnd, ':');
	input.ignore();
	getline(input, globalSettings.commander);
	iter = input;
	
	//data path
	iter = find(iter, fileEnd, ':');
	input.ignore();
	getline(input, globalSettings.bdp);
	if (globalSettings.bdp == "pwd")
		globalSettings.bdp = "";
	else if (globalSettings.bdp[globalSettings.bdp.size() -1] != '/')
		globalSettings.bdp += '/';		
	iter = input;

	//video driver
	iter = find(iter, fileEnd, ':');
	input.ignore();
	//may have been set on command line
	if (globalSettings.videoDriver == "")
		getline(input, globalSettings.videoDriver);
	iter = input;
	
	//fullscreen
	iter = find(iter, fileEnd, ':');
	++iter;
	globalSettings.fullScreen = static_cast<bool>(IterToInt(iter, fileEnd));
	
	//screen resolution
	iter = find(iter, fileEnd, ':');
	++iter;
	globalSettings.screenWidth = IterToInt(iter, fileEnd);

	iter = find(iter, fileEnd, ':');
	++iter;
	globalSettings.screenHeight = IterToInt(iter, fileEnd);
	
	//music
	iter = find(iter, fileEnd, ':');
	++iter;
	globalSettings.bMusic = static_cast<bool>(IterToInt(iter, fileEnd));
	
	//disable sound
	iter = find(iter, fileEnd, ':');
	++iter;
	globalSettings.disableSound = static_cast<bool>(IterToInt(iter, fileEnd));
	
	//default pics
	iter = find(iter, fileEnd, ':');
	input.ignore();
	getline(input, globalSettings.defaultCSPic);
	iter = input;

	iter = find(iter, fileEnd, ':');
	input.ignore();
	getline(input, globalSettings.defaultFrPic);
	iter = input;

	iter = find(iter, fileEnd, ':');
	input.ignore();
	getline(input, globalSettings.defaultSSPic);
	iter = input;

	//remembered fleets
	for (int i = 0; i != maxPlayers; ++i) {
		iter = find(iter, fileEnd, ':');
		input.ignore();
		string tempStr;
		getline(input, tempStr);
		globalSettings.rememberFleets.push_back(tempStr);
		iter = input;
	}

	iter = find(iter, fileEnd, ':');
	++iter;
	globalSettings.howGreenIsGreen = IterToInt(iter, fileEnd);
	
	oldGlobalSettings = globalSettings;
	
	if (globalSettings.batch) {
		globalSettings.disableSound = true;
		globalSettings.dontWriteSound = true;
		worldUpdateInterval = 0;
	}
}

void SaveSettings() {
#ifndef WIN32
	namespace fs = boost::filesystem;
	fs::path userHomePathPath(userHomePath);
	if (!fs::exists(userHomePathPath))
		fs::create_directory(userHomePathPath);
#endif
	string settingsStr = userHomePath + "settings.dat";
	ofstream output(settingsStr.c_str(), std::ios::trunc | std::ios::out);

	output << "- there must be exactly one space between each colon and the setting that follows" << endl;
	output << endl;
	
	output << "Commander: " << globalSettings.commander << endl;
	if (globalSettings.bdp.size())
		output << "Base data path: " << globalSettings.bdp << endl << endl;
	else
		output << "Base data path: pwd" << endl << endl;
	
	output << "Video driver: " << globalSettings.videoDriver << endl;
	
	output << "Fullscreen: " << globalSettings.fullScreen << endl;
	output << "Screen width: " << globalSettings.screenWidth << endl;
	output << "Screen height: " << globalSettings.screenHeight << endl << endl;
	
	output << "Music: " << globalSettings.bMusic << endl;
	
	if (globalSettings.dontWriteSound)
		output << "Disable sound: " << oldGlobalSettings.disableSound << endl << endl;
	else
		output << "Disable sound: " << globalSettings.disableSound << endl << endl;

	output << "Default cap ship pic: " << globalSettings.defaultCSPic << endl;
	output << "Default frigate pic: " << globalSettings.defaultFrPic << endl;
	output << "Default small ship pic: " << globalSettings.defaultSSPic << endl << endl;
	
	for (int i = 0; i != globalSettings.rememberFleets.size(); ++i)
		output << "Fleet " << i + 1 << ": " << globalSettings.rememberFleets[i] << endl;
	output << endl;

	output << "How green is green: " << globalSettings.howGreenIsGreen << endl;
}

void GameInit(char* argv[]) {
	namespace fs = boost::filesystem;
	//boost is really quite stupid
	#ifndef WIN32
		fs::path::default_name_check(fs::windows_name);
	#else
		fs::path::default_name_check(fs::native);
	#endif
	
	FindHomePath();
	LoadSettings(argv);
	
	string tmpStr = "SDL_VIDEODRIVER=" + globalSettings.videoDriver;
	char* sillyness = c_strNC(tmpStr);
	putenv(sillyness);

	JSDL.Init();
	SetupLookupTables();
	LoadStandardGraphics();

	lastSwitchTime = SDL_GetTicks();
}


bool GameMain() {
	now = SDL_GetTicks();
	
	int timePerFrame;
	//1000ms/60fps = 16 ish, though this will get rounded to 20
	if (worldUpdateInterval > 0)
		timePerFrame = 16;
	//1000ms/20fps = 50
	else	
		timePerFrame = 50;
	if (now - lastSwitchTime > timePerFrame) {
		skipDisplayFrame = false;
		lastSwitchTime = now;
	} else
		skipDisplayFrame = true;

	PollInput();
	UpdateWindows();

	if (gsTo == gsCurrent)
		game->Main();

	else try {
			SafeDelete(game);

			if (gsTo == GST_Reload)
				gsTo = gsCurrent;

			switch (gsTo) {
			case GST_MainMenu:
				game = new MainMenu::MainMenu_State;
				break;

			case GST_SetupBattle:
				game = new SetupBattle::SetupBattle_State;
				break;

			case GST_PreBattle:
				game = new PreBattle::PreBattle_State;
				break;

			case GST_Battle:
				game = new RTS::RTS_State;
				break;

			case GST_Score:
				game = new Score::Score_State;
				break;

			case GST_ForceSelect:
				game = new ForceSelect::ForceSelect_State;
				break;

			case GST_TheOS:
				return false;
				break;
			}
			
			//make sure menus have a starting event to force them to update screen before user
			//input in spite of SDL_WaitEvent
			SDL_Event event;
			event.type = SDL_USEREVENT;
			SDL_PushEvent(&event);

			//make sure we don't get stuck in poll event loop before display is updated on fast computers
			lastSwitchTime -= 1000;
			
			gsCurrent = gsTo;
		} catch(runtime_error e) {
			if (gsTo != GST_MainMenu) {
				sides.clear();
				KillAllWindows();
				const char* error = e.what();
				WriteLog(error);
				globalErrorString = error;
				
				if (gsCurrent == GST_MainMenu)
					gsCurrent = GST_TheOS;
				gsTo = GST_MainMenu;
			} else
				throw runtime_error(e);
		}

	if (fpsState == 1)
		FPSCounter();
	
	if (!globalSettings.batch) {
		static SDL_Rect mouseRect = {0, 0, genPictures[GENPIC_CURSOR]->w, genPictures[GENPIC_CURSOR]->h};
		
		int mx, my;
		SDL_GetMouseState(&mx, &my);
		mouseRect.x = mx;
		mouseRect.y = my;

		JSDL.Blt(genPictures[GENPIC_CURSOR], mouseRect);
	}

	//this does two things:

	//1. in windowed mode, it prevents it going so fast
	//it actually slows down to a crawl, I think maybe
	//because blits start having to wait for other ones to finish

	//2. In full screen mode, it means if you have the game
	//set to go more than 60fps it won't have a limit of the
	//monitor refresh rate

	if (!skipDisplayFrame)
		JSDL.Flip();

	return true;
}

void PollInput() {
	if (globalSettings.batch)
		return;	

	SDL_Event event;
	
	if (gsCurrent == GST_MainMenu || gsCurrent == GST_SetupBattle
	|| gsCurrent == GST_Score || gsCurrent == GST_ForceSelect) {
		int result = SDL_WaitEvent(&event);
		if (!result)
			throw runtime_error("Error when waiting for event");
		DealWithEvent(event);
		return;
	}
	
	while (SDL_PollEvent(&event))
		DealWithEvent(event);
}

void DealWithEvent(SDL_Event& event) {
	switch (event.type) {		
	case SDL_MOUSEBUTTONDOWN:
		if (game && !WinMouseD(event.button.button, event.button.x, event.button.y))
			game->MouseD(event.button.button, event.button.x, event.button.y);
		break;

	case SDL_MOUSEBUTTONUP:
		if (game)
			game->MouseU(event.button.button, event.button.x, event.button.y);
		break;
		
	case SDL_MOUSEMOTION:
		WinMouseM(event.motion.state, event.motion.x, event.motion.y);
		if (game)
			game->MouseM(event.motion.state, event.motion.x, event.motion.y);
		break;
		
	case SDL_KEYDOWN:
		if (WinKeyboard(event.key.keysym))
			break;
			
		switch(event.key.keysym.sym) {
		case SDLK_F4:
			CycleGroupInfoType();
			break;

		case SDLK_F8:
			if (fpsState == false)
				fpsState = true;
			else
				fpsState = false;
			break;
		}
		
		if (game)
			game->Keyboard(event.key.keysym);			
		break;
	
	/* FIXME this doesn't work at all
	case SDL_ACTIVEEVENT:
		if (event.active.state & SDL_APPACTIVE && event.active.gain == 0) {
			while (1) {
				if (SDL_PollEvent(&event)) {		
					if (event.active.state == SDL_APPACTIVE && event.active.gain == 1)
						break;
				}
				SDL_Delay(500);
			}
		}
		break;
	*/

	case SDL_QUIT:
		gsTo = GST_TheOS;
		break;
		
	default:
		break;
	}
}

void FPSCounter() {
	static int fpsCounter = 0;
	static int fpsSave = 0;
	static unsigned int fpsTimer = SDL_GetTicks();

	if (now - fpsTimer > 1000) {
		fpsSave = fpsCounter;
		fpsCounter = 0;
		fpsTimer = now;
	} else
		++fpsCounter;

	char output[60];
	sprintf(output, "FPS (sort of): %d", fpsSave);
	normalFonts.BlitString(50, globalSettings.screenHeight - 20, 0, output);
}

void GameShutdown(bool saveSettings) {
	if (saveSettings)
		SaveSettings();

	ClearStandardGraphics();

	JSDL.Shutdown();

	//should already be deleted unless an exception has been thrown
	SafeDelete(game);
}

void ExceptionShutdown(runtime_error e) {
	const string eWhat = e.what();

	const string errorLog = "Exception shutdown due to: " + eWhat;
	WriteLog(errorLog.c_str());
	
	if (globalSettings.batch) {
		GameShutdown();
		terminate();
	}

	if (JSDL.screen->locked)
		JSDL.UnlockBack();

	const string errorWin = "Major problem:\n" + eWhat + "\n\nClick the left mouse button to exit";
	CreateInfoString(errorWin);
	list<GenWindow>::reverse_iterator iter = myWindows.rbegin();
	iter->DrawSelf();

	JSDL.ForceFlip();

	while (1) {
		SDL_Event event;
		while (SDL_PollEvent(&event)) {		
			if (event.type == SDL_MOUSEBUTTONDOWN || event.type == SDL_KEYDOWN) {
				GameShutdown();
				terminate();
			}
		}
	}
}

