#include "MW4Headers.hpp"

#include "AI_FireStyle.hpp"
#include "Weapon.hpp"
#include "AI_Weapons.hpp"
#include "aiutils.hpp"
#include "MWObject.hpp"
#include "AI_FireData.hpp"
#include "ProjectileWeapon.hpp"
#include "HighExplosiveWeapon.hpp"
#include "LongTomWeapon.hpp"
#include "ArtilleryWeapon.hpp"
#include "NetWeapon.hpp"
// MSL 5.03 RTX
//#include "rtxweaponsub.hpp"
#include <Adept\Map.hpp>
#include <Adept\Site.hpp>
#include "NarcWeaponSubsystem.hpp"
#include "SubsystemClassData.hpp"
#include "MWDamageObject.hpp"
#include "ai.hpp"

#pragma warning(push)
#include <stlport\algorithm>
#pragma warning(pop)



const double linked_fire_shot_delay = 0.3f;
const Stuff::Scalar min_high_explosive_distance = 80.0f;

extern __int64 tCombatAITime_WeaponFiring;
extern __int64 tWeaponFiring;

using namespace MW4AI;

bool FireStyles::FireStyle::gHeatManagementEnabled = false;	// TODO: remove this eventually  ...


//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// General fire styles functions
//

Auto_Ptr<FireStyles::FireStyle> FireStyles::CreateFireStyle(FireStyleID id)
{
	switch (id)
	{
		case FireStyles::FIRE_MAXIMUM:	{ return (new FireStyles::MaximumFire()); }
		case FireStyles::FIRE_OPPORTUNITY: { return (new FireStyles::OpportunityFire()); }
	}

	Verify(!"Should never get here.");
	return (new FireStyles::MaximumFire());
}


void FireStyles::VerifyFireStyle(FireStyleID id)
{
	Verify(id >= FireStyles::FIRE_FIRST);
	Verify(id <= FireStyles::FIRE_LAST);
}

std::string FireStyles::FireStyleToString(FireStyleID id)
{
	VerifyFireStyle(id);

	switch (id)
	{
		case FireStyles::FIRE_MAXIMUM:	{ return ("Maximum"); }
		case FireStyles::FIRE_OPPORTUNITY:	{ return ("Opportunity"); }
		default:
		{
			Verify("Should not get here" == 0);
		}
	}

	return ("");
}

MechWarrior4::MWObject* SelfOrParentVehicle(Adept::Entity* entity)
{
	if ((entity == 0) ||
		(entity->IsDerivedFrom(MechWarrior4::MWObject::DefaultData) == false))
	{
		return (0);
	}

	MechWarrior4::MWObject* mwobject = Cast_Object(MechWarrior4::MWObject*,entity);
	Check_Object(mwobject);

	if (mwobject->GetParentVehicle() == 0)
	{
		return (mwobject);
	}

	return (mwobject->GetParentVehicle());
}


//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Functors: these plug in to TrueForAllWeapons(), TrueForAnyWeapon(), etc. to perform the appropriate task or query
//

#define FUNCTOR_HEADER(classname)				\
	public:										\
	  classname(const FireParamPackage& params)	\
		: m_Params(params)						\
	  {											\
	  }											\
	private:									\
	  const FireParamPackage& m_Params;

#define REMOVE true
#define NO_REMOVE false

class Fire_Functor
{
  public:
	virtual bool Execute(MechWarrior4::Weapon& weapon, MechWarrior4::WeaponUpdate& weapon_update) = 0;
};

class Functor_CanFire
	: public Fire_Functor
{
	FUNCTOR_HEADER(Functor_CanFire);

  public:
	bool Execute(MechWarrior4::Weapon& weapon, MechWarrior4::WeaponUpdate& weapon_update)
	{
		Check_Object(&weapon);

		if ((m_Params.can_spend_ammo == false) &&
			(weapon.GetAmmoCount() != -1))
		{
			return (REMOVE);
		}

		if (WeaponCanFire(weapon,m_Params.fire_data.GetLine().m_length,m_Params.min_delay,m_Params.max_delay) == false)
		{
			return (REMOVE);
		}

		return (NO_REMOVE);
	}
};

class Functor_CorrectFireSource
	: public Fire_Functor
{
	FUNCTOR_HEADER(Functor_CorrectFireSource);

  public:
	bool Execute(MechWarrior4::Weapon& weapon, MechWarrior4::WeaponUpdate& weapon_update)
	{
		Check_Object(&weapon);

		switch (m_Params.where_to_fire_from)
		{
			case FIRE_FROM_LEFT_ARM:
			{
				if (WeaponIsArmWeapon(weapon,true) == false)
				{
					return (REMOVE);
				}

				break;
			}
			case FIRE_FROM_RIGHT_ARM:
			{
				if (WeaponIsArmWeapon(weapon,false) == false)
				{
					return (REMOVE);
				}

				break;
			}
		}

		return (NO_REMOVE);
	}
};

class Functor_DoesDamage
	: public Fire_Functor
{
	FUNCTOR_HEADER(Functor_DoesDamage);

  public:
	bool Execute(MechWarrior4::Weapon& weapon, MechWarrior4::WeaponUpdate& weapon_update)
	{
		Check_Object(&weapon);

		if (weapon.IsDerivedFrom(MechWarrior4::ProjectileWeapon::DefaultData) == true)
		{
			MechWarrior4::ProjectileWeapon* projectile_weapon = Cast_Object(MechWarrior4::ProjectileWeapon*,&weapon);
			const ProjectileWeapon__GameModel* game_model = projectile_weapon->GetGameModel();

			if (game_model->damageAmount <= 0)
			{
				return (REMOVE);
			}

			if (weapon.IsDerivedFrom(MechWarrior4::HighExplosiveWeapon::DefaultData) == true)
			{
				if (m_Params.can_commit_suicide == false)
				{
					return (REMOVE);
				}

				if (m_Params.fire_data.GetLine().m_length > min_high_explosive_distance)
				{
					return (REMOVE);
				}

				return (NO_REMOVE);
			}
			
			if (weapon.IsDerivedFrom(MechWarrior4::ArtilleryWeapon::DefaultData) == true)
			{
				return (REMOVE);
			}

			if ((m_Params.can_fire_narc == false) &&
				(weapon.IsDerivedFrom(MechWarrior4::NarcWeapon::DefaultData) == true))
			{
				return (REMOVE);
			}
		}

		return (NO_REMOVE);
	}
};

class Functor_Fire
	: public Fire_Functor
{
	FUNCTOR_HEADER(Functor_Fire);

  public:
	bool Execute(MechWarrior4::Weapon& weapon, MechWarrior4::WeaponUpdate& weapon_update)
	{
		Check_Object(&weapon);
		Verify(WeaponCanFire(weapon,0,m_Params.min_delay,m_Params.max_delay) == true);

		std::pair<FireData,Stuff::Time> result = CalculateFireData(m_Params,weapon);

		if ((m_Params.per_weapon_raycasting == true) &&
			(weapon.IsDerivedFrom(MechWarrior4::MissileWeapon::DefaultData) == false))
		{
			FireData ray_cast_data(m_Params.fire_data.GetSource(),
								   weapon,
								   result.first.GetDest());
			ray_cast_data.Project();

			Adept::Entity* non_const_who_to_hit = const_cast<Adept::Entity*>(m_Params.who_to_hit);

			switch (ray_cast_data.GetHitResult(non_const_who_to_hit))
			{
				case FireData::HIT_NOTHING:
				case FireData::HIT_SOMETHING_ELSE:
				{
					return (REMOVE);
				}
			}
		}

		{
			COMBAT_LOGIC("Update Attacking::Firing::With Current Query::Weapon");
			TIME_FUNCTION(tWeaponFiring);
			TIME_FUNCTION(tCombatAITime_WeaponFiring);

			result.first.GetQuery().m_raySource = m_Params.component;

			weapon.FireWeapon(&(result.first.GetQuery()),result.second, Stuff::Point3D::Identity);
		}

		// begin: fill in all the weapon_update members
			weapon_update.targetOffset = Point3D::Identity;

			if ((weapon_update.target.GetCurrent() != 0) &&
				(weapon_update.target.GetCurrent() != Adept::Map::GetInstance()))
			{
				weapon_update.targetOffset.MultiplyByInverse(m_Params.fire_data.GetDest(),weapon_update.target.GetCurrent()->GetLocalToWorld());
			}
			else
			{
				weapon_update.targetOffset = m_Params.fire_data.GetDest();
			}

			weapon_update.lockTime = (Stuff::Scalar)result.second;

			weapon_update.weaponFired |= (0x1 << weapon.GetWeaponID());

			if (weapon.IsDerivedFrom(MechWarrior4::MissileWeapon::DefaultData) == true)
			{
				MechWarrior4::MissileWeapon* missile_weapon = Cast_Object(MechWarrior4::MissileWeapon*,&weapon);

				weapon_update.AntiWeaponCount[weapon.GetWeaponID()] = missile_weapon->GetAMSNumber();
			}

		// end


		return (NO_REMOVE);
	}
};

class Functor_HeatLevelOK
	: public Fire_Functor
{
  public:
	Functor_HeatLevelOK(const FireParamPackage& params)
	  : m_Params(params)
	  , m_HeatGeneratedSoFar(0)
	{
	}

  private:
	const FireParamPackage& m_Params;
	Stuff::Scalar m_HeatGeneratedSoFar;

  public:
	bool Execute(MechWarrior4::Weapon& weapon, MechWarrior4::WeaponUpdate& weapon_update)
	{
		Check_Object(&weapon);

		Stuff::Scalar heat = weapon.GetTotalHeatGenerated();

		if ((m_Params.can_generate_heat == false) &&
			(heat > 0))
		{
			return (REMOVE);
		}

		if (FireStyles::FireStyle::gHeatManagementEnabled == false)
		{
			return (NO_REMOVE);
		}

		if (weapon.IsDerivedFrom(MechWarrior4::HighExplosiveWeapon::DefaultData) == true)
		{
			return (NO_REMOVE);
		}

		if (m_Params.current_heat + m_HeatGeneratedSoFar + heat >= (m_Params.max_heat * 0.9f))
		{
			return (REMOVE);
		}

		m_HeatGeneratedSoFar += heat;

		return (NO_REMOVE);
	}
};

class Functor_HitsEntity
	: public Fire_Functor
{
  public:
	Functor_HitsEntity(const FireParamPackage& params, Adept::Entity* entity)
	  : m_Params(params)
	  , m_Entity(entity)
	{
	}

  private:
	const FireParamPackage& m_Params;
	Adept::Entity* m_Entity;

  public:
	bool Execute(MechWarrior4::Weapon& weapon, MechWarrior4::WeaponUpdate& weapon_update)
	{
		Verify(WeaponCanFire(weapon,0,m_Params.min_delay,m_Params.max_delay) == true);

		if (weapon.IsDerivedFrom(MechWarrior4::HighExplosiveWeapon::DefaultData) == true)
		{
			return (NO_REMOVE);
		}

		if ((weapon.GetGameModel() != 0) &&
			(weapon.GetGameModel()->splashRadius > m_Params.fire_data.GetLine().m_length))
		{
			return (REMOVE);
		}

		return (NO_REMOVE);
	}
};


class Functor_WontFriendlyFire
	: public Fire_Functor
{
  public:
	Functor_WontFriendlyFire(const FireParamPackage& params)
	  : m_Params(params)
	{
	}

  private:
	const FireParamPackage& m_Params;

  public:
	bool Execute(MechWarrior4::Weapon& weapon, MechWarrior4::WeaponUpdate& weapon_update)
	{
		if (m_Params.fire_data.GetSource().IsDerivedFrom(MWObject::DefaultData) == true)
		{
			MWObject* mwobject = Cast_Object(MWObject*,&m_Params.fire_data.GetSource());
			if (MightHitFriendlies(m_Params.fire_data.GetLine(),GetMaxWeaponDistance(weapon),*mwobject,m_Params.intended_target) == true)
			{
				return (REMOVE);
			}
		}

		return (NO_REMOVE);
	}
};



#undef FUNCTOR_EXECUTE_FUNCTION

typedef std::vector<MechWarrior4::Weapon*> weapon_list;
typedef std::vector<Fire_Functor*> functor_list;

void GetQualifiedWeapons(Stuff::ChainOf<MechWarrior4::Weapon*>& weapons,
						 functor_list& qualifiers,
						 weapon_list& weapons_out,
						 MechWarrior4::WeaponUpdate& weapon_update)
{
	Stuff::ChainIteratorOf<MechWarrior4::Weapon*> i(&weapons);
	MechWarrior4::Weapon* weapon = 0;
	while ((weapon = i.ReadAndNext()) != 0)
	{
		Check_Object(weapon);

		if (weapon->IsDestroyed() == false)
		{
			bool qualified = true;

			{for (functor_list::iterator i = qualifiers.begin();
				  i != qualifiers.end();
				  ++i)
			{
				if ((*i)->Execute(*weapon,weapon_update) == REMOVE)
				{
					qualified = false;
					break;
				}
			}}

			if (qualified == true)
			{
				weapons_out.push_back(weapon);
			}
		}
	}
}


//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Maximum fire : fire as much as you can, as often as you can
//

void FireStyles::MaximumFire::Execute(const FireParamPackage& params,
									  Stuff::ChainOf<MechWarrior4::Weapon *>& weapons,
									  Adept::Entity* target,
									  MechWarrior4::WeaponUpdate& weapon_update)
{
	Functor_CanFire		can_fire(params);								// which weapons can hit the target point?
	Functor_CorrectFireSource correct_fire_source(params);				// which weapons fire from the correct source (left arm only/right arm only/any & all)?
	Functor_DoesDamage	does_damage(params);							// which weapons actually do some kind of damage?
	Functor_HeatLevelOK	heat_level_ok(params);							// which weapons can fire without overheating?
	Functor_HitsEntity	hits_entity(params,target);						// which weapons hit the intended entity?
	Functor_WontFriendlyFire no_friendly_fire(params);					// which weapons have no chance of causing friendly fire
	Functor_Fire		fire(params);									// OK, fire!

	functor_list qualifiers;
	qualifiers.push_back(&can_fire);
	qualifiers.push_back(&correct_fire_source);
	qualifiers.push_back(&does_damage);
	qualifiers.push_back(&heat_level_ok);
	qualifiers.push_back(&hits_entity);
	qualifiers.push_back(&no_friendly_fire);
	qualifiers.push_back(&fire);

	weapon_list w;
	GetQualifiedWeapons(weapons,qualifiers,w,weapon_update);
}
