
//**************************************************************************
//**
//** sc_man.c : Heretic 2 : Raven Software, Corp.
//**
//** $RCSfile: sc_man.c,v $
//** $Revision: 1.3 $
//** $Date: 96/01/06 03:23:43 $
//** $Author: bgokey $
//**
//**************************************************************************

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

#include <string.h>
#include <stdlib.h>
#include "doomtype.h"
#include "i_system.h"
#include "sc_man.h"
#include "w_wad.h"
#include "cmdlib.h"
#include "m_misc.h"
#include "tarray.h"

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

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

#define NORMAL_STOPCHARS			"{}|="
#define CMODE_STOPCHARS				"`~!@#$%^&*(){}[]/=\?+|;:<>,."
#define CMODE_STOPCHARS_NODECIMAL	"`~!@#$%^&*(){}[]/=\?+|;:<>,"

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

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

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

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

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

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

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

char *sc_String;
int sc_StringLen;
int sc_Number;
float sc_Float;
int sc_Line;
BOOL sc_End;
BOOL sc_Crossed;
BOOL sc_FileScripts = false;
char *sc_ScriptsDir = "";

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

static char ScriptName[128];
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 bool FreeScript = false;
static char *SavedScriptPtr;
static int SavedScriptLine;
static bool CMode;

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

//==========================================================================
//
// SC_Open
//
//==========================================================================

void SC_Open (const char *name)
{
	SC_OpenLumpNum (Wads.GetNumForName (name), name);
}

//==========================================================================
//
// SC_OpenFile
//
// Loads a script (from a file). Uses the new/delete memory allocator for
// memory allocation and de-allocation.
//
//==========================================================================

void SC_OpenFile (const char *name)
{
	SC_Close ();
	ScriptSize = M_ReadFile (name, (byte **)&ScriptBuffer);
	ExtractFileBase (name, ScriptName);
	FreeScript = true;
	SC_PrepareScript ();
}

//==========================================================================
//
// SC_OpenMem
//
// Prepares a script that is already in memory for parsing. The caller is
// responsible for freeing it, if needed.
//
//==========================================================================

void SC_OpenMem (const char *name, char *buffer, int size)
{
	SC_Close ();
	ScriptSize = size;
	ScriptBuffer = buffer;
	strcpy (ScriptName, name);
	FreeScript = false;
	SC_PrepareScript ();
}

//==========================================================================
//
// SC_OpenLumpNum
//
// Loads a script (from the WAD files).
//
//==========================================================================

void SC_OpenLumpNum (int lump, const char *name)
{
	SC_Close ();
	ScriptSize = Wads.LumpLength (lump);
	ScriptBuffer = new char[ScriptSize];
	Wads.ReadLump (lump, ScriptBuffer);
	strcpy (ScriptName, name);
	FreeScript = true;
	SC_PrepareScript ();
}

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

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

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

void SC_Close (void)
{
	if (ScriptOpen)
	{
		if (ScriptBuffer)
		{
			if (FreeScript)
			{
				delete[] ScriptBuffer;
			}
		}
		ScriptBuffer = NULL;
		ScriptOpen = false;
	}
}

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

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

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

void SC_RestorePos (void)
{
	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 (bool noescape)
{
	char *text;
	BOOL foundToken;

	CheckOpen();
	if (AlreadyGot)
	{
		AlreadyGot = false;
		return true;
	}
	foundToken = false;
	sc_Crossed = 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;
					}
					ScriptPtr++;
					if (ScriptPtr >= ScriptEndPtr - 1)
					{
						sc_End = true;
						return false;
					}
				}
				ScriptPtr += 2;
			}
			else
			{	// C++ comment
				while (*ScriptPtr++ != '\n')
				{
					if (ScriptPtr >= ScriptEndPtr)
					{
						sc_End = true;
						return false;
					}
				}
				sc_Line++;
				sc_Crossed = true;
			}
		}
	}
	text = sc_String;
	if (*ScriptPtr == ASCII_QUOTE)
	{ // Quoted string
		ScriptPtr++;
		while (*ScriptPtr != ASCII_QUOTE)
		{
			// Hack alert: Do not allow escaped quotation marks when parsing DECORATE!
			if (*ScriptPtr=='\\' && ScriptPtr[1]=='"' && !noescape)
			{
				*text++ = '"';
				ScriptPtr+=2;
			}
			else
			{
				*text++ = *ScriptPtr++;
			}
			if (ScriptPtr == ScriptEndPtr
				|| text == &sc_String[MAX_STRING_SIZE-1])
			{
				break;
			}
		}
		ScriptPtr++;
	}
	else
	{ // Normal string
		static const char *stopchars;

		if (CMode)
		{
			stopchars = 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')
				{
					stopchars = CMODE_STOPCHARS_NODECIMAL;
					goto grabtoken;
				}
				goto gottoken;
			}
			else if (*ScriptPtr >= '0' && *ScriptPtr <= '9')
			{
				stopchars = CMODE_STOPCHARS_NODECIMAL;
			}
			else if (*ScriptPtr == '.' && ScriptPtr[1] >= '0' && ScriptPtr[1] <= '9')
			{
				stopchars = CMODE_STOPCHARS_NODECIMAL;
			}
		}
		else
		{
			stopchars = NORMAL_STOPCHARS;
		}
		if (strchr (stopchars, *ScriptPtr))
		{
			*text++ = *ScriptPtr++;
			// [GRB] Allow 2-char operators
			if (CMode && strchr ("&=|<>", *ScriptPtr))
				*text++ = *ScriptPtr++;
		}
		else
		{
grabtoken:
			while ((*ScriptPtr > ' ') && (strchr (stopchars, *ScriptPtr) == NULL)
				&& (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 = text - sc_String;
	return true;
}

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

void SC_MustGetString (void)
{
	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 (void)
{
	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 = (float)sc_Number;
		return true;
	}
	else
	{
		return false;
	}
}

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

void SC_MustGetNumber (void)
{
	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 (void)
{
	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 = (float)sc_Number;
		return true;
	}
	else
	{
		return false;
	}
}

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

BOOL SC_GetFloat (void)
{
	char *stopper;

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

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

void SC_MustGetFloat (void)
{
	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 (void)
{
	AlreadyGot = true;
}

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

/*
BOOL SC_Check(void)
{
	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 != NULL; 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 (NULL);
	}
	return i;
}

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

BOOL SC_Compare (const char *text)
{
	return (stricmp (text, sc_String) == 0);
}

//==========================================================================
//
// SC_GetOneChar
//
//==========================================================================
int SC_GetOneChar()
{
	if (*ScriptPtr == '\n')
	{
		sc_Line++;
		sc_Crossed = true;
	}
	if (ScriptPtr<ScriptEndPtr) return *(unsigned char *)ScriptPtr++;
	else return -1;
}

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

void STACK_ARGS SC_ScriptError (const char *message, ...)
{
	string composed;

	if (message == NULL)
	{
		composed = "Bad syntax.";
	}
	else
	{
		va_list arglist;
		va_start (arglist, message);
		composed.VFormat (message, arglist);
		va_end (arglist);
	}

	I_Error ("Script error, \"%s\" line %d:\n%s\n", ScriptName,
		sc_Line, composed.GetChars());
}

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

static void CheckOpen(void)
{
	if (ScriptOpen == false)
	{
		I_FatalError ("SC_ call before SC_Open().");
	}
}


//==========================================================================
//
// Script state saving
// This allows recursive script execution
// This does not save the last token!
//
//==========================================================================

struct SavedScript
{
	int sc_Line;
	BOOL sc_End;
	BOOL sc_Crossed;
	BOOL sc_FileScripts;

	char ScriptName[128];
	char *ScriptBuffer;
	char *ScriptPtr;
	char *ScriptEndPtr;
	bool ScriptOpen;
	int ScriptSize;
	bool FreeScript;
	char *SavedScriptPtr;
	int SavedScriptLine;
	bool CMode;
};


static TArray<SavedScript> SavedScripts;

void SC_SaveScriptState()
{
	SavedScript ss;

	ss.sc_Line=sc_Line;
	ss.sc_End=sc_End;
	ss.sc_Crossed=sc_Crossed;
	ss.sc_FileScripts=sc_FileScripts;
	memcpy(ss.ScriptName, ScriptName, 128);
	ss.ScriptBuffer=ScriptBuffer;
	ss.ScriptPtr=ScriptPtr;
	ss.ScriptEndPtr=ScriptEndPtr;
	ss.ScriptOpen=ScriptOpen;
	ss.ScriptSize=ScriptSize;
	ss.FreeScript=FreeScript;
	ss.SavedScriptPtr=SavedScriptPtr;
	ss.SavedScriptLine=SavedScriptLine;
	ss.CMode=CMode;
	SavedScripts.Push(ss);
}

void SC_RestoreScriptState()
{
	if (SavedScripts.Size()>0)
	{
		SavedScript ss;

		SavedScripts.Pop(ss);
		sc_Line = ss.sc_Line;
		sc_End = ss.sc_End;
		sc_Crossed = ss.sc_Crossed;
		sc_FileScripts = ss.sc_FileScripts;
		memcpy(ScriptName, ss.ScriptName, 128);
		ScriptBuffer = ss.ScriptBuffer;
		ScriptPtr = ss.ScriptPtr;
		ScriptEndPtr = ss.ScriptEndPtr;
		ScriptOpen = ss.ScriptOpen;
		ScriptSize = ss.ScriptSize;
		FreeScript = ss.FreeScript;
		SavedScriptPtr = ss.SavedScriptPtr;
		SavedScriptLine = ss.SavedScriptLine;
		CMode = ss.CMode;
		SavedScripts.ShrinkToFit();
	}
}