// for finding memory leaks in debug mode with Visual Studio
#if defined _DEBUG && defined _MSC_VER
#include <crtdbg.h>
#endif

#include <stdint.h>
#include <stdio.h>
#include "ft2_header.h"
#include "ft2_gui.h"
#include "ft2_help.h"
#include "ft2_video.h"
#include "ft2_pattern_ed.h"
#include "ft2_bmp.h"
#include "ft2_structs.h"
#include "helpdata/ft2_help_data.h"

typedef struct
{
	bool bigFont, noLine;
	uint8_t color;
	int16_t xPos;
	char text[100];
} helpRec;

#define HELP_LINES 15
#define MAX_HELP_LINES 768
#define HELP_SIZE sizeof (helpRec)
#define MAX_SUBJ 10
#define HELP_COLUMN 135
#define HELP_WIDTH (596 - HELP_COLUMN)

static uint8_t fHlp_Num;
static int16_t textLine, fHlp_Line, subjLen[MAX_SUBJ];
static int32_t helpBufferPos;
static helpRec *subjPtrArr[MAX_SUBJ];

static void addText(helpRec *t, int16_t xPos, uint8_t color, char *text)
{
	if (*text == '\0')
		return;

	t->xPos = xPos;
	t->color = color;
	t->bigFont = false;
	t->noLine = false;
	strcpy(t->text, text);
	*text = '\0'; // empty old string

	textLine++;
}

static bool getLine(char *output)
{
	if (helpBufferPos >= (int32_t)sizeof (helpData))
	{
		*output = '\0';
		return false;
	}

	const uint8_t strLen = helpData[helpBufferPos++];
	memcpy(output, &helpData[helpBufferPos], strLen);
	output[strLen] = '\0';

	helpBufferPos += strLen;

	return true;
}

static int16_t controlCodeToNum(const char *controlCode)
{
	return (((controlCode[0]-'0')%10)*100) + (((controlCode[1]-'0')%10)*10) + ((controlCode[2]-'0')%10);
}

static char *ltrim(char *s)
{
	if (*s == '\0')
		return (s);

	while (*s == ' ') s++;

	return s;
}

static char *rtrim(char *s)
{
	if (*s == '\0')
		return (s);

	int32_t i = (int32_t)strlen(s) - 1;
	while (i >= 0)
	{
		if (s[i] != ' ')
		{
			s[i+1] = '\0';
			break;
		}

		i--;
	}

	return s;
}

static void readHelp(void) // this is a bit messy...
{
	char text[256], text2[256], *s, *sEnd, *s3;
	int16_t a, b, i, k;

	helpRec *tempText = (helpRec *)malloc(HELP_SIZE * MAX_HELP_LINES);
	if (tempText == NULL)
	{
		okBox(0, "System message", "Not enough memory!", NULL);
		return;
	}
	
	text[0] = '\0';
	text2[0] = '\0';

	char *s2 = text2;

	helpBufferPos = 0;
	for (int16_t subj = 0; subj < MAX_SUBJ; subj++)
	{
		textLine = 0;
		int16_t currColumn = 0;
		uint8_t currColor = PAL_FORGRND;

		getLine(text); s = text;
		while (strncmp(s, "END", 3) != 0)
		{
			if (*s == ';')
			{
				if (!getLine(text))
					break;

				s = text;
				continue;
			}

			if (*(uint16_t *)s == 0x4C40) // @L - "big font"
			{
				addText(&tempText[textLine], currColumn, currColor, s2);
				s += 2;

				if (*(uint16_t *)s == 0x5840) // @X - "change X position"
				{
					currColumn = controlCodeToNum(&s[2]);
					s += 5;
				}

				if (*(uint16_t *)s == 0x4340) // @C - "change color
				{
					currColor = (uint8_t)controlCodeToNum(&s[2]);
					currColor = (currColor < 2) ? PAL_FORGRND : PAL_BUTTONS;
					s += 5;
				}

				helpRec *t = &tempText[textLine];
				t->xPos = currColumn;
				t->color = currColor;
				t->bigFont = true;
				t->noLine = false;
				strcpy(t->text, s);
				textLine++;

				t = &tempText[textLine];
				t->noLine = true;
				textLine++;
			}
			else
			{
				if (*s == '>')
				{
					addText(&tempText[textLine], currColumn, currColor, s2);
					s++;
				}

				if (*(uint16_t *)s == 0x5840) // @X - "set X position (relative to help X start)"
				{
					currColumn = controlCodeToNum(&s[2]);
					s += 5;
				}

				if (*(uint16_t *)s == 0x4340) // @C - "change color"
				{
					currColor = (uint8_t)controlCodeToNum(&s[2]);
					currColor = (currColor < 2) ? PAL_FORGRND : PAL_BUTTONS;
					s += 5;
				}

				s = ltrim(rtrim(s));
				if (*s == '\0')
				{
					addText(&tempText[textLine], currColumn, currColor, s2);
					strcpy(s2, " ");
					addText(&tempText[textLine], currColumn, currColor, s2);
				}

				int16_t sLen = (int16_t)strlen(s);

				sEnd = &s[sLen];
				while (s < sEnd)
				{
					if (sLen < 0)
						sLen = 0;

					i = 0;
					while (s[i] != ' ' && i < sLen) i++;
					i++;

					if (*(uint16_t *)s == 0x5440) // @T - "set absolute X position (in the middle of text)"
					{
						k = controlCodeToNum(&s[2]);
						s += 5; sLen -= 5;

						s3 = &s2[strlen(s2)];
						while (textWidth(s2) + charWidth(' ') + 1 < k-currColumn)
						{
							s3[0] = ' ';
							s3[1] = '\0';
							s3++;
						}

						b = textWidth(s2) + 1;
						if (b < k-currColumn)
						{
							s3 = &s2[strlen(s2)];
							for (a = 0; a < k-b-currColumn; a++)
								s3[a] = 127; // one-pixel spacer glyph
							s3[a] = '\0';
						}
					}

					if (textWidth(s2)+textNWidth(s,i)+2 > HELP_WIDTH-currColumn)
						addText(&tempText[textLine], currColumn, currColor, s2);

					strncat(s2, s, i);

					s += i; sLen -= i;
					if ((*s == '\0') || (s >= sEnd))
						strcat(s2, " ");
				}
			}

			if (textLine >= MAX_HELP_LINES || !getLine(text))
				break;

			s = text;
		}

		subjPtrArr[subj] = (helpRec *)malloc(HELP_SIZE * textLine);
		if (subjPtrArr[subj] == NULL)
		{
			okBox(0, "System message", "Not enough memory!", NULL);
			break;
		}

		memcpy(subjPtrArr[subj], tempText, HELP_SIZE * textLine);
		subjLen[subj] = textLine;
	}

	free(tempText);
}

static void bigTextOutHalf(uint16_t xPos, uint16_t yPos, uint8_t paletteIndex, bool lowerHalf, const char *textPtr)
{
	assert(textPtr != NULL);

	uint16_t currX = xPos;
	while (true)
	{
		const char chr = *textPtr++ & 0x7F;
		if (chr == '\0')
			break;

		if (chr != ' ')
		{
			const uint8_t *srcPtr = &bmp.font2[chr * FONT2_CHAR_W];
			if (!lowerHalf)
				srcPtr += (FONT2_CHAR_H / 2) * FONT2_WIDTH;

			uint32_t *dstPtr = &video.frameBuffer[(yPos * SCREEN_W) + currX];
			const uint32_t pixVal = video.palette[paletteIndex];

			for (uint32_t y = 0; y < FONT2_CHAR_H/2; y++)
			{
				for (uint32_t x = 0; x < FONT2_CHAR_W; x++)
				{
					if (srcPtr[x])
						dstPtr[x] = pixVal;
				}

				srcPtr += FONT2_WIDTH;
				dstPtr += SCREEN_W;
			}
		}

		currX += charWidth16(chr);
	}
}

static void writeHelp(void)
{
	helpRec *ptr = subjPtrArr[fHlp_Num];
	if (ptr == NULL)
		return;

	for (int16_t i = 0; i < HELP_LINES; i++)
	{
		const int16_t k = i + fHlp_Line;
		if (k >= subjLen[fHlp_Num])
			break;

		clearRect(HELP_COLUMN, 5 + (i * 11), HELP_WIDTH, 11);

		if (ptr[k].noLine)
		{
			if (i == 0)
				bigTextOutHalf(HELP_COLUMN + ptr[k-1].xPos, 5 + (i * 11), PAL_FORGRND, false, ptr[k-1].text);
		}
		else
		{
			if (ptr[k].bigFont)
			{
				if (i == HELP_LINES-1)
				{
					bigTextOutHalf(HELP_COLUMN + ptr[k].xPos, 5 + (i * 11), PAL_FORGRND, true, ptr[k].text);
					return;
				}
				else
				{
					clearRect(HELP_COLUMN, 5 + ((i + 1) * 11), HELP_WIDTH, 11);
					bigTextOut(HELP_COLUMN + ptr[k].xPos, 5 + (i * 11), PAL_FORGRND, ptr[k].text);
					i++;
				}
			}
			else
			{
				textOut(HELP_COLUMN + ptr[k].xPos, 5 + (i * 11), ptr[k].color, ptr[k].text);
			}
		}
	}
}

void helpScrollUp(void)
{
	if (fHlp_Line > 0)
	{
		scrollBarScrollUp(SB_HELP_SCROLL, 1);
		writeHelp();
	}
}

void helpScrollDown(void)
{
	if (fHlp_Line < subjLen[fHlp_Num]-1)
	{
		scrollBarScrollDown(SB_HELP_SCROLL, 1);
		writeHelp();
	}
}

void helpScrollSetPos(uint32_t pos)
{
	if (fHlp_Line != (int16_t)pos)
	{
		fHlp_Line = (int16_t)pos;
		writeHelp();
	}
}

void showHelpScreen(void)
{
	uint16_t tmpID;

	if (ui.extended)
		exitPatternEditorExtended();

	hideTopScreen();
	ui.helpScreenShown = true;

	drawFramework(0,   0, 128, 173, FRAMEWORK_TYPE1);
	drawFramework(128, 0, 504, 173, FRAMEWORK_TYPE1);
	drawFramework(130, 2, 479, 169, FRAMEWORK_TYPE2);

	showPushButton(PB_HELP_EXIT);
	showPushButton(PB_HELP_SCROLL_UP);
	showPushButton(PB_HELP_SCROLL_DOWN);

	uncheckRadioButtonGroup(RB_GROUP_HELP);
	switch (fHlp_Num)
	{
		default:
		case 0: tmpID = RB_HELP_FEATURES;       break;
		case 1: tmpID = RB_HELP_EFFECTS;        break;
		case 2: tmpID = RB_HELP_KEYBINDINGS;       break;
		case 3: tmpID = RB_HELP_HOW_TO_USE_FT2; break;
		case 4: tmpID = RB_HELP_FAQ;            break;
		case 5: tmpID = RB_HELP_KNOWN_BUGS;     break;
	}
	radioButtons[tmpID].state = RADIOBUTTON_CHECKED;

	showRadioButtonGroup(RB_GROUP_HELP);

	showScrollBar(SB_HELP_SCROLL);

	textOutShadow(4,   4, PAL_FORGRND, PAL_DSKTOP2, "Subjects:");
	textOutShadow(21, 19, PAL_FORGRND, PAL_DSKTOP2, "Features");
	textOutShadow(21, 35, PAL_FORGRND, PAL_DSKTOP2, "Effects");
	textOutShadow(21, 51, PAL_FORGRND, PAL_DSKTOP2, "Keybindings");
	textOutShadow(21, 67, PAL_FORGRND, PAL_DSKTOP2, "How to use FT2");
	textOutShadow(21, 83, PAL_FORGRND, PAL_DSKTOP2, "Problems/FAQ");
	textOutShadow(21, 99, PAL_FORGRND, PAL_DSKTOP2, "Known bugs");

	writeHelp();
}

void hideHelpScreen(void)
{
	hidePushButton(PB_HELP_EXIT);
	hidePushButton(PB_HELP_SCROLL_UP);
	hidePushButton(PB_HELP_SCROLL_DOWN);

	hideRadioButtonGroup(RB_GROUP_HELP);
	hideScrollBar(SB_HELP_SCROLL);

	ui.helpScreenShown = false;
}

void exitHelpScreen(void)
{
	hideHelpScreen();
	showTopScreen(true);
}

static void setHelpSubject(uint8_t Nr)
{
	fHlp_Num = Nr;
	fHlp_Line = 0;

	setScrollBarEnd(SB_HELP_SCROLL, subjLen[fHlp_Num]);
	setScrollBarPos(SB_HELP_SCROLL, 0, false);
}

void rbHelpFeatures(void)
{
	checkRadioButton(RB_HELP_FEATURES);
	setHelpSubject(0);
	writeHelp();
}

void rbHelpEffects(void)
{
	checkRadioButton(RB_HELP_EFFECTS);
	setHelpSubject(1);
	writeHelp();
}

void rbHelpKeybindings(void)
{
	checkRadioButton(RB_HELP_KEYBINDINGS);
	setHelpSubject(2);
	writeHelp();
}

void rbHelpHowToUseFT2(void)
{
	checkRadioButton(RB_HELP_HOW_TO_USE_FT2);
	setHelpSubject(3);
	writeHelp();
}

void rbHelpFAQ(void)
{
	checkRadioButton(RB_HELP_FAQ);
	setHelpSubject(4);
	writeHelp();
}

void rbHelpKnownBugs(void)
{
	checkRadioButton(RB_HELP_KNOWN_BUGS);
	setHelpSubject(5);
	writeHelp();
}

void initFTHelp(void)
{
	readHelp();
	setHelpSubject(0);
}

void windUpFTHelp(void)
{
	for (int16_t i = 0; i < MAX_SUBJ; i++)
	{
		if (subjPtrArr[i] != NULL)
		{
			free(subjPtrArr[i]);
			subjPtrArr[i] = NULL;
		}
	}
}
