#include "MW4Headers.hpp"

#include <Adept\DamageObject.hpp>
#include "AI_FindObject.hpp"
#include "Sensor.hpp"
#include "MWDamageObject.hpp"
#include "rail_move.hpp"
#include "CombatAI.hpp"
#include "MWPlayer.hpp"
#include "MWMission.hpp"
#include "VehicleInterface.hpp"
#include "MFB.hpp"


const Stuff::Scalar min_distance = 10.0f;
const Stuff::Scalar max_distance = 1000.0f;
const Stuff::Scalar min_tonnage = 40.0f;
const Stuff::Scalar max_tonnage = 105.0f;
const Stuff::Scalar find_object_shot_by_time = 2.0f;
const Stuff::Scalar building_data_refresh_rate = 4.0f;
const Stuff::Scalar find_object_cache_delay = 3.0f;

extern __int64 tCombatAITime_FindObject;


using namespace MW4AI;
using namespace MW4AI::FindObject;
using namespace MechWarrior4;
typedef SensorData sensor_data;

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Scoring functions
//

inline Stuff::Scalar ScoreDistance(Adept::Entity& src, Adept::Entity& dest)
{
	Vector3D delta;
	delta.Subtract((Point3D)src.GetLocalToWorld(),(Point3D)dest.GetLocalToWorld());
	delta.y = 0;

	return (NormalizedValue(delta.GetLengthSquared(),
							min_distance * min_distance,
							max_distance * max_distance,
							true));
}

inline Stuff::Scalar ScoreTonnage(Adept::Entity& entity)
{
	if (entity.IsDerivedFrom(MWObject::DefaultData) == false)
	{
		return (0);
	}

	MWObject* object = Cast_Object(MWObject*,&entity);
	return (NormalizedValue(object->GetTonage(),min_tonnage,max_tonnage));
}

inline Stuff::Scalar ScoreDamage(Adept::Entity& object)
{
	if (object.IsDerivedFrom(MWObject::DefaultData) == false)
	{
		return (0);
	}

	MWObject* v = Cast_Object(MWObject*,&object);

	Stuff::SortedChainIteratorOf<MWInternalDamageObject*,int> i(&(v->internalDamageObjects));

	MWInternalDamageObject* o;
	Stuff::Scalar total_damage = 0;
	Stuff::Scalar max_damage = 0;

	while ((o = i.ReadAndNext()) != 0)
	{
		total_damage += o->currentInternalDamage;
		max_damage += o->baseInternalDamage;
	}

	if (Small_Enough(max_damage))
	{
		return (0);
	}

	return (NormalizedValue(total_damage,max_damage * 0.66f,max_damage,true));	// we crank up the minimum so 33% damage is considered 100% score
}


//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// ScoreByCriteria()
// Scores the given object (with a normalized score) using the given find criteria

Stuff::Scalar ScoreByCriteria(MWObject& src,
							  MWObject& target,
							  int criteria)
{
	Verify((MW4AI::FindObject::Criteria)criteria >= MW4AI::FindObject::FC_FIRST);
	Verify((MW4AI::FindObject::Criteria)criteria <= MW4AI::FindObject::FC_LAST);

	switch ((MW4AI::FindObject::Criteria)criteria)
	{
		case MW4AI::FindObject::FC_BEST_TARGET:
		{
			return (0.45f * ScoreDistance(src,target) +
					0.35f * ScoreDamage(target) +
					0.2f * ScoreTonnage(target));
		}
		case MW4AI::FindObject::FC_GREATEST_THREAT:
		{
			return (0.6f * ScoreDistance(src,target) +
					0.4f * ScoreTonnage(target));
		}
		case MW4AI::FindObject::FC_LEAST_THREAT:
		{
			return (1.0f - (0.6f * ScoreDistance(src,target) +
							0.4f * ScoreTonnage(target)));
		}
		case MW4AI::FindObject::FC_MOST_DAMAGED:
		{
			return (ScoreDamage(target));
		}
		case MW4AI::FindObject::FC_NEAREST:
		{
			return (ScoreDistance(src,target));
		}
	}

	Verify(!"Should never get here.");
	return (ScoreDistance(src,target));
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// QualifiesByAlignment
// Indicates whether a given sensor data object qualifies by testing its alignment
// against the given alignment-specification value.

bool QualifiesByAlignment(MWObject& src,
						  MWObject& target,
						  int fa_alignment)
{
	Verify(fa_alignment != 0);								// value of 0 makes no sense
	Verify(fa_alignment <= MW4AI::FindObject::FA_ANY);

	if (&src == &target)
	{
		return (false);
	}

	switch (src.GetRelativeAlignment(target.GetAlignment()))
	{
		case Adept::Entity::Enemy:
		{
			return ((fa_alignment & MW4AI::FindObject::FA_ENEMY) != 0);
		}

		case Adept::Entity::Player:
		{
			return ((fa_alignment & MW4AI::FindObject::FA_FRIENDLY) != 0);
		}

		case Adept::Entity::DefaultAlignment:
		{
			return ((fa_alignment & MW4AI::FindObject::FA_NEUTRAL) != 0);
		}
	}

	return (false);
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// QualifiesByType
// Indicates whether a given sensor data object qualifies by testing its
// object type against the given alignment-specification value.

bool QualifiesByType(MWObject& src,
					 MWObject& target,
					 int ft_type)
{
	Verify(ft_type != 0);									// 0 is invalid: you must specify a type

	if (target.GetTargetDesirability() == MWObject::desirability_never)
	{
		return (false);
	}

	if (ft_type & FT_BUILDING)
	{
		if (target.IsDerivedFrom(Building::DefaultData) == true)
		{
			return (true);
		}
	}

	if (ft_type & FT_MFB)
	{
		if (target.IsDerivedFrom(MFB::DefaultData) == true)
		{
			return (true);
		}
	}

	if ((target.GetTargetDesirability() == MWObject::desirability_invalid) &&
		(target.IsPlayerVehicle() == false))
	{
		if (target.GetAI() == 0)
		{
			return (false);
		}

		if ((target.weaponChain.IsEmpty() == true) &&
			(target.IsDerivedFrom(Mech::DefaultData) == false) &&
			(!(ft_type & FT_UNARMED)))
		{
			return (false);
		}
	}

	if (ft_type & FT_MECH)
	{
		if (target.IsDerivedFrom(Mech::DefaultData) == true)
		{
			return (true);
		}
	}

	if (ft_type & FT_AIRPLANE)
	{
		if (target.IsDerivedFrom(Airplane::DefaultData) == true)
		{
			return (true);
		}
	}

	if (ft_type & FT_VEHICLE)
	{
		if ((target.IsDerivedFrom(Vehicle::DefaultData) == true) &&
			(target.IsDerivedFrom(MFB::DefaultData) == false) &&
			(target.IsDerivedFrom(Mech::DefaultData) == false) &&
			(target.IsDerivedFrom(Airplane::DefaultData) == false))
		{
			return (true);
		}
	}

	if (ft_type & FT_BOAT)
	{
		if (target.IsDerivedFrom(Boat::DefaultData) == true)
		{
			return (true);
		}
	}

	if (ft_type & FT_TURRET)
	{
		if (target.IsDerivedFrom(Building::DefaultData) == true)
		{
			return (true);
		}
	}

	return (false);
}


bool Qualifies(MWObject& src,
			   MWObject& target,
			   int fa_alignment,
			   int ft_type)
{
	if (QualifiesByAlignment(src,target,fa_alignment) == false)
	{
		return (false);
	}

	return (QualifiesByType(src,target,ft_type));
}


void FillNameTableData(MWObject& src,
					   std::vector<MWObject*>& objects_to_search)
{
	Adept::NameTable* table = Adept::NameTable::GetInstance();

	{
		int size(table->nameTableArray[NameTable::VehicleArray].GetLength());

		{for (int i = 0;
			  i < size;
			  ++i)
		{
			NameTableEntry *data = &table->nameTableArray [NameTable::VehicleArray][i];
			Adept::Entity* ent = data->dataPointer->GetCurrent();

			if ((ent != 0) &&
				(ent->IsDestroyed() == false) &&
				(ent != &src) &&
				(ent->IsDerivedFrom(MWObject::DefaultData) == true))
			{
				objects_to_search.push_back(Cast_Object(MWObject*,ent));
			}
		}}
	}

	{
		int size(table->nameTableArray[NameTable::PlayerArray].GetLength());

		{for (int i = 0;
			  i < size;
			  ++i)
		{
			NameTableEntry *data = &table->nameTableArray [NameTable::PlayerArray][i];
			Adept::Entity* ent = data->dataPointer->GetCurrent ();

			if ((ent != 0) &&
				(ent->IsDerivedFrom(Adept::Player::DefaultData) == true))
			{
				Adept::Player* player = Cast_Object(Adept::Player*,ent);

				if ((player->vehicle != 0) &&
					(player->vehicle->IsDestroyed() == false) &&
					(player->vehicle != &src) &&
					(player->vehicle->IsDerivedFrom(MWObject::DefaultData) == true))
				{
					objects_to_search.push_back(Cast_Object(MWObject*,player->vehicle));
				}
			}
		}}
	}
}

void FillSensorData(MWObject& src,
					const Sensor::sensorDataArray& sensed_data,
					int number_of_contacts,
					std::vector<MWObject*>& sensor_data_list)
{
	if ((src.GetAI() != 0) &&
		(src.GetAI()->GetExtendedSensorData(sensor_data_list) == true))
	{
		return;
	}

	if (number_of_contacts == 0)
	{
		return;
	}

	sensor_data_list.reserve(number_of_contacts);

	{for (int i = 0;
		  i < number_of_contacts;
		  ++i)
	{
		if ((sensed_data[i] != 0) &&
			(sensed_data[i]->object.GetCurrent() != 0) &&
			(sensed_data[i]->object.GetCurrent() != &src))
		{
			sensor_data_list.push_back(sensed_data[i]->object.GetCurrent());
		}
	}}
}


// FindObject(): finds an object given the given sensor (sensor) and qualifier (object_type)

long MW4AI::FindObject::Find(MWObject& src,
							 int fa_alignment,
							 int ft_type,
							 int fc_criteria,
							 int ff_flags,
							 Stuff::Scalar distance_limit,
							 const Adept::ObjectID* except_object)
{
	AI_LOGIC("Find Object");
	TIME_FUNCTION(tCombatAITime_FindObject);

	Verify(ff_flags <= FF_WHO_SHOT + FF_SEEPLAYER + FF_LOOK_EVERYWHERE + FF_TARGETLOS);

	if (distance_limit == 0)
	{
		distance_limit = MW4AI::FindObject::DISTANCE_LIMIT;
	}

	if (src.m_FindObjectList == 0)
	{
		src.m_FindObjectList = new MW4AI::FindObjectList;
	}

	std::vector<MWObject*>& objects_to_search = src.m_FindObjectList->objects;
	objects_to_search.clear();

	if ((ff_flags & MW4AI::FindObject::FF_SEEPLAYER) &&
		(MWMission::GetInstance()->GetGameModel() != 0) &&
		(Adept::Player::GetInstance() != 0))
	{
		MWPlayer* p = Cast_Object(MWPlayer*,Adept::Player::GetInstance());
		if ((p != 0) &&
			(p->GetInterface() != 0) &&
			(p->GetInterface()->vehicle != 0))
		{
			Vehicle* vehicle = p->GetInterface()->vehicle;

			if ((QualifiesByAlignment(src,*vehicle,fa_alignment) == true) &&
				(vehicle->IsDestroyed() == false) &&
				(vehicle->objectID != src.objectID))
			{
				const Adept::Mission::GameModel* game_model = MWMission::GetInstance()->GetGameModel();
				Stuff::Scalar fog_distance = game_model->m_generalFogEnd;

				if (fog_distance > 1200)
				{
					fog_distance = 1200;
				}

				if (fog_distance > distance_limit)
				{
					fog_distance = distance_limit;
				}

				Stuff::Point3D player_vehicle_pos(vehicle->GetLineOfSight());
				Stuff::Point3D my_pos(src.GetLineOfSight());

				Stuff::LinearMatrix4D src_los;
				src.GetLineOfSight(src_los);

				if ((GetLengthSquared(player_vehicle_pos,my_pos) < fog_distance * fog_distance) &&
					(MatrixFacesPoint(src_los,player_vehicle_pos,Stuff::Pi_Over_4) == true))
				{
					if ((src.m_FindObjectCache != 0) &&
						(src.m_FindObjectCache->m_RayCastTime + find_object_cache_delay < gos_GetElapsedTime()))
					{
						delete src.m_FindObjectCache;
						src.m_FindObjectCache = 0;
					}

					MW4AI::FireData::HIT_RESULT hit_result;

					if (src.m_FindObjectCache != 0)
					{
						hit_result = src.m_FindObjectCache->m_HitResult;
					}
					else
					{
						FireData fire_data(src,my_pos,player_vehicle_pos);
						fire_data.Project();
						hit_result = fire_data.GetHitResult(vehicle);

						src.m_FindObjectCache = new MW4AI::FindObjectCache((Stuff::Scalar)gos_GetElapsedTime(),hit_result);
					}

					if (hit_result == FireData::HIT_TARGET)
					{
						if (src.GetSensor() == 0)
						{
							return (vehicle->objectID);
						}
						else
						{
							objects_to_search.push_back(vehicle);
						}
					}
				}
			}
		}
	}

	if ((src.GetSensor() == 0) &&
		(objects_to_search.size() == 0))
	{
		return (-2);
	}

	if ((ff_flags & MW4AI::FindObject::FF_WHO_SHOT) &&
		(src.GetAI() != 0))
	{
		Adept::Entity* e = src.GetAI()->ShotBy(gos_GetElapsedTime() - find_object_shot_by_time);

		if ((e != 0) &&
			(e != &src) &&
			(e->IsDerivedFrom(MWObject::DefaultData) == true) &&
			(e->IsDestroyed() == false))
		{
			MWObject* v = Cast_Object(MWObject*,e);
			Check_Object(v);

			if (QualifiesByAlignment(src,*v,fa_alignment) == true)
			{
				return (v->objectID);
			}
		}
	}

	if (ff_flags & MW4AI::FindObject::FF_LOOK_EVERYWHERE)
	{
		FillNameTableData(src,objects_to_search);
	}
	else
	{
		FillSensorData(src,
					   src.GetSensor()->RefreshAndGetSensorData(),
					   src.GetSensor()->numberOfContacts,
					   objects_to_search);

		if (ft_type & FT_BUILDING)
		{
			if (src.GetSensor()->GetLastTimeUpdatedBuildingData() < gos_GetElapsedTime() + building_data_refresh_rate + Stuff::Random::GetFraction())
			{
				src.GetSensor()->UpdateBuildingData();
			}

			FillSensorData(src,
						   src.GetSensor()->GetBuildingData(),
						   src.GetSensor()->numberOfBuildingContacts,
						   objects_to_search);
		}
	}

	if (objects_to_search.size() == 0)
	{
		return (-2);
	}

	const Stuff::Scalar distance_limit_squared = distance_limit * distance_limit;

	Stuff::Scalar best_score = 0;
	std::vector<MWObject*>::const_iterator best_found = objects_to_search.end();

	Stuff::Point3D my_pos(src.GetLocalToWorld());

	{for (std::vector<MWObject*>::const_iterator i = objects_to_search.begin();
		  i != objects_to_search.end();
		  ++i)
	{
		if ((except_object != 0) &&
			((*i)->objectID == *except_object))
		{
			continue;
		}

		if (ff_flags & MW4AI::FindObject::FF_TARGETLOS) 
		{
			
			// Test line of sight
			Stuff::Point3D target_pos((*i)->GetLineOfSight());
			Stuff::Point3D my_pos(src.GetLineOfSight());
			FireData fire_data(src,my_pos,target_pos);
			fire_data.Project();
			if (fire_data.GetHitResult((*i)) != FireData::HIT_TARGET)
			{
				continue;
			}
		}

		if (Qualifies(src,**i,fa_alignment,ft_type) == true)
		{
			Point3D pos((*i)->GetLocalToWorld());

			Scalar distance(0);

			if (src.GetAI() != 0)
			{
				distance = src.GetAI()->GetLeastSquaredSensorDistance(pos);
			}

			if (distance == 0)
			{
				Vector3D delta;
				delta.Subtract(my_pos,pos);
				delta.y = 0;

				distance = delta.GetLengthSquared();
			}

			if (distance < distance_limit_squared)
			{
				Stuff::Scalar s;

				if ((*i)->GetTargetDesirability() == MWObject::desirability_invalid)
				{
					s = ScoreByCriteria(src,**i,fc_criteria);
				}
				else
				{
					Verify((*i)->GetTargetDesirability() != MWObject::desirability_never);
					s = (*i)->GetTargetDesirability();
				}

				Verify(s >= 0);
				Verify(s <= 1);

				if (s >= best_score)
				{
					if (s == 1)
					{
						return ((*i)->objectID);
					}

					best_score = s;
					best_found = i;
				}
			}
		}
	}}

	if (best_found == objects_to_search.end())
	{
		return (-2);							// -2 = ABL constant for nothing found
	}

	return ((*best_found)->objectID);
}
