//////////////////////////////////////////////////////////////////////////
//
// 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 "my.h"
#include "production.h"
#include "../behavior/defaultBehavior.h"
#include "../behavior/mining.h"
#include "../behavior/constructing.h"
#include "../behavior/repairing.h"
#include "../behavior/harassing.h"
#include "../territory/stronghold.h"
#include "../territory/vgridMap.h"
#include "../strategy/strategy.h"
#include "../Iron.h"
#include "cc.h"
#include "depot.h"
#include "refinery.h"
#include "bunker.h"
#include "barracks.h"
#include "factory.h"
#include "shop.h"
#include "starport.h"
#include "tower.h"
#include "comsat.h"
#include "ebay.h"
#include "academy.h"
#include "armory.h"
#include "scienceFacility.h"
#include "turret.h"

namespace { auto & bw = Broodwar; }


namespace iron
{

//////////////////////////////////////////////////////////////////////////////////////////////
//                                                                                          //
//                                  class MyUnit
//                                                                                          //
//////////////////////////////////////////////////////////////////////////////////////////////


unique_ptr<MyUnit> MyUnit::Make(BWAPI::Unit u)
{
	switch (u->getType())
	{
		#define MYUNIT_MAKE_CASE(tid)   case tid: return make_unique<My<tid>>(u);
		MYUNIT_MAKE_CASE(Terran_SCV)
		MYUNIT_MAKE_CASE(Terran_Marine)
		MYUNIT_MAKE_CASE(Terran_Vulture)
		MYUNIT_MAKE_CASE(Terran_Vulture_Spider_Mine)
		MYUNIT_MAKE_CASE(Terran_Siege_Tank_Tank_Mode)
		MYUNIT_MAKE_CASE(Terran_Goliath)
		MYUNIT_MAKE_CASE(Terran_Wraith)
	}

	assert_throw_plus(false, "could not make " + u->getType().getName());
	return nullptr;
}
	

MyUnit::MyUnit(BWAPI::Unit u, unique_ptr<IBehavior> pBehavior)
	: MyBWAPIUnit(u, move(pBehavior))
{
	ai()->GetGridMap().Add(this);
}


MyUnit::~MyUnit()
{
#if !DEV
	try //3
#endif
	{
		ai()->GetGridMap().Remove(this);
	}
#if !DEV
	catch(...){} //3
#endif
}


void MyUnit::Update()
{CI(this);
	ai()->GetGridMap().Remove(this);
	MyBWAPIUnit::Update();
	ai()->GetGridMap().Add(this);

	{
		vector<HisBWAPIUnit *> HisBWAPIUnits;
		for (auto & u : him().Units())
			HisBWAPIUnits.push_back(u.get());

		for (auto & b : him().Buildings())	
			if (b->Completed())
				HisBWAPIUnits.push_back(b.get());

		const int minDistanceToRange = 10*32;
		m_FaceOffs.clear();
		for (HisBWAPIUnit * his : HisBWAPIUnits)
			//if (!(his->IsHisBuilding() && his->IsHisBuilding()->InFog()))
			{
//				if (his->IsHisBuilding() && his->IsHisBuilding()->InFog())
//					bw << "o" << endl;
				m_FaceOffs.emplace_back(this, his);
				if ((!m_FaceOffs.back().MyAttack() && !m_FaceOffs.back().HisAttack()) ||
					((m_FaceOffs.back().DistanceToMyRange() > minDistanceToRange) && (m_FaceOffs.back().DistanceToHisRange() > minDistanceToRange)))
					m_FaceOffs.pop_back();
			}
	}
/*
	{
		vector<MyBWAPIUnit *> MyBWAPIUnits;
		for (auto & u : me().Units()) 	 MyBWAPIUnits.push_back(u);
		for (auto & b : me().Buildings())	 MyBWAPIUnits.push_back(b);

		const int minDistanceToRange = 10*32;
		m_FaceOffs.clear();
		for (MyBWAPIUnit * my : MyBWAPIUnits) if (this != my)
		{
			m_FaceOffs.emplace_back(this, my);
			if ((!m_FaceOffs.back().MyAttack() && !m_FaceOffs.back().HisAttack()) ||
				((m_FaceOffs.back().DistanceToMyRange() > minDistanceToRange) && (m_FaceOffs.back().DistanceToHisRange() > minDistanceToRange)))
				m_FaceOffs.pop_back();
		}
	}
*/
}


void MyUnit::OnDangerHere()
{CI(this);
	m_lastDangerReport = ai()->Frame();
}


bool MyUnit::AllowedToChase() const
{CI(this);
	return ai()->Frame() >= m_noChaseUntil;
}


void MyUnit::SetNoChaseFor(frame_t time)
{CI(this);
	m_noChaseUntil = ai()->Frame() + time;
}

//////////////////////////////////////////////////////////////////////////////////////////////
//                                                                                          //
//                                  class MyBuilding
//                                                                                          //
//////////////////////////////////////////////////////////////////////////////////////////////


unique_ptr<MyBuilding> MyBuilding::Make(BWAPI::Unit u)
{
	switch (u->getType())
	{
		#define MYBUILDING_MAKE_CASE(tid)   case tid: return make_unique<My<tid>>(u);
		MYBUILDING_MAKE_CASE(Terran_Command_Center)
		MYBUILDING_MAKE_CASE(Terran_Refinery)
		MYBUILDING_MAKE_CASE(Terran_Bunker)
		MYBUILDING_MAKE_CASE(Terran_Supply_Depot)
		MYBUILDING_MAKE_CASE(Terran_Barracks)
		MYBUILDING_MAKE_CASE(Terran_Factory)
		MYBUILDING_MAKE_CASE(Terran_Machine_Shop)
		MYBUILDING_MAKE_CASE(Terran_Starport)
		MYBUILDING_MAKE_CASE(Terran_Control_Tower)
		MYBUILDING_MAKE_CASE(Terran_Comsat_Station)
		MYBUILDING_MAKE_CASE(Terran_Engineering_Bay)
		MYBUILDING_MAKE_CASE(Terran_Academy)
		MYBUILDING_MAKE_CASE(Terran_Armory)
		MYBUILDING_MAKE_CASE(Terran_Missile_Turret)
		MYBUILDING_MAKE_CASE(Terran_Science_Facility)
	}

	assert_throw_plus(false, "could not make " + u->getType().getName());
	return nullptr;
}


MyBuilding::MyBuilding(BWAPI::Unit u, unique_ptr<IBehavior> pBehavior)
	: MyBWAPIUnit(u, move(pBehavior)), m_size(u->getType().tileSize())
{
	if (!Flying()) PutBuildingOnTiles();

	ai()->GetGridMap().Add(this);
}


MyBuilding::~MyBuilding()
{
#if !DEV
	try //3
#endif
	{
		if (!Flying()) RemoveBuildingFromTiles();
	
		ai()->GetGridMap().Remove(this);
	}
#if !DEV
	catch(...){} //3
#endif
}


void MyBuilding::Update()
{CI(this);
	if (Type().isAddon())
		if (Unit()->getPosition() == Positions::Unknown)
			return;

	ai()->GetGridMap().Remove(this);
	MyBWAPIUnit::Update();
	ai()->GetGridMap().Add(this);

	if (JustLifted()) RemoveBuildingFromTiles();
	if (JustLanded()) PutBuildingOnTiles();

	if (ai()->Frame() % 100 == 0)
	{
		m_activity <<= 1;
		if (Unit()->isTraining()) m_activity |= 1;
	}
}


void MyBuilding::RecordTrainingExperts(Production & Prod)
{CI(this);
	for (auto & e : m_TrainingExperts)
		Prod.Record(e.get());
}


void MyBuilding::RecordResearchingExperts(Production & Prod)
{CI(this);
	for (auto & e : m_ResearchingExperts)
		Prod.Record(e.get());
}


int MyBuilding::IdlePosition() const
{CI(this);
	vector<const MyBuilding *> List;
	for (const auto & b : me().Buildings(Type()))
		List.push_back(b.get());

	sort(List.begin(), List.end(), [](const MyBuilding * a, const MyBuilding * b)
		{
			if (a->TimeToTrain() < b->TimeToTrain()) return true;
			if (a->TimeToTrain() > b->TimeToTrain()) return false;
			return a < b;
		});

	return (int)distance(List.begin(), find(List.begin(), List.end(), this));
}


int MyBuilding::Activity() const
{CI(this);

	uint_least32_t mask20bits = (uint_least32_t(1) << 20) - 1;
	return numberOfSetBits(m_activity & mask20bits) / 2;
}


void MyBuilding::Train(BWAPI::UnitType type, bool noCheck)
{CI(this);
	assert_throw(me().CanPay(type));

	bool result = Unit()->train(type);
	OnCommandSent(noCheck, result, NameWithId() + " trains " + type.getName());
}


void MyBuilding::Research(TechType type, bool noCheck)
{CI(this);
	assert_throw(me().CanPay(type));

	bool result = Unit()->research(type);
	OnCommandSent(noCheck, result, NameWithId() + " researches " + type.getName());
}


void MyBuilding::Research(UpgradeType type, bool noCheck)
{CI(this);
	assert_throw(me().CanPay(type));

	bool result = Unit()->upgrade(type);
	OnCommandSent(noCheck, result, NameWithId() + " upgrades " + type.getName());
}


void MyBuilding::BuildAddon(BWAPI::UnitType type, bool noCheck)
{CI(this);
	assert_throw(me().CanPay(type));

///	bw << NameWithId() << "builds Addon " << type << endl;
	bool result = Unit()->buildAddon(type);
	OnCommandSent(noCheck, result, NameWithId() + " builds Addon " + type.getName());
}


void MyBuilding::Lift(bool noCheck)
{CI(this);
	bool result = Unit()->lift();
	OnCommandSent(noCheck, result, NameWithId() + " lift");
}


bool MyBuilding::Land(TilePosition location, bool noCheck)
{CI(this);
	bool result = Unit()->land(location);
	OnCommandSent(noCheck, result, NameWithId() + " land");
	return result;
}


void MyBuilding::CancelConstruction(bool noCheck)
{CI(this);
///	bw << "CancelConstruction of " << NameWithId() << endl;
///	ai()->SetDelay(5000);
	bool result = Unit()->cancelConstruction();
	OnCommandSent(noCheck, result, NameWithId() + " cancelConstruction");
}


void MyBuilding::CancelTrain(bool noCheck)
{CI(this);
///	bw << "CancelTraining of " << NameWithId() << endl;
///	ai()->SetDelay(5000);
	bool result = Unit()->cancelTrain();
	OnCommandSent(noCheck, result, NameWithId() + " cancelTrain");
}


void MyBuilding::CancelResearch(bool noCheck)
{CI(this);
//	bw << "CancelResearch of " << NameWithId() << endl;
//	ai()->SetDelay(5000);
	if (Unit()->isResearching())
	{
		bool result = Unit()->cancelResearch();
		OnCommandSent(noCheck, result, NameWithId() + " cancelResearch");
	}
	else if (Unit()->isUpgrading())
	{
		bool result = Unit()->cancelUpgrade();
		OnCommandSent(noCheck, result, NameWithId() + " cancelUpgrade");
	}
	else assert_throw(false);
}


void MyBuilding::OnOtherBWAPIUnitDestroyed_v(BWAPIUnit * pOther)
{CI(this);
	if (Type().isAddon())
	{
		CHECK_POS(TopLeft() - 1);
		if (pOther == ai()->GetVMap().GetBuildingOn(ai()->GetMap().GetTile(TopLeft() - 1)))
			ConstructingThisAddonExpert()->OnMainBuildingDestroyed(pOther->IsMyBuilding());
	}
}


bool MyBuilding::DefaultBehaviorOnFrame_common()
{CI(this);
	if (double(Life()) / MaxLife() < MinLifePercentageToRepair() - 0.000001)
		if (RepairersCount() < MaxRepairers())
			if (Repairing * pRepairer = Repairing::GetRepairer(this,
						(Life()*4 > MaxLife()*3) ? 16*32 :
						(Life()*4 > MaxLife()*2) ? 32*32 :
						(Life()*4 > MaxLife()*1) ? 64*32 : 1000000))
			{
				return true;
			}

	return false;
}

	
} // namespace iron



