
#include "MW4Headers.hpp"

#include "AI_Condition.hpp"
#include "AI_Weapons.hpp"
#include "MWPlayer.hpp"
#include "VehicleInterface.hpp"
#include "Torso.hpp"
#include "MWMission.hpp"
#include "JumpJet.hpp"



using namespace MW4AI;
using namespace MW4AI::Conditions;

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

Fuzzy Conditions::HittingSomethingElse(TacticInterface& i)
{
	AILOGFUNC("Conditions::HittingSomethingElse");

	if (i.GetUnableToFireDuration(4.0f) == true)
	{
		LOG_AND_RETURN(1);
	}
	LOG_AND_RETURN(0);
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// AnyWeaponReady
//

Fuzzy Conditions::AnyWeaponReady(TacticInterface& i)
{
	AILOGFUNC("Conditions::AnyWeaponReady");

	if (MW4AI::AnyWeaponReady(i.GetSelf()) == true)
	{
		LOG_AND_RETURN(1);
	}

	LOG_AND_RETURN(0);
}

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

Fuzzy Conditions::ProjectileApproaching(TacticInterface& i, Stuff::Scalar min_distance)
{
	AILOGFUNC("Conditions::ProjectileApproaching");
	AILOG1(min_distance);

	if (i.ProjectileApproaching() == true)
	{
		Stuff::Point3D delta;
		delta.Subtract(i.GetProjectileOrigin(),(Stuff::Point3D)i.GetSelf().GetLocalToWorld());
		if (delta.GetLengthSquared() < min_distance * min_distance)
		{
			LOG_AND_RETURN(0);
		}

		LOG_AND_RETURN(1);
	}

	LOG_AND_RETURN(0);
}

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

Fuzzy Conditions::Fired(TacticInterface& i, Stuff::Scalar within_when)
{
	AILOGFUNC("Conditions::Fired");
	AILOG1(within_when);

	if (i.FiredWithin(within_when) == true)
	{
		LOG_AND_RETURN(1);
	}

	LOG_AND_RETURN(0);
}

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

Fuzzy Conditions::TargetedByEnemy(TacticInterface& i, Stuff::Scalar duration)
{
	AILOGFUNC("Conditions::TargetedByEnemy");
	AILOG1(duration);

	if (i.GetTimeTargetedByEnemy() >= duration)
	{
		LOG_AND_RETURN(1);
	}

	LOG_AND_RETURN(0);
}

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

Fuzzy Conditions::Rammed(TacticInterface& i, Stuff::Scalar duration)
{
	AILOGFUNC("Conditions::Rammed");
	AILOG1(duration);

	if (i.GetLastTimeRammedTarget() + duration > gos_GetElapsedTime())
	{
		LOG_AND_RETURN(1);
	}

	LOG_AND_RETURN(0);
}

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

Fuzzy Conditions::InsideRadiusOfTarget(TacticInterface& i, Stuff::Scalar radius)
{
	AILOGFUNC("Conditions::InsideRadiusOfTarget");
	AILOG1(radius);

	if (i.GetDistanceFromTargetSquared() <= radius * radius)
	{
		LOG_AND_RETURN(1);
	}

	LOG_AND_RETURN(0);
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
Fuzzy Conditions::InsideRadiusOfTarget(TacticInterface&i, Stuff::Scalar radius, Stuff::Scalar when)
{
	AILOGFUNC("Conditions::InsideRadiusOfTarget");
	AILOG2(radius,when);

	Stuff::Point3D my_pos(i.GetSelf().GetLocalToWorld());
	Stuff::Point3D target_pos;
	i.GetTarget().EstimateFuturePosition(&target_pos,(Scalar)when,true);
	my_pos.y = 0;
	target_pos.y = 0;

	if (GetLengthSquared(my_pos,target_pos) <= radius * radius)
	{
		LOG_AND_RETURN(1);
	}

	LOG_AND_RETURN(0);
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
Fuzzy Conditions::InsideRadiusOfRamTarget(TacticInterface& i, Stuff::Scalar radius)
{
	AILOGFUNC("Conditions::InsideRadiusOfRamTarget");
	AILOG(radius);

	Verify(i.GetSelf_AsVehicle() != 0);

	Stuff::Scalar time_to_reach = GetApproximateLength((Stuff::Point3D)i.GetSelf().GetLocalToWorld(),
													   (Stuff::Point3D)i.GetTarget().GetLocalToWorld()) / i.GetSelf_AsVehicle()->GetGameModel()->maxSpeed;

	Stuff::Point3D target_pos;
	i.GetTarget().EstimateFuturePosition(&target_pos,time_to_reach,false);

	Stuff::Point3D my_pos((Stuff::Point3D)i.GetSelf().GetLocalToWorld());
	my_pos.y = 0;
	target_pos.y = 0;

	if (GetLengthSquared(my_pos,target_pos) <= radius * radius)
	{
		LOG_AND_RETURN(1);
	}

	LOG_AND_RETURN(0);
}

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

Fuzzy MW4AI::Conditions::InsideWeaponsRadiusOfTarget(TacticInterface& i,
													  TacticInterface::WR_WHO who,
													  TacticInterface::WR_QUALIFIER type,
													  Stuff::Scalar multiplier)
{
	AILOGFUNC("Conditions::InsideWeaponsRadiusOfTarget");
	AILOG3(who,type,multiplier);

	Stuff::Scalar radius = i.WeaponRange(who,type) * multiplier;

	if (i.GetDistanceFromTargetSquared() <= radius * radius)
	{
		LOG_AND_RETURN(1);
	}
	
	LOG_AND_RETURN(0);
}

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

Fuzzy MW4AI::Conditions::InsidePercentileRangeOf(TacticInterface& i, Stuff::Scalar percentile_range, bool nearest_enemy)
{
	AILOGFUNC("Conditions::InsidePercentileRangeOfTarget");
	AILOG2(percentile_range,nearest_enemy);

	Stuff::Scalar radius = i.GetCombatRadiusForRange(percentile_range);

	if (nearest_enemy == true)
	{	
		Adept::Entity* nearest = i.GetNearest(true);

		if (nearest == 0)
		{
			nearest = &i.GetTarget();
		}

		if (GetLengthSquared((Stuff::Point3D)i.GetSelf().GetLocalToWorld(),
							 (Stuff::Point3D)nearest->GetLocalToWorld()) <= radius * radius)
		{
			LOG_AND_RETURN(1);
		}

		LOG_AND_RETURN(0);
	}

	if (i.GetDistanceFromTargetSquared() <= radius * radius)
	{
		LOG_AND_RETURN(1);
	}
	
	LOG_AND_RETURN(0);
}

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

Fuzzy Conditions::VehicleMoving(TacticInterface& i)
{
	AILOGFUNC("Conditions::VehicleMoving");

	if (i.Moving() == true)
	{
		LOG_AND_RETURN(1);
	}

	LOG_AND_RETURN(0);
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// VehicleCrouching
//

Fuzzy Conditions::VehicleCrouching(TacticInterface& i)
{
	AILOGFUNC("Conditions::VehicleCrouching");

	if (i.Crouching() == true)
	{
		LOG_AND_RETURN(1);
	}

	LOG_AND_RETURN(0);
}

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

Fuzzy Conditions::FacingTarget(TacticInterface& i)
{
	AILOGFUNC("Conditions::FacingTarget");

	const Stuff::LinearMatrix4D& my_matrix = i.GetSelf().GetLocalToWorld();
	Stuff::Point3D target_pos(i.GetTarget().GetLocalToWorld());

	Stuff::UnitVector3D my_forward;
	my_matrix.GetLocalForwardInWorld(&my_forward);
	Stuff::Point3D forward(my_forward);
	forward += (Stuff::Point3D)my_matrix;

	Stuff::UnitVector3D my_backward;
	my_matrix.GetLocalBackwardInWorld(&my_backward);
	Stuff::Point3D backward(my_backward);
	backward += (Stuff::Point3D)my_matrix;

	if (GetLengthSquared(forward,target_pos) < GetLengthSquared(backward,target_pos))
	{
		LOG_AND_RETURN(1);
	}

	LOG_AND_RETURN(0);
}

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

Fuzzy Conditions::InFrontOfTarget(TacticInterface& i, bool fUseTorso)
{
	AILOGFUNC("Conditions::InFrontOfTarget");
	AILOG1(fUseTorso);

	LinearMatrix4D relative_to;

	if ((fUseTorso == true) &&
		(i.GetTarget().GetTorso() != 0) &&
		(i.GetTarget().GetTorso()->twistJoint != 0))
	{
		Check_Object(i.GetTarget().GetTorso());
		relative_to = i.GetTarget().GetTorso()->twistJoint->GetLocalToWorld();
	}
	else
	{
		relative_to = i.GetTarget().GetLocalToWorld();
	}
	
	Stuff::Point3D delta;
	delta.Subtract((Stuff::Point3D)i.GetSelf().GetLocalToWorld(),(Stuff::Point3D)relative_to);

	Stuff::UnitVector3D delta_normalized(delta);

	Stuff::UnitVector3D local_forward;
	relative_to.GetLocalForwardInWorld(&local_forward);

	Stuff::Scalar bearing = delta_normalized * local_forward;
	bearing += 1;
	bearing *= 0.5f;

	if (bearing >= 0.5f)
	{
		LOG_AND_RETURN(1);
	}

	LOG_AND_RETURN(0);	// TODO: bearing is already a fuzzy; should return it as a Fuzzy!
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
Fuzzy Conditions::JumpedWithin(TacticInterface& i, Stuff::Scalar duration)
{
	AILOGFUNC("Conditions::JumpedWithin");
	AILOG1(duration);

	if (i.GetLastTimeJumped() + duration >= gos_GetElapsedTime())
	{
		LOG_AND_RETURN(1);
	}

	LOG_AND_RETURN(0);
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
bool Conditions::Jumping(TacticInterface& i)
{
	AILOGFUNC("Conditions::Jumping");

	if (i.GetSelf().IsDerivedFrom(Mech::DefaultData) == true)
	{
		Mech* mech = Cast_Object(Mech*,&(i.GetSelf()));

		if ((mech->GetJumpJet() != 0) &&
			(mech->GetJumpJet()->executionState->GetState() == JumpJet::ExecutionStateEngine::JumpingState))
		{
			LOG_AND_RETURN(true);
		}
	}

	LOG_AND_RETURN(false);
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
Fuzzy Conditions::CanBeSeenByTarget(TacticInterface& i, Stuff::Scalar within_distance)
{
	AILOGFUNC("Conditions::CanBeSeenByTarget");

	const Stuff::Scalar distance = GetApproximateLength((Stuff::Point3D)i.GetSelf().GetLocalToWorld(),
														(Stuff::Point3D)i.GetTarget().GetLocalToWorld());

	if (distance > within_distance)
	{
		LOG_AND_RETURN(0);
	}

	if ((MWMission::GetInstance() != 0) &&
	    (MWMission::GetInstance()->GetGameModel() != 0))
	{
		const Adept::Mission::GameModel* game_model = MWMission::GetInstance()->GetGameModel();

		if (distance > game_model->m_generalFogEnd)
		{
			LOG_AND_RETURN(0);
		}
	}

	FireData fire_data(i.GetSelf(),
					   i.GetSelf().GetLineOfSight(),
					   i.GetTarget().GetLineOfSight());
	fire_data.Project();

	if (fire_data.GetHitResult(&i.GetTarget()) == FireData::HIT_TARGET)
	{
		LOG_AND_RETURN(1);
	}

	LOG_AND_RETURN(0);
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
Fuzzy Conditions::IsHighOffGround(TacticInterface& i)
{
	AILOGFUNC("Conditions::IsHighOffGround");

	const Stuff::Point3D my_pos(i.GetSelf().GetLocalToWorld());

	BYTE tempmaterial;
	if (my_pos.y - 10 > GetMapY(my_pos.x,my_pos.z,&i.GetSelf(),tempmaterial))
	{
		LOG_AND_RETURN(1);
	}

	LOG_AND_RETURN(0);
}

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

Fuzzy Conditions::PlayerWillHitMe(TacticInterface& i,
								  Stuff::Scalar seconds,
								  Stuff::Scalar threshold)
{
	AILOGFUNC("Conditions::PlayerWillHitMe");
	AILOG2(seconds,threshold);

	if ((Adept::Player::GetInstance() == 0) ||
		(i.GetSelf_AsVehicle() == 0))
	{
		LOG_AND_RETURN(0);
	}

	MechWarrior4::MWPlayer* player = Cast_Object(MechWarrior4::MWPlayer*,Adept::Player::GetInstance());
	Check_Object(player);
	if (player->GetInterface() == 0)
	{
		LOG_AND_RETURN(0);
	}

	Adept::Entity* player_entity = player->GetInterface()->vehicle;
	if ((player_entity == 0) ||
		(player_entity->GetAlignment() == i.GetSelf().GetAlignment()) ||
		(player_entity->IsDerivedFrom(MechWarrior4::Vehicle::DefaultData) == false))
	{
		LOG_AND_RETURN(0);
	}

	threshold *= threshold;

	MechWarrior4::Vehicle* player_vehicle = Cast_Object(MechWarrior4::Vehicle*,player_entity);
	if (GetLengthSquared((Stuff::Point3D)player_vehicle->GetLocalToWorld(),
						 (Stuff::Point3D)i.GetSelf().GetLocalToWorld()) < threshold)
	{
		LOG_AND_RETURN(1);
	}

	Stuff::Point3D next_player_pos;
	player_vehicle->EstimateFuturePosition(&next_player_pos, (Scalar)seconds);
	Stuff::Point3D next_self_pos;
	i.GetSelf_AsVehicle()->EstimateFuturePosition(&next_self_pos, (Scalar)seconds);
	if (GetLengthSquared(next_self_pos,
						 next_player_pos) < threshold)
	{
		LOG_AND_RETURN(1);
	}

	LOG_AND_RETURN(0);
}

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

Fuzzy Conditions::ClusterFuckingSomeone(TacticInterface& i,
										Stuff::Scalar radius)
{
	AILOGFUNC("Conditions::ClusterFuckingSomeone");
	AILOG1(radius);

	if (i.GetLastTimeRammedTarget() + 1.0f > gos_GetElapsedTime())
	{
		LOG_AND_RETURN(1);
	}

	Adept::Entity* entity = i.GetNearest();

	if (entity != 0)
	{
		if (i.GetSelf().IsDerivedFrom(Mech::DefaultData) == false)
		{
			radius *= 0.8f;	// this is to ensure that vehicles smaller than mechs have smaller dodge radii
		}

		if (GetLengthSquared((Stuff::Point3D)i.GetSelf().GetLocalToWorld(),
							 (Stuff::Point3D)entity->GetLocalToWorld()) < radius * radius)
		{
			LOG_AND_RETURN(1);
		}
	}

	LOG_AND_RETURN(0);
}

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

Fuzzy Conditions::EliteLevelBetween(TacticInterface& i, UserConstants::ID min, UserConstants::ID max)
{
	AILOGFUNC("Conditions::EliteLevelBetween");
	AILOG2(min,max);

	int int_min = (int)UserConstants::Instance()->Get(min);
	int int_max = (int)UserConstants::Instance()->Get(max);

	Verify(int_min <= int_max);
	Verify(int_min >= 0);
	Verify(int_max <= 100);

	LOG_AND_RETURN((i.GetEliteLevel() >= int_min) && (i.GetEliteLevel() <= int_max));
}

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

Fuzzy Conditions::AttackThrottleBetween(TacticInterface& i, Stuff::Scalar min, Stuff::Scalar max)
{
	AILOGFUNC("Conditions::AttackThrottleBetween");
	AILOG2(min,max);

	Verify(min >= 0);
	Verify(max <= 100);

	if ((i.GetAttackThrottle() >= min) &&
		(i.GetAttackThrottle() <= max))
	{
		LOG_AND_RETURN(1);
	}

	LOG_AND_RETURN(0);
}
