/**
*
* \file Asura_Platform_WeatherFX.cpp
*
* Created by Kevin Lea - 07/12/2002 12:28:01
*
* Copyright (C) 2002 Rebellion, All Rights Reserved.
*
* \brief
*
**/

#include "Core/Asura.h"

#include "SpecialFX/Asura_Weather_System.h"

#include "Camera/Asura_Camera.h"
#include "Rendering/Asura_Render.h"
#include "Rendering/Asura_Viewport.h"

#include "Rendering/Asura_Direct3D.h"
#include "Rendering/Asura_Direct3D_Shaders.h"
#include "Rendering/Asura_Direct3D_RenderTargetManager.h"
#include "Rendering/Asura_Direct3D_StoreRenderTargets.h"

static Asura_Direct3D_RenderTarget* s_pxSunGlareTarget = NULL;

Direct3D_VertexBuffer*  s_pxSunVB					= NULL;
Direct3D_VertexBuffer*	s_pxQuadVB					= NULL;

// Octagon vertices
const struct Asura_Weather_System_Sun_Glare_Vertex
{
	float m_fX, m_fY;
	D3DCOLOR m_xColour;
} axSUN_GLARE_VERTICES[] =
{
	{  0.0f,  0.0f, D3DCOLOR_ARGB(0xff, 0xff, 0xff, 0xff),},

	{ -1.00f,  0.00f, D3DCOLOR_ARGB(0x00, 0xff, 0xff, 0xff),},
	{ -0.92f, -0.38f, D3DCOLOR_ARGB(0x00, 0xff, 0xff, 0xff),},
	{ -0.70f, -0.70f, D3DCOLOR_ARGB(0x00, 0xff, 0xff, 0xff),},
	{ -0.38f, -0.92f, D3DCOLOR_ARGB(0x00, 0xff, 0xff, 0xff),},
	{  0.00f, -1.00f, D3DCOLOR_ARGB(0x00, 0xff, 0xff, 0xff),},
	{  0.38f, -0.92f, D3DCOLOR_ARGB(0x00, 0xff, 0xff, 0xff),},
	{  0.70f, -0.70f, D3DCOLOR_ARGB(0x00, 0xff, 0xff, 0xff),},
	{  0.92f, -0.38f, D3DCOLOR_ARGB(0x00, 0xff, 0xff, 0xff),},
	{  1.00f,  0.00f, D3DCOLOR_ARGB(0x00, 0xff, 0xff, 0xff),},
	{  0.92f,  0.38f, D3DCOLOR_ARGB(0x00, 0xff, 0xff, 0xff),},
	{  0.70f,  0.70f, D3DCOLOR_ARGB(0x00, 0xff, 0xff, 0xff),},
	{  0.38f,  0.92f, D3DCOLOR_ARGB(0x00, 0xff, 0xff, 0xff),},
	{  0.00f,  1.00f, D3DCOLOR_ARGB(0x00, 0xff, 0xff, 0xff),},
	{ -0.38f,  0.92f, D3DCOLOR_ARGB(0x00, 0xff, 0xff, 0xff),},
	{ -0.70f,  0.70f, D3DCOLOR_ARGB(0x00, 0xff, 0xff, 0xff),},
	{ -0.92f,  0.38f, D3DCOLOR_ARGB(0x00, 0xff, 0xff, 0xff),},
	{ -1.00f,  0.00f, D3DCOLOR_ARGB(0x00, 0xff, 0xff, 0xff),},
};

const float fSUN_SCALE_MAX = 350.f / 256.f;

DEBUGVAR float fSUN_VIEW_LIMIT = 0.845f;

static u_int s_uNumPasses = 6;

DEBUGVAR bool dbg_bRenderSilhouetteMode = false;
DEBUGVAR bool dbg_bRenderFullscreenOverlay = true;

bool CalcSunRenderingParameters(float& fRadius, float& fCenterX, float& fCenterY)
{
	Asura_Vector_3 sunPosition = Asura_Weather_System::GetSunPos();
	Asura_Vector_3 sunDirection = sunPosition - (*Asura_Camera::GetPosition());
	Asura_Vector_3 lookDirection; Asura_Camera::GetForwardVector(lookDirection);
	sunDirection.Normalise(); lookDirection.Normalise();

	float fSunAngle = sunDirection * lookDirection;
	float fViewLimit = Asura_Maths::Cosine(fSUN_VIEW_LIMIT * Asura_Camera::GetHorizontalFieldOfView());
	if ( fSunAngle < fViewLimit ) 
	{ 
		return false; 
	}

	fRadius = fSUN_SCALE_MAX * ((fSunAngle - fViewLimit) / (1 - fViewLimit));

	Asura_Camera::ProjectWorldCoordsToScreenSpace(sunPosition);
	fCenterX = sunPosition.x;
	fCenterY = sunPosition.y;
	return true;
}

// MGH 17/1/2008: Maximum number of down sampling textures. 16 should be more than enough.
const u_int uASURA_MAX_NUM_DOWNSAMPLING_TEXTURES = 16;

static Direct3D_Texture* s_apxDownsamplingTextures[uASURA_MAX_NUM_DOWNSAMPLING_TEXTURES];
static Direct3D_Surface* s_apxDownsamplingSurfaces[uASURA_MAX_NUM_DOWNSAMPLING_TEXTURES];

static u_int s_uNumDownsamplingTextures = 0;

// if we have antialiasing turned on, we'll need another render target
static Direct3D_Surface* s_pxDemultisamplingRenderTarget = NULL;

void DeallocateDownsamplingTextures();

void AllocateDownsamplingTextures()
{
	// If they're already allocated, release them
	// (See SS2 mantis bug 0043883 - I've not managed to repeat that leak, but I'm attempting to make this more robust)
	Asura_Assert(s_uNumDownsamplingTextures==0, "AllocateDownsamplingTextures() called, but textures already allocated!");

	DeallocateDownsamplingTextures();
 	
	// how many do we want?
	const u_int uTopLevelWidthPower = (u_int)(log((float)Asura_Direct3D::GetPresentParameters().BackBufferWidth) / log(2.f));
	const u_int uTopLevelHeightPower = (u_int)(log((float)Asura_Direct3D::GetPresentParameters().BackBufferHeight) / log(2.f));

	s_uNumDownsamplingTextures = Asura_Maths::Max(uTopLevelWidthPower, uTopLevelHeightPower);
	
	Asura_Assert(s_uNumDownsamplingTextures <= uASURA_MAX_NUM_DOWNSAMPLING_TEXTURES, "Requested too many downsampling textures!");
	s_uNumDownsamplingTextures = Asura_Maths::Min(s_uNumDownsamplingTextures, uASURA_MAX_NUM_DOWNSAMPLING_TEXTURES);

	u_int uWidth = 1 << uTopLevelWidthPower;
	u_int uHeight = 1 << uTopLevelHeightPower;
	
	u_int uLevel;

	for(uLevel = 0; uLevel < s_uNumDownsamplingTextures; ++uLevel)
	{
		Asura_Direct3D::D3DDevice->CreateTexture(uWidth, uHeight, 1, D3DUSAGE_RENDERTARGET, D3DFMT_A8R8G8B8, D3DPOOL_DEFAULT, &s_apxDownsamplingTextures[uLevel], NULL);
		if(!s_apxDownsamplingTextures[uLevel]) break;
		s_apxDownsamplingTextures[uLevel]->GetSurfaceLevel(0, &s_apxDownsamplingSurfaces[uLevel]);
		uWidth = max(1, uWidth >> 1);
		uHeight = max(1, uHeight >> 1);
	}
	
	s_uNumDownsamplingTextures = uLevel;

	if(Asura_Direct3D::GetPresentParameters().MultiSampleType != D3DMULTISAMPLE_NONE)
	{
		Asura_Direct3D::D3DDevice->CreateRenderTarget(	Asura_Direct3D::GetPresentParameters().BackBufferWidth,
														Asura_Direct3D::GetPresentParameters().BackBufferHeight,
														Asura_Direct3D::GetPresentParameters().BackBufferFormat,
														Asura_Direct3D::GetPresentParameters().MultiSampleType,
														Asura_Direct3D::GetPresentParameters().MultiSampleQuality,
														FALSE,
														&s_pxDemultisamplingRenderTarget,
														NULL										);
	}
}

void DeallocateDownsamplingTextures()
{
	if(s_pxDemultisamplingRenderTarget)
	{
		s_pxDemultisamplingRenderTarget->Release();
		s_pxDemultisamplingRenderTarget = NULL;
	}

	for(u_int u = 0; u < s_uNumDownsamplingTextures; ++u)
	{
		if (s_apxDownsamplingSurfaces[u])
		{
			s_apxDownsamplingSurfaces[u]->Release();
			s_apxDownsamplingSurfaces[u] = 0;
		}

		if (s_apxDownsamplingTextures[u])
		{
			s_apxDownsamplingTextures[u]->Release();
			s_apxDownsamplingTextures[u] = 0;
		}
	}

	s_uNumDownsamplingTextures = 0;
}

void Asura_Weather_System::Platform_RenderSunGlare( void )
{
	HRESULT xResult;
	
	if(s_uNumDownsamplingTextures == 0)
	{
		Asura_Assert(false, "Downsampling textures haven't been created");
		return;
	}
	if(!s_pxDemultisamplingRenderTarget && (Asura_Direct3D::GetPresentParameters().MultiSampleType != D3DMULTISAMPLE_NONE))
	{
		Asura_Assert(false, "Hasn't created demultisampling render target");
		return;
	}

	float fRadius, fPositionX, fPositionY;
	if(!CalcSunRenderingParameters(fRadius, fPositionX, fPositionY))
	{
		return; // no sun to draw
	}
	
	if( NULL == s_pxSunGlareTarget && NULL == s_pxDemultisamplingRenderTarget)
	{
		Asura_Assert( false, "Missing render target");
		return;
	}

	D3DPERFCALL(BeginEvent(0xff00ff00, L"Sun Glare"));

	// Save out the old render target
#if defined( ASURA_TARGET_SM3)
	Asura_Direct3D_StoreRenderTargets xRenderTargetStore;
	xRenderTargetStore.StoreRenderTargets();
	Asura_Direct3D::ClearRenderTargets( 1);
#endif

	Direct3D_Surface* pxOldRenderTarget = NULL;
	Asura_Direct3D::D3DDevice->GetRenderTarget(0, &pxOldRenderTarget);

	// Install our render target
	if(Asura_Direct3D::GetPresentParameters().MultiSampleType != D3DMULTISAMPLE_NONE)
	{
		Asura_Direct3D::D3DDevice->SetRenderTarget(0, s_pxDemultisamplingRenderTarget);
	}
	else
	{
		Asura_Direct3D::D3DDevice->SetRenderTarget(0, s_pxSunGlareTarget->GetSurface());
	}

	// Clear it
	Asura_Direct3D::D3DDevice->Clear(0, NULL, D3DCLEAR_TARGET, 0, 0, 0);
	
	// Render sun geometry

	// Set up render states
	Asura_Direct3D_Shaders::RequestVertexShader(ASURA_D3D_VS_SUNGLARE);
	Asura_Direct3D_Shaders::RequestPixelShader(ASURA_D3D_PS_SUNGLARE);
	Asura_Render::CurrentStates.RequestTranslucencyMode(ASURA_TRANSLUCENCY_OFF);
	Asura_Render::CurrentStates.RequestCullMode(ASURA_CULLMODE_NONE);
	Asura_Render::CurrentStates.RequestZBufferEnabled(true);
	Asura_Render::CurrentStates.RequestZBufferCompareMode(ASURA_ZBUFFERCMP_LESSEQUAL);
	Asura_Render::CurrentStates.RequestZBufferWriteEnabled(false);

	float fVertexConstant[12];
	fVertexConstant[0] = 2*fPositionX - 1;
	fVertexConstant[1] = 1 - 2*fPositionY;
	fVertexConstant[2] = 1.0f;
	fVertexConstant[3] = fRadius;
	fVertexConstant[4] = Asura_Weather_System::GetSunColourR();
	fVertexConstant[5] = Asura_Weather_System::GetSunColourG();
	fVertexConstant[6] = Asura_Weather_System::GetSunColourB();
	fVertexConstant[7] = 1.0f;
	fVertexConstant[8] = 1.0f;
	fVertexConstant[9] = ((float)Asura_Viewport::GetWidth() / (float)Asura_Viewport::GetHeight());
	fVertexConstant[11] = fRadius;
	Asura_Direct3D::D3DDevice->SetVertexShaderConstantF(0, fVertexConstant, 3);
	Asura_Direct3D::D3DDevice->SetPixelShaderConstantF(0, &fVertexConstant[4], 1);
	Asura_Direct3D::D3DDevice->SetStreamSource(0, s_pxSunVB, 0, sizeof(Asura_Weather_System_Sun_Glare_Vertex));

	Asura_Direct3D::D3DDevice->DrawPrimitive(D3DPT_TRIANGLEFAN, 0, 16);

	// restore the render target
	Asura_Direct3D::D3DDevice->SetRenderTarget(0, pxOldRenderTarget);
	pxOldRenderTarget->Release();

	// build the mipmap chain
	
	if(Asura_Direct3D::GetPresentParameters().MultiSampleType != D3DMULTISAMPLE_NONE)
	{
		// level 0
		xResult = Asura_Direct3D::D3DDevice->StretchRect(s_pxDemultisamplingRenderTarget, NULL, s_apxDownsamplingSurfaces[0], NULL, D3DTEXF_NONE);
		ASURA_D3D_OK( xResult);
	}
	else
	{
		// level 0
		xResult = Asura_Direct3D::D3DDevice->StretchRect(s_pxSunGlareTarget->GetSurface(), NULL, s_apxDownsamplingSurfaces[0], NULL, D3DTEXF_NONE);
		ASURA_D3D_OK( xResult);
	}

	// all other levels
	for(u_int uLevel = 1; uLevel < s_uNumDownsamplingTextures; ++uLevel)
	{
		xResult = Asura_Direct3D::D3DDevice->StretchRect(s_apxDownsamplingSurfaces[uLevel - 1], NULL, s_apxDownsamplingSurfaces[uLevel], NULL, D3DTEXF_NONE);
		ASURA_D3D_OK( xResult);
	}

	// High-res passes

	Asura_Direct3D_Shaders::RequestVertexShader(ASURA_D3D_VS_SUNRECT);
	Asura_Direct3D_Shaders::RequestPixelShader(ASURA_D3D_PS_NONE);
	Asura_Render::CurrentStates.RequestTranslucencyMode((dbg_bRenderSilhouetteMode)?ASURA_TRANSLUCENCY_OFF : ASURA_TRANSLUCENCY_GLOWING);
	Asura_Direct3D::D3DDevice->SetRenderState(D3DRS_SRCBLEND,D3DBLEND_ONE);
	Asura_Render::CurrentStates.RequestTextureOpMode(ASURA_TEXTUREOP_OPAQUE);
	Asura_Render::CurrentStates.RequestZBufferEnabled(false);
	Asura_Render::CurrentStates.RequestZBufferWriteEnabled(false);
	Asura_Direct3D::D3DDevice->SetStreamSource(0, s_pxQuadVB, 0, sizeof(float) * 5);

	for(u_int uLevel = 0; uLevel < min(s_uNumPasses, s_uNumDownsamplingTextures); ++uLevel)
	{
		// scale up the texcoords to pull the image out a little
		float fConstant[4] = { 1.f - ((4.f / 512.f) * 2 * uLevel), 1.f - ((4.f / 512.f) * 2 * uLevel), ((4.f / 512.f) * uLevel), ((4.f / 512.f) * uLevel) };
		Asura_Direct3D::D3DDevice->SetVertexShaderConstantF(0, fConstant, 1);
		Asura_Direct3D::D3DDevice->SetTexture(0, s_apxDownsamplingTextures[uLevel]);
		Asura_Direct3D::D3DDevice->DrawPrimitive(D3DPT_TRIANGLEFAN, 0, 2);
	}

	// render the lowest, 1x1 image over the whole screen
	if(dbg_bRenderFullscreenOverlay)
	{
		Asura_Direct3D::D3DDevice->SetTexture(0, s_apxDownsamplingTextures[s_uNumDownsamplingTextures-1]);
		Asura_Direct3D::D3DDevice->DrawPrimitive(D3DPT_TRIANGLEFAN, 0, 2);
	}

	// (AAM - 27.09.2005) Mark the texture state cache as invalid, because we directly set the
	// texture ourselves. The next time RequestTextureIndex() is called it'll be corrected

	Asura_Render::CurrentStates.RequestTextureIndex( 0, ASURA_TEXTURE_INVALID );
	
#if defined( ASURA_TARGET_SM3)
	xRenderTargetStore.RestoreRenderTargets();
#endif

	D3DPERFCALL(EndEvent());
}

void Asura_Weather_System::Platform_RenderHorizonGlare( void )
{
}

void Asura_Weather_System::Platform_RenderLensFlare( void )
{
}

void Asura_Weather_System::Platform_Initialise( void )
{
	if( NULL == Asura_Direct3D::D3DDevice)
	{
		return;
	}
	if(Asura_Direct3D::GetPresentParameters().MultiSampleType == D3DMULTISAMPLE_NONE)
	{
		s_pxSunGlareTarget = Asura_Direct3D_RenderTargetManager::CreateRenderTarget(1.0f, 1.0f, D3DFMT_A8R8G8B8, D3DFMT_UNKNOWN, true);
	}
	//s_pxSunGlareIntermediateTarget = Asura_Direct3D_RenderTargetManager::CreateRenderTarget(0.5f, 0.5f, D3DFMT_A8R8G8B8, D3DFMT_UNKNOWN, true);

	Asura_Direct3D_RenderTargetManager::AddResetCallbacks(DeallocateDownsamplingTextures, AllocateDownsamplingTextures);

	// Create the Quad vertex buffer
	if(FAILED(Asura_Direct3D::D3DDevice->CreateVertexBuffer(sizeof(float) * 5 * 4, D3DUSAGE_WRITEONLY, 0, D3DPOOL_MANAGED, &s_pxQuadVB, NULL)))
	{
		return;
	}

	float fQuadVerts[20] = {
		 -1.0f,  1.0f,  1.f,  0.0f,  0.0f ,
		  1.0f,  1.0f,  1.f,  1.0f,  0.0f ,
		  1.0f, -1.0f,  1.f,  1.0f,  1.0f ,
		 -1.0f, -1.0f,  1.f,  0.0f,  1.0f ,
	};

	void* pxVertex = NULL;
	if (SUCCEEDED(s_pxQuadVB->Lock(0, 0, &pxVertex, 0)))
	{
		memcpy(pxVertex, fQuadVerts, sizeof(fQuadVerts));
		s_pxQuadVB->Unlock();
	} else {
		Asura_Assert(false, "Failed to lock VB");
	}

	if(FAILED(Asura_Direct3D::D3DDevice->CreateVertexBuffer(sizeof(axSUN_GLARE_VERTICES), D3DUSAGE_WRITEONLY, 0, D3DPOOL_MANAGED, &s_pxSunVB, NULL)))
	{
		return;
	}

	if (SUCCEEDED(s_pxSunVB->Lock(0, 0, &pxVertex, 0)))
	{
		memcpy(pxVertex, axSUN_GLARE_VERTICES, sizeof(axSUN_GLARE_VERTICES));
		s_pxSunVB->Unlock();
	} else {
		Asura_Assert(false, "Failed to lock VB");
	}

	AllocateDownsamplingTextures();
}

void Asura_Weather_System::Platform_Shutdown( void )
{
	DeallocateDownsamplingTextures();

	if(s_pxQuadVB)
	{
		s_pxQuadVB->Release();
		s_pxQuadVB = NULL;
	}
	if(s_pxSunVB)
	{
		s_pxSunVB->Release();
		s_pxSunVB = NULL;
	}
	if(s_pxSunGlareTarget)
	{
		Asura_Direct3D_RenderTargetManager::DestroyRenderTarget(s_pxSunGlareTarget);
		s_pxSunGlareTarget = NULL;
	}
	/*Asura_Direct3D_RenderTargetManager::DestroyRenderTarget(s_pxSunGlareIntermediateTarget);
	s_pxSunGlareIntermediateTarget = NULL;*/
	Asura_Direct3D_RenderTargetManager::RemoveResetCallbacks(DeallocateDownsamplingTextures, AllocateDownsamplingTextures);
}

void Asura_Weather_System::Platform_Reset( void )
{
}
