#include "CBase.h"

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

CBase::CBase(BWAPI::TilePosition tp)
{
	this->area = map.GetArea(tp);
	this->tilePosition = tp;
	this->position = Position(tp.x * 32, tp.y * 32);
	this->initialize();
}

CBase::CBase(BWAPI::Unit unit)
{
	this->base = unit;
	this->tilePosition = unit->getTilePosition();
	this->position = unit->getPosition();
	this->initialize();
	this->area = map.GetArea(this->tilePosition);
}

CBase::CBase()
{
}

void CBase::addWorker(std::string resource)
{
	if (resource == "gas")
	{
		this->gasWorkers++;
	}
	if (resource == "minerals")
	{
		this->mineralWorkers++;
	}
}

int CBase::desiredSporeCount()
{
	int count = 1;
	if (!Macro::enemyIDs.empty())
	{
		if (Util::countUnits(Macro::enemyIDs.front(), UnitTypes::Zerg_Spire) > 0) count++;
		count += ceil(Util::countUnits(Macro::enemyIDs.front(), UnitTypes::Terran_Starport) / 2.0);
		count += ceil(Util::countUnits(Macro::enemyIDs.front(), UnitTypes::Protoss_Stargate) / 2.0);
		count += Util::countUnits(Macro::enemyIDs.front(), UnitTypes::Zerg_Mutalisk) / 5;
	}
	return 1;
}

// things to consider - 
// how many resources are left at this base
// if it's a main (has tech buildings)
// if it's a natural (leads to a main)
int CBase::desiredSunkenCount()
{
	if (this->exposedToCenter && Broodwar->getFrameCount() < 600 * 24) return 5;
	else if (this->exposedToCenter) return 2;
	else return 0;
}

int CBase::extractorsBuilt()
{
	int count = 0;
	for (auto u : Macro::players.at(Macro::selfID).units)
	{
		if ((u.unit->getType().isRefinery()) &&
			u.unit->getDistance(this->position) < 400)
		{
			count++;
		}
	}
	return count;
}

void CBase::getDefense()
{
	this->creepColonyCount = 0;
	this->sunkenCount = 0;
	this->sporeCount = 0;
	for (auto u : Macro::players.at(Macro::selfID).units)
	{
		if (u.unit->getType() == UnitTypes::Zerg_Sunken_Colony)
		{
			if (u.unit->getDistance(this->position) < 400) this->sunkenCount++;
		}
		else if (u.unit->getType() == UnitTypes::Zerg_Spore_Colony)
		{
			if (u.unit->getDistance(this->position) < 400) this->sporeCount++;
		}
		else if (u.unit->getType() == UnitTypes::Zerg_Creep_Colony)
		{
			if (u.unit->getDistance(this->position) < 400) this->creepColonyCount++;
		}
	}

	/*Broodwar->registerEvent(
		[this](Game*) {
			Broodwar->drawTextMap(Position(this->position.x, this->position.y + 20), "creep count = %d", this->creepColonyCount);
		},
		nullptr,
			23);*/
}

void CBase::getResources()
{
	if (this->base->isCompleted())
	{
		this->mineralPatches = 0;
		this->gasPatches = 0;
		this->gasAmount = 0;
		this->minsAmount = 0;
		for (auto unit : BWAPI::Broodwar->getNeutralUnits())
		{
			if ((unit->getType() == BWAPI::UnitTypes::Resource_Vespene_Geyser || unit->getType().isRefinery()) &&
				unit->getPosition().isValid() &&
				unit->getDistance(this->position) < 400)
			{
				this->gasPatches++;
				this->gasAmount += unit->getInitialResources();
			}

			if (unit->getType() == BWAPI::UnitTypes::Resource_Mineral_Field &&
				unit->getPosition().isValid() &&
				unit->getDistance(this->position) < 400)
			{
				this->mineralPatches++;
				this->minsAmount += unit->getInitialResources();
			}
		}
	}
}

void CBase::initialize()
{
	this->complete = false;
	this->creepColonyCount = 0;
	this->exposedToCenter = this->isExposedToCenter();
	this->gasPatches = 0;
	this->gasWorkers = 0;
	this->gasAmount = 0;
	this->lastAttack = 0;
	this->mineralPatches = 0;
	this->mineralWorkers = 0;
	this->minsAmount = 0;
	this->setDefenseLocation();
	this->sporeCount = 0;
	this->startLocation = isStartLocation();
	this->sunkenCount = 0;
	this->initializeResources();
}

// initialize data for this CBase
void CBase::initializeResources()
{
	this->mineralPatches = 0;
	this->gasPatches = 0;
	this->gasAmount = 0;
	this->minsAmount = 0;
	for (const BWEM::Area& a : map.Areas())
	{
		for (const BWEM::Base& b : a.Bases())
		{
			if (b.Location() == this->tilePosition)
			{
				this->gasPatches = b.Geysers().size();
				this->mineralPatches = b.Minerals().size();
				for (auto g : b.Geysers())
				{
					this->gasAmount += g->Amount();
				}
				for (auto m : b.Minerals())
				{
					this->minsAmount += m->Amount();
				}
			}
		}
	}
}

// if a base doesn't have any other bases between it
// and the center of the map, then it's easier to attack
bool CBase::isExposedToCenter()
{
	const BWEM::Area* area = map.GetArea(this->tilePosition);

	std::pair<const BWEM::Area*, bool> target = Util::getTargetArea(area, this->startLocation);
	const BWEM::Area* targetArea = target.first;
	bool foundTarget = target.second;

	if (foundTarget)
	{
		// get a path from the target Area to the player's starting base.
		// if an Area has more than two neighbors, it's not part of a
		// straight-line path and probably exposed to the center.
		// if it contains a base, then the target Area is not
		// exposed to the center
		int count = 0;
		for (auto a : CMap::getPath(area, targetArea))
		{
			if (a->Id() == area->Id()) continue;
			if (a->AccessibleNeighbours().size() > 2) return true;
			if (a->Bases().size() > 0)
			{
				return false;
			}
			count++;
		}
	}
	else
	{
		Broodwar << "Couldn't find target area in CBase::isExposedToCenter" << std::endl;
	}

	return true;
}

// determines if base is owned by someone
bool CBase::isOwned()
{
	for (size_t i = 0; i < Macro::players.size(); i++)
	{
		for (size_t j = 0; j < Macro::players.at(i).bases.size(); j++)
		{
			int x = Macro::players.at(i).bases.at(j).tilePosition.x;
			int y = Macro::players.at(i).bases.at(j).tilePosition.y;
			if (this->tilePosition.x == x && this->tilePosition.y == y) return true;
		}
	}
	return false;
}

bool CBase::isSaturated(std::string resource)
{
	if (resource == "gas")
	{
		return this->gasWorkers >= this->extractorsBuilt() * 3;
	}
	else
	{
		return this->mineralWorkers >= this->mineralPatches * 2;
	}
}

bool CBase::isStartLocation()
{
	for (auto sl : Broodwar->getStartLocations())
	{
		if (TilePosition(sl) == this->tilePosition) return true;
	}
	return false;
}

//@TODO clean this up
// find a spot to put static defense by.
// if this is a main base, build around the hatchery.
// otherwise, build pointed toward another start location
void CBase::setDefenseLocation()
{
	const BWEM::Area* area = map.GetArea(this->tilePosition);
	TilePosition tp = this->tilePosition;

	std::pair<const BWEM::Area*, bool> target = Util::getTargetArea(area, this->startLocation);
	const BWEM::Area* targetArea = target.first;
	bool foundTarget = target.second;

	if (foundTarget)
	{
		std::pair<const BWEM::Area*, bool> neighbor = Util::getNeighborArea(area, targetArea);
		const BWEM::Area* neighborArea = neighbor.first;
		bool foundNeighbor = neighbor.second;
		if (foundNeighbor)
		{
			TilePosition average = CMap::getChokepointCenter(area, neighborArea);

			// this caused an out-of-range exception in scouting.cpp:
			// std::vector<double> direction = BuildingPlacement::getDirection(Macro::players.at(Macro::selfID).bases.at(1).tilePosition);
			std::vector<double> direction = BuildingPlacement::getDirection(this->tilePosition, average);

			tp.x += direction.at(x_vector) * -5;
			tp.y += direction.at(y_vector) * -5;
		}
		else
		{
			Broodwar << "Couldn't find neighbor area in CBase::setDefenseLocation()" << std::endl;
			Broodwar << targetArea->TopLeft() << std::endl;
		}
	}
	else
	{
		//Broodwar << area->TopLeft() << std::endl;
		Broodwar << "Couldn't find target area in CBase::setDefenseLocation()" << std::endl;
	}
	this->defenseLocation = tp;
}

void CBase::subtractWorker(std::string resource)
{
	if (resource == "gas")
	{
		this->gasWorkers--;
	}
	if (resource == "minerals")
	{
		this->mineralWorkers--;
	}
}

// get number of tech buildings at this base
int CBase::techCount()
{
	std::vector<UnitType> techBuildingTypes = {
		UnitTypes::Zerg_Spawning_Pool, UnitTypes::Zerg_Hydralisk_Den,
		UnitTypes::Zerg_Evolution_Chamber, UnitTypes::Zerg_Spire,
		UnitTypes::Zerg_Queens_Nest, UnitTypes::Zerg_Greater_Spire,
		UnitTypes::Zerg_Ultralisk_Cavern, UnitTypes::Zerg_Defiler_Mound
	};
	int nearbyTech = 0;

	for (auto u : Macro::players.at(Macro::selfID).units)
	{
		if ((std::find(techBuildingTypes.begin(), techBuildingTypes.end(), u.unit->getType()) != techBuildingTypes.end())
		&& u.unit->getDistance(this->position) < 32 * 15)
			nearbyTech++;
	}
	return nearbyTech;
}