////////////////////////////////////////////////////////////////////////////
//	Module 		: hit_memory_manager.cpp
//	Created 	: 02.10.2001
//  Modified 	: 19.11.2003
//	Author		: Dmitriy Iassenev
//	Description : Hit memory manager
////////////////////////////////////////////////////////////////////////////

#include "pch_script.h"
#include "hit_memory_manager.h"
#include "memory_space_impl.h"
#include "custommonster.h"
#include "ai_object_location.h"
#include "level_graph.h"
#include "script_callback_ex.h"
#include "script_game_object.h"
#include "agent_manager.h"
#include "agent_member_manager.h"
#include "ai/stalker/ai_stalker.h"
#include "game_object_space.h"
#include "profiler.h"
#include "client_spawn_manager.h"
#include "memory_manager.h"
#include "../xrEngine/IGame_Persistent.h"

#ifndef MASTER_GOLD
#	include "actor.h"
#	include "ai_debug.h"
#endif // MASTER_GOLD

struct CHitObjectPredicate {
	const CObject *m_object;

				CHitObjectPredicate			(const CObject *object) :
					m_object				(object)
	{
	}

	bool		operator()					(const MemorySpace::CHitObject &hit_object) const
	{
		if (!m_object)
			return			(!hit_object.m_object);

		if (!hit_object.m_object)
			return			(false);

		return				(m_object->ID() == hit_object.m_object->ID());
	}
};

CHitMemoryManager::~CHitMemoryManager		()
{
	clear_delayed_objects	();

#ifdef USE_SELECTED_HIT
	xr_delete				(m_selected_hit);
#endif
}

const CHitObject *CHitMemoryManager::hit					(const CEntityAlive *object) const
{
	VERIFY					(m_hits);
	HITS::const_iterator	I = std::find_if(m_hits->begin(),m_hits->end(),CHitObjectPredicate(object));
	if (m_hits->end() != I)
		return				(&*I);

	return					(0);
}

void CHitMemoryManager::add					(const CEntityAlive *entity_alive)
{
	add						(0,Fvector().set(0,0,1),entity_alive,0);
}

void CHitMemoryManager::Load				(LPCSTR section)
{
}

void CHitMemoryManager::reinit				()
{
	m_hits					= 0;
	m_last_hit_object_id	= ALife::_OBJECT_ID(-1);
	m_last_hit_time			= 0;
}

void CHitMemoryManager::reload				(LPCSTR section)
{
#ifdef USE_SELECTED_HIT
	xr_delete				(m_selected_hit);
#endif
	m_max_hit_count			= READ_IF_EXISTS(pSettings,r_s32,section,"DynamicHitCount",1);
}

void CHitMemoryManager::add					(float amount, const Fvector &vLocalDir, const CObject *who, s16 element)
{
#ifndef MASTER_GOLD
	if (who && smart_cast<CActor const*>(who) && psAI_Flags.test(aiIgnoreActor))
		return;
#endif // MASTER_GOLD

	VERIFY						(m_hits);
	if (!object().g_Alive())
		return;

	if (who && (m_object->ID() == who->ID()))
		return;

	if (who && !fis_zero(amount)) {
		m_last_hit_object_id	= who->ID();
		m_last_hit_time			= Device.dwTimeGlobal;
	}

	object().callback(GameObject::eHit)(
		m_object->lua_game_object(), 
		amount,
		vLocalDir,
		smart_cast<const CGameObject*>(who)->lua_game_object(),
		element
	);

	Fvector						direction;
	m_object->XFORM().transform_dir	(direction,vLocalDir);

	const CEntityAlive			*entity_alive = smart_cast<const CEntityAlive*>(who);
	if (!entity_alive || (m_object->tfGetRelationType(entity_alive) == ALife::eRelationTypeFriend))
		return;

	HITS::iterator				J = std::find(m_hits->begin(),m_hits->end(),object_id(who));
	if (m_hits->end() == J) {
		CHitObject						hit_object;

		hit_object.fill					(entity_alive,m_object,!m_stalker ? squad_mask_type(-1) : m_stalker->agent_manager().member().mask(m_stalker));
		
#ifdef USE_FIRST_GAME_TIME
		hit_object.m_first_game_time	= Level().GetGameTime();
#endif
#ifdef USE_FIRST_LEVEL_TIME
		hit_object.m_first_level_time	= Device.dwTimeGlobal;
#endif
		hit_object.m_amount				= amount;

		if (m_max_hit_count <= m_hits->size()) {
			HITS::iterator		I = std::min_element(m_hits->begin(),m_hits->end(),SLevelTimePredicate<CEntityAlive>());
			VERIFY				(m_hits->end() != I);
			*I					= hit_object;
		}
		else
			m_hits->push_back	(hit_object);
	}
	else {
		(*J).fill				(entity_alive,m_object,(!m_stalker ? (*J).m_squad_mask.get() : ((*J).m_squad_mask.get() | m_stalker->agent_manager().member().mask(m_stalker))));
		(*J).m_amount			= _max(amount,(*J).m_amount);
	}
}

void CHitMemoryManager::add					(const CHitObject &_hit_object)
{
#ifndef MASTER_GOLD
	if (_hit_object.m_object && smart_cast<CActor const*>(_hit_object.m_object) && psAI_Flags.test(aiIgnoreActor))
		return;
#endif // MASTER_GOLD

	VERIFY						(m_hits);
	if (!object().g_Alive())
		return;

	CHitObject					hit_object = _hit_object;
	hit_object.m_squad_mask.set	(m_stalker->agent_manager().member().mask(m_stalker),TRUE);

	const CEntityAlive			*entity_alive = hit_object.m_object;
	HITS::iterator	J = std::find(m_hits->begin(),m_hits->end(),object_id(entity_alive));
	if (m_hits->end() == J) {
		if (m_max_hit_count <= m_hits->size()) {
			HITS::iterator	I = std::min_element(m_hits->begin(),m_hits->end(),SLevelTimePredicate<CEntityAlive>());
			VERIFY				(m_hits->end() != I);
			*I					= hit_object;
		}
		else
			m_hits->push_back	(hit_object);
	}
	else {
		hit_object.m_squad_mask.assign	(hit_object.m_squad_mask.get() | (*J).m_squad_mask.get());
		*J						= hit_object;
	}
}

struct CRemoveOfflinePredicate {
	bool		operator()						(const CHitObject &object) const
	{
		VERIFY	(object.m_object);
		return	( !object.m_object || !!object.m_object->getDestroy() || object.m_object->H_Parent() );
	}
};

void CHitMemoryManager::update()
{
	START_PROFILE("Memory Manager/hits::update")

	clear_delayed_objects		();

	VERIFY						(m_hits);
	m_hits->erase				(
		std::remove_if(	
			m_hits->begin(),
			m_hits->end(),
			CRemoveOfflinePredicate()
		),
		m_hits->end()
	);

#ifdef USE_SELECTED_HIT
	xr_delete					(m_selected_hit);
	u32							level_time = 0;
	HITS::const_iterator		I = m_hits->begin();
	HITS::const_iterator		E = m_hits->end();
	for ( ; I != E; ++I) {
		if ((*I).m_level_time > level_time) {
			xr_delete			(m_selected_hit);
			m_selected_hit		= xr_new<CHitObject>(*I);
			level_time			= (*I).m_level_time;
		}
	}
#endif
	STOP_PROFILE
}

void CHitMemoryManager::enable			(const CObject *object, bool enable)
{
	HITS::iterator				J = std::find(m_hits->begin(),m_hits->end(),object_id(object));
	if (J == m_hits->end())
		return;

	(*J).m_enabled				= enable;
}

void CHitMemoryManager::remove_links	(CObject *object)
{
	if (m_last_hit_object_id == object->ID()) {
		m_last_hit_object_id	= ALife::_OBJECT_ID(-1);
		m_last_hit_time			= 0;
	}

	VERIFY						(m_hits);
	HITS::iterator				I = std::find_if(m_hits->begin(),m_hits->end(),CHitObjectPredicate(object));
	if (I != m_hits->end())
		m_hits->erase			(I);

#ifdef USE_SELECTED_HIT
	if (!m_selected_hit)
		return;

	if (!m_selected_hit->m_object)
		return;

	if (m_selected_hit->m_object->ID() != object->ID())
		return;

	xr_delete					(m_selected_hit);
#endif
}

void CHitMemoryManager::save	(NET_Packet &packet) const
{
	if (!m_object->g_Alive())
		return;

	packet.w_u8					((u8)objects().size());

	HITS::const_iterator		I = objects().begin();
	HITS::const_iterator		E = objects().end();
	for ( ; I != E; ++I) {
		VERIFY					((*I).m_object);
		packet.w_u16			((*I).m_object->ID());
		// object params
		packet.w_u32			((*I).m_object_params.m_level_vertex_id);
		packet.w_vec3			((*I).m_object_params.m_position);
#ifdef USE_ORIENTATION
		packet.w_float			((*I).m_object_params.m_orientation.yaw);
		packet.w_float			((*I).m_object_params.m_orientation.pitch);
		packet.w_float			((*I).m_object_params.m_orientation.roll);
#endif // USE_ORIENTATION
		// self params
		packet.w_u32			((*I).m_self_params.m_level_vertex_id);
		packet.w_vec3			((*I).m_self_params.m_position);
#ifdef USE_ORIENTATION
		packet.w_float			((*I).m_self_params.m_orientation.yaw);
		packet.w_float			((*I).m_self_params.m_orientation.pitch);
		packet.w_float			((*I).m_self_params.m_orientation.roll);
#endif // USE_ORIENTATION
#ifdef USE_LEVEL_TIME
		packet.w_u32			((Device.dwTimeGlobal >= (*I).m_level_time) ? (Device.dwTimeGlobal - (*I).m_level_time) : 0);
#endif // USE_LAST_LEVEL_TIME
#ifdef USE_LEVEL_TIME
		packet.w_u32			((Device.dwTimeGlobal >= (*I).m_level_time) ? (Device.dwTimeGlobal - (*I).m_last_level_time) : 0);
#endif // USE_LAST_LEVEL_TIME
#ifdef USE_FIRST_LEVEL_TIME
		packet.w_u32			((Device.dwTimeGlobal >= (*I).m_level_time) ? (Device.dwTimeGlobal - (*I).m_first_level_time) : 0);
#endif // USE_FIRST_LEVEL_TIME
		packet.w_vec3			((*I).m_direction);
		packet.w_u16			((*I).m_bone_index);
		packet.w_float			((*I).m_amount);
	}
}

void CHitMemoryManager::load	(IReader &packet)
{
	if (!m_object->g_Alive())
		return;

	typedef CClientSpawnManager::CALLBACK_TYPE	CALLBACK_TYPE;
	CALLBACK_TYPE					callback;
	callback.bind					(&m_object->memory(),&CMemoryManager::on_requested_spawn);

	int								count = packet.r_u8();
	for (int i=0; i<count; ++i) {
		CDelayedHitObject			delayed_object;
		delayed_object.m_object_id	= packet.r_u16();

		CHitObject					&object = delayed_object.m_hit_object;
		object.m_object				= smart_cast<CEntityAlive*>(Level().Objects.net_Find(delayed_object.m_object_id));
		// object params
		object.m_object_params.m_level_vertex_id	= packet.r_u32();
		packet.r_fvector3			(object.m_object_params.m_position);
#ifdef USE_ORIENTATION
		packet.r_float				(object.m_object_params.m_orientation.yaw);
		packet.r_float				(object.m_object_params.m_orientation.pitch);
		packet.r_float				(object.m_object_params.m_orientation.roll);
#endif
		// self params
		object.m_self_params.m_level_vertex_id	= packet.r_u32();
		packet.r_fvector3			(object.m_self_params.m_position);
#ifdef USE_ORIENTATION
		packet.r_float				(object.m_self_params.m_orientation.yaw);
		packet.r_float				(object.m_self_params.m_orientation.pitch);
		packet.r_float				(object.m_self_params.m_orientation.roll);
#endif
#ifdef USE_LEVEL_TIME
		VERIFY						(Device.dwTimeGlobal >= object.m_level_time);
		object.m_level_time			= packet.r_u32();
		object.m_level_time			+= Device.dwTimeGlobal;
#endif // USE_LEVEL_TIME
#ifdef USE_LAST_LEVEL_TIME
		VERIFY						(Device.dwTimeGlobal >= object.m_last_level_time);
		object.m_last_level_time	= packet.r_u32();
		object.m_last_level_time	+= Device.dwTimeGlobal;
#endif // USE_LAST_LEVEL_TIME
#ifdef USE_FIRST_LEVEL_TIME
		VERIFY						(Device.dwTimeGlobal >= (*I).m_first_level_time);
		object.m_first_level_time	= packet.r_u32();
		object.m_first_level_time	+= Device.dwTimeGlobal;
#endif // USE_FIRST_LEVEL_TIME
		packet.r_fvector3			(object.m_direction);
		object.m_bone_index			= packet.r_u16();
		object.m_amount				= packet.r_float();

		if (object.m_object) {
			add						(object);
			continue;
		}

		m_delayed_objects.push_back	(delayed_object);

		const CClientSpawnManager::CSpawnCallback	*spawn_callback = Level().client_spawn_manager().callback(delayed_object.m_object_id,m_object->ID());
		if (!spawn_callback || !spawn_callback->m_object_callback)
			if(!g_dedicated_server)
				Level().client_spawn_manager().add	(delayed_object.m_object_id,m_object->ID(),callback);
#ifdef DEBUG
		else {
			if (spawn_callback && spawn_callback->m_object_callback) {
				VERIFY				(spawn_callback->m_object_callback == callback);
			}
		}
#endif // DEBUG
	}
}

void CHitMemoryManager::clear_delayed_objects()
{
	if (m_delayed_objects.empty())
		return;

	CClientSpawnManager						&manager = Level().client_spawn_manager();
	DELAYED_HIT_OBJECTS::const_iterator		I = m_delayed_objects.begin();
	DELAYED_HIT_OBJECTS::const_iterator		E = m_delayed_objects.end();
	for ( ; I != E; ++I)
		if (manager.callback((*I).m_object_id,m_object->ID()))
			manager.remove					((*I).m_object_id,m_object->ID());

	m_delayed_objects.clear					();
}

void CHitMemoryManager::on_requested_spawn	(CObject *object)
{
	DELAYED_HIT_OBJECTS::iterator		I = m_delayed_objects.begin();
	DELAYED_HIT_OBJECTS::iterator		E = m_delayed_objects.end();
	for ( ; I != E; ++I) {
		if ((*I).m_object_id != object->ID())
			continue;
		
		if (m_object->g_Alive()) {
			(*I).m_hit_object.m_object= smart_cast<CEntityAlive*>(object);
			VERIFY						((*I).m_hit_object.m_object);
			add							((*I).m_hit_object);
		}

		m_delayed_objects.erase			(I);
		return;
	}
}
