// --------------------------------------------------------------------------
// Citadel: ScriptLd.CPP
//
// The script loader.

#include "ctdl.h"
#pragma hdrstop

#include "script.h"
#include "scrfarry.h"

#include "msg.h"
#include "log.h"

// --------------------------------------------------------------------------
// Contents
//
// runScript()	runs a script file


static VariableTypeE parseType(const char *type);
static void dumpScript(void);
static int runCounter;

// this next stuff is for starting up a script
extern const char *resultStr;

static struct poo
	{
	char *foo;
	char doo[6];
	} xxxcall = {NULL, "_MAIN"};        // Moo!

static scriptCommandRecord xxxcmd2 =
		{NULL,		0, CMD_EXIT, NULL, 0, 0, (strList *) resultStr};
static scriptCommandRecord xxxcmd1 =
		{&xxxcmd2,	0, CMD_CALL, NULL, 0, 0, (strList *) &xxxcall};

DEBUGCODE(static Bool tableChecked;)


// Script.cpp
void executeCurrentCommand(void);


static Bool loadScript(void)
	{
	VerifyHeap();

	FILE *scr;

	if ((scr = fopen(CurScript->Name, FO_R)) != NULL)	// ASCII mode
		{
		Bool noMemory = FALSE;
		scriptFunctionList *theFunc = NULL;

		int linenum = 0;
		char ScriptFileLine[128], *ScriptFileLineStart;

		while (fgets(ScriptFileLine, 128, scr) && !noMemory &&
				!CurScript->Error)
			{
			linenum++;
			ScriptFileLineStart = ScriptFileLine;

			while (*ScriptFileLineStart &&
					(*ScriptFileLineStart < 33 && *ScriptFileLineStart > 0))
				{
				ScriptFileLineStart++;
				}

			if (*ScriptFileLineStart != '#')
				{
				continue;
				}

			char *words[128];
			const int count = parse_it(words, ScriptFileLineStart);

			for (int i = 0; i < S_NUM; i++)
				{
				if (SameString(words[0] + 1, ((char **) ScriptDData->aux)[i]))
					{
					break;
					}
				}

			switch (i)
				{
				case S_VERSION:
					{
					if (count > 1)
						{
						const long Needed = atol(words[1]);

						if (Needed > NumericVer)
							{
							mPrintf(getscrmsg(17), Needed / 1000,
									Needed % 1000);
							CurScript->Error = TRUE;
							}
						}
					else
						{
						mPrintf(getscrmsg(1), CurScript->Name, linenum,
								words[0]);
						CurScript->Error = TRUE;
						}

					break;
					}

				case S_GVAR:
					{
					if (count > 2 && words[2][0] == '_')
						{
						scriptVariables *theVar = (scriptVariables *)
								addLL((void **) &CurScript->gvBase,
								sizeof(*theVar));

						if (theVar)
							{
							theVar->type = parseType(words[1]);
							CopyStringToBuffer(theVar->name, words[2],
									LABELSIZE);

							if (theVar->type >= TYPE_STRING_MIN &&
									theVar->type <= TYPE_STRING_MAX)
								{
								theVar->v.string = new char[theVar->type -
										TYPE_STRING_MIN + 2];

								if (theVar->v.string)
									{
									*theVar->v.string = 0;
									}
								else
									{
									noMemory = TRUE;
									}
								}
							}
						else
							{
							noMemory = TRUE;
							}
						}
					else
						{
						mPrintf(getscrmsg(1), CurScript->Name, linenum, words[0]);
						CurScript->Error = TRUE;
						}

					break;
					}

				case S_LVAR:
					{
					if (theFunc)
						{
						if (count > 2 && words[2][0] == '_')
							{
							scriptVariables *theVar = (scriptVariables *)
									addLL((void **) &(theFunc->vars),
									sizeof(*(theFunc->vars)));

							if (theVar)
								{
								theVar->type = parseType(words[1]);
								CopyStringToBuffer(theVar->name, words[2],
										LABELSIZE);

								if (theVar->type >= TYPE_STRING_MIN &&
										theVar->type <= TYPE_STRING_MAX)
									{
									theVar->v.string = new char[theVar->type -
											TYPE_STRING_MIN + 2];

									if (theVar->v.string)
										{
										*theVar->v.string = 0;
										}
									else
										{
										noMemory = TRUE;
										}
									}
								}
							else
								{
								noMemory = TRUE;
								}
							}
						else
							{
							mPrintf(getscrmsg(1), CurScript->Name, linenum, words[0]);
							CurScript->Error = TRUE;
							}
						}
					else
						{
						mPrintf(getscrmsg(2), CurScript->Name, linenum, words[0]);
						CurScript->Error = TRUE;
						}

					break;
					}

				case S_FUNC:
					{
					if (count > 2 && words[1][0] == '_')
						{
						theFunc = (scriptFunctionList *) addLL((void **)
								&CurScript->sfBase, sizeof(*theFunc));

						if (theFunc)
							{
							theFunc->rett = parseType(words[2]);

							CopyStringToBuffer(theFunc->name, words[1],
									LABELSIZE);

							for (int j = 0; j < MAXARGS; j++)
								{
								if (count > j * 2 + 4)
									{
									theFunc->argt[j] =
											parseType(words[j * 2 + 3]);

									CopyStringToBuffer(theFunc->argn[j],
											words[j * 2 + 4], LABELSIZE);

									// make the variable
									scriptVariables *theVar = new scriptVariables;
									theVar->next = NULL;

									theFunc->argv[j] = theVar;

									if (theVar)
										{
										theVar->type = theFunc->argt[j] & ~BY_REF;

										CopyStringToBuffer(theVar->name,
												theFunc->argn[j],
												LABELSIZE);

										if (theVar->type >= TYPE_STRING_MIN &&
												theVar->type <= TYPE_STRING_MAX)
											{
											theVar->v.string = new
													char[theVar->type -
													TYPE_STRING_MIN + 2];

											if (theVar->v.string)
												{
												*theVar->v.string = 0;
												}
											else
												{
												noMemory = TRUE;
												}
											}
										}
									else
										{
										noMemory = TRUE;
										}
									}
								}
							}
						else
							{
							noMemory = TRUE;
							}
						}
					else
						{
						mPrintf(getscrmsg(1), CurScript->Name, linenum, words[0]);
						CurScript->Error = TRUE;
						}

					break;
					}

				case S_ENDFUNC:
					{
					theFunc = NULL;
					break;
					}

				case S_ADD:
				case S_SUB:
				case S_MUL:
				case S_DIV:
				case S_MOD:
				case S_CALL:
				case S_AND:
				case S_OR:
				case S_XOR:
					{
					if (theFunc)
						{
						if (count > 2 || (i == S_CALL && count == 2))
							{
							scriptCommandRecord *theCmd =
									(scriptCommandRecord *) addLL((void **)
									&(theFunc->cmds),
									sizeof(*(theFunc->cmds)));

							if (theCmd)
								{
								switch (i)
									{
									case S_ADD:
										{
										theCmd->type = CMD_ADD;
										break;
										}

									case S_SUB:
										{
										theCmd->type = CMD_SUB;
										break;
										}

									case S_MUL:
										{
										theCmd->type = CMD_MUL;
										break;
										}

									case S_DIV:
										{
										theCmd->type = CMD_DIV;
										break;
										}

									case S_MOD:
										{
										theCmd->type = CMD_MOD;
										break;
										}

									case S_CALL:
										{
										theCmd->type = CMD_CALL;
										break;
										}

									case S_AND:
										{
										theCmd->type = CMD_AND;
										break;
										}

									case S_OR:
										{
										theCmd->type = CMD_OR;
										break;
										}

									case S_XOR:
										{
										theCmd->type = CMD_XOR;
										break;
										}
									}

								theCmd->line = linenum;

								for (int j = 1; j < count && !noMemory; j++)
									{
									strList *theSL = (strList *)
											addLL((void **) &(theCmd->c.sl),
											sizeof(*(theCmd->c.sl)) +
											strlen(words[j]));

									if (theSL)
										{
										strcpy(theSL->string, words[j]);
										}
									else
										{
										noMemory = TRUE;
										}
									}
								}
							else
								{
								noMemory = TRUE;
								}
							}
						else
							{
							mPrintf(getscrmsg(1), CurScript->Name, linenum, words[0]);
							CurScript->Error = TRUE;
							}
						}
					else
						{
						mPrintf(getscrmsg(1), CurScript->Name, linenum, words[0]);
						CurScript->Error = TRUE;
						}

					break;
					}

				case S_ASGN:
				case S_EQ:
				case S_GT:
				case S_LT:
				case S_NE:
				case S_GE:
				case S_LE:
					{
					if (theFunc)
						{
						if (count == 3 && (i != S_ASGN || words[1][0] == '_'))
							{
							scriptCommandRecord *theCmd =
									(scriptCommandRecord *) addLL((void **)
									&(theFunc->cmds),
									sizeof(*(theFunc->cmds)));

							if (theCmd)
								{
								if (i == S_ASGN)
									{
									theCmd->type = CMD_ASGN;
									}
								else if (i == S_EQ)
									{
									theCmd->type = CMD_EQ;
									}
								else if (i == S_GT)
									{
									theCmd->type = CMD_GT;
									}
								else if (i == S_LT)
									{
									theCmd->type = CMD_LT;
									}
								else if (i == S_NE)
									{
									theCmd->type = CMD_NE;
									}
								else if (i == S_GE)
									{
									theCmd->type = CMD_GE;
									}
								else
									{
									theCmd->type = CMD_LE;
									}

								theCmd->line = linenum;

								for (int j = 1; j < count && !noMemory; j++)
									{
									strList *theSL = (strList *)
											addLL((void **) &(theCmd->c.sl),
											sizeof(*(theCmd->c.sl)) +
											strlen(words[j]));

									if (theSL)
										{
										strcpy(theSL->string, words[j]);
										}
									else
										{
										noMemory = TRUE;
										}
									}
								}
							else
								{
								noMemory = TRUE;
								}
							}
						else
							{
							mPrintf(getscrmsg(1), CurScript->Name, linenum, words[0]);
							CurScript->Error = TRUE;
							}
						}
					else
						{
						mPrintf(getscrmsg(2), CurScript->Name, linenum, words[0]);
						CurScript->Error = TRUE;
						}

					break;
					}

				case S_STR:
					{
					if (theFunc)
						{
						if (count > 2)
							{
							scriptCommandRecord *theCmd =
									(scriptCommandRecord *) addLL((void **)
									&(theFunc->cmds),
									sizeof(*(theFunc->cmds)));

							if (theCmd)
								{
								switch (toupper(words[1][0]))
									{
									case 'B':
										{
										theCmd->type = CMD_STRB;
										break;
										}

									case 'U':
										{
										theCmd->type = CMD_STRU;
										break;
										}

									case 'L':
										{
										theCmd->type = CMD_STRL;
										break;
										}

									case 'C':
										{
										theCmd->type = CMD_STRC;
										break;
										}

									case 'S':
										{
										theCmd->type = CMD_STRS;
										break;
										}

									case 'E':
										{
										theCmd->type = CMD_STRE;
										break;
										}

									case 'P':
										{
										if (toupper(words[1][1]) == 'R')
											{
											theCmd->type = CMD_STRPR;
											break;
											}
										else if (toupper(words[1][1]) == 'L')
											{
											theCmd->type = CMD_STRPL;
											break;
											}
										}

									default:
										{
										mPrintf(getscrmsg(1), CurScript->Name, linenum, words[0]);
										CurScript->Error = TRUE;
										break;
										}
									}

								if (!CurScript->Error)
									{
									theCmd->line = linenum;

									for (int j = 2; j < count && !noMemory; j++)
										{
										strList *theSL = (strList *)
												addLL((void **)
												&(theCmd->c.sl),
												sizeof(*(theCmd->c.sl)) +
												strlen(words[j]));

										if (theSL)
											{
											strcpy(theSL->string, words[j]);
											}
										else
											{
											noMemory = TRUE;
											}
										}
									}
								}
							else
								{
								noMemory = TRUE;
								}
							}
						else
							{
							mPrintf(getscrmsg(1), CurScript->Name, linenum, words[0]);
							CurScript->Error = TRUE;
							}
						}
					else
						{
						mPrintf(getscrmsg(2), CurScript->Name, linenum, words[0]);
						CurScript->Error = TRUE;
						}

					break;
					}

				case S_WHILE:
				case S_WHILENOT:
				case S_LOOP:
				case S_LOOPNOT:
				case S_IF:
				case S_IFNOT:
				case S_GOTO:
				case S_LABEL:
				case S_RET:
				case S_EXIT:
				case S_RUN:
				case S_CHAIN:
					{
					if (theFunc)
						{
						if (count == 2)
							{
							scriptCommandRecord *theCmd =
									(scriptCommandRecord *) addLL((void **)
									&(theFunc->cmds),
									sizeof(*(theFunc->cmds)));

							if (theCmd)
								{
								if (i == S_WHILE)
									{
									theCmd->type = CMD_WHILE;
									}
								else if (i == S_WHILENOT)
									{
									theCmd->type = CMD_WHILENOT;
									}
								else if (i == S_LOOP)
									{
									theCmd->type = CMD_LOOP;
									}
								else if (i == S_LOOPNOT)
									{
									theCmd->type = CMD_LOOPNOT;
									}
								else if (i == S_IF)
									{
									theCmd->type = CMD_IF;
									}
								else if (i == S_IFNOT)
									{
									theCmd->type = CMD_IFNOT;
									}
								else if (i == S_GOTO)
									{
									theCmd->type = CMD_GOTO;
									}
								else if (i == S_RET)
									{
									theCmd->type = CMD_RET;
									}
								else if (i == S_EXIT)
									{
									theCmd->type = CMD_EXIT;
									}
								else if (i == S_RUN)
									{
									theCmd->type = CMD_RUN;
									}
								else if (i == S_CHAIN)
									{
									theCmd->type = CMD_CHAIN;
									}
								else
									{
									theCmd->type = CMD_LABEL;
									}

								theCmd->line = linenum;

								theCmd->c.str = strdup(words[1]);

								if (!theCmd->c.str)
									{
									noMemory = TRUE;
									}
								}
							else
								{
								noMemory = TRUE;
								}
							}
						else
							{
							mPrintf(getscrmsg(1), CurScript->Name, linenum, words[0]);
							CurScript->Error = TRUE;
							}
						}
					else
						{
						mPrintf(getscrmsg(2), CurScript->Name, linenum, words[0]);
						CurScript->Error = TRUE;
						}

					break;
					}

				case S_WEND:
				case S_DO:
				case S_ELSE:
				case S_ENDIF:
					{
					if (theFunc)
						{
						if (count == 1)
							{
							scriptCommandRecord *theCmd = (scriptCommandRecord *) addLL((void **)
									&(theFunc->cmds),
									sizeof(*(theFunc->cmds)));

							if (theCmd)
								{
								if (i == S_WEND)
									{
									theCmd->type = CMD_WEND;
									}
								else if (i == S_DO)
									{
									theCmd->type = CMD_DO;
									}
								else if (i == S_ELSE)
									{
									theCmd->type = CMD_ELSE;
									}
								else
									{
									theCmd->type = CMD_ENDIF;
									}


								theCmd->line = linenum;
								}
							else
								{
								noMemory = TRUE;
								}
							}
						else
							{
							mPrintf(getscrmsg(1), CurScript->Name, linenum, words[0]);
							CurScript->Error = TRUE;
							}
						}
					else
						{
						mPrintf(getscrmsg(2), CurScript->Name, linenum, words[0]);
						CurScript->Error = TRUE;
						}

					break;
					}

				default:
					{
					mPrintfCR(getscrmsg(3), CurScript->Name, words[0]);
					break;
					}
				}
			}

		fclose(scr);

		if (noMemory || CurScript->Error)
			{
			return (FALSE);
			}

		return (TRUE);
		}
	else
		{
		return (FALSE);
		}
	}

Bool runScript(const char *what, void (*InitScript)(ScriptInfoS *si))
	{
	VerifyHeap();

	ScriptInfoS *SaveScript = CurScript;
	CurScript = new ScriptInfoS;

	if (!CurScript)
		{
		mPrintf(getmsg(188), getmsg(1360));
		CurScript = SaveScript;
		return (FALSE);
		}

	memset(CurScript, 0, sizeof(*CurScript));

//	CurScript->Error = FALSE;

	runCounter++;

	if (!ScriptDData)
		{
		if (cfg.FastScripts)
			{
			compactMemory(1);
			}

		ScriptDData = readData(2);

		if (!ScriptDData)
			{
			dumpScript();
			mPrintfCR(getmsg(188), getmsg(1360));

			delete CurScript;
			CurScript = SaveScript;

			return (FALSE);
			}
		}

#ifndef NDEBUG
	if (!tableChecked)
		{
		CITWINDOW *w = ScreenSaver.IsOn() ? NULL :
				CitWindowsMsg(NULL, "Checking function names... ");

		const char **arry = (const char **) ScriptDData->next->aux;

		char LastOne[128];
		*LastOne = 0;

		for (int i = 0; internalFunctions[i].func; i++)
			{
			if (*LastOne)
				{
				assert(strcmpi(LastOne, arry[i]) < 0);
				}

			CopyStringToBuffer(LastOne, arry[i], sizeof(LastOne) - 1);

			for (const char *ptr = LastOne; *ptr; ptr++)
				{
				assert(isdigit(*ptr) || isupper(*ptr) || *ptr == '_');
				}
			}

		tableChecked = TRUE;

		if (w)
			{
			destroyCitWindow(w, FALSE);
			}
		}
#endif

	if (strchr(what, '\\') || strchr(what, ':'))
		{
		strcpy(CurScript->Name, what);
		}
	else
		{
		sprintf(CurScript->Name, sbs, cfg.ScriptPath, what);
		}

	if (CurScript->Name[strlen(CurScript->Name) - 1] == '*')
		{
		CurScript->Debug = CurScript->Pause = TRUE;
		CurScript->Name[strlen(CurScript->Name) - 1] = 0;
		}
	else
		{
		CurScript->Debug = CurScript->Pause = FALSE;
		}

	CurScript->SingleStep = FALSE;
	CurScript->oldSystemDebug = debug;

	if (!strchr(CurScript->Name, '.'))
		{
		strcat(CurScript->Name, getscrmsg(30));
		}

	strupr(CurScript->Name);

	compactMemory();

	// load script into memory...
	if (loadScript())
		{
		// then let our custom initialization happen...
		if (InitScript)
			{
			(*InitScript)(CurScript);
			}

		// okay -- now run it
		xxxcmd1.ptr = NULL;
		xxxcmd2.ptr = NULL;

		CurScript->curCmd = &xxxcmd1;

		const Bool HadConnectionToUser = HaveConnectionToUser();

		while (CurScript->curCmd)
			{
			executeCurrentCommand();

			if (CurScript->Error ||
					(HadConnectionToUser && !HaveConnectionToUser()) ||
					TI()ExitToMsdos)
				{
				CurScript->curCmd = NULL;
				CurScript->ToRet = FALSE;
				}
			}

		dumpScript();

		if (CurScript->ReadAplOnExit)
			{
			readAplFile();
			}

		Bool toRet = CurScript->ToRet;

		delete CurScript;
		CurScript = SaveScript;

		return (toRet);
		}
	else
		{
		CRmPrintfCR(getscrmsg(54), CurScript->Name);
		dumpScript();

		delete CurScript;
		CurScript = SaveScript;

		return (FALSE);
		}
	}

static void dumpVars(scriptVariables *base)
	{
	scriptVariables *theVar, *theVar2;

	for (theVar = base; theVar; theVar = theVar2)
		{
		if (theVar->type >= TYPE_STRING_MIN &&
				theVar->type <= TYPE_STRING_MAX)
			{
			delete [] theVar->v.string;
			}

		theVar2 = theVar->next;

		delete theVar;
		}
	}

static void dumpScript(void)
	{
	VerifyHeap();

	// Run any de-init we have set for this script.
	if (CurScript->DeinitScript)
		{
		(*CurScript->DeinitScript)(CurScript);
		}

	runCounter--;

	int i;
	scriptFunctionList *theFunc, *theFunc2;
	scriptCommandRecord *theCmd, *theCmd2;

	if (!runCounter && !cfg.FastScripts)
		{
		discardData(ScriptDData);
		ScriptDData = NULL;
		}

	if (CurScript->DebuggingWindow)
		{
		destroyCitWindow(CurScript->DebuggingWindow, FALSE);
		CurScript->DebuggingWindow = NULL;
		}

	disposeLL((void **) &CurScript->BPoints);
	disposeLL((void **) &CurScript->callList);

	dumpVars(CurScript->gvBase);
	CurScript->gvBase = NULL;

	// then dump the functions
	for (theFunc = CurScript->sfBase; theFunc; theFunc = theFunc2)
		{
		for (i = 0; i < MAXARGS; i++)
			{
			if (theFunc->argv[i])
				{
				dumpVars(theFunc->argv[i]);
				}
			}

		dumpVars(theFunc->vars);

		disposeLL((void **) &(theFunc->loopInfo));

		for (theCmd = theFunc->cmds; theCmd; theCmd = theCmd2)
			{
			switch (theCmd->type)
				{
				case CMD_WHILE:
				case CMD_WHILENOT:
				case CMD_LOOP:
				case CMD_LOOPNOT:
				case CMD_IF:
				case CMD_IFNOT:
				case CMD_GOTO:
				case CMD_LABEL:
				case CMD_RET:
				case CMD_EXIT:
				case CMD_RUN:
				case CMD_CHAIN:
					{
					delete [] theCmd->c.str;
					break;
					}

				case CMD_ADD:
				case CMD_SUB:
				case CMD_MUL:
				case CMD_DIV:
				case CMD_MOD:
				case CMD_CALL:
				case CMD_ASGN:
				case CMD_EQ:
				case CMD_GT:
				case CMD_LT:
				case CMD_NE:
				case CMD_GE:
				case CMD_LE:
				case CMD_STRB:
				case CMD_STRU:
				case CMD_STRL:
				case CMD_STRC:
				case CMD_STRS:
				case CMD_STRE:
				case CMD_STRPR:
				case CMD_STRPL:
					{
					disposeLL((void **) &theCmd->c.sl);
					break;
					}
				}

			theCmd2 = theCmd->next;

			delete theCmd;
			}

		theFunc2 = theFunc->next;
		delete theFunc;
		}

	CurScript->sfBase = NULL;

	delete CurScript->UsrMsg;
	CurScript->UsrMsg = NULL;

	if (!CurScript->UseCU)
		{
		delete CurScript->UsrUsr;
		}

	CurScript->UseCU = FALSE;
	CurScript->UsrUsr = NULL;

	disposeLL((void **) &CurScript->dupCheck);

	delete [] CurScript->Files;
	CurScript->Files = NULL;

	for (i = 0; i < 5; i++)
		{
		if (CurScript->UsrFile[i])
			{
			fclose(CurScript->UsrFile[i]);
			CurScript->UsrFile[i] = NULL;
			}
		}

	*CurScript->Name = 0;

	VerifyHeap();
	}

static VariableTypeE parseType(const char *a_type)
	{
	int i;

	VariableTypeE type = TYPE_NONE;

	if (SameString(a_type, getscrmsg(18)))
		{
		type = TYPE_NONE;
		}
	if (strncmpi(a_type, getscrmsg(19), 3) == SAMESTRING)
		{
		type = TYPE_INT;
		}
	else if (strncmpi(a_type, getscrmsg(20), 4) == SAMESTRING)
		{
		type = TYPE_UINT;
		}
	else if (strncmpi(a_type, getscrmsg(21), 4) == SAMESTRING)
		{
		type = TYPE_LONG;
		}
	else if (strncmpi(a_type, getscrmsg(22), 5) == SAMESTRING)
		{
		type = TYPE_ULONG;
		}
	else if (strncmpi(a_type, getscrmsg(23), 4) == SAMESTRING)
		{
		type = TYPE_BOOL;
		}
	else if (strncmpi(a_type, getscrmsg(24), 6) == SAMESTRING)
		{
		if (strchr(a_type, '_'))
			{
			type = TYPE_STRING_MAX;
			}
		else
			{
			i = atoi(a_type + 6);
			if (i < 1 || i > 255)
				{
				i = 255;
				}

			type = (VariableTypeE) (TYPE_STRING_MIN + i - 1);
			}
		}

	if (strchr(a_type, '_'))
		{
		type = (VariableTypeE) (type | BY_REF);
		}

	return (type);
	}

