//////////////////////////////////////////////////////////////////////////
//
// 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 "strategy.h"
#include "firstBarracksPlacement.h"
#include "firstFactoryPlacement.h"
#include "groupAttack.h"
#include "groupAttackSCV.h"
#include "mineAttack.h"
#include "wraithAttack.h"
#include "scan.h"
#include "enemyScout.h"
#include "zerglingRush.h"
#include "zealotRush.h"
#include "marineRush.h"
#include "dragoonRush.h"
#include "cannonRush.h"
#include "wraithRush.h"
#include "freeTurrets.h"
#include "earlyRunBy.h"
#include "unblockTraffic.h"
#include "patrolBases.h"
#include "mineSpots.h"
#include "baseDefense.h"
#include "killMines.h"
#include "watchArmy.h"
#include "expand.h"
#include "../units/cc.h"
#include "../units/fight.h"
#include "../units/production.h"
#include "../behavior/scouting.h"
#include "../behavior/mining.h"
#include "../behavior/refining.h"
#include "../behavior/supplementing.h"
#include "../behavior/constructing.h"
#include "../behavior/harassing.h"
#include "../behavior/exploring.h"
#include "../behavior/executing.h"
#include "../behavior/fleeing.h"
#include "../behavior/chasing.h"
#include "../behavior/laying.h"
#include "../behavior/vchasing.h"
#include "../behavior/defaultBehavior.h"
#include "../Iron.h"
#include <numeric>

namespace { auto & bw = Broodwar; }




namespace iron
{


//////////////////////////////////////////////////////////////////////////////////////////////
//                                                                                          //
//                                  class Strategy
//                                                                                          //
//////////////////////////////////////////////////////////////////////////////////////////////


Strategy::Strategy()
: m_HisPossibleLocations(ai()->GetMap().StartingLocations())
{
	m_Strats.push_back(make_unique<FirstBarracksPlacement>());
	m_Strats.push_back(make_unique<FirstFactoryPlacement>());
	m_Strats.push_back(make_unique<EnemyScout>());
	m_Strats.push_back(make_unique<ZerglingRush>());
	m_Strats.push_back(make_unique<ZealotRush>());
	m_Strats.push_back(make_unique<MarineRush>());
	m_Strats.push_back(make_unique<DragoonRush>());
	m_Strats.push_back(make_unique<CannonRush>());
	m_Strats.push_back(make_unique<WraithRush>());
	m_Strats.push_back(make_unique<EarlyRunBy>());
	m_Strats.push_back(make_unique<FreeTurrets>());
	m_Strats.push_back(make_unique<UnblockTraffic>());
	m_Strats.push_back(make_unique<PatrolBases>());
	m_Strats.push_back(make_unique<Expand>());
	m_Strats.push_back(make_unique<MineSpots>());
	m_Strats.push_back(make_unique<KillMines>());
	m_Strats.push_back(make_unique<BaseDefense>());
	m_Strats.push_back(make_unique<WatchArmy>());

	const TilePosition myStartingPos = me().Player()->getStartLocation();

	swap(m_HisPossibleLocations.front(), *find(m_HisPossibleLocations.begin(), m_HisPossibleLocations.end(), myStartingPos));
	assert_throw(m_HisPossibleLocations.front() == myStartingPos);

	const Area * pMyStartingArea = ai()->GetMap().GetArea(myStartingPos);
	really_remove_if(m_HisPossibleLocations, [pMyStartingArea](TilePosition t)
		{ return !ai()->GetMap().GetArea(t)->AccessibleFrom(pMyStartingArea); });

	// sorts m_HisPossibleLocations, making each element the nearest one from the previous one
	for (int i = 1 ; i < (int)m_HisPossibleLocations.size() ; ++i)
	{
		TilePosition lastPos = m_HisPossibleLocations[i-1];
		for (int j = i+1 ; j < (int)m_HisPossibleLocations.size() ; ++j)
		{
			int groundDist_lastPos_i;
			int groundDist_lastPos_j;
			ai()->GetMap().GetPath(Position(lastPos), Position(m_HisPossibleLocations[i]), &groundDist_lastPos_i);
			ai()->GetMap().GetPath(Position(lastPos), Position(m_HisPossibleLocations[j]), &groundDist_lastPos_j);
			if (groundDist_lastPos_j < groundDist_lastPos_i)
				swap(m_HisPossibleLocations[i], m_HisPossibleLocations[j]);
		}
	}

	really_remove(m_HisPossibleLocations, myStartingPos);

	//	assert_throw_plus(!m_HisPossibleLocations.empty(), "enemy must be accessible");
	if (m_HisPossibleLocations.empty()) him().SetNotAccessible();
}


void Strategy::RemovePossibleLocation(TilePosition pos)
{
	assert_throw(contains(m_HisPossibleLocations, pos));
	really_remove(m_HisPossibleLocations, pos);
}


void Strategy::OnBWAPIUnitDestroyed(BWAPIUnit * pBWAPIUnit)
{
	for (auto & strat : m_Strats)
		strat->OnBWAPIUnitDestroyed(pBWAPIUnit);
}


bool Strategy::TimeToBuildFirstShop() const
{
	if (me().Buildings(Terran_Machine_Shop).size() >= 1) return false;

	const int minVulturesBeforeShops = (him().Race() == Races::Protoss) ? 0 : 3;
	if ((int)me().Units(Terran_Vulture).size() < minVulturesBeforeShops)
		return false;

	return true;
}


bool Strategy::RessourceDispatch()
{
	assert_throw(!me().Bases().empty());
	const VBase * main = me().GetVBase(0);

	int currentRefiners = Refining::Instances().size();

	int maxRefiners = 0;
	for (const VBase * base : me().Bases())
		maxRefiners += base->MaxRefiners();

	int wantedRefiners = currentRefiners;

	// TODO : limit
	if ((me().Buildings(Terran_Factory).size() <= 1) && (me().GasAvailable() < 140) && (me().SCVworkers() >= 13))
	{
		wantedRefiners = main->MaxRefiners();
		if (me().Buildings(Terran_Factory).size() == 1)
			wantedRefiners = min(2, wantedRefiners);
	}
	else
	{
		const bool useAvailableRatio = max(me().Production().MineralsAvailable(), me().Production().GasAvailable()) >= 100;
		const double g = 0.5;
		const double ratio1 = useAvailableRatio ? me().Production().AvailableRatio() : 1.0;

		if		(ratio1 < 1*g) { wantedRefiners = max(0, currentRefiners - 1); }//bw << ratio1 << " < " << 1*g << " --> " << wantedRefiners << endl; }
		else if (ratio1 > 1/g) { wantedRefiners = min(maxRefiners, currentRefiners + 1); }//bw << ratio1 << " > " << 1/g << " --> " << wantedRefiners << endl; }
		else
		{
			const double h = 0.8;
			const double ratio2 = gatheringRatio();

			if		(ratio2 < 5*h) { wantedRefiners = max(0, currentRefiners - 1); }//bw << ratio2 << " < " << 5*h << " --> " << wantedRefiners << endl; }
			else if (ratio2 > 5/h) { wantedRefiners = min(maxRefiners, currentRefiners + 1); }//bw << ratio2 << " > " << 5/h << " --> " << wantedRefiners << endl; }
		}
	}

	if (auto s = ai()->GetStrategy()->Detected<MarineRush>())
		if (!((me().Buildings(Terran_Factory).size() == 0) && (me().GasAvailable() < 100)))
			wantedRefiners = 0;

	if (auto s = ai()->GetStrategy()->Detected<ZerglingRush>())
		if (!s->TechRestartingCondition())
			wantedRefiners = 0;

	if (auto s = ai()->GetStrategy()->Detected<ZealotRush>())
		if (!s->TechRestartingCondition())
			wantedRefiners = 0;

	if (currentRefiners < wantedRefiners)
	{
		const VBase * pBestBase = nullptr;
		int maxGasAmount = numeric_limits<int>::min();
		for (const VBase * base : me().Bases())
			if (base->LackingRefiners() > 0)
				if (!base->Miners().empty())
					if (base->GasAmount() > maxGasAmount)
					{
						maxGasAmount = base->GasAmount();
						pBestBase = base;
					}

		if (pBestBase)
			for (Mining * m : pBestBase->Miners())
				if (m->MovingTowardsMineral())
				{
					m->Agent()->ChangeBehavior<Refining>(m->Agent());
					return true;
				}
	}
	else if (currentRefiners > wantedRefiners)
	{
		const VBase * pBestBase = nullptr;
		int minGasAmount = numeric_limits<int>::max();
		for (const VBase * base : me().Bases())
			if (base->LackingMiners() > 0)
				if (!base->Refiners().empty())
					if (base->GasAmount() < minGasAmount)
					{
						minGasAmount = base->GasAmount();
						pBestBase = base;
					}

		if (pBestBase)
			for (Refining * r : pBestBase->Refiners())
				if (r->MovingTowardsRefinery())
				{
					r->Agent()->ChangeBehavior<Mining>(r->Agent());
					return true;
				}
	}

	return false;
}


bool Strategy::MiningDispatch()
{
	if (me().Bases().size() < 2) return false;

	const VBase * pLowestRateBase = nullptr;
	const VBase * pHighestRateBase = nullptr;
	int minLackingMiners = numeric_limits<int>::max();
	int maxLackingMiners = numeric_limits<int>::min();

	for (const VBase * base : me().Bases())
		if (base->Active())
		{
			if (base->LackingMiners() < minLackingMiners)
			{
				minLackingMiners = base->LackingMiners();
				pHighestRateBase = base;
			}

			if (base->LackingMiners() > maxLackingMiners)
				if (base->OtherSCVs() < 3)
				{
					maxLackingMiners = base->LackingMiners();
					pLowestRateBase = base;
				}
		}

	if (pLowestRateBase && pHighestRateBase)
		if (pLowestRateBase != pHighestRateBase)
			if (maxLackingMiners >= 3)
				if (maxLackingMiners - minLackingMiners >= 2)
					for (Mining * m : pHighestRateBase->Miners())
						if (m->MovingTowardsMineral())
						{
							My<Terran_SCV> * pSCV = m->Agent();
							pSCV->ChangeBehavior<Exploring>(pSCV, pLowestRateBase->GetArea()->BWEMPart());
							pSCV->LeaveStronghold();
							pSCV->EnterStronghold(pLowestRateBase->GetStronghold());
							pSCV->ChangeBehavior<Mining>(pSCV);
							return true;
						}

	return false;
}


static void cancelFreeTurrets()
{
	if (MyBuilding * b = me().FindBuildingNeedingBuilder(Terran_Missile_Turret, !bool("inStrongHold")))
		if (b->CanAcceptCommand())
		{
		///	ai()->SetDelay(5000);
		///	bw << "CancelConstruction of " << b->NameWithId() << endl;
			b->CancelConstruction();
		}
}


void Strategy::OnFrame(bool preBehavior)
{
	for (auto & strat : m_Strats)
		if (preBehavior == strat->PreBehavior())
			strat->OnFrame();

	really_remove_if(m_Strats, [](const unique_ptr<Strat> & strat){ return strat->ToRemove(); });


	if (preBehavior) return;

/*
	static const int N = 500000;// + 5*(rand()%100);
	if (him().Accessible())
		if (ai()->Frame() == N)
		{
			for (int i = 0 ; i < 1 ; ++i)
			{
				auto * pScout = me().Units(Terran_SCV)[i]->As<Terran_SCV>();
				pScout->ChangeBehavior<Scouting>(pScout);
			}
		}
*/



//	bw << ai()->Frame() << ") " << bw->getLatencyFrames() << " ; " << bw->getRemainingLatencyFrames() << endl;

//	DO_ONCE	bw << me().Player()->damage(UnitType(Terran_Vulture_Spider_Mine).groundWeapon()) << endl;



	// Initial scout
	if (!him().StartingBase() || (Scouting::InstancesCreated() == 0) || ai()->GetStrategy()->Detected<CannonRush>())
		if (me().Buildings(Terran_Barracks).size() >= 1)
			if (me().SCVsoldiers() < m_minScoutingSCVs)
				if (My<Terran_SCV> * pWorker = findFreeWorker(me().GetVBase(0)))
					return pWorker->ChangeBehavior<Scouting>(pWorker);


	groupAttackSCV();


	groupAttack();


	mineAttack();


	wraithAttack();


	scan();


	cancelFreeTurrets();


	for (VBase * base : me().Bases())
		if (base->CheckSupplementerAssignment())
			return;


	if (RessourceDispatch()) return;


	if (MiningDispatch()) return;

/*
	if (ai()->Delay() == 1000)
		for (const auto & u : me().Units(Terran_Vulture))
			if (u->Completed())
				if (u->Unit()->getSpiderMineCount() > 0)
					if (u->Pos() != u->PrevPos(30))
						if (!u->GetBehavior()->IsLaying())
							return u->ChangeBehavior<Laying>(u.get());
*/
/*
	if (ai()->Delay() == 1000)
		for (const auto & u : me().Units(Terran_Vulture))
			if (u->Completed())
				if (u->Pos() != u->PrevPos(30))
					if (!u->GetBehavior()->IsExecuting())
						return u->ChangeBehavior<Executing>(u.get());
*/
/*
	DO_ONCE
	{
		Timer t;
		FightSim fight;
		fight.AddMyUnit(Terran_Vulture, 22, 0);
		fight.AddMyUnit(Terran_Vulture, 44, 0);
		fight.AddMyUnit(Terran_Vulture, 58, 0);
		fight.AddMyUnit(Terran_Vulture, 58, 0);
//		fight.AddMyUnit(Terran_Vulture, 80, 0);


//		fight.AddHisUnit(Terran_Vulture, 80, 0);
//		fight.AddHisUnit(Terran_Vulture, 80, 0);
//		fight.AddHisUnit(Terran_Goliath, 125, 0);
//		fight.AddHisUnit(Terran_Goliath, 125, 0);
		fight.AddHisUnit(Terran_Siege_Tank_Tank_Mode, 79, 0);
//		fight.AddHisUnit(Terran_Siege_Tank_Tank_Mode, 150, 0);
		fight.Eval();
		bw << t.ElapsedMilliseconds() << endl;
	}
*/
}


} // namespace iron



