//////////////////////////////////////////////////////////////////////////
//
// 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 "him.h"
#include "../strategy/strategy.h"
#include "../behavior/checking.h"
#include "../Iron.h"

namespace { auto & bw = Broodwar; }

namespace iron
{

class Him & him() { return ai()->Him(); }


//////////////////////////////////////////////////////////////////////////////////////////////
//                                                                                          //
//                                  class Him
//                                                                                          //
//////////////////////////////////////////////////////////////////////////////////////////////


const BWAPI::Player Him::Player() const
{
	return bw->enemy();
}


BWAPI::Player Him::Player()
{
	return bw->enemy();
}


void Him::UpdateStartingBase()
{
	if (m_pStartingBase) return;

	if (ai()->GetStrategy()->HisPossibleLocations().size() == 1)
	{
		SetStartingBase(ai()->GetVMap().GetBaseAt(ai()->GetStrategy()->HisPossibleLocations().front()));
		return;
	}

	for (auto & b : Buildings())
		if (b->Type().isResourceDepot())
		{
		//	ai()->SetDelay(50);
			for (auto & base : ai()->GetVMap().Bases())
				if (base.BWEMPart()->Starting())
					if (abs(base.BWEMPart()->Location().x - b->TopLeft().x) < 4)
					if (abs(base.BWEMPart()->Location().y - b->TopLeft().y) < 3)
					{
						SetStartingBase(&base);
						return;
					}
		}

	for (auto & b : Buildings())
		if (!b->Flying())
			for (auto & base : ai()->GetVMap().Bases())
				if (base.BWEMPart()->Starting())
					if (!contains(me().Bases(), &base))
						if (dist(base.BWEMPart()->Location(), b->TopLeft()) < 48)
							if (any_of(b->GetArea()->ChokePoints().begin(), b->GetArea()->ChokePoints().end(),
								[&b](const ChokePoint * p){ return dist(TilePosition(p->Center()), b->TopLeft()) < 10; }))
								{
									SetStartingBase(&base);
									return;
								}
/*
	vector<VBase *> HisMainAndNaturalCandidates;
	for (auto & base : ai()->GetVMap().Bases())
		if (base.BWEMPart()->Starting())
			if (!contains(me().Bases(), &base))
			{
				HisMainAndNaturalCandidates.push_back(&base);
				if (VBase * pNatural = findNatural(&base))
					if (!contains(HisMainAndNaturalCandidates, pNatural))
						HisMainAndNaturalCandidates.push_back(pNatural);
			}

	for (auto & b : Buildings())
		for (VBase * base : HisMainAndNaturalCandidates)
//			if (dist(base.BWEMPart()->Location(), b->TopLeft()) < 48)
				if (any_of(b->GetArea()->ChokePoints().begin(), b->GetArea()->ChokePoints().end(),
					[&b](const ChokePoint * p){ return dist(center(p->Center()), b->Pos()) < 10*32; }))
					{
						SetStartingBase(base);
						return;
					}
	for (auto & u : Units())
		if (!(u->Type().isWorker() || u->Flying()))
			if (!u->Unit()->isMoving())
				for (VBase * base : HisMainAndNaturalCandidates)
		//			if (dist(base.BWEMPart()->Location(), b->TopLeft()) < 48)
						if (any_of(u->GetArea()->ChokePoints().begin(), u->GetArea()->ChokePoints().end(),
							[&u](const ChokePoint * p){ return dist(center(p->Center()), u->Pos()) < 10*32; }))
							{
								SetStartingBase(base);
								return;
							}
*/
}


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

	UpdateUnitTrace();

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

	{	// Remove any InFog-HisUnit having at least one tile visible since 10 frames. See also Him::OnShowUnit
		vector<BWAPI::Unit> ObsoleteInFogUnits;
		for (auto & u : Units())
			if (u->InFog())
				if (!(u->Type().isBurrowable() || u->Is(Terran_Vulture_Spider_Mine)))
					if (ai()->Frame() - u->LastFrameNoVisibleTile() > 10)
						ObsoleteInFogUnits.push_back(u->Unit());

		for (BWAPI::Unit u : ObsoleteInFogUnits)
			RemoveUnit(u, !bool("informOthers"));
	}

	{	// Remove any InFog-HisBuilding having at least one tile visible since 10 frames. See also Him::OnShowBuilding
		vector<BWAPI::Unit> ObsoleteInFogBuildings;
		for (auto & b : Buildings())
			if (b->InFog())
				if (ai()->Frame() - b->LastFrameNoVisibleTile() > 10)
					ObsoleteInFogBuildings.push_back(b->Unit());

		for (BWAPI::Unit b : ObsoleteInFogBuildings)
			RemoveBuilding(b, !bool("informOthers"));
	}

	UpdateStartingBase();
	CheckTanksInFog();
}


void Him::AddUnit(BWAPI::Unit u)
{
	assert_throw_plus(u->getPlayer() == Player(), u->getType().getName());
	assert_throw(!u->getType().isBuilding());

	assert_throw_plus(none_of(m_Units.begin(), m_Units.end(), [u](const unique_ptr<HisUnit> & up){ return up->Unit() == u; }), to_string(u->getID()));

	m_Units.push_back(make_unique<HisUnit>(u));
}


void Him::RemoveUnit(BWAPI::Unit u, bool informOthers)
{
	auto iHisUnit = find_if(m_Units.begin(), m_Units.end(), [u](const unique_ptr<HisUnit> & up){ return up->Unit() == u; });
	if (iHisUnit != m_Units.end())
	{
		if (informOthers && !iHisUnit->get()->InFog()) me().OnBWAPIUnitDestroyed_InformOthers(iHisUnit->get());
		fast_erase(m_Units, distance(m_Units.begin(), iHisUnit));
	}
}


void Him::AddBuilding(BWAPI::Unit u)
{
	assert_throw_plus(u->getPlayer() == Player(), u->getType().getName());
	assert_throw(u->getType().isBuilding());

	assert_throw(none_of(m_Buildings.begin(), m_Buildings.end(), [u](const unique_ptr<HisBuilding> & up){ return up->Unit() == u; }));

	m_Buildings.push_back(make_unique<HisBuilding>(u));
}


void Him::RemoveBuilding(BWAPI::Unit u, bool informOthers)
{
	auto iHisBuilding = find_if(m_Buildings.begin(), m_Buildings.end(), [u](const unique_ptr<HisBuilding> & up){ return up->Unit() == u; });
	if (iHisBuilding != m_Buildings.end())
	{
	//	bw << "Him::RemoveBuilding(" << iHisBuilding->get()->NameWithId() << ")" << endl;
		if (informOthers && !iHisBuilding->get()->InFog()) me().OnBWAPIUnitDestroyed_InformOthers(iHisBuilding->get());
		fast_erase(m_Buildings, distance(m_Buildings.begin(), iHisBuilding));
	}
}


HisUnit * Him::FindUnit(BWAPI::Unit u) const
{
	auto iHisUnit = find_if(m_Units.begin(), m_Units.end(), [u](const unique_ptr<HisUnit> & up){ return up->Unit() == u; });
	return iHisUnit != m_Units.end() ? iHisUnit->get() : nullptr;
}


HisBuilding * Him::FindBuilding(BWAPI::Unit u) const
{
	auto iHisBuilding = find_if(m_Buildings.begin(), m_Buildings.end(), [u](const unique_ptr<HisBuilding> & up){ return up->Unit() == u; });
	return iHisBuilding != m_Buildings.end() ? iHisBuilding->get() : nullptr;
}


void Him::UpdateUnitTrace()
{
	for (auto & u : Units())
	{
		if (!u->InFog())
			if (u->Type() != Terran_Vulture_Spider_Mine)
			{
				if (u->Type().isBuilding())	m_UnitTrace.erase(u->Unit());
				else
				{
					auto iInserted = m_UnitTrace.emplace(u->Unit(), u.get());
					auto & trace = iInserted.first->second;
					if (!iInserted.second) trace.Update(u.get());
					assert_throw(trace.LastTimeVisible() == ai()->Frame());
				}
			}
	}

	for (auto & trace : m_UnitTrace)
		if (trace.second.LastTimeVisible() != ai()->Frame())
			trace.second.Update(nullptr);


	for (auto & trace : m_UnitTrace)
	{
		assert_throw(trace.first->getType() != Terran_Vulture_Spider_Mine);
		assert_throw(!trace.second.GetHisUnit() || !trace.second.GetHisUnit()->InFog());
	}
}


void Him::CheckTanksInFog()
{
	vector<BWAPI::Unit> ToCheck;
	for (const auto & u : m_Units)
		if (u->InFog())
			if (u->Is(Terran_Siege_Tank_Siege_Mode) || u->Is(Zerg_Lurker))
			{
				auto iTrace = m_UnitTrace.find(u->Unit());
				assert_throw(iTrace != m_UnitTrace.end());

			///	bw << "check " << u->NameWithId() << " in " << iTrace->second.NextTimeToCheck() - ai()->Frame() << endl;

				if (iTrace->second.TimeToCheck())
				{
				///	bw << "check " << u->NameWithId() << " : " << endl;

					// If possible, we simply assign a unit to check if u is still there:
					// if (u->getType() == Terran_Siege_Tank_Siege_Mode)
					if (ai()->Frame() - iTrace->second.NextTimeToCheck() < 200)
					{
						if (MyUnit * pCandidate = Checking::FindCandidate(u.get()))
						{
						///	ai()->SetDelay(1000);
						///	bw << ai()->Frame() << ") check " << u->NameWithId() << " after " << ai()->Frame() - iTrace->second.NextTimeToCheck() << endl;
							pCandidate->ChangeBehavior<Checking>(pCandidate, u.get());
							iTrace->second.OnChecked();
							return;
						}
					}
					else // Otherwise, after a delay, we remove u so that u won't be considered as a threat anymore (until u shows again):
					{
					///	ai()->SetDelay(1000);
					///	bw << ai()->Frame() << ") forget inFog Unit " << u->NameWithId() << " after " << ai()->Frame() - iTrace->second.NextTimeToCheck() << endl;
						RemoveUnit(u->Unit(), !bool("informOthers"));
						iTrace->second.OnChecked();
						return;
					}
				}
			}
}


void Him::CheckObsoleteInFogUnit(BWAPI::Unit u)
{
	vector<BWAPI::Unit> ObsoleteInFogUnits;
	for (const auto & v : m_Units)
		if (v->Unit() == u)
		{
			assert_throw(v->InFog());
			ObsoleteInFogUnits.push_back(v->Unit());
		}

	for (BWAPI::Unit v : ObsoleteInFogUnits)
		RemoveUnit(v, !bool("informOthers"));
}


void Him::CheckObsoleteInFogBuilding(BWAPI::Unit u)
{
	vector<BWAPI::Unit> ObsoleteInFogBuildings;
	for (const auto & b : m_Buildings)
		if (b->Unit() == u)
		{
			assert_throw(b->InFog());
			ObsoleteInFogBuildings.push_back(b->Unit());
		}

	for (BWAPI::Unit b : ObsoleteInFogBuildings)
		RemoveBuilding(b, !bool("informOthers"));
}


void Him::OnShowUnit(BWAPI::Unit u)
{
	// First, remove any corresponding InFog-HisUnit. See also Him::Update
	CheckObsoleteInFogUnit(u);

	assert_throw(none_of(m_Buildings.begin(), m_Buildings.end(), [u](const unique_ptr<HisBuilding> & up){ return up->Unit() == u; }));

	AddUnit(u);
}


void Him::OnShowBuilding(BWAPI::Unit u)
{
	// Check destroyed Neutrals not triggered by onUnitDestroy because the were not visible.
	// TODO: refactor and do the check globally as soon as one tile of the destroyed Neutrals is visible.
	for (int dy = 0 ; dy < u->getType().tileSize().y ; ++dy)
	for (int dx = 0 ; dx < u->getType().tileSize().x ; ++dx)
	{
		auto & tile = ai()->GetMap().GetTile(u->getTilePosition() + TilePosition(dx, dy));
		if (Neutral * n = tile.GetNeutral())
		{
			if (n->IsMineral())
			{
			///	bw << "Check destroyed Mineral " << n->TopLeft() << endl;
				ai()->OnMineralDestroyed(n->Unit());
			}
			else if (n->IsStaticBuilding())
			{
			///	bw << "Check destroyed Static Building " << n->TopLeft() << endl;
				ai()->OnStaticBuildingDestroyed(n->Unit());
			}
		}
	}

	// First, remove any corresponding InFog-HisBuilding. See also Him::Update
	CheckObsoleteInFogUnit(u);		// in case of zerg's morphing
	CheckObsoleteInFogBuilding(u);

	AddBuilding(u);
}


void Him::OnHideUnit(BWAPI::Unit u)
{
	auto iHisUnit = find_if(m_Units.begin(), m_Units.end(), [u](const unique_ptr<HisUnit> & up){ return up->Unit() == u; });
	if (iHisUnit != m_Units.end())
	{
		if (
			iHisUnit->get()->Is(Zerg_Lurker)/* && iHisUnit->get()->Burrowed()*/ ||
			iHisUnit->get()->Is(Terran_Siege_Tank_Siege_Mode) ||
		//	iHisUnit->get()->Type().isWorker() ||
			iHisUnit->get()->Is(Terran_Vulture_Spider_Mine)
			)
		{
		//	if (iHisUnit->get()->Is(Terran_Vulture_Spider_Mine))
		//	{ bw << " SetInFog : " << (*iHisUnit)->Type().getName() << endl; ai()->SetDelay(500); }

			me().OnBWAPIUnitDestroyed_InformOthers(iHisUnit->get());
			iHisUnit->get()->SetInFog();
		}
		else
		{
		//	if (iHisUnit->get()->Is(Zerg_Lurker))
		//	{ bw << " RemoveUnit : " << (*iHisUnit)->Type().getName() << endl; ai()->SetDelay(5000); }

			RemoveUnit(u, bool("informOthers"));
		}
	}
}


void Him::OnHideBuilding(BWAPI::Unit u)
{
	auto iHisBuilding = find_if(m_Buildings.begin(), m_Buildings.end(), [u](const unique_ptr<HisBuilding> & up){ return up->Unit() == u; });
	if (iHisBuilding != m_Buildings.end())
	{
		if (!iHisBuilding->get()->Flying())
		{
			me().OnBWAPIUnitDestroyed_InformOthers(iHisBuilding->get());
			iHisBuilding->get()->SetInFog();
		}
		else
			RemoveBuilding(u, bool("informOthers"));
	}
}


void Him::OnDestroyed(BWAPI::Unit u)
{
	RemoveUnit(u, !bool("informOthers"));
	RemoveBuilding(u, !bool("informOthers"));
	m_UnitTrace.erase(u);
}


const Area * Him::GetArea() const
{
	return m_pStartingBase ? m_pStartingBase->BWEMPart()->GetArea() : nullptr;
}

	
} // namespace iron



