//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose: D3DX command implementation.
//
// $NoKeywords: $
//
//=============================================================================//

#include "shadercompile.h"

#include "d3dxfxc.h"
#include "cmdsink.h"

// Required to compile using D3DX* routines in the same process
#include <d3dx9shader.h>
#include "dx_proxy/dx_proxy.h"

#include <tier0/icommandline.h>
#include <tier1/strtools.h>

#define D3DXSHADER_MICROCODE_BACKEND_OLD_DEPRECATED ( 1 << 25 )

namespace InterceptFxc
{

	// The command that is intercepted by this namespace routines
	static const char *s_pszCommand = "fxc.exe ";
	static size_t s_uCommandLen = strlen( s_pszCommand );

	namespace Private
	{
		//
		// Response implementation
		//
		class CResponse : public CmdSink::IResponse
		{
		public:
			explicit CResponse( LPD3DXBUFFER pShader, LPD3DXBUFFER pListing, HRESULT hr );
			~CResponse( void );

		public:
			virtual bool Succeeded( void ) { return m_pShader && (m_hr == D3D_OK); }
			virtual size_t GetResultBufferLen( void ) { return ( Succeeded() ? m_pShader->GetBufferSize() : 0 ); }
			virtual const void * GetResultBuffer( void ) { return ( Succeeded() ? m_pShader->GetBufferPointer() : NULL ); }
			virtual const char * GetListing( void ) { return (const char *) ( m_pListing ? m_pListing->GetBufferPointer() : NULL ); }

		protected:
			LPD3DXBUFFER m_pShader;
			LPD3DXBUFFER m_pListing;
			HRESULT m_hr;
		};

		CResponse::CResponse( LPD3DXBUFFER pShader, LPD3DXBUFFER pListing, HRESULT hr ) :
			m_pShader(pShader),
			m_pListing(pListing),
			m_hr(hr)
		{
			NULL;
		}

		CResponse::~CResponse( void )
		{
			if ( m_pShader )
				m_pShader->Release();

			if ( m_pListing )
				m_pListing->Release();
		}

		//
		// Perform a fast shader file compilation.
		//		TODO: avoid writing "shader.o" and "output.txt" files to avoid extra filesystem access.
		//
		// @param pszFilename		the filename to compile (e.g. "debugdrawenvmapmask_vs20.fxc")
		// @param pMacros			null-terminated array of macro-defines
		// @param pszModel			shader model for compilation
		//
		void FastShaderCompile( const char *pszFilename, const D3DXMACRO *pMacros, const char *pszModel, CmdSink::IResponse **ppResponse )
		{
			LPD3DXBUFFER pShader = NULL; // NOTE: Must release the COM interface later
			LPD3DXBUFFER pErrorMessages = NULL; // NOTE: Must release COM interface later
			
			// DxProxyModule
			static DxProxyModule s_dxModule;

			// X360TEMP: This needs to be moved to an external semantic (or fixed)
			bool bIsX360 = false;
			for ( int i=0; ;i++ )
			{
				if ( !pMacros[i].Name )
				{
					break;
				}
				if ( V_stristr( pMacros[i].Name, "_X360" ) && atoi( pMacros[i].Definition ) )
				{
					bIsX360 = true;
					break;
				}
			}
			
			HRESULT hr = s_dxModule.D3DXCompileShaderFromFile( pszFilename, pMacros, NULL /* LPD3DXINCLUDE */,
				"main",	pszModel, 0, &pShader, &pErrorMessages,
				NULL /* LPD3DXCONSTANTTABLE *ppConstantTable */ );

			if ( ppResponse )
			{
				*ppResponse = new CResponse( pShader, pErrorMessages, hr );
			}
			else
			{
				if ( pShader )
				{
					pShader->Release();
				}

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

	}; // namespace Private

	//
	// Completely mimic the behaviour of "fxc.exe" in the specific cases related
	// to shader compilations.
	//
	// @param pCommand       the command in form
	//		"fxc.exe /DSHADERCOMBO=1 /DTOTALSHADERCOMBOS=4 /DCENTROIDMASK=0 /DNUMDYNAMICCOMBOS=4 /DFLAGS=0x0 /DNUM_BONES=1 /Dmain=main /Emain /Tvs_2_0 /DSHADER_MODEL_VS_2_0=1 /D_X360=1 /nologo /Foshader.o debugdrawenvmapmask_vs20.fxc>output.txt 2>&1"
	//
	void ExecuteCommand( const char *pCommand, CmdSink::IResponse **ppResponse )
	{
		// Expect that the command passed is exactly "fxc.exe"
		Assert( !strncmp( pCommand, s_pszCommand, s_uCommandLen ) );
		pCommand += s_uCommandLen;

		// A duplicate portion of memory for modifications
		void *bufEditableCommand = alloca( strlen( pCommand ) + 1 );
		char *pEditableCommand = strcpy( (char *) bufEditableCommand, pCommand );

		// Macros to be defined for D3DX
		CUtlVector<D3DXMACRO> macros;

		// Shader model (determined when parsing "/D" flags)
		const char *pszShaderModel = NULL;

		// Iterate over the command line and find all "/D...=..." settings
		for ( char *pszFlag = pEditableCommand;
			( pszFlag = strstr( pszFlag, "/D" ) ) != NULL;
			/* advance inside */ )
		{
			// Make sure this is a command-line flag (basic check for preceding space)
			if ( pszFlag > pEditableCommand &&
				pszFlag[-1] &&
				' ' != pszFlag[-1] )
			{
				++ pszFlag;
				continue;
			}

			// Name is immediately after "/D"
			char *pszFlagName = pszFlag + 2; // 2 = length of "/D"
			// Value will be determined later
			char *pszValue = "";

			if ( char *pchEq = strchr( pszFlag, '=' ) )
			{
				// Value is after '=' sign
				*pchEq = 0;
				pszValue = pchEq + 1;
				pszFlag = pszValue;
			}

			if ( char *pchSpace = strchr( pszFlag, ' ' ) )
			{
				// Space is designating the end of the flag
				*pchSpace = 0;
				pszFlag = pchSpace + 1;
			}
			else
			{
				// Reached end of command line
				pszFlag = "";
			}

			// Shader model extraction
			if ( !strncmp(pszFlagName, "SHADER_MODEL_", 13) )
			{
				pszShaderModel = pszFlagName + 13;
			}

			// Add the macro definition to the macros array
			int iMacroIdx = macros.AddToTail();
			D3DXMACRO &m = macros[iMacroIdx];

			// Fill the macro data
			m.Name = pszFlagName;
			m.Definition = pszValue;
		}

		// Add a NULL-terminator
		{
			D3DXMACRO nullTerminatorMacro = { NULL, NULL };
			macros.AddToTail( nullTerminatorMacro );
		}

		// Convert shader model to lowercase
		char chShaderModel[20] = {0};
		if(pszShaderModel)
		{
			Q_strncpy( chShaderModel, pszShaderModel, sizeof(chShaderModel) - 1 );
		}
		Q_strlower( chShaderModel );

		// Determine the file name (at the end of the command line before redirection)
		char const *pszFilename = "";
		if ( const char *pchCmdRedirect = strstr( pCommand, ">output.txt " ) )
		{
			size_t uCmdEndOffset = ( pchCmdRedirect - pCommand );

			pEditableCommand[uCmdEndOffset] = 0;
			pszFilename = &pEditableCommand[uCmdEndOffset];

			while ( pszFilename > pEditableCommand &&
				pszFilename[-1] &&
				' ' != pszFilename[-1] )
			{
				-- pszFilename;
			}
		}

		// Compile the stuff
		Private::FastShaderCompile( pszFilename, macros.Base(), chShaderModel, ppResponse );
	}

	bool TryExecuteCommand( const char *pCommand, CmdSink::IResponse **ppResponse )
	{
		{
			static bool s_bNoIntercept = ( CommandLine()->FindParm("-nointercept") != 0 );
			static int s_dummy = ( Msg( s_bNoIntercept ?
				"[shadercompile] Using old slow technique - runs 'fxc.exe'.\n" :
				"[shadercompile] Using new faster Vitaliy's implementation.\n" ), 1 );
			if ( !s_bNoIntercept && !strncmp(pCommand, InterceptFxc::s_pszCommand, InterceptFxc::s_uCommandLen) )
			{
				// Trap "fxc.exe" so that we did not spawn extra process every time
				InterceptFxc::ExecuteCommand( pCommand, ppResponse );
				return true;
			}
		}

		return false;
	}

}; // namespace InterceptFxc