#include "MW4Headers.hpp"

#include "AI_FireData.hpp"

#include <Adept\CollisionGrid.hpp>
#include <Adept\Map.hpp>
#include "MWMover.hpp"
#include "aiutils.hpp"
#include "Weapon.hpp"
#include "ProjectileWeapon.hpp"
#include "Vehicle.hpp"
#include "MWDamageObject.hpp"
#include <Adept\Entity.hpp>
#include <Adept\Site.hpp>



using namespace MW4AI;
using namespace Stuff;

extern __int64 tCombatAITime_RayCasting;
extern __int64 tRayCasting;

Stuff::Scalar ground_hit_fudge_distance = 20.0f;


// TODO: how to ensure that our lifetime is longer than the source?  refcounting?  will it be an issue?

FireData::FireData(Adept::Entity& source,
				   const Stuff::Point3D& src,
				   const Stuff::Point3D& dest)
	: m_Query(&m_Line,&m_Normal,Adept::Entity::CanBeShotFlag,&source)
	, m_Normal(1,0,0)
	, m_Source(source)
{
	Check_Object(&source);

	CreateLine(src,dest);
}

FireData::FireData(Adept::Entity& source,
				   MechWarrior4::Weapon& weapon,
				   const Stuff::Point3D& dest)
	: m_Query(&m_Line,&m_Normal,Adept::Entity::CanBeShotFlag,&source)
	, m_Normal(1,0,0)
	, m_Source(source)
{
	Check_Object(&source);

	Stuff::Point3D src;
	if (weapon.sitePointer == 0)
	{
		src = weapon.GetParentVehicle()->GetLocalToWorld();
	}
	else
	{
		src = weapon.sitePointer->GetLocalToWorld();
	}

	CreateLine(src,dest);
}

void FireData::CreateLine(const Stuff::Point3D& src,
						  const Stuff::Point3D& dest)
{
	Vector3D target_direction;
	target_direction.Subtract(dest,src);

	m_Line.SetOrigin(src);

	if (Small_Enough(target_direction))
	{
		m_Line.SetDirection(UnitVector3D(1,0,0));
		m_Line.m_length = 0;
	}
	else
	{
		UnitVector3D origin_direction;

		if (target_direction.GetLengthSquared() == 0)
		{
			target_direction = Stuff::Point3D(1,0,0);
		}

		origin_direction.Normalize(target_direction);
		m_Line.SetDirection(origin_direction);
		m_Line.m_length = target_direction.GetLength();
	}
}

FireData::FireData(const FireData& copy_from)
	: m_Query(copy_from.m_Query)
	, m_Normal(copy_from.m_Normal)
	, m_Line(copy_from.m_Line)
	, m_Source(copy_from.m_Source)
{
	m_Query.m_line = &m_Line;
	m_Query.m_normal = &m_Normal;
}

Adept::Entity* FireData::Project()
{
	if ((Small_Enough(m_Line.m_length)) ||
		(Small_Enough(m_Line.m_direction)))
	{
		return (0);
	}

	Adept::Entity *entity_hit;
	Check_Object(CollisionGrid::Instance);

	{
		TIME_FUNCTION(tCombatAITime_RayCasting);
		TIME_FUNCTION(tRayCasting);
		entity_hit = CollisionGrid::Instance->ProjectLine(&m_Query);
	}

	m_Query.m_raySource = entity_hit;

	return (entity_hit);
}

FireData::HIT_RESULT FireData::GetHitResult(Adept::Entity* desired_target, bool ignore_dead_components) const
{
	Verify(Adept::Map::GetInstance() != 0);

	if (desired_target == 0)
	{
		if ((m_Query.m_raySource == 0) ||
			(m_Query.m_raySource == Adept::Map::GetInstance()))
		{
			return (HIT_TARGET);
		}

		return (HIT_SOMETHING_ELSE);
	}

	Check_Object(desired_target);

	if (m_Query.m_raySource == 0)
	{
		return (HIT_NOTHING);
	}

	if ((m_Query.m_raySource == Adept::Map::GetInstance()) &&
		(m_Query.m_line != 0))
	{
		if (m_Query.m_line->m_length + ground_hit_fudge_distance > GetApproximateLength(GetOrigin(),(Stuff::Point3D)desired_target->GetLocalToWorld()))
		{
			return (HIT_NOTHING);
		}

		return (HIT_SOMETHING_ELSE);
	}

	if (m_Query.m_raySource->IsDerivedFrom(MWMover::DefaultData) == false)
	{
		if (m_Query.m_raySource == desired_target)
		{
			return (HIT_TARGET);
		}

		return (HIT_SOMETHING_ELSE);
	}

	MechWarrior4::MWMover* mover = Cast_Object(MechWarrior4::MWMover*,m_Query.m_raySource);
	Check_Object(mover);

	if (mover == desired_target)
	{
		return (HIT_TARGET);
	}

	if (ignore_dead_components == false)
	{
		if ((mover->damageObject != 0) &&
			(mover->damageObject->internalDamageObject != 0) &&
			(mover->damageObject->internalDamageObject->GetCurrentDamageLevel() == Adept::InternalDamageObject::Destroyed))
		{
			return (HIT_NOTHING);
		}
	}

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

	if ((mover->GetParentVehicle() == desired_target) ||
	    (mover->GetParentVehicle()->GetAlignment() != m_Source.GetAlignment()))
	{
		return (HIT_TARGET);
	}

	return (HIT_SOMETHING_ELSE);
}

bool FireData::HitsGround() const
{
	if ((m_Query.m_raySource != 0) &&
		(m_Query.m_raySource == Adept::Map::GetInstance()) &&
		(m_Query.m_line != 0))
	{
		return (true);
	}

	return (false);
}

