//////////////////////////////////////////////////////////////////////////
//
// 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 "factory.h"
#include "shop.h"
#include "production.h"
#include "army.h"
#include "../behavior/raiding.h"
#include "../behavior/exploring.h"
#include "../behavior/executing.h"
#include "../behavior/guarding.h"
#include "../behavior/razing.h"
#include "../behavior/walking.h"
#include "../behavior/scouting.h"
#include "../territory/stronghold.h"
#include "../strategy/strategy.h"
#include "../strategy/expand.h"
#include "../strategy/zerglingRush.h"
#include "../strategy/zealotRush.h"
#include "../strategy/marineRush.h"
#include "../strategy/cannonRush.h"
#include "../strategy/wraithRush.h"
#include "../Iron.h"

namespace { auto & bw = Broodwar; }


namespace iron
{

static bool enemyBunkerNearBase()
{
	const vector<const Area *> MyEnlargedAreas = me().EnlargedAreas();
	vector<const Area *> MyTerritory = MyEnlargedAreas;
	for (const Area * area : MyEnlargedAreas)
		for (const Area * neighbour : area->AccessibleNeighbours())
			push_back_if_not_found(MyTerritory, neighbour);

	for (const unique_ptr<HisBuilding> & b : him().Buildings())
		if (b->Is(Terran_Bunker))
			if (contains(MyTerritory, b->GetArea(no_check)))
				return true;

	return false;
}




//////////////////////////////////////////////////////////////////////////////////////////////
//                                                                                          //
//                                  class My<Terran_Factory>
//                                                                                          //
//////////////////////////////////////////////////////////////////////////////////////////////


template<>
class ExpertInTraining<Terran_Vulture> : public TrainingExpert
{
public:
						ExpertInTraining(MyBuilding * pWhere) : TrainingExpert(Terran_Vulture, pWhere) {}

	void				UpdateTrainingPriority() override;

private:
};


void ExpertInTraining<Terran_Vulture>::UpdateTrainingPriority()
{
//	if (me().GetBase(0)->Center().y < ai()->GetMap().Center().y) { m_priority = 0; return; }

	if (ai()->GetStrategy()->Detected<CannonRush>())
		if (me().Units(Terran_Siege_Tank_Tank_Mode).size() < 2)
			{ m_priority = 0; return; }

	if (ai()->GetStrategy()->Detected<ZerglingRush>() ||
		ai()->GetStrategy()->Detected<ZealotRush>() ||
		ai()->GetStrategy()->Detected<MarineRush>())
		{ m_priority = 620; return; }

//	if (ai()->GetStrategy()->TimeToBuildFirstShop())
//		{ m_priority = 0; return; }

	int minVulturesBeforeTanks = 5;
	if ((him().Race() == Races::Terran) && !ai()->GetStrategy()->Detected<WraithRush>())
	{
		minVulturesBeforeTanks = 7;

		if (me().HasResearched(TechTypes::Spider_Mines))
			if (count_if(me().Units(Terran_Vulture).begin(), me().Units(Terran_Vulture).end(), [](const unique_ptr<MyUnit> & u)
						{ return u->Completed() && u->IsMy<Terran_Vulture>()->RemainingMines() >= 2; }) < 4)
				minVulturesBeforeTanks = 10;
	}

	if (him().HasCannons()) minVulturesBeforeTanks = 2;

	if (Units() + Where()->IdlePosition() < minVulturesBeforeTanks)
		m_priority = (Units() < 3) ? 605 : (Units() < 5) ? 585 : 470;
	else
		m_priority = 400;
}


template<>
class ExpertInTraining<Terran_Siege_Tank_Tank_Mode> : public TrainingExpert
{
public:
						ExpertInTraining(MyBuilding * pWhere) : TrainingExpert(Terran_Siege_Tank_Tank_Mode, pWhere) {}

	void				UpdateTrainingPriority() override;

private:
};


void ExpertInTraining<Terran_Siege_Tank_Tank_Mode>::UpdateTrainingPriority()
{
//	if (me().GetBase(0)->Center().y < ai()->GetMap().Center().y) { m_priority = 0; return; }

	if (ai()->GetStrategy()->Detected<CannonRush>())
		if (Units() < 2)
			{ m_priority = 10000; return; }
	
	for (auto & u : him().Units())
		if (u->Is(Protoss_Dragoon))
			if (u->GetArea() == me().GetArea())
				{ m_priority = 610; return; }

	if (him().Race() == Races::Terran)
	{
		static bool goTank = false;
		if (!goTank)
			if (me().Buildings(Terran_Command_Center).size() >= 2)
				if (!ai()->GetStrategy()->Detected<WraithRush>())
					if (me().Army().GroundLead() || (me().Units(Terran_Vulture).size() >= 10))
						goTank = true;

		if (!goTank)
			if (!ai()->GetStrategy()->Detected<MarineRush>())
				if (enemyBunkerNearBase())
					goTank = true;

		if (!goTank) { m_priority = 0; return; }
	}

	int defensiveTanks = count_if(me().Units(Terran_Siege_Tank_Tank_Mode).begin(), me().Units(Terran_Siege_Tank_Tank_Mode).end(),
		[](const unique_ptr<MyUnit> & u){ return u->GetStronghold(); });

	if (him().MayReaver())
		if (defensiveTanks < 2)
			{ m_priority = 410; return; }
	
	if ((me().Production().GasAvailable() >= Cost(Terran_Siege_Tank_Tank_Mode).Gas()*2/4) &&
		(Units()*10 + Where()->IdlePosition() < me().Army().TankRatioWanted() *
													(int)(me().Units(Terran_Vulture).size() +
														  me().Units(Terran_Siege_Tank_Tank_Mode).size() +
														  me().Units(Terran_Goliath).size())))
		m_priority = 410;
	else
		m_priority = 390;
}


template<>
class ExpertInTraining<Terran_Goliath> : public TrainingExpert
{
public:
						ExpertInTraining(MyBuilding * pWhere) : TrainingExpert(Terran_Goliath, pWhere) {}

	void				UpdateTrainingPriority() override;

private:
};


void ExpertInTraining<Terran_Goliath>::UpdateTrainingPriority()
{
	if (Units()*10 + Where()->IdlePosition() < me().Army().GoliathRatioWanted() *
													(int)(me().Units(Terran_Vulture).size() +
														  me().Units(Terran_Siege_Tank_Tank_Mode).size() +
														  me().Units(Terran_Goliath).size()))
		m_priority = 410;
	else
		m_priority = 390;
}


template<>
class ExpertInConstructing<Terran_Factory> : public ConstructingExpert
{
public:
						ExpertInConstructing() : ConstructingExpert(Terran_Factory) {}

	void				UpdateConstructingPriority() override;

private:
};


void ExpertInConstructing<Terran_Factory>::UpdateConstructingPriority()
{
	if (me().CompletedBuildings(Terran_Barracks) == 0) { m_priority = 0; return; }
	if (me().CompletedBuildings(Terran_Refinery) == 0) { m_priority = 0; return; }
	
	if (Builders() < BuildingsUncompleted()) { m_priority = 590; return; }

	if (auto s = ai()->GetStrategy()->Detected<ZerglingRush>())	if (!s->TechRestartingCondition())	{ m_priority = 0; return; }
	if (auto s = ai()->GetStrategy()->Detected<ZealotRush>())	if (!s->TechRestartingCondition())	{ m_priority = 0; return; }
	if (ai()->GetStrategy()->Detected<MarineRush>())				if (Buildings() >= 1)				{ m_priority = 0; return; }
	if (ai()->GetStrategy()->Detected<CannonRush>())				if (Buildings() >= 1)				{ m_priority = 0; return; }


	if (Buildings() == 0)
	{
		if ((me().SupplyUsed() >= 16) && (me().GasAvailable() > 75) &&
			(!ai()->GetStrategy()->Detected<ZerglingRush>() || (me().MineralsAvailable() > 150)))	m_priority = 610;
		else															m_priority = 590;
		return;
	}

	if (Buildings() == 1)
	{
		if ((me().SupplyUsed() >= 18) && (me().GasAvailable() > 75) &&
			(!ai()->GetStrategy()->Detected<ZerglingRush>() || (me().MineralsAvailable() > 150)))	m_priority = 610;
		else															m_priority = 590;
		return;
	}


	const int mechProductionSites = me().Buildings(Terran_Factory).size() + me().Buildings(Terran_Starport).size();
/*
	if (him().Race() == Races::Protoss)
		if (mechProductionSites < 3)
			if ((me().Bases().size() == 1) && !ai()->GetStrategy()->Active<Expand>())
				if (me().CompletedBuildings(Terran_Machine_Shop) >= 2)
					{ m_priority = 290; return; }
*/

	const int activeBases = count_if(me().Bases().begin(), me().Bases().end(), [](VBase * b){ return b->Active(); });
//	const int lowerBound = min(12, 2*activeBases);
	const int upperBound = min(12, 3*activeBases);

	if (me().Buildings(Terran_Command_Center).size() >= 2)
		if (mechProductionSites < upperBound)
		{

/*
			if (mechProductionSites < lowerBound-1)	{ m_priority = min(500, 390 + elapsed/10); return; }
			if (mechProductionSites < lowerBound)	{ m_priority = min(500, 290 + elapsed/20); return; }
			if (mechProductionSites < upperBound)	{ m_priority = 200 + 10*(upperBound - mechProductionSites); return; }
*/
			m_priority = 50 * me().FactoryActivity();

			if (me().SupplyUsed() >= 195)
			{
				static map<int, frame_t> startingFrameByNumberOfBases;
				if (startingFrameByNumberOfBases[activeBases] == 0)
					startingFrameByNumberOfBases[activeBases] = ai()->Frame();
				frame_t elapsed = ai()->Frame() - startingFrameByNumberOfBases[activeBases];

				m_priority = max(m_priority, min(500, 290 + elapsed/20));
			}

			return;
		}

	m_priority = 0;
}


ExpertInConstructing<Terran_Factory>	My<Terran_Factory>::m_ConstructingExpert;

ConstructingExpert * My<Terran_Factory>::GetConstructingExpert() { return &m_ConstructingExpert; }

vector<ConstructingAddonExpert *> My<Terran_Factory>::m_ConstructingAddonExperts {My<Terran_Machine_Shop>::GetConstructingAddonExpert()};


My<Terran_Factory>::My(BWAPI::Unit u)
	: MyBuilding(u, make_unique<DefaultBehavior>(this))
{
	assert_throw(u->getType() == Terran_Factory);

	AddTrainingExpert<Terran_Vulture>();
	AddTrainingExpert<Terran_Siege_Tank_Tank_Mode>();
	AddTrainingExpert<Terran_Goliath>();

	m_ConstructingExpert.OnBuildingCreated();
}




void My<Terran_Factory>::DefaultBehaviorOnFrame()
{CI(this);
	if (DefaultBehaviorOnFrame_common()) return;
}

//////////////////////////////////////////////////////////////////////////////////////////////
//                                                                                          //
//                                  class My<Terran_Vulture>
//                                                                                          //
//////////////////////////////////////////////////////////////////////////////////////////////

My<Terran_Vulture>::My(BWAPI::Unit u)
	: MyUnit(u, make_unique<DefaultBehavior>(this))
{
	assert_throw(u->getType() == Terran_Vulture);
}


void My<Terran_Vulture>::PlaceMine(Position pos, bool noCheck)
{CI(this);
	assert_throw(me().HasResearched(TechTypes::Spider_Mines));
	assert_throw(RemainingMines() >= 1);
///	bw << NameWithId() << " place mine at" << pos << "!" << endl;
	bool result = Unit()->useTech(TechTypes::Spider_Mines, pos);
	OnCommandSent(noCheck, result, NameWithId() + " place mine at " + my_to_string(pos));
}


bool My<Terran_Vulture>::WorthBeingRepaired() const
{
	if (Life() < 50)
//		if (me().Units(Terran_Vulture).size() > 3)
			if (me().HasResearched(TechTypes::Spider_Mines))
				if (RemainingMines() <= 1)
					return false;

	return true;
}


void My<Terran_Vulture>::DefaultBehaviorOnFrame()
{CI(this);
//	return ChangeBehavior<Walking>(this, Pos(), __FILE__ + to_string(__LINE__));
//	return ChangeBehavior<Walking>(this, ai()->GetMap().Center(), __FILE__ + to_string(__LINE__));

	if (him().StartingBase())
	{
		if (him().StartingBase()->BWEMPart()->GetArea()->AccessibleFrom(GetArea()))
		{
			if (!him().StartingBaseDestroyed())
			{
			///	bw << NameWithId() << " go to his base!" << endl; ai()->SetDelay(2000);
				return ChangeBehavior<Raiding>(this, him().StartingBase()->BWEMPart()->Center());
			}
			else
			{
				return ChangeBehavior<Exploring>(this, GetArea());
			}
		}
	}
	else
	{
	///	bw << NameWithId() << " go to his base!" << endl; ai()->SetDelay(2000);
		return ChangeBehavior<Scouting>(this);
	}
}



//////////////////////////////////////////////////////////////////////////////////////////////
//                                                                                          //
//                                  class My<Terran_Siege_Tank_Tank_Mode>
//                                                                                          //
//////////////////////////////////////////////////////////////////////////////////////////////

My<Terran_Siege_Tank_Tank_Mode>::My(BWAPI::Unit u)
	: MyUnit(u, make_unique<DefaultBehavior>(this))
{
	assert_throw(u->getType() == Terran_Siege_Tank_Tank_Mode);
}



void My<Terran_Siege_Tank_Tank_Mode>::DefaultBehaviorOnFrame()
{CI(this);
//	return ChangeBehavior<Walking>(this, Pos(), __FILE__ + to_string(__LINE__));
//	return ChangeBehavior<Walking>(this, ai()->GetMap().Center(), __FILE__ + to_string(__LINE__));
//	return ChangeBehavior<Executing>(this);

	if (him().StartingBase())
	{
		if (him().StartingBase()->BWEMPart()->GetArea()->AccessibleFrom(GetArea()))
		{
			if (him().MayReaver())
				if (GetStronghold())
				{
					const Base * base = GetStronghold()->HasBase()->BWEMPart();
					int defensiveTanks = count_if(me().Units(Terran_Siege_Tank_Tank_Mode).begin(), me().Units(Terran_Siege_Tank_Tank_Mode).end(),
						[this](const unique_ptr<MyUnit> & u){ return u->GetStronghold() == GetStronghold(); });

					if (defensiveTanks <= 2)
						return ChangeBehavior<Guarding>(this, base);
				}

			if (ai()->GetStrategy()->Detected<CannonRush>() && Razing::Condition(this))
				return ChangeBehavior<Razing>(this, GetArea());

			if (!him().StartingBaseDestroyed())
			{
			///	bw << NameWithId() << " go to his base!" << endl; ai()->SetDelay(2000);
				return ChangeBehavior<Raiding>(this, him().StartingBase()->BWEMPart()->Center());
			}
			else
			{
				return ChangeBehavior<Exploring>(this, GetArea());
			}
		}
	}
	else
	{
	///	bw << NameWithId() << " go to his base!" << endl; ai()->SetDelay(2000);
		return ChangeBehavior<Scouting>(this);
	}
}


int My<Terran_Siege_Tank_Tank_Mode>::CanSiegeAttack(const BWAPIUnit * u) const
{CI(this);
	// from UnitInterface::isInWeaponRange:

    int minRange = 2*32;
    int maxRange = 12*32;

    int distance = GetDistanceToTarget(u);

    if ((minRange ? minRange < distance : true) && distance <= maxRange)
		return max(distance, 1);

	return 0;
}


void My<Terran_Siege_Tank_Tank_Mode>::Siege(bool noCheck)
{CI(this);
	assert_throw(Is(Terran_Siege_Tank_Tank_Mode));
///	bw << NameWithId() + " sieges" << endl;
	bool result = Unit()->siege();
	OnCommandSent(noCheck, result, NameWithId() + " siege");
}


void My<Terran_Siege_Tank_Tank_Mode>::Unsiege(bool noCheck)
{CI(this);
	assert_throw(Is(Terran_Siege_Tank_Siege_Mode));
///	bw << NameWithId() + " unsieges" << endl;
	bool result = Unit()->unsiege();
	OnCommandSent(noCheck, result, NameWithId() + " unsiege");
}


//////////////////////////////////////////////////////////////////////////////////////////////
//                                                                                          //
//                                  class My<Terran_Vulture_Spider_Mine>
//                                                                                          //
//////////////////////////////////////////////////////////////////////////////////////////////

My<Terran_Vulture_Spider_Mine>::My(BWAPI::Unit u)
	: MyUnit(u, make_unique<DefaultBehavior>(this))
{
	assert_throw(u->getType() == Terran_Vulture_Spider_Mine);

	TilePosition t(u->getPosition());
	t.x -= t.x % 3;
	t.y -= t.y % 3;
	CHECK_POS(t);
	ai()->GetVMap().PutMine(ai()->GetMap().GetTile(t));
}

My<Terran_Vulture_Spider_Mine>::~My()
{
	TilePosition t(Pos());
	t.x -= t.x % 3;
	t.y -= t.y % 3;
	CHECK_POS(t);
	ai()->GetVMap().RemoveMine(ai()->GetMap().GetTile(t));
}


void My<Terran_Vulture_Spider_Mine>::DefaultBehaviorOnFrame()
{CI(this);
	m_pTarget = nullptr;
	m_dangerous = false;

	if (Unit()->isBurrowed())
		m_ready = true;
	
	if (m_ready)
	{
		int minDist = GroundRange() + 2*32;
		for (const auto & faceOff : FaceOffs())
			if (faceOff.His()->IsHisUnit())
				if (faceOff.MyAttack())
					if (faceOff.GroundDistanceToHitHim() < minDist)
					{
						minDist = faceOff.GroundDistanceToHitHim();
						m_pTarget = &faceOff;
					}

		if (m_pTarget)
			if ((minDist < GroundRange()) ||
				(minDist < GroundRange() + 8) && !Unit()->isBurrowed())
			{
				m_dangerous = true;
			///	ai()->SetDelay(50);
			///	bw << NameWithId() << " dangerous !!!" << endl;

				for (const auto & u : me().Units())
					if (u->Completed())
					if (u->CanAcceptCommand())
						if (!u->Flying())
							if (!u->Is(Terran_Siege_Tank_Siege_Mode))
							if (!u->GetBehavior()->IsConstructing())
								if (groundDist(u->Pos(), Pos()) < 12*32)
								{
									Vect V = toVect(u->Pos() - m_pTarget->His()->Pos());
									V.Normalize();
									Position delta = toPosition(V * 2*32);
									Position dest = ai()->GetMap().Crop(u->Pos() + delta);
#if DEV
									drawLineMap(u->Pos(), dest, Colors::Yellow, crop);//1
#endif
									u->Move(dest);
								}
			}
	}


#if DEV
	if (m_pTarget)
	{
	//	ai()->SetDelay(500);
		bw->drawLineMap(Pos(), m_pTarget->His()->Pos(), Colors::Orange);
		m_pTarget->His()->IsHisUnit()->AddMineTargetingThis(Pos());
	}
#endif

}



//////////////////////////////////////////////////////////////////////////////////////////////
//                                                                                          //
//                                  class My<Terran_Goliath>
//                                                                                          //
//////////////////////////////////////////////////////////////////////////////////////////////

My<Terran_Goliath>::My(BWAPI::Unit u)
	: MyUnit(u, make_unique<DefaultBehavior>(this))
{
	assert_throw(u->getType() == Terran_Goliath);
}


void My<Terran_Goliath>::DefaultBehaviorOnFrame()
{CI(this);
//	return ChangeBehavior<Walking>(this, Pos(), __FILE__ + to_string(__LINE__));
//	return ChangeBehavior<Walking>(this, ai()->GetMap().Center(), __FILE__ + to_string(__LINE__));
//	return ChangeBehavior<Executing>(this);

	if (him().StartingBase())
	{
		if (him().StartingBase()->BWEMPart()->GetArea()->AccessibleFrom(GetArea()))
		{
			if (!him().StartingBaseDestroyed())
			{
			///	bw << NameWithId() << " go to his base!" << endl; ai()->SetDelay(2000);
				return ChangeBehavior<Raiding>(this, him().StartingBase()->BWEMPart()->Center());
			}
			else
			{
				return ChangeBehavior<Exploring>(this, GetArea());
			}
		}
	}
	else
	{
	///	bw << NameWithId() << " go to his base!" << endl; ai()->SetDelay(2000);
		return ChangeBehavior<Scouting>(this);
	}
}




	
} // namespace iron



