#include "PastPerformanceManager.h"
//#include "Windows.h"
#include <ostream>
#include <fstream>
#include <sstream>

PastPerformanceManager::PastPerformanceManager()
{

	TiXmlDocument doc("scailconfig.xml");
	MAX_ROUND_WINDOW = -1;
	bool loadOkay = doc.LoadFile();
	if(!loadOkay) {
		Broodwar->sendText("**** SERIOUS ERROR: Unable to load config file scailconfig.xml!");
	} else {
	TiXmlElement* pElement = doc.FirstChildElement();
		rdir = pElement->Attribute("readtarget");
		wdir = pElement->Attribute("writetarget");
	}
	lastRoundNumber = returnLastRoundNumber();

}

void PastPerformanceManager::registerNewGameResult( std::string oname, int sused, int winloss, int score, std::pair<int,int> mdim )
{
	if(sused == -1){
		return;
	}
	string curFilename;

	stringstream strm;
	strm << wdir << lastRoundNumber+1 << ".scail";

	curFilename = strm.str();

	////Broodwar->sendText("writing to: %s", curFilename.c_str());

	TiXmlDocument doc( curFilename.c_str() );
	doc.LoadFile();
	TiXmlDeclaration * decl = new TiXmlDeclaration( "1.0", "", "" );
	TiXmlElement * element = new TiXmlElement( "match" );
	element->SetAttribute("oname",oname.c_str());
	element->SetAttribute("strat", sused);
	element->SetAttribute("win", winloss);
	element->SetAttribute("fscore", score);
	element->SetAttribute("mdx", mdim.first);
	element->SetAttribute("mdy", mdim.second);
	element->SetAttribute("mname", Broodwar->mapFileName().c_str());
	element->SetAttribute("mhash", Broodwar->mapHash().c_str());

	doc.LinkEndChild( element );
	doc.SaveFile( curFilename.c_str() );


}

int PastPerformanceManager::getMostSuccessfulStrat( std::string oname )
{
	if(matchesDefaultAI(oname)) {
		if(Broodwar->enemy()->getRace() == BWAPI::Races::Protoss) {
			return HIGHLEVEL_STRAT_PVP_GAMMA;
		}
		if(Broodwar->enemy()->getRace() == BWAPI::Races::Zerg) {
			return HIGHLEVEL_STRAT_PVZ_GAMMA;
		}
		if(Broodwar->enemy()->getRace() == BWAPI::Races::Terran || Broodwar->enemy()->getRace() == BWAPI::Races::Unknown) {
			return HIGHLEVEL_STRAT_PVT_ALPHA;
		}
	}




	int startFromRound = 1;
	int windowStart = 1;
	if(MAX_ROUND_WINDOW != -1) {
		windowStart = lastRoundNumber-MAX_ROUND_WINDOW;
	}

	if(windowStart >= startFromRound) {
		startFromRound = windowStart;
	}


	if(lastRoundNumber == 0) {
		// this is the first round we have played
		// so we should just return a random strategy
		return returnRandomStrategy();
	} 

	int gamesPlayedAgainstOpponent = 0;
	int gamesPlayedAgainstOpponentOnMapType = 0;
	int gamesWon = 0;
	int gamesLost = 0;

	int bestScoreOverall = 0;
	int bestScoreMap = 0;

	// initialise these randomly, just in case all of this fails horribly
	int bestOverallScoringStrat = returnRandomStrategy();
	int bestScoringStratOnMap = returnRandomStrategy();

	std::map<int, int> pMap;
	std::map<int, int> stratPlayedTotal;
	std::map<int, int> stratPlayedOnMap;
	std::map<int, int> mapPmap;
	std::pair<int,int> curMapDims = std::make_pair(Broodwar->mapWidth(), Broodwar->mapHeight());


	////////////////////// BEGIN DATA LOADING

	for(int i = startFromRound; i <= lastRoundNumber; i++) {

		string curFilename;

		stringstream strm;
		strm << rdir << i << ".scail";
		
		curFilename = strm.str();

		////Broodwar->sendText("reading: %s", curFilename.c_str());

		TiXmlDocument doc(curFilename.c_str());
		bool loadOkay = doc.LoadFile();
		if(!loadOkay) {
			//OutputDebugString(TEXT("*** unable to load file\n"));
			continue;
		} else {
			//OutputDebugString(TEXT("--- NEW FILE LOADED\n"));
		}

		TiXmlElement* pElement = doc.FirstChildElement();

		while(pElement) {
			const char* name = pElement->Attribute("oname");
			////Broodwar->sendText("BUH!! %s %s", name, oname.c_str());
				//OutputDebugString(TEXT("found a game\n"));
			if(oname == name) {
				//OutputDebugString(TEXT("against this opponent!\n"));
				int strat = 0;
				int win = 0;
				int width = 0;
				int height = 0;

				int fscore = 0;

				if(pElement->QueryIntAttribute("win", &win) != TIXML_SUCCESS){ continue;} ;
				if(pElement->QueryIntAttribute("fscore", &fscore) != TIXML_SUCCESS){ continue;} 
				if(pElement->QueryIntAttribute("strat", &strat) != TIXML_SUCCESS){ continue;} 
				if(pElement->QueryIntAttribute("mdx", &width) != TIXML_SUCCESS){ continue;} 
				if(pElement->QueryIntAttribute("mdy", &height) != TIXML_SUCCESS){ continue;} 

				std::pair<int,int> md = std::make_pair(width,height);
				bool addToMp = false;
				if(mapDimensionsEquivalent(md, curMapDims)){
					addToMp = true;
					gamesPlayedAgainstOpponentOnMapType++;
					stratPlayedOnMap[strat]++;
				}

				gamesPlayedAgainstOpponent++;
				stratPlayedTotal[strat]++;

				if(win == 1) {
					gamesWon++;
					pMap[strat]++;
					if(addToMp) {
						mapPmap[strat]++;
					}
				} else { 
					gamesLost++;	

				}

				if(fscore > bestScoreOverall) {
					bestScoreOverall = fscore;
					bestOverallScoringStrat = strat;
				}
				if(addToMp) {
					if(fscore > bestScoreMap) {
						bestScoreMap = fscore;
						bestScoringStratOnMap = strat;
					}
				}

			}

			pElement = pElement->NextSiblingElement();
		}

	}
	////////////////////// END DATA LOADING




	////////////////////// BEGIN DATA EVALUATION

	if(gamesPlayedAgainstOpponent < 10) {
		//OutputDebugString(TEXT("returning random strat"));
		return returnRandomStrategy();
	}
	if(gamesWon == 0) {
		if(bestScoreOverall > bestScoreMap) {
			return bestOverallScoringStrat;
		}
		return bestScoringStratOnMap;
	}

	float bestOverallSoFar = 0.0;
	int bestOverallStratSoFar = returnRandomStrategy();
	// calculate general win rate over all strategies
	for(std::map<int,int>::const_iterator i = pMap.begin(); i != pMap.end(); i++) {
		int strat = (*i).first;
		float swins = (*i).second;
		if(stratPlayedTotal[strat] == 0){
			continue;
		}
		float winP = (swins/stratPlayedTotal[strat])*100.0;
		if(winP > bestOverallSoFar) {
			bestOverallSoFar = winP;
			bestOverallStratSoFar = strat;
		}
	}

	float bestOnMapSoFar = 0.0;
	int bestMapStratSoFar = returnRandomStrategy();
	if(gamesPlayedAgainstOpponentOnMapType >= 10) {

		for(std::map<int,int>::const_iterator it = mapPmap.begin(); it != mapPmap.end(); it++) {
			int strat = (*it).first;
			float swins = (*it).second;
			if(stratPlayedOnMap[strat] == 0){
				continue;
			}
			float winP = (swins/stratPlayedOnMap[strat])*100.0;
			if(winP > bestOnMapSoFar) {
				bestOnMapSoFar = winP;
				bestMapStratSoFar = strat;
			}
		}
	}

	// ideal situation
	if(bestMapStratSoFar == bestOverallStratSoFar)  {
		return bestMapStratSoFar;
	}

	// if not, what works well on this map
	if(bestOnMapSoFar > bestOverallSoFar) {
	return bestOnMapSoFar;
	}

	//Broodwar->sendText("employing OVERALL strategy %d with win rate %f", bestOverallStratSoFar, bestOnMapSoFar);

	return bestOverallStratSoFar; 
}

bool PastPerformanceManager::matchesDefaultAI( std::string oname )
{
	if(Broodwar->enemy()->getRace() == BWAPI::Races::Zerg) {
		if(oname == "Jormungand Brood") return true;
		if(oname == "Baelrog Brood") return true;
		if(oname == "Tiamat Brood") return true;
		if(oname == "Grendel Brood") return true;
		if(oname == "Surtur Brood") return true;
		if(oname == "Garm Brood") return true;
		if(oname == "Leviathan Brood") return true;
		if(oname == "Fenris Brood") return true;
	}

	if(Broodwar->enemy()->getRace() == BWAPI::Races::Protoss) {
		if(oname == "Ara Tribe") return true;
		if(oname == "Sargas Tribe") return true;
		if(oname == "Akilae Tribe") return true;
		if(oname == "Furinax Tribe") return true;
		if(oname == "Auriga Tribe") return true;
		if(oname == "Venatir Tribe") return true;
		if(oname == "Shelak Tribe") return true;
		if(oname == "Velari Tribe") return true;

	}

	if(Broodwar->enemy()->getRace() == BWAPI::Races::Terran) {
		if(oname == "Elite Guard") return true;
		if(oname == "Kel-Morian Combine") return true;
		if(oname == "Mar Sara") return true;
		if(oname == "Antiga") return true;
		if(oname == "Delta Squadron") return true;
		if(oname == "Atlas Wing") return true;
		if(oname == "Cronus Wing") return true;
		if(oname == "Epsilon Squadron") return true;
		if(oname == "Alpha Squadron") return true;
		if(oname == "Omega Squadron") return true;
	}

	return false;
}

int PastPerformanceManager::returnRandomStrategy()
{

	if(Broodwar->enemy()->getRace() == BWAPI::Races::Unknown) {
		//OutputDebugString(TEXT("Unknown race. Making the safest choice.\n"));
		return HIGHLEVEL_STRAT_PVT_ALPHA;
	}

	int rnum = rand()%3;
	if(Broodwar->enemy()->getRace() == BWAPI::Races::Terran) {
		switch(rnum) {
		case 0:
			return HIGHLEVEL_STRAT_PVT_ALPHA;
		case 1:
			return HIGHLEVEL_STRAT_PVT_BETA;
		case 2:
			return HIGHLEVEL_STRAT_PVT_GAMMA;
		}
	} 
	if(Broodwar->enemy()->getRace() == BWAPI::Races::Protoss) {
		rnum = rand()%4;
		switch(rnum) {
		case 0:
			return HIGHLEVEL_STRAT_PVP_ALPHA;
		case 1:
			return HIGHLEVEL_STRAT_PVP_BETA;
		case 2:
			return HIGHLEVEL_STRAT_PVP_GAMMA;
		case 3:
			return HIGHLEVEL_STRAT_PVP_DELTA;
		}

	} 
	if(Broodwar->enemy()->getRace() == BWAPI::Races::Zerg) {

		switch(rnum) {
		case 0:
			return HIGHLEVEL_STRAT_PVZ_ALPHA;
		case 1:
			return HIGHLEVEL_STRAT_PVZ_BETA;
		case 2:
			return HIGHLEVEL_STRAT_PVZ_GAMMA;
		}

	} 
	return 666;
}

bool PastPerformanceManager::mapDimensionsEquivalent( std::pair<int,int> a, std::pair<int,int> b )
{
	if(a.first == b.first) {
		if(a.second == b.second) {
			return true;
		}
	}
	if(a.first == b.second) {
		if(a.second == b.first) {
			return true;
		}
	}
	return false;
}

int PastPerformanceManager::returnLastRoundNumber()
{
	std::vector<std::string> rl = readDirectory(rdir, "scail");
	int highestSoFar = 0;
	for(std::vector<std::string>::const_iterator i = rl.begin(); i != rl.end(); i++) {
		std::string cur = *i;
		std::string nc (cur.begin(), cur.end()-4);
		//int curV = atoi(nc.c_str());

		stringstream ss(nc.c_str()); // Could of course also have done ss("1234") directly.
		int curV;
		ss >> curV;
	
		if(curV > highestSoFar) {
			highestSoFar = curV;
		}

	}

	return highestSoFar;
}

std::vector<std::string> PastPerformanceManager::readDirectory(const std::string &directoryLocation, const std::string &extension)
{
	vector<string> result;
	string lcExtension( strToLower(extension) );

	DIR *dir;
	struct dirent *ent;

	if ((dir = opendir(directoryLocation.c_str())) == NULL) {
		throw std::exception("readDirectory() - Unable to open directory.");
	}

	while ((ent = readdir(dir)) != NULL)
	{
		string entry( ent->d_name );
		string lcEntry( strToLower(entry) );

		// Check extension matches (case insensitive)
		size_t pos = lcEntry.rfind(lcExtension);
		if (pos!=string::npos && pos==lcEntry.length()-lcExtension.length()) {
			result.push_back( entry );
		}
	}

	if (closedir(dir) != 0) {
		throw std::exception("readDirectory() - Unable to close directory.");
	}

	return result;
}


///StrToLower converts a std::string to lowercase
//From http://www.richelbilderbeek.nl/CppStrToLower.htm
const std::string PastPerformanceManager::strToLower(std::string s)
{
	std::transform(s.begin(), s.end(), s.begin(),std::ptr_fun<int,int>(std::tolower));
	return s;
}