#include <iostream>
#include <fstream>
#include <algorithm>
#include <boost/filesystem.hpp>
#include <SDL/SDL_net.h>

#include "Model.h"
#include "Physics.h"
#include "PhysicsBase.h"

#include "EntityFactory.h"
#include "AsteroidsEvents.h"
#include "AsteroidEntity.h"

#include <assert.h>

namespace asteroids {

static Model* ModelInstance = NULL;

Engine::Variable Model::HighscoreServerName ("highscore_server_name", "asteroids.fysx.org");
Engine::Variable Model::HighscoreServerPath ("highscore_server_path", "/highscore/highscore.php?format=raw");
Engine::Variable Model::UseServerHighscore ("use_server_highscore", "false");
// makes sure we have asked whether the player wants to submit the highscore data
Engine::Variable Model::UseServerHighscoreAsked ("use_server_highscore_asked", "false");

/*
 * Inherited Module functions
 */
int Model::OnInit (int argc, char* argv[]) {
	int result = Engine::ModelBase::OnInit (argc, argv);

	Engine::LogMessage ("Model Initialization!");

	ModelInstance = this;

	mGameState = GameStatePaused;
	mLastGameState = GameStatePaused;

	/// \TODO use <optional> or similar for initialization of mCurrentLevelIndex
	mCurrentLevelIndex = 99999;

	mGameModified = false;
	mCurrentLevelModified = false;

	// populate the level hashes
	mLevelHashes["level01_lets_get_started.map"]            	= "1165bac2b862118fb9c830e2f2f87c47782dded737954d81430f93adcadd3567";
	mLevelHashes["level02_sing_along.map"]                  	= "206e1e8b60a75a97de2bd91f5422ccaade43463daf5d5d4df971f0c3d9ef3f28";
	mLevelHashes["level03_spacewalk.map"]                   	= "e6c615af0b7ece9e9c826d2f431a0de080c199e2685b4f0772bb54e16894e9de";
	mLevelHashes["level04_spiessrutenlauf.map"]             	= "4ba5c830f38e2b07b7b899d744fc4cefbd40921904492dfb396f24897a9925c0";
	mLevelHashes["level05_WolleNew1_might_want.map"]        	= "7289454d67c74a8d5e1afe02996e786458655182355b19fe2cd1a51f4139d1da";
	mLevelHashes["level06_WolleNew3_watch_your.map"]        	= "e5e86667623b12a491a2395a9b361617ee961edf8d349b7419942f0dbf76b769";
	mLevelHashes["level07_circled.map"]                     	= "98aa36bf2c8f6be8cd23c7c284d6ff413db992f09e5d5866e5a3aa393a93b367";
	mLevelHashes["level08_highway.map"]                     	= "bf5e678750d3f4d47c7bfec5ae62d852db1f475146ef013040d1e67308708a66";
	mLevelHashes["level09_WolleNew4_wheres_the_brain.map"]  	= "f3f685f6eb6cc703a5eaa0177472717ea49dddc2099e2bd1865f551f94e8fdf4";
	mLevelHashes["level10_WolleWhatever_whatever.map"]      	= "2f97984e86bd0969909032fa44bcf883dd181fbe61782ee7dd704b0b63dfd0e1";
	mLevelHashes["level11_Strahlensatz.map"]                	= "3f293d4b0ea23688c15761a54b2ee097cc6a41b30750cc8fc5fccf94c20bf018";
	mLevelHashes["level12_almost_there_test5.map"]            = "5cefdc7dadbbfc30f32580f3e50cfbb6cae9ca5f432fd73e777bdbb835f3660a";
	mLevelHashes["level13_WolleHarder1_tribute_to_lw.map"]  	= "1b52ccfb9f6211eb07d880cab6bf23b3ac05646561b555077bae14ad8f40c80c";
	mLevelHashes["level14_asteroid_field_lets_hide.map"]    	= "a9e2ddca00eef71a51f564f7d7cdd482e8a4d4fc7b70d4d2bf3d5f496188fce1";
	mLevelHashes["level15_WolleNew6_smack_my.map"]          	= "1d7e1223bbd8a68492108c2416c1a8ba828d293601c02e489dbf6b5db6a1aaa2";
	mLevelHashes["level16_Wolle7_todesspirale.map"]         	= "d82d8f6c21806238a7b6a87c1217a0dff42f4e59e73f28e7af3f0c8afd908e38";
	mLevelHashes["level17_WolleEasyNew1_twin_supernova.map"]	= "4e177f82a0ba8f909ff1af1adf13caf82ab41cf09d69504c87f3e0551b9b73ed";
	mLevelHashes["level18_WolleNew2_dont_drink.map"]        	= "3b7b0fa59ee652e763ea43ffd7e572e1bd0436116fd5d5b13ed679fce362d265";
	mLevelHashes["level19_Geisterfahrer.map"]               	= "2036741d6fd4a863324210e53b903d7a898c4478e5eb9607a43a110bb6999ace";
	mLevelHashes["level20_billiard.map"]                    	= "23dcffbf61bd887db7613b02d0c9ad04d77d800af631edbcf5822a169dcc2a9d";
	mLevelHashes["level21_final.map"]                       	= "6384b1565c0c844d7083e744d6cfbaf9de42f5ad5ecb0cb668c3a2458860c8ac";
	mLevelHashes["level22_bonus.map"]                       	= "a5c9455284fb2311687b5076693ab0df50c7766044561d79f4c46896842a6b44";

	if (InitLevelList() == 0)
		Engine::LogError ("No levels found!");

	// Reset the newest highscore entry index which may be used for highlighting
	// the newest entry.
	mNewestHighscoreEntryIndex = std::numeric_limits<unsigned int>::max();
	mHighscoreIsOnline = false;

	// initialize event handlers and register them
	Engine::RegisterListener (this, EventGameOver);
	Engine::RegisterListener (this, EventShipExplode);

	mPlayerName = "Player";

	mLevelAuthor = "";
	mLevelTitle = "";
	mLevelName = "";
	mLevelParTimeSeconds = -1.f;
	mPoints = 0;

	// initialize SDL_net to be able to retrieve highscore from the internet
	if (SDLNet_Init() == -1) {
		Engine::LogError("SDLNet_Init: %s\n", SDLNet_GetError());
	}

	Engine::RegisterListener (this, EventAccelerateStart);
	Engine::RegisterListener (this, EventAccelerateStop);
	Engine::RegisterListener (this, EventLevelComplete);

	return result;
}

void Model::OnDestroy() {
	Engine::ModelBase::OnDestroy();
	mAsteroids.clear();
	mLevelList.clear();
	SaveLocalHighscoreList();

	SDLNet_Quit();
}

bool Model::OnReceiveEvent (const Engine::EventBasePtr &event) {
	switch (event->mEventType) {
		case EventAccelerateStart:
			Engine::PlaySoundLoop(Engine::GetResourceFullPath("/data/sounds/thrust.wav"), -1);
			break;
		case EventAccelerateStop:
			Engine::HaltSoundLoop(Engine::GetResourceFullPath("/data/sounds/thrust.wav"));
			break;
		case EventShipExplode:
			OnShipExplode();
			break;
		case EventLevelComplete:
			OnLevelComplete ();
			break;
		case EventGameOver:
			return OnGameOver();
			break;

		default: Engine::LogWarning ("Received Event with type %d but don't know what to do with it!", event->mEventType);
						 break;
	}

	return false;
}

/*
 * Module specific functions
 */
void Model::Process () {
	if (mLastGameState == mGameState) {
		if (mGameState == GameStateRunning) {
			// Only if we are still controlling the ship we increase the level
			// timer
			if (Engine::GetPlayerEntityId() != Engine::NullEntityId)
				mLevelTimeSeconds += Engine::GetFrameDuration();

			Engine::ModelBase::Process();
		}
		return;
	}

	// when we are here we know that something has changed so we need to take
	// some action.
	Engine::LogDebug ("Switching from %s->%s", GetStringGameState(mLastGameState), GetStringGameState(mGameState));

	// ... and we have to set the last game state to the current gamestate
	// otherwise we end up in an infinite loop of performing the switching
	// action.
	mLastGameState = mGameState;
}

unsigned int Model::InitLevelList () {
	boost::filesystem::path level_dir_path;
	std::string level_dir_name = Engine::GetResourceFullPath("/data/levels/");
	Engine::LogDebug ("Searching for levels in %s", level_dir_name.c_str());

	mLevelList.clear();

	boost::filesystem::path level_dir(level_dir_name);
	
	if (!boost::filesystem::exists(level_dir.file_string())) {
		Engine::LogError ("Could not init level list: %s does not exist!", level_dir.filename());
	}

	if (!boost::filesystem::is_directory(level_dir)) {
		Engine::LogError ("Could not init level list: %s is not a directory!", level_dir_name.c_str());
	}

	boost::filesystem::directory_iterator end_iter;
	for (boost::filesystem::directory_iterator dir_iter(level_dir);
			dir_iter != end_iter;
			++dir_iter) {
		if (boost::filesystem::is_regular_file (dir_iter->status())) {
			std::string level_relative_path (level_dir_name);
			level_relative_path += dir_iter->path().filename();

			// check whether we found an official level
			std::string map_name (dir_iter->filename());

			if (mLevelHashes.find(map_name) == mLevelHashes.end()) {
				Engine::LogDebug ("Skipping unofficial level %s", std::string(level_relative_path).c_str());
				continue;
			}

			mLevelList.push_back (level_relative_path);
			Engine::LogDebug ("Found level %s", mLevelList[mLevelList.size()-1].c_str());
		}
	}

	std::sort(mLevelList.begin(), mLevelList.end());

	return mLevelList.size();
}

void Model::ParseHighscoreStream (std::istream &highscore_stream) {
	std::string line;

	while (getline(highscore_stream, line)) {
		std::string name;
		unsigned int points;

		std::string::size_type delimiter = line.find ('\t');
		if (delimiter == std::string::npos)
			break;

		name = line.substr(0, delimiter);

		std::istringstream points_stream(line.substr(delimiter + 1, line.size()));
		points_stream >> points;

		Engine::LogDebug ("Read Highscore Entry Name: %s Points: %d", name.c_str(), points);
		AddLocalHighscoreEntry (name, points);
	}
}

void Model::LoadHighscoreList () {
	mHighscoreList.clear();

	if (mHighscoreList.size() < 10) {
		AddLocalHighscoreEntry ("Imperator",      1000000);
		AddLocalHighscoreEntry ("Luke Skywalker",  800000);
		AddLocalHighscoreEntry ("Darth Vader",     600000);
		AddLocalHighscoreEntry ("Han Solo",        400000);
		AddLocalHighscoreEntry ("Princess Leia",   200000);
		AddLocalHighscoreEntry ("C3PO",            100000);
		AddLocalHighscoreEntry ("R2-D2",            50000);
		AddLocalHighscoreEntry ("Chewy",            10000);
		AddLocalHighscoreEntry ("Mr. Ewok",          5000);
		AddLocalHighscoreEntry ("Jabba the Hutt",    1000);
	}

	mHighscoreIsOnline = false;

	if (Model::UseServerHighscore.GetBoolValue()) {
		Engine::LogDebug ("Retrieving Highscore from server");
		std::stringstream global_highscore_stream;

		if (PullGlobalHighscore(global_highscore_stream)) {
			ParseHighscoreStream(global_highscore_stream);
			mHighscoreIsOnline = true;
			return;
		}

		Engine::LogMessage ("Could not load highscore from server, falling back to local.");
	} 

	LoadLocalHighscoreList();
	SaveLocalHighscoreList();
}

void Model::LoadLocalHighscoreList () {
	std::string highscore_filename = Engine::GetUserDirFullPath("/highscore.dat").c_str();
	boost::filesystem::path highscore_file(highscore_filename);

	// if the file does not exist, we create it and write standard values into
	// it.
	if (!boost::filesystem::exists(highscore_file)) {
		Engine::LogDebug ("Local highscore file not found!");
		return;
	}

	if (!boost::filesystem::is_regular_file(highscore_file)) {
		Engine::LogError ("Could not load highscore file: %s is not a regular file!", highscore_file.filename().c_str());
	}

	Engine::LogDebug ("Loading highscore file '%s'", highscore_filename.c_str());

	std::ifstream score_stream (highscore_filename.c_str());
	ParseHighscoreStream (score_stream);

	score_stream.close();
}

void Model::SaveLocalHighscoreList () {
	Engine::LogDebug ("Saving local highscore file");
	std::ofstream highscore_file (Engine::GetUserDirFullPath("/highscore.dat").c_str());

	std::list<HighscoreEntry>::iterator iter = mHighscoreList.begin();

	while (iter != mHighscoreList.end()) {
		highscore_file << iter->name << "\t" << iter->points << std::endl;
		iter++;
	}

	highscore_file.close();
}

bool highscore_cmp (Model::HighscoreEntry a, Model::HighscoreEntry b) {
	return a.points > b.points;
}

/** \brief Addes an entry to the highscore list and takes care that the list stays valid
 *
 * \TODO Re-think usage of mNewestHighscoreEntryIndex variable in this function
 */
unsigned int Model::AddLocalHighscoreEntry(const std::string &name, const unsigned int points) {
	HighscoreEntry entry;
	entry.name = name;
	entry.points = points;

	Engine::LogDebug ("Adding entry %s points = %d", name.c_str(), points);

	unsigned int counter = 0;
	std::list<HighscoreEntry>::iterator iter = mHighscoreList.begin();
	while (iter != mHighscoreList.end()) {
		if (name == iter->name && points == iter->points)
			break;

		if (points >= iter->points) {
			mHighscoreList.insert (iter, entry);
			mNewestHighscoreEntryIndex = counter;
			break;
		}
		iter++;
		counter ++;
	}

	if (mHighscoreList.size() < 10) {
		mHighscoreList.push_back(entry);
		mNewestHighscoreEntryIndex = mHighscoreList.size();
		return mHighscoreList.size();
	}

	while (mHighscoreList.size() > 10) {
		mHighscoreList.pop_back();
	}

	if (counter < 10)
		return counter;

	mNewestHighscoreEntryIndex = 99999;

	return 99999;
}

bool Model::PullGlobalHighscore(std::stringstream &highscore_stream) {
	highscore_stream.str("");

	IPaddress server_address;

	if (SDLNet_ResolveHost (&server_address, Model::HighscoreServerName.GetStringValue().c_str(), 80) == -1) {
		Engine::LogWarning ("Could not retrieve global highscore list: host name resolution for server %s failed!",
				Model::HighscoreServerName.GetStringValue().c_str());
		return false;
	}

	char ip_address_char[4];
	memcpy (&ip_address_char[0], &server_address.host, sizeof(char) * 4);

	int ip_address[4];
	ip_address[0] = static_cast<int>(ip_address_char[0]);
	ip_address[1] = static_cast<int>(ip_address_char[1]);
	ip_address[2] = static_cast<int>(ip_address_char[2]);
	ip_address[3] = static_cast<int>(ip_address_char[3]);

	if (ip_address[0] < 0)
		ip_address[0] += 256;
	if (ip_address[1] < 0)
		ip_address[1] += 256;
	if (ip_address[2] < 0)
		ip_address[2] += 256;
	if (ip_address[3] < 0)
		ip_address[3] += 256;

	Engine::LogDebug ("Pulling global highscore from server %s (%d.%d.%d.%d)",
			Model::HighscoreServerName.GetStringValue().c_str(),
			ip_address[0],
			ip_address[1],
			ip_address[2],
			ip_address[3]
		);

	TCPsocket server_socket = SDLNet_TCP_Open (&server_address);
	if (!server_socket) {
		Engine::LogError ("SDL_net tcp open: %s", SDLNet_GetError());
		return false;
	}

	std::string http_query_string;
	http_query_string = std::string ("GET ") + Model::HighscoreServerPath.GetStringValue() + std::string(" HTTP/1.1\r\nHost: asteroids.fysx.org\r\nConnection: close\r\n\r\n");

	int bytes_sent;
	bytes_sent = SDLNet_TCP_Send (server_socket, http_query_string.c_str(), http_query_string.size());

	if (bytes_sent != http_query_string.size()) {
		Engine::LogError ("SDL_net tcp send: %s", SDLNet_GetError());
		return false;
	}

	char receive_buffer[255];
	std::string http_result;

	bool receiving = true;
	while (receiving) {
		// reset the buffer
		bzero (&receive_buffer[0], 255);

		// read the data
		int received_bytes = SDLNet_TCP_Recv (server_socket, receive_buffer, 255);

		if (received_bytes <= 0) {
			receiving = false;
		}

		http_result.append (receive_buffer, received_bytes);
	}

	// we have to strip the whitespaces twice to cut out the content
	http_result = strip_whitespaces (http_result);
	http_result = http_result.substr (http_result.find ("\n\r"), http_result.size());
	http_result = strip_whitespaces (http_result);

	Engine::LogDebug ("Received Highscore: %s", http_result.c_str());

	SDLNet_TCP_Close (server_socket);

	highscore_stream.str(http_result);

	return true;
}

bool Model::SubmitGlobalHigscoreEntry (std::string name, const unsigned int points) {
	IPaddress server_address;

	if (SDLNet_ResolveHost (&server_address, Model::HighscoreServerName.GetStringValue().c_str(), 80) == -1) {
		Engine::LogWarning ("Cannot submit highscore entry: host name resolution for server %s failed!",
				Model::HighscoreServerName.GetStringValue().c_str());
		return false;
	}

	char ip_address_char[4];
	memcpy (&ip_address_char[0], &server_address.host, sizeof(char) * 4);

	int ip_address[4];
	ip_address[0] = static_cast<int>(ip_address_char[0]);
	ip_address[1] = static_cast<int>(ip_address_char[1]);
	ip_address[2] = static_cast<int>(ip_address_char[2]);
	ip_address[3] = static_cast<int>(ip_address_char[3]);

	if (ip_address[0] < 0)
		ip_address[0] += 256;
	if (ip_address[1] < 0)
		ip_address[1] += 256;
	if (ip_address[2] < 0)
		ip_address[2] += 256;
	if (ip_address[3] < 0)
		ip_address[3] += 256;

	std::string escaped_name = name;

	while (escaped_name.find(" ") != std::string::npos) {
		escaped_name.replace (escaped_name.find(" "), 1, "%20");
	}

	Engine::LogDebug ("Escaped name (%u) = '%s'", escaped_name.size(), escaped_name.c_str());

	Engine::LogDebug ("Submitting highscore player_name='%s' score_value='%d' to server %s (%d.%d.%d.%d)",
			name.c_str(),
			points,
			Model::HighscoreServerName.GetStringValue().c_str(),
			ip_address[0],
			ip_address[1],
			ip_address[2],
			ip_address[3]
		);

	TCPsocket server_socket = SDLNet_TCP_Open (&server_address);
	if (!server_socket) {
		Engine::LogError ("SDL_net tcp open: %s", SDLNet_GetError());
		return false;
	}

	std::stringstream points_stringstream;
	points_stringstream << points;

	std::stringstream hash_input;
	hash_input << name << ":" << points << ":" << sha256_hash ("asteroids rule");

	std::string key = sha256_hash (hash_input.str());

	std::string http_query_string;
	http_query_string = std::string ("GET ")
		+ Model::HighscoreServerPath.GetStringValue()
		+ std::string("&player_name=") + escaped_name
		+ std::string("&score_value=") + points_stringstream.str()
		+ std::string("&key=") + key
    + std::string(" HTTP/1.1\r\nHost: asteroids.fysx.org\r\nConnection: close\r\n\r\n");

	Engine::LogDebug ("http submitting query: %s", http_query_string.c_str());

	int bytes_sent;
	bytes_sent = SDLNet_TCP_Send (server_socket, http_query_string.c_str(), http_query_string.size());

	if (bytes_sent != http_query_string.size()) {
		Engine::LogError ("SDL_net tcp send: %s", SDLNet_GetError());
		return false;
	}

	char receive_buffer[255];
	std::string http_result;

	bool receiving = true;
	while (receiving) {
		// reset the buffer
		bzero (&receive_buffer[0], 255);

		// read the data
		int received_bytes = SDLNet_TCP_Recv (server_socket, receive_buffer, 255);

		if (received_bytes <= 0) {
			receiving = false;
		}

		http_result.append (receive_buffer, received_bytes);
	}

	// we have to strip the whitespaces twice to cut out the content 
	http_result = strip_whitespaces (http_result);
	http_result = http_result.substr (http_result.find ("\n\r"), http_result.size());
	http_result = strip_whitespaces (http_result);

	SDLNet_TCP_Close (server_socket);

	if (http_result == "OK") {
		Engine::LogDebug ("Submission successful: %s", http_result.c_str());
		LoadHighscoreList();	
		return true;
	}	else {
		Engine::LogMessage ("Submission unsuccessful: %s", http_result.c_str());
	}

	return false;
}

void Model::SubmitHighscoreEntry (const std::string &name, const unsigned int points) {
	if (Model::UseServerHighscore.GetBoolValue()) {
		if (SubmitGlobalHigscoreEntry (name, points)) {
			return;
		}

		Engine::LogWarning ("Could not send highscore entry to server!");
	} 

	// if it did not succeed or we do not want to use the global highscore
	// fall back to the local highscore system.
	AddLocalHighscoreEntry (name, points);
	SaveLocalHighscoreList();
}

int Model::DoLoadLevel (const char* filename) {
	// verify the hash of the map
	std::string map_name (filename);
	map_name = boost::filesystem::path(map_name).filename();
	std::string map_hash = sha256_hash_file (filename);
	if (map_hash != mLevelHashes[map_name]) {
		Engine::LogMessage ("Map verification for file %s failed!", map_name.c_str());
		mGameModified = true;
		mCurrentLevelModified = true;
	}
	Engine::LogDebug ("Verification for level %s OK: loading level.", map_name.c_str());

	std::fstream level_file (filename, std::ios::in);
	
	if (!level_file) {
		Engine::LogError ("Unable to open file %s for reading!", filename);
		exit (-1);
	}

	ClearEntities();
	mAsteroids.clear();

	std::string entity_type_str;

	int entity_count = 0;
	SetLevelTitle ("");
	SetLevelAuthor ("");
	mLevelTimeSeconds = 0.f;
	mLevelParTimeSeconds = -1.f;
	mLevelPoints = 0;
	mLevelTimeBonusPoints = 0;

	while (level_file >> entity_type_str) {
		if (entity_type_str[0] == '#') {
			getline (level_file, entity_type_str);
			Engine::LogDebug ("Read Comment: %s", entity_type_str.c_str());
			continue;
		} else if (entity_type_str == "Title") {
			std::string level_title;

			getline (level_file, level_title);
			level_title = strip_whitespaces (level_title);
			SetLevelTitle (level_title);
			continue;
		} else if (entity_type_str == "Author") {
			std::string level_author;

			getline (level_file, level_author);
			level_author = strip_whitespaces (level_author);
			SetLevelAuthor (level_author);
			continue;
		} else if (entity_type_str == "ParTime") {
			std::string partime_string;

			getline (level_file, partime_string);
			partime_string = strip_whitespaces (partime_string);
			std::stringstream partime_stream (partime_string);

			float partime = -1.;
			partime_stream >> partime;

			if (partime > 0.)
				mLevelParTimeSeconds = partime;
				
			continue;
		}


		GameEntityType entity_type = GameEntityTypeUnknown;

		if (entity_type_str == "GameEntityTypeShip")
			entity_type = GameEntityTypeShip;
		else if (entity_type_str == "GameEntityTypeAsteroid")
			entity_type = GameEntityTypeAsteroid;
		else {
			Engine::LogError ("Unknown Entity type: %s", entity_type_str.c_str());
			exit (-1);
		}

		Engine::EntityBase* entity = CreateEntity (entity_type);

		bool is_player;
		level_file >> is_player;

		if (is_player) {
			mPlayerEntityId = entity->mId;
			Engine::LogDebug ("Entity with id '%d' is player", entity->mId);
		}

		level_file >> entity->mPhysicState->mPosition[0];
		level_file >> entity->mPhysicState->mPosition[1];
		level_file >> entity->mPhysicState->mPosition[2];

		level_file >> entity->mPhysicState->mOrientation[0];
		level_file >> entity->mPhysicState->mOrientation[1];
		level_file >> entity->mPhysicState->mOrientation[2];

		level_file >> entity->mPhysicState->mVelocity[0];
		level_file >> entity->mPhysicState->mVelocity[1];
		level_file >> entity->mPhysicState->mVelocity[2];

		level_file >> entity->mPhysicState->mAngleVelocity;

		entity_count ++;
	}

	if (mLevelParTimeSeconds == -1.f) {
		Engine::LogWarning ("Level %s has no par time set. Setting to 120s.", filename);
		mLevelParTimeSeconds = 120.f;
	}

	level_file.close();

	Engine::LogDebug ("%d Entities loaded!", mEntities.size());

	return 0;
}

int Model::DoSaveLevel (const char* filename) {
	Engine::LogMessage ("Saving level to %s", filename);
	std::fstream level_file (filename, std::ios::out);
	
	if (!level_file) {
		Engine::LogError ("Unable to open file %s for writing!", filename);
		exit (-1);
	}

	level_file << "# Format" << std::endl;
	level_file << "# <Type> <player?> <xpos> <ypos> <zpos> <zrot> <yrot> <xrot> <xvel> <yvel> <zvel> <rotvel>" << std::endl;

	if (GetLevelTitle() != "") {
		level_file << "Title " << mLevelTitle << std::endl;
	}
	if (GetLevelAuthor() != "") {
		level_file << "Author " << mLevelAuthor << std::endl;
	}

	std::map<unsigned int, Engine::EntityBase*>::iterator iter = mEntities.begin();
	unsigned int player_id = GetPlayerEntityId();

	for (iter = mEntities.begin(); iter != mEntities.end(); iter++) {
		Engine::EntityBase* game_entity = iter->second;

		level_file << GetStringGameEntityType((GameEntityType)game_entity->mType) << "\t"
			// this stores the player id
			<< (game_entity->mId == player_id) << "\t"
			<< game_entity->mPhysicState->mPosition[0] << "\t"
			<< game_entity->mPhysicState->mPosition[1] << "\t"
			<< game_entity->mPhysicState->mPosition[2] << "\t"
			<< game_entity->mPhysicState->mOrientation[0] << "\t"
			<< game_entity->mPhysicState->mOrientation[1] << "\t"
			<< game_entity->mPhysicState->mOrientation[2] << "\t"
			<< game_entity->mPhysicState->mVelocity[0] << "\t"
			<< game_entity->mPhysicState->mVelocity[1] << "\t"
			<< game_entity->mPhysicState->mVelocity[2] << "\t"
			<< game_entity->mPhysicState->mAngleVelocity << "\t"
			<< std::endl;
	}
	
	level_file.close();
	return 0;
}

void Model::LoadLevelFromIndex (unsigned int level_index) {
	if (level_index < mLevelList.size()) {
		mCurrentLevelIndex = level_index;
		DoLoadLevel(mLevelList[mCurrentLevelIndex].c_str());
	}
}

void Model::ReloadLevel () {
	Engine::LogDebug ("Reloading level %d", mCurrentLevelIndex + 1);

	if (mCurrentLevelIndex < 0 || mCurrentLevelIndex >= mLevelList.size())
		Engine::LogError ("Invalid level index: %u", mCurrentLevelIndex);

	DoLoadLevel(mLevelList[mCurrentLevelIndex].c_str());
}

void Model::ProceedToNextLevel () {
	Engine::LogDebug ("Proceeding to next level %d", mCurrentLevelIndex + 1);
	mCurrentLevelIndex++;

	if (mCurrentLevelIndex == mLevelList.size()) {
		Engine::EventBasePtr gameover_event (new Engine::EventBase());
		gameover_event->mEventType = EventGameOver;
		QueueEvent (gameover_event);
	}	else {
		DoLoadLevel(mLevelList[mCurrentLevelIndex].c_str());
	}
}

void Model::SetGameState (const unsigned int &state) {
	mLastGameState = mGameState;
	mGameState = state;
}

bool Model::OnGameOver() {
	Engine::LogDebug ("Points = %d lowest = %d", mPoints, mHighscoreList.back().points );

	if (mPoints > mHighscoreList.back().points) {
		Engine::LogMessage ("New Highscore!");
	}

	return false;
};

void Model::OnNewGame() {
	ClearEntities();

	mNewestHighscoreEntryIndex = std::numeric_limits<unsigned int>::max();
	mPlayerLives = 3;
	mCurrentLevelIndex = 0;
	mPoints = 0;
	mLevelPoints = 0;

	DoLoadLevel (mLevelList[mCurrentLevelIndex].c_str());
}

void Model::OnShipExplode () {
	Engine::PlaySound(Engine::GetResourceFullPath("/data/sounds/ship_destroyed.wav"));

	if (GetPlayerEntityId() == Engine::NullEntityId) {
		return;
	}

	mPlayerLives --;

	if (mPlayerLives == 0) {
		Engine::EventBasePtr gameover_event (new Engine::EventBase());
		gameover_event->mEventType = EventGameOver;
		QueueEvent (gameover_event);
	} else {
		Engine::EventBasePtr playerdied_event (new Engine::EventBase());
		playerdied_event->mEventType = EventPlayerDied;
		QueueEvent (playerdied_event);
	}
}

void Model::OnLevelComplete() {
	// calculate the bonus points
	float level_time = roundf(mLevelTimeSeconds);
	float level_par_time = roundf(mLevelParTimeSeconds);
	int bonus_points = 0;

	if (level_time <= level_par_time) {
		// secret time bonus formula
		float a = 2,
					b = -4.2,
					c = 2.2;
		float t_n = mLevelTimeSeconds / mLevelParTimeSeconds; 
		float bonus_points_f = roundf ( (a * t_n * t_n + b * t_n + c) * static_cast<float>( mLevelPoints ));
		if (bonus_points_f < 0.)
			bonus_points_f = 0.;

		// round to the closest decimal number
		bonus_points_f = roundf (bonus_points_f / 10.) * 10. + 100.;
		mLevelTimeBonusPoints = static_cast<unsigned int> (bonus_points_f);

		Engine::LogDebug("Bonus Points: t_n = %f bonus_points_f = %f bonus_points = %u", t_n, bonus_points_f, mLevelTimeBonusPoints);

	} else {
		mLevelTimeBonusPoints = 0;
	}

	int extra_lives = floor ((mPoints + mLevelPoints) / 400000.f) - floor (mPoints / 400000.f);
	if (extra_lives > 0) {
		mPlayerLives += extra_lives;
	}

	mLevelPoints += mLevelTimeBonusPoints;
	mPoints += mLevelPoints;

	if (mGameModified)
		mPoints = 0;
}

void Model::OnCreateEntity (const int type, const unsigned int id) {
	GameEntityType entity_type = (GameEntityType) type;

	if (entity_type == GameEntityTypeAsteroid) {
		mAsteroids.push_back (id);
	}
}

void Model::OnKillEntity (const Engine::EntityBase *entity) {
	GameEntityType entity_type = (GameEntityType) entity->mType;

	if (entity_type == GameEntityTypeAsteroid) {
		Engine::PlaySound(Engine::GetResourceFullPath("/data/sounds/rock_destroyed.wav"));

		unsigned int i;
		const AsteroidEntity *asteroid = static_cast<const AsteroidEntity*>(entity);

		if (GetPlayerEntityId() != Engine::NullEntityId) {
			mLevelPoints += (150 + asteroid->mSubAsteroidsCount * 75) * 6;
		}

		for (i = 0; i < mAsteroids.size(); i++) {
			if (mAsteroids.at(i) == entity->mId) {
				std::vector<unsigned int>::iterator entity_iter = mAsteroids.begin() + i;
				mAsteroids.erase (entity_iter);
				break;
			}
		}

		if (mAsteroids.size() == 0) {
			Engine::EventBasePtr level_complete_event (new Engine::EventBase());
			level_complete_event->mEventType = EventLevelComplete;

			QueueEvent (level_complete_event);
		}
	}
}

float Model::GetWorldWidth () {
	return static_cast<Physics*>(mPhysics)->GetWorldWidth();
}

float Model::GetWorldHeight () {
	return static_cast<Physics*>(mPhysics)->GetWorldHeight();
}

}


