//////////////////////////////////////////////////////////////////////////
//
// 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 "zerglingRush.h"
#include "../units/cc.h"
#include "../behavior/mining.h"
#include "../behavior/fleeing.h"
#include "../behavior/walking.h"
#include "../behavior/chasing.h"
#include "../behavior/blocking.h"
#include "../behavior/sniping.h"
#include "../behavior/raiding.h"
#include "../behavior/defaultBehavior.h"
#include "../strategy/strategy.h"
#include "../Iron.h"

namespace { auto & bw = Broodwar; }




namespace iron
{


//////////////////////////////////////////////////////////////////////////////////////////////
//                                                                                          //
//                                  class ZerglingRush
//                                                                                          //
//////////////////////////////////////////////////////////////////////////////////////////////


ZerglingRush::ZerglingRush()
{
}


ZerglingRush::~ZerglingRush()
{
	if (m_pSentry)
		if (m_pSentry->GetBehavior()->IsWalking())
			m_pSentry->ChangeBehavior<DefaultBehavior>(m_pSentry);

	ai()->GetStrategy()->SetMinScoutingSCVs(1);
}


string ZerglingRush::StateDescription() const
{
	if (!m_detected) return "-";
	if (m_detected) return "detected";

	return "-";
}


void ZerglingRush::OnBWAPIUnitDestroyed(BWAPIUnit * pBWAPIUnit)
{
	if (m_pSentry == pBWAPIUnit)
		m_pSentry = nullptr;
}


static vector<Position> getChokePointDefensePositions(const ChokePoint * cp, int n)
{
    const Area * pDefenseArea =
		dist(center(cp->GetAreas().first->Top()), me().GetBase(0)->Center()) <
		dist(center(cp->GetAreas().second->Top()), me().GetBase(0)->Center())
		? cp->GetAreas().first
		: cp->GetAreas().second;

	vector<Position> List{center(cp->Center())};
	
	List.push_back(center(cp->Geometry().front()));
	List.push_back(center(cp->Geometry().back()));

	int i = 0;
	while ((int)List.size() != n)
	{
		auto startingNode = ChokePoint::node(i++ % ChokePoint::node_count);
		Position newPos = center(ai()->GetMap().BreadthFirstSearch(cp->PosInArea(startingNode, pDefenseArea),
			[pDefenseArea, &List](const MiniTile & miniTile, WalkPosition w)	// findCond
				{ return (miniTile.AreaId() == pDefenseArea->Id()) &&
						none_of(List.begin(), List.end(), [w](Position p) { return roundedDist(w, WalkPosition(p)) < 4; }) &&
						any_of(List.begin(), List.end(), [w](Position p) { return roundedDist(w, WalkPosition(p)) >= 4; }); },
			[pDefenseArea](const MiniTile & miniTile, WalkPosition)		// visitCond
				{ return (miniTile.AreaId() == pDefenseArea->Id()); }
			));

		List.push_back(newPos);
	}

	return List;
}

/*
static TilePosition findBunkerLocation(const ChokePoint * pDefenseCP)
{
	
		TilePosition location = center(ai()->GetMap().BreadthFirstSearch(cp->PosInArea(startingNode, pDefenseArea),
			[pDefenseArea, &List](const MiniTile & miniTile, WalkPosition w)	// findCond
				{ return (miniTile.AreaId() == pDefenseArea->Id()) &&
						none_of(List.begin(), List.end(), [w](Position p) { return roundedDist(w, WalkPosition(p)) < 4; }) &&
						any_of(List.begin(), List.end(), [w](Position p) { return roundedDist(w, WalkPosition(p)) >= 4; }); },
			[pDefenseArea](const MiniTile & miniTile, WalkPosition)		// visitCond
				{ return (miniTile.AreaId() == pDefenseArea->Id()); }
			));
}
*/

void ZerglingRush::ChokePointDefense()
{
	if (const ChokePoint * pDefenseCP = ai()->Me().GetVArea()->DefenseCP())
	{
//		DO_ONCE
//			m_BunkerLocation = findBunkerLocation(pDefenseCP);

		int zerglingsHere = count_if(him().UnitTrace().begin(), him().UnitTrace().end(), [pDefenseCP](const pair<Unit, HisUnitTrace> & p)
								{
									return p.second.Type() == Zerg_Zergling &&
											ai()->Frame() - p.second.LastTimeVisible() < 150 &&
											dist(p.second.LastPosition(), center(pDefenseCP->Center())) < 25*32;
								});
		int defenders = min(9, 6 + zerglingsHere);

	///	bw << zerglingsHere << "  " << defenders << endl;


		MyUnit * pClosestMarine = nullptr;
		int minDistMarine = 32*10;
		for (const auto & u : me().Units(Terran_Marine))
			if (u->Completed())
				if (dist(u->Pos(), center(pDefenseCP->Center())) < minDistMarine)
				{
					minDistMarine = roundedDist(u->Pos(), center(pDefenseCP->Center()));
					pClosestMarine = u.get();
				}

		vector<Position> DefensePositions = getChokePointDefensePositions(pDefenseCP, defenders+3);
		sort(DefensePositions.begin(), DefensePositions.end(), [pDefenseCP, pClosestMarine](Position a, Position b)
				{ return dist(a, center(pDefenseCP->Center())) + (pClosestMarine ? dist(a, pClosestMarine->Pos()) : 0) <
						 dist(b, center(pDefenseCP->Center())) + (pClosestMarine ? dist(b, pClosestMarine->Pos()) : 0); });

		vector<Blocking *> AssignedBlockers;
	///	int i = 0;
		for (Position p : DefensePositions)
		{
		///	bw->drawTextMap(p, "%c%d", Text::White, i++);
		///	bw->drawCircleMap(p, 3, Colors::White);

			if (any_of(Blocking::Instances().begin(), Blocking::Instances().end(), [p](const Blocking * b)
											{ return roundedDist(b->Agent()->Pos(), p) < 4; }))
				continue;

			int minDist = numeric_limits<int>::max();
			Blocking * pClosestCandidate = nullptr;
			for (Blocking * b : Blocking::Instances())
				if (!contains(AssignedBlockers, b))
					if (b->State() == Blocking::blocking)
						//if (b->Agent()->Unit()->isIdle())
							if (roundedDist(b->Agent()->Pos(), p) < minDist)
						{
							minDist = roundedDist(b->Agent()->Pos(), p);
							pClosestCandidate = b;
						}

			if (!pClosestCandidate) break;

			AssignedBlockers.push_back(pClosestCandidate);

			if (pClosestCandidate->Agent()->CanAcceptCommand())
			{
				pClosestCandidate->Agent()->Move(p);
			///	bw->drawLineMap(pClosestCandidate->Agent()->Pos(), p, Colors::White);
			}
		}
			


		int minDistToCC = numeric_limits<int>::max();
		Blocking * pBackEndBlocker = nullptr;

		for (Blocking * pBlocker : Blocking::Instances())
			if (pBlocker->State() == Blocking::blocking)
			{
				int d = roundedDist(pBlocker->Agent()->Pos(), me().GetBase(0)->Center());
				if (d < minDistToCC)
				{
					minDistToCC = d;
					pBackEndBlocker = pBlocker;
				}
			}

		
		int marinesAroundBlockingPos = 0;
		Position marinesBlockingPos = pBackEndBlocker ? pBackEndBlocker->Agent()->Pos() : center(pDefenseCP->Center());
		for (const auto & u : me().Units(Terran_Marine))
			if (u->Completed())
			{
				if (dist(u->Pos(), marinesBlockingPos) < 32*8)
					++marinesAroundBlockingPos;

				if (u->GetBehavior()->IsExploring())
				{
					if (dist(u->Pos(), me().GetBase(0)->Center()) + 32*3 > dist(center(pDefenseCP->Center()), me().GetBase(0)->Center()))
					{
						Position retraitingPos = (me().GetBase(0)->Center() + marinesBlockingPos)/2;
						if (ai()->GetMap().GetArea(WalkPosition(retraitingPos)) != me().GetBase(0)->GetArea())
							retraitingPos = center(me().GetBase(0)->GetArea()->Top());

						u->ChangeBehavior<Raiding>(u.get(), retraitingPos);
					}

					if (dist(u->Pos(), marinesBlockingPos) > 32*6)
					{
						u->ChangeBehavior<Raiding>(u.get(), marinesBlockingPos);
					}
				}
			}

		const int blockersWanted = max(2, defenders - marinesAroundBlockingPos * (m_snipersAvailable ? 3 : 1));

		const int blockers = count_if(Blocking::Instances().begin(), Blocking::Instances().end(), [](const Blocking * pBlocker)
								{ return pBlocker->State() != Blocking::dragOut; });

		if (blockers < blockersWanted)
		{
			if (My<Terran_SCV> * pWorker = findFreeWorker(me().GetVBase(0), [](const My<Terran_SCV> * pSCV)
						{ return pSCV->Life() > 50; }))
				return pWorker->ChangeBehavior<Blocking>(pWorker, VChokePoint::Get(pDefenseCP));
		}
		else if (blockers > blockersWanted)
		{
			int minLife = numeric_limits<int>::max();
			MyUnit * pBlockerToRemove = nullptr;
			for (Blocking * pBlocker : Blocking::Instances())
				if (pBlocker->State() != Blocking::dragOut)
					if (pBlocker->Agent()->Life() < minLife)
					{
						minLife = pBlocker->Agent()->Life();
						pBlockerToRemove = pBlocker->Agent();
					}

			if (pBlockerToRemove)
				return pBlockerToRemove->ChangeBehavior<DefaultBehavior>(pBlockerToRemove);
		}
	}
}


void ZerglingRush::WorkerDefense()
{
	for (const auto & u : me().Units(Terran_SCV))
		if (u->Completed())
			if (groundDist(u->Pos(), me().GetBase(0)->Center()) < 10*32)
				if (u->GetBehavior()->IsMining() ||
					u->GetBehavior()->IsRefining())
				{
					int minDistToMyRange = 3*32;
					HisUnit * pNearestTarget = nullptr;
					for (const auto & faceOff : u->FaceOffs())
						if (faceOff.MyAttack() && faceOff.HisAttack())
						if (faceOff.DistanceToMyRange() < minDistToMyRange)
						{
							minDistToMyRange = faceOff.DistanceToMyRange();
							pNearestTarget = faceOff.His()->IsHisUnit();
						}

					if (pNearestTarget)
						u->ChangeBehavior<Chasing>(u.get(), pNearestTarget, bool("insist"), 15 + minDistToMyRange/4);
				}
}


bool ZerglingRush::TechRestartingCondition() const
{
	const int minMarinesToRestart = 2;

	return m_snipersAvailable && (me().CompletedUnits(Terran_Marine) >= minMarinesToRestart);
}


void ZerglingRush::OnFrame_v()
{
	m_zerglings = count_if(him().UnitTrace().begin(), him().UnitTrace().end(), [](const pair<Unit, HisUnitTrace> & p)
							{ return p.second.Type() == Zerg_Zergling; });

	const int bigRushZerglingsThreshold = 9;
	m_maxMarines = max(m_maxMarines, m_zerglings <= bigRushZerglingsThreshold ? 4 : 8);

	if ((him().Race() == Races::Terran) || (him().Race() == Races::Protoss)) return Abort();

	if (m_zerglings <= bigRushZerglingsThreshold)
	{
		if (me().CompletedUnits(Terran_Vulture) >= 2) return Abort();
		if ((me().CompletedUnits(Terran_Vulture) == 1) && (me().CompletedUnits(Terran_Marine) >= 2)) return Abort();
	}
	else
	{
		if (me().CompletedBuildings(Terran_Bunker) >= 2)
		if (me().CompletedUnits(Terran_Marine) >= 8)
		if (me().CompletedUnits(Terran_Vulture) >= 3)
			return Abort();
	}

	if (m_pSentry)
	{
		// Walking agents do not check threats themselves.
		if (m_pSentry->Life() < m_pSentry->PrevLife(10))
		{
			m_pSentry->ChangeBehavior<Fleeing>(m_pSentry);
			m_pSentry = nullptr;
		}
	}
	else
		if (ai()->Frame() >= 2700)
			if (me().GetVArea()->DefenseCP() && (me().GetVArea()->SentryPos() != Positions::None))
				if (My<Terran_SCV> * pWorker = findFreeWorker(me().GetVBase(0)))
					DO_ONCE
					{
						pWorker->ChangeBehavior<Walking>(pWorker, me().GetVArea()->SentryPos(), __FILE__ + to_string(__LINE__));
						m_pSentry = pWorker;
						return;
					}

	if (m_detected)
	{
		if (!Sniping::Instances().empty())
			m_snipersAvailable = true;

		if (m_pSentry)
		{
			if (!m_pSentry->FaceOffs().empty())
			{
				m_zerglingsNearSentry = true;
				m_pSentry->ChangeBehavior<Blocking>(m_pSentry, VChokePoint::Get(ai()->Me().GetVArea()->DefenseCP()));
				m_pSentry = nullptr;
				return;
			}
		}

		DO_ONCE
			for (const auto & b : me().Buildings(Terran_Refinery))
				if (!b->Completed())
					if (b->CanAcceptCommand())
						return b->CancelConstruction();

		DO_ONCE
			if (Sniping::Instances().empty())
				if (me().SupplyMax() == 18)
					for (const auto & b : me().Buildings(Terran_Supply_Depot))
						if (!b->Completed())
							if (b->CanAcceptCommand())
								return b->CancelConstruction();

		if (!TechRestartingCondition())
		{


			if (me().Units(Terran_Vulture).size() == 0)
				if (Mining::Instances().size() < 6)
					if (me().MineralsAvailable() < 30)
						if (!(me().UnitsBeingTrained(Terran_SCV) && me().UnitsBeingTrained(Terran_Marine)))
						{
							MyBuilding * pLatestUncompletedFactory = nullptr;
							for (const auto & b : me().Buildings(Terran_Factory))
								if (!b->Completed())
									if (!pLatestUncompletedFactory || (b->RemainingBuildTime() > pLatestUncompletedFactory->RemainingBuildTime()))
										pLatestUncompletedFactory = b.get();

							if (pLatestUncompletedFactory)
								if (pLatestUncompletedFactory->RemainingBuildTime() > 750)
									if (pLatestUncompletedFactory->CanAcceptCommand())
										return pLatestUncompletedFactory->CancelConstruction();
						}

			static frame_t lastCancel = 0;
			if (ai()->Frame() - lastCancel > 3*bw->getRemainingLatencyFrames())
			{
				MyBuilding * pBarrack = me().Buildings(Terran_Barracks).empty() ? nullptr : me().Buildings(Terran_Barracks).front().get();

				if (me().Units(Terran_Vulture).size() == 0)
					if (me().SupplyAvailable() >= 1)
						if (me().MineralsAvailable() + 3*(int)Mining::Instances().size() < 50)
							if (pBarrack && pBarrack->Completed() && pBarrack->CanAcceptCommand() && !pBarrack->Unit()->isTraining())
								if ((int)me().Units(Terran_Marine).size() < MaxMarines())
								{
									for (const auto & b : me().Buildings(Terran_Command_Center))
										if (b->Unit()->isTraining())
											if (b->TimeToTrain() > 50)
												if (b->CanAcceptCommand())
												{
												///	bw << "CancelTrain SCV" << endl;
												///	ai()->SetDelay(5000);
													lastCancel = ai()->Frame();
													return b->CancelTrain();
												}

									for (const auto & b : me().Buildings(Terran_Supply_Depot))
										if (!b->Completed())
											if (b->CanAcceptCommand())
											{
											///	bw << "CancelConstruction Depot" << endl;
											///	ai()->SetDelay(5000);
												lastCancel = ai()->Frame();
												return b->CancelConstruction();
											}
								}

				if (me().Units(Terran_Vulture).size() == 0)
					if (me().SupplyAvailable() == 0)
						if (pBarrack && pBarrack->Completed() && pBarrack->CanAcceptCommand() && !pBarrack->Unit()->isTraining())
							if ((int)me().Units(Terran_Marine).size() < MaxMarines())
								for (const auto & b : me().Buildings(Terran_Command_Center))
									if (b->Unit()->isTraining())
										if (b->CanAcceptCommand())
										{
										///	bw << "CancelTrain SCV" << endl;
										///	ai()->SetDelay(5000);
											lastCancel = ai()->Frame();
											return b->CancelTrain();
										}
			}
		}

		if (m_zerglingsNearSentry || (ai()->Frame() >= 3200))
			ChokePointDefense();

		WorkerDefense();
	}
	else
	{
//		if (me().SupplyUsed() >= 18) return Abort();
		if (me().CompletedUnits(Terran_Vulture) >= 1) return Abort();
		
		if (m_zerglings >= 1)
		{
		///	ai()->SetDelay(50);

			bool threat = true;
			for (const auto & b : me().Buildings(Terran_Factory))
				if (b->Life() > 700)
					threat = false;

			if (m_zerglings >= 5) threat = true;

			if (threat)
			{
				m_detected = true;
				m_detetedSince = ai()->Frame();
			}
			else
			{
			//	ai()->GetStrategy()->SetMinScoutingSCVs(2);
			}
		}
	}
}


} // namespace iron



