//
// Asura_PlatformRenderWater.cpp
//
// Created by KOR - 10 October 2006
//
// Copyright (C) 2006 Rebellion, All Rights Reserved.
//

#include "Core/Asura.h"

// This file's header.
#include "Rendering/Asura_Water.h"

// Asura
#include "Camera/Asura_Camera.h"
#include "Core/Asura_TextureManagement.h"
#include "Core/Asura_Timers.h"
#include "Rendering/Asura_Colour.h"
#include "Rendering/Asura_Fog.h"
#include "Rendering/Asura_Render.h"

// Wii
#include "Rendering/Asura_GX.h"
#include "Rendering/Asura_GX_Environment.h"
#include "Rendering/Asura_PlatformRenderWater.h"
#include "SpecialFX/Asura_Wii_Skybox.h"
#include "SpecialFX/Asura_GX_Fog.h"

// Lib
#include "revolution/gx.h"

static int g_iBumpMapTexture = ASURA_TEXTURE_NONE;

void Asura_Water_System::Platform_Initialise(void)
{
}

void Asura_Water_System::Platform_Reset(void)
{
}

void Asura_Water_System::Platform_Shutdown(void)
{
}

DEBUGVAR bool dbg_bRenderSkyReflectionTexture = false;
DEBUGVAR bool dbg_bBumpMapTest = true;
DEBUGVAR float dbg_fWaterScrollSpeedU = 0.07f;
DEBUGVAR float dbg_fWaterScrollSpeedV = 0.05f;
DEBUGVAR float dbg_fScale0 = 1.f;
DEBUGVAR float dbg_fScale1 = 1.11f;
DEBUGVAR float dbg_fBumpOffsetScaleU = 0.25f;
DEBUGVAR float dbg_fBumpOffsetScaleV = 0.25f;
void Asura_Water_System::Platform_BeginRender(void)
{
	Asura_Render::CurrentStates.RequestZBufferEnabled(true);
	Asura_Render::CurrentStates.RequestZBufferWriteEnabled(true);
	Asura_Render::CurrentStates.RequestZBufferCompareMode(ASURA_ZBUFFERCMP_LESSEQUAL);
	Asura_Render::CurrentStates.RequestCullMode(ASURA_CULLMODE_NONE);
	Asura_Render::CurrentStates.RequestAlphaTest(false);
	Asura_Render::CurrentStates.RequestTextureIndex(0, s_pxController->m_aiTextureID[0]);
	Asura_Render::CurrentStates.RequestTextureIndex(1, g_iBumpMapTexture);
	Asura_Render::CurrentStates.RequestTranslucencyMode(ASURA_TRANSLUCENCY_OFF);

	GXSetTevSwapMode(GX_TEVSTAGE0, GX_TEV_SWAP0, GX_TEV_SWAP0);

	// Number of Texture Coordinates to be Generated
	GXSetNumTexGens(1);
	// Channel Color Used
	GXSetNumChans(1);
	// Number of TEV Stages Used
	GXSetNumTevStages(1);

	GXSetTexCoordGen(GX_TEXCOORD0, GX_TG_MTX2x4, GX_TG_TEX0, GX_IDENTITY);

	if(dbg_bRenderSkyReflectionTexture)
	{
		GXLoadTexObj(Asura_GX_Water::GetReflectionTexture(), GX_TEXMAP0);

		GXSetTevOrder( GX_TEVSTAGE0, GX_TEXCOORD0, GX_TEXMAP0, GX_COLOR_NULL);
		// Input = Tex colour, raster colour
		GXSetTevColorIn( GX_TEVSTAGE0, GX_CC_ZERO, GX_CC_ZERO, GX_CC_ZERO, GX_CC_TEXC );
		// Output Color = Tex colour * raster colour
		GXSetTevColorOp( GX_TEVSTAGE0, GX_TEV_ADD, GX_TB_ZERO, GX_CS_SCALE_1, GX_ENABLE, GX_TEVPREV );
		// Input = 0
		GXSetTevAlphaIn( GX_TEVSTAGE0, GX_CA_ZERO, GX_CA_ZERO, GX_CA_ZERO, GX_CA_ZERO );
		// Output = 1
		GXSetTevAlphaOp( GX_TEVSTAGE0, GX_TEV_ADD, GX_TB_ADDHALF, GX_CS_SCALE_2, GX_ENABLE, GX_TEVPREV );

		GXSetChanCtrl(GX_COLOR0A0, GX_DISABLE, GX_SRC_REG, GX_SRC_REG, GX_LIGHT_NULL, GX_DF_CLAMP, GX_AF_NONE);

		GXSetTexCoordGen(GX_TEXCOORD0, GX_TG_MTX2x4, GX_TG_TEX0, GX_IDENTITY);

		GXClearVtxDesc( );
		GXSetVtxDesc( GX_VA_POS, GX_DIRECT );
		GXSetVtxDesc( GX_VA_TEX0, GX_DIRECT );

		Asura_GX::Set3DProjection(false);
		const int iScreenWidth = Asura_GX::GetScreenWidth();
		const int iScreenHeight = Asura_GX::GetScreenHeight();
		const float fScreenWidth = static_cast<float>(iScreenWidth);
		const float fScreenHeight = static_cast<float>(iScreenHeight);
		const int iHalfWidth = iScreenWidth / 2;
		const int iHalfHeight = iScreenHeight / 2;
		const float fHalfWidth = static_cast<float>(iHalfWidth);
		const float fHalfHeight = static_cast<float>(iHalfHeight);
		GXBegin(GX_QUADS, GX_VTXFMT2, 4);
			GXPosition2f32(0.0F, 0.0F);
			GXTexCoord2f32(0.0F, 0.0F);
			GXPosition2f32(fHalfWidth, 0.0F);
			GXTexCoord2f32(1.0F, 0.0F);
			GXPosition2f32(fHalfWidth, fHalfHeight);
			GXTexCoord2f32(1.0F, 1.0F);
			GXPosition2f32(0.0F, fHalfHeight);
			GXTexCoord2f32(0.0F, 1.0F);
		GXEnd();
	}

	// Make sure position and normal matrices are correct.
	Asura_GX::Set3DProjection(true, true);
	// Tex co-ord 0 is the base texture UVs
	static float fUOffset = 0.f;
	static float fVOffset = 0.f;
	fUOffset += Asura_Timers::GetGameTimeSlice() * dbg_fWaterScrollSpeedU;
	fVOffset += Asura_Timers::GetGameTimeSlice() * dbg_fWaterScrollSpeedV;
	Mtx xTexMtx0;
	MTXTrans(xTexMtx0, 0.f, fVOffset, 0.f);
	xTexMtx0[0][0] = dbg_fScale0;
	xTexMtx0[1][1] = dbg_fScale0;
	Mtx xTexMtx1;
	MTXTrans(xTexMtx1, fUOffset, 0.f, 0.f);
	xTexMtx1[0][0] = dbg_fScale1;
	xTexMtx1[1][1] = dbg_fScale1;
	GXLoadTexMtxImm(xTexMtx0, GX_TEXMTX0, GX_MTX2x4);
	GXLoadTexMtxImm(xTexMtx1, GX_TEXMTX1, GX_MTX2x4);
	GXSetTexCoordGen(GX_TEXCOORD0, GX_TG_MTX2x4, GX_TG_TEX0, GX_TEXMTX0);
	GXSetTexCoordGen(GX_TEXCOORD1, GX_TG_MTX2x4, GX_TG_TEX0, GX_TEXMTX0);
	GXSetTexCoordGen(GX_TEXCOORD2, GX_TG_MTX2x4, GX_TG_TEX0, GX_TEXMTX1);

	// No lighting
	GXSetChanCtrl(GX_COLOR0, GX_DISABLE, GX_SRC_REG, GX_SRC_VTX, GX_LIGHT_NULL, GX_DF_CLAMP, GX_AF_NONE);
	// Use the alpha lighting to simulate fresnel.
	GXSetChanCtrl(GX_ALPHA0, GX_ENABLE, GX_SRC_REG, GX_SRC_REG, GX_LIGHT0, GX_DF_CLAMP, GX_AF_NONE);
	GXColor xAmb = {0, 0, 0, 0};
	GXColor xWhite = {0xff, 0xff, 0xff, 0xff};
	GXLightObj xLight;
	GXSetChanAmbColor(GX_COLOR0A0, xAmb);
	GXSetChanMatColor(GX_COLOR0A0, xWhite);	
	GXInitLightColor(&xLight, xWhite);
	GXInitLightPos(&xLight, 0.f, 0.f, 0.f);
	GXInitLightDir(&xLight, 1.0f, 0.0f, 0.0f);
	GXInitLightAttnA(&xLight, 1.0f, 0.0f, 0.0f);
	GXInitLightDistAttn(&xLight, 1.f, 1.f, GX_DA_OFF);
	GXLoadLightObjImm(&xLight, GX_LIGHT0);

	GXClearVtxDesc();
	GXSetVtxDesc(GX_VA_POS, GX_DIRECT);
	GXSetVtxDesc(GX_VA_NRM, GX_DIRECT);
	GXSetVtxDesc(GX_VA_CLR0, GX_DIRECT);
	GXSetVtxDesc(GX_VA_TEX0, GX_DIRECT);

	Mtx xProj;
	float afProjArray[GX_PROJECTION_SZ];
	GXGetProjectionv( afProjArray );        

	MTXIdentity( xProj );
	xProj[0][0] = -afProjArray[1];
	xProj[0][2] = 1.0f;
	xProj[1][1] = afProjArray[3];
	xProj[1][2] = 1.0f;
	xProj[2][2] = 2.0f;

	GXLoadTexMtxImm(xProj, GX_PTTEXMTX0, GX_MTX3x4);

	GXSetTevSwapMode(GX_TEVSTAGE0, GX_TEV_SWAP0, GX_TEV_SWAP0); // RGBA
	GXSetTevSwapMode(GX_TEVSTAGE1, GX_TEV_SWAP0, GX_TEV_SWAP0); // RGBA
	GXSetTevSwapMode(GX_TEVSTAGE2, GX_TEV_SWAP0, GX_TEV_SWAP0); // RGBA

	if(dbg_bBumpMapTest)
	{
		GXLoadTexObj(Asura_GX_Water::GetReflectionTexture(), GX_TEXMAP2);

		// Tex co-ord 0 is the projected sky reflection texture.
		GXSetTexCoordGen2(
			GX_TEXCOORD0,   // Output tex coord id
			GX_TG_MTX3x4,   // Texgen function type
			GX_TG_POS,      // Texgen source
			GX_PNMTX0,      // 1st. transform matrix ( re-use position matrix )
			GX_FALSE,       // normalization
			GX_PTTEXMTX0 ); // 2nd. transform matrix

		GXSetNumTevStages(2);

		GXSetNumIndStages(2);

		GXSetNumTexGens(3);
		GXSetNumChans(1);

		// Set up the indirect bump calculation for Stage 0
		const float afIndTexMtx[2][3] =
		{
			{ dbg_fBumpOffsetScaleU, 0.f, 0.f },
			{ 0.f, dbg_fBumpOffsetScaleV, 0.f },
		};
		GXSetIndTexMtx(GX_ITM_0, afIndTexMtx, 0);

		GXSetIndTexOrder(GX_INDTEXSTAGE0, GX_TEXCOORD1, GX_TEXMAP1);
		GXSetIndTexCoordScale(GX_INDTEXSTAGE0, GX_ITS_1, GX_ITS_1);

		GXSetTevIndirect(
			GX_TEVSTAGE0, // tev stage
			GX_INDTEXSTAGE0, // indirect stage
			GX_ITF_8,     // format
			GX_ITB_STU,   // bias
			GX_ITM_0,     // matrix select (matrix 0 with scale 0)
			GX_ITW_OFF,   // wrap direct S
			GX_ITW_OFF,   // wrap direct T
			FALSE,        // add prev stage output?
			FALSE,        // use unmodified TC for LOD?
			GX_ITBA_OFF   // bump alpha select
			);

		// Stage 0 - direct diffuse, indirect first bump map
		GXSetTevOrder(GX_TEVSTAGE0, GX_TEXCOORD0, GX_TEXMAP0, GX_COLOR0A0);
		GXSetTevColorIn(GX_TEVSTAGE0, GX_CC_ZERO, GX_CC_RASC, GX_CC_TEXC, GX_CC_ZERO);
		GXSetTevColorOp(GX_TEVSTAGE0, GX_TEV_ADD, GX_TB_ZERO, GX_CS_SCALE_1, TRUE, GX_TEVPREV);
		// Alpha = 1
		GXSetTevAlphaIn(GX_TEVSTAGE0, GX_CA_ZERO, GX_CA_ZERO, GX_CA_ZERO, GX_CA_ZERO);
		GXSetTevAlphaOp(GX_TEVSTAGE0, GX_TEV_ADD, GX_TB_ADDHALF, GX_CS_SCALE_2, TRUE, GX_TEVPREV);

		GXSetIndTexOrder(GX_INDTEXSTAGE1, GX_TEXCOORD2, GX_TEXMAP1);
		GXSetIndTexCoordScale(GX_INDTEXSTAGE1, GX_ITS_1, GX_ITS_1);

		GXSetTevIndirect(
			GX_TEVSTAGE1, // tev stage
			GX_INDTEXSTAGE1, // indirect stage
			GX_ITF_8,     // format
			GX_ITB_STU,   // bias
			GX_ITM_0,     // matrix select (matrix 0 with scale 0)
			GX_ITW_0,   // wrap direct S
			GX_ITW_0,   // wrap direct T
			TRUE,         // add prev stage output?
			FALSE,        // use unmodified TC for LOD?
			GX_ITBA_OFF   // bump alpha select
			);

		// Stage 1 - use fresnel to blend between diffuse and reflection
		GXSetTevOrder(GX_TEVSTAGE1, GX_TEXCOORD0, GX_TEXMAP2, GX_COLOR0A0);
		GXSetTevColorIn(GX_TEVSTAGE1, GX_CC_TEXC, GX_CC_CPREV, GX_CC_RASA, GX_CC_ZERO);
		GXSetTevColorOp(GX_TEVSTAGE1, GX_TEV_ADD, GX_TB_ZERO, GX_CS_SCALE_1, TRUE, GX_TEVPREV);
		// Alpha is one
		GXSetTevAlphaIn(GX_TEVSTAGE1, GX_CA_ZERO, GX_CA_ZERO, GX_CA_ZERO, GX_CA_ZERO);
		GXSetTevAlphaOp(GX_TEVSTAGE1, GX_TEV_ADD, GX_TB_ADDHALF, GX_CS_SCALE_2, TRUE, GX_TEVPREV);
	}
	else
	{
		GXSetTevOrder( GX_TEVSTAGE0, GX_TEXCOORD0, GX_TEXMAP0, GX_COLOR0A0);
		GXSetTevColorIn( GX_TEVSTAGE0, GX_CC_ZERO, GX_CC_TEXC, GX_CC_RASC, GX_CC_ZERO );
		GXSetTevColorOp( GX_TEVSTAGE0, GX_TEV_ADD, GX_TB_ZERO, GX_CS_SCALE_1, GX_ENABLE, GX_TEVPREV );
		GXSetTevAlphaIn( GX_TEVSTAGE0, GX_CA_ZERO, GX_CA_ZERO, GX_CA_ZERO, GX_CA_ZERO );
		GXSetTevAlphaOp( GX_TEVSTAGE0, GX_TEV_ADD, GX_TB_ADDHALF, GX_CS_SCALE_2, GX_ENABLE, GX_TEVPREV );
	}
}

void Asura_Water_System::Platform_Render(Asura_Platform_Water_Packet *pxPacket)
{
	Asura_Assert(pxPacket, "");
	if(pxPacket)
	{
		pxPacket->Render();
	}
}

void Asura_Water_System::Platform_EndRender(void)
{ 
	GXSetTevDirect(GX_TEVSTAGE0);
	GXSetTevDirect(GX_TEVSTAGE1);
	GXSetTevDirect(GX_TEVSTAGE2);
}

Asura_Platform_Water_Packet::Asura_Platform_Water_Packet()
: m_pxVertices(NULL)
, m_uVertexCount(0)
, m_uCurrentWriteVertex(0)
{

}

Asura_Platform_Water_Packet::~Asura_Platform_Water_Packet()
{
	delete [] m_pxVertices;
}

void Asura_Platform_Water_Packet::SetVertexCount(u_int uVertexCount)
{
	delete [] m_pxVertices;
	Asura_Assert(m_uCurrentWriteVertex == 0, "Already writing?");
	m_pxVertices = new Asura_GX_Water_Vertex[uVertexCount];
	m_uVertexCount = uVertexCount;
	m_uCurrentWriteVertex = 0;
}

void Asura_Platform_Water_Packet::AddVertex(float x, float y, float z, float r, float g, float b, float u, float v)
{
	Asura_GX_Water_Vertex& xVert = m_pxVertices[m_uCurrentWriteVertex];
	xVert.m_xPos.x = x;
	xVert.m_xPos.y = y;
	xVert.m_xPos.z = z;
	Asura_RGBA xColour(r, g, b, 255.f);
	xColour *= (1.f / 255.f);
	xVert.m_uColour = xColour.ToUIntRGBA(true);
	xVert.m_xTexCoords.x = u;
	xVert.m_xTexCoords.y = v;

	++m_uCurrentWriteVertex;
}

void Asura_Platform_Water_Packet::Close()
{
	Asura_Assert(m_uCurrentWriteVertex == m_uVertexCount, "");
}

static bool g_bEnableWater = true;
void Asura_Platform_Water_Packet::Render() const
{
	if(!g_bEnableWater)
	{
		return;
	}
	GXBegin(GX_TRIANGLES, GX_VTXFMT3, m_uVertexCount);
	for(u_int u = 0; u < m_uVertexCount; ++u)
	{
		Asura_GX_Water_Vertex& xVert = m_pxVertices[u];
		GXPosition3f32(xVert.m_xPos.x, xVert.m_xPos.y, xVert.m_xPos.z);
		GXNormal3f32(0.f, -1.f, 0.f);
		GXColor1u32(xVert.m_uColour);
		GXTexCoord2f32(xVert.m_xTexCoords.x, xVert.m_xTexCoords.y);
	}
	GXEnd();
}

u_char*		Asura_GX_Water::s_pucSkyReflectionBuffer = NULL;
GXTexObj	Asura_GX_Water::s_xSkyReflectionTex;

void Asura_GX_Water::Initialise()
{
	// Allocate buffers
	const int iWidth = Asura_GX::GetScreenWidth() / 2;
	const int iHeight = Asura_GX::GetScreenHeight() / 2; 
	// could use OSRoundUp32B instead?
	const int iImageHeight = iHeight + ((iHeight & 31) ? 32 - (iHeight & 31) : 0);
	// could put these in MEM2?
	s_pucSkyReflectionBuffer = static_cast<u_char*>(Asura_AllocateAlign(iWidth * iImageHeight * sizeof(u_int),ASURA_ALIGN_32_BYTE));
	Asura_Assert( s_pucSkyReflectionBuffer,"Failed to allocate sky reflection buffer");
	GXInitTexObj( &s_xSkyReflectionTex, s_pucSkyReflectionBuffer, iWidth, iHeight, GX_TF_RGBA8, GX_CLAMP, GX_CLAMP, GX_DISABLE );
	GXInitTexObjLOD( &s_xSkyReflectionTex, GX_NEAR, GX_LINEAR, 0.0f, 0.0f, 0.0f, GX_DISABLE, GX_DISABLE, GX_ANISO_1 );

	g_iBumpMapTexture = Asura_Texture_Management::AddTextureWithResetGroup("water_bumpmap.tpl", ASURA_TEXTURE_LOADED_PERMANENT); 
	Asura_Texture_Management::LoadTextures();
}

void Asura_GX_Water::Shutdown()
{
	// Release buffers
	Asura_DeallocateAlign(s_pucSkyReflectionBuffer);
	s_pucSkyReflectionBuffer = NULL;
}

DEBUGVAR bool dbg_bReflectEnvironment = true;
DEBUGVAR bool dbg_bFogSkybox = true;
void Asura_GX_Water::RenderReflectionTexture()
{
	if(!Asura_Water_System::IsAnyWaterVisible())
	{
		return;
	}
	const Asura_RGBA& xFogColour = Asura_Fog::GetFogColour();
	GXColor xGXFogColour;
	Asura_GX::ARGBToGXColor(xFogColour.ToUIntARGB(true), xGXFogColour);
	const float fSkyboxFog = Asura_Fog::GetSkyboxFogBlendFactor();
	if(dbg_bFogSkybox && (fSkyboxFog > 0.f))
	{
		// Set up skybox fog
		GXSetFog(GX_FOG_PERSP_LIN, 0.f, fASURA_GX_FAR_CLIP / fSkyboxFog, fASURA_GX_NEAR_CLIP, fASURA_GX_FAR_CLIP, xGXFogColour);
	}
	// Render the inverted skybox
	Asura_Wii_Skybox::Render(true);

	if(dbg_bReflectEnvironment)
	{
		Asura_GX_Environment::Render(true);
	}
	// Ensure that when we clear, we clear the z too.
	Asura_Render::CurrentStates.RequestZBufferWriteEnabled(true);

	const GXRenderModeObj& xRenderMode = Asura_GX::GetRenderModeObject();
	// Disable the filter as it's doing nasty things to the exact pixel values I'd like.
	GXSetCopyFilter(GX_FALSE, xRenderMode.sample_pattern, GX_FALSE, xRenderMode.vfilter);

	const int iScreenWidth = Asura_GX::GetScreenWidth();
	const int iScreenHeight = Asura_GX::GetScreenHeight();
	const int iHalfWidth = iScreenWidth / 2;
	const int iHalfHeight = iScreenHeight / 2;

	const float fHalfWidth = static_cast<float>(iHalfWidth);
	const float fHalfHeight = static_cast<float>(iHalfHeight);

	// Copy out full screen 
	GXSetTexCopySrc( 0, 0, iScreenWidth, iScreenHeight );
	GXSetTexCopyDst(iHalfWidth, iHalfHeight, GX_TF_RGBA8, GX_ENABLE );
	// Clear after the copy.
	GXCopyTex( s_pucSkyReflectionBuffer, GX_ENABLE );

	// Re-enable filter
	GXSetCopyFilter(GX_FALSE, xRenderMode.sample_pattern, GX_TRUE, xRenderMode.vfilter);

	Asura_GX_Fog::DisableHardwareFog();
}