#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
// GROUND-SHADOW list (task #49b): tshd proxies draw between the STATIC opaque
// geometry (terrain) and the DYNAMIC opaque list (mech bodies) -- the classic
// decal order.  The feet then z-pass over the already-drawn shadow, so the
// shadow's big terrain depth-bias can never paint OVER the mech.  (The list is
// drawn with pass id PASS_ALPHABLEND so d3d_OBJECT::DrawMesh's mIsShadow
// state-block branch runs unchanged.)
#define PASS_SHADOW			5
#define PASS_TOTAL_COUNT	6

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;
	// PORT (draw-cost fix): explicit index range for BGF-built meshes.  D3DX
	// DrawSubset needs an attribute TABLE; our double-sided BGF indices make
	// GenerateAdjacency/OptimizeInplace fail silently, so DrawSubset fell back to
	// scanning the whole attribute buffer PER CALL (~44us x ~1400 calls = ~60ms/
	// frame).  The loader knows each batch's exact range -- draw it directly.
	int bgfStartIndex;		// first index of the batch in the mesh IB
	int bgfPrimCount;		// triangle count (0 = not a BGF batch -> DrawSubset)
	// PORT (authentic LODs): this batch's LOD viewing band [lodNear..lodFar) from
	// the BGF's 0x2046 headers.  DrawMesh draws only the ops whose band contains
	// the camera->object distance (the IG board's distance LOD selection).  A
	// zero-init op (lodFar==0) is treated as always-drawn (non-BGF fallback).
	float lodNear;
	float lodFar;
	// DIAG (BT_LOD_LOG): last band-gate visibility (0=unknown 1=visible 2=hidden)
	// so DrawMesh can log per-op FLIP events during movement (which piece blinks).
	int lastBandVisible;
	// PUNCH (dpl_Punchize, PATCH SV_SPECIAL): cutout punch-through -- the texture's
	// black texels were loaded with alpha 0; draw with alpha TEST in the opaque pass
	// (z-write preserved), rejecting hole texels.
	bool punchThrough;
	// VERTEX-ALPHA BLEND (BGF 0x0082/0x008A verts with authored alpha < 1): an
	// effect card (the FLAMEBIG flame fan).  Drawn in the alpha-blend pass,
	// UNLIT, colour = texture x the authored per-vertex gradient, alpha = the
	// per-vertex fade (SRCALPHA/INVSRCALPHA).
	bool vertexAlphaBlend;
	// COCKPIT PUNCH STENCIL-CUT (task #55, i860-firmware-decoded): 0=normal,
	// 1=aperture MASK (drawn stencil-only, paired with the following hull op),
	// 2=HULL (drawn stencil-rejected under the mask = window cutouts).
	int copRole;
	// LAYER DEPTH BIAS (additive-LOD coplanar resolution; see bgfload.h): finer
	// layers get a slightly more negative normalized-depth bias so EXACTLY-
	// coplanar duplicated surfaces resolve to the detail layer instead of
	// venetian-blind z-fighting (the board's submission-order rule, in D3D terms).
	float lodDepthBias;
};

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; }
	// BT (task #20): mark the mech's *_tshd SHADOW-PROXY geometry -- the binary's
	// shadow is this flat silhouette posed by jointshadow/jointtshadow; it must
	// draw TRANSLUCENT dark in the blend pass (the port drew it opaque black).
	inline void SetIsShadow(int v)							{ mIsShadow = v; }
	inline int  GetIsShadow()								{ return mIsShadow; }
	int mIsShadow;
	// DIAG (BT_LOD_LOG): model basename for flip telemetry (set by LoadObjectBGF;
	// MUST be initialized in BOTH ctors -- debug heap 0xCD fill reads as garbage).
	char mDbgName[24];
	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);

	// PORT (turn-hitch fix): bounding-sphere frustum culling.  The 1995 renderer
	// drew EVERYTHING every frame (the IG board clipped in hardware); on D3D9 that
	// is ~2700 draw calls/frame = ~107ms CPU.  The renderer sets the frame's view
	// frustum once (SetCullFrustum); Draw() then skips objects fully outside it.
	static void SetCullFrustum(const D3DXMATRIX *viewProj);	// NULL disables for the frame
	static void SetCameraPosition(float x, float y, float z);	// for LOD distance selection
	static void GetCameraPosition(float *x, float *y, float *z);	// wreck-flame billboard yaw
	D3DXVECTOR3 mCullCenter;	// model-space bounding sphere (computed at load)
	float       mCullRadius;	// <= 0 -> unknown, never culled

	// PORT (draw-cost fix): direct-draw path for BGF meshes (see L4DRAWOP).  The
	// mesh's own VB/IB, cached AddRef'd at load; drawn with DrawIndexedPrimitive
	// per batch range instead of D3DX DrawSubset.  NULL -> DrawSubset fallback.
	LPDIRECT3DVERTEXBUFFER9 mBgfVB;
	LPDIRECT3DINDEXBUFFER9  mBgfIB;
	UINT                    mBgfStride;
	UINT                    mBgfNumVerts;

private:
	static d3d_OBJECT* LoadSpheres(LPDIRECT3DDEVICE9 device, char *fileName);
	// Builds a mesh-backed d3d_OBJECT directly from a VWE .BGF (pod ships .bgf, not .x).
	static d3d_OBJECT* LoadObjectBGF(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;
