#include "Military.h"

void Military::addSquadlessUnits(int squadIndex)
{
	for (auto u : Macro::players.at(Macro::selfID).units)
	{
		if (u.unit->getType().supplyProvided() == 0
			&& !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
			&& !u.isInSquad()
			)
		{
			Macro::squads.at(squadIndex).units.push_back(u);
		}
	}
}

// attacks the enemy with everything except one squad for defense
void Military::attackMaxed()
{
	int squadIndex = getAttackSquadsIndex();

	addSquadlessUnits(squadIndex);

	Position attackLocation = Position(getAttackLocation().first);
	for (size_t i = 0; i < Macro::squads.size(); i++)
	{
		if (Macro::squads.at(i).action == "attack")
		{
			for (auto u : Macro::squads.at(i).units)
			{
				u.unit->attack(attackLocation);
				if (u.unit->getType() == UnitTypes::Zerg_Overlord)
				{
					u.unit->move(attackLocation);
				}
			}
		}
	}
}

// attacks the enemy with one squad
void Military::attackNonMaxed()
{
	// things to consider:
	// how strong the most vulnerable enemy base is
	// how strong the attack squads are
	std::pair<TilePosition, int> baseToAttack = getAttackLocation();
	int damagePadding = 120;
	int squadIndex = getIdleSquad();

	// find which squad to send to attack
	if (squadIndex > -1)
	{
		addSquadlessUnits(squadIndex);
		Macro::squads.at(squadIndex).updateDamage();

		if (Macro::squads.at(squadIndex).groundDamage > baseToAttack.second + damagePadding)
		{
			Macro::squads.at(squadIndex).attack(baseToAttack);
		}
	}
}

// determine whether to attack
void Military::checkAttack()
{
	// things to consider:
	// if supply is maxed
	// if a base is vulnerable (hit and run)
	// if the enemy is at a disadvantage (do some damage and/or go for the win)

	if (Broodwar->self()->supplyUsed() > 380)
		attackMaxed();
	else if (shouldAttackNonMaxed())
		attackNonMaxed();
}

// determine where is being attacked and send units to defend
void Military::checkDefense()
{
	bool attacking = false;
	Position attackLocation;

	// check if enemies are in certain locations and send defending units if they are
	for (size_t i = 0; i < Macro::players.at(Macro::selfID).bases.size(); i++)
	{
		Macro::players.at(Macro::selfID).bases.at(i).getDefense();
		double attackerScore = checkEnemiesAt(Macro::players.at(Macro::selfID).bases.at(i));
		if (attackerScore > 5)
		{
			attacking = true;
			Macro::setLastAttack(Broodwar->getFrameCount());
			sendDefense(
				Macro::players.at(Macro::selfID).bases.at(i).base->getClosestUnit(Filter::IsEnemy)->getTilePosition(), 
				attackerScore, 
				Macro::players.at(Macro::selfID).bases.at(i).sunkenCount * UnitTypes::Zerg_Sunken_Colony.groundWeapon().damageAmount()/2);
			//attackLocation = Macro::players.at(Macro::selfID).bases.at(i).base->getClosestUnit(Filter::IsEnemy)->getPosition();
		}
	}

	for (size_t i = 0; i < Macro::squads.size(); i++)
	{

	}

	/*
	if (attacking)
	{
		for (auto u : Macro::players.at(Macro::selfID).units)
		{
			if (u.unit->getType().supplyProvided() == 0
				&& !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
				&& !Util::isInSquad(u)
				)
			{
				if (Macro::squads.size() == 0)
					Macro::squads.push_back(Squad());
				Macro::squads.back().units.push_back(u);
				u.action = "defend";
				u.unit->attack(attackLocation);
			}
		}
	}*/
}

// returns how much attack power is at the specified CBase
double Military::checkEnemiesAt(CBase b)
{
	double score = 0;
	for (auto u : b.base->getUnitsInRadius(32*20, Filter::IsEnemy))
	{
		if (u->getType().groundWeapon() != WeaponTypes::None)
		{
			score += u->getType().groundWeapon().damageAmount();
		}
	}
	return score;
}

void Military::checkSquads()
{
	int added = 0;
	for (size_t i = 0; i < Macro::squads.size(); i++)
	{
		Macro::squads.at(i).updateCenter();
		Macro::squads.at(i).updateDamage();

		if (Macro::squads.at(i).units.size() > 0)
		{
			int baseIndex = Util::getClosestOwnedBase(Macro::squads.at(i).units.front().unit);
			if (baseIndex > -1)
			{
				if (Broodwar->getFrameCount() > Macro::players.at(Macro::selfID).bases.at(baseIndex).lastAttack + (10 * 24))
				{
					Macro::squads.at(i).action = "";
					for (size_t j = 0; j < Macro::squads.at(i).units.size(); j++)
					{
						Macro::squads.at(i).units.at(j).action = "";
					}
				}
			}
		}

		if (!Macro::squads.at(i).containsType(UnitTypes::Zerg_Overlord))
		{
			for (size_t j = 0; j < Macro::players.at(Macro::selfID).units.size(); j++)
			{
				if (Macro::players.at(Macro::selfID).units.at(j).unit->getType() == UnitTypes::Zerg_Overlord
					&& Macro::players.at(Macro::selfID).units.at(j).action != "scout"
					&& Macro::players.at(Macro::selfID).units.at(j).action != "squad"
					&& added < 2)
				{
					added++;
					Macro::squads.at(i).units.push_back(Macro::players.at(Macro::selfID).units.at(j));
					Macro::players.at(Macro::selfID).units.at(j).action = "squad";
				}
			}
		}
	}
}

// for defending in rushes or other low-eco situations
bool Military::droneDefenseNeeded()
{
	return (Util::countUnits(Macro::selfID, BWAPI::UnitTypes::Zerg_Zergling) < 10
		&& Util::countUnits(Macro::selfID, BWAPI::UnitTypes::Zerg_Hydralisk) < 10);
}

std::pair<TilePosition, int> Military::getAttackLocation()
{
	// start here - pick the most vulnerable base (getbasedefense) and attack
	if (Macro::players.at(Macro::enemyIDs.front()).bases.size() > 0)
	{
		std::pair<int, int> pair = getMostVulnerableBase(Macro::enemyIDs.front());
		int baseIndex = pair.first;
		std::pair<TilePosition, int> returnPair;
		try
		{
			returnPair.first = Macro::players.at(Macro::enemyIDs.front()).bases.at(baseIndex).tilePosition;
			returnPair.second = pair.second;
		}
		catch (const std::exception& e)
		{
			Broodwar << "out of range error: base index =  " << baseIndex << std::endl;
			returnPair.first = TilePosition(50,50);
			returnPair.second = 1000;
		}

		//Broodwar << " first return: " << returnPair.first << std::endl;
		return returnPair;
	}
	else
	{
		for (auto u : Macro::players.at(Macro::enemyIDs.front()).units)
		{
			//TODO sometimes a unit isn't being added properly and this returns a wrong value
			if (u.unit->getType().isBuilding())
			{
				std::pair<TilePosition, int> returnPair;
				returnPair.first = u.lastSeen;
				returnPair.second = u.unit->getType().groundWeapon().damageAmount();
				Broodwar << " second return: " << returnPair.first << std::endl;
				return returnPair;
			}
		}
	}
	std::pair<TilePosition, int> returnPair;
	returnPair.first = TilePosition(Broodwar->mapWidth() / 2, Broodwar->mapHeight() / 2);
	returnPair.second = 0;
	return returnPair;
}

int Military::getAttackSquadsIndex()
{
	int skippedSquads = 0;
	int squadIndex = -1;
	for (size_t i = 0; i < Macro::squads.size(); i++)
	{
		if (Macro::squads.at(i).action == "defend" && skippedSquads == 0)
		{
			skippedSquads++;
		}
		else
		{
			Macro::squads.at(i).action = "attack";
			squadIndex = i;
		}
	}

	if (squadIndex == -1)
	{
		Macro::squads.push_back(Squad());
		squadIndex = Macro::squads.size() - 1;
	}

	return squadIndex;
}

double Military::getBaseDefense(CBase base, std::string type, int playerID)
{
	double score = 0;
	if (type == "ground")
	{
		for (size_t i = 0; i < Macro::players.at(playerID).units.size(); i++)
		{
			if (Util::getDistance(Macro::players.at(playerID).units.at(i).lastSeen, base.tilePosition) < 15)
				if (Macro::players.at(playerID).units.at(i).unitType.groundWeapon() != WeaponTypes::None)
				{
					score += Macro::players.at(playerID).units.at(i).unitType.groundWeapon().damageAmount();
				}
		}
		return score + base.sunkenCount;
	}
	else
	{
		for (auto u : Broodwar->getUnitsInRadius(base.base->getPosition(), 15*32))
		{
			if (u->getType().airWeapon() != WeaponTypes::None
				&& u->getPlayer()->getID() == playerID)
			{
				score += u->getType().airWeapon().damageAmount();
			}
		}
		return score + base.sporeCount;
	}
}

int Military::getClosestSquad(TilePosition tp)
{
	int index = -1;
	double distance = 1000;
	for (size_t i = 0; i < Macro::squads.size(); i++)
	{
		if (Util::getDistance(tp, Macro::squads.at(i).center) < distance
			&& Macro::squads.at(i).action != "defend")
		{
			index = i;
			distance = Util::getDistance(tp, Macro::squads.at(i).center);
		}
	}
	return index;
}

int Military::getIdleSquad()
{
	int squadIndex = -1;
	for (size_t i = 0; i < Macro::squads.size(); i++)
	{
		if (Macro::squads.at(i).action != "defend"
			&& Macro::squads.at(i).action != "attack")
		{
			squadIndex = i;
			break;
		}
	}
	return squadIndex;
}

std::pair<int, int> Military::getMostVulnerableBase(int playerID)
{
	int index = -1;
	double score = 10000;

	for (size_t i = 0; i < Macro::players.at(playerID).bases.size(); i++)
	{
		int baseScore = getBaseDefense(Macro::players.at(playerID).bases.at(i), "ground", playerID);
		if (baseScore < score && 
			(Macro::players.at(playerID).bases.at(i).exposedToCenter || Macro::players.at(playerID).bases.size() == 1))
		{
			score = baseScore;
			index = i;
		}
	}
	if (index == -1 && Macro::players.at(playerID).bases.size() == 1)
	{
		index = 0;
	}
	std::pair<int, int> pair;
	pair.first = index;
	pair.second = score;
	return pair;
}

bool Military::isThreatened(TilePosition tp)
{
	for (auto u : Broodwar->getUnitsInRadius(Position(tp), 20, Filter::IsEnemy))
	{
		if (u->canAttackUnit(UnitTypes::Zerg_Overlord)
			&& inThreatRange(tp, CUnit(u))) return true;
	}
	return false;
}

bool Military::inThreatRange(TilePosition tp, CUnit unit)
{
	return Util::getDistance(tp, unit.unit->getTilePosition()) < unit.unit->getType().seekRange() + 1;
}

// returns how likely on a scale of 0 to 1 the enemy is to attack soon
// things to consider:
// how long it's been since the last time they attacked
// how many units the enemy might have
double Military::likelihoodToAttack()
{
	double score = 1;
	double timeModifier = 0;
	double attackTime = 360.0 * 24;
	if (!Macro::enemyIDs.empty())
	{
		if (Macro::players.at(Macro::enemyIDs.front()).bases.size() == 2) attackTime = 540.0 * 24;
	}

	if(Broodwar->getFrameCount() < 360*24)
		timeModifier = Broodwar->getFrameCount() / attackTime;
	
	else
		timeModifier = (Broodwar->getFrameCount() - Macro::lastAttack) / (540.0 * 24);

	if (Broodwar->getFrameCount() - Macro::lastAttack < 10*24 && Macro::lastAttack > 0) return 1;
	if (timeModifier > 1) timeModifier = 1.0;

	if (Util::countUnits(Macro::enemyIDs.front(), UnitTypes::Terran_Barracks) == 0
		&& Util::countUnits(Macro::enemyIDs.front(), UnitTypes::Protoss_Gateway) == 0
		&& Util::countUnits(Macro::enemyIDs.front(), UnitTypes::Zerg_Spawning_Pool) == 0)
	{
		score = 0;
	}

	return score * timeModifier;
}


// sends a squad to defend
// things to consider:
// how important the place being attacked is
// if a bigger win can be found by attacking instead (base race)
// how powerful the attacking force is
// how powerful the defending force is
void Military::sendDefense(TilePosition tp, int attackerScore, int defenseScore)
{
	int squadIndex = getClosestSquad(tp);
	if (squadIndex == -1)
	{
		squadIndex = 0;
		Macro::squads.push_back(Squad());
	}
	int padding = 80; // give the defense some breathing room by spotting the attackers some damage

	Macro::squads.at(squadIndex).action = "defend";
	if (defenseScore + Macro::squads.at(squadIndex).groundDamage < attackerScore + padding)
	{
		int index = 0;
		for (auto u : Macro::players.at(Macro::selfID).units)
		{
			if (defenseScore + Macro::squads.at(squadIndex).groundDamage < attackerScore + padding)
			{
				if ((Util::isAttackingUnit(u) || droneDefenseNeeded()) && !u.isInSquad())
				{
					Macro::squads.at(squadIndex).addUnit(u);
					u.squadIndex = squadIndex;
					if (u.unit->getType() == UnitTypes::Zerg_Drone)
					{
						Macro::freeUnit(index);
						u.unsetTargetUnit();
					}
					/*Macro::squads.at(squadIndex).units.push_back(u);
					Macro::squads.at(squadIndex).airDamage += u.unitType.airWeapon().damageAmount();
					Macro::squads.at(squadIndex).groundDamage += u.unitType.groundWeapon().damageAmount();*/
				}
			}
			index++;
		}
	}
	for (auto u : Macro::squads.at(squadIndex).units)
	{
		u.unit->attack(Position(tp));
		u.action = "defend";
	}
}

bool Military::shouldAttackNonMaxed()
{
	return ((Macro::lastAttack > 0 && (Broodwar->getFrameCount() - Macro::lastAttack > 24 * 10))
		|| Broodwar->getFrameCount() > 24 * 360);
}