#include "common.h"
#include "main.h"

#include "General.h"
#include "Timer.h"
#include "Weather.h"
#include "Camera.h"
#include "World.h"
#include "Vehicle.h"
#include "ZoneCull.h"
#include "Stats.h"
#include "TxdStore.h"
#include "RenderBuffer.h"
#include "Rubbish.h"

#define RUBBISH_MAX_DIST (23.0f)
#define RUBBISH_FADE_DIST (20.0f)

RwTexture *gpRubbishTexture[4];
RwImVertexIndex RubbishIndexList[6];
bool CRubbish::bRubbishInvisible;
int CRubbish::RubbishVisibility;
COneSheet CRubbish::aSheets[NUM_RUBBISH_SHEETS];
COneSheet CRubbish::StartEmptyList;
COneSheet CRubbish::EndEmptyList;
COneSheet CRubbish::StartStaticsList;
COneSheet CRubbish::EndStaticsList;
COneSheet CRubbish::StartMoversList;
COneSheet CRubbish::EndMoversList;


void
COneSheet::AddToList(COneSheet *list)
{
	this->m_next = list->m_next;
	this->m_prev = list;
	list->m_next = this;
	this->m_next->m_prev = this;
}

void
COneSheet::RemoveFromList(void)
{
	m_next->m_prev = m_prev;
	m_prev->m_next = m_next;
}


void
CRubbish::Render(void)
{
	int type;

	if(RubbishVisibility == 0)
		return;

	RwRenderStateSet(rwRENDERSTATEZWRITEENABLE, (void*)FALSE);
	RwRenderStateSet(rwRENDERSTATEVERTEXALPHAENABLE, (void*)TRUE);
	RwRenderStateSet(rwRENDERSTATEFOGENABLE, (void*)TRUE);

	for(type = 0; type < 4; type++){
		if(type < 3 || CStats::PamphletMissionPassed)
			RwRenderStateSet(rwRENDERSTATETEXTURERASTER, RwTextureGetRaster(gpRubbishTexture[type]));

		TempBufferIndicesStored = 0;
		TempBufferVerticesStored = 0;

		COneSheet *sheet;
		for(sheet = &aSheets[type*NUM_RUBBISH_SHEETS / 4];
		    sheet < &aSheets[(type+1)*NUM_RUBBISH_SHEETS / 4];
		    sheet++){
			if(sheet->m_state == 0)
				continue;

			uint32 alpha = 100;
			CVector pos;
			if(sheet->m_state == 1){
				pos = sheet->m_basePos;
				if(!sheet->m_isVisible)
					alpha = 0;
			}else{
				pos = sheet->m_animatedPos;
				// Not fully visible during animation, calculate current alpha
				if(!sheet->m_isVisible || !sheet->m_targetIsVisible){
					float t = (float)(CTimer::GetTimeInMilliseconds() - sheet->m_moveStart)/sheet->m_moveDuration;
					float f1 = sheet->m_isVisible ? 1.0f-t : 0.0f;
					float f2 = sheet->m_targetIsVisible ? t : 0.0f;
					alpha = 100 * (f1+f2);
				}
			}

			float camDist = (pos - TheCamera.GetPosition()).Magnitude2D();
			if(camDist < RUBBISH_MAX_DIST){
				if(camDist >= RUBBISH_FADE_DIST)
					alpha -= alpha*(camDist-RUBBISH_FADE_DIST)/(RUBBISH_MAX_DIST-RUBBISH_FADE_DIST);
				alpha = (RubbishVisibility*alpha)/256;

				float vx1, vy1, vx2, vy2;
				if(type == 0 || type == 1){
					vx1 = 0.9f*Sin(sheet->m_angle);
					vy1 = 0.9f*Cos(sheet->m_angle);
					vx2 = 0.3f*Cos(sheet->m_angle);
					vy2 = -0.3f*Sin(sheet->m_angle);
				}else{
					vx1 = 0.3f*Sin(sheet->m_angle);
					vy1 = 0.3f*Cos(sheet->m_angle);
					vx2 = 0.3f*Cos(sheet->m_angle);
					vy2 = -0.3f*Sin(sheet->m_angle);
				}

				int v = TempBufferVerticesStored;
				RwIm3DVertexSetPos(&TempBufferRenderVertices[v+0], pos.x + vx1 + vx2, pos.y + vy1 + vy2, pos.z);
				RwIm3DVertexSetPos(&TempBufferRenderVertices[v+1], pos.x + vx1 - vx2, pos.y + vy1 - vy2, pos.z);
				RwIm3DVertexSetPos(&TempBufferRenderVertices[v+2], pos.x - vx1 + vx2, pos.y - vy1 + vy2, pos.z);
				RwIm3DVertexSetPos(&TempBufferRenderVertices[v+3], pos.x - vx1 - vx2, pos.y - vy1 - vy2, pos.z);
				RwIm3DVertexSetRGBA(&TempBufferRenderVertices[v+0], 255, 255, 255, alpha);
				RwIm3DVertexSetRGBA(&TempBufferRenderVertices[v+1], 255, 255, 255, alpha);
				RwIm3DVertexSetRGBA(&TempBufferRenderVertices[v+2], 255, 255, 255, alpha);
				RwIm3DVertexSetRGBA(&TempBufferRenderVertices[v+3], 255, 255, 255, alpha);
				RwIm3DVertexSetU(&TempBufferRenderVertices[v+0], 0.0f);
				RwIm3DVertexSetV(&TempBufferRenderVertices[v+0], 0.0f);
				RwIm3DVertexSetU(&TempBufferRenderVertices[v+1], 1.0f);
				RwIm3DVertexSetV(&TempBufferRenderVertices[v+1], 0.0f);
				RwIm3DVertexSetU(&TempBufferRenderVertices[v+2], 0.0f);
				RwIm3DVertexSetV(&TempBufferRenderVertices[v+2], 1.0f);
				RwIm3DVertexSetU(&TempBufferRenderVertices[v+3], 1.0f);
				RwIm3DVertexSetV(&TempBufferRenderVertices[v+3], 1.0f);

				int i = TempBufferIndicesStored;
				TempBufferRenderIndexList[i+0] = RubbishIndexList[0] + TempBufferVerticesStored;
				TempBufferRenderIndexList[i+1] = RubbishIndexList[1] + TempBufferVerticesStored;
				TempBufferRenderIndexList[i+2] = RubbishIndexList[2] + TempBufferVerticesStored;
				TempBufferRenderIndexList[i+3] = RubbishIndexList[3] + TempBufferVerticesStored;
				TempBufferRenderIndexList[i+4] = RubbishIndexList[4] + TempBufferVerticesStored;
				TempBufferRenderIndexList[i+5] = RubbishIndexList[5] + TempBufferVerticesStored;
				TempBufferVerticesStored += 4;
				TempBufferIndicesStored += 6;
			}
		}

		if(TempBufferIndicesStored != 0){
			LittleTest();
			if(RwIm3DTransform(TempBufferRenderVertices, TempBufferVerticesStored, nil, rwIM3D_VERTEXUV)){
				RwIm3DRenderIndexedPrimitive(rwPRIMTYPETRILIST, TempBufferRenderIndexList, TempBufferIndicesStored);
				RwIm3DEnd();
			}
		}
	}

	RwRenderStateSet(rwRENDERSTATEFOGENABLE, (void*)FALSE);
	RwRenderStateSet(rwRENDERSTATEZWRITEENABLE, (void*)FALSE);
	RwRenderStateSet(rwRENDERSTATEVERTEXALPHAENABLE, (void*)TRUE);
}

void
CRubbish::StirUp(CVehicle *veh)
{
	if((CTimer::GetFrameCounter() ^ (veh->m_randomSeed&3)) == 0)
		return;

	if(Abs(veh->GetPosition().x - TheCamera.GetPosition().x) < 20.0f &&
	   Abs(veh->GetPosition().y - TheCamera.GetPosition().y) < 20.0f)
		if(Abs(veh->GetMoveSpeed().x) > 0.05f || Abs(veh->GetMoveSpeed().y) > 0.05f){
			float speed = veh->GetMoveSpeed().Magnitude2D();
			if(speed > 0.05f){
				bool movingForward = DotProduct2D(veh->GetMoveSpeed(), veh->GetForward()) > 0.0f;
				COneSheet *sheet = StartStaticsList.m_next;
				CVector2D size = veh->GetColModel()->boundingBox.max;

				// Check all static sheets
				while(sheet != &EndStaticsList){
					COneSheet *next = sheet->m_next;
					CVector2D carToSheet = sheet->m_basePos - veh->GetPosition();
					float distFwd = DotProduct2D(carToSheet, veh->GetForward());

					// sheet has to be a bit behind car
					if(movingForward && distFwd < -0.5f*size.y && distFwd > -1.5f*size.y ||
					   !movingForward && distFwd > 0.5f*size.y && distFwd < 1.5f*size.y){
						float distSide = Abs(DotProduct2D(carToSheet, veh->GetRight()));
						if(distSide < 1.5*size.x){
							// Check with higher speed for sheet directly behind car
							float speedToCheck = distSide < size.x ? speed : speed*0.5f;
							if(speedToCheck > 0.05f){
								sheet->m_state = 2;
								if(speedToCheck > 0.15f)
									sheet->m_animationType = 2;
								else
									sheet->m_animationType = 1;
								sheet->m_moveDuration = 2000;
								sheet->m_xDist = veh->GetMoveSpeed().x;
								sheet->m_yDist = veh->GetMoveSpeed().y;
								float dist = Sqrt(SQR(sheet->m_xDist)+SQR(sheet->m_yDist));
								sheet->m_xDist *= 25.0f*speed/dist;
								sheet->m_yDist *= 25.0f*speed/dist;
								sheet->m_animHeight = 3.0f*speed;
								sheet->m_moveStart = CTimer::GetTimeInMilliseconds();
								float tx = sheet->m_basePos.x + sheet->m_xDist;
								float ty = sheet->m_basePos.y + sheet->m_yDist;
								float tz = sheet->m_basePos.z + 3.0f;
								sheet->m_targetZ = CWorld::FindGroundZFor3DCoord(tx, ty, tz, nil) + 0.1f;
								sheet->RemoveFromList();
								sheet->AddToList(&StartMoversList);
							}
						}
					}

					sheet = next;
				}
			}
		}
}

static float aAnimations[3][34] = {
	{ 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f,
	  0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f },

	// Normal move
	{ 0.0f, 0.05f, 0.12f, 0.25f, 0.42f, 0.57f, 0.68f, 0.8f, 0.86f, 0.9f, 0.93f, 0.95f, 0.96f, 0.97f, 0.98f, 0.99f, 1.0f,	// XY movemnt
	  0.15f, 0.35f, 0.6f, 0.9f, 1.2f, 1.25f, 1.3f, 1.2f, 1.1f, 0.95f, 0.8f, 0.6f, 0.45f, 0.3f, 0.2f, 0.1f, 0 },	// Z movement

	// Stirred up by fast vehicle
	{ 0.0f, 0.05f, 0.12f, 0.25f, 0.42f, 0.57f, 0.68f, 0.8f, 0.95f, 1.1f, 1.15f, 1.18f, 1.15f, 1.1f, 1.05f, 1.03f, 1.0f,
	  0.15f, 0.35f, 0.6f, 0.9f, 1.2f, 1.25f, 1.3f, 1.2f, 1.1f, 0.95f, 0.8f, 0.6f, 0.45f, 0.3f, 0.2f, 0.1f, 0 }
};

void
CRubbish::Update(void)
{
	bool foundGround;

	// FRAMETIME
	if(bRubbishInvisible)
		RubbishVisibility = Max(RubbishVisibility-5, 0);
	else
		RubbishVisibility = Min(RubbishVisibility+5, 255);

	// Spawn a new sheet
	COneSheet *sheet = StartEmptyList.m_next;
	if(sheet != &EndEmptyList){
		float spawnDist;
		float spawnAngle;

		spawnDist = (CGeneral::GetRandomNumber()&0xFF)/256.0f + RUBBISH_MAX_DIST;
		uint8 r = CGeneral::GetRandomNumber();
		if(r&1)
			spawnAngle = (CGeneral::GetRandomNumber()&0xFF)/256.0f * 6.28f;
		else
			spawnAngle = (r-128)/160.0f + TheCamera.Orientation;
		sheet->m_basePos.x = TheCamera.GetPosition().x + spawnDist*Sin(spawnAngle);
		sheet->m_basePos.y = TheCamera.GetPosition().y + spawnDist*Cos(spawnAngle);
		sheet->m_basePos.z = CWorld::FindGroundZFor3DCoord(sheet->m_basePos.x, sheet->m_basePos.y, TheCamera.GetPosition().z, &foundGround) + 0.1f;
		if(foundGround){
			// Found ground, so add to statics list
			sheet->m_angle = (CGeneral::GetRandomNumber()&0xFF)/256.0f * 6.28f;
			sheet->m_state = 1;
			if(CCullZones::FindAttributesForCoors(sheet->m_basePos, nil) & ATTRZONE_NORAIN)
				sheet->m_isVisible = false;
			else
				sheet->m_isVisible = true;
			sheet->RemoveFromList();
			sheet->AddToList(&StartStaticsList);
		}
	}

	// Process animation
	sheet = StartMoversList.m_next;
	while(sheet != &EndMoversList){
		uint32 currentTime = CTimer::GetTimeInMilliseconds() - sheet->m_moveStart;
		if(currentTime < sheet->m_moveDuration){
			// Animation
			int step = 16 * currentTime / sheet->m_moveDuration;	// 16 steps in animation
			int stepTime = sheet->m_moveDuration/16;	// time in each step
			float s = (float)(currentTime - stepTime*step) / stepTime;	// position on step
			float t = (float)currentTime / sheet->m_moveDuration;	// position on total animation
			// factors for xy and z-movment
			float fxy = aAnimations[sheet->m_animationType][step]*(1.0f-s) + aAnimations[sheet->m_animationType][step+1]*s;
			float fz = aAnimations[sheet->m_animationType][step+17]*(1.0f-s) + aAnimations[sheet->m_animationType][step+1+17]*s;
			sheet->m_animatedPos.x = sheet->m_basePos.x + fxy*sheet->m_xDist;
			sheet->m_animatedPos.y = sheet->m_basePos.y + fxy*sheet->m_yDist;
			sheet->m_animatedPos.z = (1.0f-t)*sheet->m_basePos.z + t*sheet->m_targetZ + fz*sheet->m_animHeight;
			sheet->m_angle += CTimer::GetTimeStep()*0.04f;
			if(sheet->m_angle > 6.28f)
				sheet->m_angle -= 6.28f;
			sheet = sheet->m_next;
		}else{
			// End of animation, back into statics list
			sheet->m_basePos.x += sheet->m_xDist;
			sheet->m_basePos.y += sheet->m_yDist;
			sheet->m_basePos.z = sheet->m_targetZ;
			sheet->m_state = 1;
			sheet->m_isVisible = sheet->m_targetIsVisible;

			COneSheet *next = sheet->m_next;
			sheet->RemoveFromList();
			sheet->AddToList(&StartStaticsList);
			sheet = next;
		}
	}

	// Stir up a sheet by wind
	// FRAMETIME
	int freq;
	if(CWeather::Wind < 0.1f)
		freq = 31;
	else if(CWeather::Wind < 0.4f)
		freq = 7;
	else if(CWeather::Wind < 0.7f)
		freq = 1;
	else
		freq = 0;
	if((CTimer::GetFrameCounter() & freq) == 0){
		// Pick a random sheet and set animation state if static
		int i = CGeneral::GetRandomNumber() % NUM_RUBBISH_SHEETS;
		if(aSheets[i].m_state == 1){
			aSheets[i].m_moveStart = CTimer::GetTimeInMilliseconds();
			aSheets[i].m_moveDuration = CWeather::Wind*1500.0f + 1000.0f;
			aSheets[i].m_animHeight = 0.2f;
			aSheets[i].m_xDist = 3.0f*CWeather::Wind;
			aSheets[i].m_yDist = 3.0f*CWeather::Wind;
			// Check if target position is ok
			float tx = aSheets[i].m_basePos.x + aSheets[i].m_xDist;
			float ty = aSheets[i].m_basePos.y + aSheets[i].m_yDist;
			float tz = aSheets[i].m_basePos.z + 3.0f;
			aSheets[i].m_targetZ = CWorld::FindGroundZFor3DCoord(tx, ty, tz, &foundGround) + 0.1f;
			if(CCullZones::FindAttributesForCoors(CVector(tx, ty, aSheets[i].m_targetZ), nil) & ATTRZONE_NORAIN)
				aSheets[i].m_targetIsVisible = false;
			else
				aSheets[i].m_targetIsVisible = true;
			if(foundGround){
				// start animation
				aSheets[i].m_state = 2;
				aSheets[i].m_animationType = 1;
				aSheets[i].RemoveFromList();
				aSheets[i].AddToList(&StartMoversList);
			}
		}
	}

	// Remove sheets that are too far away
	int i = (CTimer::GetFrameCounter()%(NUM_RUBBISH_SHEETS/4))*4;
	int last = ((CTimer::GetFrameCounter()%(NUM_RUBBISH_SHEETS/4)) + 1)*4;
	for(; i < last; i++){
		if(aSheets[i].m_state == 1 &&
		   (aSheets[i].m_basePos - TheCamera.GetPosition()).MagnitudeSqr2D() > SQR(RUBBISH_MAX_DIST+1.0f)){
			aSheets[i].m_state = 0;
			aSheets[i].RemoveFromList();
			aSheets[i].AddToList(&StartEmptyList);
		}
	}
}

void
CRubbish::SetVisibility(bool visible)
{
	bRubbishInvisible = !visible;
}

void
CRubbish::Init(void)
{
	int i;
	for(i = 0; i < NUM_RUBBISH_SHEETS; i++){
		aSheets[i].m_state = 0;
		if(i < NUM_RUBBISH_SHEETS-1)
			aSheets[i].m_next = &aSheets[i+1];
		else
			aSheets[i].m_next = &EndEmptyList;
		if(i > 0)
			aSheets[i].m_prev = &aSheets[i-1];
		else
			aSheets[i].m_prev = &StartEmptyList;
	}

	StartEmptyList.m_next = &aSheets[0];
	StartEmptyList.m_prev = nil;
	EndEmptyList.m_next = nil;
	EndEmptyList.m_prev = &aSheets[NUM_RUBBISH_SHEETS-1];

	StartStaticsList.m_next = &EndStaticsList;
	StartStaticsList.m_prev = nil;
	EndStaticsList.m_next = nil;
	EndStaticsList.m_prev = &StartStaticsList;

	StartMoversList.m_next = &EndMoversList;
	StartMoversList.m_prev = nil;
	EndMoversList.m_next = nil;
	EndMoversList.m_prev = &StartMoversList;

	RubbishIndexList[0] = 0;
	RubbishIndexList[1] = 1;
	RubbishIndexList[2] = 2;
	RubbishIndexList[3] = 1;
	RubbishIndexList[4] = 3;
	RubbishIndexList[5] = 2;

	CTxdStore::PushCurrentTxd();
	int slot = CTxdStore::FindTxdSlot("particle");
	CTxdStore::SetCurrentTxd(slot);
	gpRubbishTexture[0] = RwTextureRead("gameleaf01_64", nil);
	gpRubbishTexture[1] = RwTextureRead("gameleaf02_64", nil);
	gpRubbishTexture[2] = RwTextureRead("newspaper01_64", nil);
	gpRubbishTexture[3] = RwTextureRead("newspaper02_64", nil);
	CTxdStore::PopCurrentTxd();
	RubbishVisibility = 255;
	bRubbishInvisible = false;
}

void
CRubbish::Shutdown(void)
{
	RwTextureDestroy(gpRubbishTexture[0]);
	gpRubbishTexture[0] = nil;
	RwTextureDestroy(gpRubbishTexture[1]);
	gpRubbishTexture[1] = nil;
	RwTextureDestroy(gpRubbishTexture[2]);
	gpRubbishTexture[2] = nil;
	RwTextureDestroy(gpRubbishTexture[3]);
	gpRubbishTexture[3] = nil;
}
