#include "common.h"
#include "patcher.h"
#include "WaterCannon.h"
#include "Vector.h"
#include "General.h"
#include "main.h"
#include "Timer.h"
#include "Pools.h"
#include "Ped.h"
#include "AnimManager.h"
#include "Fire.h"
#include "WaterLevel.h"
#include "Camera.h"

#define WATERCANNONVERTS 4
#define WATERCANNONINDEXES 12

RwIm3DVertex WaterCannonVertices[WATERCANNONVERTS];
RwImVertexIndex WaterCannonIndexList[WATERCANNONINDEXES];

CWaterCannon CWaterCannons::aCannons[NUM_WATERCANNONS];

void CWaterCannon::Init(void)
{
	m_nId = 0;
	m_nCur = 0;
	m_nTimeCreated = CTimer::GetTimeInMilliseconds();
	
	for ( int32 i = 0; i < NUM_SEGMENTPOINTS; i++ )
		m_abUsed[i] = false;
	
	RwIm3DVertexSetU(&WaterCannonVertices[0], 0.0f);
	RwIm3DVertexSetV(&WaterCannonVertices[0], 0.0f);
	
	RwIm3DVertexSetU(&WaterCannonVertices[1], 1.0f);
	RwIm3DVertexSetV(&WaterCannonVertices[1], 0.0f);
	
	RwIm3DVertexSetU(&WaterCannonVertices[2], 0.0f);
	RwIm3DVertexSetV(&WaterCannonVertices[2], 0.0f);
	
	RwIm3DVertexSetU(&WaterCannonVertices[3], 1.0f);
	RwIm3DVertexSetV(&WaterCannonVertices[3], 0.0f);
	
	WaterCannonIndexList[0]  = 0;
	WaterCannonIndexList[1]  = 1;
	WaterCannonIndexList[2]  = 2;
	
	WaterCannonIndexList[3]  = 1;
	WaterCannonIndexList[4]  = 3;
	WaterCannonIndexList[5]  = 2;
	
	WaterCannonIndexList[6]  = 0;
	WaterCannonIndexList[7]  = 2;
	WaterCannonIndexList[8]  = 1;
	
	WaterCannonIndexList[9]  = 1;
	WaterCannonIndexList[10] = 2;
	WaterCannonIndexList[11] = 3;
}

void CWaterCannon::Update_OncePerFrame(int16 index)
{
	ASSERT(index < NUM_WATERCANNONS);
	
	if (CTimer::GetTimeInMilliseconds() > m_nTimeCreated + WATERCANNON_LIFETIME )
	{
		m_nCur = (m_nCur + 1) % -NUM_SEGMENTPOINTS;
		m_abUsed[m_nCur] = false;
	}
	
	for ( int32 i = 0; i < NUM_SEGMENTPOINTS; i++ )
	{
		if ( m_abUsed[i] )
		{
			m_avecVelocity[i].z += -WATERCANNON_GRAVITY * CTimer::GetTimeStep();
			m_avecPos[i]        += m_avecVelocity[i]    * CTimer::GetTimeStep();
		}
	}
	
	int32 extinguishingPoint = CGeneral::GetRandomNumber() & (NUM_SEGMENTPOINTS - 1);
	if ( m_abUsed[extinguishingPoint] )
		gFireManager.ExtinguishPoint(m_avecPos[extinguishingPoint], 3.0f);
	
	if ( ((index + CTimer::GetFrameCounter()) & 3) == 0 )
		PushPeds();
	
	// free if unused
	
	int32 i = 0;
	while ( 1 )
	{
		if ( m_abUsed[i] )
			break;
		
		if ( ++i >= NUM_SEGMENTPOINTS )
		{
			m_nId = 0;
			return;
		}
	}
}

void CWaterCannon::Update_NewInput(CVector *pos, CVector *dir)
{
	ASSERT(pos != NULL);
	ASSERT(dir != NULL);
	
	m_avecPos[m_nCur]      = *pos;
	m_avecVelocity[m_nCur] = *dir;
	m_abUsed[m_nCur]       = true;
}

void CWaterCannon::Render(void)
{
	RwRenderStateSet(rwRENDERSTATEZWRITEENABLE,      (void *)FALSE);
	RwRenderStateSet(rwRENDERSTATEVERTEXALPHAENABLE, (void *)TRUE);
	RwRenderStateSet(rwRENDERSTATEFOGENABLE,         (void *)TRUE);
	RwRenderStateSet(rwRENDERSTATETEXTURERASTER,     (void *)gpWaterRaster);
	
	float v = float(CGeneral::GetRandomNumber() & 255) / 256;
		
	RwIm3DVertexSetV(&WaterCannonVertices[0], v);
	RwIm3DVertexSetV(&WaterCannonVertices[1], v);
	RwIm3DVertexSetV(&WaterCannonVertices[2], v);
	RwIm3DVertexSetV(&WaterCannonVertices[3], v);
	
	int16 pointA = m_nCur % -NUM_SEGMENTPOINTS;
	
	int16 pointB = pointA - 1;
	if ( (pointA - 1) < 0 )
		pointB += NUM_SEGMENTPOINTS;

	bool bInit = false;
	CVector norm;
	
	for ( int32 i = 0; i < NUM_SEGMENTPOINTS - 1; i++ )
	{
		if ( m_abUsed[pointA] && m_abUsed[pointB] )
		{
			if ( !bInit )
			{
				CVector cp = CrossProduct(m_avecPos[pointB] - m_avecPos[pointA], TheCamera.GetForward());
				cp.Normalise(0.05f);
				norm = cp;
				bInit = true;
			}
			
			float dist       = float(i*i*i) / 300.0f + 1.0f;
			float brightness = float(i) / NUM_SEGMENTPOINTS;
			
			int32 color = (int32)((1.0f - brightness*brightness) * 255.0f);
			CVector offset = dist * norm;
			
			RwIm3DVertexSetRGBA(&WaterCannonVertices[0], color, color, color, color);
			RwIm3DVertexSetPos (&WaterCannonVertices[0], m_avecPos[pointA].x - offset.x, m_avecPos[pointA].y - offset.y, m_avecPos[pointA].z - offset.z);
				
			RwIm3DVertexSetRGBA(&WaterCannonVertices[1], color, color, color, color);
			RwIm3DVertexSetPos (&WaterCannonVertices[1], m_avecPos[pointA].x + offset.x, m_avecPos[pointA].y + offset.y, m_avecPos[pointA].z + offset.z);
			
			RwIm3DVertexSetRGBA(&WaterCannonVertices[2], color, color, color, color);
			RwIm3DVertexSetPos (&WaterCannonVertices[2], m_avecPos[pointB].x - offset.x, m_avecPos[pointB].y - offset.y, m_avecPos[pointB].z - offset.z);
			
			RwIm3DVertexSetRGBA(&WaterCannonVertices[3], color, color, color, color);
			RwIm3DVertexSetPos (&WaterCannonVertices[3], m_avecPos[pointB].x + offset.x, m_avecPos[pointB].y + offset.y, m_avecPos[pointB].z + offset.z);
			
			LittleTest();

			if ( RwIm3DTransform(WaterCannonVertices, WATERCANNONVERTS, NULL, rwIM3D_VERTEXUV) )
			{
				RwIm3DRenderIndexedPrimitive(rwPRIMTYPETRILIST, WaterCannonIndexList, WATERCANNONINDEXES);
				RwIm3DEnd();
			}
		}
		
		pointA = pointB--;
		if ( pointB < 0 )
			pointB += NUM_SEGMENTPOINTS;
	}
	
	RwRenderStateSet(rwRENDERSTATEZWRITEENABLE,      (void *)TRUE);
	RwRenderStateSet(rwRENDERSTATEVERTEXALPHAENABLE, (void *)FALSE);
	RwRenderStateSet(rwRENDERSTATEFOGENABLE,         (void *)FALSE);
}

void CWaterCannon::PushPeds(void)
{
	float minx = 10000.0f;
	float maxx = -10000.0f;
	float miny = 10000.0f;
	float maxy = -10000.0f;
	float minz = 10000.0f;
	float maxz = -10000.0f;
  
	for ( int32 i = 0; i < NUM_SEGMENTPOINTS; i++ )
	{
		if ( m_abUsed[i] )
		{
			minx = min(minx, m_avecPos[i].x);
			maxx = max(maxx, m_avecPos[i].x);
			
			miny = min(miny, m_avecPos[i].y);
			maxy = max(maxy, m_avecPos[i].y);
			
			minz = min(minz, m_avecPos[i].z);
			maxz = max(maxz, m_avecPos[i].z);
		}
	}
	
	for ( int32 i = CPools::GetPedPool()->GetSize() - 1; i >= 0; i--)
	{
		CPed *ped = CPools::GetPedPool()->GetSlot(i);
		if ( ped )
		{
			if (   ped->GetPosition().x > minx && ped->GetPosition().x < maxx
				&& ped->GetPosition().y > miny && ped->GetPosition().y < maxy
				&& ped->GetPosition().z > minz && ped->GetPosition().z < maxz )
			{
				for ( int32 j = 0; j < NUM_SEGMENTPOINTS; j++ )
				{
					if ( m_abUsed[j] )
					{
						CVector dist = m_avecPos[j] - ped->GetPosition();
						
						if ( dist.MagnitudeSqr() < 5.0f )
						{
							int32 localDir = ped->GetLocalDirection(CVector2D(1.0f, 0.0f));
							
							ped->bIsStanding = false;
							
							ped->ApplyMoveForce(0.0f, 0.0f, 2.0f * CTimer::GetTimeStep());
							
							ped->m_vecMoveSpeed.x = (0.6f * m_avecVelocity[j].x + ped->m_vecMoveSpeed.x) * 0.5f;
							ped->m_vecMoveSpeed.y = (0.6f * m_avecVelocity[j].y + ped->m_vecMoveSpeed.y) * 0.5f;
							
							ped->SetFall(2000, AnimationId(ANIM_KO_SKID_FRONT + localDir), 0);
							
							CFire *fire = ped->m_pFire;
							if ( fire )
								fire->Extinguish();
							
							j = NUM_SEGMENTPOINTS;
						}
					}
				}
			}
		}
	}	
}

void CWaterCannons::Init(void)
{
	for ( int32 i = 0; i < NUM_WATERCANNONS; i++ )
		aCannons[i].Init();
}

void CWaterCannons::UpdateOne(uint32 id, CVector *pos, CVector *dir)
{
	ASSERT(pos != NULL);
	ASSERT(dir != NULL);
	
	// find the one by id
	{
		int32 n = 0;
		while ( n < NUM_WATERCANNONS && id != aCannons[n].m_nId )
			n++;
		
		if ( n < NUM_WATERCANNONS )
		{
			aCannons[n].Update_NewInput(pos, dir);
			return;
		}
	}
	
	// if no luck then find a free one
	{
		int32 n = 0;
		while ( n < NUM_WATERCANNONS && 0 != aCannons[n].m_nId )
			n++;
		
		if ( n < NUM_WATERCANNONS )
		{
			aCannons[n].Init();
			aCannons[n].m_nId = id;
			aCannons[n].Update_NewInput(pos, dir);
			return;
		}
	}
}

void CWaterCannons::Update(void)
{
	for ( int32 i = 0; i < NUM_WATERCANNONS; i++ )
	{
		if ( aCannons[i].m_nId != 0 )
			aCannons[i].Update_OncePerFrame(i);
	}
}

void CWaterCannons::Render(void)
{
	for ( int32 i = 0; i < NUM_WATERCANNONS; i++ )
	{
		if ( aCannons[i].m_nId != 0 )
			aCannons[i].Render();
	}
}

STARTPATCHES
	InjectHook(0x521A30, &CWaterCannon::Init, PATCH_JUMP);
	InjectHook(0x521B80, &CWaterCannon::Update_OncePerFrame, PATCH_JUMP);
	InjectHook(0x521CC0, &CWaterCannon::Update_NewInput, PATCH_JUMP);
	InjectHook(0x521D30, &CWaterCannon::Render, PATCH_JUMP);
	InjectHook(0x5220B0, &CWaterCannon::PushPeds, PATCH_JUMP);
	InjectHook(0x522440, CWaterCannons::Init, PATCH_JUMP);
	InjectHook(0x522470, CWaterCannons::UpdateOne, PATCH_JUMP);
	InjectHook(0x522510, CWaterCannons::Update, PATCH_JUMP);
	InjectHook(0x522550, CWaterCannons::Render, PATCH_JUMP);
	//InjectHook(0x522B40, `global constructor keyed to'watercannon.cpp, PATCH_JUMP);
	//InjectHook(0x522B60, CWaterCannon::CWaterCannon, PATCH_JUMP);
ENDPATCHES