/**
* Manipulation of bones
* @version 5-3-03
* @author Joseph 'Pcjoe' Florencio
*/

#include "hud.h"
#include "parsemsg.h"
#include "const.h"
#include "cl_util.h"
#include "r_efx.h"
#include "pm_defs.h"
#include "pmtrace.h"
#include "event_api.h"
#include "triangleapi.h"
#include "r_studioint.h"
#include "math.h"
#include "com_model.h"
#include "StudioModelRenderer.h"
#include "GameStudioModelRenderer.h"

using namespace std;

// Defines used server side
typedef enum
{	MANIPM_REMOVE_ALL=0,	// Remove all effects
	/*
	* Create Effects
	*/
	MANIPM_CONNECT,			// Place models X units apart from eachother
	MANIPM_ANGLE_FIX,		// Fix model angles
	MINIPM_ANGLE_FIX_ENT,	// Fix model angles to aim at a entity
	MANIPM_GRAB,			// Grab player ( Attach left + right/right + left hands with a small offset )
	MANIPM_MSTRUGGLE,		// Melee struggle ( pushes hands together )
	MANIPM_MSTRUGGLE_FIX,	// re-fix angles for melee struggle
	MANIPM_SWITCH,			// Attack roll switches
	MANIPM_RECPOS,			// Record bone origins
	MANIPM_BLURSEQ,			// Blur specific sequence
	// Non-Player effects
	MANIPM_AURABEND,		// Bend aura depending on entities velocity

	/*
	* Remove Effects
	*/
	MANIPM_REMOVE_CONNECT,
	MANIPM_REMOVE_AFIX,
	MANIPM_REMOVE_GRAB,
	MANIPM_REMOVE_MSTRUGGLE,
	MANIPM_REMOVE_RECPOS,
	// Non-Player effects
	MANIPM_REMOVE_AURABEND,
};

extern void VectorAngles( const float *forward, float *angles );
extern engine_studio_api_t IEngineStudio;
extern CGameStudioModelRenderer g_StudioRenderer;

DECLARE_MESSAGE(m_ManipModel, ManipModel);


int CHudManipModel::Init(void)
{	
	HOOK_MESSAGE(ManipModel);
	
	InitHUDMap();

	gHUD.AddHudElem(this);
	m_iFlags |= HUD_ACTIVE;
	return 1;
}

void CHudManipModel::InitHUDMap(void)
{	// Clean everything out
	m_pModelManip.clear();

	// Reset interpolated gait here as well
	g_StudioRenderer.ClearInterpolGates();
}

int CHudManipModel::VidInit(void)
{	return 1;
}

void CHudManipModel::Think( void )
{	
}

bool CHudManipModel::RecordPos( int flags )
{	if( !(flags & MANIP_FLAG_RECPOS) && ((flags & MANIP_FLAG_GRAB) || (flags & MANIP_FLAG_MSTRUGGLE) || (flags & MANIP_FLAG_CONNECT)) )
	{	return true;
	}
	return false;
}

list<model_manip_s>::iterator CHudManipModel::AddEntity( model_manip_s modelManip )
{
	//pModelManip->pNext = NULL;

	// Just add flags for blur sequence
	if(modelManip.iFlags & MANIP_FLAG_BLURSEQ)
	{	/*modified by harSens
		list<model_manip_s>::iterator pManip = FindByPlayerAndOtherIndex(modelManip.iPlayerIndex);
		*/
		model_manip_s *pManip = FindByPlayerAndOtherIndex(modelManip.iPlayerIndex);
		if(pManip!=NULL)
		{	if(pManip->iPlayerIndex == modelManip.iPlayerIndex)
			{	pManip->flAITimePlayer = 0;
				pManip->iAISeqPlayer= modelManip.iAISeqPlayer;
				pManip->blPlayedAIPlayer = false;
			}
			else
			{	pManip->flAITimeOther = 0;
				pManip->iAISeqOther = modelManip.iAISeqPlayer;
				pManip->blPlayedAIOther = false;
			}
			pManip->iFlags |= MANIP_FLAG_BLURSEQ;

			//delete pModelManip;
			return NULL;
		}
	}
	else if(modelManip.iFlags & MANIP_FLAG_CONNECT)
	{	/*modified by harSens
		list<model_manip_s>::iterator pOthers = FindByPlayerAndOtherIndex(modelManip.iPlayerIndex);
		*/
		model_manip_s *pOthers = FindByPlayerAndOtherIndex(modelManip.iPlayerIndex);
		// We already have a member, update info
		if(pOthers!=NULL && (pOthers->iFlags & MANIP_FLAG_CONNECT))
		{	pOthers->iDistance = modelManip.iDistance;

			//delete pModelManip;
			return NULL;
		}
		else
		{	RemoveAll(modelManip.iPlayerIndex);
		}
	}
	else
	{	RemoveAll(modelManip.iPlayerIndex);
	}

	// Record bone origins
	if(RecordPos(modelManip.iFlags))
	{	modelManip.iFlags |= MANIP_FLAG_RECPOS;
	}

	// Reset motion blur varibles if we're not using them
	if(!(modelManip.iFlags & MANIP_FLAG_BLURSEQ))
	{	modelManip.iAISeqPlayer = -1;
		modelManip.iAISeqOther = -1;
		modelManip.flAITimePlayer = 0;
		modelManip.flAITimeOther = 0;
	}

	modelManip.blAttached = false;
	modelManip.blCanUseOtherPos = false;
	modelManip.blCanUsePlayerPos = false;
	modelManip.blFixedAngles = false;
	modelManip.blFixedViewAngles = false;
	modelManip.blPlayedAIPlayer = false;
	modelManip.blPlayedAIOther = false;
	modelManip.blTeleportPlayer = false;
	modelManip.blTeleportOther = false;
	modelManip.vecTele = vec3_t(0,0,0);
	modelManip.vecDir = vec3_t(0,0,1);
	modelManip.vecOldOrigin = vec3_t(0,0,0);
	modelManip.flLastTime = 0;
	modelManip.iBody = -1;
	modelManip.iBodyPlayer = 0;
	modelManip.iBodyOther = 0;
	modelManip.iBodyPlayerSeq = 0;
	modelManip.iBodyOtherSeq = 0;
	modelManip.dl = NULL;

	m_pModelManip.push_front(modelManip);

	//return pModelManip;
	return m_pModelManip.begin();
//	ConsolePrint("Added Entity\n");
}

list<model_manip_s>::iterator CHudManipModel::AddRecordBone( int iIndex )
{	model_manip_s newModelManip;

	newModelManip.iFlags = MANIP_FLAG_RECPOS;
	newModelManip.iPlayerIndex = iIndex;
	newModelManip.iOtherIndex = 0;
	newModelManip.vecAngles = vec3_t(0,0,0);
	newModelManip.iDistance = 0;
	newModelManip.blLockGait = false;

	return AddEntity( newModelManip );
}


list<model_manip_s>::iterator CHudManipModel::AddMotionBlur( int iIndex, int iSeq )
{	model_manip_s newModelManip;

	newModelManip.iFlags = MANIP_FLAG_BLURSEQ;
	newModelManip.iPlayerIndex = iIndex;
	newModelManip.iOtherIndex = 0;
	newModelManip.vecAngles = vec3_t(0,0,0);
	newModelManip.iDistance = 0;
	newModelManip.blLockGait = false;
	newModelManip.iAISeqPlayer = iSeq;
	newModelManip.flAITimePlayer = 0;
	newModelManip.iAISeqOther = -1;
	newModelManip.flAITimeOther = 0;

	return AddEntity( newModelManip );
}

void CHudManipModel::AddEntity( int iFlag, int iIndex )
{
	model_manip_s newModelManip;

	newModelManip.iFlags = iFlag;
	newModelManip.iPlayerIndex = iIndex;
	newModelManip.iOtherIndex = 0;
	newModelManip.vecAngles = vec3_t(0,0,0);
	newModelManip.iDistance = 0;
	newModelManip.blLockGait = true;

	AddEntity( newModelManip );
}

void CHudManipModel::AddEntity( int iFlag, int iPlayerIndex, int iOtherIndex )
{
	model_manip_s newModelManip;

	newModelManip.iFlags = iFlag;
	newModelManip.iPlayerIndex = iPlayerIndex;
	newModelManip.iOtherIndex = iOtherIndex;
	newModelManip.vecAngles = vec3_t(0,0,0);
	newModelManip.iDistance = 0;
	newModelManip.blLockGait = true;

	AddEntity( newModelManip );
}

void CHudManipModel::AddEntity( int iFlag, int iPlayerIndex, int iOtherIndex, int iDistance )
{
	model_manip_s newModelManip;

	newModelManip.iFlags = iFlag;
	newModelManip.iPlayerIndex = iPlayerIndex;
	newModelManip.iOtherIndex = iOtherIndex;
	newModelManip.vecAngles = vec3_t(0,0,0);
	newModelManip.iDistance = iDistance;
	newModelManip.blLockGait = true;

	AddEntity( newModelManip );
}

void CHudManipModel::AddEntity( int iFlag, int iPlayerIndex, vec3_t vecAngles )
{
	model_manip_s newModelManip;

	newModelManip.iFlags = iFlag;
	newModelManip.iPlayerIndex = iPlayerIndex;
	newModelManip.iOtherIndex = 0;
	newModelManip.vecAngles = vecAngles;
	newModelManip.iDistance = 0;
	newModelManip.blLockGait = true;

	AddEntity( newModelManip );
}

/*
* Re-fix melee struggle angles
*/
void CHudManipModel::FixMstruggleAngles( int iPlayerIndex, vec3_t vecAngles )
{
	/*modified by harSens
	list<model_manip_s>::iterator pManip = FindByPlayerAndOtherIndex( iPlayerIndex );
	*/
	model_manip_s *pManip = FindByPlayerAndOtherIndex( iPlayerIndex );

	if(pManip!=NULL)
	{	pManip->vecAngles = vecAngles;
		pManip->blFixedAngles = true;
	}
}

/*
* Switch rolls
*/
void CHudManipModel::Switch( int iIndex )
{	/*modified by harSens
	list<model_manip_s>::iterator pManip = FindByPlayerAndOtherIndex( iIndex );
	*/
	model_manip_s *pManip = FindByPlayerAndOtherIndex( iIndex );

	if(pManip!=NULL && (pManip->iFlags & MANIP_FLAG_CONNECT))
	{	// Switch player indexes
		int iPlayerIndex, iOtherIndex;
		iPlayerIndex = pManip->iPlayerIndex;
		iOtherIndex = pManip->iOtherIndex;
		pManip->iPlayerIndex = iOtherIndex;
		pManip->iOtherIndex = iPlayerIndex;
		
		// Flip angles around
		pManip->vecAngles.y += 180;
		if(pManip->vecAngles.y > 180)
		{	pManip->vecAngles.y -= 360;
		}
	}
}

/*
* Find entity by index
*/

/*modified by harSens
std::list<model_manip_s>::iterator CHudManipModel::FindByPlayerIndex( int iIndex )
*/
model_manip_s* CHudManipModel::FindByPlayerIndex( int iIndex )
{
	std::list<model_manip_s>::iterator current = m_pModelManip.begin();
	std::list<model_manip_s>::iterator end = m_pModelManip.end();

	while(current!=end)
	{
		if(current->iPlayerIndex == iIndex)
		{	return &(*current);
		}
		current++;
	}

	return NULL;
}

/*modified by harSens
std::list<model_manip_s>::iterator CHudManipModel::FindByOtherIndex( int iIndex)
*/
model_manip_s *CHudManipModel::FindByOtherIndex( int iIndex)
{
	std::list<model_manip_s>::iterator current = m_pModelManip.begin();
	std::list<model_manip_s>::iterator end = m_pModelManip.end();
	while(current!=end)
	{
		if(current->iOtherIndex == iIndex)
		{	return &(*current);
		}
		current++;
	}
	return NULL;
}

/*modified by harSens
std::list<model_manip_s>::iterator CHudManipModel::FindByPlayerAndOtherIndex( int iIndex)
*/
model_manip_s* CHudManipModel::FindByPlayerAndOtherIndex( int iIndex)
{
	std::list<model_manip_s>::iterator current = m_pModelManip.begin();
	std::list<model_manip_s>::iterator end = m_pModelManip.end();
	while(current!=end)
	{
		if((current->iPlayerIndex == iIndex) || (current->iOtherIndex == iIndex))
		{	return &(*current);
		}
		current++;
	}
	return NULL;
}

/*
* Find entity by index, start searching at pStart
*/
/*modified by harSens
std::list<model_manip_s>::iterator CHudManipModel::FindByPlayerIndex( std::list<model_manip_s>::iterator pStart, int iIndex )
*/
model_manip_s *CHudManipModel::FindByPlayerIndex( std::list<model_manip_s>::iterator pStart, int iIndex )
{
	
	/*
	if(!m_pModelManip)
	{	return NULL;
	}

	if(!pStart)
	{	return NULL;
	}
    
	model_manip_s *pCurrent = pStart;
	*/
	std::list<model_manip_s>::iterator current = pStart;
	std::list<model_manip_s>::iterator end = m_pModelManip.end();
	while(current!=end)
	{
		if(current->iPlayerIndex == iIndex)
		{	return &(*current);
		}
		current++;
	}

	return NULL;
}
/*modified by harSens
std::list<model_manip_s>::iterator CHudManipModel::FindByOtherIndex( std::list<model_manip_s>::iterator pStart, int iIndex )
*/
model_manip_s *CHudManipModel::FindByOtherIndex( std::list<model_manip_s>::iterator pStart, int iIndex )
{
	/*
	if(!m_pModelManip)
	{	return NULL;
	}

	if(!pStart)
	{	return NULL;
	}
	*/

	std::list<model_manip_s>::iterator pCurrent = pStart;
	std::list<model_manip_s>::iterator end = m_pModelManip.end();
	while(pCurrent!=end)
	{
		if(pCurrent->iOtherIndex == iIndex)
		{	return &(*pCurrent);
		}
		pCurrent++;
	}
	return NULL;	
}

/*modified by harSens
std::list<model_manip_s>::iterator CHudManipModel::FindByPlayerAndOtherIndex( std::list<model_manip_s>::iterator pStart, int iIndex)
*/
model_manip_s *CHudManipModel::FindByPlayerAndOtherIndex( std::list<model_manip_s>::iterator pStart, int iIndex)
{
	/*
	if(!m_pModelManip)
	{	return NULL;
	}

	if(!pStart)
	{	return NULL;
	}
	*/

	std::list<model_manip_s>::iterator pCurrent = pStart;
	std::list<model_manip_s>::iterator end = m_pModelManip.end();

	while(pCurrent!=end)
	{
		if((pCurrent->iPlayerIndex == iIndex) || (pCurrent->iOtherIndex == iIndex))
		{	
			return &(*pCurrent);			
		}
		pCurrent++;
	}
	return NULL;	
}

void CHudManipModel::Remove( model_manip_s *pManip )
{
	
	if (pManip==NULL)
	{	return;
	}

	// Check for light, remove it
	if(pManip->dl)
	{	pManip->dl->die = gEngfuncs.GetClientTime()+2;
		pManip->dl->decay = 256;
		pManip->dl = NULL;
	}

	std::list<model_manip_s>::iterator pCurrent = m_pModelManip.begin();
	std::list<model_manip_s>::iterator end = m_pModelManip.end();

	while(pCurrent!=end)
	{
		if(&(*pCurrent)==pManip)
		{	
			pCurrent = m_pModelManip.erase(pCurrent);
			break;
		}
		pCurrent++;
	}
	
}

/*
* Remove functions
*/
std::list<model_manip_s>::iterator CHudManipModel::Remove( std::list<model_manip_s>::iterator pManip )
{
	/* removed by harSens, NULL is not a valid argument
	if (pManip==NULL)
	{	return NULL;
	}
	*/
	//std::list<model_manip_s>::iterator next=m_pModelManip.erase(pManip);

	std::list<model_manip_s>::iterator next = pManip;
	next++;
	m_pModelManip.erase(pManip);
	if (next==m_pModelManip.end())
	{	/* modified by harSens
		return NULL;
		*/
		return m_pModelManip.end();
	}
	else
	{	return next;
	}
	/*
	if(!m_pModelManip || !pManip)
	{	return NULL;
	}
	

	// Beginning of list, remove it
	if(m_pModelManip == pManip)
	{	m_pModelManip = m_pModelManip->pNext;
		delete pManip;
		return m_pModelManip;
	}
	// Search + destroy
	else
	{	model_manip_s *pCurrent = m_pModelManip;

		while(pCurrent->pNext)
		{
			// We found it, lets delete it
			if(pCurrent->pNext == pManip)
			{	model_manip_s *pPrev = pCurrent;
				pCurrent = pCurrent->pNext;
				pPrev->pNext = pCurrent->pNext;
				delete pCurrent;
				return pPrev->pNext;
			}
			else
			{	pCurrent = pCurrent->pNext;
			}
		}
	}	
	return NULL;
	*/
}

void CHudManipModel::RemoveAll( int iIndex )
{	Remove( FindByPlayerIndex( iIndex ) );
}

void CHudManipModel::RemoveEffect( int iIndex, int iFlag )
{
	/*modified by harSens
	std::list<model_manip_s>::iterator pManip = FindByPlayerIndex( iIndex );
	*/
	model_manip_s *pManip = FindByPlayerIndex( iIndex );
	if(pManip!=NULL)
	{	pManip->iFlags &= ~iFlag;
		// Remove it if we have no more flags
		if(pManip->iFlags == 0)
		{	Remove( pManip );
		}
	}
}

/*
* Create / Destroy messages
*/

int CHudManipModel::MsgFunc_ManipModel( const char *pszName, int iSize, void *pbuf )
{
	int iType;
	int iPlayerIndex;
	int iOtherIndex;
	int iDistance;
	int iSequence;
	vec3_t vecAngles;

	BEGIN_READ( pbuf, iSize );
	
	iType = READ_BYTE();

	// Handle differently depending on types
	switch(iType)
	{
	case MANIPM_REMOVE_ALL:
		iPlayerIndex = READ_SHORT();
		RemoveAll(iPlayerIndex);
		break;

	case MANIPM_CONNECT:
		iPlayerIndex = READ_SHORT();
		iOtherIndex = READ_SHORT();
		iDistance = READ_BYTE();
		AddEntity(MANIP_FLAG_CONNECT,iPlayerIndex,iOtherIndex, iDistance);
		break;

	case MANIPM_ANGLE_FIX:
		iPlayerIndex = READ_SHORT();
		AddEntity(MANIP_FLAG_ANGLE_FIX,iPlayerIndex);
		break;

	case MINIPM_ANGLE_FIX_ENT:
		iPlayerIndex = READ_SHORT();
		vecAngles.x = READ_COORD();
		vecAngles.y = READ_COORD();
		vecAngles.z = READ_COORD();
		AddEntity(MANIP_FLAG_ANGLE_FIX_ENT,iPlayerIndex,vecAngles);
		break;

	case MANIPM_GRAB:
		iPlayerIndex = READ_SHORT();
		iOtherIndex = READ_SHORT();
		AddEntity(MANIP_FLAG_GRAB,iPlayerIndex, iOtherIndex);
		break;

	case MANIPM_MSTRUGGLE:
		iPlayerIndex = READ_SHORT();
		iOtherIndex = READ_SHORT();
		AddEntity(MANIP_FLAG_MSTRUGGLE, iPlayerIndex, iOtherIndex);
		break;

	case MANIPM_MSTRUGGLE_FIX:
		iPlayerIndex = READ_SHORT();
		vecAngles.x = READ_COORD();
		vecAngles.y = READ_COORD();
		vecAngles.z = READ_COORD();
		FixMstruggleAngles( iPlayerIndex, vecAngles );
		break;

	case MANIPM_SWITCH:
		iPlayerIndex = READ_SHORT();
		Switch(iPlayerIndex);
		break;
	case MANIPM_RECPOS:
		iPlayerIndex = READ_SHORT();
		AddEntity(MANIP_FLAG_RECPOS, iPlayerIndex);
		break;

	case MANIPM_BLURSEQ:
		iPlayerIndex = READ_SHORT();
		iSequence = READ_SHORT();
		AddMotionBlur(iPlayerIndex, iSequence);
		break;

	case MANIPM_AURABEND:
		iPlayerIndex = READ_SHORT();
		AddEntity(MANIP_FLAG_AURABEND, iPlayerIndex);
		break;

	case MANIPM_REMOVE_CONNECT:
		iPlayerIndex = READ_SHORT();
		RemoveEffect(iPlayerIndex,MANIP_FLAG_CONNECT);
		break;

	case MANIPM_REMOVE_AFIX:
		iPlayerIndex = READ_SHORT();
		RemoveEffect(iPlayerIndex,MANIP_FLAG_ANGLE_FIX);
		break;

	case MANIPM_REMOVE_GRAB:
		iPlayerIndex = READ_SHORT();
		RemoveEffect(iPlayerIndex,MANIP_FLAG_MSTRUGGLE);
		break;

	case MANIPM_REMOVE_MSTRUGGLE:
		iPlayerIndex = READ_SHORT();
		RemoveEffect(iPlayerIndex,MANIP_FLAG_MSTRUGGLE);
		break;

	case MANIPM_REMOVE_AURABEND:
		iPlayerIndex = READ_SHORT();
		RemoveEffect(iPlayerIndex,MANIP_FLAG_AURABEND);
		break;

	case MANIPM_REMOVE_RECPOS:
		iPlayerIndex = READ_SHORT();
		RemoveEffect(iPlayerIndex,MANIP_FLAG_RECPOS);
		break;
	}

	return 1;
}
