// Copyright (c) 2025 Rachael Alexanderson
// Copyright (c) 2025 GZDoom Maintainers and Contributors
// 
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
// 
// 1. Redistributions of source code must retain the above copyright notice,
// this list of conditions and the following disclaimer.
// 
// 2. Redistributions in binary form must reproduce the above copyright notice,
// this list of conditions and the following disclaimer in the documentation
// and/or other materials provided with the distribution.
// 
// 3. Neither the name of the copyright holder nor the names of its
// contributors may be used to endorse or promote products derived from this
// software without specific prior written permission.
// 
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS “AS IS”
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
// POSSIBILITY OF SUCH DAMAGE.

#include <stdio.h>
#include <stdint.h>
#include <stdbool.h>
#include <stdlib.h>

#ifdef _WIN32
// windows platform code uses special stdout handling, let's make sure to use that
#include <windows.h>
extern HANDLE StdOut;
static void CPrint(const char* in)
{
	DWORD bytes_written;
	if (!StdOut)
		return;
	WriteFile(StdOut, in, strlen(in), &bytes_written, NULL);
}
#else
static void CPrint(const char* in)
{
	fputs(in, stdout);
}
#endif

enum class Support { DUMB, NONE, BASIC, FULL };

static const char *ansi_esc[2] = {"\x1b[", ";"};
static const char *ansi_end[2] = {"", "m"};

// Map DOS color to ANSI escape code
static const char *ansi_fg[16] = 
{
	"30", "34", "32", "36", "31", "35", "33", "37", 
	"90", "94", "92", "96", "91", "95", "93", "97"
};
// Only standard backgrounds (no bright backgrounds in classic ANSI)
static const char *ansi_bg[8] = 
{
	"40", "44", "42", "46",
	"41", "45", "43", "47"
};
// ANSI codes for truecolor DOS colors
static const char *ansi_tc_fg[16] = 
{
	"38;2;0;0;0", "38;2;0;0;170", "38;2;0;170;0", "38;2;0;170;170",
	"38;2;170;0;0", "38;2;170;0;170", "38;2;170;85;0", "38;2;170;170;170",
	"38;2;85;85;85", "38;2;85;85;255", "38;2;85;255;0", "38;2;85;255;255",
	"38;2;255;85;85", "38;2;255;85;255", "38;2;255;255;85", "38;2;255;255;255"
};
static const char *ansi_tc_bg[8] = 
{
	"48;2;0;0;0", "48;2;0;0;170", "48;2;0;170;0", "48;2;0;170;170",
	"48;2;170;0;0", "48;2;170;0;170", "48;2;170;85;0", "48;2;170;170;170"
};

static const char *ansi_flash[2] = { "25", "5" };

inline void ansi_ctrl(bool open)
{
	static bool is_ansi_open = false;
	if (open)
		CPrint(ansi_esc[is_ansi_open]);
	else
		CPrint(ansi_end[is_ansi_open]);
	is_ansi_open = open;
}

// Best effort to translate cp437 to ascii, in order to support dumb terminals
char cp437_to_ascii(uint8_t ch)
{
#if 1
	static char lo[32] = {
///////	 0   1   2   3   4   5   6   7   8   9   a   b   c   d   e   f
/* 0 */	' ','+','#','+','+','+','+','+','#','+','#','+','+','+','+','+',
/* 1 */	'<','>','|','!','$','$','-','|','^','v','<','>','-','-','^','v',
	};

	if (ch < 32) return lo[ch];

	static char hi[128] = {
///////	 0   1   2   3   4   5   6   7   8   9   a   b   c   d   e   f
/* 8 */	'C','u','e','a','a','a','a','c','e','e','e','i','i','i','A','A',
/* 9 */	'E','a','A','o','o','o','u','u','y','O','U','$','$','$','$','f',
/* a */	'a','i','o','u','n','N','*','*','?','-','-','/','/','!','"','"',
/* b */	':','+','#','|','+','+','+','+','+','+','|','+','+','+','+','+',
/* c */	'+','+','+','+','-','+','+','+','+','+','+','+','+','-','+','+',
/* d */	'+','+','+','+','+','+','+','+','+','+','+','#','-','|','|','-',
/* e */	'a','B','G','p','S','s','u','t','P','T','O','d','8','h','E','-',
/* f */	'=','+','>','<','|','|','%','=','*','*','*','Q','n','2','#',' ',
	};

	if (ch >= 128) return hi[ch-128];
#else
	if (ch < 32 || ch >= 128) return ' ';
#endif

	return ch;
}

void ibm437_to_utf8(char* result, char in);

void vga_to_ansi(const uint8_t *buf)
{
#ifdef _WIN32
	// FIXME: support for alternative terminals and old windows
	Support termcaps = Support::FULL;
#else
	const char *term = getenv("TERM");
	const char *cterm = getenv("COLORTERM");

	Support termcaps =
		(!term || strcmp(term, "dumb")==0)
			? Support::DUMB
		: (!cterm)
			? Support::NONE
		: (strcmp(cterm, "truecolor")==0 || strcmp(cterm, "24bit")==0)
			? Support::FULL
			: Support::BASIC;
#endif

	for (int row = 0; row < 25; ++row) 
	{
		int last_fg = -1, last_bg = -1;
		bool last_blink = false;
		for (int col = 0; col < 80; ++col) 
		{
			int off = (row * 80 + col) * 2;
			uint8_t ch = buf[off];
			uint8_t attr = buf[off + 1];
			int fg = attr & 0x0F;
			int bg = (attr >> 4) & 0x07;
			bool blink = !!(attr & 0x80);
			bool spacer = (ch == 0) || (ch == 32) || (ch == 255);

			if (termcaps > Support::NONE)
			{
				// Output color if changed
				if ((fg != last_fg) && !spacer)
				{
					ansi_ctrl(1);
					if (termcaps == Support::FULL)
						CPrint(ansi_tc_fg[fg]);
					else
						CPrint(ansi_fg[fg]);
					last_fg = fg;
				}
				if (bg != last_bg)
				{
					ansi_ctrl(1);
					if (termcaps == Support::FULL)
						CPrint(ansi_tc_bg[bg]);
					else
						CPrint(ansi_bg[bg]);
					last_bg = bg;
				}
				if (blink != last_blink)
				{
					ansi_ctrl(1);
					CPrint(ansi_flash[blink]);
					last_blink = blink;
				}
				ansi_ctrl(0);
			}

			if (termcaps == Support::DUMB)
			{
				char result[2] = { cp437_to_ascii(ch), '\0' };
				CPrint(result);
			}
			else
			{
				// Output character, convert CP437 to UTF-8
				char result[4] = "\u00A0"; // use nbsp as space
				if (!spacer) ibm437_to_utf8(result, ch);
				CPrint(result);
			}
		}
		if (termcaps > Support::NONE)
			CPrint("\x1b[0m"); // Reset colors
		CPrint("\n");
	}
}

