//////////////////////////////////////////////////////////////////////////
//
// 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 "exploring.h"
#include "walking.h"
#include "repairing.h"
#include "fleeing.h"
#include "kiting.h"
#include "sniping.h"
#include "razing.h"
#include "raiding.h"
#include "defaultBehavior.h"
#include "../strategy/strategy.h"
#include "../strategy/zerglingRush.h"
#include "../strategy/marineRush.h"
#include "../territory/vgridMap.h"
#include "../Iron.h"

namespace { auto & bw = Broodwar; }


namespace iron
{

//////////////////////////////////////////////////////////////////////////////////////////////
//                                                                                          //
//                                  class Exploring
//                                                                                          //
//////////////////////////////////////////////////////////////////////////////////////////////

vector<Exploring *> Exploring::m_Instances;


Exploring::Exploring(MyUnit * pAgent, const Area * pWhere)
	: Behavior(pAgent, behavior_t::Exploring), m_pWhere(pWhere)
{
	assert_throw(pAgent->Flying() || pWhere->AccessibleFrom(pAgent->GetArea()));

	PUSH_BACK_UNCONTAINED_ELEMENT(m_Instances, this);

	m_inStateSince = ai()->Frame();
}


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


void Exploring::ChangeState(state_t st)
{CI(this);
	assert_throw(m_state != st);
	
	m_state = st; OnStateChanged();
}


string Exploring::StateName() const
{CI(this);
	switch(State())
	{
	case reachingArea:		return "reachingArea";
	case lastVisited:		return "lastVisited";
	case random:			return "random";
	default:				return "?";
	}
}


void Exploring::OnFrame_v()
{CI(this);
#if DEV
	if ((State() != reachingArea) && (m_target != Positions::None)) drawLineMap(Agent()->Pos(), m_target, GetColor());//1
#endif


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

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

	if (Agent()->GroundRange() > 3*32)
	{
		if (Agent()->Is(Terran_Marine))
			if (ai()->GetStrategy()->Detected<ZerglingRush>() ||
				ai()->GetStrategy()->Detected<MarineRush>())
				if (Sniping::Instances().empty())
					if (My<Terran_Bunker> * pBunker = Sniping::FindBunker(Agent(), 25*32))
						return Agent()->ChangeBehavior<Sniping>(Agent(), pBunker);

		if (Kiting::KiteCondition(Agent()) || Kiting::AttackCondition(Agent()))
			return Agent()->ChangeBehavior<Kiting>(Agent());
	}
	else
	{
		auto Threats3 = findThreats(Agent(), 3*32);
		if (!Threats3.empty())
			return Agent()->ChangeBehavior<Fleeing>(Agent());
	}

	if (Agent()->Type().isMechanical())
		if (Agent()->Life() < Agent()->MaxLife())
			if (Agent()->RepairersCount() < Agent()->MaxRepairers())
				if (Repairing * pRepairer = Repairing::GetRepairer(Agent(),
							(Agent()->Life()*4 > Agent()->MaxLife()*3) ? 16*32 :
							(Agent()->Life()*4 > Agent()->MaxLife()*2) ? 32*32 :
							(Agent()->Life()*4 > Agent()->MaxLife()*1) ? 64*32 : 1000000))
					return Agent()->ChangeBehavior<Repairing>(Agent(), pRepairer);

	if (State() != reachingArea)
	{
		const frame_t delayBeforeRazing = Agent()->Is(Terran_Vulture) ? 100 : 0;
		if (ai()->Frame() - m_inStateSince > delayBeforeRazing)
		{
			if (Razing::Condition(Agent()))
				return Agent()->ChangeBehavior<Razing>(Agent(), Agent()->FindArea());

			if (!him().StartingBaseDestroyed())
				if (Agent()->GetArea(no_check) == him().GetArea())
					if (none_of(him().Buildings().begin(), him().Buildings().end(),
								[](const unique_ptr<HisBuilding> & b){ return !b->Flying() && (b->GetArea() == him().GetArea()); }))
					{
						him().SetStartingBaseDestroyed();
					}

			if (!Agent()->Is(Terran_Marine))
			if (!Agent()->Is(Terran_SCV))
			{
				for (const auto & b : him().Buildings())
					if (Agent()->Flying() ||
						(!b->Flying() && b->GetArea()->AccessibleFrom(Agent()->GetArea())))
							return Agent()->ChangeBehavior<Raiding>(Agent(), b->Pos());

				if (!Agent()->Flying())
					for (const Area & area : ai()->GetMap().Areas())
						if (area.AccessibleFrom(Agent()->GetArea()))
							return Agent()->ChangeBehavior<Raiding>(Agent(), center(area.Top()));
			}
		}
	}

	switch (State())
	{
	case reachingArea:	OnFrame_reachingArea(); break;
	case lastVisited:	OnFrame_lastVisited(); break;
	case random:		OnFrame_random(); break;
	default: assert_throw(false);
	}
}


void Exploring::OnFrame_reachingArea()
{CI(this);
//	const bool inHisArea = (Agent()->GetArea() == HisBase()->BWEMPart()->GetArea());

	if (const Area * pAgentArea = Agent()->GetArea(no_check))
		if (pAgentArea == Where())
		{
			ResetSubBehavior();
			return ChangeState(lastVisited);
		}

	if (JustArrivedInState())
	{
		m_inStateSince = ai()->Frame();
		if (Agent()->Flying())
			return Agent()->Move(Position(Where()->Top()));
		else
			return SetSubBehavior<Walking>(Agent(), Position(Where()->Top()), __FILE__ + to_string(__LINE__));
	}

	if (ai()->Frame() - m_inStateSince > 5)
		if (Agent()->Unit()->isIdle())
			return Agent()->ChangeBehavior<DefaultBehavior>(Agent());
}


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

	if (him().StartingBaseDestroyed() && Agent()->Flying())
		return ChangeState(random);

	if (Target() != Positions::None)
		if (ai()->Frame() - m_inStateSince > 10)
			if (ai()->Frame() - Agent()->LastFrameMoving() > 5)
			{
				ai()->GetGridMap().GetCell(TilePosition(Target())).lastFrameVisible = ai()->Frame();
				return Agent()->ChangeBehavior<DefaultBehavior>(Agent());
			}

	{
		int i1, j1, i2, j2;

		tie(i1, j1) = ai()->GetGridMap().GetCellCoords(Where()->TopLeft());
		tie(i2, j2) = ai()->GetGridMap().GetCellCoords(Where()->BottomRight());

		multimap<int, Position> Targets;

		vector<HisUnit *> ThreatsInArea;

		for (int j = j1 ; j <= j2 ; ++j)
		for (int i = i1 ; i <= i2 ; ++i)
		{
			Position cellCenter = Position(TilePosition(i*VGridMap::cell_width_in_tiles, j*VGridMap::cell_width_in_tiles)) +
								  Position(TilePosition(VGridMap::cell_width_in_tiles, VGridMap::cell_width_in_tiles)) / 2;
			Position target = nearestEmpty(cellCenter);
			CHECK_POS(target);
			if (ai()->GetMap().GetArea(WalkPosition(target)) == Where())
			{
				auto & Cell = ai()->GetGridMap().GetCell(i, j);
				ThreatsInArea.insert(ThreatsInArea.end(), Cell.HisUnits.begin(), Cell.HisUnits.end());

				if (none_of(Exploring::Instances().begin(), Exploring::Instances().end(),
							[target, this](const Exploring * e){ return (e != this) && (e->Target() == target); }))
					if (int time = ai()->Frame() - Cell.lastFrameVisible)
						Targets.emplace(time, target);
			}
		}

		if (Targets.size() < 5) return ChangeState(random);

		for (HisUnit * u : ThreatsInArea)
			if (FaceOff(Agent(), u).MyAttack())
				return Agent()->Move(m_target = u->Pos());

		Position newTarget = Targets.rbegin()->second;
		if (newTarget != m_target)
			return Agent()->Move(m_target = newTarget);
	}
}


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

	if (ai()->Frame() - m_inStateSince > 10)
		if (ai()->Frame() - Agent()->LastFrameMoving() > 5)
		{
			return Agent()->ChangeBehavior<Exploring>(Agent(), Where());
		}

	if ((Target() == Positions::None) || (Agent()->Pos().getApproxDistance(Target()) < 4 * 32))
	{
		if (him().StartingBaseDestroyed() && Agent()->Flying())
		{
			m_target = ai()->GetMap().RandomPosition();
			return Agent()->Move(m_target);
		}

		TilePosition topLeftAreas     = {numeric_limits<int>::max(), numeric_limits<int>::max()};
		TilePosition bottomRightAreas = {numeric_limits<int>::min(), numeric_limits<int>::min()};
		for (const Area * area : {Where()})
		{
			makeBoundingBoxIncludePoint(topLeftAreas, bottomRightAreas, area->TopLeft());
			makeBoundingBoxIncludePoint(topLeftAreas, bottomRightAreas, area->BottomRight());
		}

		for (int tries = 0 ; tries < 100 ; ++tries)
		{
			TilePosition t = topLeftAreas + TilePosition(rand() % (1 + bottomRightAreas.x - topLeftAreas.x),
													     rand() % (1 + bottomRightAreas.y - topLeftAreas.y));
			Position target = nearestEmpty(center(t));
			CHECK_POS(target);
			if (ai()->GetMap().GetArea(WalkPosition(target)) == Where())
			{
				return Agent()->Move(m_target = target);
			}
		}
	}
}



} // namespace iron



