#include "ShaderEffect.h"
#include "ShaderManager.h"
#include "DeviceStateManager.h"

#include <sys/stat.h> //fstat stuff
#include <direct.h> //_chdir

static const int g_SHFXRBFV = 10;

ShaderEffect::ShaderEffect(ShaderManager* const manager, const XString& fn) : ShaderProgram(manager, SH_FX, fn)
{
	m_Shader = NULL;

	//OpenBin();

	Open(m_Filename);

	m_bInvalidated = false;
}

void ShaderEffect::Open(const XString& fn)
{
	//CClock asdf;
	//asdf.StartCapture(0);

	m_Filename = fn;

	//ShaderProgram::Open(fn);
	OpenBin();
	
	//asdf.EndCapture(0);
	/*asdf.GetCaptureTime(0);
	double x = asdf.GetCaptureTime(0) * 1000.0 / (double)asdf.GetTicksPerSec();
	wlog("ShaderEffectOpen: %fms", (float)x);*/
}

void ShaderEffect::DoRestore(void)
{
	if (m_Shader)
		m_Shader->OnResetDevice();
}

void ShaderEffect::DoInvalidate(void)
{
	if (m_Shader)
		m_Shader->OnLostDevice();
}

void ShaderEffect::Compile(bool debug)
{
	ReleaseSH();
	m_Compiled = false;

	if(!m_Manager)
		return;

	if(m_Code.getlen() == 0)
	{
		m_ErrorMsgs = "ERROR: File not found or empty!";
		return;
	}

	//change to directory, to find includes
	_chdir("Shaders");

	LPD3DXBUFFER pErrors = NULL;
	HRESULT hr = D3DXCreateEffect(m_Manager->GetDevice()->GetDevice(),m_Code.getbuffer(),m_Code.getlen(),NULL,NULL ,debug?D3DXSHADER_SKIPOPTIMIZATION|D3DXSHADER_DEBUG:0,NULL,&m_Shader, &pErrors );

	_chdir("..");
    
	if(!FAILED(hr) && m_Shader)
	{
		m_Compiled = true;
        m_ErrorMsgs = "Compilation Successful!";
	}
	else
	{
		if(pErrors)
			m_ErrorMsgs = (char*)pErrors->GetBufferPointer();
		else
			m_ErrorMsgs = "ERROR: Unknown Error!";
	}

	SAFERELEASE(pErrors);
}

void ShaderEffect::OpenBin(void)
{
	FILE* fbin = NULL;
	bool txtfilefound = false;

	fopen_s(&fbin, (m_Filename + XString(".bin")).getbuffer(), "rb");

	//check txt file date
	struct _stat64 txtfilestats;
	if (0 == _stat64(m_Filename.getbuffer(), &txtfilestats))
	{
		txtfilefound = true;
	}

	if (fbin)
	{
		//read header
		int version;
		__time64_t mtime;
		__int64 size;
			
		fread(&version, sizeof(int), 1, fbin);
		fread(&mtime, sizeof(mtime), 1, fbin); //modification time
		fread(&size, sizeof(size), 1, fbin); //file size

		//check version
		if (version == g_SHFXRBFV)
		{
			if (txtfilefound && (txtfilestats.st_mtime != mtime || txtfilestats.st_size != size))
			{
				//has to recompile because date is different
				fclose(fbin);
				fbin = NULL;
			}
			//else: bin file up to date, first load bin file, but then also text file to be able to modify the shader code
		}
	}

	if (fbin)
	{
		int codelength;
		fread(&codelength, sizeof(int), 1, fbin);

		m_Code.SetLength(codelength);
		fread(m_Code.getbuffer(), sizeof(char), codelength, fbin);

		fclose(fbin);
		fbin = NULL;
	}
	else if (txtfilefound) // read txt file
	{		
		CreatePrecompiledCode();

		if (m_Code.getlen() > 0) //codelen == 0 if there were errrors
		{
			//save compiled code
			FILE *fbinout;
			if (0 == fopen_s(&fbinout, (m_Filename + XString(".bin")).getbuffer(), "wb"))
			{
				fwrite(&g_SHFXRBFV, sizeof(int), 1, fbinout);
				fwrite(&txtfilestats.st_mtime, sizeof(txtfilestats.st_mtime), 1, fbinout); //modification time
				fwrite(&txtfilestats.st_size, sizeof(txtfilestats.st_size), 1, fbinout); //file size

				int codelength = m_Code.getlen();
				fwrite(&codelength, sizeof(int), 1, fbinout);

				fwrite(m_Code.getbuffer(), sizeof(char), codelength, fbinout);

				fclose(fbinout);
			}
		}
		else
		{
			ReadFile(m_Filename);
		}
	}
	else
	{
		m_ErrorMsgs = "ERROR: File not found!";
		return;
	}

	//compile the readed code
	Compile(false);

	//try also to get text version to be able to get code and modify it
	if (txtfilefound)
	{
		ReadFile(m_Filename);
	}
}

void ShaderEffect::CreatePrecompiledCode()
{
	// compile effect using fxc
	//fxc /Tfx_2_0 /Fo MyEffect.fxo MyEffect.fx

	XString tmpfn;
	tmpfn.SetLength(40);
	GetTempFileName("Shaders","",0, tmpfn.getbuffer());

	XString cmd = XString("Shaders\\fxc /Tfx_2_0 /Fo ") + tmpfn + XString(" ") + m_Filename;
	
	//execute command
	system(cmd.getbuffer());

	//read file to codebuffer
	ReadFile(tmpfn);

	//delte tempfile
	remove(tmpfn.getbuffer());
}

XString ShaderEffect::GetDisassembly(void)
{
	LPD3DXBUFFER basm;

	HRESULT hr = D3DXDisassembleEffect(m_Shader, false, &basm);
	if(FAILED(hr))
	{
		m_ErrorMsgs = "ERROR: Disassembling Effect!";
		return "";
	}

	return XString((char*)basm->GetBufferPointer());
}

void ShaderEffect::ReleaseSH(void)
{
	SAFERELEASE(m_Shader);
}

unsigned ShaderEffect::GetSamplerIndex(XString sampler)
{
	D3DXHANDLE handle = m_Shader->GetParameterByName(NULL,sampler.getbuffer());
	if(handle)
	{ 
		D3DXPARAMETER_DESC pDesc;
		m_Shader->GetParameterDesc(handle,&pDesc);
		return pDesc.Annotations;
	}
	return (unsigned)-1;
}

void ShaderEffect::SetTechnique(const XString& tech)
{
	if(m_Shader)
	{
		HRESULT hr;
		if(FAILED(hr = m_Shader->SetTechnique(tech.getbuffer())))
		{
			wlog("SetTechnique Failed(%r): Technique \"%s\" not found.", hr, tech.getbuffer());
		}
	}
}