#pragma once

#include <D3DX9.h>
#include <hash_map>
#include <string>

#define L4VERTEX_FVF		(D3DFVF_XYZ | D3DFVF_TEX1 | D3DFVF_DIFFUSE | D3DFVF_NORMAL)
#define L4POINTVERTEX_FVF	(D3DFVF_XYZ)
#define L4VERTEX_2D_FVF		(D3DFVF_XYZRHW | D3DFVF_DIFFUSE)
#define L4VERTEX_2D_TEX_FVF	(D3DFVF_XYZRHW | D3DFVF_DIFFUSE | D3DFVF_TEX1)

#define PASS_OPAQUE			0
#define PASS_DECAL			1
#define PASS_ALPHABLEND		2
#define PASS_SPHERE			3
#define PASS_SKY			4
#define PASS_TOTAL_COUNT	5

struct L4VERTEX
{
	float x, y, z;
	float nx, ny, nz;
	DWORD color;
	float u, v;
};

struct L4POINTVERTEX
{
	float x, y, z;
};

struct L4VERTEX_2D
{
	float x, y, z, rhw;
	DWORD color;
};

struct L4VERTEX_2D_TEX
{
	float x, y, z, rhw;
	DWORD color;
	float u, v;
};

struct L4TEXOP
{
	enum WrapType
	{
		REPEAT,
		CLAMP,
		SELECT
	} wrap_u, wrap_v;

	LPDIRECT3DTEXTURE9 texture;

	bool doScroll;
	float scrollUDelta;
	float scrollVDelta;
};

struct L4RAMP
{
	float r0, g0, b0;
	float r1, g1, b1;
};

struct L4DRAWOP
{
	D3DMATERIAL9 material;
	L4TEXOP texture;
	bool alphaTest;
	bool drawAsDecal;
	bool drawAsSky;
};

class d3d_OBJECT
{
public:
	d3d_OBJECT(LPDIRECT3DDEVICE9 device, int vertCount);
	d3d_OBJECT(LPDIRECT3DDEVICE9 device, LPD3DXMESH mesh, DWORD *adjacencyBuffer, int drawOpCount);
	~d3d_OBJECT();

	inline LPDIRECT3DDEVICE9 GetDevice()	{ return mDevice; }
	inline int GetVertCount()				{ return mVertCount; }
	inline int GetDrawOpCount()				{ return mDrawOpCount; }
	inline L4DRAWOP* GetDrawOp(int index)	{ return &(mDrawOps[index]); }

	void Draw(int pass, const D3DXMATRIX *viewTransform, Time targetRenderFrame);

	inline void SetRadius(float radius)						{ mRadius = radius; }
	inline void SetOrigin(L4POINTVERTEX origin)				{ mOrigin = origin; }
	inline void SetImmune(bool immune)						{ mImmune = immune; }
	inline void SetLocalToWorld(D3DXMATRIX localToWorld)	{ mLocalToWorld = localToWorld; }

	inline float GetRadius()			{ return mRadius; }
	inline L4POINTVERTEX GetOrigin()			{ return mOrigin; }
	inline bool GetImmune()				{ return mImmune; }
	inline D3DXMATRIX GetLocalToWorld()	{ return mLocalToWorld; }
	inline LPD3DXMESH GetMesh()			{ return mMesh; }

	inline d3d_OBJECT* GetNext(int pass)					{ return mNext[pass + 1]; }
	inline d3d_OBJECT* GetPrevious( int pass)				{ return mPrev[pass + 1]; }
	inline void SetNext(d3d_OBJECT *next, int pass)			{ mNext[pass + 1] = next; }
	inline void SetPrevious(d3d_OBJECT *previous, int pass)	{ mPrev[pass + 1] = previous; }

	static void ResetState(LPDIRECT3DDEVICE9 device);
	static d3d_OBJECT* LoadObject(LPDIRECT3DDEVICE9 device, char *fileName);
	static L4TEXOP LoadTexture(LPDIRECT3DDEVICE9 device, const char *fileName);
	// Cached textures are bound to the device that created them - the
	// cache MUST be flushed when that device goes away (the single-binary
	// race loop tears the renderer down between missions).
	static void FlushTextureCache();

private:
	static d3d_OBJECT* LoadSpheres(LPDIRECT3DDEVICE9 device, char *fileName);

	LPDIRECT3DDEVICE9 mDevice;
	inline void SetVB(LPDIRECT3DVERTEXBUFFER9 buffer)			{ mVB = buffer; }
	void DrawMesh(int pass, const D3DXMATRIX *viewTransform, Time targetRenderFrame);
	void DrawSpheres(int pass, const D3DXMATRIX *viewTransform);

	long mID;
	
	// vertex based
	int mVertCount;
	LPDIRECT3DVERTEXBUFFER9 mVB;

	// mesh based
	LPD3DXMESH mMesh;

	int mDrawOpCount;
	L4DRAWOP* mDrawOps;

	bool mImmune;
	D3DXMATRIX mLocalToWorld;
	L4POINTVERTEX mOrigin;
	float mRadius;

	d3d_OBJECT *mNext[PASS_TOTAL_COUNT + 1];
	d3d_OBJECT *mPrev[PASS_TOTAL_COUNT + 1];

	void SetTextureScrolling(const L4TEXOP *texture, Time targetRenderFrame);
	void SetTextureAddressing(L4TEXOP::WrapType wrap_u, L4TEXOP::WrapType wrap_v);
	void SetTexture(LPDIRECT3DTEXTURE9 texture);
	// state information for optimizing renderstate changes
	static bool mLastTextureScrollState;
	static L4TEXOP::WrapType mLastWrapU;
	static L4TEXOP::WrapType mLastWrapV;
	static bool mLastTexturingState;
	
	static long mNextID;
	static stdext::hash_map< std::string , L4TEXOP > mTextureCache;
};

extern int gNumBatches;
