#ifdef ASURA_USE_PRAGMA_ONCE
	#pragma once
#endif

#ifndef __ASURA_PS3_FRAGMENT_SHADER_COPY_CG_H__
#define __ASURA_PS3_FRAGMENT_SHADER_COPY_CG_H__

#include <sdk_version.h>
#include "Interface/Asura_CommandConsole.h"
#include "Rendering/Asura_PS3_ScratchRenderMemory.h"

class Asura_PS3_FragmentShaderCopy
{
public:
	Asura_PS3_FragmentShaderCopy() {}

	void Initialise( Asura_PS3_FragmentShaderBase* pxBaseShader, void* uStartProgram, u_int uProgramSize )
	{
		m_pxBaseShader				= pxBaseShader;
#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

		// reserve some space in local memory and upload the fragment program ucode
		m_pvFragmentAddr			= Asura_PS3_ScratchRenderMemory::AllocMainVideoMemory( uRealCodeSize );

		if( m_pvFragmentAddr )
		{
			Asura_Mem::Copy( m_pvFragmentAddr, pxCode, uRealCodeSizeT ); 
			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( u_int u = 0; u < 16; ++u )
		{
			m_bTexturesSetInThisFrame[ u ] = false;
		}
		
		m_bStripped = false;
	}

	~Asura_PS3_FragmentShaderCopy()
	{
	}

	void SetBool( int iElementID, const bool bValue ) const
	{
		Asura_Assert(!m_bStripped,"SetBool() called after StripUnusedBranches(), this will corrupt the shader.");

		const unsigned short* usaOffsets = m_pxBaseShader->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 );
		}
	}

	void SetFloats( int iElementID, const unsigned char ucElements, const float* pfData ) const
	{
		Asura_Assert(!m_bStripped,"SetFloats() called after StripUnusedBranches(), this will corrupt the shader.");

		const unsigned short* usaOffsets = m_pxBaseShader->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 );
		}
	}

	void SetArray( int iElementID, const unsigned char ucRows, const unsigned char ucCols, const float* pfData ) const
	{
		Asura_Assert(!m_bStripped,"SetArray() called after StripUnusedBranches(), this will corrupt the shader.");

		const unsigned short* usaOffsets = m_pxBaseShader->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 );
			}
		}
	}

	void SetInts( int iElementID, const unsigned char ucElements, const int* pfData ) const
	{
		Asura_Assert(!m_bStripped,"SetInts() called after StripUnusedBranches(), this will corrupt the shader.");

		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 );
	}

	void SetTexture( int iTextureID, int iTextureIndex )
	{
		Asura_Assert(!m_bStripped,"SetTexture() called after StripUnusedBranches(), this will corrupt the shader.");

		int iTextureChannel = m_pxBaseShader->m_pxHandles->m_pcTextureUnit[ iTextureID ];
		if( iTextureChannel >= 0 && iTextureChannel < 16 && m_pxBaseShader->m_bTexturesUsedByShader[ iTextureChannel ] )
		{
			if( Asura_PS3_Graphics::UseGCmMacros() )
			{
				Asura_PS3_RenderingStates::SetTextureMacro( iTextureChannel, Asura_PS3_TextureManagement::GetTexture( iTextureIndex ), m_pxBaseShader->m_uiTextureAddress[ iTextureChannel ], m_pxBaseShader->m_uiTextureControl0[ iTextureChannel ], m_pxBaseShader->m_uiTextureControl2[ iTextureChannel ], m_pxBaseShader->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_pxBaseShader->m_eTextureAddressMode[ iTextureChannel ][ 0 ], m_pxBaseShader->m_eTextureAddressMode[ iTextureChannel ][ 1 ], m_pxBaseShader->m_eTextureAddressMode[ iTextureChannel ][ 2 ], m_pxBaseShader->m_bUse_sRGBConversion[ iTextureChannel ] );
				}
				else
				{
					Asura_PS3_RenderingStates::SetTextureAddressMode( iTextureChannel, m_pxBaseShader->m_eTextureAddressMode[ iTextureChannel ][ 0 ], m_pxBaseShader->m_eTextureAddressMode[ iTextureChannel ][ 1 ], m_pxBaseShader->m_eTextureAddressMode[ iTextureChannel ][ 2 ], false );
				}
				Asura_PS3_RenderingStates::SetTextureFilter( iTextureChannel, m_pxBaseShader->m_eTextureMinFilter[ iTextureChannel ], m_pxBaseShader->m_eTextureMagFilter[ iTextureChannel ], m_pxBaseShader->m_eTextureConvolitionFilter[ iTextureChannel ] );
				Asura_PS3_RenderingStates::SetTextureOptimization( iTextureChannel, m_pxBaseShader->m_eTextureOptimization[ iTextureChannel ] );
				Asura_PS3_RenderingStates::SetTextureControl( iTextureChannel, true, m_pxBaseShader->m_eTextureAniso[ iTextureChannel ] );
			}
			m_bTexturesSetInThisFrame[ iTextureChannel ] = true;
		}
	}

	void SetTexture( int iTextureID, Asura_PS3_Texture* pxTexture, bool bNormalizedTextureCoordinates )
	{
		Asura_Assert(!m_bStripped,"SetTexture() called after StripUnusedBranches(), this will corrupt the shader.");
	
		int iTextureChannel = m_pxBaseShader->m_pxHandles->m_pcTextureUnit[ iTextureID ];
		if( iTextureChannel >= 0 && iTextureChannel < 16 && m_pxBaseShader->m_bTexturesUsedByShader[ iTextureChannel ] )
		{
			if( Asura_PS3_Graphics::UseGCmMacros() )
			{
				pxTexture->SetUseNormalizedCoordinates( bNormalizedTextureCoordinates );
				Asura_PS3_RenderingStates::SetTextureMacro( iTextureChannel, pxTexture, m_pxBaseShader->m_uiTextureAddress[ iTextureChannel ], m_pxBaseShader->m_uiTextureControl0[ iTextureChannel ], m_pxBaseShader->m_uiTextureControl2[ iTextureChannel ], m_pxBaseShader->m_uiTextureFilter[ iTextureChannel ] );
			}
			else
			{
				if( pxTexture != NULL && pxTexture->Is8BitTexture() )
				{
					Asura_PS3_RenderingStates::SetTextureAddressMode( iTextureChannel, m_pxBaseShader->m_eTextureAddressMode[ iTextureChannel ][ 0 ], m_pxBaseShader->m_eTextureAddressMode[ iTextureChannel ][ 1 ], m_pxBaseShader->m_eTextureAddressMode[ iTextureChannel ][ 2 ], m_pxBaseShader->m_bUse_sRGBConversion[ iTextureChannel ] );
				}
				else
				{
					Asura_PS3_RenderingStates::SetTextureAddressMode( iTextureChannel, m_pxBaseShader->m_eTextureAddressMode[ iTextureChannel ][ 0 ], m_pxBaseShader->m_eTextureAddressMode[ iTextureChannel ][ 1 ], m_pxBaseShader->m_eTextureAddressMode[ iTextureChannel ][ 2 ], false );
				}
				Asura_PS3_RenderingStates::SetTextureFilter( iTextureChannel, m_pxBaseShader->m_eTextureMinFilter[ iTextureChannel ], m_pxBaseShader->m_eTextureMagFilter[ iTextureChannel ], m_pxBaseShader->m_eTextureConvolitionFilter[ iTextureChannel ] );
				Asura_PS3_RenderingStates::SetTextureOptimization( iTextureChannel, m_pxBaseShader->m_eTextureOptimization[ iTextureChannel ] );
				Asura_PS3_RenderingStates::RequestTextureIndex( iTextureChannel, ASURA_TEXTURE_INVALID );
				pxTexture->SetAsTextureIndex( iTextureChannel, bNormalizedTextureCoordinates );
				Asura_PS3_RenderingStates::SetTextureControl( iTextureChannel, true, m_pxBaseShader->m_eTextureAniso[ iTextureChannel ] );
			}
			m_bTexturesSetInThisFrame[ iTextureChannel ] = true;
		}
	}
	
	int GetTextureIndex( int iTextureID )
	{
		return m_pxBaseShader->m_pxHandles->m_pcTextureUnit[ iTextureID ];
	}
	
	// Remove unnecessary "if"s from the shader
	// Do not change fragment shader constants after calling this.
	//
	//BW - This function works, but the code it produces is nowhere near as efficient as optimising-out a block of code at compile time.
	// In a simple test case: a shader went from 82 to 79 instructions by removing a block of code before compiling, wrapping that block of code
	// in a [branch]if(constant != 0){...code...} increased the shader length to 93 instructions, and calling this function to strip the if after
	// setting the constant to 0 reduced the code length to 91 instructions. i.e. it's faster to just execute the code!
	//
	// So only use this if it's faster than the other available options, where possible have 2 versions of the shader, one with and without the conditional code.
	void StripUnusedBranches ( void )
	{
#if (CELL_SDK_VERSION < 0x250000)	// the function cellGcmCgStripBranchesFromFragmentUCode() wasn't available before this version
		Asura_Assert(0,"StripUnusedBranches: You need SDK 250 or later to use this function");
#else

		u_int		uRealCodeSizeT	= cellCgbGetUCodeSize( &m_xProgram );
		u_int		uRealCodeSize	= uRealCodeSizeT + 400; //+400 is the workaround described in the RSX White Paper Fragment Program Overfetch

		// reserve some space in local memory and upload the fragment program ucode
		// if this pushes it over the memory limits, could do this in an intermediate buffer then copy the result back.
		void*		pxCode = Asura_PS3_ScratchRenderMemory::AllocMainVideoMemory( uRealCodeSize );
		u_int		uStrippedSize;
	
		// make a new copy of the shader with all branches removed
		int iErr = cellGcmCgStripBranchesFromFragmentUCode (
						m_pvFragmentAddr,
						uRealCodeSizeT,
						pxCode,
						&uStrippedSize
					);
		
		if ( iErr == CELL_OK )
		{
			m_pvFragmentAddr = pxCode;

			// setup prog & progconfig to match new shader
			cellCgbRead( m_pvFragmentAddr, uStrippedSize, &m_xProgram );
			cellCgbGetFragmentConfiguration( &m_xProgram, &m_xProgramConfig );
			if( cellGcmAddressToOffset( m_pvFragmentAddr, &m_xProgramConfig.offset ) != CELL_OK )
			{
				Asura_Assert( false, "Fragment shader address error. Probably wrong shader" );
			}
			
			m_bStripped = true;
		}
		else
		{
			Asura_Assert( iErr == CELL_OK, "cellGcmCgStripBranchesFromFragmentUCode failed, using un-optimized shader." );
			ASURA_PLATFORM_DEBUGBREAK();
		}
#endif
	}

	void Bind( void )
	{
		int i;
		for( i = 0; i < 16; ++i )
		{
			if( m_pxBaseShader->m_bTexturesUsedByShader[ i ] )
			{
				if( !m_bTexturesSetInThisFrame[ i ] )
				{
					Asura_PS3_RenderingStates::RequestTextureIndex( i, ASURA_TEXTURE_NONE );
				}
			}
			else
			{
				Asura_PS3_RenderingStates::SetTextureControl( i, false );
			}
		}
		if( m_pvFragmentAddr )
		{
			Asura_PS3_RenderingStates::SetFragmentShader( &m_xProgramConfig, true );
		}
		for( i = 0; i < 16; ++i )
		{
			m_bTexturesSetInThisFrame[ i ] = false;
		}
	}

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

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" );

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

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

	CellCgbProgram							m_xProgram;
	CellCgbFragmentProgramConfiguration		m_xProgramConfig;
	void*									m_pvFragmentAddr;
	Asura_PS3_FragmentShaderBase*			m_pxBaseShader;
	bool									m_bTexturesSetInThisFrame[ 16 ];
	bool									m_bStripped;
}; //class Asura_PS3_GCM_FragmentShader

#endif //#ifndef __ASURA_PS3_FRAGMENT_SHADER_COPY_CG_H__
