/*
    Reads wad files, builds nodes, and saves new wad files.
	Copyright (C) 1996 Raven Software 
    Copyright (C) 2002-2006 Randy Heit

    This program is free software; you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation; either version 2 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program; if not, write to the Free Software
    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.

*/

// HEADER FILES ------------------------------------------------------------

#include <string.h>
#include <stdlib.h>
#include <stdio.h>
#include <stdarg.h>
#include <limits.h>
#include "sc_man.h"

#ifdef _MSC_VER
#pragma warning(disable:4996)
#endif

// MACROS ------------------------------------------------------------------

#define MAX_STRING_SIZE 4096
#define ASCII_COMMENT (';')
#define CPP_COMMENT ('/')
#define C_COMMENT ('*')
#define ASCII_QUOTE (34)
#define LUMP_SCRIPT 1
#define FILE_ZONE_SCRIPT 2

// TYPES -------------------------------------------------------------------

// EXTERNAL FUNCTION PROTOTYPES --------------------------------------------

// PUBLIC FUNCTION PROTOTYPES ----------------------------------------------

// PRIVATE FUNCTION PROTOTYPES ---------------------------------------------

static void SC_PrepareScript ();
static void CheckOpen ();

// EXTERNAL DATA DECLARATIONS ----------------------------------------------

// PUBLIC DATA DEFINITIONS -------------------------------------------------

char *sc_String;
int sc_StringLen;
int sc_Number;
double sc_Float;
int sc_Line;
bool sc_End;
bool sc_Crossed;
bool sc_StringQuoted;
bool sc_FileScripts = false;
//FILE *sc_Out;

// PRIVATE DATA DEFINITIONS ------------------------------------------------

static char *ScriptBuffer;
static char *ScriptPtr;
static char *ScriptEndPtr;
static char StringBuffer[MAX_STRING_SIZE];
static bool ScriptOpen = false;
static int ScriptSize;
static bool AlreadyGot = false;
static char *SavedScriptPtr;
static int SavedScriptLine;
static bool CMode;

// CODE --------------------------------------------------------------------

//==========================================================================
//
// SC_OpenFile
//
// Loads a script 
//
//==========================================================================

void SC_OpenMem (const char *name, char *buffer, int len)
{
	SC_Close ();
	ScriptSize = len;
	ScriptBuffer = buffer;
	SC_PrepareScript ();
}

//==========================================================================
//
// SC_PrepareScript
//
// Prepares a script for parsing.
//
//==========================================================================

static void SC_PrepareScript ()
{
	ScriptPtr = ScriptBuffer;
	ScriptEndPtr = ScriptPtr + ScriptSize;
	sc_Line = 1;
	sc_End = false;
	ScriptOpen = true;
	sc_String = StringBuffer;
	AlreadyGot = false;
	SavedScriptPtr = nullptr;
	CMode = false;
}

//==========================================================================
//
// SC_Close
//
//==========================================================================

void SC_Close ()
{
	if (ScriptOpen)
	{
		ScriptBuffer = nullptr;
		ScriptOpen = false;
	}
}

//==========================================================================
//
// SC_SavePos
//
// Saves the current script location for restoration later
//
//==========================================================================

void SC_SavePos ()
{
	CheckOpen ();
	if (sc_End)
	{
		SavedScriptPtr = nullptr;
	}
	else
	{
		SavedScriptPtr = ScriptPtr;
		SavedScriptLine = sc_Line;
	}
}

//==========================================================================
//
// SC_RestorePos
//
// Restores the previously saved script location
//
//==========================================================================

void SC_RestorePos ()
{
	if (SavedScriptPtr)
	{
		ScriptPtr = SavedScriptPtr;
		sc_Line = SavedScriptLine;
		sc_End = false;
		AlreadyGot = false;
	}
}

//==========================================================================
//
// SC_SetCMode
//
// Enables/disables C mode. In C mode, more characters are considered to
// be whole words than in non-C mode.
//
//==========================================================================

void SC_SetCMode (bool cmode)
{
	CMode = cmode;
}

//==========================================================================
//
// SC_GetString
//
//==========================================================================

bool SC_GetString ()
{
	char *text;
	bool foundToken;

	CheckOpen();
	if (AlreadyGot)
	{
		AlreadyGot = false;
		return true;
	}
	foundToken = false;
	sc_Crossed = false;
	sc_StringQuoted = false;
	if (ScriptPtr >= ScriptEndPtr)
	{
		sc_End = true;
		return false;
	}
	while (foundToken == false)
	{
		while (ScriptPtr < ScriptEndPtr && *ScriptPtr <= ' ')
		{
			if (*ScriptPtr++ == '\n')
			{
				sc_Line++;
				sc_Crossed = true;
			}
		}
		if (ScriptPtr >= ScriptEndPtr)
		{
			sc_End = true;
			return false;
		}
		if ((CMode || *ScriptPtr != ASCII_COMMENT) &&
			!(ScriptPtr[0] == CPP_COMMENT && ScriptPtr < ScriptEndPtr - 1 &&
			  (ScriptPtr[1] == CPP_COMMENT || ScriptPtr[1] == C_COMMENT)))
		{ // Found a token
			foundToken = true;
		}
		else
		{ // Skip comment
			if (ScriptPtr[0] == CPP_COMMENT && ScriptPtr[1] == C_COMMENT)
			{	// C comment
				while (ScriptPtr[0] != C_COMMENT || ScriptPtr[1] != CPP_COMMENT)
				{
					if (ScriptPtr[0] == '\n')
					{
						sc_Line++;
						sc_Crossed = true;
					}
//					fputc(ScriptPtr[0], sc_Out);
					ScriptPtr++;
					if (ScriptPtr >= ScriptEndPtr - 1)
					{
						sc_End = true;
						return false;
					}
//					fputs("*/", sc_Out);
				}
				ScriptPtr += 2;
			}
			else
			{	// C++ comment
				while (*ScriptPtr++ != '\n')
				{
//					fputc(ScriptPtr[-1], sc_Out);
					if (ScriptPtr >= ScriptEndPtr)
					{
						sc_End = true;
						return false;
					}
				}
				sc_Line++;
				sc_Crossed = true;
//				fputc('\n', sc_Out);
			}
		}
	}
	text = sc_String;
	if (*ScriptPtr == ASCII_QUOTE)
	{ // Quoted string - return string including the quotes
		*text++ = *ScriptPtr++;
		sc_StringQuoted = true;
		while (*ScriptPtr != ASCII_QUOTE)
		{
			if (*ScriptPtr >= 0 && *ScriptPtr < ' ')
			{
				ScriptPtr++;
			}
			else if (*ScriptPtr == '\\')
			{
				// Add the backslash character and the following chararcter to the text.
				// We do not translate the escape sequence in any way, since the only
				// thing that will happen to this string is that it will be written back
				// out to disk. Basically, we just need this special case here so that
				// string reading won't terminate prematurely when a \" sequence is
				// used to embed a quote mark in the string.
				*text++ = *ScriptPtr++;
				*text++ = *ScriptPtr++;
			}
			else
			{
				*text++ = *ScriptPtr++;
			}
			if (ScriptPtr == ScriptEndPtr
				|| text == &sc_String[MAX_STRING_SIZE-1])
			{
				break;
			}
		}
		*text++ = '"';
		ScriptPtr++;
	}
	else
	{ // Normal string
		static const char *stopchars;

		if (CMode)
		{
			stopchars = "`~!@#$%^&*(){}[]/=\?+|;:<>,";

			// '-' can be its own token, or it can be part of a negative number
			if (*ScriptPtr == '-')
			{
				*text++ = '-';
				ScriptPtr++;
				if (ScriptPtr < ScriptEndPtr || (*ScriptPtr >= '0' && *ScriptPtr <= '9'))
				{
					goto grabtoken;
				}
				goto gottoken;
			}
		}
		else
		{
			stopchars = "{}|=";
		}
		if (strchr (stopchars, *ScriptPtr))
		{
			*text++ = *ScriptPtr++;
		}
		else
		{
grabtoken:
			while ((*ScriptPtr > ' ') && (strchr (stopchars, *ScriptPtr) == nullptr)
				&& (CMode || *ScriptPtr != ASCII_COMMENT)
				&& !(ScriptPtr[0] == CPP_COMMENT && (ScriptPtr < ScriptEndPtr - 1) &&
					 (ScriptPtr[1] == CPP_COMMENT || ScriptPtr[1] == C_COMMENT)))
			{
				*text++ = *ScriptPtr++;
				if (ScriptPtr == ScriptEndPtr
					|| text == &sc_String[MAX_STRING_SIZE-1])
				{
					break;
				}
			}
		}
	}
gottoken:
	*text = 0;
	sc_StringLen = int(text - sc_String);
	return true;
}

//==========================================================================
//
// SC_MustGetString
//
//==========================================================================

void SC_MustGetString ()
{
	if (SC_GetString () == false)
	{
		SC_ScriptError ("Missing string (unexpected end of file).");
	}
}

//==========================================================================
//
// SC_MustGetStringName
//
//==========================================================================

void SC_MustGetStringName (const char *name)
{
	SC_MustGetString ();
	if (SC_Compare (name) == false)
	{
		SC_ScriptError ("Expected '%s', got '%s'.", name, sc_String);
	}
}

//==========================================================================
//
// SC_CheckString
//
// Checks if the next token matches the specified string. Returns true if
// it does. If it doesn't, it ungets it and returns false.
//==========================================================================

bool SC_CheckString (const char *name)
{
	if (SC_GetString ())
	{
		if (SC_Compare (name))
		{
			return true;
		}
		SC_UnGet ();
	}
	return false;
}

//==========================================================================
//
// SC_GetNumber
//
//==========================================================================

bool SC_GetNumber ()
{
	char *stopper;

	CheckOpen ();
	if (SC_GetString())
	{
		if (strcmp (sc_String, "MAXINT") == 0)
		{
			sc_Number = INT_MAX;
		}
		else
		{
			sc_Number = strtol (sc_String, &stopper, 0);
			if (*stopper != 0)
			{
				SC_ScriptError ("SC_GetNumber: Bad numeric constant \"%s\".", sc_String);
			}
		}
		sc_Float = sc_Number;
		return true;
	}
	else
	{
		return false;
	}
}

//==========================================================================
//
// SC_MustGetNumber
//
//==========================================================================

void SC_MustGetNumber ()
{
	if (SC_GetNumber() == false)
	{
		SC_ScriptError ("Missing integer (unexpected end of file).");
	}
}

//==========================================================================
//
// SC_CheckNumber
// similar to SC_GetNumber but ungets the token if it isn't a number 
// and does not print an error
//
//==========================================================================

bool SC_CheckNumber ()
{
	char *stopper;

	//CheckOpen ();
	if (SC_GetString())
	{
		if (strcmp (sc_String, "MAXINT") == 0)
		{
			sc_Number = INT_MAX;
		}
		else
		{
			sc_Number = strtol (sc_String, &stopper, 0);
			if (*stopper != 0)
			{
				SC_UnGet();
				return false;
			}
		}
		sc_Float = sc_Number;
		return true;
	}
	else
	{
		return false;
	}
}

//==========================================================================
//
// SC_CheckFloat
// [GRB] Same as SC_CheckNumber, only for floats
//
//==========================================================================

bool SC_CheckFloat ()
{
	char *stopper;

	//CheckOpen ();
	if (SC_GetString())
	{
		sc_Float = strtod (sc_String, &stopper);
		sc_Number = (int)sc_Float;
		if (*stopper != 0)
		{
			SC_UnGet();
			return false;
		}
		return true;
	}
	else
	{
		return false;
	}
}


//==========================================================================
//
// SC_GetFloat
//
//==========================================================================

bool SC_GetFloat ()
{
	char *stopper;

	CheckOpen ();
	if (SC_GetString())
	{
		sc_Float = strtod (sc_String, &stopper);
		if (*stopper != 0)
		{
			SC_ScriptError("SC_GetFloat: Bad numeric constant \"%s\".\n",sc_String);
		}
		sc_Number = (int)sc_Float;
		return true;
	}
	else
	{
		return false;
	}
}

//==========================================================================
//
// SC_MustGetFloat
//
//==========================================================================

void SC_MustGetFloat ()
{
	if (SC_GetFloat() == false)
	{
		SC_ScriptError ("Missing floating-point number (unexpected end of file).");
	}
}

//==========================================================================
//
// SC_UnGet
//
// Assumes there is a valid string in sc_String.
//
//==========================================================================

void SC_UnGet ()
{
	AlreadyGot = true;
}

//==========================================================================
//
// SC_Check
//
// Returns true if another token is on the current line.
//
//==========================================================================

/*
bool SC_Check()
{
	char *text;

	CheckOpen();
	text = ScriptPtr;
	if(text >= ScriptEndPtr)
	{
		return false;
	}
	while(*text <= 32)
	{
		if(*text == '\n')
		{
			return false;
		}
		text++;
		if(text == ScriptEndPtr)
		{
			return false;
		}
	}
	if(*text == ASCII_COMMENT)
	{
		return false;
	}
	return true;
}
*/

//==========================================================================
//
// SC_MatchString
//
// Returns the index of the first match to sc_String from the passed
// array of strings, or -1 if not found.
//
//==========================================================================

int SC_MatchString (const char **strings)
{
	int i;

	for (i = 0; *strings != nullptr; i++)
	{
		if (SC_Compare (*strings++))
		{
			return i;
		}
	}
	return -1;
}

//==========================================================================
//
// SC_MustMatchString
//
//==========================================================================

int SC_MustMatchString (const char **strings)
{
	int i;

	i = SC_MatchString (strings);
	if (i == -1)
	{
		SC_ScriptError (nullptr);
	}
	return i;
}

//==========================================================================
//
// SC_Compare
//
//==========================================================================

bool SC_Compare (const char *text)
{
#ifdef _MSC_VER
	return (_stricmp (text, sc_String) == 0);
#else
	return (strcasecmp (text, sc_String) == 0);
#endif
}

//==========================================================================
//
// SC_ScriptError
//
//==========================================================================

void SC_ScriptError (const char *message, ...)
{
	char composed[2048];
	if (message == nullptr)
	{
		message = "Bad syntax.";
	}

	va_list arglist;
	va_start (arglist, message);
	vsprintf (composed, message, arglist);
	va_end (arglist);

	printf ("Script error, line %d:\n%s\n", sc_Line, composed);
	exit(1);
}

//==========================================================================
//
// CheckOpen
//
//==========================================================================

static void CheckOpen()
{
	if (ScriptOpen == false)
	{
		printf ("SC_ call before SC_Open().\n");
		exit(1);
	}
}
