#include "Macro.h"

std::vector<CBase> Macro::bases;
std::vector<int> Macro::enemyIDs;
std::vector<Location> Macro::locations;
std::vector<CPlayer> Macro::players;
std::vector<QueueEntry> Macro::queue;
std::vector<CUnit> Macro::scouts;
std::vector<Squad> Macro::squads;
int Macro::lastAttack = 0;
int Macro::selfID;
TilePosition Macro::lastAttackLocation;
TilePosition Macro::lastBuildLocation;
bool Macro::macroHatch;
bool Macro::newBase;
bool Macro::stopProduction;
bool Macro::workerScout = false;
namespace { auto & map = BWEM::Map::Instance(); }

void Macro::addBase(TilePosition tp)
{
	bases.push_back(CBase(tp));
}

void Macro::addLocation(TilePosition tp, bool StartLocation)
{
	locations.push_back(Location(tp, StartLocation));
}

void Macro::addPlayer(BWAPI::Player player)
{
	stopProduction = false; // needed to initialize it somewhere
	players.push_back(CPlayer(player));
}

void Macro::addSquad()
{
	squads.push_back(Squad());
}

// does top-level analysis of what to build
//@TODO clean this up
void Macro::analyzeQueue()
{
	std::string entry = "";
	bool status = false;
	double score = 0;
	int index = -1;
	int count = 0;

	for (auto e : queue)
	{
		if ((!e.unit.isBuilding() && e.unit.supplyProvided() == 0) || !e.inProgress)
		{
			if (e.isTech()) e.score = Analysis::analyze(e.tech);
			else if (e.isUnit()) e.score = Analysis::analyze(e.unit);
			else if (e.isUpgrade()) e.score = Analysis::analyze(e.upgrade);
			if (e.score > score)
			{
				score = e.score;
				index = count;
			}
		}
		count++;
	}

	if (index > -1)
	{
		if (queue.at(index).isUnit())
		{
			if (!queue.at(index).unit.isBuilding())
			{
				if (!stopProduction || Broodwar->self()->minerals() > 300 + queue.at(index).unit.mineralPrice())
				{
					queue.at(index).inProgress = getTrainingBase().unit->train(queue.at(index).unit);
					if (queue.at(index).inProgress) queue.erase(queue.begin() + index);
				}
			}
			else if (queue.at(index).unit.isBuilding())
			{
				if (queue.at(index).unit == UnitTypes::Zerg_Lair || queue.at(index).unit == UnitTypes::Zerg_Hive)
				{
					queue.at(index).inProgress = players.at(selfID).bases.front().base->morph(queue.at(index).unit);
				}
				else
				{
					queue.at(index).inProgress = true;
					// find a drone and send them off
					int workerIndex = Util::buildWorkerIndex();

					if (workerIndex > -1)
					{
						players.at(selfID).bases.at(players.at(selfID).units.at(workerIndex).miningBase).subtractWorker("minerals");
						if (newBase && queue.at(index).unit == UnitTypes::Zerg_Hatchery && !stopProduction)
						{
							players.at(selfID).units.at(workerIndex).action = "expand";
							CBase base = Util::getNextExpand(selfID);
							players.at(selfID).units.at(workerIndex).target = base.tilePosition;
							players.at(selfID).units.at(workerIndex).unit->move(Position(players.at(selfID).units.at(workerIndex).target));
							stopProduction = true;
						}
						else
						{
							players.at(selfID).units.at(workerIndex).action = "build";
							TilePosition target = BuildingPlacement::getPosition(players.at(selfID).units.at(workerIndex).targetUnit);
							if (queue.at(index).unit == UnitTypes::Zerg_Sunken_Colony
								|| queue.at(index).unit == UnitTypes::Zerg_Spore_Colony)
							{
								players.at(selfID).units.at(workerIndex).targetUnit = UnitTypes::Zerg_Creep_Colony;
								target = BuildingPlacement::getPosition(queue.at(index).unit);
							}
							else
							{
								players.at(selfID).units.at(workerIndex).targetUnit = queue.at(index).unit;
								target = BuildingPlacement::getPosition(players.at(selfID).units.at(workerIndex).targetUnit);
							}
							players.at(selfID).units.at(workerIndex).target = target;
						}
					}
				}
			}
		}
		else if (queue.at(index).isUpgrade())
		{
			if (!stopProduction || Broodwar->self()->minerals() > 300 + queue.at(index).upgrade.mineralPrice())
			{
				if (Util::findUpgradeBuilding(queue.at(index).upgrade) > -1)
				{
					players.at(selfID).units.at(Util::findUpgradeBuilding(queue.at(index).upgrade)).unit->upgrade(queue.at(index).upgrade);
					queue.erase(queue.begin() + index);
				}
			}
		}
	}
}

// assigns a base to a player
void Macro::assignBase(int playerID, CBase base)
{
	players.at(playerID).bases.push_back(base);
}

// find the base that's closest to the unit and also unsaturated
int Macro::findMiningBase(BWAPI::Unit unit, std::string type)
{
	bool check = false;
	int distance = 10000;
	int index = -1;
	index = Util::getClosestOwnedBase(unit);

	if (players.at(selfID).bases.at(index).isSaturated(type)) index = -1;

	if (index == -1)
	{
		for (size_t i = 0; i < players.at(selfID).bases.size(); i++)
		{
			if (players.at(selfID).bases.at(i).complete)
			{
				int d = unit->getDistance(players.at(selfID).bases.at(i).base);

				if (d < distance &&	!players.at(selfID).bases.at(i).isSaturated(type))
				{
					distance = d;
					index = i;
				}
			}
		}
	}
	return index;
}

// stops a drone from mining minerals or gas
void Macro::freeUnit(int index)
{
	if (players.at(selfID).units.at(index).isMining())
	{
		int base = players.at(selfID).units.at(index).miningBase;
		if (base > -1)
			players.at(selfID).bases.at(base).subtractWorker(players.at(selfID).units.at(index).action);
	}
	if (players.at(selfID).units.at(index).action == "expand")
	{
		stopProduction = 0;
	}

	players.at(selfID).units.at(index).action = "";
	players.at(selfID).units.at(index).miningBase = -1;
	players.at(selfID).units.at(index).target = TilePosition(-1, -1);
	players.at(selfID).units.at(index).unit->move(players.at(selfID).units.at(index).unit->getPosition());
	//players.at(selfID).units.at(index).unit->stop();
}

// find a base with larva to morph a unit at
CUnit Macro::getTrainingBase()
{
	int id = 0;
	int count = 0;
	for (auto u : players.at(selfID).units)
	{
		if (u.unit->getType() == UnitTypes::Zerg_Hatchery) id = count;
		if (u.unit->getLarva().size() > 0)
		{
			return u;
		}
		count++;
	}

	return players.at(selfID).units.at(id);
}

void Macro::morphCreepColonies()
{
	for (size_t i = 0; i < players.at(selfID).units.size(); i++)
	{
		if (players.at(selfID).units.at(i).unit->getType() == UnitTypes::Zerg_Creep_Colony)
		{
			int base = Util::getClosestOwnedBase(players.at(selfID).units.at(i).unit);
			if(players.at(selfID).bases.at(base).sunkenCount < players.at(selfID).bases.at(base).desiredSunkenCount())
			{
				players.at(selfID).units.at(i).unit->morph(UnitTypes::Zerg_Sunken_Colony);
			}
			else players.at(selfID).units.at(i).unit->morph(UnitTypes::Zerg_Spore_Colony);
		}
	}
}

void Macro::onUnitComplete(Unit unit)
{
	if (int id = unit->getPlayer()->getID() == selfID 
		&& unit->getType() != UnitTypes::Zerg_Sunken_Colony
		&& unit->getType() != UnitTypes::Zerg_Spore_Colony)
	{
		int index = -1;
		int count = 0;
		for (auto e : queue)
		{
			if (e.unit == unit->getType() && e.inProgress)
			{
				index = count;
				break;
			}
			count++;
		}
		if(index > -1) queue.erase(queue.begin()+index);
		if (unit->getType() == UnitTypes::Zerg_Hatchery)
		{
			bool chk = false;

			for (auto b : bases)
			{
				if (Util::isSameTilePosition(unit->getTilePosition(), b.tilePosition))
				{
					chk = true;
					break;
				}
			}

			if (Util::isBase(unit) && players.at(selfID).bases.size() > 0) players.at(selfID).bases.back().complete = true;
		}
		int unitIndex = Util::getUnitIndex(unit);
		if (unitIndex > -1)
		{
			players.at(selfID).units.at(unitIndex).unitType = unit->getType();
		}
	}
}

// assigns a unit or base to the respective player
void Macro::onUnitCreate(Unit unit)
{
	if (unit->getPlayer() != Broodwar->neutral())
	{
		int id = unit->getPlayer()->getID();
		if (unit->getType().isResourceDepot())
		{
			for (auto b : bases)
			{
				if (Util::isSameTilePosition(unit->getTilePosition(), b.tilePosition))
				{
					CBase base = CBase(unit);
					base.complete = true;
					assignBase(id, base);
				}
			}
		}
		players.at(id).units.push_back(CUnit(unit));
	}
}

//@TODO clean this up
//@TODO bot doesn't seem to re-add gas workers after they die
void Macro::onUnitDestroy(Unit unit)
{
	if (unit->getPlayer() != Broodwar->neutral())
	{
		int id = unit->getPlayer()->getID();
		int index = Util::getUnitIndex(unit);
		players.at(id).gasLost += unit->getType().gasPrice();
		players.at(id).mineralsLost += unit->getType().mineralPrice();

		if (index > -1)
		{
			if (unit->getPlayer() == Broodwar->self())
			{
				if (players.at(id).units.at(index).action == "build"
					|| players.at(id).units.at(index).action == "expand")
				{
					//TODO newbase isn't being cleared properly when a drone dies
					// on the way to build a hatchery
					if (players.at(id).units.at(index).action == "expand")
					{
						newBase = false;
						stopProduction = false;
					}
					int queueIndex = -1;
					for (size_t i = 0; i < queue.size(); i++)
					{
						if (queue.at(i).unit == players.at(id).units.at(index).targetUnit)
						{
							queueIndex = i;
							break;
						}
					}
					if (queueIndex > -1) queue.erase(queue.begin() + queueIndex);
				}

				else if (players.at(id).units.at(index).action == "minerals")
				{
					int baseIndex = players.at(id).units.at(index).miningBase;
					if (baseIndex > -1)
					{
						players.at(id).bases.at(baseIndex).mineralWorkers--;
					}
				}

				else if (players.at(id).units.at(index).action == "gas")
				{
					int baseIndex = players.at(id).units.at(index).miningBase;
					if (baseIndex > -1)
					{
						players.at(id).bases.at(baseIndex).gasWorkers--;
					}
				}

				int squadindex = -1;
				int unitindex = -1;
				for (size_t j = 0; j < squads.size(); j++)
				{
					for (size_t k = 0; k < squads.at(j).units.size(); k++)
					{
						if (squads.at(j).units.at(k).id == unit->getID())
						{
							squadindex = j;
							unitindex = k;
							break;
						}
					}
				}
				if (unitindex > -1)
				{
					squads.at(squadindex).units.erase(squads.at(squadindex).units.begin() + unitindex);
				}
				int scoutindex = -1;
				for (size_t j = 0; j < scouts.size(); j++)
				{
					if (scouts.at(j).unit->getID() == unit->getID())
					{
						scoutindex = j;
						break;
					}
				}
				if (scoutindex > -1) scouts.erase(scouts.begin() + scoutindex);
				
			}
			players.at(id).units.erase(players.at(id).units.begin() + index);
		}

		if (unit->getType().isResourceDepot())
		{
			index = Util::getBaseIndex(unit);

			if (index > -1) players.at(id).bases.erase(players.at(id).bases.begin() + index);
		}
	}
}

void Macro::onUnitDiscover(Unit unit)
{
	if (unit->getPlayer() != Broodwar->neutral() && unit->getPlayer() != Broodwar->self() && !Util::unitCounted(unit))
	{
		int id = unit->getPlayer()->getID();
		players.at(id).gasSpent += unit->getType().gasPrice();
		players.at(id).mineralsSpent += unit->getType().mineralPrice();
		if (unit->getType().isResourceDepot())
		{
			for (auto b : bases)
			{
				if (unit->getTilePosition().x == b.tilePosition.x && unit->getTilePosition().y == b.tilePosition.y)
				{
					CBase base = CBase(unit);
					base.complete = unit->isCompleted();
					assignBase(id, base);
				}
			}
		}
		CUnit u = CUnit(unit);
		u.lastSeen = u.unit->getTilePosition();
		players.at(id).units.push_back(u);
	}
}

void Macro::onUnitMorph(Unit unit)
{
	if (unit->getPlayer()->getID() == selfID)
	{
		if (unit->getType().isRefinery())
		{
			players.at(selfID).units.push_back(CUnit(unit));
		}
		int index = Util::getQueueIndex(unit->getType());

		if (index > -1)
		{
			queue.erase(queue.begin() + index);
		}

		if (unit->getType() == UnitTypes::Zerg_Hatchery && newBase
			&& Util::isBasePosition(unit->getTilePosition()))
		{
			players.at(selfID).bases.push_back(CBase(unit));
			newBase = false;
			stopProduction = false;
		}
		else if (unit->getType() == UnitTypes::Zerg_Hatchery && macroHatch)
		{
			macroHatch = false;
		}

		int unitIndex = Util::getUnitIndex(unit);
		if (unitIndex > -1)
		{
			players.at(selfID).units.at(unitIndex).unitType = unit->getType();
		}
	}
}

void Macro::onUnitShow(Unit unit)
{
	if (!unit->getPlayer()->isNeutral() && !unit->getPlayer()->getID() == Broodwar->self()->getID())
	{
		int id = unit->getPlayer()->getID();
		for (size_t i = 0; i < players.at(id).units.size(); i++)
		{
			if (players.at(id).units.at(i).unit->getID() == unit->getID())
			{
				players.at(id).units.at(i).lastSeen = unit->getTilePosition();
				players.at(id).units.at(i).unitType = unit->getType();
			}
		}
	}
}

int Macro::queueCount(BWAPI::UnitType unit)
{
	int count = 0;
	for (auto e : queue)
	{
		if (e.unit == unit) count++;
	}
	return count;
}

bool Macro::queueHas(BWAPI::TechType tech)
{
	for (auto e : queue)
	{
		if (e.tech == tech) return true;
	}
	return false;
}

bool Macro::queueHas(BWAPI::UnitType unit)
{
	for (auto e : queue)
	{
		if (e.unit == unit) return true;
	}
	return false;
}

bool Macro::queueHas(BWAPI::UpgradeType upgrade)
{
	for (auto e : queue)
	{
		if (e.upgrade == upgrade) return true;
	}
	return false;
}

void Macro::setEnemyID(int id)
{
	enemyIDs.push_back(id);
}

void Macro::setLastAttack(int frame)
{
	lastAttack = frame;
}

void Macro::setLastBuildLocation(TilePosition tp)
{
	lastBuildLocation = tp;
}

void Macro::setMacroHatch(bool status)
{
	macroHatch = status;
}

void Macro::setNewBase(bool status)
{
	newBase = status;
}

void Macro::setSelfID(int id)
{
	selfID = id;
}

void Macro::setStopProduction(bool status)
{
	stopProduction = status;
}