#ifdef ASURA_USE_PRAGMA_ONCE
	#pragma once
#endif

#ifndef __ASURA_PS3_FRAGMENT_SHADER_BASE_CG_H__
#define __ASURA_PS3_FRAGMENT_SHADER_BASE_CG_H__

#include "Interface/Asura_CommandConsole.h"
#include "cell/gcm/gcm_method_data.h"

class Asura_PS3_FragmentShaderBase
{
	friend class Asura_PS3_FragmentShaderCopy;
public:
	Asura_PS3_FragmentShaderBase( void* uStartProgram, u_int uProgramSize, Asura_PS3_FP_Handles* pxHandles )
	{
#ifdef ASURA_ASSERT
		int		iRes					= 
#endif // #ifdef ASURA_ASSERT
			cellCgbRead( uStartProgram, uProgramSize, &m_xProgram );
		Asura_Assert( iRes == CELL_CGB_OK, "Error: couldn't read vertex program binary\n" );
		const void*	pxCode			= cellCgbGetUCode( &m_xProgram );
		u_int		uRealCodeSizeT	= cellCgbGetUCodeSize( &m_xProgram );
		u_int		uRealCodeSize	= uRealCodeSizeT + 400; //+400 is the workaround described in the RSX White Paper Fragment Program Overfetch

		m_iProgramSize				= uRealCodeSizeT;
		// reserve some space in local memory and upload the fragment program ucode 64B alignment required (128 to be safe)
		m_pvFragmentAddr			= ASURA_PS3_VIDEOMEMORY_ALLOCMAIN( uRealCodeSize, 128  );

		Asura_Mem::Copy( m_pvFragmentAddr, pxCode, m_iProgramSize ); 
		cellCgbGetFragmentConfiguration( &m_xProgram, &m_xProgramConfig );
		if( cellGcmAddressToOffset( m_pvFragmentAddr, &m_xProgramConfig.offset ) != CELL_OK )
		{
			Asura_Assert( false, "Fragment shader address error. Probably wrong shader" );
		}

		for( int i = 0; i < 16; ++i )
		{
			m_bTexturesUsedByShader[ i ]	= false;
		}

		m_pxHandles = pxHandles;
		if( m_pxHandles )
		{
			m_pxHandles->InitialiseFromShader( this );
		}
	}

	~Asura_PS3_FragmentShaderBase()
	{
		Asura_PS3_VideoMemoryManager::FreeMain( m_pvFragmentAddr );
		m_pvFragmentAddr = NULL;
	}

	void InitUniformElement( int iElementID, const char* szName, unsigned short usArraySize )
	{
		if( usArraySize > 1 )
		{
			//TODO - remove ASURA_MAXIMUM_FRAGMENT_SHADER_OFFSETS & allocate the actual number needed
			// I've removed it from non-array elements but it's a bit trickier for this - BW 06/01/09
		
			m_pxHandles->m_pxOffsets[ iElementID ].m_pOffsets = new unsigned short[ usArraySize * ASURA_MAXIMUM_FRAGMENT_SHADER_OFFSETS ];
			unsigned short* usaOffsets = m_pxHandles->m_pxOffsets[ iElementID ].m_pOffsets;

			for( int j = 0; j < usArraySize; ++j )
			{
				char szBuffer[ 256 ];
				Asura_PrintfToBfr( szBuffer, "%s[%d]", 255, true, szName, j );
				GetOffsets( szBuffer, &usaOffsets[ j * ASURA_MAXIMUM_FRAGMENT_SHADER_OFFSETS ], ASURA_MAXIMUM_FRAGMENT_SHADER_OFFSETS );
			}
		}
		else
		{
			unsigned int uIndex = cellCgbMapLookup( &m_xProgram, szName );

			if( uIndex != CELL_CGB_ERROR_FAILED )
			{
				unsigned int uiOffsetCount = 0;
				cellCgbMapGetFragmentUniformOffsets( &m_xProgram, uIndex, NULL, &uiOffsetCount );

				m_pxHandles->m_pxOffsets[ iElementID ].m_pOffsets = new unsigned short[ uiOffsetCount+1 ];
				unsigned short* usaOffsets = m_pxHandles->m_pxOffsets[ iElementID ].m_pOffsets;

				cellCgbMapGetFragmentUniformOffsets( &m_xProgram, uIndex, usaOffsets, &uiOffsetCount );
				usaOffsets[ uiOffsetCount ] = 0;
			}
			else
			{
				m_pxHandles->m_pxOffsets[ iElementID ].m_pOffsets = new unsigned short[1];
				*m_pxHandles->m_pxOffsets[ iElementID ].m_pOffsets = 0;
			}
		}
	}

	void InitTextureUnit( int iTextureID, const char* szName, CellGcmEnum eAddressModeS, CellGcmEnum eAddressModeT, CellGcmEnum eMinFilter, CellGcmEnum eMagFilter, bool bUse_sRGBConversion = false, CellGcmEnum eAddressModeR = CELL_GCM_TEXTURE_CLAMP_TO_EDGE, CellGcmEnum eTextureAniso = ASURA_PS3_DEFAULT_ANISO, CellGcmEnum eConvolutionFilter = ASURA_PS3_DEFAULT_CONVOLUTION_FILTER, ASURA_PS3_TEXTURE_OPTIMIZATION eTextureOptimization = ASURA_PS3_DEFAULT_TEXTURE_OPTIMIZATION )
	{
		char* cTextureUnit = &m_pxHandles->m_pcTextureUnit[ iTextureID ];
		u_int attribute_tmp = cellCgbMapLookupTextureUnit( &m_xProgram, szName );
		if( attribute_tmp == CELL_CGB_ERROR_FAILED )
		{
			cTextureUnit[0] = -1;
		}
		else
		{
			cTextureUnit[0] = attribute_tmp;
			m_bTexturesUsedByShader[ attribute_tmp ]		= true;
			u_int uSRgb = 0;
			if( bUse_sRGBConversion )
			{
				uSRgb = CELL_GCM_TEXTURE_GAMMA_R | CELL_GCM_TEXTURE_GAMMA_G | CELL_GCM_TEXTURE_GAMMA_B;
			}

			m_uiTextureAddress[ attribute_tmp ]				= CELL_GCM_METHOD_DATA_TEXTURE_ADDRESS( eAddressModeS, eAddressModeT, eAddressModeR, CELL_GCM_TEXTURE_UNSIGNED_REMAP_NORMAL, CELL_GCM_TEXTURE_ZFUNC_LESS, uSRgb, 0 );
			m_uiTextureControl0[ attribute_tmp ]			= CELL_GCM_METHOD_DATA_TEXTURE_CONTROL0( CELL_GCM_TRUE, 0, 12<<8, eTextureAniso );
			m_uiTextureFilter[ attribute_tmp ]				= CELL_GCM_METHOD_DATA_TEXTURE_FILTER( 0, eMinFilter, eMagFilter, eConvolutionFilter );
			switch( eTextureOptimization )
			{
			case ASURA_PS3_TEXTURE_OPTIMIZATION_LOW_QUALITY:
				m_uiTextureControl2[ attribute_tmp ]		= CELL_GCM_METHOD_DATA_TEXTURE_CONTROL2( 0x1F, CELL_GCM_TEXTURE_ISO_LOW, CELL_GCM_TEXTURE_ANISO_LOW );
				break;
			case ASURA_PS3_TEXTURE_OPTIMIZATION_MEDIUM_QUALITY:
				m_uiTextureControl2[ attribute_tmp ]		= CELL_GCM_METHOD_DATA_TEXTURE_CONTROL2( 0x08, CELL_GCM_TEXTURE_ISO_HIGH, CELL_GCM_TEXTURE_ANISO_HIGH );
				break;
			case ASURA_PS3_TEXTURE_OPTIMIZATION_HIGH_QUALITY:
			default:
				m_uiTextureControl2[ attribute_tmp ]		= CELL_GCM_METHOD_DATA_TEXTURE_CONTROL2( 0x00, CELL_GCM_TEXTURE_ISO_HIGH, CELL_GCM_TEXTURE_ANISO_HIGH );
				break;
			}

			m_bUse_sRGBConversion[ attribute_tmp ]			= bUse_sRGBConversion;
			m_eTextureAddressMode[ attribute_tmp ][ 0 ]		= eAddressModeS;
			m_eTextureAddressMode[ attribute_tmp ][ 1 ]		= eAddressModeT;
			m_eTextureAddressMode[ attribute_tmp ][ 2 ]		= eAddressModeR;
			m_eTextureAniso[ attribute_tmp ]				= eTextureAniso;
			m_eTextureMinFilter[ attribute_tmp ]			= eMinFilter;
			m_eTextureMagFilter[ attribute_tmp ]			= eMagFilter;
			m_eTextureConvolitionFilter[ attribute_tmp ]	= eConvolutionFilter;
			m_eTextureOptimization[ attribute_tmp ]			= eTextureOptimization;
		}
	}

	void SetRegisterCount( unsigned char ucRegisterCount )
	{
		m_xProgramConfig.registerCount = ucRegisterCount;
	}

protected:
	inline void GetOffsets( const char* szName, unsigned short* usaOffsets, const unsigned int uiMaxOffsets )
	{
		unsigned int uIndex = cellCgbMapLookup( &m_xProgram, szName );

		if( uIndex != CELL_CGB_ERROR_FAILED )
		{
			unsigned int uiOffsetCount = uiMaxOffsets - 1;

			cellCgbMapGetFragmentUniformOffsets( &m_xProgram, uIndex, usaOffsets, &uiOffsetCount );
			usaOffsets[ uiOffsetCount ] = 0;
			Asura_Assertf( uiOffsetCount != ( uiMaxOffsets - 1 ), ("Warning: Increase Maximum number of offset in fragment shader (%s)", szName) );
		}
		else
		{
			usaOffsets[ 0 ] = 0;
		}
	}

	CellCgbProgram							m_xProgram;
	CellCgbFragmentProgramConfiguration		m_xProgramConfig;
	void*									m_pvFragmentAddr;
	int										m_iProgramSize;

	u_int									m_uiTextureAddress[ ASURA_PS3_MAXIMUM_TEXTURES ]; // address (wraps, wrapt, wrapr, ...)
	u_int									m_uiTextureControl0[ ASURA_PS3_MAXIMUM_TEXTURES ]; //  minlod, maxlod, maxaniso
	u_int									m_uiTextureControl2[ ASURA_PS3_MAXIMUM_TEXTURES ]; //slope,iso,aniso
	u_int									m_uiTextureFilter[ ASURA_PS3_MAXIMUM_TEXTURES ]; // filter ( min, mag, conv )

	bool									m_bTexturesUsedByShader[ 16 ];
	bool									m_bUse_sRGBConversion[ 16 ];
	CellGcmEnum								m_eTextureAddressMode[ 16 ][ 3 ];
	CellGcmEnum								m_eTextureAniso[ 16 ];
	CellGcmEnum								m_eTextureMinFilter[ 16 ];
	CellGcmEnum								m_eTextureMagFilter[ 16 ];
	CellGcmEnum								m_eTextureConvolitionFilter[ 16 ];
	ASURA_PS3_TEXTURE_OPTIMIZATION			m_eTextureOptimization[ 16 ];
	Asura_PS3_FP_Handles*					m_pxHandles;
}; //class Asura_PS3_GCM_FragmentShader

class Asura_PS3_FragmentShader : public Asura_PS3_FragmentShaderBase
{
public:
	Asura_PS3_FragmentShader( unsigned char* uStartProgram, unsigned char* uEndProgram, Asura_PS3_FP_Handles* pxHandles )
	: Asura_PS3_FragmentShaderBase( uStartProgram, uEndProgram - uStartProgram, pxHandles )
	{

	}

	// This function should only be called when first setting up a shader, because the value is patched into the shader itself, and
	// then read by the RSX at a later point. If you need to change the value in a shader on the fly use Asura_PS3_GCM_ShadersLibrary
	// AddFP() to create your shader and GetPermanentFP() whenever you want a new instance of the shader to set new values.
	void SetBool( int iElementID, const bool bValue ) const
	{
		if( m_pxHandles == NULL ) return;
		const unsigned short* usaOffsets = m_pxHandles->m_pxOffsets[ iElementID ].m_pOffsets;
		float fBoolValue = bValue ? 1.0f : 0.0f;
		float faData[ 4 ] = { fBoolValue, fBoolValue, fBoolValue, fBoolValue };

		for( int i = 0; usaOffsets[ i ]; ++i )
		{
			SetFragmentProgramParameter( usaOffsets[ i ], 1, faData );
		}
	}

	// This function should only be called when first setting up a shader, because the value is patched into the shader itself, and
	// then read by the RSX at a later point. If you need to change the value in a shader on the fly use Asura_PS3_GCM_ShadersLibrary
	// AddFP() to create your shader and GetPermanentFP() whenever you want a new instance of the shader to set new values.
	void SetFloats( int iElementID, const unsigned char ucElements, const float* pfData ) const
	{
		if( m_pxHandles == NULL ) return;
		const unsigned short* usaOffsets = m_pxHandles->m_pxOffsets[ iElementID ].m_pOffsets;
		float	faData[ 4 ] = { 1.0f, 1.0f, 1.0f, 1.0f };
		int		i;

		for( i = 0; i < ucElements; ++i )
		{
			faData[ i ] = pfData[ i ];
		}
		for( i = 0; usaOffsets[ i ]; ++i )
		{
			SetFragmentProgramParameter( usaOffsets[ i ], ucElements, faData );
		}
	}

	// This function should only be called when first setting up a shader, because the value is patched into the shader itself, and
	// then read by the RSX at a later point. If you need to change the value in a shader on the fly use Asura_PS3_GCM_ShadersLibrary
	// AddFP() to create your shader and GetPermanentFP() whenever you want a new instance of the shader to set new values.
	void SetArray( int iElementID, const unsigned char ucRows, const unsigned char ucCols, const float* pfData ) const
	{
		if( m_pxHandles == NULL ) return;
		const unsigned short* usaOffsets = m_pxHandles->m_pxOffsets[ iElementID ].m_pOffsets;
		int i, j;
		for( i = 0; i < ucRows; ++i )
		{
			float	faData[4]	= { 1.0f, 1.0f, 1.0f, 1.0f };
			int		k			= i * ucCols;

			for( j = 0; j < ucCols; ++j )
			{
				faData[ j ] = pfData[ k + j ];
			}
			for( j = i * ASURA_MAXIMUM_FRAGMENT_SHADER_OFFSETS; usaOffsets[ j ]; ++j )
			{
				SetFragmentProgramParameter( usaOffsets[ j ], ucCols, faData );
			}
		}
	}

	// This function should only be called when first setting up a shader, because the value is patched into the shader itself, and
	// then read by the RSX at a later point. If you need to change the value in a shader on the fly use Asura_PS3_GCM_ShadersLibrary
	// AddFP() to create your shader and GetPermanentFP() whenever you want a new instance of the shader to set new values.
	void SetInts( int iElementID, const unsigned char ucElements, const int* pfData ) const
	{
		if( m_pxHandles == NULL ) return;
		float	faData[ 4 ]	= { 1.0f, 1.0f, 1.0f, 1.0f };

		for( int i = 0; i < ucElements; ++i )
		{
			faData[ i ] = pfData[ i ];
		}
		SetFloats( iElementID, ucElements, faData );
	}

	// This function should only be called when first setting up a shader, because the value is patched into the shader itself, and
	// then read by the RSX at a later point. If you need to change the value in a shader on the fly use Asura_PS3_GCM_ShadersLibrary
	// AddFP() to create your shader and GetPermanentFP() whenever you want a new instance of the shader to set new values.
	void SetTexture( int iTextureID, int iTextureIndex )
	{
		if( m_pxHandles == NULL ) return;
		int iTextureChannel = m_pxHandles->m_pcTextureUnit[ iTextureID ];
		if( iTextureChannel >= 0 && iTextureChannel < 16 && m_bTexturesUsedByShader[ iTextureChannel ] )
		{
			if( Asura_PS3_Graphics::UseGCmMacros() )
			{
				Asura_PS3_RenderingStates::SetTextureMacro( iTextureChannel, Asura_PS3_TextureManagement::GetTexture( iTextureIndex ), m_uiTextureAddress[ iTextureChannel ], m_uiTextureControl0[ iTextureChannel ], m_uiTextureControl2[ iTextureChannel ], m_uiTextureFilter[ iTextureChannel ] );
			}
			else
			{
				Asura_PS3_Texture* pxTex = Asura_PS3_RenderingStates::RequestTextureIndex( iTextureChannel, iTextureIndex );
				if( pxTex != NULL && pxTex->Is8BitTexture() )
				{
					Asura_PS3_RenderingStates::SetTextureAddressMode( iTextureChannel, m_eTextureAddressMode[ iTextureChannel ][ 0 ], m_eTextureAddressMode[ iTextureChannel ][ 1 ], m_eTextureAddressMode[ iTextureChannel ][ 2 ], m_bUse_sRGBConversion[ iTextureChannel ] );
				}
				else
				{
					Asura_PS3_RenderingStates::SetTextureAddressMode( iTextureChannel, m_eTextureAddressMode[ iTextureChannel ][ 0 ], m_eTextureAddressMode[ iTextureChannel ][ 1 ], m_eTextureAddressMode[ iTextureChannel ][ 2 ], false );
				}
				Asura_PS3_RenderingStates::SetTextureFilter( iTextureChannel, m_eTextureMinFilter[ iTextureChannel ], m_eTextureMagFilter[ iTextureChannel ], m_eTextureConvolitionFilter[ iTextureChannel ] );
				Asura_PS3_RenderingStates::SetTextureOptimization( iTextureChannel, m_eTextureOptimization[ iTextureChannel ] );
				Asura_PS3_RenderingStates::SetTextureControl( iTextureChannel, true, m_eTextureAniso[ iTextureChannel ] );
			}
			m_bTexturesSetInThisFrame[ iTextureChannel ] = true;
		}
	}

	// This function should only be called when first setting up a shader, because the value is patched into the shader itself, and
	// then read by the RSX at a later point. If you need to change the value in a shader on the fly use Asura_PS3_GCM_ShadersLibrary
	// AddFP() to create your shader and GetPermanentFP() whenever you want a new instance of the shader to set new values.
	void SetTexture( int iTextureID, Asura_PS3_Texture* pxTexture, bool bNormalizedTextureCoordinates )
	{
		if( m_pxHandles == NULL ) return;
		int iTextureChannel = m_pxHandles->m_pcTextureUnit[ iTextureID ];
		if( iTextureChannel >= 0 && iTextureChannel < 16 && m_bTexturesUsedByShader[ iTextureChannel ] )
		{
			if( Asura_PS3_Graphics::UseGCmMacros() )
			{
				pxTexture->SetUseNormalizedCoordinates( bNormalizedTextureCoordinates );
				Asura_PS3_RenderingStates::SetTextureMacro( iTextureChannel, pxTexture, m_uiTextureAddress[ iTextureChannel ], m_uiTextureControl0[ iTextureChannel ], m_uiTextureControl2[ iTextureChannel ], m_uiTextureFilter[ iTextureChannel ] );
			}
			else
			{
				if( pxTexture != NULL && pxTexture->Is8BitTexture() )
				{
					Asura_PS3_RenderingStates::SetTextureAddressMode( iTextureChannel, m_eTextureAddressMode[ iTextureChannel ][ 0 ], m_eTextureAddressMode[ iTextureChannel ][ 1 ], m_eTextureAddressMode[ iTextureChannel ][ 2 ], m_bUse_sRGBConversion[ iTextureChannel ] );
				}
				else
				{
					Asura_PS3_RenderingStates::SetTextureAddressMode( iTextureChannel, m_eTextureAddressMode[ iTextureChannel ][ 0 ], m_eTextureAddressMode[ iTextureChannel ][ 1 ], m_eTextureAddressMode[ iTextureChannel ][ 2 ], false );
				}
				Asura_PS3_RenderingStates::SetTextureFilter( iTextureChannel, m_eTextureMinFilter[ iTextureChannel ], m_eTextureMagFilter[ iTextureChannel ], m_eTextureConvolitionFilter[ iTextureChannel ] );
				Asura_PS3_RenderingStates::SetTextureOptimization( iTextureChannel, m_eTextureOptimization[ iTextureChannel ] );
				Asura_PS3_RenderingStates::RequestTextureIndex( iTextureChannel, ASURA_TEXTURE_INVALID );
				pxTexture->SetAsTextureIndex( iTextureChannel, bNormalizedTextureCoordinates );
				Asura_PS3_RenderingStates::SetTextureControl( iTextureChannel, true, m_eTextureAniso[ iTextureChannel ] );
			}
			m_bTexturesSetInThisFrame[ iTextureChannel ] = true;
		}
	}

	void Bind( void )
	{
		int i;
		for( i = 0; i < 16; ++i )
		{
			if( m_bTexturesUsedByShader[ i ] )
			{
				if( !m_bTexturesSetInThisFrame[ i ] )
				{
					Asura_PS3_RenderingStates::RequestTextureIndex( i, ASURA_TEXTURE_NONE );
				}
			}
			else
			{
				Asura_PS3_RenderingStates::SetTextureControl( i, false );
			}
		}

		Asura_PS3_RenderingStates::SetFragmentShader( &m_xProgramConfig, true );

		for( i = 0; i < 16; ++i )
		{
			m_bTexturesSetInThisFrame[ i ] = false;
		}
	}

protected:

	inline void SetFragmentProgramParameter( const u_int uDstByteOffset, const u_int uSizeInFloats, const float* pfSrcAddr ) const
	{
        Asura_Assert( !(uDstByteOffset & 3), "Offsets must be word-aligned" );

  		float* pfDst = static_cast<float*>(m_pvFragmentAddr) + (uDstByteOffset >> 2);
  		const float* pfSrc= pfSrcAddr;

  		for( u_int j = 0; j < uSizeInFloats; ++j )
  		{
  			*pfDst++ = cellGcmSwap16Float32( *pfSrc++ );
  		}
	}

	bool m_bTexturesSetInThisFrame[ 16 ];
};

#endif //#ifndef __ASURA_PS3_FRAGMENT_SHADER_BASE_CG_H__
