#include "KoraBot.h"
#include "BWEM/src/bwem.h"
#include <iostream>

using namespace BWAPI;
using namespace Filter;
using namespace BWEM;
using namespace BWEM::BWAPI_ext;
using namespace BWEM::utils;

namespace { auto& theMap = BWEM::Map::Instance(); }

KoraBot::KoraBot()
{
}

void KoraBot::onStart()
{
	Broodwar->setLatCom(false);
	// Hello World!
	//Broodwar->sendText("Hello world!");

	//self = custom::Player(Broodwar->self()->getID());
	// Print the map name.
	// BWAPI returns std::string when retrieving a string, don't forget to add .c_str() when printing!

	// Broodwar << "The map is " << Broodwar->mapName() << "!" << std::endl;

	// Enable the UserInput flag, which allows us to control the bot and type messages.
	Broodwar->enableFlag(Flag::UserInput);

	// Uncomment the following line and the bot will know about everything through the fog of war (cheat).
	//Broodwar->enableFlag(Flag::CompleteMapInformation);

	// Set the command optimization level so that common commands can be grouped
	// and reduce the bot's APM (Actions Per Minute).
	Broodwar->setCommandOptimizationLevel(2);

	// Check if this is a replay
	if (Broodwar->isReplay())
	{
		// Announce the players in the replay
		Broodwar << "The following players are in this replay:" << std::endl;

		// Iterate all the players in the game using a std:: iterator
		Playerset players = Broodwar->getPlayers();
		for (auto p : players)
		{
			// Only print the player if they are not an observer
			if (!p->isObserver())
				Broodwar << p->getName() << ", playing as " << p->getRace() << std::endl;
		}

	}
	else // if this is not a replay
	{
		// Retrieve you and your enemy's races. enemy() will just return the first enemy.
		// If you wish to deal with multiple enemies then you must use enemies().
		/*if (Broodwar->enemy()) // First make sure there is an enemy
		Broodwar << "The matchup is " << Broodwar->self()->getRace() << " vs " << Broodwar->enemy()->getRace() << std::endl;*/

		// Retrieve you and your enemy's races. enemy() will just return the first enemy.
		// If you wish to deal with multiple enemies then you must use enemies().
		//if (Broodwar->enemy()) // First make sure there is an enemy
		//	Broodwar << "The matchup is " << Broodwar->self()->getRace() << " vs " << Broodwar->enemy()->getRace() << std::endl;
	
		for (auto p : Broodwar->getPlayers())
		{
			if (!p->isNeutral())
			{

				Macro::addPlayer(p);
				if (p->getID() == Broodwar->self()->getID())
				{
					Macro::setSelfID(p->getID());
				}
				else if (p->isEnemy(Broodwar->self())) Macro::setEnemyID(p->getID());
			}
		}
	
		try
		{
			theMap.Initialize();
			theMap.EnableAutomaticPathAnalysis();
			bool startingLocationsOK = theMap.FindBasesForStartingLocations();
			assert(startingLocationsOK);
			
			BWEM::utils::MapPrinter::Initialize(&theMap);
			BWEM::utils::printMap(theMap);      // will print the map into the file <StarCraftFolder>/bwapi-data/map.bmp
			BWEM::utils::pathExample(theMap);   // add to the printed map a path between two starting locations

			int count = 0;
			for (const Area& area : theMap.Areas())
			{
				for (const Base& base : area.Bases())
				{
					Macro::addBase(base.Location());
					Macro::addLocation(base.Location(), base.Starting());
				}
			}
			Macro::addSquad();
		}
		catch (const std::exception& e)
		{
			Broodwar << "EXCEPTION: " << e.what() << std::endl;
		}
	
		Broodwar << "KoraBot 1.0" << std::endl;
	}
	
}

void KoraBot::onEnd(bool isWinner)
{
	// Called when the game ends
	if (isWinner)
	{
		// Log your win here!
	}
}

void KoraBot::onFrame()
{
	Broodwar->drawTextScreen(60, 0, "FPS: %d", Broodwar->getFPS());
	Broodwar->drawTextScreen(60, 20, "Average FPS: %f", Broodwar->getAverageFPS());
	
	// Called once every game frame
	try
	{
		// these work but i don't need them right now
		//BWEM::utils::gridMapExample(theMap);
		//BWEM::utils::drawMap(theMap);
		
		if (Broodwar->getFrameCount() > 5)
		{
			int count = 0;

			//for (auto s : Macro::scouts)
			//{
			//	Broodwar->drawTextScreen(200, 10 + (count * 20), "%d", s.unit->getID());
			//	count++;
			//}
			Broodwar->drawTextScreen(200, 10, "likelihood: %f", Military::likelihoodToAttack());
			Broodwar->drawTextScreen(200, 30, "stop production: %d", Macro::stopProduction);
			// Util::drawQueue();

			//for (auto unit : BWAPI::Broodwar->getNeutralUnits())
			//{
				//Broodwar->drawTextMap(unit->getPosition(), unit->getType().c_str());
			//}
			//for (auto u : Macro::players.at(Macro::selfID).units)
			//{
			//	Broodwar->drawTextMap(u.unit->getPosition(), "%d", u.unit->getID());
			//}
			//for (auto u : Broodwar->self()->getUnits())
			//{
			//	Position p = Position(u->getPosition().x - 10, u->getPosition().y - 10);
			//	Broodwar->drawTextMap(p, "%d", u->getID());
			//}
			//for (auto b : Macro::players.at(Macro::selfID).bases)
			//{
			//	Broodwar->drawTextMap(b.position, "%d", b.mineralPatches);
			//}
		}
	}
	catch (const std::exception& e)
	{
		Broodwar << "EXCEPTION: " << e.what() << std::endl;
	}
	
	/// Return if the game is a replay or is paused
	if (Broodwar->isReplay() || Broodwar->isPaused() || !Broodwar->self())
		return;

	// 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;

	// in the top right spawn of jade, exposed to center is false for the natural unless it's run later
	if (Broodwar->getFrameCount() == 240 * 24)
	{
		for (size_t i = 0; i < Macro::players.at(Macro::selfID).bases.size(); i++)
		{
			Macro::players.at(Macro::selfID).bases.at(i).exposedToCenter = Macro::players.at(Macro::selfID).bases.at(i).isExposedToCenter();
		}
	}

	// functions to be run about every eight seconds (191 frames, 192 frames is 8 seconds)
	if (Broodwar->getFrameCount() % 191 == 0)
	{
		CheckItem::checkBases();
		CheckItem::checkEvoChamber();
		CheckItem::checkGas();
		CheckItem::checkGroundArmor();
		CheckItem::checkHive();
		CheckItem::checkHydraDen();
		CheckItem::checkHydraRange();
		CheckItem::checkHydraSpeed();
		CheckItem::checkLair();
		CheckItem::checkLingSpeed();
		CheckItem::checkMissile();
		CheckItem::checkOverlordSpeed();
		CheckItem::checkMacroHatch();
		CheckItem::checkQueensNest();
		CheckItem::checkSpawningPool();
		CheckItem::checkSpire();
		CheckItem::checkSpore();
		CheckItem::checkSunken();
		CheckItem::checkUltraCavern();
		Macro::morphCreepColonies();
		Military::checkAttack();
		Military::checkSquads();
		Scouting::setScouts();
	}

	// functions to be run about every two seconds
	if (Broodwar->getFrameCount() % 47 == 0)
	{
		Scouting::scout();
	}

	// functions to be run about once a second
	if (Broodwar->getFrameCount() % 23 == 0)
	{
		Military::checkDefense();
		CheckItem::checkNewBase();
	}

	// functions to be run every thirteenth frame (about twice a second)
	if (Broodwar->getFrameCount() % 13 == 0)
	{
		if (Broodwar->getFrameCount() > 10)
		{
			CheckItem::checkMutalisk();
			CheckItem::checkHydralisk();
			CheckItem::checkSupply();
			//auto start = std::chrono::high_resolution_clock::now();
			CheckItem::checkWorkers();
			//std::chrono::duration<double, std::milli> elapsed = std::chrono::high_resolution_clock::now() - start;
			CheckItem::checkUltralisk();
			CheckItem::checkZergling();

			// start here - use this to check other function times
			/*Broodwar->registerEvent(
				[elapsed](Game*) {
					Broodwar->drawTextScreen(Position(400, 10), "%f", elapsed.count());
				},
				nullptr,
					12);*/
		}
	}

	// functions to be run every fifth frame (4.8 times a second) - unit production / macro analysis
	if (Broodwar->getFrameCount() % 5 == 0)
	{
		if (Broodwar->getFrameCount() > 6)
		{
			Macro::analyzeQueue();
			//Macro::drawActions();
		}
	}

	
	if (Broodwar->getFrameCount() > 5)
	{
		//Macro::drawPlayers();
	}
	// functions to be run every tick (24 times a second) - unit micro
}

void KoraBot::onSendText(std::string text)
{
	BWEM::utils::MapDrawer::ProcessCommand(text);

	// Send the text to the game if it is not being processed.
	Broodwar->sendText("%s", text.c_str());

	// Make sure to use %s and pass the text as a parameter,
	// otherwise you may run into problems when you use the %(percent) character!
}

void KoraBot::onReceiveText(BWAPI::Player player, std::string text)
{
	// Parse the received text
	//Broodwar << player->getName() << " said \"" << text << "\"" << std::endl;
}

void KoraBot::onPlayerLeft(BWAPI::Player player)
{
	// Interact verbally with the other players in the game by
	// announcing that the other player has left.
	Broodwar->sendText("GG %s!", player->getName().c_str());
}

void KoraBot::onNukeDetect(BWAPI::Position target)
{

	// Check if the target is a valid position
	if (target)
	{
		// if so, print the location of the nuclear strike target
		Broodwar << "Nuclear Launch Detected at " << target << std::endl;
	}
	else
	{
		// Otherwise, ask other players where the nuke is!
		Broodwar->sendText("Where's the nuke?");
	}

	// You can also retrieve all the nuclear missile targets using Broodwar->getNukeDots()!
}

void KoraBot::onUnitDiscover(BWAPI::Unit unit)
{
	Macro::onUnitDiscover(unit);
}

void KoraBot::onUnitEvade(BWAPI::Unit unit)
{
}

void KoraBot::onUnitShow(BWAPI::Unit unit)
{
	Macro::onUnitShow(unit);
}

void KoraBot::onUnitHide(BWAPI::Unit unit)
{
}

void KoraBot::onUnitCreate(BWAPI::Unit unit)
{

	if (Broodwar->isReplay())
	{
		// if we are in a replay, then we will print out the build order of the structures
		if (unit->getType().isBuilding() && !unit->getPlayer()->isNeutral())
		{
			int seconds = Broodwar->getFrameCount() / 24;
			int minutes = seconds / 60;
			seconds %= 60;
			Broodwar->sendText("%.2d:%.2d: %s creates a %s", minutes, seconds, unit->getPlayer()->getName().c_str(), unit->getType().c_str());
		}
	}
	else
	{
		Macro::onUnitCreate(unit);
	}

}

void KoraBot::onUnitDestroy(BWAPI::Unit unit)
{
	try
	{
		if (!Broodwar->isReplay())
		{
			Macro::onUnitDestroy(unit);
		}
		else
		{
			if (unit->getType().isMineralField()) 
				theMap.OnMineralDestroyed(unit);
			else if (unit->getType().isSpecialBuilding())
				theMap.OnStaticBuildingDestroyed(unit);
		}
	}
	catch (const std::exception& e)
	{
		Broodwar << "EXCEPTION: " << e.what() << std::endl;
	}
}

void KoraBot::onUnitMorph(BWAPI::Unit unit)
{
	if (Broodwar->isReplay())
	{
		// if we are in a replay, then we will print out the build order of the structures
		if (unit->getType().isBuilding() && !unit->getPlayer()->isNeutral())
		{
			int seconds = Broodwar->getFrameCount() / 24;
			int minutes = seconds / 60;
			seconds %= 60;
			Broodwar->sendText("%.2d:%.2d: %s morphs a %s", minutes, seconds, unit->getPlayer()->getName().c_str(), unit->getType().c_str());
		}
	}
	else
	{
		Macro::onUnitMorph(unit);
	}
}

void KoraBot::onUnitRenegade(BWAPI::Unit unit)
{
}

void KoraBot::onSaveGame(std::string gameName)
{
	Broodwar << "The game was saved to \"" << gameName << "\"" << std::endl;
}

void KoraBot::onUnitComplete(BWAPI::Unit unit)
{
	if (Broodwar->getFrameCount() > 0 && !Broodwar->isReplay())
	{
		if (unit->getPlayer() == Broodwar->self())
		{
			Macro::onUnitComplete(unit);
		}
	}
}