//////////////////////////////////////////////////////////////////////////////
//
// Asura_Platform_MaterialResponse_System.cpp
//
// Copyright (c) 2006 Rebellion. All rights reserved.
//
//////////////////////////////////////////////////////////////////////////////

/******************************************************************************/
/* Included Files                                                             */
/******************************************************************************/

#include "Core/Asura.h"

#if defined ASURA_USE_MATERIAL_RESPONSE_SYSTEM

#include "Core/Asura_MaterialResponse_System.h"

// Asura Platform-independent
#include "Core/Asura_HalfFloat.h"
#include "Core/Asura_HashTree.h"

// Asura PC
#include "Rendering/Asura_Direct3D.h"
#include "Rendering/Asura_Direct3D_MaterialResponse.h"

// Tools
#if defined ASURA_TOOLS
	#include "Materials/Tools_MaterialResponse_System.h"
#endif

// Misc
#include <d3dx9.h>

/******************************************************************************/
/* Constants                                                                  */
/******************************************************************************/

// DLH - 14 June 2007 11:40:09 - Turn this on to fill all the graphs with a 0.25 apart from the first and last item
// DLH - 14 June 2007 11:40:09 - This makes checking the graph texture reads in the shader easier.
//#define ASURA_PC_MATERIALRESPONSE_UPDATETEXTURE_FILL_WITHDEBUGLIMIT


/******************************************************************************/
/* Functions                                                                  */
/******************************************************************************/

#if defined( ASURA_TOOLS )
void Asura_PC_MaterialResponse_InvalidTexture()
{
	Asura_Direct3D_MaterialResponse::SetMaterialsTextureValid( false);
}

template< class TPixelType>
static TPixelType Convert( float fScale, float fValue)
{
	float fScaledValue = fScale * fValue;
	TPixelType tScaledValue = static_cast< TPixelType>( fScaledValue);
	
	return( tScaledValue);
}

template< class TPixelType>
void Asura_PC_MaterialResponse_UpdateTexture_Fill( const float fTypeRange, const u_int uOffsetR, const u_int uOffsetG, const u_int uOffsetB, const u_int uOffsetA)
{
	Direct3D_Texture* pxMaterialsTexture = Asura_Direct3D_MaterialResponse::GetMaterialsTexture( false);
	if( !pxMaterialsTexture )
	{
		Asura_Assert( false, "Trying to update material response texture, but it doesn't exist." );
		return;
	}
	
	const float fMaterialTextureScale = Asura_Direct3D_MaterialResponse::GetMaterialsTextureScale();
	const float fMaterialTextureTypeScale = fTypeRange / fMaterialTextureScale;
	
	D3DLOCKED_RECT xLockedRect;
	const HRESULT xResult = pxMaterialsTexture->LockRect( 0, &xLockedRect, NULL, 0 );
	if( FAILED( xResult ) )
	{
		Asura_Assert( false, "Failed to lock material response texture." );
		return;
	}

	// Format is ABGR, but we'll write little-endian floats
	TPixelType* pxDataStart = static_cast< TPixelType* >( xLockedRect.pBits );
	u_int uNumMaterials = Tools_MaterialResponse_System::GetNumMaterials();
			
	for( u_int u = 0; u < uNumMaterials; ++u )
	{

		Tools_MaterialResponse* pxCurrMaterial = Tools_MaterialResponse_System::GetMaterialByIndex_WithoutLoading(u);
		if ( ! pxCurrMaterial->IsLoaded() ) continue;

		uint32 uRow = pxCurrMaterial->GetResponseRowIndex();		


		if (  (uRow < (ASURA_PC_MATERIALS_TEXTURE_HEIGHT-1))  || (pxCurrMaterial->GetHash()==ASURA_DEFAULT_MATERIAL_RESPONSE && uRow == (ASURA_PC_MATERIALS_TEXTURE_HEIGHT-1)))
		{		
			// First we'll have the graphs, one in each colour channel

			// Pitch is specified in bytes for non-DXT formats
			TPixelType* pxData = pxDataStart + (uRow * (xLockedRect.Pitch / sizeof( TPixelType )));

			Asura_RGBA xTexel;

			u_int uElement = 0;

			while( uElement < (4 * ASURA_MATERIAL_RESPONSE_GRAPH_WIDTH) )
			{
				const float fProportion = static_cast< float >( uElement  / 4 ) / static_cast< float >( ASURA_MATERIAL_RESPONSE_GRAPH_WIDTH - 1 );
				const float fTime = Asura_Maths::Lerp( -1.0f, 1.0f, fProportion );

#if !defined( ASURA_PC_MATERIALRESPONSE_UPDATETEXTURE_FILL_WITHDEBUGLIMIT)
				xTexel.m_fR = pxCurrMaterial->m_xDiffuseGraph.GetValueAtTime( fTime ) / 100.0f;
				xTexel.m_fG = pxCurrMaterial->m_xSpecularGraph.GetValueAtTime( fTime ) / 100.0f;
				xTexel.m_fB = pxCurrMaterial->m_xTransparencyGraph.GetValueAtTime( fTime ) / 100.0f;
				xTexel.m_fA = 0.0f;
#else
				if( 0 != uElement && uElement != (4 * (uMaterialsGraphPixelSize-1)))
				{
					xTexel.Set( 0.25f, 0.25f, 0.25f, 0.0f );
				}
				else
				{
					xTexel.Set( 0.0f, 0.0f, 0.0f, 0.0f );
				}
#endif

				pxData[ uElement + uOffsetR ] = Convert< TPixelType>( fMaterialTextureTypeScale, xTexel.m_fR);
				pxData[ uElement + uOffsetG ] = Convert< TPixelType>( fMaterialTextureTypeScale, xTexel.m_fG);
				pxData[ uElement + uOffsetB ] = Convert< TPixelType>( fMaterialTextureTypeScale, xTexel.m_fB);
				pxData[ uElement + uOffsetA ] = Convert< TPixelType>( fMaterialTextureTypeScale, xTexel.m_fA);

				uElement += 4;
			}

			// Write the last graph texel again. If any errors in the floating-point maths take the graph
			// read co-ords slightly out of bounds, we don't want to sample the colourise data

			pxData[ uElement + uOffsetR ] = Convert< TPixelType>( fMaterialTextureTypeScale, xTexel.m_fR);
			pxData[ uElement + uOffsetG ] = Convert< TPixelType>( fMaterialTextureTypeScale, xTexel.m_fG);
			pxData[ uElement + uOffsetB ] = Convert< TPixelType>( fMaterialTextureTypeScale, xTexel.m_fB);
			pxData[ uElement + uOffsetA ] = Convert< TPixelType>( fMaterialTextureTypeScale, xTexel.m_fA);
			uElement += 4;

			// Then the rest of the material's properties

			pxData[ uElement+uOffsetR ] = Convert< TPixelType>( fMaterialTextureTypeScale, pxCurrMaterial->m_xDiffuseColour.m_fR);
			pxData[ uElement+uOffsetG ] = Convert< TPixelType>( fMaterialTextureTypeScale, pxCurrMaterial->m_xDiffuseColour.m_fG);
			pxData[ uElement+uOffsetB ] = Convert< TPixelType>( fMaterialTextureTypeScale, pxCurrMaterial->m_xDiffuseColour.m_fB);
			pxData[ uElement+uOffsetA ] = Convert< TPixelType>( fMaterialTextureTypeScale, pxCurrMaterial->m_fColouriseDiffuse / 100.0f);
			uElement += 4;

			pxData[ uElement+uOffsetR ] = Convert< TPixelType>( fMaterialTextureTypeScale, pxCurrMaterial->m_xSpecularColour.m_fR);
			pxData[ uElement+uOffsetG ] = Convert< TPixelType>( fMaterialTextureTypeScale, pxCurrMaterial->m_xSpecularColour.m_fG);
			pxData[ uElement+uOffsetB ] = Convert< TPixelType>( fMaterialTextureTypeScale, pxCurrMaterial->m_xSpecularColour.m_fB);
			pxData[ uElement+uOffsetA ] = Convert< TPixelType>( fMaterialTextureTypeScale, pxCurrMaterial->m_fColouriseSpecular / 100.0f);
			uElement += 4;

			pxData[ uElement+uOffsetR ] = Convert< TPixelType>( fMaterialTextureTypeScale, pxCurrMaterial->m_fMetalicity / 100.0f);
			pxData[ uElement+uOffsetG ] = Convert< TPixelType>( fMaterialTextureTypeScale, 0.0f); // Don't need graph scales on PC yet
			pxData[ uElement+uOffsetB ] = Convert< TPixelType>( fMaterialTextureTypeScale, 0.0f);
			pxData[ uElement+uOffsetA ] = Convert< TPixelType>( fMaterialTextureTypeScale, 0.0f);
			uElement += 4;

			Asura_Assertf( uElement <= (ASURA_MATERIAL_RESPONSE_TEXTURE_WIDTH * 4), ("Too many data elements in material. %u used but only space for %u.", uElement, ASURA_MATERIAL_RESPONSE_TEXTURE_WIDTH * 4));			
		}
	}


	pxMaterialsTexture->UnlockRect(0);
}

void Asura_PC_MaterialResponse_UpdateTexture()
{
	const D3DFORMAT eMaterialTextureFormat = Asura_Direct3D_MaterialResponse::GetMaterialsTextureFormat();
	
	switch( eMaterialTextureFormat)
	{
		//case D3DFMT_A8R8G8B8: Asura_PC_MaterialResponse_UpdateTexture_Fill<uint8>( 0xFF); break;
		case D3DFMT_A16B16G16R16F: Asura_PC_MaterialResponse_UpdateTexture_Fill<Asura_HalfFloat>( 1.0f, 0, 1, 2, 3); break;
		case D3DFMT_A16B16G16R16: Asura_PC_MaterialResponse_UpdateTexture_Fill<uint16>( 0xFFFF, 0, 1, 2, 3); break;
		case D3DFMT_A8R8G8B8: Asura_PC_MaterialResponse_UpdateTexture_Fill<uint8>( 0xFF, 2, 1, 0, 3); break;
		default: Asura_Assert( false, "Unknown Format for MaterialsTexture"); break;
	}

	Asura_Direct3D_MaterialResponse::SetMaterialsTextureValid( true);
	// Uncomment this to save the file out for inspection - N.B. massively slows the update
	//Direct3D_Texture* pxTexture = Asura_Direct3D_MaterialResponse::GetMaterialsTexture( false );
	//D3DXSaveTextureToFile( "MaterialsTexture.tga", D3DXIFF_TGA, pxTexture, NULL );
}

#endif // ASURA_TOOLS

/******************************************************************************/
/* Asura_MaterialResponse_System Static Class Functions                       */
/******************************************************************************/

void Asura_MaterialResponse_System::Platform_Initialise()
{
	Asura_Direct3D_MaterialResponse::Initialise();
}

void Asura_MaterialResponse_System::Platform_Reset( const bool bFullReset )
{
	Asura_Direct3D_MaterialResponse::Reset();
}

void Asura_MaterialResponse_System::Platform_Shutdown()
{
	Asura_Direct3D_MaterialResponse::Shutdown();
}

void Asura_MaterialResponse_System::Platform_InitialiseMaterial( const Asura_Hash_ID uNameHash,
	const Asura_MaterialResponse& xMaterial )
{
	// TODO: if we keep using effects, we could create a parameter block here. Or if we switch to
	// just using shaders directly, even just a memory block to hold the constants together
}

#endif // defined ASURA_USE_MATERIAL_RESPONSE_SYSTEM
