#pragma once

#include "dllinterface.h"
#include "DeviceStateManager.h"
#include "DXMaterial.h"
#include "Camera.h"
#include "..\utilities\stack.h"
#include "ShaderManager.h"
#include "BSPTree.h"
#include "BufferManager.h"

struct DXFUNCTIONS_API TEXTUREPARAMETER
{
	D3DXHANDLE Handle;
	DXSurface* Surface;

	TEXTUREPARAMETER(void)
	{
		ZeroMemory(this, sizeof(TEXTUREPARAMETER));
	}

	TEXTUREPARAMETER(const D3DXHANDLE handle, DXSurface* const surface)
	{
		Handle = handle;
		Surface = surface;
	}
};

//limitations: no multiple vertexstreams
struct DXFUNCTIONS_API RenderJobDrawCall
{
	//Geometry Parameters
	LPDIRECT3DVERTEXBUFFER9 VB;
	LPDIRECT3DINDEXBUFFER9 IB;
	UINT VertexStride;
	PRIMITIVETYPE PrimitiveType;
	INT BaseVertexIndex;
	INT MinIndex;
	UINT NumVertices;
	UINT StartIndex;
	UINT PrimitiveCount;
	VertexDeclaration* VDecl;
	
	RenderJobDrawCall(void) { ZeroMemory(this, sizeof(RenderJobDrawCall)); }

	void Execute(DeviceStateManager* const device) const
	{
		device->SetIndices(IB);
		device->SetStreamSource(0, VB, 0, VertexStride);
		device->SetVertexDeclaration(VDecl);

		if(IB)
			device->DrawIndexedPrimitive(PrimitiveType, BaseVertexIndex, MinIndex, NumVertices, StartIndex, PrimitiveCount);
		else
			device->DrawPrimitive(PrimitiveType, StartIndex, PrimitiveCount);
	}

	void ExecuteSorted(DeviceStateManager* const device, const LPDIRECT3DINDEXBUFFER9 ib, const unsigned StartIndex) const
	{
		device->SetIndices(ib);
		device->SetStreamSource(0, VB, 0, VertexStride);
		device->SetVertexDeclaration(VDecl);
		device->DrawIndexedPrimitive(PT_TRIANGLELIST, BaseVertexIndex, MinIndex, NumVertices, StartIndex, PrimitiveCount);
	}
};

//Note: Renderstates have to be set by shader effect
class DXFUNCTIONS_API RenderJob
{
	unsigned BaseParam;
	unsigned BaseTextures;

	unsigned NumParam;
	unsigned SizeParam;
	SHADERPARAMETER** pParameters;

	unsigned NumTextures;
	unsigned SizeTextures;
	TEXTUREPARAMETER** pTextures;

public:

	void* UserData;

	const RenderJobDrawCall* DrawCall;
	
	//Material Parameters
	ShaderEffect* Effect;
	D3DXHANDLE Technique;

	BSPTree* pBSPTree; // also indicates that this renderjob contains transparent primitives
	DXMatrix matWorldToMesh; //inv combined world matrix, needed to transform the viewpoint in objects space todo bsp sorting

	RenderJob(void)
	{
		ZeroMemory(this, sizeof(RenderJob));
	}

	~RenderJob(void)
	{
		delete [] pParameters;
		delete [] pTextures;
		//delete pBSPTree; only reference
	}
	
	void CaptureBaseParams(void)
	{
		BaseParam = NumParam;
		BaseTextures = NumTextures;
	}

	void ResetToBaseParams(void)
	{
		SetNumParameters(BaseParam);
		SetNumTextures(BaseTextures);
	}

	void AddParameter(SHADERPARAMETER* const param)
	{
		if(NumParam >= SizeParam)
			SetParamSize(SizeParam + 5);
		pParameters[NumParam++] = param;
	}

	void AddTexture(TEXTUREPARAMETER* const tex)
	{
		if(NumTextures >= SizeTextures)
			SetTexturesSize(SizeTextures + 5);
		pTextures[NumTextures++] = tex;
	}

	unsigned GetNumParameters(void) const
	{
		return NumParam;
	}

	void SetNumParameters(const unsigned count)
	{
		if(count > SizeParam)
			SetParamSize(count);
		NumParam = count;
	}

	unsigned GetNumTextures(void) const
	{
		return NumTextures;
	}

	void SetNumTextures(const unsigned count)
	{
		if(count > SizeTextures)
			SetTexturesSize(count);
		NumTextures = count;
	}

	void SetParamSize(const unsigned size)
	{
		SHADERPARAMETER** pnew = new SHADERPARAMETER*[size];
		if(SizeParam)
			memcpy(pnew, pParameters, sizeof(SHADERPARAMETER*) * SizeParam);
		delete [] pParameters;
		pParameters = pnew;
		SizeParam = size;
	}

	void SetTexturesSize(const unsigned size)
	{
		TEXTUREPARAMETER** pnew = new TEXTUREPARAMETER*[size];
		if(SizeTextures)
			memcpy(pnew, pTextures, sizeof(TEXTUREPARAMETER*) * SizeTextures);
		delete [] pTextures;
		pTextures = pnew;
		SizeTextures = size;
	}

	void Init(void) const
	{
		SHADERPARAMETER* const *pp = pParameters;
		for(unsigned i = 0; i < NumParam; i++)
		{
			const SHADERPARAMETER* const p = *pp++;
			if(p && p->Value)
			{
				Effect->SetValue(p->Handle, p->Value, p->ByteCount);
			}
		}

		TEXTUREPARAMETER* const *tt = pTextures;
		for(unsigned i = 0; i < NumTextures; i++)
		{
			const TEXTUREPARAMETER* const t = *tt++;
			if(t && t->Surface)
			{
				Effect->SetTexture(t->Handle, t->Surface);
			}
		}

		Effect->GetShader()->CommitChanges();
	}
};

inline bool CmpJobGreater(RenderJob* const &a, RenderJob* const &b)
{
	if(a->pBSPTree != NULL && b->pBSPTree == NULL)
		return true; //a is transparent and b not, therefore a > b
	else if(a->pBSPTree == NULL && b->pBSPTree != NULL)
		return false;
	
	//first sort by effect, shadermanager ensures that shaders are only loaded once, so it can be sorted by pointers
	if(a->Effect > b->Effect)
		return true;
	else if(a->Effect < b->Effect)
		return false;

	//by technique
	if(a->Technique > b->Technique)
		return true;
	else if(a->Technique < b->Technique)
		return false;

	//by mesh now, because only VB matters we compare vb directly
	if(a->DrawCall->VB > b->DrawCall->VB)
		return true;
	//else if(a->VB < b->VB)
		//	return false;

	return false;

	/*if(a->DrawCall > b->DrawCall)
	{
		if(a->DrawCall->VB > b->DrawCall->VB)
			return true;
		//else if(a->VB < b->VB)
		//	return false;
	}
	else if((a->DrawCall < b->DrawCall))
	{
		if(a->DrawCall->VB > b->DrawCall->VB)
			return true;
		//else if(a->VB < b->VB)
		//	return false;
	}

	return false;*/

	//no further sorting
}

inline bool CmpJobLesser(RenderJob* const &a, RenderJob* const &b)
{
	if(a->pBSPTree == NULL && b->pBSPTree != NULL)
		return true; //a is not transparent and b however is, therefore a < b
	else if(a->pBSPTree != NULL && b->pBSPTree == NULL)
		return false;

	//first sort by effect, shadermanager ensures that shaders are only loaded once, so it can be sorted by pointers
	if(a->Effect < b->Effect)
		return true;
	else if(a->Effect > b->Effect)
		return false;

	//by technique
	if(a->Technique < b->Technique)
		return true;
	else if(a->Technique > b->Technique)
		return false;

	//by mesh now, because only VB matters we compare vb directly
	if(a->DrawCall->VB < b->DrawCall->VB)
		return true;
	//else if(a->VB > b->VB)
		return false;

	/*if(a->DrawCall < b->DrawCall)
	{
		if(a->DrawCall->VB < b->DrawCall->VB)
			return true;
		//else if(a->VB > b->VB)
		//	return false;
	}
	else if((a->DrawCall > b->DrawCall))
	{
		if(a->DrawCall->VB < b->DrawCall->VB)
			return true;
		//else if(a->VB > b->VB)
		//	return false;
	}

	return false;*/

	//no further sorting
}

struct STRenderListStats
{
	unsigned EffectSwitches;
	unsigned TechniqueSwitches;
	double RenderTime;
	double SortingTime;

	STRenderListStats(void)
	{
		Reset();
	}

	STRenderListStats(const STRenderListStats& s)
	{
		*this = s;
	}

	void Reset(void)
	{
		ZeroMemory(this, sizeof(STRenderListStats));
	}

	STRenderListStats& operator = (const STRenderListStats& s)
	{
		memcpy(this, &s, sizeof(STRenderListStats));
		return *this;
	}

	STRenderListStats& operator += (const STRenderListStats& s)
	{
		EffectSwitches += s.EffectSwitches;
		TechniqueSwitches += s.TechniqueSwitches;
		RenderTime += s.RenderTime;
		SortingTime += s.SortingTime;

		return *this;
	}

	const STRenderListStats& operator + (const STRenderListStats& s) const
	{
		return STRenderListStats(*this) += s;
	}
};

class DXFUNCTIONS_API RenderList
{
protected:
	DynamicStack<RenderJob*> m_List;

	DynamicIB* m_pSortedIB;

	Camera	m_Camera;
	DXColor m_AmbientLight;
	int m_TexFilter;
	int m_TexAniso;
	int m_CullMode;

	//stats
	STRenderListStats m_Stats;

	CClock m_Clock;

	void InitShaderParameterBySemantic(ShaderEffect* const fx);

public:

	RenderList(void);
	~RenderList(void) { SAFERELEASE(m_pSortedIB); }

	//this functions has to be overwritten to set user defined parameters by semantic
	virtual void OnInitShader(ShaderEffect* const fx) {}

	void Clear(void) { m_List.PopAll(); }
	void AddRJ(RenderJob* job) { m_List.Push(job); }
	void Sort(void);
	void Render(DeviceStateManager* const device);

	void SetCullMode(const CULL cullmode) { m_CullMode = cullmode; }
	void SetCamera(const Camera& cam) { m_Camera = cam; }
	void SetAmbientLight(const DXColor& amb) { m_AmbientLight = amb; }
	void SetTexFilter(const TEXTUREFILTERTYPE texf) { m_TexFilter = (int)texf; }
	void SetTexAnisotropy(const int aniso) { m_TexAniso = aniso; }
	
	unsigned GetJobCount(void) const { return m_List.GetElementCount(); }

	const STRenderListStats& GetStats(void) const { return m_Stats; }
	void ResetStats(void) { m_Stats.Reset(); }

	const DynamicStack<RenderJob*>& GetJobs(void) const { return m_List; }
};