/*
** thingdef_data.cpp
**
** DECORATE data tables
**
**---------------------------------------------------------------------------
** Copyright 2002-2008 Christoph Oelckers
** Copyright 2004-2008 Randy Heit
** All rights reserved.
**
** 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. The name of the author may not be used to endorse or promote products
**    derived from this software without specific prior written permission.
**
** THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``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 AUTHOR 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 "zstring.h"
#include "vm.h"
#include "gstrings.h"
#include "v_font.h"
#include "types.h"
#include "utf8.h"



FString FStringFormat(VM_ARGS, int offset)
{
	PARAM_VA_POINTER(va_reginfo)	// Get the hidden type information array
	assert(va_reginfo[offset] == REGT_STRING);

	FString fmtstring = param[offset].s().GetChars();

	param += offset;
	numparam -= offset;
	va_reginfo += offset;

	// note: we don't need a real printf format parser.
	//       enough to simply find the subtitution tokens and feed them to the real printf after checking types.
	//       https://en.wikipedia.org/wiki/Printf_format_string#Format_placeholder_specification
	FString output;
	bool in_fmt = false;
	FString fmt_current;
	int argnum = 1;
	int argauto = 1;
	// % = starts
	//  [0-9], -, +, \s, 0, #, . continue
	//  %, s, d, i, u, fF, eE, gG, xX, o, c, p, aA terminate
	// various type flags are not supported. not like stuff like 'hh' modifier is to be used in the VM.
	// the only combination that is parsed locally is %n$...
	bool haveargnums = false;
	for (size_t i = 0; i < fmtstring.Len(); i++)
	{
		char c = fmtstring[i];
		if (in_fmt)
		{
			if (c == '*' && (fmt_current.Len() == 1 || (fmt_current.Len() == 2 && fmt_current[1] == '0')))
			{
				fmt_current += c;
			}
			else if ((c >= '0' && c <= '9') ||
				c == '-' || c == '+' || (c == ' ' && fmt_current.Back() != ' ') || c == '#' || c == '.')
			{
				fmt_current += c;
			}
			else if (c == '$') // %number$format
			{
				if (!haveargnums && argauto > 1)
					ThrowAbortException(X_FORMAT_ERROR, "Cannot mix explicit and implicit arguments.");
				FString argnumstr = fmt_current.Mid(1);
				if (!argnumstr.IsInt()) ThrowAbortException(X_FORMAT_ERROR, "Expected a numeric value for argument number, got '%s'.", argnumstr.GetChars());
				auto argnum64 = argnumstr.ToLong();
				if (argnum64 < 1 || argnum64 >= numparam) ThrowAbortException(X_FORMAT_ERROR, "Not enough arguments for format (tried to access argument %d, %d total).", argnum64, numparam);
				fmt_current = "%";
				haveargnums = true;
				argnum = int(argnum64);
			}
			else
			{
				fmt_current += c;

				switch (c)
				{
					// string
				case 's':
				{
					if (argnum < 0 && haveargnums)
						ThrowAbortException(X_FORMAT_ERROR, "Cannot mix explicit and implicit arguments.");
					in_fmt = false;
					// fail if something was found, but it's not a string
					if (argnum >= numparam) ThrowAbortException(X_FORMAT_ERROR, "Not enough arguments for format.");
					if (va_reginfo[argnum] != REGT_STRING) ThrowAbortException(X_FORMAT_ERROR, "Expected a string for format %s.", fmt_current.GetChars());
					// append
					output.AppendFormat(fmt_current.GetChars(), param[argnum].s().GetChars());
					if (!haveargnums) argnum = ++argauto;
					else argnum = -1;
					break;
				}

				// pointer
				case 'p':
				{
					if (argnum < 0 && haveargnums)
						ThrowAbortException(X_FORMAT_ERROR, "Cannot mix explicit and implicit arguments.");
					in_fmt = false;
					// fail if something was found, but it's not a string
					if (argnum >= numparam) ThrowAbortException(X_FORMAT_ERROR, "Not enough arguments for format.");
					if (va_reginfo[argnum] != REGT_POINTER) ThrowAbortException(X_FORMAT_ERROR, "Expected a pointer for format %s.", fmt_current.GetChars());
					// append
					output.AppendFormat(fmt_current.GetChars(), param[argnum].a);
					if (!haveargnums) argnum = ++argauto;
					else argnum = -1;
					break;
				}

				// int formats (including char)
				case 'd':
				case 'i':
				case 'u':
				case 'x':
				case 'X':
				case 'o':
				case 'c':
				case 'B':
				{
					if (argnum < 0 && haveargnums)
						ThrowAbortException(X_FORMAT_ERROR, "Cannot mix explicit and implicit arguments.");
					in_fmt = false;
					// append
					if (fmt_current[1] == '*' || fmt_current[2] == '*')
					{
						// fail if something was found, but it's not an int
						if (argnum+1 >= numparam) ThrowAbortException(X_FORMAT_ERROR, "Not enough arguments for format.");
						if (va_reginfo[argnum] != REGT_INT &&
							va_reginfo[argnum] != REGT_FLOAT) ThrowAbortException(X_FORMAT_ERROR, "Expected a numeric value for format %s.", fmt_current.GetChars());
						if (va_reginfo[argnum+1] != REGT_INT &&
							va_reginfo[argnum+1] != REGT_FLOAT) ThrowAbortException(X_FORMAT_ERROR, "Expected a numeric value for format %s.", fmt_current.GetChars());

						output.AppendFormat(fmt_current.GetChars(), param[argnum].ToInt(va_reginfo[argnum]), param[argnum + 1].ToInt(va_reginfo[argnum + 1]));
						argauto++;
					}
					else
					{
						// fail if something was found, but it's not an int
						if (argnum >= numparam) ThrowAbortException(X_FORMAT_ERROR, "Not enough arguments for format.");
						if (va_reginfo[argnum] != REGT_INT &&
							va_reginfo[argnum] != REGT_FLOAT) ThrowAbortException(X_FORMAT_ERROR, "Expected a numeric value for format %s.", fmt_current.GetChars());
						output.AppendFormat(fmt_current.GetChars(), param[argnum].ToInt(va_reginfo[argnum]));
					}
					if (!haveargnums) argnum = ++argauto;
					else argnum = -1;
					break;
				}

				// double formats
				case 'f':
				case 'F':
				case 'e':
				case 'E':
				case 'g':
				case 'G':
				case 'a':
				case 'A':
				{
					if (argnum < 0 && haveargnums)
						ThrowAbortException(X_FORMAT_ERROR, "Cannot mix explicit and implicit arguments.");
					in_fmt = false;
					if (fmt_current[1] == '*' || fmt_current[2] == '*')
					{
						// fail if something was found, but it's not an int
						if (argnum + 1 >= numparam) ThrowAbortException(X_FORMAT_ERROR, "Not enough arguments for format.");
						if (va_reginfo[argnum] != REGT_INT &&
							va_reginfo[argnum] != REGT_FLOAT) ThrowAbortException(X_FORMAT_ERROR, "Expected a numeric value for format %s.", fmt_current.GetChars());
						if (va_reginfo[argnum + 1] != REGT_INT &&
							va_reginfo[argnum + 1] != REGT_FLOAT) ThrowAbortException(X_FORMAT_ERROR, "Expected a numeric value for format %s.", fmt_current.GetChars());

						output.AppendFormat(fmt_current.GetChars(), param[argnum].ToInt(va_reginfo[argnum]), param[argnum + 1].ToDouble(va_reginfo[argnum + 1]));
						argauto++;
					}
					else
					{
						// fail if something was found, but it's not a float
						if (argnum >= numparam) ThrowAbortException(X_FORMAT_ERROR, "Not enough arguments for format.");
						if (va_reginfo[argnum] != REGT_INT &&
							va_reginfo[argnum] != REGT_FLOAT) ThrowAbortException(X_FORMAT_ERROR, "Expected a numeric value for format %s.", fmt_current.GetChars());
						// append
						output.AppendFormat(fmt_current.GetChars(), param[argnum].ToDouble(va_reginfo[argnum]));
					}
					if (!haveargnums) argnum = ++argauto;
					else argnum = -1;
					break;
				}

				default:
					// invalid character
					output += fmt_current;
					in_fmt = false;
					break;
				}
			}
		}
		else
		{
			if (c == '%')
			{
				if (i + 1 < fmtstring.Len() && fmtstring[i + 1] == '%')
				{
					output += '%';
					i++;
				}
				else
				{
					in_fmt = true;
					fmt_current = "%";
				}
			}
			else
			{
				output += c;
			}
		}
	}

	return output;
}

DEFINE_ACTION_FUNCTION(FStringStruct, Format)
{
	PARAM_PROLOGUE;
	FString s = FStringFormat(VM_ARGS_NAMES);
	ACTION_RETURN_STRING(s);
}

DEFINE_ACTION_FUNCTION(FStringStruct, AppendFormat)
{
	PARAM_SELF_STRUCT_PROLOGUE(FString);
	// first parameter is the self pointer
	FString s = FStringFormat(VM_ARGS_NAMES, 1);
	(*self) += s;
	return 0;
}

DEFINE_ACTION_FUNCTION(FStringStruct, AppendCharacter)
{
	PARAM_SELF_STRUCT_PROLOGUE(FString);
	PARAM_INT(c);
	self->AppendCharacter(c);
	return 0;
}

DEFINE_ACTION_FUNCTION(FStringStruct, DeleteLastCharacter)
{
	PARAM_SELF_STRUCT_PROLOGUE(FString);
	self->DeleteLastCharacter();
	return 0;
}

//=====================================================================================
//
// FString exports
//
//=====================================================================================

static void LocalizeString(const FString &label, bool prefixed, FString *result)
{
	if (!prefixed) *result = GStrings.GetString(label);
	else if (label[0] != '$') *result = label;
	else *result = GStrings.GetString(&label[1]);
}

DEFINE_ACTION_FUNCTION_NATIVE(FStringTable, Localize, LocalizeString)
{
	PARAM_PROLOGUE;
	PARAM_STRING(label);
	PARAM_BOOL(prefixed);
	FString result;
	LocalizeString(label, prefixed, &result);
	ACTION_RETURN_STRING(result);
}

static void StringReplace(FString *self, const FString &s1, const FString &s2)
{
	self->Substitute(s1, s2);
}

DEFINE_ACTION_FUNCTION_NATIVE(FStringStruct, Replace, StringReplace)
{
	PARAM_SELF_STRUCT_PROLOGUE(FString);
	PARAM_STRING(s1);
	PARAM_STRING(s2);
	self->Substitute(s1, s2);
	return 0;
}

static void StringMid(FString *self, unsigned pos, unsigned len, FString *result)
{
	*result = self->Mid(pos, len);
}

DEFINE_ACTION_FUNCTION_NATIVE(FStringStruct, Mid, StringMid)
{
	PARAM_SELF_STRUCT_PROLOGUE(FString);
	PARAM_UINT(pos);
	PARAM_UINT(len);
	FString s = self->Mid(pos, len);
	ACTION_RETURN_STRING(s);
}

static void StringLeft(FString *self, unsigned len, FString *result)
{
	*result = self->Left(len);
}

DEFINE_ACTION_FUNCTION_NATIVE(FStringStruct, Left, StringLeft)
{
	PARAM_SELF_STRUCT_PROLOGUE(FString);
	PARAM_UINT(len);
	FString s = self->Left(len);
	ACTION_RETURN_STRING(s);
}

static void StringTruncate(FString *self, unsigned len)
{
	self->Truncate(len);
}

DEFINE_ACTION_FUNCTION_NATIVE(FStringStruct, Truncate, StringTruncate)
{
	PARAM_SELF_STRUCT_PROLOGUE(FString);
	PARAM_UINT(len);
	self->Truncate(len);
	return 0;
}

static void StringRemove(FString *self, unsigned index, unsigned remlen)
{
	self->Remove(index, remlen);
}

DEFINE_ACTION_FUNCTION_NATIVE(FStringStruct, Remove, StringRemove)
{
	PARAM_SELF_STRUCT_PROLOGUE(FString);
	PARAM_UINT(index);
	PARAM_UINT(remlen);
	self->Remove(index, remlen);
	return 0;
}

static void StringCharAt(FString *self, int pos, FString *result)
{
	if ((unsigned)pos >= self->Len()) *result = "";
	else *result = FString((*self)[pos]);
}
// CharAt and CharCodeAt is how JS does it, and JS is similar here in that it doesn't have char type as int.
DEFINE_ACTION_FUNCTION_NATIVE(FStringStruct, CharAt, StringCharAt)
{
	PARAM_SELF_STRUCT_PROLOGUE(FString);
	PARAM_INT(pos);
	FString result;
	StringCharAt(self, pos, &result);
	ACTION_RETURN_STRING(result);
}

static int StringCharCodeAt(FString *self, int pos)
{
	if ((unsigned)pos >= self->Len()) return 0;
	else return (*self)[pos];
}

DEFINE_ACTION_FUNCTION_NATIVE(FStringStruct, CharCodeAt, StringCharCodeAt)
{
	PARAM_SELF_STRUCT_PROLOGUE(FString);
	PARAM_INT(pos);
	ACTION_RETURN_INT(StringCharCodeAt(self, pos));
}

static int StringByteAt(FString *self, int pos)
{
	if ((unsigned)pos >= self->Len()) return 0;
	else return (uint8_t)((*self)[pos]);
}

DEFINE_ACTION_FUNCTION_NATIVE(FStringStruct, ByteAt, StringByteAt)
{
	PARAM_SELF_STRUCT_PROLOGUE(FString);
	PARAM_INT(pos);
	ACTION_RETURN_INT(StringByteAt(self, pos));
}

static void StringFilter(FString *self, FString *result)
{
	*result = strbin1(self->GetChars());
}

DEFINE_ACTION_FUNCTION_NATIVE(FStringStruct, Filter, StringFilter)
{
	PARAM_SELF_STRUCT_PROLOGUE(FString);
	ACTION_RETURN_STRING(strbin1(self->GetChars()));
}

static int StringIndexOf(FString *self, const FString &substr, int startIndex)
{
	return (int)self->IndexOf(substr, startIndex);
}

DEFINE_ACTION_FUNCTION_NATIVE(FStringStruct, IndexOf, StringIndexOf)
{
	PARAM_SELF_STRUCT_PROLOGUE(FString);
	PARAM_STRING(substr);
	PARAM_INT(startIndex);
	ACTION_RETURN_INT(StringIndexOf(self, substr, startIndex));
}

static int StringLastIndexOf(FString *self, const FString &substr, int endIndex)
{
	return (int)self->LastIndexOfBroken(substr, endIndex);
}

DEFINE_ACTION_FUNCTION_NATIVE(FStringStruct, LastIndexOf, StringLastIndexOf)
{
	PARAM_SELF_STRUCT_PROLOGUE(FString);
	PARAM_STRING(substr);
	PARAM_INT(endIndex);
	ACTION_RETURN_INT(StringLastIndexOf(self, substr, endIndex));
}

static int StringRightIndexOf(FString *self, const FString &substr, int endIndex)
{
	return (int)self->LastIndexOf(substr, endIndex);
}

DEFINE_ACTION_FUNCTION_NATIVE(FStringStruct, RightIndexOf, StringRightIndexOf)
{
	PARAM_SELF_STRUCT_PROLOGUE(FString);
	PARAM_STRING(substr);
	PARAM_INT(endIndex);
	ACTION_RETURN_INT(StringRightIndexOf(self, substr, endIndex));
}

static void StringToUpper(FString *self)
{
	self->ToUpper();
}

DEFINE_ACTION_FUNCTION_NATIVE(FStringStruct, ToUpper, StringToUpper)
{
	PARAM_SELF_STRUCT_PROLOGUE(FString);
	self->ToUpper();
	return 0;
}

static void StringToLower(FString *self)
{
	self->ToLower();
}

DEFINE_ACTION_FUNCTION_NATIVE(FStringStruct, ToLower, StringToLower)
{
	PARAM_SELF_STRUCT_PROLOGUE(FString);
	self->ToLower();
	return 0;
}

static void StringMakeUpper(FString *self, FString *out)
{
	*out = self->MakeUpper();
}

DEFINE_ACTION_FUNCTION_NATIVE(FStringStruct, MakeUpper, StringMakeUpper)
{
	PARAM_SELF_STRUCT_PROLOGUE(FString);
	ACTION_RETURN_STRING(self->MakeUpper());
}

static void StringMakeLower(FString *self, FString *out)
{
	*out = self->MakeLower();
}

DEFINE_ACTION_FUNCTION_NATIVE(FStringStruct, MakeLower, StringMakeLower)
{
	PARAM_SELF_STRUCT_PROLOGUE(FString);
	ACTION_RETURN_STRING(self->MakeLower());
}

static int StringCharUpper(int ch)
{
	return ch >= 0 && ch < 65536 ? upperforlower[ch] : ch;
}

DEFINE_ACTION_FUNCTION_NATIVE(FStringStruct, CharUpper, StringCharUpper)
{
	PARAM_PROLOGUE;
	PARAM_INT(ch);
	ACTION_RETURN_INT(StringCharUpper(ch));
}

static int StringCharLower(int ch)
{
	return ch >= 0 && ch < 65536 ? lowerforupper[ch] : ch;
}

DEFINE_ACTION_FUNCTION_NATIVE(FStringStruct, CharLower, StringCharLower)
{
	PARAM_PROLOGUE;
	PARAM_INT(ch);
	ACTION_RETURN_INT(StringCharLower(ch));
}


static int StringToInt(FString *self, int base)
{
	return (int)self->ToLong(base);
}

DEFINE_ACTION_FUNCTION_NATIVE(FStringStruct, ToInt, StringToInt)
{
	PARAM_SELF_STRUCT_PROLOGUE(FString);
	PARAM_INT(base);
	ACTION_RETURN_INT((int)self->ToLong(base));
}

static double StringToDbl(FString *self)
{
	return self->ToDouble();
}

DEFINE_ACTION_FUNCTION_NATIVE(FStringStruct, ToDouble, StringToDbl)
{
	PARAM_SELF_STRUCT_PROLOGUE(FString);
	ACTION_RETURN_FLOAT(self->ToDouble());
}

static void StringSubst(FString *self, const FString &substr, const FString& replc)
{
	self->Substitute(substr, replc);
}

DEFINE_ACTION_FUNCTION_NATIVE(FStringStruct, Substitute, StringSubst)
{
	PARAM_SELF_STRUCT_PROLOGUE(FString);
	PARAM_STRING(substr);
	PARAM_STRING(replc);
	StringSubst(self, substr, replc);
	return 0;
}

static void StringStripRight(FString* self, const FString& junk)
{
	if (junk.IsNotEmpty()) self->StripRight(junk);
	else self->StripRight();
}

DEFINE_ACTION_FUNCTION_NATIVE(FStringStruct, StripRight, StringStripRight)
{
	PARAM_SELF_STRUCT_PROLOGUE(FString);
	PARAM_STRING(junk);
	StringStripRight(self, junk);
	return 0;
}

static void StringStripLeft(FString* self, const FString& junk)
{
	if (junk.IsNotEmpty()) self->StripLeft(junk);
	else self->StripLeft();
}

DEFINE_ACTION_FUNCTION_NATIVE(FStringStruct, StripLeft, StringStripLeft)
{
	PARAM_SELF_STRUCT_PROLOGUE(FString);
	PARAM_STRING(junk);
	StringStripLeft(self, junk);
	return 0;
}

static void StringStripLeftRight(FString* self, const FString& junk)
{
	if (junk.IsNotEmpty()) self->StripLeftRight(junk);
	else self->StripLeftRight();
}

DEFINE_ACTION_FUNCTION_NATIVE(FStringStruct, StripLeftRight, StringStripLeftRight)
{
	PARAM_SELF_STRUCT_PROLOGUE(FString);
	PARAM_STRING(junk);
	StringStripLeftRight(self, junk);
	return 0;
}

static void StringSplit(FString* self, TArray<FString>* tokens, const FString& delimiter, int keepEmpty)
{
	self->Split(*tokens, delimiter, static_cast<FString::EmptyTokenType>(keepEmpty));
}

DEFINE_ACTION_FUNCTION_NATIVE(FStringStruct, Split, StringSplit)
{
	PARAM_SELF_STRUCT_PROLOGUE(FString);
	PARAM_POINTER(tokens, TArray<FString>);
	PARAM_STRING(delimiter);
	PARAM_INT(keepEmpty);
	StringSplit(self, tokens, delimiter, keepEmpty);
	return 0;
}

static int StringCodePointCount(FString *self)
{
	return (int)self->CharacterCount();
}

DEFINE_ACTION_FUNCTION_NATIVE(FStringStruct, CodePointCount, StringCodePointCount)
{
	PARAM_SELF_STRUCT_PROLOGUE(FString);
	ACTION_RETURN_INT(StringCodePointCount(self));
}

static int StringNextCodePoint(FString *self, int inposition, int *position)
{
	int codepoint = self->GetNextCharacter(inposition);
	if (position) *position = inposition;
	return codepoint;
}

DEFINE_ACTION_FUNCTION_NATIVE(FStringStruct, GetNextCodePoint, StringNextCodePoint)
{
	PARAM_SELF_STRUCT_PROLOGUE(FString);
	PARAM_INT(pos);
	if (numret > 0) ret[0].SetInt(self->GetNextCharacter(pos));
	if (numret > 1) ret[1].SetInt(pos);
	return numret;
}


