//
// Asura_Xbox360_Effect_System.cpp
//
// Created by KJFL - 30 September 2005
//
// Copyright (C) 2005 Rebellion, All Rights Reserved.
//

#include "Core/Asura.h"

#include "Rendering/Asura_Xbox360_Effect_System.h"

// Asura Platform-independent
#include "Camera/Asura_Camera.h"
#include "Debugging/Asura_Assert.h"
#include "FileAccess/Asura_FileAccess.h"

// Asura Xbox 360
#include "Rendering/Asura_Xbox360_Graphics.h"
#include "Rendering/Asura_Xbox360_TextureManagement.h"

// Misc
#include <fxl.h>

/******************************************************************************/
/* Classes                                                                    */
/******************************************************************************/

class IncludeHandler : public ID3DXInclude
{
public:

	HRESULT Open( D3DXINCLUDE_TYPE eIncludeType, LPCSTR szFilename, LPCVOID pParentData, LPCVOID* ppData, UINT* puBytes, LPSTR szFullPath, DWORD uMaxFullPathLength );
	HRESULT Close( LPCVOID pData );
};

/******************************************************************************/
/* Asura_Xbox360_Effect_System Static Class Data                              */
/******************************************************************************/

FXLEffectPool* Asura_Xbox360_Effect_System::s_pxEffectPool = NULL;

Asura_Xbox360_Effect_System::Handle Asura_Xbox360_Effect_System::s_xCameraPositionHandle = 0;
Asura_Xbox360_Effect_System::Handle	Asura_Xbox360_Effect_System::s_xViewMatrixHandle = 0;
Asura_Xbox360_Effect_System::Handle	Asura_Xbox360_Effect_System::s_xProjectionMatrixHandle = 0;
Asura_Xbox360_Effect_System::Handle	Asura_Xbox360_Effect_System::s_xViewProjectionMatrixHandle = 0;

bool Asura_Xbox360_Effect_System::s_bHasRetrievedPoolParameters = false;

/******************************************************************************/
/* Asura_Xbox360_Effect Class Functions                                       */
/******************************************************************************/

////////////////////////////////////////////////////////////////////////////////
//
// Asura_Xbox360_Effect::~Asura_Xbox360_Effect()
//
// Destructor
//
// Parameters:		None
// Return value:	None
//
////////////////////////////////////////////////////////////////////////////////

Asura_Xbox360_Effect::~Asura_Xbox360_Effect()
{
	SCOPED_DEVICE_OWNERSHIP();

	if( m_pxEffect )
	{
		m_pxEffect->Release();
	}
}

////////////////////////////////////////////////////////////////////////////////
//
// Asura_Xbox360_Effect::Begin()
//
// Begins an effect technique and pass
//
// Parameters:		uTechniqueIndex - Index of technique to begin. Defaults to 0
// Return value:	None
//
////////////////////////////////////////////////////////////////////////////////

void Asura_Xbox360_Effect::Begin( u_int uTechniqueIndex , u_int uPassIndex )
{
	m_pxEffect->BeginTechniqueFromIndex( uTechniqueIndex, 0 );
	m_pxEffect->BeginPassFromIndex(uPassIndex);
}

////////////////////////////////////////////////////////////////////////////////
//
// Asura_Xbox360_Effect::BeginPass()
//
// Begins a different pass in a technique
//
// Parameters:		uPassIndex - Index of pass to begin
// Return value:	None
//
////////////////////////////////////////////////////////////////////////////////

void Asura_Xbox360_Effect::BeginPass( const u_int uPassIndex )
{
	m_pxEffect->EndPass();
	m_pxEffect->BeginPassFromIndex( uPassIndex );
}

////////////////////////////////////////////////////////////////////////////////
//
// Asura_Xbox360_Effect::Commit()
//
// Commits effect parameters through to the rendering device. Must be called after
// Begin() and before drawing anything
//
// Parameters:		None
// Return value:	None
//
////////////////////////////////////////////////////////////////////////////////

void Asura_Xbox360_Effect::Commit()
{
	m_pxEffect->Commit();
}

////////////////////////////////////////////////////////////////////////////////
//
// Asura_Xbox360_Effect::End()
//
// Ends the current pass and technique
//
// Parameters:		None
// Return value:	None
//
////////////////////////////////////////////////////////////////////////////////

void Asura_Xbox360_Effect::End()
{
	m_pxEffect->EndPass();
	m_pxEffect->EndTechnique();
}

////////////////////////////////////////////////////////////////////////////////
//
// Asura_Xbox360_Effect::GetParameterHandle()
//
// Retrieves a handle for a given parameter
//
// Parameters:		szParameterName - Parameter name
// Return value:	The handle, or 0 in case of failure
//
////////////////////////////////////////////////////////////////////////////////

Asura_Xbox360_Effect_System::Handle Asura_Xbox360_Effect::GetParameterHandle( const char *const szParameterName ) const
{
	Asura_Assert( m_pxEffect, "Effect hasn't been loaded yet." );

	const FXLHANDLE xHandle = m_pxEffect->GetParameterHandle( szParameterName );
	Asura_Assertf( xHandle != 0, ("Invalid effect parameter %s.", szParameterName) );

	return xHandle;
}

////////////////////////////////////////////////////////////////////////////////
//
// Asura_Xbox360_Effect::SetBool()
//
// Sets a bool constant in an effect
//
// Parameters:		xParamHandle - Parameter handle
//					bParamValue - New value
//
// Return value:	None
//
////////////////////////////////////////////////////////////////////////////////

void Asura_Xbox360_Effect::SetBool( const Asura_Xbox360_Effect_System::Handle xParamHandle, const bool bParamValue )
{
	GetInterface()->SetBool( xParamHandle, bParamValue );
}

////////////////////////////////////////////////////////////////////////////////
//
// Asura_Xbox360_Effect::SetFloat()
//
// Sets a float constant in an effect
//
// Parameters:		xParamHandle - Parameter handle
//					fParamValue - New value
//
// Return value:	None
//
////////////////////////////////////////////////////////////////////////////////

void Asura_Xbox360_Effect::SetFloat( const Asura_Xbox360_Effect_System::Handle xParamHandle, const float fParamValue )
{
	GetInterface()->SetFloat( xParamHandle, fParamValue );
}

////////////////////////////////////////////////////////////////////////////////
//
// Asura_Xbox360_Effect::SetInt()
//
// Sets an int constant in an effect
//
// Parameters:		xParamHandle - Parameter handle
//					iParamValue - New value
//
// Return value:	None
//
////////////////////////////////////////////////////////////////////////////////

void Asura_Xbox360_Effect::SetInt( const Asura_Xbox360_Effect_System::Handle xParamHandle, const int iParamValue )
{
	GetInterface()->SetInt( xParamHandle, iParamValue );
}

////////////////////////////////////////////////////////////////////////////////
//
// Asura_Xbox360_Effect::SetMatrix()
//
// Sets a matrix constant in an effect from an FXLMATRIX type
//
// Parameters:		xParamHandle - Parameter handle
//					xMatrix - New value
//
// Return value:	None
//
////////////////////////////////////////////////////////////////////////////////

void Asura_Xbox360_Effect::SetMatrix( const Asura_Xbox360_Effect_System::Handle xParamHandle, FXLMATRIX& xMatrix )
{
	// FXLEffect::SetMatrix() isn't const-correct
	GetInterface()->SetMatrix( xParamHandle, &xMatrix );
}

////////////////////////////////////////////////////////////////////////////////
//
// Asura_Xbox360_Effect::SetMatrix()
//
// Sets a matrix constant in an effect from a float array, which must be the
// same size as the constant
//
// Parameters:		xParamHandle - Parameter handle
//					pfMatrix - New value
//
// Return value:	None
//
////////////////////////////////////////////////////////////////////////////////

void Asura_Xbox360_Effect::SetMatrix( const Asura_Xbox360_Effect_System::Handle xParamHandle, const float* const pfMatrix )
{
	GetInterface()->SetMatrixF( xParamHandle, pfMatrix );
}

////////////////////////////////////////////////////////////////////////////////
//
// Asura_Xbox360_Effect::SetTexture()
//
// Sets a texture in an effect from a Direct3D texture interface
//
// Parameters:		xParamHandle - Parameter handle
//					pxTexture - Pointer to texture to set
//
// Return value:	None
//
////////////////////////////////////////////////////////////////////////////////

void Asura_Xbox360_Effect::SetTexture( const Asura_Xbox360_Effect_System::Handle xParamHandle, Direct3D_BaseTexture* const pxTexture )
{
	GetInterface()->SetSampler( xParamHandle, pxTexture );
}

////////////////////////////////////////////////////////////////////////////////
//
// Asura_Xbox360_Effect::SetTexture()
//
// Sets a texture in an effect from an Asura texture ID
//
// Parameters:		xParamHandle - Parameter handle
//					iTextureID - Texture ID to use. ASURA_TEXTURE_NONE will bind
//								 the default white texture
//
// Return value:	None
//
////////////////////////////////////////////////////////////////////////////////

void Asura_Xbox360_Effect::SetTexture( const Asura_Xbox360_Effect_System::Handle xParamHandle, const int iTextureID )
{
	Asura_Xbox360_Texture* const pxTexture = Asura_Xbox360_TextureManagement::GetTexture( iTextureID );
	GetInterface()->SetSampler( xParamHandle, pxTexture->GetD3DTexture() );
}

////////////////////////////////////////////////////////////////////////////////
//
// Asura_Xbox360_Effect::SetVector()
//
// Sets a float vector constant in an effect from an FXLVECTOR
//
// Parameters:		xParamHandle - Parameter handle
//					xVector - New value
//
// Return value:	None
//
////////////////////////////////////////////////////////////////////////////////

void Asura_Xbox360_Effect::SetVector( const Asura_Xbox360_Effect_System::Handle xParamHandle, const FXLVECTOR xVector)
{
	GetInterface()->SetVector( xParamHandle, xVector );
}

////////////////////////////////////////////////////////////////////////////////
//
// Asura_Xbox360_Effect::SetVector()
//
// Sets a float vector constant in an effect from a float array, which must be
// at least the same size as the constant
//
// Parameters:		xParamHandle - Parameter handle
//					pfVector - New value
//
// Return value:	None
//
////////////////////////////////////////////////////////////////////////////////

void Asura_Xbox360_Effect::SetVector( const Asura_Xbox360_Effect_System::Handle xParamHandle, const float* const pfVector )
{
	GetInterface()->SetVectorF( xParamHandle, pfVector );
}

////////////////////////////////////////////////////////////////////////////////
//
// Asura_Xbox360_Effect::SetVector()
//
// Sets an int vector constant in an effect from an int array, which must be at
// least the same size as the constant
//
// Parameters:		xParamHandle - Parameter handle
//					piVector - New value
//
// Return value:	None
//
////////////////////////////////////////////////////////////////////////////////

void Asura_Xbox360_Effect::SetVector( const Asura_Xbox360_Effect_System::Handle xParamHandle, const int* const piVector )
{
	GetInterface()->SetVectorI( xParamHandle, piVector );
}

/******************************************************************************/
/* Asura_Xbox360_Effect_System Static Class Functions                         */
/******************************************************************************/

////////////////////////////////////////////////////////////////////////////////
//
// Asura_Xbox360_Effect_System::Initialise()
//
// Initialises the Xbox 360 effect system
//
// Parameters:		None
// Return value:	None
//
////////////////////////////////////////////////////////////////////////////////

void Asura_Xbox360_Effect_System::Initialise()
{
	FXLCreateEffectPool( &s_pxEffectPool );
	Asura_Assert( s_pxEffectPool, "Failed to create effect pool." );
}

////////////////////////////////////////////////////////////////////////////////
//
// Asura_Xbox360_Effect_System::Reset()
//
// Resets the Xbox 360 effect system. Called between level loads
//
// Parameters:		None
// Return value:	None
//
////////////////////////////////////////////////////////////////////////////////

void Asura_Xbox360_Effect_System::Reset()
{
}

////////////////////////////////////////////////////////////////////////////////
//
// Asura_Xbox360_Effect_System::Shutdown()
//
// Shuts down the Xbox 360 effect system
//
// Parameters:		None
// Return value:	None
//
////////////////////////////////////////////////////////////////////////////////

void Asura_Xbox360_Effect_System::Shutdown()
{
	s_pxEffectPool->Release();
	s_pxEffectPool = NULL;

	s_xCameraPositionHandle = 0;
	s_xViewMatrixHandle = 0;
	s_xProjectionMatrixHandle = 0;
	s_xViewProjectionMatrixHandle = 0;

	s_bHasRetrievedPoolParameters = false;
}

////////////////////////////////////////////////////////////////////////////////
//
// Asura_Xbox360_Effect_System::CreateEffectFromFile()
//
// Loads an effect from a text .fx file
//
// Parameters:		szFilename - Relative path to effect file
// Return value:	Pointer to the new effect, or NULL if failed
//
////////////////////////////////////////////////////////////////////////////////

Asura_Xbox360_Effect* Asura_Xbox360_Effect_System::CreateEffectFromFile( const char* szFilename )
{
	Asura_Assert( szFilename != 0, "Invalid filename" );
	if( !Asura_FileAccess::FileExists( szFilename ) )
	{
		Asura_Assertf( false, ( "Effect file does not exist: <%s>", szFilename ) );
		return 0;
	}

	char* pcSourceData = 0;

	const Asura_Handle iFileHandle = Asura_FileAccess::Open( szFilename );
	const u_int uSourceDataLength = Asura_FileAccess::GetFileSize( iFileHandle );

	// memory fragmentation issues?
	pcSourceData = new char[ uSourceDataLength ];

	Asura_FileAccess::ReadData( iFileHandle, pcSourceData, uSourceDataLength );
	Asura_FileAccess::Close( iFileHandle );

 	// first compile the effect source 
    LPD3DXBUFFER pxCompiledEffect = 0;
    LPD3DXBUFFER pxErrorMessages = 0;
	IncludeHandler xIncludeHandler;

	HRESULT uResult;

	uResult = FXLCompileEffect
	(
		pcSourceData, // point to ASCII effect buffer
		uSourceDataLength, // length of ASCII effect buffer, in bytes
		NULL, // macro defines
		&xIncludeHandler, // include directives
		D3DXSHADER_FXLPARAMETERS_AS_VARIABLE_NAMES, // compiler flags
		&pxCompiledEffect, // output compiled effect buffer
		&pxErrorMessages // output error buffer
	);

#if defined(ASURA_ASSERT)
	if ( !pxCompiledEffect || FAILED( uResult ) )
	{
		// No compiled effect - create the error string...
		const char* szErrorMessage = pxErrorMessages ? static_cast<char*>(pxErrorMessages->GetBufferPointer()) : "unknown"; 

		Asura_Assertf( false, ( "Failed to compile effect \"%s\" due to error: \n%s.", szFilename, szErrorMessage ) );
	}
#endif	// defined(ASURA_ASSERT)

	Asura_Xbox360_Effect* pxEffect = new Asura_Xbox360_Effect;

	if (pxCompiledEffect)
	{
		uResult = FXLCreateEffect
		(
			Asura_Xbox360_Graphics::GetDevice(),
			pxCompiledEffect->GetBufferPointer(),
			s_pxEffectPool,
			&(pxEffect->m_pxEffect)
		);

		Asura_Assert( SUCCEEDED(uResult), "Error returned from FXLCreateEffect" );

		pxCompiledEffect->Release();
	}

	if (pxErrorMessages)
	{
		pxErrorMessages->Release();
	}

	delete [] pcSourceData;

	return pxEffect;
}

////////////////////////////////////////////////////////////////////////////////
//
// Asura_Xbox360_Effect_System::CreateEffectFromMemory()
//
// Loads an effect from an in memory buffer
//
// Parameters:		pvData - Loaded effect data
// Return value:	Pointer to the effect, or NULL if failed
//
////////////////////////////////////////////////////////////////////////////////

Asura_Xbox360_Effect* Asura_Xbox360_Effect_System::CreateEffectFromMemory( const void* const pvData)
{
	Asura_Assert( pvData, "NULL parameter passed to Asura_Xbox360_Effect_System::CreateEffectFromMemory()." );

	// Create an effect from the compiled data

	Asura_Xbox360_Effect* const pxEffect = new Asura_Xbox360_Effect;
	Asura_Assert( pxEffect, "Failed to allocate memory for effect." );

#if defined ASURA_ASSERT
	const HRESULT xResult =
#endif
		FXLCreateEffect( Asura_Xbox360_Graphics::GetDevice(), pvData, s_pxEffectPool, &pxEffect->m_pxEffect );

	Asura_Assert( SUCCEEDED( xResult ) && pxEffect->m_pxEffect, "Failed to create effect." );

	return pxEffect;
}



////////////////////////////////////////////////////////////////////////////////
//
// Asura_Xbox360_Effect_System::CreateEffectFromCompiledFile()
//
// Loads an effect from a binary .fxo file
//
// Parameters:		szFilename - Relative path to effect file
// Return value:	Pointer to the effect, or NULL if failed
//
////////////////////////////////////////////////////////////////////////////////

Asura_Xbox360_Effect* Asura_Xbox360_Effect_System::CreateEffectFromCompiledFile( const char* const szFilename )
{
	Asura_Assert( szFilename, "NULL parameter passed to Asura_Xbox360_Effect_System::CreateEffectFromCompiledFile()." );

	// Read the compiled file into memory

	const Asura_Handle iFileHandle = Asura_FileAccess::Open( szFilename );

	Asura_Assertf( iFileHandle != ASURA_INVALID_HANDLE, ("Failed to open file %s.", szFilename) );

	const u_int uFileSize = Asura_FileAccess::GetFileSize( iFileHandle );
	char* pcData = new char[ uFileSize ];

	Asura_Assert( pcData, "Failed to allocate memory for compiled effect." );

	#if defined ASURA_ASSERT
		const bool bSuccess =
	#endif
		Asura_FileAccess::ReadData( iFileHandle, pcData, uFileSize );

	Asura_Assert( bSuccess, "Failed to read from compiled effect." );

	Asura_FileAccess::Close( iFileHandle );

	// Create an effect from the compiled data
	Asura_Xbox360_Effect* const  pxEffect = CreateEffectFromMemory(pcData);

	delete[] pcData;

	return pxEffect;
}

////////////////////////////////////////////////////////////////////////////////
//
// Asura_Xbox360_Effect_System::Destroy()
//
// Destroys a previously-loaded effect
//
// Parameters:		pxEffect - Pointer to effect to destroy
// Return value:	None
//
////////////////////////////////////////////////////////////////////////////////

void Asura_Xbox360_Effect_System::Destroy( Asura_Xbox360_Effect* pxEffect )
{
	delete pxEffect;
}

////////////////////////////////////////////////////////////////////////////////
//
// Asura_Xbox360_Effect_System::SetSharedParameters()
//
// Sets common parameters in the shared pool (camera position, matrices etc.)
//
// Parameters:		None
// Return value:	None
//
////////////////////////////////////////////////////////////////////////////////




void Asura_Xbox360_Effect_System::SetSharedParameters()
{
	// Can't retrieve these handles till the parameters actually exist in the pool, which
	// means we can't do it in Initialise()

	if( !s_bHasRetrievedPoolParameters )
	{
		s_xCameraPositionHandle = s_pxEffectPool->GetParameterHandle( "g_xCameraPosition" );
		s_xViewMatrixHandle = s_pxEffectPool->GetParameterHandle( "g_xViewMatrix" );
		s_xProjectionMatrixHandle = s_pxEffectPool->GetParameterHandle( "g_xProjectionMatrix" );
		s_xViewProjectionMatrixHandle = s_pxEffectPool->GetParameterHandle( "g_xViewProjectionMatrix" );

		// Don't assert about these, since they'll only exist if they're actually referenced in a shader
		// loaded so far. Could load a dummy effect which defines them instead?

		s_bHasRetrievedPoolParameters = true;
	}

	// Set camera position

	if( s_xCameraPositionHandle != 0 )
	{
		const Asura_Vector_3* const pxCamPos = Asura_Camera::GetPosition();
		const float afCamPos[4] = { pxCamPos->x, pxCamPos->y, pxCamPos->z, 0.0f };
		s_pxEffectPool->SetVectorF( s_xCameraPositionHandle, afCamPos );
	}

	// Set matrices

	if( s_xViewMatrixHandle != 0 )
	{
		s_pxEffectPool->SetMatrixF( s_xViewMatrixHandle, Asura_Xbox360_Graphics::GetViewMatrix() );
	}

	if( s_xProjectionMatrixHandle != 0 )
	{
		s_pxEffectPool->SetMatrixF( s_xProjectionMatrixHandle, Asura_Xbox360_Graphics::GetProjectionMatrix() );
	}

	if( s_xViewProjectionMatrixHandle != 0 )
	{
		s_pxEffectPool->SetMatrixF( s_xViewProjectionMatrixHandle, Asura_Xbox360_Graphics::GetViewProjectionMatrix() );
	}
}

void Asura_Xbox360_Effect_System::SetPredicationVPosOffset(u_int uOffsetX, u_int uOffsetY)
{
	static Asura_Xbox360_Effect_System::Handle xVPOSOffsetHandle = s_pxEffectPool->GetParameterHandle( "g_xVPOSOffsetVector" );
	const float afOffset[4] = { (float)uOffsetX, (float)uOffsetY, 0.0f, 0.0f };
	s_pxEffectPool->SetVectorF( xVPOSOffsetHandle, afOffset );
}
/******************************************************************************/
/* IncludeHandler Class Functions                                             */
/******************************************************************************/

////////////////////////////////////////////////////////////////////////////////
//
// IncludeHandler::Open()
//
// Called by the effect framework to read the contents of a #included file when
// loading text .fx files
//
// Parameters:		eIncludeType - Local or system include type
//					szFilename - Filename to read, as it is #included
//					pParentData - Pointer to the including dataset
//					ppData - Output pointer to address of created data buffer
//					puBytes - Number of bytes output to ppData
//					szFullPath - Output full path string
//					uMaxFullPathLength - Size of full path string
//
// Return value:	Success code
//
////////////////////////////////////////////////////////////////////////////////

HRESULT IncludeHandler::Open( D3DXINCLUDE_TYPE eIncludeType, LPCSTR szFilename, LPCVOID pParentData, LPCVOID* ppData, UINT* puBytes, LPSTR szFullPath, DWORD uMaxFullPathLength )
{
	Asura_PrintfToBfr( szFullPath, "game:\\Shaders\\%s", uMaxFullPathLength - 1, true, szFilename );

	const HANDLE xFile = CreateFile( szFullPath, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_FLAG_SEQUENTIAL_SCAN, NULL );
	Asura_Assertf( xFile != INVALID_HANDLE_VALUE, ("Unable to open shader include %s.", szFullPath) );

	const DWORD uFileSize = GetFileSize( xFile, NULL );

	char* pcData = new char[ uFileSize ];
	Asura_Assert( pcData, "Unable to allocate memory for shader include handler." );

	DWORD uNumBytesRead;
	if( !ReadFile( xFile, pcData, uFileSize, &uNumBytesRead, NULL ) || uNumBytesRead != uFileSize )
	{
		Asura_Assertf( false, ("Unable to read from file %s.", szFullPath) );
	}

	CloseHandle( xFile );

	*ppData = static_cast< void* >( pcData );
	*puBytes = uFileSize;

	return S_OK;
}

////////////////////////////////////////////////////////////////////////////////
//
// IncludeHandler::Close
//
// Called by the effects framework to close an include file previously opened
//
// Parameters:		pData - Pointer to the data buffer
// Return value:	Success code
//
////////////////////////////////////////////////////////////////////////////////

HRESULT IncludeHandler::Close( LPCVOID pData )
{
	delete[] pData;
	return S_OK;
}
