#include "ExampleAIModule.h"

using namespace BWAPI;
using namespace Filter;

void ExampleAIModule::onStart()
{
	match = 0;
	victories = 0;
	defeats = 0;
	meanReward = 0;
	// Enable some cheat flags
	Broodwar->enableFlag(Flag::UserInput);
	boostGame(BOOST);
	init();
}

void ExampleAIModule::onEnd(bool isWinner){
	agentMgr.step(true, isWinner);
	Broodwar->sendText("Final reward: %.2f", agentMgr.getTotalReward());
	if (isWinner) victories++;
	else defeats++;
	finish(SAVE);
	if (match >= EPISODES) Broodwar->pauseGame();
}

void ExampleAIModule::onFrame(){
	// Return if the game is a replay or is paused
	if (Broodwar->isReplay() || Broodwar->isPaused() || !Broodwar->self())
		return;

	// Draw some info
	//drawInfo();
	// Draw some stats
	//drawStats();
	// Draw some info on the map
	drawOnMap();

	// Prevent spamming by only running our onFrame once every number of latency frames.
	// Latency frames are the number of frames before commands are processed.
	if (Broodwar->getFrameCount() % Broodwar->getLatencyFrames() != 0)
		return;

	agentMgr.step(false, false);
}

void ExampleAIModule::init(){
	loadMatchStats();
	//loadRewardStats();
	match++;
	agentMgr.init(loadQTable());
}

void ExampleAIModule::finish(bool save){
	if (save){
		saveQTable(agentMgr.getQTable());		
		saveMatchStats();
		saveRewardStats();
	}
}


void ExampleAIModule::drawInfo(){
	Broodwar->drawTextScreen(10, 120, "Reward: %.2f", agentMgr.getTotalReward());
	Broodwar->drawTextScreen(10, 140, "Match: %d", match);
	Broodwar->drawTextScreen(10, 160, "Latency frames: %d", Broodwar->getLatencyFrames());
	Broodwar->drawTextScreen(10, 180, "FPS: %d", Broodwar->getFPS());
	Broodwar->drawTextScreen(10, 200, "Average FPS: %.2f", Broodwar->getAverageFPS());
}

void ExampleAIModule::drawStats(){
	Broodwar->drawTextScreen(5, 0, "I have %d units:", Broodwar->self()->getUnits().size());
	std::map<UnitType, int> MyUnitTypeCounts;
	// Iterate through all the units that we own
	for (auto &u : Broodwar->self()->getUnits())
	{
		// if the unit type is not mapped yet it ands a pair with key = unitType and value = 0.
		if (MyUnitTypeCounts.find(u->getType()) == MyUnitTypeCounts.end())
		{
			MyUnitTypeCounts.insert(std::make_pair(u->getType(), 0));
		}
		MyUnitTypeCounts.find(u->getType())->second++; // increments the value for a given key.
	}

	int line = 1;
	for (auto &i : MyUnitTypeCounts){
		Broodwar->drawTextScreen(5, 16 * line, "- %d %ss", i.second, i.first.getName().c_str());
		line++;
	}


	/* Display visible enemy units */
	Broodwar->drawTextScreen(200, 0, "%d enemy units detected:", Broodwar->enemy()->getUnits().size());
	std::map<UnitType, int> enemyUnitTypeCounts;
	for (auto &u : Broodwar->enemy()->getUnits()){
		// if the unit type is not mapped yet it adds a pair with key = unitType and value = 0.
		if (enemyUnitTypeCounts.find(u->getType()) == enemyUnitTypeCounts.end())
		{
			enemyUnitTypeCounts.insert(std::make_pair(u->getType(), 0));
		}
		enemyUnitTypeCounts.find(u->getType())->second++; // increments the value for a given key.
	}

	line = 1;
	for (auto &i : enemyUnitTypeCounts){
		Broodwar->drawTextScreen(205, 16 * line, "- %d %ss", i.second, i.first.getName().c_str());
		line++;
	}
}

void ExampleAIModule::drawOnMap(){
	for (auto& qAgent : agentMgr.getQAgents()){
		//Broodwar->drawCircleMap((*qAgent).getUnit()->getPosition(), (*qAgent).getUnit()->getType().groundWeapon().maxRange(), BWAPI::Colors::Grey);

		if ((*qAgent).getUnit()->getOrderTarget() != NULL){
			Broodwar->drawLineMap((*qAgent).getUnit()->getPosition(), (*qAgent).getUnit()->getOrderTarget()->getPosition(), Colors::Red);
			Broodwar->drawCircleMap((*qAgent).getUnit()->getOrderTarget()->getPosition(), 3, Colors::Red, true);
		}
		Broodwar->drawLineMap((*qAgent).getUnit()->getPosition(), (*qAgent).getUnit()->getTargetPosition(), Colors::Green);
		Broodwar->drawCircleMap((*qAgent).getUnit()->getTargetPosition(), 3, Colors::Green, true);

		//displayStateOnMap((*qAgent));
	}
}

void ExampleAIModule::displayStateOnMap(QAgent qAgent){
	State s = qAgent.getCurrentState();
	int a = qAgent.getCurrentAction();
	Broodwar->drawTextMap(qAgent.getUnit()->getPosition(), "Enemies: %d\nAction: %d\ncool: %d\ndistance: %d\nattacked? %d", s.getEnemies(), a,s.getCoolDown(), s.getDistance(), s.getAttacked());
}


//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

QTable ExampleAIModule::loadQTable(){
	QTable qt;
	std::ifstream ifs(".\\bwapi-data\\AI\\QLearningBotData\\QTable.txt");
	if (ifs.is_open()){
		boost::archive::text_iarchive ia(ifs);
		ia >> qt;
		ifs.close();
	}
	//else Broodwar->sendText("ERROR: file 'QTable.txt' not found...");
	return qt;
}

void ExampleAIModule::saveQTable(QTable qt){
	// create and open a character archive for output
	std::ofstream ofs(".\\bwapi-data\\AI\\QLearningBotData\\QTable.txt");
	boost::archive::text_oarchive oa(ofs);
	// write class instance to archive
	oa & qt;
	ofs.close();
}

std::vector<double> ExampleAIModule::loadWeights(){
	std::vector<double> weights(FEATURES);
	std::string line;
	int i = 0;
	std::ifstream dataFile(".\\bwapi-data\\AI\\QLearningBotData\\weights.bin"); // std::ios::in is implicit and is combined.
	if (dataFile.is_open()){
		while (getline(dataFile, line)){
			weights.at(i) = atoi(line.c_str());
			i++;
		}
		dataFile.close();
	}
	return weights;
}

void ExampleAIModule::saveWeights(std::vector<double> weights){
	std::ofstream dataFile(".\\bwapi-data\\AI\\QLearningBotData\\weights.bin"); // std::ios::out is implicit and it's combined.
	if (dataFile.is_open()){
		for (std::vector<double>::iterator it = weights.begin(); it != weights.end(); ++it) { // already 0.
			dataFile << *it << "\n";
		}
		dataFile.close();
	}
}

void ExampleAIModule::saveLogWeights(std::vector<double> weights){
	std::ofstream dataFile(".\\bwapi-data\\AI\\QLearningBotData\\weights.log", std::ios::app); // std::ios::out is implicit and it's combined.
	if (dataFile.is_open()){
		for (std::vector<double>::iterator it = weights.begin(); it != weights.end(); ++it) { // already 0.
			dataFile << *it << "\n";
		}
		dataFile << "\n";
		dataFile.close();
	}
}

void ExampleAIModule::loadMatchStats(){
	std::string line;
	int i = 0;
	std::ifstream dataFile(".\\bwapi-data\\AI\\QLearningBotData\\match.bin"); // std::ios::in is implicit and is combined.
	if (dataFile.is_open()){
		while (getline(dataFile, line)){
			switch (i){
			case 0:
				match = atoi(line.c_str());
				break;
			case 1:
				victories = atoi(line.c_str());
				break;
			case 2:
				defeats = atoi(line.c_str());
				break;
			}
			i++;
		}
		dataFile.close();
	}
}

void ExampleAIModule::loadRewardStats(){
	std::string line;
	std::ifstream dataFile(".\\bwapi-data\\AI\\QLearningBotData\\reward.bin"); // std::ios::in is implicit and is combined.
	if (dataFile.is_open()){
		getline(dataFile, line);
		meanReward = atoi(line.c_str());
		dataFile.close();
	}
}

void ExampleAIModule::saveMatchStats(){
	std::ofstream dataFile(".\\bwapi-data\\AI\\QLearningBotData\\match.bin"); // std::ios::out is implicit and it's combined.
	if (dataFile.is_open()){
		dataFile << match << "\n" << victories << "\n" << defeats;
		dataFile.close();
	}
}

void ExampleAIModule::saveRewardStats(){
	std::ofstream dataFile(".\\bwapi-data\\AI\\QLearningBotData\\reward.bin", std::ios::app); // std::ios::out is implicit and it's combined.
	if (dataFile.is_open()){
		dataFile << agentMgr.getTotalReward() << "\n";
		dataFile.close();
	}
}


//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

/* Increases the game speed to the maximum and disables the GUI, HINT: don't forget to turn off the sound in bwapi.ini */
void ExampleAIModule::boostGame(bool boost){
	if (boost){
		// Disable the Graphical User Interface (GUI)
		BWAPI::Broodwar->setGUI(false);
		// Disable the delay between frames
		BWAPI::Broodwar->setLocalSpeed(0);
	}
}

// For AI vs AI
void ExampleAIModule::restartMatch()
{
	/////////// Launch an AutoHotkey that will click on "OK" in 10 sec
	char procName[200];
	sprintf_s(procName, "C:\\Juegos\\StarCraft\\bwapi-data\\AI\\autoRestart.exe");

	// Initialize StartupInfo structure
	STARTUPINFO    StartupInfo;
	memset(&StartupInfo, 0, sizeof(StartupInfo));
	StartupInfo.cb = sizeof(StartupInfo);

	// This will contain the information about the newly created process
	PROCESS_INFORMATION ProcessInformation;

	BOOL results = CreateProcess(0,
		procName, // Process and arguments
		0, // Process Attributes
		0, // Thread Attributes
		FALSE, // Inherit Handles
		0, // CreationFlags,
		0, // Environment
		0, // Current Directory
		&StartupInfo, // StartupInfo
		&ProcessInformation // Process Information
		);
	// Cleanup
	CloseHandle(ProcessInformation.hProcess);
	CloseHandle(ProcessInformation.hThread);
}



//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

void ExampleAIModule::onSendText(std::string text){}
void ExampleAIModule::onReceiveText(BWAPI::Player player, std::string text){}
void ExampleAIModule::onPlayerLeft(BWAPI::Player player){}
void ExampleAIModule::onNukeDetect(BWAPI::Position target){}
void ExampleAIModule::onUnitDiscover(BWAPI::Unit unit){}
void ExampleAIModule::onUnitEvade(BWAPI::Unit unit){}
void ExampleAIModule::onUnitShow(BWAPI::Unit unit){}
void ExampleAIModule::onUnitHide(BWAPI::Unit unit){}
void ExampleAIModule::onUnitCreate(BWAPI::Unit unit){}
void ExampleAIModule::onUnitDestroy(BWAPI::Unit unit){
	/*if (unit->getPlayer() == Broodwar->enemy()){
		Broodwar->sendText("enemy destroyed");
	}*/
	if (unit->getPlayer() == Broodwar->self()){
		Broodwar->sendText("Unit lost");
		agentMgr.terminate(unit);
	}
}
void ExampleAIModule::onUnitMorph(BWAPI::Unit unit){}
void ExampleAIModule::onUnitRenegade(BWAPI::Unit unit){}
void ExampleAIModule::onSaveGame(std::string gameName){}
void ExampleAIModule::onUnitComplete(BWAPI::Unit unit){}



//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

// DEBUGGING TO A FILE
//std::ofstream logFile(".\\bwapi-data\\AI\\QLearningBotData\\debug.log", std::ios::app); // std::ios::out is implicit and it's combined.
//if (logFile.is_open()) logFile << "Loading the qtable...";
//if (logFile.is_open()) logFile.close();

// GET THE CLOSEST ENEMY
//BWAPI::Unit closestFoe = unit->getClosestUnit(BWAPI::Filter::IsEnemy, unit->getType().sightRange());