// SONIC ROBO BLAST 2
//-----------------------------------------------------------------------------
// Copyright (C) 2014-2016 by John "JTE" Muniz.
// Copyright (C) 2014-2024 by Sonic Team Junior.
//
// This program is free software distributed under the
// terms of the GNU General Public License, version 2.
// See the 'LICENSE' file for more details.
//-----------------------------------------------------------------------------
/// \file  lua_hudlib_drawlist.c
/// \brief a data structure for managing cached drawlists for the Lua hud lib

#include "lua_hudlib_drawlist.h"

#include <string.h>

#include "v_video.h"
#include "z_zone.h"

enum drawitem_e {
	DI_Draw = 0,
	DI_DrawScaled,
	DI_DrawStretched,
	DI_DrawCropped,
	DI_DrawNum,
	DI_DrawPaddedNum,
	DI_DrawFill,
	DI_DrawString,
	DI_DrawNameTag,
	DI_DrawScaledNameTag,
	DI_DrawLevelTitle,
	DI_FadeScreen,
	DI_MAX,
};

// A single draw item with all possible arguments needed for a draw call.
typedef struct drawitem_s {
	enum drawitem_e type;
	fixed_t x;
	fixed_t y;
	fixed_t w;
	fixed_t h;
	INT32 c;
	fixed_t scale;
	fixed_t hscale;
	fixed_t vscale;
	patch_t *patch;
	INT32 flags;
	UINT16 basecolor;
	UINT16 outlinecolor;
	UINT8 *colormap;
	UINT8 *basecolormap;
	UINT8 *outlinecolormap;
	fixed_t sx;
	fixed_t sy;
	INT32 num;
	INT32 digits;
	const char *str;
	UINT16 color;
	UINT8 strength;
	INT32 align;
} drawitem_t;

// The internal structure of a drawlist.
struct huddrawlist_s {
	drawitem_t *items;
	size_t items_capacity;
	size_t items_len;
	char *strbuf;
	size_t strbuf_capacity;
	size_t strbuf_len;
};

// alignment types for v.drawString
enum align {
	align_left = 0,
	align_center,
	align_right,
	align_fixed,
	align_fixedcenter,
	align_fixedright,
	align_small,
	align_smallfixed,
	align_smallfixedcenter,
	align_smallfixedright,
	align_smallcenter,
	align_smallright,
	align_smallthin,
	align_smallthincenter,
	align_smallthinright,
	align_smallthinfixed,
	align_smallthinfixedcenter,
	align_smallthinfixedright,
	align_thin,
	align_thinfixed,
	align_thinfixedcenter,
	align_thinfixedright,
	align_thincenter,
	align_thinright
};

huddrawlist_h LUA_HUD_CreateDrawList(void)
{
	huddrawlist_h drawlist;

	drawlist = (huddrawlist_h) Z_Calloc(sizeof(struct huddrawlist_s), PU_STATIC, NULL);
	drawlist->items = NULL;
	drawlist->items_capacity = 0;
	drawlist->items_len = 0;
	drawlist->strbuf = NULL;
	drawlist->strbuf_capacity = 0;
	drawlist->strbuf_len = 0;

	return drawlist;
}

void LUA_HUD_ClearDrawList(huddrawlist_h list)
{
	// rather than deallocate, we'll just save the existing allocation and empty
	// it out for reuse

	// this memset probably isn't necessary
	if (list->items)
	{
		memset(list->items, 0, sizeof(drawitem_t) * list->items_capacity);
	}

	list->items_len = 0;

	if (list->strbuf)
	{
		list->strbuf[0] = 0;
	}
	list->strbuf_len = 0;
}

void LUA_HUD_DestroyDrawList(huddrawlist_h list)
{
	if (list == NULL) return;

	if (list->items)
	{
		Z_Free(list->items);
	}
	Z_Free(list);
}

boolean LUA_HUD_IsDrawListValid(huddrawlist_h list)
{
	if (!list) return false;

	// that's all we can really do to check the validity of the handle right now
	return true;
}

static size_t AllocateDrawItem(huddrawlist_h list)
{
	if (!list) I_Error("can't allocate draw item: invalid list");
	if (list->items_capacity <= list->items_len + 1)
	{
		if (list->items_capacity == 0) list->items_capacity = 128;
		else list->items_capacity *= 2;
		list->items = (drawitem_t *) Z_Realloc(list->items, sizeof(struct drawitem_s) * list->items_capacity, PU_STATIC, NULL);
	}

	return list->items_len++;
}

// copy string to list's internal string buffer
// lua can deallocate the string before we get to use it, so it's important to
// keep our own copy
static const char *CopyString(huddrawlist_h list, const char* str)
{
	size_t lenstr;

	if (!list) I_Error("can't allocate string; invalid list");
	lenstr = strlen(str);
	if (list->strbuf_capacity <= list->strbuf_len + lenstr + 1)
	{
		const char *old_offset = list->strbuf;
		size_t i;
		if (list->strbuf_capacity == 0) list->strbuf_capacity = 256;
		while (list->strbuf_capacity <= list->strbuf_len + lenstr + 1)
			list->strbuf_capacity *= 2;
		list->strbuf = (char*) Z_Realloc(list->strbuf, sizeof(char) * list->strbuf_capacity, PU_STATIC, NULL);

		// align the string pointers to make sure old pointers don't point towards invalid addresses
		// this is necessary since Z_ReallocAlign might actually move the string buffer in memory
		for (i = 0; i < list->items_len; i++)
		{
			list->items[i].str += list->strbuf - old_offset;
		}
	}
	const char *result = (const char *) &list->strbuf[list->strbuf_len];
	strncpy(&list->strbuf[list->strbuf_len], str, lenstr + 1);
	list->strbuf_len += lenstr + 1;
	return result;
}

void LUA_HUD_AddDraw(
	huddrawlist_h list,
	INT32 x,
	INT32 y,
	patch_t *patch,
	INT32 flags,
	UINT8 *colormap
)
{
	size_t i = AllocateDrawItem(list);
	drawitem_t *item = &list->items[i];
	item->type = DI_Draw;
	item->x = x;
	item->y = y;
	item->patch = patch;
	item->flags = flags;
	item->colormap = colormap;
}

void LUA_HUD_AddDrawScaled(
	huddrawlist_h list,
	fixed_t x,
	fixed_t y,
	fixed_t scale,
	patch_t *patch,
	INT32 flags,
	UINT8 *colormap
)
{
	size_t i = AllocateDrawItem(list);
	drawitem_t *item = &list->items[i];
	item->type = DI_DrawScaled;
	item->x = x;
	item->y = y;
	item->scale = scale;
	item->patch = patch;
	item->flags = flags;
	item->colormap = colormap;
}

void LUA_HUD_AddDrawStretched(
	huddrawlist_h list,
	fixed_t x,
	fixed_t y,
	fixed_t hscale,
	fixed_t vscale,
	patch_t *patch,
	INT32 flags,
	UINT8 *colormap
)
{
	size_t i = AllocateDrawItem(list);
	drawitem_t *item = &list->items[i];
	item->type = DI_DrawStretched;
	item->x = x;
	item->y = y;
	item->hscale = hscale;
	item->vscale = vscale;
	item->patch = patch;
	item->flags = flags;
	item->colormap = colormap;
}

void LUA_HUD_AddDrawCropped(
	huddrawlist_h list,
	fixed_t x,
	fixed_t y,
	fixed_t hscale,
	fixed_t vscale,
	patch_t *patch,
	INT32 flags,
	UINT8 *colormap,
	fixed_t sx,
	fixed_t sy,
	fixed_t w,
	fixed_t h
)
{
	size_t i = AllocateDrawItem(list);
	drawitem_t *item = &list->items[i];
	item->type = DI_DrawCropped;
	item->x = x;
	item->y = y;
	item->hscale = hscale;
	item->vscale = vscale;
	item->patch = patch;
	item->flags = flags;
	item->colormap = colormap;
	item->sx = sx;
	item->sy = sy;
	item->w = w;
	item->h = h;
}

void LUA_HUD_AddDrawNum(
	huddrawlist_h list,
	INT32 x,
	INT32 y,
	INT32 num,
	INT32 flags
)
{
	size_t i = AllocateDrawItem(list);
	drawitem_t *item = &list->items[i];
	item->type = DI_DrawNum;
	item->x = x;
	item->y = y;
	item->num = num;
	item->flags = flags;
}

void LUA_HUD_AddDrawPaddedNum(
	huddrawlist_h list,
	INT32 x,
	INT32 y,
	INT32 num,
	INT32 digits,
	INT32 flags
)
{
	size_t i = AllocateDrawItem(list);
	drawitem_t *item = &list->items[i];
	item->type = DI_DrawPaddedNum;
	item->x = x;
	item->y = y;
	item->num = num;
	item->digits = digits;
	item->flags = flags;
}

void LUA_HUD_AddDrawFill(
	huddrawlist_h list,
	INT32 x,
	INT32 y,
	INT32 w,
	INT32 h,
	INT32 c
)
{
	size_t i = AllocateDrawItem(list);
	drawitem_t *item = &list->items[i];
	item->type = DI_DrawFill;
	item->x = x;
	item->y = y;
	item->w = w;
	item->h = h;
	item->c = c;
}

void LUA_HUD_AddDrawString(
	huddrawlist_h list,
	fixed_t x,
	fixed_t y,
	const char *str,
	INT32 flags,
	INT32 align
)
{
	size_t i = AllocateDrawItem(list);
	drawitem_t *item = &list->items[i];
	item->type = DI_DrawString;
	item->x = x;
	item->y = y;
	item->str = CopyString(list, str);
	item->flags = flags;
	item->align = align;
}

void LUA_HUD_AddDrawNameTag(
	huddrawlist_h list,
	INT32 x,
	INT32 y,
	const char *str,
	INT32 flags,
	UINT16 basecolor,
	UINT16 outlinecolor,
	UINT8 *basecolormap,
	UINT8 *outlinecolormap
)
{
	size_t i = AllocateDrawItem(list);
	drawitem_t *item = &list->items[i];
	item->type = DI_DrawNameTag;
	item->x = x;
	item->y = y;
	item->str = CopyString(list, str);
	item->flags = flags;
	item->basecolor = basecolor;
	item->outlinecolor = outlinecolor;
	item->basecolormap = basecolormap;
	item->outlinecolormap = outlinecolormap;
}

void LUA_HUD_AddDrawScaledNameTag(
	huddrawlist_h list,
	fixed_t x,
	fixed_t y,
	const char *str,
	INT32 flags,
	fixed_t scale,
	UINT16 basecolor,
	UINT16 outlinecolor,
	UINT8 *basecolormap,
	UINT8 *outlinecolormap
)
{
	size_t i = AllocateDrawItem(list);
	drawitem_t *item = &list->items[i];
	item->type = DI_DrawScaledNameTag;
	item->x = x;
	item->y = y;
	item->str = CopyString(list, str);
	item->flags = flags;
	item->scale = scale;
	item->basecolor = basecolor;
	item->outlinecolor = outlinecolor;
	item->basecolormap = basecolormap;
	item->outlinecolormap = outlinecolormap;
}

void LUA_HUD_AddDrawLevelTitle(
	huddrawlist_h list,
	INT32 x,
	INT32 y,
	const char *str,
	INT32 flags
)
{
	size_t i = AllocateDrawItem(list);
	drawitem_t *item = &list->items[i];
	item->type = DI_DrawLevelTitle;
	item->x = x;
	item->y = y;
	item->str = CopyString(list, str);
	item->flags = flags;
}

void LUA_HUD_AddFadeScreen(
	huddrawlist_h list,
	UINT16 color,
	UINT8 strength
)
{
	size_t i = AllocateDrawItem(list);
	drawitem_t *item = &list->items[i];
	item->type = DI_FadeScreen;
	item->color = color;
	item->strength = strength;
}

void LUA_HUD_DrawList(huddrawlist_h list)
{
	size_t i;

	if (!list) I_Error("HUD drawlist invalid");
	if (list->items_len <= 0) return;
	if (!list->items) I_Error("HUD drawlist->items invalid");

	for (i = 0; i < list->items_len; i++)
	{
		drawitem_t *item = &list->items[i];

		switch (item->type)
		{
			case DI_Draw:
				V_DrawFixedPatch(item->x<<FRACBITS, item->y<<FRACBITS, FRACUNIT, item->flags, item->patch, item->colormap);
				break;
			case DI_DrawScaled:
				V_DrawFixedPatch(item->x, item->y, item->scale, item->flags, item->patch, item->colormap);
				break;
			case DI_DrawStretched:
				V_DrawStretchyFixedPatch(item->x, item->y, item->hscale, item->vscale, item->flags, item->patch, item->colormap);
				break;
			case DI_DrawCropped:
				V_DrawCroppedPatch(item->x, item->y, item->hscale, item->vscale, item->flags, item->patch, item->colormap, item->sx, item->sy, item->w, item->h);
				break;
			case DI_DrawNum:
				V_DrawTallNum(item->x, item->y, item->flags, item->num);
				break;
			case DI_DrawPaddedNum:
				V_DrawPaddedTallNum(item->x, item->y, item->flags, item->num, item->digits);
				break;
			case DI_DrawFill:
				V_DrawFill(item->x, item->y, item->w, item->h, item->c);
				break;
			case DI_DrawString:
				switch(item->align)
				{
				// hu_font
				case align_left:
					V_DrawString(item->x, item->y, item->flags, item->str);
					break;
				case align_center:
					V_DrawCenteredString(item->x, item->y, item->flags, item->str);
					break;
				case align_right:
					V_DrawRightAlignedString(item->x, item->y, item->flags, item->str);
					break;
				case align_fixed:
					V_DrawStringAtFixed(item->x, item->y, item->flags, item->str);
					break;
				case align_fixedcenter:
					V_DrawCenteredStringAtFixed(item->x, item->y, item->flags, item->str);
					break;
				case align_fixedright:
					V_DrawRightAlignedStringAtFixed(item->x, item->y, item->flags, item->str);
					break;
				// hu_font, 0.5x scale
				case align_small:
					V_DrawSmallString(item->x, item->y, item->flags, item->str);
					break;
				case align_smallfixed:
					V_DrawSmallStringAtFixed(item->x, item->y, item->flags, item->str);
					break;
				case align_smallfixedcenter:
					V_DrawCenteredSmallStringAtFixed(item->x, item->y, item->flags, item->str);
					break;
				case align_smallfixedright:
					V_DrawRightAlignedSmallStringAtFixed(item->x, item->y, item->flags, item->str);
					break;
				case align_smallcenter:
					V_DrawCenteredSmallString(item->x, item->y, item->flags, item->str);
					break;
				case align_smallright:
					V_DrawRightAlignedSmallString(item->x, item->y, item->flags, item->str);
					break;
				case align_smallthin:
					V_DrawSmallThinString(item->x, item->y, item->flags, item->str);
					break;
				case align_smallthincenter:
					V_DrawCenteredSmallThinString(item->x, item->y, item->flags, item->str);
					break;
				case align_smallthinright:
					V_DrawRightAlignedSmallThinString(item->x, item->y, item->flags, item->str);
					break;
				case align_smallthinfixed:
					V_DrawSmallThinStringAtFixed(item->x, item->y, item->flags, item->str);
					break;
				case align_smallthinfixedcenter:
					V_DrawCenteredSmallThinStringAtFixed(item->x, item->y, item->flags, item->str);
					break;
				case align_smallthinfixedright:
					V_DrawRightAlignedSmallThinStringAtFixed(item->x, item->y, item->flags, item->str);
					break;
				// tny_font
				case align_thin:
					V_DrawThinString(item->x, item->y, item->flags, item->str);
					break;
				case align_thincenter:
					V_DrawCenteredThinString(item->x, item->y, item->flags, item->str);
					break;
				case align_thinright:
					V_DrawRightAlignedThinString(item->x, item->y, item->flags, item->str);
					break;
				case align_thinfixed:
					V_DrawThinStringAtFixed(item->x, item->y, item->flags, item->str);
					break;
				case align_thinfixedcenter:
					V_DrawCenteredThinStringAtFixed(item->x, item->y, item->flags, item->str);
					break;
				case align_thinfixedright:
					V_DrawRightAlignedThinStringAtFixed(item->x, item->y, item->flags, item->str);
					break;
				}
				break;
			case DI_DrawNameTag:
				V_DrawNameTag(item->x, item->y, item->flags, FRACUNIT, item->basecolormap, item->outlinecolormap, item->str);
				break;
			case DI_DrawScaledNameTag:
				V_DrawNameTag(FixedInt(item->x), FixedInt(item->y), item->flags, item->scale, item->basecolormap, item->outlinecolormap, item->str);
				break;
			case DI_DrawLevelTitle:
				V_DrawLevelTitle(item->x, item->y, item->flags, item->str);
				break;
			case DI_FadeScreen:
				V_DrawFadeScreen(item->color, item->strength);
				break;
			default:
				I_Error("can't draw draw list item: invalid draw list item type");
				continue;
		}
	}
}
