//////////////////////////////////////////////////////////////////////////
//
// 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 "cc.h"
#include "refinery.h"
#include "../behavior/defaultBehavior.h"
#include "../behavior/scouting.h"
#include "../behavior/mining.h"
#include "../behavior/refining.h"
#include "../behavior/supplementing.h"
#include "../behavior/exploring.h"
#include "../behavior/harassing.h"
#include "../territory/stronghold.h"
#include "../strategy/strategy.h"
#include "../strategy/expand.h"
#include "../Iron.h"

namespace { auto & bw = Broodwar; }


namespace iron
{



//////////////////////////////////////////////////////////////////////////////////////////////
//                                                                                          //
//                                  class My<Terran_Command_Center>
//                                                                                          //
//////////////////////////////////////////////////////////////////////////////////////////////


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

	void				UpdateTrainingPriority() override;

private:
};




void ExpertInTraining<Terran_SCV>::UpdateTrainingPriority()
{
	assert_throw(Where()->GetStronghold());
	VBase * base = Where()->GetStronghold()->HasBase();

	if (Units() > 100) { m_priority = 0; return; }

	int k = Where()->Unit()->isTraining() ? 1 : 0;


	if ((base->LackingMiners() > k) || (base->LackingRefiners() > k) || ((int)base->Supplementers().size() + k == 0))
	{
		if (me().ExceedingSCVsoldiers() <= 3)
			m_priority = 600;//me().Army().TerritoryCond() ? 600 : 
		else
			m_priority = 0;
	}
	else
	{
		if (me().LackingSCVsoldiers() > 0)
		{
			m_priority = 380;
		}
		else
			m_priority = 0;
	}

	// SCV are cheap, so we clear the priority if the CC is still training for some time.
	if (m_priority > 0)
		if (me().SupplyAvailable() >= 2)
		{
			double miningCapacity = double(Mining::Instances().size()) / 4 / (1 + Where()->IdlePosition());
			int timeNeeded = int(1.25 * UnitType(Terran_SCV).buildTime() / miningCapacity);
		///	bw << timeNeeded << endl;
			if (Where()->TimeToTrain() > timeNeeded)
				m_priority = 0;
		}

}


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

	void				UpdateConstructingPriority() override;

private:
};


void ExpertInConstructing<Terran_Command_Center>::UpdateConstructingPriority()
{
	m_priority = 0;

	if (Builders() < BuildingsUncompleted()) { m_priority = 590; return; }

	if (ai()->GetStrategy()->Active<Expand>())
		if (me().Bases().back()->NeverBuiltYet())
		{
			m_priority = 450;
			return;
		}

	for (VBase * base : me().Bases())
		if (base->Lost())
			if (base->ShouldRebuild())
			{
				m_priority = 440;
				return;
			}
}


ExpertInConstructing<Terran_Command_Center>	My<Terran_Command_Center>::m_ConstructingExpert;

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


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

	AddTrainingExpert<Terran_SCV>();

	m_ConstructingExpert.OnBuildingCreated();
}


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

	if (double(Life()) / MaxLife() < 0.60)
	{
		const bool underAttack = Life() < PrevLife(63);
		const Base * pMyStartingBase = me().GetBase(0);

		if (Flying())
		{
			if (underAttack)
			{
				if (!Unit()->isMoving())
					return Move(ai()->GetVMap().RandomSeaPosition());
			}
			else
				if (ai()->Frame() % 10 == 0)
					if (!Land(pMyStartingBase->Location(), no_check))
						for (MyUnit * u : me().Units())
							if (u->Completed())
								if (u->CanAcceptCommand())
									if (u->Pos().getApproxDistance(pMyStartingBase->Center()) < 3*32)
										u->Move(ai()->GetVMap().RandomPosition(Position(pMyStartingBase->GetArea()->Top()), 5*32));

			return;
		}
		else
		{
			if (underAttack)
				if (TopLeft() == pMyStartingBase->Location())
					return Lift(no_check);
		}
	}
}




//////////////////////////////////////////////////////////////////////////////////////////////
//                                                                                          //
//                                  class My<Terran_SCV>
//                                                                                          //
//////////////////////////////////////////////////////////////////////////////////////////////

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


void My<Terran_SCV>::DefaultBehaviorOnFrame()
{CI(this);
//	const int countSCVatHome = count_if(me().Units(Terran_SCV).begin(), me().Units(Terran_SCV).end(),
//		[](const unique_ptr<MyUnit> & u){ return u->GetArea() == me().GetArea(); });

//	if ((GetArea() == base->BWEMPart()->GetArea()) || ((int)Mining::Instances().size() < 4))
	if (GetStronghold())
	{
		auto * base = GetStronghold()->HasBase();

		if (!base->Active())
			return ChangeBehavior<Exploring>(this, base->GetArea()->BWEMPart());

		if (base->LackingMiners() > 0)
//		if ((GetArea() == me().GetArea()) ||						// SCVs at home should mine by default
//			(Mining::Instances().empty() && (countSCVatHome == 0)))		// SCVs not at home are sent to mine only if no miner exist and no SCV is at home (SCV at home will mine soon or later).
			return ChangeBehavior<Mining>(this);

		if (base->LackingRefiners() > 0)
			return ChangeBehavior<Refining>(this);

		if (base->LackingSupplementers() > 0)
		{
			if (me().LackingSCVsoldiers() <= 0)
				return ChangeBehavior<Supplementing>(this);
		}
	}
	else if (!SoldierForever())
		for (const VBase * base : me().Bases())
			if (base->Active())
			{
				int k = base->GetCC()->Unit()->isTraining() ? 1 : 0;
				if ((base->LackingMiners() > k) || (base->LackingRefiners() > k) || ((int)base->Supplementers().size() + k == 0))
					if ((Mining::Instances().size() < 2) || (me().ExceedingSCVsoldiers() > 3))
					{
					///	bw << NameWithId() << " go back to CC" << endl;
						EnterStronghold(base->GetStronghold());
						return ChangeBehavior<Supplementing>(this);
					}
			}

	//Move(ai()->GetMap().RandomPosition());
	return ChangeBehavior<Scouting>(this);

//	if (him().Accessible())
//		return ChangeBehavior<Harassing>(this);
}


void My<Terran_SCV>::Gather(Mineral * m, bool noCheck)
{CI(this);
///	bw << ai()->Frame() << ") " << NameWithId() + " gather Mineral #" << m->Unit()->getID() << endl;
	bool result = Unit()->gather(m->Unit());
	OnCommandSent(noCheck, result, NameWithId() + " gather " + "Mineral #" + to_string(m->Unit()->getID()));
}


void My<Terran_SCV>::Gather(Geyser * g, bool noCheck)
{CI(this);
	My<Terran_Refinery> * pRefinery = static_cast<My<Terran_Refinery> *>(g->Ext());
	assert_throw(pRefinery);
	bool result = Unit()->gather(pRefinery->Unit());
	OnCommandSent(noCheck, result, NameWithId() + " gather " + "Refinery #" + to_string(pRefinery->Unit()->getID()));
}


void My<Terran_SCV>::ReturnCargo(bool noCheck)
{CI(this);
	bool result = Unit()->returnCargo();
	OnCommandSent(noCheck, result, NameWithId() + " returnCargo");
}


void My<Terran_SCV>::Repair(MyBWAPIUnit * target, bool noCheck)
{CI(this);
///	bw << NameWithId() + " repair " + target->NameWithId() << endl;
	bool result = Unit()->repair(target->Unit());
	OnCommandSent(noCheck, result, NameWithId() + " repair " + target->NameWithId());
}


bool My<Terran_SCV>::Build(BWAPI::UnitType type, TilePosition location, bool noCheck)
{CI(this);
///	bw << NameWithId() + " builds " + type.getName() << endl;
	bool result = Unit()->build(type, location);
	OnCommandSent(noCheck, result, NameWithId() + " build " + type.getName());
	return result;
}


	
} // namespace iron



