//===========================================================================//
// File:	dpl2d.cpp                                                          //
// Project: BattleTech port (WinTesla / btl4)                                //
//---------------------------------------------------------------------------//
// libDPL 2D vector display-list layer, re-hosted over Direct3D 9.            //
//                                                                            //
// Replaces the inert dpl2d_* stubs that used to live in btstubs.cpp.  The    //
// recorder calls (dpl2d_NewDisplayList / Begin / SetColor / Circle /         //
// PushMatrix / PopMatrix / MoveTo / End / Compile -- prototypes in           //
// btl4vid.hpp) build a small command list; dpl2d_ExecuteList (dpl2d.hpp)     //
// draws a compiled list with screen-space 2D primitives.                     //
//                                                                            //
// The recorded calls match what the reconstructed reticle/PIP builder in     //
// btl4vid.cpp actually issues, e.g.                                          //
//      dpl2d_Begin(list, 1);                                                 //
//      dpl2d_SetColor(list, r, g, b);                                        //
//      dpl2d_Circle(list, x, y, 0.012f, 1);    // filled blip                //
//      dpl2d_SetColor(list, 0, 0, 0);                                        //
//      dpl2d_Circle(list, x, y, 0.014f, 0);    // dark outline ring          //
//      dpl2d_PushMatrix(list); dpl2d_MoveTo(list, x, y); dpl2d_PopMatrix();  //
//      dpl2d_End(list); dpl2d_Compile(list);                                 //
//===========================================================================//

#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>

//===========================================================================//
// Pre-transformed 2D vertex -- mirrors MUNGA_L4/L4D3D.h L4VERTEX_2D and its
// L4VERTEX_2D_FVF, redeclared locally so this TU does not need the MUNGA_L4
// source dir on the include path (the engine lib uses the identical layout).
//===========================================================================//
namespace
{
	struct Vertex2D
	{
		float x, y, z, rhw;
		DWORD color;
	};
	const DWORD kVertex2DFVF = (D3DFVF_XYZRHW | D3DFVF_DIFFUSE);

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

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

	//
	// One recorded drawing command.  The dpl2d_ API the port uses only ever
	// emits coloured circles (filled disc or outline ring); the matrix stack
	// and pen move are tracked as a translation so any future MoveTo-relative
	// geometry lands in the right place.
	//
	struct Command
	{
		enum Kind { kCircleFill, kCircleOutline } kind;
		DWORD color;
		float x, y;			// normalised view coords (already translated)
		float radius;		// normalised (fraction of viewport width)
	};

	struct Vec2 { float x, y; };
}

//
// The concrete display list.  dpl2d_DISPLAY is an opaque empty class
// (DPLSTUB.h), so the recorder allocates one of these and hands the caller a
// reinterpret_cast handle; every dpl2d_ call casts it straight back.
//
struct Dpl2dList
{
	bool				recording;
	bool				compiled;
	int					mode;
	DWORD				currentColor;
	float				penX, penY;			// current pen (MoveTo)
	float				transX, transY;		// current translation
	std::vector<Vec2>	matrixStack;		// saved translations (Push/Pop)
	std::vector<Command> commands;

	Dpl2dList()
		: recording(false), compiled(false), mode(0),
		  currentColor(0xFFFFFFFF),
		  penX(0.0f), penY(0.0f), transX(0.0f), transY(0.0f)
	{}
};

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

//===========================================================================//
// Recorder -- the dpl2d_* entry points the game calls (declared in btl4vid.hpp)
//===========================================================================//

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

void dpl2d_Begin(dpl2d_DISPLAY *list, int mode)
{
	Dpl2dList *self = AsList(list);
	if (self == 0) return;
	self->recording   = true;
	self->compiled    = false;
	self->mode        = mode;
	self->currentColor= 0xFFFFFFFF;
	self->transX      = self->transY = 0.0f;
	self->penX        = self->penY   = 0.0f;
	self->matrixStack.clear();
	self->commands.clear();
}

void dpl2d_SetColor(dpl2d_DISPLAY *list, Scalar red, Scalar green, Scalar blue)
{
	Dpl2dList *self = AsList(list);
	if (self == 0) return;
	self->currentColor = D3DCOLOR_COLORVALUE(
		ClampUnit((float)red), ClampUnit((float)green), ClampUnit((float)blue), 1.0f);
}

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

void dpl2d_PushMatrix(dpl2d_DISPLAY *list)
{
	Dpl2dList *self = AsList(list);
	if (self == 0) return;
	Vec2 saved = { self->transX, self->transY };
	self->matrixStack.push_back(saved);
}

void dpl2d_PopMatrix(dpl2d_DISPLAY *list)
{
	Dpl2dList *self = AsList(list);
	if (self == 0 || self->matrixStack.empty()) return;
	Vec2 saved = self->matrixStack.back();
	self->matrixStack.pop_back();
	self->transX = saved.x;
	self->transY = saved.y;
}

//
// MoveTo: position the pen.  In the recovered usage it is bracketed by
// Push/Pop with no draw in between (a no-op marker stamp), so it only updates
// the pen + the current translation; nothing is emitted.
//
void dpl2d_MoveTo(dpl2d_DISPLAY *list, Scalar x, Scalar y)
{
	Dpl2dList *self = AsList(list);
	if (self == 0) return;
	self->penX   = (float)x;
	self->penY   = (float)y;
	self->transX = (float)x;
	self->transY = (float)y;
}

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

void dpl2d_Compile(dpl2d_DISPLAY *list)
{
	Dpl2dList *self = AsList(list);
	if (self == 0) return;
	self->compiled = true;		// geometry is tessellated lazily at execute time
}

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

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;
	const float ox = (float)vp.X;
	const float oy = (float)vp.Y;
	const float vw = (float)vp.Width;
	const float vh = (float)vp.Height;

	// --- save the render states we touch ---------------------------------
	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);

	Vertex2D ring[kCircleSegments + 2];

	for (size_t i = 0; i < self->commands.size(); ++i)
	{
		const Command &cmd = self->commands[i];

		// normalised view coords -> pixels.  radius scales with viewport width
		// so it stays round on a non-square viewport.
		const float cx = ox + cmd.x * vw;
		const float cy = oy + cmd.y * vh;
		const float rx = cmd.radius * vw;
		const float ry = cmd.radius * vw;

		if (cmd.kind == Command::kCircleFill)
		{
			// triangle fan: centre + rim, closed back to the first rim vertex.
			ring[0].x = cx; ring[0].y = cy; ring[0].z = 0.0f; ring[0].rhw = 1.0f;
			ring[0].color = cmd.color;
			for (int s = 0; s <= kCircleSegments; ++s)
			{
				float a = (2.0f * kPi * s) / kCircleSegments;
				ring[s + 1].x     = cx + cosf(a) * rx;
				ring[s + 1].y     = cy + sinf(a) * ry;
				ring[s + 1].z     = 0.0f;
				ring[s + 1].rhw   = 1.0f;
				ring[s + 1].color = cmd.color;
			}
			device->DrawPrimitiveUP(
				D3DPT_TRIANGLEFAN, kCircleSegments,
				ring, sizeof(Vertex2D));
		}
		else // kCircleOutline
		{
			// line strip around the rim, closed.
			for (int s = 0; s <= kCircleSegments; ++s)
			{
				float a = (2.0f * kPi * s) / kCircleSegments;
				ring[s].x     = cx + cosf(a) * rx;
				ring[s].y     = cy + sinf(a) * ry;
				ring[s].z     = 0.0f;
				ring[s].rhw   = 1.0f;
				ring[s].color = cmd.color;
			}
			device->DrawPrimitiveUP(
				D3DPT_LINESTRIP, kCircleSegments,
				ring, sizeof(Vertex2D));
		}
	}

	// --- restore ----------------------------------------------------------
	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);
}
