//////////////////////////////////////////////////////////////////////////
//
// 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 "watchArmy.h"
#include "baseDefense.h"
#include "../units/fight.h"
#include "../units/factory.h"
#include "../units/cc.h"
#include "../behavior/defaultBehavior.h"
#include "../behavior/fighting.h"
#include "../behavior/raiding.h"
#include "../Iron.h"

namespace { auto & bw = Broodwar; }


namespace iron
{



class WatchedGroup
{
public:
	static void						ClearFighters()				{ m_Fighters.clear(); }
	static const vector<MyUnit *> &	Fighters()					{ return m_Fighters; }

									WatchedGroup(const HisUnitTrace * u) : m_Members(1, u) {}

	bool							ShouldAdd(const HisUnitTrace * u) const;
	void							Add(const HisUnitTrace * u)	{ push_back_assert_does_not_contain(m_Members, u); }

	void							AddFightingCandidate(MyUnit * u)
									{
										push_back_assert_does_not_contain(m_FightingCandidates, u);
										push_back_assert_does_not_contain(WatchedGroup::m_Fighters, u);
									}

	void							Eval();
	void							Process() const;

	const vector<const HisUnitTrace *> &	Members() const		{ return m_Members; }
	const vector<MyUnit *> &		FightingCandidates() const	{ return m_FightingCandidates;}

	bool							Visible() const		{ return m_visible; }
	bool							Dangerous() const	{ return m_dangerous; }
	bool							AntiAir() const		{ return m_antiAir; }

	Position						Pos() const			{ return m_position; }

	int								Size() const		{ return (int)m_Members.size(); }

	// lowest value = highest priority
	int								Priority() const	{ return m_priority; }

	int								Power() const		{ return m_power; }

	void							ComputeStats();

	void							Draw() const;


private:
//	void							RestrictFightingCandidates();
	bool							StartFightCondition() const;
	bool							LeaveFightCondition() const;

	static vector<MyUnit *>			m_Fighters;
	vector<const HisUnitTrace *>	m_Members;
	Position						m_position;
	VBase *							m_pMyBaseNearby;
	int								m_distToMyBase;
	int								m_power;
	int								m_priority;
	bool							m_visible;
	bool							m_dangerous;
	bool							m_antiAir;
	bool							m_groundThreatBuildingNearby;
	bool							m_airThreatBuildingNearby;

	vector<MyUnit *>				m_FightingCandidates;
	string							m_fightSimFinalState = "";
	int								m_fightSimEfficiency = 0;
	bool							m_fightSimWin = false;
};


vector<MyUnit *> WatchedGroup::m_Fighters;

bool WatchedGroup::ShouldAdd(const HisUnitTrace * u) const
{
	assert_throw(!contains(m_Members, u));

	int baseMaxTileDist = 12;
	if (u->Type() == Terran_Vulture) baseMaxTileDist += 4;

	for (const HisUnitTrace * m : m_Members)
	{
		int maxTileDist = baseMaxTileDist;
		if (m->Type() == Terran_Vulture) maxTileDist += 4;
		
		if (groundDist(m->LastPosition(), u->LastPosition()) > 32*maxTileDist) return false;
	}

	return true;
}


void WatchedGroup::ComputeStats()
{
	assert_throw(Size() > 0);

	m_position = {0, 0};
	for (const HisUnitTrace * m : m_Members)
		m_position += m->LastPosition();
	m_position /= Size();

	if (!ai()->GetMap().GetArea(WalkPosition(m_position)))
		for (const HisUnitTrace * m : m_Members)
			if (ai()->GetMap().GetArea(WalkPosition(m->LastPosition())))
			{
				m_position = m->LastPosition();
				break;
			}


	m_visible = any_of(m_Members.begin(), m_Members.end(), [](const HisUnitTrace * m) { return m->GetHisUnit(); });


	const bool siegedTankHere = any_of(m_Members.begin(), m_Members.end(), [](const HisUnitTrace * m)
											{ return m->Type() == Terran_Siege_Tank_Siege_Mode; });
	
	const bool goliathHere = any_of(m_Members.begin(), m_Members.end(), [](const HisUnitTrace * m)
											{ return m->Type() == Terran_Goliath; });
	
	const bool medicHere = any_of(m_Members.begin(), m_Members.end(), [](const HisUnitTrace * m)
											{ return m->Type() == Terran_Medic; });
	
	const bool marinesHere = count_if(m_Members.begin(), m_Members.end(), [](const HisUnitTrace * m)
											{ return m->Type() == Terran_Marine; }) >= 2;
	
	m_groundThreatBuildingNearby = any_of(him().Buildings().begin(), him().Buildings().end(), [this](const unique_ptr<HisBuilding> & b)
											{ return b->GroundThreatBuilding() && dist(b->Pos(), Pos()) < 10*32; });

	m_airThreatBuildingNearby = any_of(him().Buildings().begin(), him().Buildings().end(), [this](const unique_ptr<HisBuilding> & b)
											{ return b->AirThreatBuilding() && dist(b->Pos(), Pos()) < 10*32; });
						
	m_dangerous = siegedTankHere || m_groundThreatBuildingNearby || medicHere;

	m_antiAir = goliathHere || marinesHere;

	m_power = 0;
	for (const HisUnitTrace * m : m_Members)
	{
		if (m->GetHisUnit()) m_visible = true;

		switch(m->Type())
		{
			case Terran_Marine:						m_power += 1; break;
			case Terran_Medic:						m_power += 2; break;
			case Terran_Vulture:					m_power += 2; break;
			case Terran_Siege_Tank_Tank_Mode:		m_power += 6; break;
			case Terran_Siege_Tank_Siege_Mode:		m_power += 6; break;
			case Terran_Goliath:					m_power += 6; break;
			default:								m_power += max(1, (m->LastLife() + m->LastShields()) / 40); break;
			break;
		}
	}
	m_power /= 2;


	m_pMyBaseNearby = findMyClosestBase(m_position);
	m_distToMyBase = groundDist(m_position, m_pMyBaseNearby ? m_pMyBaseNearby->BWEMPart()->Center() : me().GetBase(0)->Center());

	m_priority = m_pMyBaseNearby ? 0 : m_distToMyBase / (32*8);
//	m_priority -= m_power;
}


void WatchedGroup::Draw() const
{
#if DEV
	assert_throw(Size() > 0);

	Position topLeft     = {numeric_limits<int>::max(), numeric_limits<int>::max()};
	Position bottomRight = {numeric_limits<int>::min(), numeric_limits<int>::min()};
	for (const HisUnitTrace * m : m_Members)
	{
		makeBoundingBoxIncludePoint(topLeft, bottomRight, m->LastPosition());
		makeBoundingBoxIncludePoint(topLeft, bottomRight, m->LastPosition());
	}

	topLeft = topLeft - 30;
	bottomRight = bottomRight + 30;
	bw->drawBoxMap(topLeft, bottomRight, Colors::White);
	bw->drawTextMap(topLeft + Position(2, -12), "%c%c (%d) %s", Text::White, m_fightSimWin ? 'W' : 'L', m_fightSimEfficiency, m_fightSimFinalState.c_str());

	for (const MyUnit * u : m_FightingCandidates)
		bw->drawLineMap(u->Pos(), Pos(), Colors::White);

#endif
}

/*
void WatchedGroup::RestrictFightingCandidates()
{
	if (all_of(m_Members.begin(), m_Members.end(), [](const HisUnitTrace * u)
		{
			return	u->Type() == Terran_Siege_Tank_Tank_Mode ||
					u->Type() == Terran_Siege_Tank_Siege_Mode ||
					u->Type() == Terran_Goliath;
		})) return;

	for (MyUnit * u : m_FightingCandidates)
		if (u->Is(Terran_SCV))
			if (u->GetBehavior()->IsFighting())
				u->ChangeBehavior<DefaultBehavior>(u);

	really_remove_if(m_FightingCandidates, [](const MyUnit * u) { return u->Is(Terran_SCV); });
}
*/

void WatchedGroup::Eval()
{
	assert_throw(Size() > 0);
	assert_throw(Visible());

//	RestrictFightingCandidates();
//	if (m_FightingCandidates.empty()) return;

	FightSim fight;

	sort(m_FightingCandidates.begin(), m_FightingCandidates.end(), [](const MyUnit * a, const MyUnit * b)
												{ return a->LifeWithShields() < b->LifeWithShields(); });

	sort(m_Members.begin(), m_Members.end(), [](const HisUnitTrace * a, const HisUnitTrace * b)
												{ return a->LastLife() + a->LastShields() < b->LastLife() + b->LastShields(); });

	for (const MyUnit * u : m_FightingCandidates)
		fight.AddMyUnit(u->Type(), u->Life(), u->Shields());

	for (const HisUnitTrace * m : m_Members)
		fight.AddHisUnit(m->Type(), m->LastLife(), m->LastShields());


	fight.Eval();
	m_fightSimFinalState = fight.FinalState();
	m_fightSimEfficiency = fight.Efficiency();
	m_fightSimWin = fight.Win();
}


bool WatchedGroup::StartFightCondition() const
{
	return m_fightSimWin;
}


bool WatchedGroup::LeaveFightCondition() const
{
	if (m_fightSimWin) return false;

	return true;
}


void WatchedGroup::Process() const
{
	assert_throw(Size() > 0);
	assert_throw(Visible());

	if (StartFightCondition())
	{

		MyUnit * pFarestFightingCandidate = nullptr;
		int distFarestFightingCandidate = numeric_limits<int>::min();

		MyUnit * pFarestNonFightingCandidate = nullptr;
		int distFarestNonFightingCandidate = numeric_limits<int>::min();

		bool firstShootOccured = false;
		for (MyUnit * u : m_FightingCandidates)
			if (u->GetBehavior()->IsFighting() && u->GetBehavior()->IsFighting()->Shooted())
				firstShootOccured = true;
			else
			{
				int d = u->Flying()
					? roundedDist(u->Pos(), Pos())
					: groundDist(u->Pos(), Pos());

				if (u->GetBehavior()->IsFighting())
				{
					if (d > distFarestFightingCandidate)
					{
						distFarestFightingCandidate = d;
						pFarestFightingCandidate = u;
					}
				}
				else if (u->CanMove() && !(u->Is(Terran_Siege_Tank_Siege_Mode) || u->GetBehavior()->IsSieging()))
				if (!u->GetBehavior()->IsLaying())
				if (!u->GetBehavior()->IsDestroying())
				{
					if (d > distFarestNonFightingCandidate)
					{
						distFarestNonFightingCandidate = d;
						pFarestNonFightingCandidate = u;
					}
				}
			}

		if (!pFarestFightingCandidate)
		{
			if (pFarestNonFightingCandidate)
				pFarestNonFightingCandidate->ChangeBehavior<Fighting>(pFarestNonFightingCandidate, Pos());
		}
		else
		{
			for (MyUnit * u : m_FightingCandidates)
				if (!u->GetBehavior()->IsFighting())
					if (u->CanMove() && !(u->Is(Terran_Siege_Tank_Siege_Mode) || u->GetBehavior()->IsSieging()))
						if (!u->GetBehavior()->IsLaying())
						{
							int d = u->Flying()
								? roundedDist(u->Pos(), Pos())
								: groundDist(u->Pos(), Pos());

							if (firstShootOccured || (d > distFarestFightingCandidate - 2*32))
								u->ChangeBehavior<Fighting>(u, Pos());
						}
		}
	}
	else if (LeaveFightCondition())
	{
		for (MyUnit * u : m_FightingCandidates)
			if (u->GetBehavior()->IsFighting())
				u->ChangeBehavior<DefaultBehavior>(u);
	}
}





//////////////////////////////////////////////////////////////////////////////////////////////
//                                                                                          //
//                                  class WatchArmy
//                                                                                          //
//////////////////////////////////////////////////////////////////////////////////////////////


WatchArmy::WatchArmy()
{
}


WatchArmy::~WatchArmy()
{
}


string WatchArmy::StateDescription() const
{
	if (!m_active) return "-";
	if (m_active) return "active";

	return "-";
}


void WatchArmy::OnFrame_v()
{
	if (him().Race() != Races::Terran) return;

	WatchedGroup::ClearFighters();

	vector<const HisUnitTrace *> WatchedUnits;
	for (const auto & info : him().UnitTrace())
		if (!info.second.Type().isWorker())
			//if (ai()->Frame() - info.second.LastTimeVisible() < 50)
		{
			switch (info.second.Type())
			{
			case Terran_Marine:
			case Terran_Medic:
			case Terran_Vulture:
			case Terran_Siege_Tank_Tank_Mode:
			case Terran_Siege_Tank_Siege_Mode:
			case Terran_Goliath:
				WatchedUnits.push_back(&info.second);
			}
		}

	const Position myBasePos = me().GetBase(0)->Center();
	sort(WatchedUnits.begin(), WatchedUnits.end(), [myBasePos](const HisUnitTrace * a, const HisUnitTrace * b)
			{
				if (a->GetHisUnit() && !b->GetHisUnit()) return true;
				if (!a->GetHisUnit() && b->GetHisUnit()) return false;
				return roundedDist(a->LastPosition(), myBasePos) < roundedDist(b->LastPosition(), myBasePos);
			});

	vector<WatchedGroup> WatchedGroups;
	for (const HisUnitTrace * u : WatchedUnits)
	{
		bool merged = false;
		for (WatchedGroup & Group : WatchedGroups)
			if (Group.ShouldAdd(u))
			{
				Group.Add(u);
				merged = true;
				break;
			}

		if (!merged) WatchedGroups.emplace_back(u);
	}

	for (WatchedGroup & Group : WatchedGroups)
		Group.ComputeStats();

	sort(WatchedGroups.begin(), WatchedGroups.end(), [](const WatchedGroup & a, const WatchedGroup & b)
													{ return a.Priority() < b.Priority(); });

	vector<WatchedGroup *> VisibleGroups;
	for (WatchedGroup & Group : WatchedGroups)
		if (Group.Visible())
			VisibleGroups.push_back(&Group);

//	if (VisibleGroups.empty()) return;

	vector<MyUnit *> FightingCandidates;

	for (UnitType type : {Terran_Siege_Tank_Siege_Mode, Terran_Siege_Tank_Tank_Mode, Terran_Goliath, Terran_Vulture})//, Terran_SCV})
		for (const auto & u : me().Units(type))
			if (u->Completed())
			if (!u->Loaded())
				if (!((type == Terran_SCV) && u->GetStronghold()))
//						if ((u->Life() > u->MaxLife()*1/4) || !u->WorthBeingRepaired())
						if (!u->GetBehavior()->IsBlocking())
						if (!u->GetBehavior()->IsChecking())
						if (!u->GetBehavior()->IsDestroying())
						if (!u->GetBehavior()->IsKillingMine())
						if (!u->GetBehavior()->IsLaying())
						if (!u->GetBehavior()->IsSniping())
						if (!u->GetBehavior()->IsWalking())
							FightingCandidates.push_back(u.get());

	for (MyUnit * cand : FightingCandidates)
	{
		WatchedGroup *	pNearestVisibleWatchedGroup = nullptr;

		const bool siegedTank = cand->Is(Terran_Siege_Tank_Siege_Mode) || cand->GetBehavior()->IsSieging();

		int minDist = siegedTank ? 32*12 : 32*30;
		for (WatchedGroup * group : VisibleGroups)
		{
			int d = cand->Flying() || siegedTank
				? roundedDist(cand->Pos(), group->Pos())
				: groundDist(cand->Pos(), group->Pos());

			if (d < minDist)
			{
				minDist = d;
				pNearestVisibleWatchedGroup = group;
			}
		}

		if (pNearestVisibleWatchedGroup)
			if ((pNearestVisibleWatchedGroup->Priority() <= 3) || !pNearestVisibleWatchedGroup->Dangerous())
			if (!(pNearestVisibleWatchedGroup->AntiAir() && cand->Flying()))
				pNearestVisibleWatchedGroup->AddFightingCandidate(cand);
	}

	for (WatchedGroup * pGroup : VisibleGroups)
		if (!pGroup->FightingCandidates().empty())
		{
			pGroup->Eval();
			pGroup->Process();
		}

	for (WatchedGroup & Group : WatchedGroups)
		Group.Draw();

	// Finally, release all Fighting units that were not processed through a WatchedGroup:
	vector<Fighting *> Fighters = Fighting::Instances();
	for (Fighting * pFighter : Fighters)
		if (!contains(WatchedGroup::Fighters(), pFighter->Agent()))
			pFighter->Agent()->ChangeBehavior<DefaultBehavior>(pFighter->Agent());
}




} // namespace iron



