//////////////////////////////////////////////////////////////////////////
//
// 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 "constructing.h"
#include "fleeing.h"
#include "walking.h"
#include "defaultBehavior.h"
#include "../units/cc.h"
#include "../territory/stronghold.h"
#include "../strategy/strategy.h"
#include "../strategy/firstFactoryPlacement.h"
#include "../strategy/firstBarracksPlacement.h"
#include "../strategy/cannonRush.h"
#include "../strategy/marineRush.h"
#include "../strategy/zerglingRush.h"
#include "../strategy/enemyScout.h"
#include "../Iron.h"

namespace { auto & bw = Broodwar; }


namespace iron
{


int beingConstructed(BWAPI::UnitType type)
{
	return count_if(Constructing::Instances().begin(), Constructing::Instances().end(), [type]
		(const Constructing * p){ return p->Type() == type; });
}



static int countVerticalAdjacentBuildings(TilePosition pos, TilePosition dim, int yDir)
{
	assert_(abs(yDir) == 1);

	TilePosition t = pos + TilePosition(0, yDir*dim.y);
	if (ai()->GetMap().Valid(t))
		if (const BWAPIUnit * u = ai()->GetVMap().GetBuildingOn(ai()->GetMap().GetTile(t)))
			if (const MyBuilding * b = u->IsMyBuilding())
				if ((b->TopLeft() == t) && (b->Size() == dim))
					return 1 + countVerticalAdjacentBuildings(t, dim, yDir);
		
	return 0;
}


static bool canBuild(BWAPI::UnitType type, TilePosition location, int & verticalAdjacentBuildings)
{
	TilePosition dim(type.tileSize());
	if (type.canBuildAddon()) dim.x += 2;

	verticalAdjacentBuildings = 0;
	int topAdjacentBuildings = 0;		// clear warning
	int bottomAdjacentBuildings = 0;	// clear warning

	CHECK_POS(location);
	CHECK_POS(location + dim - 1);

	for (int dy = -1 ; dy <= dim.y ; ++dy)
	for (int dx = -1 ; dx <= dim.x ; ++dx)
	{
		TilePosition t = location + TilePosition(dx, dy);

		if (dy == -1)
		{
			if (dx == -1)
				topAdjacentBuildings = countVerticalAdjacentBuildings(location, type.tileSize(), -1);

			if ((topAdjacentBuildings == 1) || (topAdjacentBuildings == 2)) continue;
		}
		else if (dy == dim.y)
		{
			if (dx == -1)
				bottomAdjacentBuildings = countVerticalAdjacentBuildings(location, type.tileSize(), +1);

			if ((bottomAdjacentBuildings >= 1) && (topAdjacentBuildings + bottomAdjacentBuildings <= 2)) continue;
		}

		if ((dx == -1) || (dy == -1) || (dx == dim.x) || (dy == dim.y))
			if (!ai()->GetMap().Valid(t)) continue;

		const auto & tile = ai()->GetMap().GetTile(t);
		if (!tile.Buildable()) return false;
		if (tile.GetNeutral()) return false;
		if (ai()->GetVMap().GetBuildingOn(tile)) return false;
		if (ai()->GetVMap().AddonRoom(tile)) return false;
		if (type == Terran_Command_Center)
		{
			if (ai()->GetVMap().CommandCenterImpossible(tile)) return false;
		}
		else
		{
			if ((dx >= 0) && (dy >= 0) && (dx < dim.x) && (dy < dim.y))
				if (ai()->GetVMap().CommandCenterWithAddonRoom(tile)) return false;
		}
	}

	verticalAdjacentBuildings = topAdjacentBuildings + bottomAdjacentBuildings;
	if (verticalAdjacentBuildings && (type == Terran_Missile_Turret)) return false;

	return true;
}


MyUnit * findBlockingMine(BWAPI::UnitType type, TilePosition location)
{
	TilePosition dim(type.tileSize());
	if (type.canBuildAddon()) dim.x += 2;

	for (const auto & u : me().Units(Terran_Vulture_Spider_Mine))
		if (u->Completed())
			if (distToRectangle(u->Pos(), location, dim) <= 1)
				return u.get();

	return nullptr;
}


static bool tileInEnlargedArea(const TilePosition & t, const VArea * varea, const Area * area)
{
	CHECK_POS(t);
	const Area * a = ai()->GetMap().GetArea(t);
	return (a == area) || contains(varea->EnlargedArea(), a);
}


TilePosition Constructing::FindFirstBarracksPlacement()
{
	assert_throw(!ai()->GetStrategy()->Active<FirstBarracksPlacement>());

	return FindBuildingPlacement(Terran_Barracks, me().Bases()[0], TilePosition(me().Bases()[0]->BWEMPart()->Center()));
}


TilePosition Constructing::FindFirstFactoryPlacement()
{
	assert_throw(!ai()->GetStrategy()->Active<FirstFactoryPlacement>());

	const UnitType type = Terran_Factory;
	const Area * area = ai()->Me().GetArea();
	const VArea * varea = ai()->Me().GetVArea();

	assert_throw(varea->HotCP());

	TilePosition EnlargedArea_TopLeft     = {numeric_limits<int>::max(), numeric_limits<int>::max()};
	TilePosition EnlargedArea_BottomRight = {numeric_limits<int>::min(), numeric_limits<int>::min()};
	for (const Area * area : varea->EnlargedArea())
	{
		makeBoundingBoxIncludePoint(EnlargedArea_TopLeft, EnlargedArea_BottomRight , area->TopLeft());
		makeBoundingBoxIncludePoint(EnlargedArea_TopLeft, EnlargedArea_BottomRight , area->BottomRight());
	}

	map<double, TilePosition> Candidates;
	
	TilePosition dim(type.tileSize());
	dim.x += 2;

	TilePosition SearchBoundingBox_TopLeft = ai()->GetMap().Crop(EnlargedArea_TopLeft);
	TilePosition SearchBoundingBox_BottomRight = ai()->GetMap().Crop(EnlargedArea_BottomRight);
	SearchBoundingBox_BottomRight -= dim-1;


//	bw->drawBoxMap(center(SearchBoundingBox_TopLeft), center(SearchBoundingBox_BottomRight), Colors::Orange);

	for (int y = SearchBoundingBox_TopLeft.y ; y <= SearchBoundingBox_BottomRight.y ; ++y)
	for (int x = SearchBoundingBox_TopLeft.x ; x <= SearchBoundingBox_BottomRight.x ; ++x)
	{
		int verticalAdjacentBuildings;
		TilePosition target(x, y);
		TilePosition bottomRight = target + dim-1;

		if (tileInEnlargedArea(target, varea, area))
		if (tileInEnlargedArea(bottomRight, varea, area))
		if (tileInEnlargedArea(TilePosition(target.x, bottomRight.y), varea, area))
		if (tileInEnlargedArea(TilePosition(bottomRight.x, target.y), varea, area))
			if (canBuild(type, target, verticalAdjacentBuildings))
			{
				double score = dist(target + dim/2, TilePosition(varea->HotCP()->Center()))
							 + dist(target + dim/2, TilePosition(ai()->Me().GetBase(0)->Center()));
				Candidates[score] = target;
			}

	}

	return Candidates.empty() ? TilePositions::None : Candidates.rbegin()->second;
}


TilePosition Constructing::FindBuildingPlacement(BWAPI::UnitType type, VBase * base, TilePosition near, int maxTries)
{
	assert_throw(base);

	if (type == Terran_Command_Center)
		return base->BWEMPart()->Location();

	const VArea * varea = base->GetArea();
	const Area * area = varea->BWEMPart();

	bool restrictToBaseEnlargedArea = true;
	bool restrictToAreasUnderDefenseCP = false;
/*
	if (const MyBuilding * b = me().FindBuildingNeedingBuilder(type))
	{
		//b->CancelConstruction();

		if (m_pExpert) m_pExpert->OnBuildingCreated();
		return b->TopLeft();
	}
*/
	const bool bunkerForDefenseCP =	(type == Terran_Bunker) &&
									ai()->GetStrategy()->Detected<ZerglingRush>() &&
									ai()->Me().GetVArea()->DefenseCP();

//	const bool bunkerForMarineRush =(type == Terran_Bunker) &&
//									ai()->GetStrategy()->Detected<MarineRush>();

	if (bunkerForDefenseCP)
	{
		restrictToBaseEnlargedArea = false;
		restrictToAreasUnderDefenseCP = true;
		near = TilePosition(ai()->Me().GetVArea()->DefenseCP()->Center());
	}

	if (type == Terran_Barracks)
		if (const FirstBarracksPlacement * pFirstBarracksPlacement = ai()->GetStrategy()->Active<FirstBarracksPlacement>())
			return pFirstBarracksPlacement->Location();

	if (type == Terran_Factory)
		if (const FirstFactoryPlacement * pFirstFactoryPlacement = ai()->GetStrategy()->Active<FirstFactoryPlacement>())
			return pFirstFactoryPlacement->Location();

	if (type == Terran_Refinery)
		return base->BWEMPart()->Geysers().front()->TopLeft();

	map<double, TilePosition> Candidates;
	int radius = 32;
	
	TilePosition dim(type.tileSize());
	if (type.canBuildAddon()) dim.x += 2;

	for (int tries = 1 ; tries <= maxTries ; ++tries)
	{
		if (tries > 1) restrictToBaseEnlargedArea = false;

		TilePosition SearchBoundingBox_TopLeft = ai()->GetMap().Crop(near - radius);
		TilePosition SearchBoundingBox_BottomRight = ai()->GetMap().Crop(near + radius);
		SearchBoundingBox_BottomRight -= dim-1;


	//	bw->drawCircleMap(center(near), 10, Colors::Orange);
	//	bw->drawBoxMap(center(SearchBoundingBox_TopLeft), center(SearchBoundingBox_BottomRight), Colors::Orange);
	//	if (m_type == Terran_Command_Center) ai()->SetDelay(10000);

		for (int y = SearchBoundingBox_TopLeft.y ; y <= SearchBoundingBox_BottomRight.y ; ++y)
		for (int x = SearchBoundingBox_TopLeft.x ; x <= SearchBoundingBox_BottomRight.x ; ++x)
		{
			int verticalAdjacentBuildings;
			TilePosition target(x, y);
			TilePosition bottomRight = target + dim-1;

			if (restrictToAreasUnderDefenseCP)
			{
				const Area * area = ai()->GetMap().GetArea(target + dim/2);
				if (!(area && VArea::Get(area)->UnderDefenseCP()))
					continue;
			}

			if (restrictToBaseEnlargedArea)
				if (!tileInEnlargedArea(target, varea, area) ||
					!tileInEnlargedArea(bottomRight, varea, area) ||
					!tileInEnlargedArea(TilePosition(target.x, bottomRight.y), varea, area) ||
					!tileInEnlargedArea(TilePosition(bottomRight.x, target.y), varea, area))
					continue;

			if (canBuild(type, target, verticalAdjacentBuildings))
			{
				double score = dist(target + dim/2, near);
				if (type != Terran_Bunker)
					if (verticalAdjacentBuildings) score -= 3;

				if (const CannonRush * pCannonRush = ai()->GetStrategy()->Detected<CannonRush>())
					score = -(pCannonRush->DistanceToHotPositions(center(target + dim/2))/32);

				if (type == Terran_Bunker)
					if (ai()->GetStrategy()->Detected<MarineRush>())
						if (const ChokePoint * pHotCP = me().GetVArea()->HotCP())
						{
							int distToHotCP = groundDist(center(target + dim/2), center(pHotCP->Center()));
							score += distToHotCP/32 *1/2;
						}

				Candidates[score] = target;
			}
		}

		if (!Candidates.empty()) return Candidates.begin()->second;
		radius += 5;
	}

	if (bunkerForDefenseCP) ai()->GetStrategy()->Has<ZerglingRush>()->SetNoLocationForBunkerForDefenseCP();
	return TilePositions::None;
}



//////////////////////////////////////////////////////////////////////////////////////////////
//                                                                                          //
//                                  class Constructing
//                                                                                          //
//////////////////////////////////////////////////////////////////////////////////////////////

vector<Constructing *> Constructing::m_Instances;


Constructing::Constructing(My<Terran_SCV> * pSCV, BWAPI::UnitType type, ConstructingExpert * pExpert)
	: Behavior(pSCV, behavior_t::Constructing), m_type(type), m_pExpert(pExpert)
{
	assert_throw(!pExpert || (!pSCV->GetStronghold() == (pExpert->GetFreeLocation() != TilePositions::None)));
	assert_throw(!pSCV->GetStronghold() || pSCV->GetStronghold()->HasBase());

	m_pBase = pSCV->GetStronghold() ? pSCV->GetStronghold()->HasBase() : nullptr;

	if (type == Terran_Refinery)
	{
		assert_throw(m_pBase && !m_pBase->BWEMPart()->Geysers().empty());
		for (const auto & b : me().Buildings(Terran_Refinery))
		{
			assert_throw(!b->Completed() || (b->TopLeft() != m_pBase->BWEMPart()->Geysers().front()->TopLeft()));
		}
	}

	PUSH_BACK_UNCONTAINED_ELEMENT(m_Instances, this);
}


Constructing::~Constructing()
{
#if !DEV
	try //3
#endif
	{
		assert_throw(contains(m_Instances, this));
		really_remove(m_Instances, this);

		if (m_pExpert) m_pExpert->OnConstructionAborted();
	}
#if !DEV
	catch(...){} //3
#endif
}


string Constructing::StateName() const
{CI(this);
	switch(State())
	{
	case reachingLocation:	return "reachingLocation";
	case waitingForMoney:	return "waitingForMoney";
	case constructing:		return "constructing";
	default:			return "?";
	}
}


void Constructing::OnOtherBWAPIUnitDestroyed_v(BWAPIUnit * other)
{CI(this);
	if (other->TopLeft() == Location()) 
		return Agent()->ChangeBehavior<DefaultBehavior>(Agent());
}



TilePosition Constructing::FindBuildingPlacement(TilePosition near) const
{CI(this);
	assert_throw(GetBase());


//	if (Type() == Terran_Command_Center)
//		return GetBase()->BWEMPart()->Location();

	if (const MyBuilding * b = me().FindBuildingNeedingBuilder(Type()))
	{
		//b->CancelConstruction();

		if (m_pExpert) m_pExpert->OnBuildingCreated();
		return b->TopLeft();
	}

	static int failures = 0;
	TilePosition res = FindBuildingPlacement(Type(), GetBase(), near);
	if (res == TilePositions::None)
	{
		if (++failures > 100)
		{
			res = FindBuildingPlacement(Type(), GetBase(), near, 10);
			if (res != TilePositions::None) m_timeToReachLocation = 1000;
			if (failures > 120) failures = 0;
		}
	}
	else
		failures = 0;

	return res;
}


const Tile & Constructing::LocationTile() const
{CI(this);
	assert_throw(Location() != TilePositions::None);

	return ai()->GetMap().GetTile(Location());
}


BWAPIUnit * Constructing::FindBuilding() const
{CI(this);
	return ai()->GetVMap().GetBuildingOn(LocationTile());
}


void Constructing::OnFrame_v()
{CI(this);
#if DEV
	if (Location() != TilePositions::None) bw->drawBoxMap(Position(Location()), Position(Location() + Type().tileSize()), GetColor());//1
#endif

	if (!Agent()->CanAcceptCommand()) return;

	switch (State())
	{
	case reachingLocation:		OnFrame_reachingLocation(); break;
	case waitingForMoney:		OnFrame_waitingForMoney(); break;
	case constructing:			OnFrame_constructing(); break;
	default: assert_throw(false);
	}
}



void Constructing::OnFrame_reachingLocation()
{CI(this);
	if (JustArrivedInState())
	{
		m_inStateSince = ai()->Frame();
		m_location = Agent()->GetStronghold()
			? FindBuildingPlacement(TilePosition(GetBase()->BWEMPart()->Center()))
			: m_pExpert->GetFreeLocation();

		if (m_location == TilePositions::None)
		{
			if (m_pExpert) m_pExpert->OnNoMoreLocation();
			return Agent()->ChangeBehavior<DefaultBehavior>(Agent());
		}

		SetSubBehavior<Walking>(Agent(), CenterLocation(), __FILE__ + to_string(__LINE__));
	}


	if (Agent()->Life() < Agent()->PrevLife(30))
		return Agent()->ChangeBehavior<Fleeing>(Agent());


	if (Agent()->GetStronghold())
		if (m_pExpert)
			if (m_pExpert->CanChangeBuilder())
			{
				My<Terran_SCV> * pCloserWorker = findFreeWorker(GetBase(), [this](const My<Terran_SCV> * pGatherer)
					{ return dist(pGatherer->Pos(), CenterLocation()) < dist(Agent()->Pos(), CenterLocation() - 5); });

				if (pCloserWorker)
				{
					m_pExpert->OnBuilderChanged(pCloserWorker);
					pCloserWorker->ChangeBehavior<Constructing>(pCloserWorker, Type(), m_pExpert);
					m_pExpert = nullptr;
					return Agent()->ChangeBehavior<DefaultBehavior>(Agent());
				}
			}

	bool buildingExists = false;
	if (auto * b = FindBuilding())
		if (b->Is(Type()))
			if (!b->Completed())
				buildingExists = true;

	if (Agent()->Pos().getApproxDistance(GetSubBehavior()->IsWalking()->Target()) < (buildingExists || (m_type == Terran_Refinery) ? 4 : 2) * 32)
	//	if (Agent()->Life() >= 30)
		{
			ResetSubBehavior();
			return ChangeState(waitingForMoney);
		}

	if (ai()->Frame() - m_inStateSince > 200)
		return Agent()->ChangeBehavior<DefaultBehavior>(Agent());
}



void Constructing::OnFrame_waitingForMoney()
{CI(this);
	if (JustArrivedInState())
	{
		m_inStateSince = ai()->Frame();
	}

	if (ai()->Frame() - m_inStateSince > 250)
		return Agent()->ChangeBehavior<DefaultBehavior>(Agent());

	if (Agent()->Life() < Agent()->PrevLife(30))
		return Agent()->ChangeBehavior<Fleeing>(Agent());


	if (Agent()->Unit()->isConstructing())
		return ChangeState(constructing);


	bool buildingExists = false;
	if (auto * b = FindBuilding())
		if (b->Is(Type()))
			if (!b->Completed())
				buildingExists = true;

	if (buildingExists)
	{
		Agent()->RightClick(FindBuilding(), no_check);
	}
	else
	{
		if (me().CanPay(Type()))
			if (!Agent()->Build(Type(), Location(), no_check))
				if (MyUnit * pBlockingMine = findBlockingMine(Type(), Location()))
					Agent()->Attack(pBlockingMine);
	}


}



void Constructing::OnFrame_constructing()
{CI(this);
	if (JustArrivedInState())
	{
		m_inStateSince = ai()->Frame();
	}

	if (Agent()->Life() < Agent()->PrevLife(30))
//	if (Agent()->Life() < 20)
	{

		bool enemyScoutNear = false;
		if (const EnemyScout * pEnemyScout = ai()->GetStrategy()->Detected<EnemyScout>())
			if (pEnemyScout->Get())
				if (dist(Agent()->Pos(), pEnemyScout->Get()->Pos()) < 2*32)
					enemyScoutNear = true;

		if (!enemyScoutNear)
			return Agent()->ChangeBehavior<Fleeing>(Agent());
	}


	if (Agent()->Unit()->isConstructing())
	{
		if (BWAPIUnit * b = FindBuilding())
			if (b->Is(Type()))
				if (!b->Completed())
					if (b->IsMyBuilding()->CanAcceptCommand())
						if (ai()->GetVMap().AddonRoom(LocationTile()))
						{
						//	bw << "constructing on Addon room !" << endl;
							b->IsMyBuilding()->CancelConstruction();
							return Agent()->ChangeBehavior<DefaultBehavior>(Agent());
						}
	}
	else
	{
		if (ai()->Frame() - m_inStateSince > 30)
		{
			if (!FindBuilding())
			{
			//	bw << Agent()->NameWithId() << " stop constructing..." << endl;
			//	ai()->SetDelay(3333);
			}
			return Agent()->ChangeBehavior<DefaultBehavior>(Agent());
		}
	}
}






} // namespace iron



