#include "extdll.h"
#include "util.h"
#include "cbase.h"
#include "monsters.h"
#include "weapons.h"
#include "nodes.h"
#include "soundent.h"
#include "decals.h"
#include "effects.h"
#include "aura.h"
#include "classes.h"
#include "magicattack.h"
#include "explodingattack.h"
#include "player.h"
#include "gamerules.h"
#include "magicattack.h"
#include "explodingattack.h"
#include "powerstruggle.h"

#define POWERSTRUGGLE_MAX_IDLETIME 5.0
#define POWERSTRUGGLE_MIN_MOVEMENT 5	//10 per second
#define PS_OVERPOWERRATIO 0.40

extern cvar_t	multipleps;

LINK_ENTITY_TO_CLASS( powerstruggle, CPowerStruggle );

CPowerStruggle *CPowerStruggle::CreatePowerStruggle(CExplodingAttack *a1, CExplodingAttack *a2,int type)
{	CPowerStruggle *pPowerStruggle = GetClassPtr((CPowerStruggle*)NULL);
	pPowerStruggle->m_pDelayedDamage = NULL;
	pPowerStruggle->m_pAttack = NULL;
	pPowerStruggle->m_pDelayedDamage = NULL;
	pPowerStruggle->m_iAttacks = 0;	
	pPowerStruggle->m_iType = type;
	pPowerStruggle->SetTouch(NULL);
	pPowerStruggle->m_flIdleTime = 0.0;
	pPowerStruggle->m_fExploding = false;
	if(type==FRONTPS)
	{
		pPowerStruggle->SetThink(&CPowerStruggle::PSFrontThink);		
		pPowerStruggle->AddAttack(a1,1);
		pPowerStruggle->AddAttack(a2,2);	
	}
	else
	{
		pPowerStruggle->SetThink(&CPowerStruggle::PSSideThink);
		pPowerStruggle->AddAttack(a1,0);
		pPowerStruggle->AddAttack(a2,0);	
	}
	pPowerStruggle->pev->nextthink = gpGlobals->time;	

	//move camera to PS
	MESSAGE_BEGIN( MSG_SPEC, SVC_DIRECTOR );
		WRITE_BYTE ( 9 );
		WRITE_BYTE ( DRC_EVENT );
		WRITE_SHORT( ENTINDEX(a1->pev->owner));
		WRITE_SHORT( ENTINDEX(a2->pev->owner));
		WRITE_LONG(6);
	MESSAGE_END();
	pPowerStruggle->m_flIdleTime = gpGlobals->time;
	return pPowerStruggle;
}

double CPowerStruggle::GetDistance()
{
	CExplodingAttack *pFirst = m_pAttack->attack;	
	CExplodingAttack *pSecond = m_pAttack->next->attack;
	entvars_t *pevOwner1;
	if (pFirst->pev->owner)
	{	pevOwner1 = VARS(pFirst->pev->owner);
	}
	else
	{	ALERT(at_console,"No valid owner in ps\n");
		return 4096;
	}
	entvars_t *pevOwner2;
	if (pSecond->pev->owner)
	{	pevOwner2 = VARS(pSecond->pev->owner);
	}
	else
	{	ALERT(at_console,"No valid owner in ps\n");
		return 4096;
	}
	return (pevOwner1->origin-pevOwner2->origin).Length();
}

void CPowerStruggle::PSFrontThink()
{
	ApplyDelayedDamage();
	CExplodingAttack *pFirst = m_pAttack->attack;	
	CExplodingAttack *pSecond = m_pAttack->next->attack;
	int iDMG1 = pFirst->pev->dmg;
	int iDMG2 = pSecond->pev->dmg;	
	float flAvgSpeed = 0.5*(pFirst->m_flSpeed+pSecond->m_flSpeed);
	psAttack *pCurrentAttack = m_pAttack->next->next;
	while(pCurrentAttack)
	{	CExplodingAttack *pAttack = pCurrentAttack->attack;
		if(pCurrentAttack->side==1)
		{
			iDMG1+=pAttack->pev->dmg;
		}
		if(pCurrentAttack->side==2)
		{
			iDMG2+=pAttack->pev->dmg;
		}
		pCurrentAttack = pCurrentAttack->next;
	}
	
	float flSpeedRatio=0;
	Vector vecStart = pFirst->pev->origin;
	edict_t *pOwner;
	int iWinner;


	if(iDMG1>iDMG2)
	{
		flSpeedRatio = (float)(iDMG1-iDMG2)/(float)iDMG1;
		pOwner = pSecond->pev->owner;		
		iWinner = 1;
	}
	else //if(iDMG2>iDMG1)
	{
		flSpeedRatio = (float)(iDMG2-iDMG1)/(float)iDMG2;
		pOwner = pFirst->pev->owner;
		iWinner = 2;
	}

	//overpowered?
	/*
	ALERT(at_console,"Speedratio %f\n",flSpeedRatio);
	*/
	if (flSpeedRatio > PS_OVERPOWERRATIO)
	{
		flSpeedRatio = 1.0;
		psAttack *pCurrentAttack = m_pAttack;
		int maxDamage = 0;
		CExplodingAttack *pStrongest = NULL;
		while(pCurrentAttack)
		{	CExplodingAttack *pAttack = pCurrentAttack->attack;
			if(pCurrentAttack->side==iWinner)
			{
				if(pAttack->pev->dmg>maxDamage)
				{
					maxDamage = pAttack->pev->dmg;
					pStrongest = pAttack;
				}
			}
			else
			{
				pAttack->pev->dmg = 0;
			}
			pCurrentAttack = pCurrentAttack->next;
		}		
		pStrongest->pev->dmg += (iDMG1<iDMG2?iDMG1:iDMG2);
		/*
		ALERT(at_console,"Overpowering\n");
		*/
	}
	//ALERT(at_console,"Speed ratio %f",flSpeedRatio);

	entvars_t *pevOwner;
	if (pOwner)
	{	pevOwner = VARS(pOwner);
	}
	else
	{	ALERT(at_console,"No valid owner in frontal ps\n");
		return;
	}
	Vector vecVelocity = (pevOwner->origin-vecStart).Normalize()*flAvgSpeed*flSpeedRatio;

	//set velocity and struggle ratio's
	pCurrentAttack = m_pAttack;
	while(pCurrentAttack)
	{	//set speed
		CExplodingAttack *pAttack = pCurrentAttack->attack;
		pAttack->pev->velocity = vecVelocity;

		if(pCurrentAttack->side==iWinner)
		{
			pAttack->m_flStruggleRatio = flSpeedRatio;
		}
		else
		{
			pAttack->m_flStruggleRatio = -flSpeedRatio;
		}
		pCurrentAttack = pCurrentAttack->next;
	}
		
	//check movement
	if (vecVelocity.Length()<POWERSTRUGGLE_MIN_MOVEMENT)
	{	//we didn't move enough, increase idle counter
		if (gpGlobals->time-m_flIdleTime>POWERSTRUGGLE_MAX_IDLETIME)
		{	//Max idle time passed: Explode
			Remove();
			return;
		}
	}
	else
	{	//reset idle counter
		m_flIdleTime = gpGlobals->time;
	}	
	pev->nextthink = gpGlobals->time + 0.02;
}

void CPowerStruggle::PSSideThink()
{
	ApplyDelayedDamage();
	//holds the result of the attacks directions
	Vector vecResult = Vector(0,0,0);

	int iTotalDamage = 0;
	int iNumberOfAttacks = 0;
	float flTotalSpeed = 0.0;

	if(m_pAttack)
	{	psAttack *pCurrentAttack = m_pAttack;
		//get attacks statistics
		while(pCurrentAttack)
		{	CExplodingAttack *pAttack = pCurrentAttack->attack;
			UTIL_MakeAimVectors(pAttack->pev->angles);		
			vecResult = vecResult + pAttack->pev->dmg * gpGlobals->v_forward;
			iTotalDamage += pAttack->pev->dmg;
			flTotalSpeed += pAttack->m_flSpeed;
			iNumberOfAttacks++;
			pCurrentAttack = pCurrentAttack->next;
		}
		float flSpeedRatio = vecResult.Length()/(float)iTotalDamage;
		float flAvgSpeed = flTotalSpeed/(float)iNumberOfAttacks;
		float flSpeed = flSpeedRatio * flAvgSpeed;
		//calculate new velocity
		Vector vecVelocity = vecResult.Normalize() * flSpeed;

		//set velocity and struggle ratio's
		pCurrentAttack = m_pAttack;
		while(pCurrentAttack)
		{	//set speed
			CExplodingAttack *pAttack = pCurrentAttack->attack;
			pAttack->pev->velocity = vecVelocity;

			//set struggle ratio
			float flStruggleRatio = flSpeedRatio;
			UTIL_MakeAimVectors(pAttack->pev->angles);
			
			//>90 degrees angle with ps means we're losing.
			float flAngle = acos(DotProduct(vecVelocity.Normalize(),gpGlobals->v_forward));
			if (flAngle>0.5*M_PI)
			{	flStruggleRatio = -flStruggleRatio;
			}

			pAttack->m_flStruggleRatio = flStruggleRatio;
			pCurrentAttack = pCurrentAttack->next;
		}
		
		//check movement
		if (vecVelocity.Length()<POWERSTRUGGLE_MIN_MOVEMENT)
		{	//we didn't move enough, increase idle counter
			m_flIdleTime+=0.2;
			if (m_flIdleTime>POWERSTRUGGLE_MAX_IDLETIME)
			{	//Max idle time passed: Explode
				Remove();
				return;
			}
		}
		else
		{	//reset idle counter
			m_flIdleTime = 0.0;
		}
	}
	pev->nextthink = gpGlobals->time + 0.02;
}


void CPowerStruggle::AddAttack(CExplodingAttack *attack,int iSide)
{
	m_iAttacks++;
	psAttack *pNewAttack = new psAttack;
	pNewAttack->attack = attack;	
	pNewAttack->next = NULL;
	pNewAttack->side = iSide;
	psAttack *pCurrent = m_pAttack;
	if(!pCurrent)
	{	m_pAttack = pNewAttack;		
	}
	else
	{
		while(pCurrent->next)
		{
			pCurrent=pCurrent->next;
		}
		pCurrent->next=pNewAttack;
	}
	//move camera to PS
	MESSAGE_BEGIN( MSG_SPEC, SVC_DIRECTOR );
		WRITE_BYTE ( 9 );
		WRITE_BYTE ( DRC_EVENT );
		WRITE_SHORT( ENTINDEX(attack->pev->owner));
		WRITE_SHORT( attack->entindex());
		WRITE_LONG(6);
	MESSAGE_END();

	attack->m_fPowerStruggle = true;
	attack->m_pPowerStruggle = this;
	attack->SetThink(&CExplodingAttack::PowerStruggleThink);
	attack->pev->nextthink = gpGlobals->time;
}

BOOL CPowerStruggle::IsFull()
{	// Edited by Pcjoe
//	if(m_iAttacks>=2 && !g_pGameRules->IsTeamplay() && multipleps.value!=1.0)
	if(m_iAttacks>=2 && !multipleps.value)
	{   return true;
	}
	else
	{	return false;
	}
}

BOOL CPowerStruggle::InStruggle(entvars_t *attack)
{
	psAttack *pCurrent=m_pAttack;
	while(pCurrent)
	{
		if(pCurrent->attack->pev == attack)
		{	return true;
		}
		pCurrent = pCurrent->next;
	}
	return false;
}

void CPowerStruggle::ApplyDelayedDamage()
{
	DelayedDamage *pDelayedDamage = m_pDelayedDamage;
	DelayedDamage *pPrevDelayedDamage = NULL;
	DelayedDamage *temp;
	while(pDelayedDamage)
	{
		entvars_t *pevOwner;
		if (pDelayedDamage->pAttack->pev->owner)
		{	pevOwner = VARS(pDelayedDamage->pAttack->pev->owner);
		}
		double flDist = (pevOwner->origin - pDelayedDamage->pAttack->pev->origin).Length();
		int iDmg;
		if(flDist < (gpGlobals->time - pDelayedDamage->flAddTime)*pDelayedDamage->speed)
		{	
			iDmg=pDelayedDamage->damage;
			if(GetType()==FRONTPS)
			{
				double flTotalDist = GetDistance();		
				iDmg *= (1.0-(flDist/flTotalDist));
			}
			//ALERT(at_console,"Adding delayed damage %d\n",iDmg);
			pDelayedDamage->pAttack->pev->dmg+=iDmg;
			temp = pDelayedDamage;
			pDelayedDamage = pDelayedDamage->next;
			if(pPrevDelayedDamage)
			{
				pPrevDelayedDamage->next=pDelayedDamage;
			}
			else
			{
				m_pDelayedDamage=pDelayedDamage;
			}
			delete temp;
		}
		else
		{
			pPrevDelayedDamage=pDelayedDamage;
			pDelayedDamage = pDelayedDamage->next;
		}		
	}
}

void CPowerStruggle::AddPSDamage(DelayedDamage *delayedDamage)
{
	Vector vecAttack = delayedDamage->pAttack->pev->origin;
	entvars_t *pevOwner;
	if (delayedDamage->pAttack->pev->owner)
	{	pevOwner = VARS(delayedDamage->pAttack->pev->owner);
	}
	Vector vecOwner = pevOwner->origin;
	//it takes less than 0.2 secs for the charge to reach the beam head, just add it immidiatly
	if((vecOwner-vecAttack).Length()/delayedDamage->speed<0.2)
	{
		//ALERT(at_console,"Adding damage directly %d\n",delayedDamage->damage);
		delayedDamage->pAttack->pev->dmg+=delayedDamage->damage;
		delete delayedDamage;
	}
	else
	{
		delayedDamage->next = m_pDelayedDamage;	
		m_pDelayedDamage = delayedDamage;
	}	
}

CExplodingAttack* CPowerStruggle::GetStrongestAttack()
{
	CExplodingAttack* pStrongest = NULL;
	double maxDamage = 0;
	psAttack *pCurrent = m_pAttack;
	while (pCurrent)
	{	if(pCurrent->attack->m_flStruggleRatio >= 0)
		{	if(pCurrent->attack->pev->dmg>maxDamage)
			{	pStrongest = pCurrent->attack;
				maxDamage = pCurrent->attack->pev->dmg;
			}
		}
		pCurrent = pCurrent->next;
	}
	return pStrongest;
}

void CPowerStruggle::Remove()
{	m_fExploding = true;	
	CExplodingAttack *pStrongest=GetStrongestAttack();
	edict_t *pOwner = pStrongest->pev->owner;
	//ALERT(at_console,"Removing ps\n");
	while (m_pAttack)
	{	if (!m_pAttack->attack->m_fExploded)
		{	CExplodingAttack *pExp = m_pAttack->attack;
			//pExp->m_fPowerStruggle = false;			
			
			//give the strongest attack's owner credit for dmg from the enemies			
			if(pExp->m_flStruggleRatio<0)
			{	//ALERT(at_console,"Setting new owner\n");
				pExp->pev->owner = pOwner;
			}			

			/*
			if(pExp!=pStrongest)
			{
				pStrongest->pev->dmg += pExp->pev->dmg;
				pExp->Vaporize();
			}
			*/
			m_pAttack->attack->Explode();
		}
		psAttack *pTempAttack;
		pTempAttack = m_pAttack->next;
		delete m_pAttack;
		m_pAttack = pTempAttack;		
	}
	RemoveDelayedDamage(m_pDelayedDamage);
	//pStrongest->Explode();
	CMagicAttack::Remove();
}