//////////////////////////////////////////////////////////////////////////
//
// This file is part of Iron's source files.
// Iron is free software, licensed under the MIT/X11 License. 
// A copy of the license is provided with the library in the LICENSE file.
// Copyright (c) 2016, Igor Dimitrijevic
//
//////////////////////////////////////////////////////////////////////////


#include "me.h"
#include "production.h"
#include "army.h"
#include "../Iron.h"
#include "../behavior/mining.h"
#include "../behavior/constructing.h"
#include "../behavior/defaultBehavior.h"
#include "../territory/stronghold.h"
#include "../strategy/strategy.h"

namespace { auto & bw = Broodwar; }

namespace iron
{


class Me & me() { return ai()->Me(); }

//////////////////////////////////////////////////////////////////////////////////////////////
//                                                                                          //
//                                  class Me
//                                                                                          //
//////////////////////////////////////////////////////////////////////////////////////////////

	
Me::Me(char)
: m_Production(make_unique<iron::Production>()),
  m_Army(make_unique<iron::Army>())
{

}

	
Me::~Me()
{
}


const BWAPI::Player Me::Player() const
{
	return bw->self();
}


BWAPI::Player Me::Player()
{
	return bw->self();
}

	
void Me::Initialize()
{
	for (auto & base : ai()->GetVMap().Bases())
		if (base.BWEMPart()->Location() == Player()->getStartLocation())
			AddBase(&base);

	assert_throw(Bases().size() == 1);

	AddBaseStronghold(Bases().back());
}

	
void Me::Update()
{
	for (auto * u : Units())
		exceptionHandler("Me::Update(" + u->NameWithId() + ")", 5000, [u]()
		{
			u->Update();
		});

	for (auto * b : Buildings())
		exceptionHandler("Me::Update(" + b->NameWithId() + ")", 5000, [b]()
		{
			b->Update();
		});

	m_mineralsAvailable = Player()->minerals();
	m_gasAvailable = Player()->gas();

	static vector<int> PreviousSupplyUsed(ai()->Latency() + 2, Player()->supplyUsed() / 2);
	for (int i = (int)PreviousSupplyUsed.size()-1 ; i > 0 ; --i)
		PreviousSupplyUsed[i] = PreviousSupplyUsed[i-1];
	PreviousSupplyUsed[0] = Player()->supplyUsed() / 2;
	bool supplyUsedStable = true;
	for (auto n : PreviousSupplyUsed)
		if (n != PreviousSupplyUsed[0])
			supplyUsedStable = false;
/*
	static vector<int> PreviousSupplyTotal(ai()->Latency() + 2, Player()->supplyTotal() / 2);
	for (int i = (int)PreviousSupplyTotal.size()-1 ; i > 0 ; --i)
		PreviousSupplyTotal[i] = PreviousSupplyTotal[i-1];
	PreviousSupplyTotal[0] = Player()->supplyTotal() / 2;
	bool supplyTotalStable = true;
	for (auto n : PreviousSupplyTotal)
		if (n != PreviousSupplyTotal[0])
			supplyTotalStable = false;
*/
	if (supplyUsedStable) m_supplyUsed = Player()->supplyUsed() / 2;

	//if (supplyTotalStable)
	m_supplyMax = Player()->supplyTotal() / 2;


	m_factoryActivity = 0;
	if (!me().Buildings(Terran_Factory).empty())
	{
		for (const auto & b : me().Buildings(Terran_Factory))
			m_factoryActivity += b->Activity();

		m_factoryActivity /= me().Buildings(Terran_Factory).size();
	}


	m_SCVworkers = count_if(Units(Terran_SCV).begin(), Units(Terran_SCV).end(), [](const unique_ptr<MyUnit> & u){ return u->GetStronghold(); });
	m_SCVsoldiers = Units(Terran_SCV).size() - m_SCVworkers;

	m_SCVsoldiersObjective = int(2*sqrt((double)CountMechGroundFighters()) - 0.6);
	if (Bases().size() <= 2)
		if (Army().MyGroundUnitsAhead() < Army().HisGroundUnitsAhead())
			m_SCVsoldiersObjective /= 2;

	Army().Update();

}

	
void Me::RunExperts()
{
	for (auto * e : Production().Experts())
		if (!e->Unselected())
			exceptionHandler("Me::RunExperts(" + e->ToString() + ")", 5000, [e]()
			{
				e->OnFrame();
			});
}

	
void Me::RunBehaviors()
{
	for (auto & u : Units(Terran_Vulture_Spider_Mine))
		exceptionHandler("Me::RunBehaviors(" + u->NameWithId() + ")", 5000, [&u]()
		{
			u->GetBehavior()->OnFrame();
		});
	for (auto * u : Units())
		exceptionHandler("Me::RunBehaviors(" + u->NameWithId() + ")", 5000, [u]()
			{
				if (!u->Is(Terran_Vulture_Spider_Mine))
				if (!u->Unit()->isLockedDown())
				if (!u->Unit()->isMaelstrommed())
				if (!u->Unit()->isStasised())
					u->GetBehavior()->OnFrame();
			});

	for (auto * b : Buildings())
		exceptionHandler("Me::RunBehaviors(" + b->NameWithId() + ")", 5000, [b]()
		{
			b->GetBehavior()->OnFrame();
		});
}

	
void Me::RunProduction()
{
	Production().OnFrame();
}


VArea * Me::GetVArea()
{
	return VArea::Get(GetArea());
}


void Me::AddUnit(BWAPI::Unit u)
{
	assert_throw(u->getPlayer() == Player());
	assert_throw(!u->getType().isBuilding());
	assert_throw(u->getType() != Terran_Siege_Tank_Siege_Mode);

	vector<unique_ptr<MyUnit>> & List = Units(u->getType());
	assert_throw(none_of(List.begin(), List.end(), [u](const unique_ptr<MyUnit> & up){ return up->Unit() == u; }));

	if (u->getType().isSpell()) return;

	List.push_back(MyUnit::Make(u));
	++m_creationCount[u->getType()];
	List.back()->InitializeStronghold();
	PUSH_BACK_UNCONTAINED_ELEMENT(m_AllUnits, List.back().get());
}


void Me::SetSiegeModeType(BWAPI::Unit u)
{
	vector<unique_ptr<MyUnit>> & List = Units(Terran_Siege_Tank_Tank_Mode);
	auto iMyUnit = find_if(List.begin(), List.end(), [u](const unique_ptr<MyUnit> & up){ return up->Unit() == u; });
	assert_throw(iMyUnit != List.end());

	(*iMyUnit)->SetSiegeModeType();
}


void Me::SetTankModeType(BWAPI::Unit u)
{
	vector<unique_ptr<MyUnit>> & List = Units(Terran_Siege_Tank_Tank_Mode);
	auto iMyUnit = find_if(List.begin(), List.end(), [u](const unique_ptr<MyUnit> & up){ return up->Unit() == u; });
	assert_throw(iMyUnit != List.end());

	(*iMyUnit)->SetTankModeType();
}


void Me::OnBWAPIUnitDestroyed_InformOthers(BWAPIUnit * pBWAPIUnit)
{
	for (auto * u : Units())
		if (u != pBWAPIUnit)
			u->OnOtherBWAPIUnitDestroyed(pBWAPIUnit);

	for (auto * b : Buildings())
		if (b != pBWAPIUnit)
			b->OnOtherBWAPIUnitDestroyed(pBWAPIUnit);
}


void Me::RemoveUnit(BWAPI::Unit u)
{
	UnitType type = u->getType();
	if (type == Terran_Siege_Tank_Siege_Mode) type = Terran_Siege_Tank_Tank_Mode;

	if (type.isSpell()) return;

	vector<unique_ptr<MyUnit>> & List = Units(type);
	auto iMyUnit = find_if(List.begin(), List.end(), [u](const unique_ptr<MyUnit> & up){ return up->Unit() == u; });
	assert_throw(iMyUnit != List.end());

	OnBWAPIUnitDestroyed_InformOthers(iMyUnit->get());
	ai()->GetStrategy()->OnBWAPIUnitDestroyed(iMyUnit->get());

	iMyUnit->get()->GetBehavior()->OnAgentBeingDestroyed();
	iMyUnit->get()->ChangeBehavior<DefaultBehavior>(iMyUnit->get());
	iMyUnit->get()->GetBehavior()->OnAgentBeingDestroyed();
	if (iMyUnit->get()->GetStronghold()) iMyUnit->get()->LeaveStronghold();

	{
		MyUnit * pMyUnit = iMyUnit->get();
		auto i = find(m_AllUnits.begin(), m_AllUnits.end(), pMyUnit);
		assert_throw(i != m_AllUnits.end());
		fast_erase(m_AllUnits, distance(m_AllUnits.begin(), i));
	}

	fast_erase(List, distance(List.begin(), iMyUnit));
}


int Me::CompletedBuildings(UnitType type) const
{
	assert_throw(type <= max_utid_used);
	assert_throw(type.isBuilding());
	
	return count_if(Buildings(type).begin(), Buildings(type).end(),
					[](const unique_ptr<MyBuilding> & b){ return b->Completed(); });
}


int Me::BuildingsBeingTrained(UnitType type) const
{
	assert_throw(type <= max_utid_used);
	assert_throw(type.isBuilding());
	
	return (int)Buildings(type).size() - CompletedBuildings(type);
}


int Me::CompletedUnits(UnitType type) const
{
	assert_throw(type <= max_utid_used);
	assert_throw(!type.isBuilding());
	
	return count_if(Units(type).begin(), Units(type).end(),
					[](const unique_ptr<MyUnit> & u){ return u->Completed(); });
}


int Me::UnitsBeingTrained(UnitType type) const
{
	assert_throw(type <= max_utid_used);
	assert_throw(!type.isBuilding());
	
	return (int)Units(type).size() - CompletedUnits(type);
}


void Me::AddBuilding(BWAPI::Unit u)
{
	assert_throw(u->getPlayer() == Player());
	assert_throw(u->getType().isBuilding());

	vector<unique_ptr<MyBuilding>> & List = Buildings(u->getType());
	assert_throw(none_of(List.begin(), List.end(), [u](const unique_ptr<MyBuilding> & up){ return up->Unit() == u; }));

	List.push_back(MyBuilding::Make(u));
	++m_creationCount[u->getType()];
	List.back()->InitializeStronghold();
	PUSH_BACK_UNCONTAINED_ELEMENT(m_AllBuildings, List.back().get());

	List.back()->RecordTrainingExperts(Production());
	List.back()->RecordResearchingExperts(Production());
}


void Me::RemoveBuilding(BWAPI::Unit u, bool no_check)
{
	UnitType type = u->getType();
	if (type == Resource_Vespene_Geyser) type = Race().getRefinery();

	vector<unique_ptr<MyBuilding>> & List = Buildings(type);
	auto iMyBuilding = find_if(List.begin(), List.end(), [u](const unique_ptr<MyBuilding> & up){ return up->Unit() == u; });
	if (iMyBuilding != List.end())
	{
		OnBWAPIUnitDestroyed_InformOthers(iMyBuilding->get());

		iMyBuilding->get()->GetBehavior()->OnAgentBeingDestroyed();
		iMyBuilding->get()->ChangeBehavior<DefaultBehavior>(iMyBuilding->get());
		iMyBuilding->get()->GetBehavior()->OnAgentBeingDestroyed();
		if (iMyBuilding->get()->GetStronghold()) iMyBuilding->get()->LeaveStronghold();

		{
			MyBuilding * pMyBuilding = iMyBuilding->get();
			auto i = find(m_AllBuildings.begin(), m_AllBuildings.end(), pMyBuilding);
			assert_throw(i != m_AllBuildings.end());
			fast_erase(m_AllBuildings, distance(m_AllBuildings.begin(), i));
		}

		fast_erase(List, distance(List.begin(), iMyBuilding));
	}
	else
	{
		assert_throw(no_check);
	}
}


void Me::AddBaseStronghold(VBase * b)
{
	assert_throw(!b->GetStronghold());

	m_Strongholds.push_back(make_unique<BaseStronghold>(b));
}


void Me::AddBase(VBase * b)
{
	PUSH_BACK_UNCONTAINED_ELEMENT(m_Bases, b);
}


void Me::RemoveBase(VBase * b)
{
	auto iBase = find(m_Bases.begin(), m_Bases.end(), b);
	assert_throw(iBase != m_Bases.end());

	fast_erase(m_Bases, distance(m_Bases.begin(), iBase));
}


bool Me::CanPay(const Cost & cost) const
{
	if (cost.Minerals() > MineralsAvailable()) return false;
	if (cost.Gas() > GasAvailable()) return false;
	if (cost.Supply() > max(0, SupplyAvailable())) return false;
	return true;
}


int Me::CountMechGroundFighters() const
{
	return	Units(Terran_Vulture).size() +
			Units(Terran_Siege_Tank_Tank_Mode).size() +
			Units(Terran_Goliath).size();
}


vector<const Area *> Me::EnlargedAreas() const
{
	vector<const Area *> List;

	for (VBase * base : Bases())
		List.insert(List.end(), base->GetArea()->EnlargedArea().begin(), base->GetArea()->EnlargedArea().end());

	return List;
}


MyBuilding * Me::FindBuildingNeedingBuilder(UnitType type, bool inStrongHold) const
{
	for (auto & b : Buildings(type))
		if (!b->Completed())
			if (none_of(Constructing::Instances().begin(), Constructing::Instances().end(),
						[&b](const Constructing * c) { return c->Location() == b->TopLeft(); }))
				if ((b->GetStronghold() != nullptr) == inStrongHold)
					return b.get();

	return nullptr;
}


} // namespace iron



