#include "MW4Headers.hpp"

#include "AI_Tactics.hpp"

#include "Helicopter.hpp"
#include "Mech.hpp"
#include "AI_TacticInterface.hpp"
#include "LRMWeaponSubsystem.hpp"
#include "MRMWeaponSubsystem.hpp"
#include "AI_Condition.hpp"
#include "Building.hpp"
#include "SubsystemClassData.hpp"


using namespace Stuff;
using namespace MW4AI;
using namespace MW4AI::Tactics;
using namespace MechWarrior4;

#pragma warning(disable:4239)



const Stuff::Time satisfaction_delta_per_second = 0.08f;
const Stuff::Scalar min_satisfaction_threshold = 0.1f;
const Stuff::Time duration_to_ignore_tactic_satisfaction = 8.0f;	// we initially ignore whether or not the tactic is working for this many seconds

const Stuff::Scalar min_tactic_duration = 30.0f;	// minimum duration of any tactic, in seconds
const Stuff::Scalar max_tactic_duration = 120.0f;	// maximum duration of any tactic, in seconds

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//

Tactics::Registrar* Registrar::instance = 0;

void Tactics::Registrar::IncrementRefCount(TacticInterface& i)
{
	if (instance == 0)
	{
		gos_PushCurrentHeap(MechWarrior4::Heap);
		instance = new Registrar(i);
		gos_PopCurrentHeap();
	}
	else
	{
		Verify(instance->m_RefCount > 0);
	}

	++(instance->m_RefCount);
}

void Tactics::Registrar::DecrementRefCount()
{
	Verify(instance != 0);
	Verify(instance->m_RefCount > 0);

	--(instance->m_RefCount);
	if (instance->m_RefCount == 0)
	{
		delete instance;
		instance = 0;
	}
}

Tactics::Registrar& Registrar::GetInstance()
{
	Verify(instance != 0);
	Verify(instance->m_RefCount > 0);
	return (*instance);
}

Registrar::Registrar(TacticInterface& iface)
	: m_RefCount(0)
{
	#define ADD(tactic)						\
		{ Auto_Ptr<Tactic> t(new tactic);	\
		  m_Tactics.push_back(t); }

	ADD(Circle);
	ADD(Strafe);
	ADD(CircleHover);
	ADD(StandGround);
	ADD(StopAndFire);
	ADD(Ram);
	ADD(Joust);
	ADD(Rush);
	ADD(HitAndRun);
	ADD(Front);
	ADD(Rear);
	ADD(Retreat);
	ADD(BackUpAndFire);
	ADD(JumpAndShoot);
	ADD(Snipe);
	ADD(ShootOnly);
	ADD(Defend);
	ADD(LocalPatrol);
	ADD(Surrender);
	ADD(Ambush);
	ADD(DeathFromAbove);
	ADD(HeliPopup);
	ADD(DiveBomb);
	ADD(FastCircle);
	ADD(Stare);

	#undef ADD
}

Tactics::TacticID Registrar::SelectTactic(TacticInterface& iface, const std::vector<TacticID>& most_recent_tactics) const
{
// MSL 5.05
	if (iface.GetSelf().IsDerivedFrom(Building::DefaultData) == true)
	{
		return (TACTIC_SHOOT_ONLY);
	}
	if ((iface.GetSelf().IsDerivedFrom(Vehicle::DefaultData) == false) ||
		(iface.IsShootOnlyAI() == true))
	{
		return (TACTIC_SHOOT_ONLY);
	}

	if (iface.GetSelf().IsDerivedFrom(Helicopter::DefaultData) == true)
	{
		return (TACTIC_CIRCLE_HOVER);
	}

	if (iface.GetSelf().IsDerivedFrom(Airplane::DefaultData) == true)
	{
		MechWarrior4::Airplane* plane = Cast_Object(MechWarrior4::Airplane*,&(iface.GetSelf()));
		const MechWarrior4::Airplane::GameModel* game_model = plane->GetGameModel();

		switch (game_model->m_attackType)
		{
			default:
			{
				Verify(!"Error: no airplane attack type specified.");
				return (TACTIC_DIVE_BOMB);
			}
			case MechWarrior4::Vehicle::NightShadeAttackType:
			{
				return (TACTIC_DIVE_BOMB);
			}
			case MechWarrior4::Vehicle::ShiloneAttackType:
			{
				return (TACTIC_STRAFE);
			}
		}
	}

	if (iface.GetSelf().IsDerivedFrom(Mech::DefaultData) == false)
	{
		if (iface.GetMood() <= 2)
		{
			return (TACTIC_RETREAT);
		}

		return (TACTIC_SNIPE);
	}

	std::vector<Tactic*> tactics;
	{for (tactic_container::const_iterator i = m_Tactics.begin();
		  i != m_Tactics.end();
		  ++i)
	{
		if (((*i)->CanBeSelectedAutomatically(iface) == true) &&
			(std::find(most_recent_tactics.begin(),most_recent_tactics.end(),(*i)->GetID()) == most_recent_tactics.end()))
		{
			tactics.push_back(*i);
		}
	}}

	if (tactics.size() == 0)
	{
		{for (tactic_container::const_iterator i = m_Tactics.begin();
			  i != m_Tactics.end();
			  ++i)
		{
			if ((*i)->CanBeSelectedAutomatically(iface) == true)
			{
				if (((*i)->GetID() != TACTIC_RETREAT) ||
					(iface.CanRetreat() == true))
				{
					tactics.push_back(*i);
				}
			}
		}}

		if (tactics.size() == 0)
		{
			return (TACTIC_CIRCLE_OF_DEATH);
		}
	}

	if (tactics.size() == 1)
	{
		return ((*(tactics.begin()))->GetID());
	}

	while (tactics.size() > 2)
	{
		std::vector<Tactic*>::iterator worst = tactics.end();
		Stuff::Scalar worst_score = 100;

		{for (std::vector<Tactic*>::iterator i = tactics.begin();
			  i != tactics.end();
			  ++i)
		{
			const Stuff::Scalar score((*i)->Evaluate(iface));
			if (score <= worst_score)
			{
				worst_score = score;
				worst = i;
			}
		}}

		tactics.erase(worst);
	}

	return (tactics[Stuff::Random::GetInt() & 0x01]->GetID());
}

Tactic& Registrar::GetTactic(TacticID id) const
{
	{for (tactic_container::const_iterator i = m_Tactics.begin();
		  i != m_Tactics.end();
		  ++i)
	{
		if ((*i)->GetID() == id)
		{
			return (**i);
		}
	}}
	
	Verify(!"Should never get here!");
	return (**(m_Tactics.begin()));	
}

Auto_Ptr<MW4AI::Tactics::Tactic> MW4AI::Tactics::CreateTactic(TacticInterface& i, TacticID id)
{
	Tactic* t = 0;

	switch (id)
	{
		case TACTIC_STOP_AND_FIRE:		{ t = new StopAndFire();	break;	}
		case TACTIC_RAM:				{ t = new Ram();			break;	}
		case TACTIC_JOUST:				{ t = new Joust();			break;  }
		case TACTIC_RUSH:				{ t = new Rush();			break;	}
		case TACTIC_HIT_AND_RUN:		{ t = new HitAndRun();		break;	}
		case TACTIC_FRONT:				{ t = new Front();			break;	}
		case TACTIC_REAR:				{ t = new Rear();			break;	}
		case TACTIC_CIRCLE_OF_DEATH:	{ t = new Circle();			break;	}
		case TACTIC_RETREAT:			{ t = new Retreat();		break;	}
		case TACTIC_BACK_UP_AND_FIRE:	{ t = new BackUpAndFire();	break;	}
		case TACTIC_CIRCLE_HOVER:		{ t = new CircleHover();	break;	}
		case TACTIC_STRAFE:				{ t = new Strafe();			break;	}
		case TACTIC_STAND_GROUND:		{ t = new StandGround();	break;	}
		case TACTIC_JUMP_AND_SHOOT:		{ t = new JumpAndShoot();	break;  }
		case TACTIC_SNIPE:				{ t = new Snipe();			break;	}
		case TACTIC_SHOOT_ONLY:			{ t = new ShootOnly();		break;  }
		case TACTIC_DEFEND:				{ t = new Defend();			break;	}
		case TACTIC_LOCAL_PATROL:		{ t = new LocalPatrol();	break;  }
		case TACTIC_SURRENDER:			{ t = new Surrender();		break;  }
		case TACTIC_AMBUSH:				{ t = new Ambush();			break;  }
		case TACTIC_DEATH_FROM_ABOVE:	{ t = new DeathFromAbove(); break;	}
		case TACTIC_HELI_POPUP:			{ t = new HeliPopup();		break;	}
		case TACTIC_DIVE_BOMB:			{ t = new DiveBomb();		break;	}
		case TACTIC_FAST_CIRCLE:		{ t = new FastCircle();		break;	}
		case TACTIC_STARE:				{ t = new Stare();			break;	}
		default:
		{
			Verify("Tactic not found!" == 0);
		}
	}

	if (t == 0)
	{
		return (0);
	}

	return (t);
}

Registrar::~Registrar()
{
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// General tactics functions
//

void VerifyTactic(TacticID id)
{
	Verify(id >= TACTIC_FIRST);
	Verify(id <= TACTIC_LAST);
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Tactic
//

void Tactic::SetExplicit(bool is_explicit)
{
	m_Explicit = is_explicit;
}

bool Tactic::GetExplicit() const
{
	return (m_Explicit);
}

bool Tactic::CanBeSelectedAutomatically(TacticInterface& iface) const
{
	return (false);
}

Tactic::Tactic()
	: m_CreationTime((Stuff::Scalar)gos_GetElapsedTime())
	, m_CreationMood(-1)
	, m_Satisfaction(1.0f)
	, m_LastSatisfactionUpdate(0)
	, m_TacticToIgnore((TacticID)0)
{
	m_AutoEndTime = m_CreationTime + min_tactic_duration + ((max_tactic_duration - min_tactic_duration) * Stuff::Random::GetFraction());
	m_CreationTime += Stuff::Random::GetFraction();
}

void Tactic::SetIgnoreExplicitTactic(TacticID tactic)
{
	m_TacticToIgnore = tactic;
}

TacticID Tactic::GetIgnoreExplicitTactic() const
{
	return (m_TacticToIgnore);
}

Tactic::~Tactic()
{
}

Stuff::Scalar Tactic::GetDuration() const
{
	return ((Stuff::Scalar)gos_GetElapsedTime() - m_CreationTime);
}

void Tactic::Update(TacticInterface& i)
{
	if (m_CreationMood == -1)
	{
		m_CreationMood = (Stuff::Scalar)i.GetMood();
	}
}

bool Tactic::ShouldAbandon(TacticInterface& i) const
{
	if (ShouldAbandonImmediately() == true)
	{
		m_Explicit = false;
		return (true);
	}

	if ((m_Explicit == true) ||
		(GetDuration() < UserConstants::Instance()->Get(UserConstants::min_tactic_time)) ||
		(m_CreationMood == -1))
	{
		return (false);
	}

	if ((i.GetSelf().IsDerivedFrom(Building::DefaultData) == true) &&
		(i.GetSelf().IsDerivedFrom(Airplane::DefaultData) == true))
	{
		return (false);
	}

	Stuff::Scalar mood_delta = Stuff::Fabs(m_CreationMood - i.GetMood());

	if (mood_delta > 1)
	{
		return (true);
	}

	if (gos_GetElapsedTime() > m_AutoEndTime)
	{
		return (true);
	}

	return (IsIneffective());
}

bool Tactic::IsIneffective() const
{
	if (m_Satisfaction <= min_satisfaction_threshold)
	{
		return (true);
	}

	return (false);
}

Stuff::Scalar Tactic::Evaluate(TacticInterface& iface) const
{
	return (0);
}

void Tactic::UpdateSatisfaction()
{
	if ((m_LastSatisfactionUpdate != 0) &&
		(m_CreationTime + duration_to_ignore_tactic_satisfaction < gos_GetElapsedTime()))
	{
		const Stuff::Time time_delta = gos_GetElapsedTime() - m_LastSatisfactionUpdate;

		if (IsHappy() == true)
		{
			m_Satisfaction += (Stuff::Scalar)(satisfaction_delta_per_second * time_delta);
			if (m_Satisfaction > 1.0f)
			{
				m_Satisfaction = 1.0f;
			}
		}
		else
		{
			m_Satisfaction -= (Stuff::Scalar)(satisfaction_delta_per_second * time_delta);
			if (m_Satisfaction < 0.0f)
			{
				m_Satisfaction = 0.0f;
			}
		}
	}

	m_LastSatisfactionUpdate = gos_GetElapsedTime();
}

bool Tactic::Finished() const
{
	if (ShouldAbandonImmediately() == true)
	{
		return (true);
	}

	if ((m_TacticToIgnore != (TacticID)0) &&
		(GetID() != m_TacticToIgnore))
	{
		return (true);
	}

	return (false);
}

bool Tactic::ShouldIgnoreLOS() const
{
	return (false);
}


//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Tactic
//

inline bool Qualifies(TacticInterface& iface,
					  UserConstants::ID min_elite, UserConstants::ID max_elite,
					  UserConstants::ID min_mood,  UserConstants::ID max_mood)
{
	if ((iface.GetEliteLevel() < UserConstants::Instance()->Get(min_elite)) ||
		(iface.GetEliteLevel() > UserConstants::Instance()->Get(max_elite)))
	{
		return (false);
	}

	if ((iface.GetMood() < UserConstants::Instance()->Get(min_mood)) ||
		(iface.GetMood() > UserConstants::Instance()->Get(max_mood)))
	{
		return (false);
	}

	return (true);
}

Stuff::Scalar Score_FasterThanTarget(TacticInterface& iface)
{
	Vehicle* v1 = iface.GetSelf_AsVehicle();
	Vehicle* v2 = iface.GetTarget_AsVehicle();

	Stuff::Scalar my_speed(0);

	if (v1 != 0)
	{
		my_speed = v1->GetGameModel()->maxSpeed;
	}

	Stuff::Scalar target_speed(0);

	if (v2 != 0)
	{
		target_speed = v2->GetGameModel()->maxSpeed;
	}

	if (my_speed > target_speed)
	{
		return (1);
	}

	return (0);
}

Stuff::Scalar Score_HaveMissiles(TacticInterface& iface)
{
	Stuff::ChainIteratorOf<MechWarrior4::Weapon*> i(&(iface.GetSelf().weaponChain));
	MechWarrior4::Weapon* weapon = 0;
	while ((weapon = i.ReadAndNext()) != 0)
	{
		if (weapon->GetAmmoCount() > 0)		// TODO: comment this back in when ammo # works correctly  ...
		{
			if ((weapon->IsDerivedFrom(LRMWeaponSubsystem::DefaultData) == true) ||
				(weapon->IsDerivedFrom(MRMWeaponSubsystem::DefaultData) == true))
			{
				return (1);
			}
		}
	}

	return (0);
}

Stuff::Scalar Score_TargetFacingAway(TacticInterface& iface)
{
	if (Conditions::InFrontOfTarget(iface,true) < 0.5f)
	{
		return (1);
	}

	return (0);
}

Stuff::Scalar Score_FarFromTarget(TacticInterface& iface)
{
	if (GetApproximateLength((Stuff::Point3D)iface.GetSelf().GetLocalToWorld(),
							 (Stuff::Point3D)iface.GetTarget().GetLocalToWorld()) > 250)
	{
		return (1);
	}

	return (0);
}

Stuff::Scalar Score_Gimped(TacticInterface& iface)
{
	if (iface.NumLegsDestroyed() == 1)
	{
		return (1);
	}

	return (0);
}

Stuff::Scalar Score_WantsToUseVCH(TacticInterface& iface)
{
	if (iface.GetCurrentVulnerableComponent() != 0)
	{
		return (1);
	}

	return (0);
}

bool Joust::CanBeSelectedAutomatically(TacticInterface& iface) const
{
	return (Qualifies(iface,
					  UserConstants::min_elite_joust,
					  UserConstants::max_elite_joust,
					  UserConstants::min_mood_joust,
					  UserConstants::max_mood_joust));
}

Stuff::Scalar Joust::Evaluate(TacticInterface& iface) const
{
	return (   ( 0.5f * Score_FasterThanTarget(iface))
			 + (-1.0f * Score_FarFromTarget(iface))
			 + (-1.0f * Score_Gimped(iface))
			 + (-1.0f * Score_WantsToUseVCH(iface)));
}

bool Rush::CanBeSelectedAutomatically(TacticInterface& iface) const
{
	return (Qualifies(iface,
					  UserConstants::min_elite_rush,
					  UserConstants::max_elite_rush,
					  UserConstants::min_mood_rush,
					  UserConstants::max_mood_rush));
}

Stuff::Scalar Rush::Evaluate(TacticInterface& iface) const
{
	return (   (-0.5f * Score_FasterThanTarget(iface))
			 + (-1.0f * Score_HaveMissiles(iface))
			 + ( 1.0f * Score_TargetFacingAway(iface))
			 + (-0.5f * Score_FarFromTarget(iface))
			 + ( 1.0f * Score_Gimped(iface))
			 + (-0.5f * Score_WantsToUseVCH(iface)));
}

bool HitAndRun::CanBeSelectedAutomatically(TacticInterface& iface) const
{
	return (Qualifies(iface,
					  UserConstants::min_elite_hitandrun,
					  UserConstants::max_elite_hitandrun,
					  UserConstants::min_mood_hitandrun,
					  UserConstants::max_mood_hitandrun));
}

Stuff::Scalar HitAndRun::Evaluate(TacticInterface& iface) const
{
	return (   ( 1.0f * Score_FasterThanTarget(iface))
			 + (-1.0f * Score_Gimped(iface))
			 + (-1.0f * Score_WantsToUseVCH(iface)));
}

bool Rear::CanBeSelectedAutomatically(TacticInterface& iface) const
{
	return (Qualifies(iface,
					  UserConstants::min_elite_rear,
					  UserConstants::max_elite_rear,
					  UserConstants::min_mood_rear,
					  UserConstants::max_mood_rear));
}

Stuff::Scalar Rear::Evaluate(TacticInterface& iface) const
{
	return (   ( 1.0f * Score_TargetFacingAway(iface))
			 + ( 1.0f * Score_Gimped(iface))
			 + ( 0.5f * Score_WantsToUseVCH(iface)));
}

bool Circle::CanBeSelectedAutomatically(TacticInterface& iface) const
{
	return (Qualifies(iface,
					  UserConstants::min_elite_circle,
					  UserConstants::max_elite_circle,
					  UserConstants::min_mood_circle,
					  UserConstants::max_mood_circle));
}

Stuff::Scalar Circle::Evaluate(TacticInterface& iface) const
{
	return (   ( 0.5f * Score_FasterThanTarget(iface))
			 + ( 1.0f * Score_HaveMissiles(iface))
			 + ( 0.5f * Score_FarFromTarget(iface))
			 + (-0.5f * Score_Gimped(iface))
			 + ( 1.0f * Score_WantsToUseVCH(iface)));
}

bool Retreat::CanBeSelectedAutomatically(TacticInterface& iface) const
{
	return (Qualifies(iface,
					  UserConstants::min_elite_retreat,
					  UserConstants::max_elite_retreat,
					  UserConstants::min_mood_retreat,
					  UserConstants::max_mood_retreat));
}

Stuff::Scalar Retreat::Evaluate(TacticInterface& iface) const
{
	return (   ( 1.0f * Score_FasterThanTarget(iface))
			 + (-1.0f * Score_TargetFacingAway(iface))
			 + (-1.0f * Score_FarFromTarget(iface))
			 + (-1.0f * Score_Gimped(iface))
			 + (-0.5f * Score_WantsToUseVCH(iface)));
}

bool DeathFromAbove::CanBeSelectedAutomatically(TacticInterface& iface) const
{
	if ((iface.CanJump() == false) ||
		(iface.GetTarget().IsDerivedFrom(Mech::DefaultData) == false))
	{
		return (false);
	}

	return (Qualifies(iface,
					  UserConstants::min_elite_dfa,
					  UserConstants::max_elite_dfa,
					  UserConstants::min_mood_dfa,
					  UserConstants::max_mood_dfa));
}

Stuff::Scalar DeathFromAbove::Evaluate(TacticInterface& iface) const
{
	return (   ( 1.0f * Score_FasterThanTarget(iface))
			 + (-1.0f * Score_FarFromTarget(iface))
			 + (-2.0f * Score_Gimped(iface))
			 + (-0.5f * Score_WantsToUseVCH(iface)));
}
