//===========================================================================//
// File:	dpl2d.cpp                                                          //
// Project: BattleTech port (WinTesla / btl4)                                //
//---------------------------------------------------------------------------//
// libDPL 2D vector display-list layer, re-hosted over Direct3D 9.            //
//                                                                            //
// OPCODE-FAITHFUL rework (task #35 / phase-02): every dpl2d_ recorder in the //
// binary self-identifies via its debug-name string (part_010.c 0x487f34..    //
// 0x4888c0) -- the full API + opcode map is recorded in                      //
// phases/phase-02-dpl2d-reticle.md.  This TU implements that model:          //
//                                                                            //
//   OpenDisplayList(mode) .. CloseDisplayList .. FlushDisplayList            //
//   AddOpenPolypoint(2)/AddClosePolypoint(3)    -- a POINT set               //
//   AddOpenPolyline(4)/AddClosePolyline(5)      -- a CLOSED loop             //
//   AddOpenLines(6)/AddCloseLines(7)            -- an OPEN line strip        //
//   AddPoint(8, x, y)                           -- vertex of the open prim   //
//   AddCircle(9, x, y, r, fill)                                              //
//   AddSetColor(0xF, r, g, b)                                                //
//   AddSetMatrix(0x10, 2x3) / AddConcatMatrix(0x11, 2x3)                     //
//   AddPushState(0x12) / AddPopState(0x13)                                   //
//   AddSetLineWidth(0x15, w)                                                 //
//   AddCallDisplayList(list)                     -- nested glyph reuse       //
//   AddFullScreenClipRegion                      -- clip reset (no-op here)  //
//                                                                            //
// Coordinates are normalised view space (the reticle builder's constants     //
// are fractions like 0.04/0.1/0.35); dpl2d_ExecuteList maps them to the      //
// current viewport and draws XYZRHW primitives with state save/restore.      //
//                                                                            //
// NOTE the OLD trio dpl2d_PushMatrix/MoveTo/PopMatrix was a misreading of    //
// AddOpenPolypoint/AddPoint/AddClosePolypoint ("draw a point"); the aliases  //
// below keep old callers compiling while mapping to the true semantics.      //
//===========================================================================//

#include <bt.hpp>				// Scalar + engine prelude
#pragma hdrstop

#if !defined(BTL4VID_HPP)
#	include <btl4vid.hpp>		// dpl2d_DISPLAY (opaque) + dpl2d_* prototypes
#endif
#if !defined(DPL2D_HPP)
#	include <dpl2d.hpp>
#endif

#include <d3d9.h>
#include <vector>
#include <math.h>

namespace
{
	struct Vertex2D
	{
		float x, y, z, rhw;
		DWORD color;
	};
	const DWORD kVertex2DFVF = (D3DFVF_XYZRHW | D3DFVF_DIFFUSE);

	const int   kCircleSegments = 32;	// tessellation of AddCircle
	const float kPi             = 3.14159265358979323846f;

	inline float ClampUnit(float v)
	{
		return (v < 0.0f) ? 0.0f : (v > 1.0f ? 1.0f : v);
	}

	struct Vec2 { float x, y; };

	// 2x3 affine (row vectors): [x y 1] * | m00 m01 |
	//                                     | m10 m11 |
	//                                     | m20 m21 |
	struct Mat2x3
	{
		float m[6];		// m00 m01 m10 m11 m20 m21
		void Identity() { m[0]=1; m[1]=0; m[2]=0; m[3]=1; m[4]=0; m[5]=0; }
		Vec2 Apply(float x, float y) const
		{
			Vec2 r;
			r.x = x * m[0] + y * m[2] + m[4];
			r.y = x * m[1] + y * m[3] + m[5];
			return r;
		}
		static Mat2x3 Mul(const Mat2x3 &a, const Mat2x3 &b)	// a then b
		{
			Mat2x3 r;
			r.m[0] = a.m[0]*b.m[0] + a.m[1]*b.m[2];
			r.m[1] = a.m[0]*b.m[1] + a.m[1]*b.m[3];
			r.m[2] = a.m[2]*b.m[0] + a.m[3]*b.m[2];
			r.m[3] = a.m[2]*b.m[1] + a.m[3]*b.m[3];
			r.m[4] = a.m[4]*b.m[0] + a.m[5]*b.m[2] + b.m[4];
			r.m[5] = a.m[4]*b.m[1] + a.m[5]*b.m[3] + b.m[5];
			return r;
		}
	};

	//
	// One recorded command (the opcode stream, structurally).
	//
	struct Command
	{
		enum Kind
		{
			kPoints,			// vertex run: a point per vertex
			kLineStrip,			// vertex run: open strip
			kPolyLoop,			// vertex run: closed loop
			kCircleFill,
			kCircleOutline,
			kSetColor,
			kSetWidth,
			kPushState,
			kPopState,
			kSetMatrix,
			kConcatMatrix,
			kCallList
		} kind;
		DWORD				color;		// kSetColor (prebaked ARGB)
		float				a, b, c, d;	// circle x,y,r,fill / width in a
		Mat2x3				mat;		// kSetMatrix/kConcatMatrix
		void				*callee;	// kCallList (Dpl2dList*)
		std::vector<Vec2>	verts;		// vertex runs
	};
}

struct Dpl2dList
{
	bool				recording;
	bool				compiled;
	int					mode;
	std::vector<Command> commands;
	// the currently-open vertex primitive (between AddOpenX .. AddCloseX)
	int					openKind;		// -1 none, else Command::Kind
	std::vector<Vec2>	openVerts;

	Dpl2dList() : recording(false), compiled(false), mode(0), openKind(-1) {}
};

static inline Dpl2dList *AsList(dpl2d_DISPLAY *handle)
{
	return reinterpret_cast<Dpl2dList *>(handle);
}

static void FlushOpenPrimitive(Dpl2dList *self)
{
	if (self->openKind < 0)
		return;
	Command cmd;
	cmd.kind  = (Command::Kind)self->openKind;
	cmd.verts = self->openVerts;
	self->commands.push_back(cmd);
	self->openKind = -1;
	self->openVerts.clear();
}

//===========================================================================//
// Recorder -- the dpl2d_ entry points (prototypes in btl4vid.hpp)
//===========================================================================//

dpl2d_DISPLAY *dpl2d_NewDisplayList()
{
	return reinterpret_cast<dpl2d_DISPLAY *>(new Dpl2dList);
}

void dpl2d_Begin(dpl2d_DISPLAY *list, int mode)		// OpenDisplayList
{
	Dpl2dList *self = AsList(list);
	if (self == 0) return;
	self->recording = true;
	self->compiled  = false;
	self->mode      = mode;
	self->openKind  = -1;
	self->openVerts.clear();
	self->commands.clear();
}

void dpl2d_End(dpl2d_DISPLAY *list)					// CloseDisplayList
{
	Dpl2dList *self = AsList(list);
	if (self == 0) return;
	FlushOpenPrimitive(self);
	self->recording = false;
}

void dpl2d_Compile(dpl2d_DISPLAY *list)				// FlushDisplayList
{
	Dpl2dList *self = AsList(list);
	if (self == 0) return;
	self->compiled = true;
}

void dpl2d_SetColor(dpl2d_DISPLAY *list, Scalar red, Scalar green, Scalar blue)
{
	Dpl2dList *self = AsList(list);
	if (self == 0) return;
	Command cmd;
	cmd.kind  = Command::kSetColor;
	cmd.color = D3DCOLOR_COLORVALUE(
		ClampUnit((float)red), ClampUnit((float)green), ClampUnit((float)blue), 1.0f);
	self->commands.push_back(cmd);
}

void dpl2d_SetLineWidth(dpl2d_DISPLAY *list, Scalar width)
{
	Dpl2dList *self = AsList(list);
	if (self == 0) return;
	Command cmd;
	cmd.kind = Command::kSetWidth;
	cmd.a    = (float)width;
	self->commands.push_back(cmd);
}

void dpl2d_PushState(dpl2d_DISPLAY *list)
{
	Dpl2dList *self = AsList(list);
	if (self == 0) return;
	Command cmd; cmd.kind = Command::kPushState;
	self->commands.push_back(cmd);
}

void dpl2d_PopState(dpl2d_DISPLAY *list)
{
	Dpl2dList *self = AsList(list);
	if (self == 0) return;
	Command cmd; cmd.kind = Command::kPopState;
	self->commands.push_back(cmd);
}

void dpl2d_SetMatrix(dpl2d_DISPLAY *list, const Scalar *six)
{
	Dpl2dList *self = AsList(list);
	if (self == 0 || six == 0) return;
	Command cmd; cmd.kind = Command::kSetMatrix;
	for (int i = 0; i < 6; ++i) cmd.mat.m[i] = (float)six[i];
	self->commands.push_back(cmd);
}

void dpl2d_ConcatMatrix(dpl2d_DISPLAY *list, const Scalar *six)
{
	Dpl2dList *self = AsList(list);
	if (self == 0 || six == 0) return;
	Command cmd; cmd.kind = Command::kConcatMatrix;
	for (int i = 0; i < 6; ++i) cmd.mat.m[i] = (float)six[i];
	self->commands.push_back(cmd);
}

void dpl2d_CallList(dpl2d_DISPLAY *list, dpl2d_DISPLAY *callee)
{
	Dpl2dList *self = AsList(list);
	if (self == 0 || callee == 0) return;
	Command cmd; cmd.kind = Command::kCallList;
	cmd.callee = AsList(callee);
	self->commands.push_back(cmd);
}

void dpl2d_OpenPolypoint(dpl2d_DISPLAY *list)
{
	Dpl2dList *self = AsList(list);
	if (self == 0) return;
	FlushOpenPrimitive(self);
	self->openKind = Command::kPoints;
}

void dpl2d_ClosePolypoint(dpl2d_DISPLAY *list)
{
	Dpl2dList *self = AsList(list);
	if (self == 0) return;
	FlushOpenPrimitive(self);
}

void dpl2d_OpenLines(dpl2d_DISPLAY *list)
{
	Dpl2dList *self = AsList(list);
	if (self == 0) return;
	FlushOpenPrimitive(self);
	self->openKind = Command::kLineStrip;
}

void dpl2d_CloseLines(dpl2d_DISPLAY *list)
{
	Dpl2dList *self = AsList(list);
	if (self == 0) return;
	FlushOpenPrimitive(self);
}

void dpl2d_OpenPolyline(dpl2d_DISPLAY *list)
{
	Dpl2dList *self = AsList(list);
	if (self == 0) return;
	FlushOpenPrimitive(self);
	self->openKind = Command::kPolyLoop;
}

void dpl2d_ClosePolyline(dpl2d_DISPLAY *list)
{
	Dpl2dList *self = AsList(list);
	if (self == 0) return;
	FlushOpenPrimitive(self);
}

void dpl2d_AddPoint(dpl2d_DISPLAY *list, Scalar x, Scalar y)
{
	Dpl2dList *self = AsList(list);
	if (self == 0) return;
	if (self->openKind < 0)
		self->openKind = Command::kPoints;	// tolerate a stray vertex
	Vec2 v = { (float)x, (float)y };
	self->openVerts.push_back(v);
}

void dpl2d_Circle(dpl2d_DISPLAY *list, Scalar x, Scalar y, Scalar radius, int fill)
{
	Dpl2dList *self = AsList(list);
	if (self == 0) return;
	FlushOpenPrimitive(self);
	Command cmd;
	cmd.kind = fill ? Command::kCircleFill : Command::kCircleOutline;
	cmd.a = (float)x; cmd.b = (float)y; cmd.c = (float)radius;
	self->commands.push_back(cmd);
}

void dpl2d_FullScreenClip(dpl2d_DISPLAY * /*list*/)
{
	// clip reset -- the executor always draws to the full viewport
}

//
// LEGACY ALIASES (the earlier AddWeapon transcription used these names for
// what the binary's opcodes reveal to be the point primitive).
//
void dpl2d_PushMatrix(dpl2d_DISPLAY *list)			{ dpl2d_OpenPolypoint(list); }
void dpl2d_MoveTo(dpl2d_DISPLAY *list, Scalar x, Scalar y)	{ dpl2d_AddPoint(list, x, y); }
void dpl2d_PopMatrix(dpl2d_DISPLAY *list)			{ dpl2d_ClosePolypoint(list); }

//===========================================================================//
// Executor -- rasterise a compiled list on the supplied device.
//===========================================================================//

namespace
{
	struct ExecState
	{
		DWORD	color;
		float	width;
		Mat2x3	mat;
	};

	//
	// COORDINATE MODEL (from the reticle's authored constants): centred origin,
	// +x right, +y down, unit = half the viewport HEIGHT on both axes (keeps
	// circles round; the reticle content spans roughly +-0.5).
	//
	inline float MapX(float x, float ox, float vw, float vh)
	{ return ox + vw * 0.5f + x * (vh * 0.5f); }
	inline float MapY(float y, float oy, float vh)
	{ return oy + vh * 0.5f + y * (vh * 0.5f); }

	void DrawVertexRun(IDirect3DDevice9 *device, const Command &cmd,
		const ExecState &st, float ox, float oy, float vw, float vh)
	{
		if (cmd.verts.empty())
			return;
		static std::vector<Vertex2D> verts;
		verts.clear();
		for (size_t i = 0; i < cmd.verts.size(); ++i)
		{
			Vec2 p = st.mat.Apply(cmd.verts[i].x, cmd.verts[i].y);
			Vertex2D v;
			v.x = MapX(p.x, ox, vw, vh); v.y = MapY(p.y, oy, vh);
			v.z = 0.0f; v.rhw = 1.0f; v.color = st.color;
			verts.push_back(v);
		}
		if (cmd.kind == Command::kPoints)
		{
			// a point renders as a small filled quad (D3D9 point size is
			// unreliable on the fixed function path); ~width pixels square.
			float half = (st.width > 1.0f) ? st.width : 1.0f;
			for (size_t i = 0; i < verts.size(); ++i)
			{
				Vertex2D q[4] = { verts[i], verts[i], verts[i], verts[i] };
				q[0].x -= half; q[0].y -= half;
				q[1].x += half; q[1].y -= half;
				q[2].x -= half; q[2].y += half;
				q[3].x += half; q[3].y += half;
				device->DrawPrimitiveUP(D3DPT_TRIANGLESTRIP, 2, q, sizeof(Vertex2D));
			}
		}
		else if (cmd.kind == Command::kLineStrip)
		{
			if (verts.size() >= 2)
				device->DrawPrimitiveUP(D3DPT_LINESTRIP,
					(UINT)verts.size() - 1, &verts[0], sizeof(Vertex2D));
		}
		else // kPolyLoop -- closed
		{
			if (verts.size() >= 2)
			{
				verts.push_back(verts[0]);
				device->DrawPrimitiveUP(D3DPT_LINESTRIP,
					(UINT)verts.size() - 1, &verts[0], sizeof(Vertex2D));
			}
		}
	}

	void ExecuteListInner(Dpl2dList *self, IDirect3DDevice9 *device,
		ExecState &st, std::vector<ExecState> &stack,
		float ox, float oy, float vw, float vh, int depth)
	{
		if (self == 0 || depth > 8)
			return;
		Vertex2D ring[kCircleSegments + 2];

		for (size_t i = 0; i < self->commands.size(); ++i)
		{
			const Command &cmd = self->commands[i];
			switch (cmd.kind)
			{
			case Command::kSetColor:	st.color = cmd.color;	break;
			case Command::kSetWidth:	st.width = cmd.a;		break;
			case Command::kPushState:	stack.push_back(st);	break;
			case Command::kPopState:
				if (!stack.empty()) { st = stack.back(); stack.pop_back(); }
				break;
			case Command::kSetMatrix:	st.mat = cmd.mat;		break;
			case Command::kConcatMatrix:
				st.mat = Mat2x3::Mul(cmd.mat, st.mat);
				break;
			case Command::kCallList:
				// INLINE include: a called list's state changes (matrices,
				// colours) PERSIST into the caller -- the reticle's range
				// caret is a called list holding just a translate that shifts
				// the master's subsequent geometry.
				ExecuteListInner((Dpl2dList *)cmd.callee, device,
					st, stack, ox, oy, vw, vh, depth + 1);
				break;
			case Command::kPoints:
			case Command::kLineStrip:
			case Command::kPolyLoop:
				DrawVertexRun(device, cmd, st, ox, oy, vw, vh);
				break;
			case Command::kCircleFill:
			case Command::kCircleOutline:
			{
				Vec2 p = st.mat.Apply(cmd.a, cmd.b);
				const float cx = MapX(p.x, ox, vw, vh);
				const float cy = MapY(p.y, oy, vh);
				const float r  = cmd.c * (vh * 0.5f);	// height-unit -> stays round
				if (cmd.kind == Command::kCircleFill)
				{
					ring[0].x = cx; ring[0].y = cy; ring[0].z = 0.0f;
					ring[0].rhw = 1.0f; ring[0].color = st.color;
					for (int s = 0; s <= kCircleSegments; ++s)
					{
						float a = (2.0f * kPi * s) / kCircleSegments;
						ring[s + 1].x = cx + cosf(a) * r;
						ring[s + 1].y = cy + sinf(a) * r;
						ring[s + 1].z = 0.0f; ring[s + 1].rhw = 1.0f;
						ring[s + 1].color = st.color;
					}
					device->DrawPrimitiveUP(D3DPT_TRIANGLEFAN,
						kCircleSegments, ring, sizeof(Vertex2D));
				}
				else
				{
					for (int s = 0; s <= kCircleSegments; ++s)
					{
						float a = (2.0f * kPi * s) / kCircleSegments;
						ring[s].x = cx + cosf(a) * r;
						ring[s].y = cy + sinf(a) * r;
						ring[s].z = 0.0f; ring[s].rhw = 1.0f;
						ring[s].color = st.color;
					}
					device->DrawPrimitiveUP(D3DPT_LINESTRIP,
						kCircleSegments, ring, sizeof(Vertex2D));
				}
				break;
			}
			}
		}
	}
}

void dpl2d_ExecuteList(dpl2d_DISPLAY *list, IDirect3DDevice9 *device)
{
	Dpl2dList *self = AsList(list);
	if (self == 0 || device == 0 || self->commands.empty())
		return;

	D3DVIEWPORT9 vp;
	if (FAILED(device->GetViewport(&vp)))
		return;

	DWORD oldLighting, oldZ, oldCull, oldAlpha, oldSrc, oldDst, oldFog;
	device->GetRenderState(D3DRS_LIGHTING,         &oldLighting);
	device->GetRenderState(D3DRS_ZENABLE,          &oldZ);
	device->GetRenderState(D3DRS_CULLMODE,         &oldCull);
	device->GetRenderState(D3DRS_ALPHABLENDENABLE, &oldAlpha);
	device->GetRenderState(D3DRS_SRCBLEND,         &oldSrc);
	device->GetRenderState(D3DRS_DESTBLEND,        &oldDst);
	device->GetRenderState(D3DRS_FOGENABLE,        &oldFog);
	IDirect3DBaseTexture9 *oldTex = 0;
	device->GetTexture(0, &oldTex);

	device->SetTexture(0, 0);
	device->SetRenderState(D3DRS_LIGHTING,         FALSE);
	device->SetRenderState(D3DRS_ZENABLE,          FALSE);
	device->SetRenderState(D3DRS_FOGENABLE,        FALSE);
	device->SetRenderState(D3DRS_CULLMODE,         D3DCULL_NONE);
	device->SetRenderState(D3DRS_ALPHABLENDENABLE, TRUE);
	device->SetRenderState(D3DRS_SRCBLEND,         D3DBLEND_SRCALPHA);
	device->SetRenderState(D3DRS_DESTBLEND,        D3DBLEND_INVSRCALPHA);
	device->SetFVF(kVertex2DFVF);

	ExecState st;
	st.color = 0xFFFFFFFF;
	st.width = 1.0f;
	st.mat.Identity();
	std::vector<ExecState> stack;
	ExecuteListInner(self, device,
		st, stack, (float)vp.X, (float)vp.Y, (float)vp.Width, (float)vp.Height, 0);

	device->SetTexture(0, oldTex);
	if (oldTex) oldTex->Release();
	device->SetRenderState(D3DRS_LIGHTING,         oldLighting);
	device->SetRenderState(D3DRS_ZENABLE,          oldZ);
	device->SetRenderState(D3DRS_FOGENABLE,        oldFog);
	device->SetRenderState(D3DRS_CULLMODE,         oldCull);
	device->SetRenderState(D3DRS_ALPHABLENDENABLE, oldAlpha);
	device->SetRenderState(D3DRS_SRCBLEND,         oldSrc);
	device->SetRenderState(D3DRS_DESTBLEND,        oldDst);
}
