#include "Util.h"

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

// gets the index of a drone that's currently mining minerals
int Util::buildWorkerIndex()
{
	for (size_t i = 0; i < Macro::players.at(Macro::selfID).units.size(); i++)
	{
		if (Macro::players.at(Macro::selfID).units.at(i).unit->getType() == UnitTypes::Zerg_Drone
			&& Macro::players.at(Macro::selfID).units.at(i).action == "minerals"
			&& Macro::players.at(Macro::selfID).units.at(i).miningBase > -1)
		{
			return i;
		}
	}
	return -1;
}

// checks if there's at least one complete building of the type specified
bool Util::completedBuilding(BWAPI::UnitType unitType)
{
	for (auto u : Macro::players.at(Macro::selfID).units)
	{
		if (u.unit->getType() == unitType && u.unit->isCompleted()) return true;
		if (unitType == UnitTypes::Zerg_Spire
			&& u.unit->getType() == UnitTypes::Zerg_Greater_Spire && u.unit->isCompleted()) return true;
		if (unitType == UnitTypes::Zerg_Hatchery
			&& u.unit->getType() == UnitTypes::Zerg_Lair && u.unit->isCompleted()) return true;
		if (unitType == UnitTypes::Zerg_Hatchery
			&& u.unit->getType() == UnitTypes::Zerg_Hive && u.unit->isCompleted()) return true;
		if (unitType == UnitTypes::Zerg_Lair
			&& u.unit->getType() == UnitTypes::Zerg_Hive && u.unit->isCompleted()) return true;
	}
	return false;
}

int Util::countUnits(int playerID, BWAPI::UnitType unitType)
{
	int count = 0;
	for (auto u : Macro::players.at(playerID).units)
	{
		if (u.unitType == unitType) count++;
		// if the unit is changing into the target UnitType,
		// count it
		else if (u.targetUnit == unitType) count++;
		// lairs and hives count as hatcheries,
		// hives count as lairs
		if (unitType == UnitTypes::Zerg_Hatchery)
		{
			if (u.unitType == UnitTypes::Zerg_Lair) count++;
			if (u.unitType == UnitTypes::Zerg_Hive) count++;
		}
		else if (unitType == UnitTypes::Zerg_Lair)
			if (u.unitType == UnitTypes::Zerg_Hive) count++;
	}
	return count;
}

// draws the queue at the top center of the screen
void Util::displayQueue()
{
	int count = 0;
	for (auto e : Macro::queue)
	{
		if (e.unit != UnitTypes::Unknown)
			Broodwar->drawTextScreen(200, 50 + (count * 20), "%s: %d", e.unit.c_str(), e.inProgress);
		else
			Broodwar->drawTextScreen(200, 50 + (count * 20), "%s: %d", e.upgrade.c_str(), e.inProgress);
		count++;
	}
}

// number of units of a particular type being produced
int Util::eggCount(BWAPI::UnitType unitType)
{
	int count = 0;
	for (auto u : Macro::players.at(Macro::selfID).units)
	{
		
		if (u.unit->getBuildType() == unitType &&
			u.unit->isMorphing()) count++;
	}

	return count;
}

// return the index of a building that can research a particular upgrade
int Util::findUpgradeBuilding(UpgradeType upgradeType)
{
	for (size_t i = 0; i < Macro::players.at(Macro::selfID).units.size(); i++)
	{
		for (auto u : Macro::players.at(Macro::selfID).units.at(i).unit->getType().upgradesWhat())
		{
			if (upgradeType == u) return i;
		}
		if (Macro::players.at(Macro::selfID).units.at(i).unit->getType() == UnitTypes::Zerg_Hive)
		{
			for (auto u : UnitTypes::Zerg_Lair.upgradesWhat())
			{
				if (upgradeType == u) return i;
			}
			for (auto u : UnitTypes::Zerg_Hatchery.upgradesWhat())
			{
				if (upgradeType == u) return i;
			}
		}
		if (Macro::players.at(Macro::selfID).units.at(i).unit->getType() == UnitTypes::Zerg_Lair)
		{
			for (auto u : UnitTypes::Zerg_Hatchery.upgradesWhat())
			{
				if (upgradeType == u) return i;
			}
		}
	}
	return -1;
}

int Util::getBaseIndex(Unit u)
{
	for (size_t i = 0; i < Macro::players.at(u->getPlayer()->getID()).bases.size(); i++)
	{
		if (u->getID() == Macro::players.at(u->getPlayer()->getID()).bases.at(i).base->getID())
		{
			return i;
		}
	}
	return -1;
}

//@TODO move this to CUnit
int Util::getClosestOwnedBase(Unit unit)
{
	unit->getUnitsInRadius(256*32, BWAPI::Filter::IsResourceDepot);
	double distance = 1000;
	int index = -1;
	for (size_t i = 0; i < Macro::players.at(Macro::selfID).bases.size(); i++)
	{
		double d = getDistance(unit->getTilePosition(), Macro::players.at(Macro::selfID).bases.at(i).tilePosition);
		if (d < distance)
		{
			distance = d;
			index = i;
		}
	}
	return index;
}

// get air distance between two TilePositions
double Util::getDistance(TilePosition tp1, TilePosition tp2)
{
	double deltaX = tp1.x - tp2.x;
	double deltaY = tp1.y - tp2.y;
	return sqrt(pow(deltaX, 2.0) + pow(deltaY, 2.0));
}

// get the index of a Location
int Util::getLocation(TilePosition tp)
{
	for (size_t i = 0; i < Macro::locations.size(); i++)
	{
		if (Macro::locations.at(i).tilePosition == tp)
			return i;
	}
	return -1;
}

// on a path from a1 to a2, get the Area adjacent to a1
std::pair<const BWEM::Area*, bool> Util::getNeighborArea(const BWEM::Area* a1, const BWEM::Area* a2)
{
	const BWEM::Area* area;
	int count = 0;
	
	std::vector< const BWEM::Area*> path = CMap::getPath(a1, a2);
	if(path.size() > 1)
		return std::pair<const BWEM::Area*, bool>(path.at(1), true);

	return std::pair<const BWEM::Area*, bool>(area, false);
}

// things to consider:
// how close the base is by ground
// how close the base is to the enemy
CBase Util::getNextExpand(int playerID)
{
	double distance = 1000;
	int index = -1;
	int count = 0;
	for (auto b : Macro::bases)
	{
		if (!b.isOwned())
		{
			CBase home = Macro::players.at(playerID).bases.front();
			double dist = getWalkDistance(home.tilePosition, b.tilePosition);
			if (dist < distance
				&& !(home.tilePosition.x == b.tilePosition.x && home.tilePosition.y == b.tilePosition.y)
				&& map.GetArea(home.tilePosition)->AccessibleFrom(map.GetArea(b.tilePosition)))
			{
				distance = dist;
				index = count;
			}
		}
		count++;
	}
	return Macro::bases.at(index);
}

int Util::getQueueIndex(UnitType ut)
{
	for (size_t i = 0; i < Macro::queue.size(); i++)
	{
		if (Macro::queue.at(i).unit == ut)
		{
			return i;
		}
	}

	return -1;
}

int Util::getStaticDCount(int playerID, std::string type = "all")
{
	if (type == "air")
	{
		return (countUnits(playerID, UnitTypes::Terran_Missile_Turret)
			+ countUnits(playerID, UnitTypes::Terran_Bunker)
			+ countUnits(playerID, UnitTypes::Protoss_Photon_Cannon)
			+ countUnits(playerID, UnitTypes::Zerg_Spore_Colony));
	}
	else if (type == "ground")
	{
		return (countUnits(playerID, UnitTypes::Terran_Bunker)
			+ countUnits(playerID, UnitTypes::Protoss_Photon_Cannon)
			+ countUnits(playerID, UnitTypes::Zerg_Sunken_Colony));
	}
	return (countUnits(playerID, UnitTypes::Terran_Bunker)
		+ countUnits(playerID, UnitTypes::Terran_Missile_Turret)
		+ countUnits(playerID, UnitTypes::Protoss_Photon_Cannon)
		+ countUnits(playerID, UnitTypes::Zerg_Sunken_Colony)
		+ countUnits(playerID, UnitTypes::Zerg_Spore_Colony));
}

// return another starting location besides the one provided
// for the purpose of pathing / defense placement
// 
//@TODO once the enemy's base is known, just return that
std::pair<const BWEM::Area*, bool> Util::getTargetArea(const BWEM::Area* startingArea, bool startLocation)
{
	const BWEM::Area* area;

	for (const TilePosition tp : map.StartingLocations())
	{
		const BWEM::Area* nearestArea = map.GetNearestArea(tp);
		if (!isSameArea(nearestArea, startingArea));
		{
			bool chk = true;
			if (!startLocation
				&& Macro::players.at(Macro::selfID).bases.size() > 0
				&& Macro::players.at(Macro::selfID).bases.front().tilePosition == tp)
			{
				chk = false;
			}
			if (chk)
			{
				return std::pair<const BWEM::Area*, bool>(nearestArea, true);
			}
		}
	}

	return std::pair<const BWEM::Area*, bool>(area, false);
}

int Util::getUnitIndex(Unit unit)
{
	for (size_t i = 0; i < Macro::players.at(unit->getPlayer()->getID()).units.size(); i++)
	{
		if (unit->getID() == Macro::players.at(unit->getPlayer()->getID()).units.at(i).unit->getID())
		{
			return i;
		}
	}
	return -1;
}

double Util::getWalkDistance(TilePosition tp1, TilePosition tp2)
{
	double distance = 0;
	const BWEM::Area* area = map.GetArea(tp1);
	const BWEM::Area* targetArea = map.GetArea(tp2);
	TilePosition currentPosition = tp1;

	int count = 0;
	for (auto a : CMap::getPath(area, targetArea))
	{
		if (count > 0)
		{
			TilePosition nextPosition = CMap::getAreaAverage(a);
			distance += getDistance(currentPosition, nextPosition);
			currentPosition = nextPosition;
		}
		count++;
	}
	return distance;
}

bool Util::isAttackingUnit(CUnit u)
{
	return (!u.unit->getType().isBuilding()
		&& u.unit->getType() != UnitTypes::Zerg_Larva
		&& u.unit->getType() != UnitTypes::Zerg_Egg
		&& u.unit->getType() != UnitTypes::Zerg_Drone
		&& u.unit->getType() != UnitTypes::Zerg_Overlord);
}

// run on hatcheries to see if they're a macro hatch or a mining base
bool Util::isBase(Unit unit)
{
	for (auto b : Macro::bases)
	{
		if (isSameTilePosition(unit->getTilePosition(), b.tilePosition))
		{
			return true;
		}
	}

	return false;
}

// determines if a tileposition is a base position
bool Util::isBasePosition(TilePosition tp)
{
	for (auto b : Macro::bases)
	{
		if (tp == b.tilePosition)
			return true;
	}
	return false;
}

bool Util::isSameArea(const BWEM::Area* area1, const BWEM::Area* area2)
{
	return (area1->TopLeft().x == area2->TopLeft().x
		&& area1->TopLeft().y == area2->TopLeft().y);
}

bool Util::isSameTilePosition(TilePosition tp1, TilePosition tp2)
{
	return (tp1.x == tp2.x && tp1.y == tp2.y);
}

// returns id of player who owns a base at tileposition tp
int Util::ownedBy(TilePosition tp)
{
	for (size_t i = 0; i < Macro::players.size(); i++)
	{
		for (size_t j = 0; j < Macro::players.at(i).bases.size(); j++)
		{
			if (Macro::players.at(i).bases.at(j).tilePosition == tp) return i;
		}
	}

	return -1;
}

bool Util::unitCounted(Unit unit)
{
	for (auto u : Macro::players.at(unit->getPlayer()->getID()).units)
	{
		if (unit->getID() == u.unit->getID())
		{
			return true;
		}
	}
	return false;
}

// checks number of workers who have been told to build a certain building
int Util::workerBuildingTargetCount(UnitType unit)
{
	int i = 0;
	for (auto u : Macro::players.at(Macro::selfID).units)
	{
		if (u.unit->getType() == UnitTypes::Zerg_Drone
			&& u.targetUnit == unit)
			i++;
	}
	return i;
}