#include "dxstdafx.h"
#include "PPEffectsCommon.h"
#include "TargetableTexture.h"
#include "EffectParam.h"

#pragma warning(push)			// Pushing a warning disabler...
#pragma warning(disable:4995)	// to disable deprecated warning...
#include <iostream>				// ...for those old C++ libs...
#include <fstream>				//
#include <string>				//
#include <sstream>				//
using namespace std;			//
#pragma warning(pop)			// Restoring the previous warning states


#pragma once


//------------------------------------------------------------------------------------------------
// Constructor
// Load the effect and setup the basics handles (world matrix, etc)
// Also retrieve the used ouputs numbers
//------------------------------------------------------------------------------------------------
PPEffectsCommon::PPEffectsCommon(LPCWSTR filePath, const UINT nNbAvailableRTs)
{
	// Nullify everything
	m_pEffect = NULL;
	m_hTechnique = NULL;
	m_hRTTextures = NULL;
	m_hSceneTextures = NULL;
	m_hTime = NULL;
	m_bWriteToRT = NULL;
	ZeroMemory(m_pParam, MAX_NUM_PARAM*sizeof(EffectParam*));
	m_nNbAvailableParams = 0;
	
	// Compile the effect using the filePath parameter
	if( SUCCEEDED(CompileEffect(filePath)) )
	{
		// We instanciate the handles and the RT indicator
		m_hRTTextures = new D3DXHANDLE[nNbAvailableRTs];
		ZeroMemory(m_hRTTextures, sizeof(D3DXHANDLE)*nNbAvailableRTs);

		m_hSceneTextures = new D3DXHANDLE[nNbAvailableRTs];
		ZeroMemory(m_hSceneTextures, sizeof(D3DXHANDLE)*nNbAvailableRTs);

		m_bWriteToRT = new bool[nNbAvailableRTs];
		FillMemory(m_bWriteToRT, sizeof(bool)*nNbAvailableRTs, false);
		
		// Creating the basic handles
		SetupCommonHandles(nNbAvailableRTs);

		// Retrieving the RTs numbers the effect writes to
		GetWrittenRTSlots(nNbAvailableRTs);

		// Prepare the handle parameters for the GUI
		SetupParamHandles();
	}
}


//--------------------------------------------------------------------------------------
// Destructor
//--------------------------------------------------------------------------------------
PPEffectsCommon::~PPEffectsCommon()
{
	// deleting the handles and the RT indicator
	if(m_hRTTextures) delete [] m_hRTTextures;
	if(m_hSceneTextures) delete [] m_hSceneTextures;
	if(m_bWriteToRT) delete [] m_bWriteToRT;

	// Release the parameters variables if any
	for(int i=0; i<MAX_NUM_PARAM; ++i)
	{
		if(m_pParam[i])
			delete (m_pParam[i]);
	}

	// Release the effect
	SAFE_RELEASE(m_pEffect);
}


//--------------------------------------------------------------------------------------
// Compile an effect file and store the binary code in m_pEffect
// Return a log file with error compilation messages if some problems are detected
//--------------------------------------------------------------------------------------
HRESULT PPEffectsCommon::CompileEffect(LPCWSTR filePath)
{
	// Flushing the current effect
	SAFE_RELEASE(m_pEffect);

	// Load the effect
	ID3DXBuffer *pBuffer = NULL; //to store the compilation error messages
	if(FAILED(D3DXCreateEffectFromFile(DXUTGetD3DDevice(), filePath, NULL, NULL, D3DXSHADER_PREFER_FLOW_CONTROL, NULL, &m_pEffect, &pBuffer)))
	{
		// Dumping message if there are some
		if(pBuffer != NULL)
		{
			ofstream fileStream("shader_dump.txt");
			fileStream << "Failed compiling shader\n";
			char * compileString = (char*)(pBuffer->GetBufferPointer());
			for(unsigned int i=0; i<pBuffer->GetBufferSize(); ++i)
				fileStream << compileString[i] ;

			fileStream.close();
			pBuffer->Release();
		}
		return E_FAIL;
	}

	return S_OK;
}


//--------------------------------------------------------------------------------------
// Prepare the common handles
//--------------------------------------------------------------------------------------
void PPEffectsCommon::SetupCommonHandles(const int nNbAvailableRTs)
{
	// If the effect has been successfully compiled
	if(m_pEffect)
	{
		// Retrieve the handles for the render targets textures from the shader variables names...
		CHAR paramBaseName[20];
		CHAR paramNumber[3];
		CHAR paramFullName[23];
		strcpy_s(paramBaseName, 20, "g_txTarget");		// For the source textures
		for(int i=0; i<nNbAvailableRTs; ++i)
		{
			_itoa_s(i, paramNumber, 3, 10);				// Retrieve the param number as a char
			strcpy_s(paramFullName, 23, paramBaseName);	// And reform the full parameter name
			strcat_s(paramFullName, 23, paramNumber);	// ...
			
			// We can now get a handle on the parameter
			m_hRTTextures[i] = m_pEffect->GetParameterByName(NULL, (LPCSTR)paramFullName);
		}
		
		strcpy_s(paramBaseName, 20, "g_txScene");		// For the initial scene textures
		for(int i=0; i<nNbAvailableRTs; ++i)
		{
			_itoa_s(i, paramNumber, 3, 10);				// Retrieve the param number as a char
			strcpy_s(paramFullName, 23, paramBaseName);	// And reform the full parameter name
			strcat_s(paramFullName, 23, paramNumber);	// ...

			m_hSceneTextures[i] = m_pEffect->GetParameterByName(NULL, (LPCSTR)paramFullName);
		}

		// Retrieve the handle on the time value
		m_hTime = m_pEffect->GetParameterByName(NULL, "g_fTime");

		// Prepare the handle for the technique
		m_hTechnique = m_pEffect->GetTechniqueByName("PostProcess");
	}
}


//--------------------------------------------------------------------------------------
// Prepare the handles on the parameters
//--------------------------------------------------------------------------------------
void PPEffectsCommon::SetupParamHandles()
{
	D3DXHANDLE hAnnotation;

	// Obtain the handles to the changeable parameters, if any.
	for(int i=0; i<MAX_NUM_PARAM; ++i)
	{
		char szName[32];
		LPCSTR szParamName;

		// Get the parameter size
		StringCchPrintfA(szName, 32, "Parameter%dSize", i);
		hAnnotation = m_pEffect->GetAnnotationByName(m_hTechnique, szName);
		
		// No annotation! I quit!
		if(!hAnnotation) return;

		// If there is an "Parameter%dSize" annotation we search for its associated int
		int nParamSize = 0;
		m_pEffect->GetInt(hAnnotation, &nParamSize);
		if(nParamSize==0) return;

		// Else we instanciate a new parameter
		m_pParam[i] = new EffectParam(nParamSize);
		if(!m_pParam[i]) return;

		// Get the parameter default value(s)
		StringCchPrintfA(szName, 32, "Parameter%dDef", i);
		hAnnotation = m_pEffect->GetAnnotationByName(m_hTechnique, szName);
		if(hAnnotation)
			if( FAILED(m_pEffect->GetFloatArray(hAnnotation, m_pParam[i]->GetParamDefault(), m_pParam[i]->GetParamSize())) ) return;

		// Get the parameter  "Parameter0/1/2/3/..." name
		StringCchPrintfA(szName, 32, "Parameter%d", i);
		hAnnotation = m_pEffect->GetAnnotationByName(m_hTechnique, szName);

		// Now we can retrieve the parameter name in szParamName from the annotation
		if( hAnnotation && SUCCEEDED(m_pEffect->GetString(hAnnotation, &szParamName)) )
		{
			// We found something associated to the annotation, so we retrieve the handle on the parameter
			m_pParam[i]->SetParamHandle(m_pEffect->GetParameterByName(NULL, szParamName));

			// We allocate the parameter name as WCHAR* (because of unicode)
			LPWSTR pParamName = new WCHAR[strlen(szParamName)+1];		// Never forget to add 1 for the string termination character
			ZeroMemory(pParamName, strlen(szParamName)*sizeof(WCHAR));
			
			// we need to convert the name from MultiByte to WideChar since we use Unicode
			MultiByteToWideChar(CP_ACP, 0, szParamName, -1, pParamName, (int)strlen(szParamName)+1);

			// Saving the parameter name in the parameter instance
			m_pParam[i]->SetParamName(pParamName);
		}

		// Get the parameter description
		StringCchPrintfA(szName, 32, "Parameter%dDesc", i);
		hAnnotation = m_pEffect->GetAnnotationByName(m_hTechnique, szName);
		if( hAnnotation && SUCCEEDED(m_pEffect->GetString(hAnnotation, &szParamName)) )
		{
			// We allocate the parameter name as WCHAR* (because of unicode)
			LPWSTR pParamDesc = new WCHAR[strlen(szParamName)+1];		// Never forget to add 1 for the string termination character
			ZeroMemory(pParamDesc, strlen(szParamName)*sizeof(WCHAR));

			// we need to convert the name from MultiByte to WideChar since we use Unicode
			MultiByteToWideChar(CP_ACP, 0, szParamName, -1, pParamDesc, (int)strlen(szParamName)+1);

			// Saving the parameter name in the parameter instance
			m_pParam[i]->SetParamDesc(pParamDesc);
		}
		
		// we successfully added a new parameter to the array
		m_nNbAvailableParams++;
	}
}


//--------------------------------------------------------------------------------------
// Prepare the common handles
//--------------------------------------------------------------------------------------
void PPEffectsCommon::GetWrittenRTSlots(const UINT nRTCount)
{
	if(!m_hTechnique) return;

	// Find out what render targets the technique writes to.
	D3DXTECHNIQUE_DESC techdesc;
	if( SUCCEEDED(m_pEffect->GetTechniqueDesc(m_hTechnique, &techdesc)) )
	{
		for(UINT i=0; i<techdesc.Passes; ++i)
		{
			D3DXPASS_DESC passdesc;
			if( SUCCEEDED(m_pEffect->GetPassDesc(m_pEffect->GetPass(m_hTechnique, i), &passdesc)) )
			{
				D3DXSEMANTIC aSem[MAXD3DDECLLENGTH];
				UINT uCount;
				HRESULT hr = S_OK;
				hr = D3DXGetShaderOutputSemantics(passdesc.pPixelShaderFunction, aSem, &uCount);
				V(hr);
				if( SUCCEEDED(hr) )
				{
					// Find out which render target this technique writes to.
					while(uCount--)
					{
						if(aSem[uCount].Usage==D3DDECLUSAGE_COLOR && aSem[uCount].UsageIndex<nRTCount)
							m_bWriteToRT[uCount] = true;
					}
				}
			}
		}
	}
}


//--------------------------------------------------------------------------------------
// Bind nCount of textures to the target textures set of the effect
//--------------------------------------------------------------------------------------
HRESULT PPEffectsCommon::BindEffectRTTextures(LPDIRECT3DTEXTURE9 * pSrcTexturesSets, const int nCount) const
{
	if(nCount<1 || !pSrcTexturesSets || !m_pEffect) return D3DERR_INVALIDCALL;

	HRESULT hr;	

	// Setting up the RT textures knowing which set is used as source
	for(int i=0; i<nCount; ++i)
		V_RETURN(m_pEffect->SetTexture(m_hRTTextures[i], pSrcTexturesSets[i]));

	return S_OK;
}


//--------------------------------------------------------------------------------------
// Bind nCount of textures to the scene textures set of the effect
//--------------------------------------------------------------------------------------
HRESULT PPEffectsCommon::BindEffectSceneTextures(TargetableTexture * pSceneTexturesSets, const int nCount) const
{
	if(nCount<1 || !pSceneTexturesSets || !m_hSceneTextures || !m_pEffect) return D3DERR_INVALIDCALL;

	HRESULT hr;

	// Setting up the RT textures knowing which set is used as source
	for(int i=0; i<nCount; ++i)
	{
		if(m_hSceneTextures[i])
			V_RETURN(m_pEffect->SetTexture(m_hSceneTextures[i], pSceneTexturesSets[i].GetTexture()));
	}

	return S_OK;
}


//--------------------------------------------------------------------------------------
// Bind time value to the effect
//--------------------------------------------------------------------------------------
HRESULT PPEffectsCommon::BindTime(const float fTime) const
{
	if(!m_pEffect) return D3DERR_INVALIDCALL;
	if(m_hTime)
		m_pEffect->SetFloat(m_hTime, fTime);
	return S_OK;
}


//--------------------------------------------------------------------------------------
// Simply set the effect to use the technique
//--------------------------------------------------------------------------------------
HRESULT PPEffectsCommon::SetEffectTechnique() const
{
	if(!m_pEffect) return D3DERR_INVALIDCALL;
	return m_pEffect->SetTechnique(m_hTechnique);
}


//--------------------------------------------------------------------------------------
// OnResetDevice
//--------------------------------------------------------------------------------------
HRESULT PPEffectsCommon::OnResetDevice(const int nWidth, const int nHeight)
{
	// If no effect available, return!
	if(!m_pEffect) return D3DERR_INVALIDCALL;

	// First, reset the effect
	if(FAILED(m_pEffect->OnResetDevice())) return D3DERR_DEVICENOTRESET;

	// And then, let's find the annotations and process them
	// Converting the "ConvertPixelsToTexels" annoted effect parameters
	D3DXHANDLE hParamToConvert;
	D3DXHANDLE hAnnotation;
	UINT uParamIndex = 0;

	// Parsing every effect parameters
	while( NULL != (hParamToConvert = m_pEffect->GetParameter(NULL, uParamIndex++)) )
	{
		// Searching for the ones answering to the searched annotation
		if( NULL != (hAnnotation = m_pEffect->GetAnnotationByName(hParamToConvert, "ConvertPixelsToTexels")) )
		{
			// Found one :)
			// We can now retrieve a handle on this parameter
			LPCSTR szSource;
			m_pEffect->GetString(hAnnotation, &szSource);
			D3DXHANDLE hConvertSource = m_pEffect->GetParameterByName(NULL, szSource);

			// Is the handle successfully retrieved?
			if(hConvertSource)
			{
				// Yes! We retrieve the kernel size
				D3DXPARAMETER_DESC desc;
				m_pEffect->GetParameterDesc(hConvertSource, &desc);

				// Each element of the kernel is a 2 floats component
				DWORD cKernel = desc.Bytes / (2 * sizeof(float));
				D3DXVECTOR4 *pvKernel = new D3DXVECTOR4[cKernel];
				if( !pvKernel ) return E_OUTOFMEMORY;
				m_pEffect->GetVectorArray(hConvertSource, pvKernel, cKernel);

				// Effectively convert pixel to texels
				for(DWORD i=0; i<cKernel; ++i)
				{
					pvKernel[i].x = pvKernel[i].x / nWidth;
					pvKernel[i].y = pvKernel[i].y / nHeight;
				}
				// Copy back
				m_pEffect->SetVectorArray( hParamToConvert, pvKernel, cKernel );

				delete[] pvKernel;
			}
		}
	}

	// Finished!
	return S_OK;
}


//--------------------------------------------------------------------------------------
// OnLostDevice
//--------------------------------------------------------------------------------------
HRESULT PPEffectsCommon::OnLostDevice()
{
	if(!m_pEffect) return D3DERR_INVALIDCALL;
	return (m_pEffect->OnLostDevice());
}