//-------------------------------------------------------------------------
/*
Copyright (C) 1996, 2003 - 3D Realms Entertainment

This file is part of Duke Nukem 3D version 1.5 - Atomic Edition

Duke Nukem 3D 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., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.

Original Source: 1996 - Todd Replogle
Prepared for public release: 03/21/2003 - Charlie Wiederhold, 3D Realms
*/
//-------------------------------------------------------------------------

#include <assert.h>
#include <ctype.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include "duke3d.h"
#include "scriplib.h"

//#define DEBUG_SCRIPLIB 1

#define MAX_SCRIPTS 5

typedef enum {
	SCRIPTFLAG_UNKNOWN,
	SCRIPTFLAG_CATEGORY,
	SCRIPTFLAG_ONESTRING,
	SCRIPTFLAG_TWOSTRING,
	SCRIPTFLAG_HEX,
	SCRIPTFLAG_DECIMAL,
	SCRIPTFLAG_FLOAT
} scriptflag_t;

typedef struct scriptnode_s {
	struct scriptnode_s *child;
	struct scriptnode_s *sibling;
	char *key;
	scriptflag_t type;
	union {
		char *string[2];
		int number;
		float floatnumber;
	} data;
} scriptnode_t;

static int script_nexthandle = 0;
static int script_numscriptsopen = 0;

static scriptnode_t *script_headnode[MAX_SCRIPTS];

/* Utility functions */
static scriptnode_t *SCRIPT_constructnode (void)
{
	scriptnode_t *s;

	s = (scriptnode_t *) malloc (sizeof (scriptnode_t));
	if (s != NULL)
	{
		s->child = NULL;
		s->sibling = NULL;
		s->key = NULL;
		s->data.string[0] = NULL;
		s->data.string[1] = NULL;
		s->type = SCRIPTFLAG_UNKNOWN;
	}
	return s;
}

static void SCRIPT_freenode (scriptnode_t *node)
{
	assert (node != NULL);

	if (node->type == SCRIPTFLAG_ONESTRING) {
		free (node->data.string[0]);
	} else if (node->type == SCRIPTFLAG_TWOSTRING) {
		free (node->data.string[0]);
		free (node->data.string[1]);
	}

	free (node->key);
	free (node->sibling);
	free (node->child);
	free (node);
}

static void SCRIPT_writenode (scriptnode_t *node, FILE *fp)
{
	switch (node->type)
	{
		case SCRIPTFLAG_UNKNOWN:
			return;
			break;
		case SCRIPTFLAG_CATEGORY:
			fprintf (fp, "\n[%s]\n", node->key);
			break;
		case SCRIPTFLAG_ONESTRING:
			fprintf (fp, "%s = \"%s\"\n", node->key, node->data.string[0]);
			break;
		case SCRIPTFLAG_TWOSTRING:
			fprintf (fp, "%s = \"%s\" \"%s\"\n", node->key, node->data.string[0], node->data.string[1]);
			break;
		case SCRIPTFLAG_HEX:
			fprintf (fp, "%s = 0x%X\n", node->key, node->data.number);
			break;
		case SCRIPTFLAG_DECIMAL:
			fprintf (fp, "%s = %d\n", node->key, (unsigned short)node->data.number);
			break;
		case SCRIPTFLAG_FLOAT:
			 fprintf (fp, "%s = %ff\n", node->key, node->data.floatnumber);
			 break;
	}
}

static void SCRIPT_recursivewrite (scriptnode_t *node, FILE *fp)
{
	if (node == NULL) return;

	SCRIPT_writenode (node, fp);

	/* Free dependant nodes first */
	if (node->child) {
		SCRIPT_recursivewrite (node->child, fp);
	}

	if (node->sibling) {
		SCRIPT_recursivewrite (node->sibling, fp);
	}
}

static void SCRIPT_recursivefree (scriptnode_t *node)
{
	assert (node != NULL);

	/* Free dependant nodes first */
	if (node->child) {
		SCRIPT_recursivefree (node->child);
		node->child = NULL;
	}

	if (node->sibling) {
		SCRIPT_recursivefree (node->sibling);
		node->sibling = NULL;
	}

	SCRIPT_freenode (node);
	node = NULL;
}

static void SCRIPT_addsibling (scriptnode_t *node, scriptnode_t *sibling)
{
	assert (node != NULL);
	assert (sibling != NULL);
	
	/* printf ("addsib: %p, %p, %p\n", node, node->sibling, sibling); */

	if (node->sibling == NULL) {
		node->sibling = sibling;
	} else {
		SCRIPT_addsibling (node->sibling, sibling);
	}
}

static void SCRIPT_addchild (scriptnode_t *parent, scriptnode_t *child)
{
	assert (parent != NULL);
	assert (child != NULL);

	if (parent->child == NULL) {
		parent->child = child;
	} else {
		SCRIPT_addsibling (parent->child, child);
	}
}

static char *SCRIPT_copystring (char * s)
{
	char *ret;

	ret = (char *) malloc (strlen (s)+1);
	if (ret != NULL)
	{
		strcpy (ret, s);
	}
	return ret;
}

static int SCRIPT_getnexttoken (char *buffer, char* token, int start)
{
	int iterator = start;

	if (buffer[start] == '\0') {
		token[0] = '\0';
		return start;
	}

	while (iterator < 128 && !isspace (buffer[iterator]))
	{
		token[iterator-start] = buffer[iterator];
		iterator++;
	}

	token[iterator-start] = '\0';

	/* Trim off any extra whitespace */
	while (iterator < 127 && isspace(buffer[iterator+1]))
		iterator++;

	return ++iterator;
}

/* Fills in a scriptnode with the interpreted contents of a line */
static void SCRIPT_parseline (char *curline, scriptnode_t *node)
{
	char token[128];
	int i;

	/* Needs to handle 5 cases: */
	/* 	key = someint */
	/* 	key = 0xsomehexnum */
	/* 	key = ~ */
	/* 	key = "onestring" */
	/* 	key = "two" "strings" */

	assert (node != NULL);
	assert (curline != NULL);

	i = SCRIPT_getnexttoken (curline, token, 0);
	node->key = SCRIPT_copystring (token);

	i = SCRIPT_getnexttoken (curline, token, i);
	/* Sanity check... this token should be "=" */
	if (strcmp (token, "=")) {
		/* Error state, free the memory allocated */
		SCRIPT_recursivefree (node);
		return;
	}

	/* This is where the real fun begins... */
	/* we can begin to determine which of the 5 */
	/* possibilities the node is now */
	i = SCRIPT_getnexttoken (curline, token, i);

	if (!strncmp (token, "0x", 2)) {
		/* Found a hex digit! */
		node->type = SCRIPTFLAG_HEX;
		node->data.number = strtol (token, NULL, 16);
	} else if (isdigit (token[0]) || token[0] == '-') {
		/* Found a number! */
		node->type = SCRIPTFLAG_DECIMAL;
		node->data.number = atoi (token);
	} else if (token[strlen(token) - 1] == 'f') {
		/* Found a float */
		node->type = SCRIPTFLAG_FLOAT;
		node->data.floatnumber = atof(token);
	} else if (token[0] == '~') {
		/* Found a ... who knows */
		node->type = SCRIPTFLAG_DECIMAL;
		node->data.number = -1;
	} else if (token[0] == '"') {
		char workbuf[128];
		int r;

		/* Found one of possibly two strings */
		strcpy (workbuf, token);
		while (token != NULL && workbuf[strlen(workbuf)-1] != '"')
		{
			i = SCRIPT_getnexttoken (curline, token, i);
			strcat (workbuf, " ");
			strcat (workbuf, token);
		}
		r = sscanf(workbuf, "\"%[^\"]\"", workbuf);
		if (r == 0) workbuf[0] = '\0';

		node->type = SCRIPTFLAG_ONESTRING;
		node->data.string[0] = SCRIPT_copystring (workbuf);
		/* Check for a second string */
		i = SCRIPT_getnexttoken (curline, token, i);
		if (token[0] == '"') {
			strcpy (workbuf, token);
			while (token != NULL && workbuf[strlen(workbuf)-1] != '"')
			{
				i = SCRIPT_getnexttoken (curline, token, i);
				strcat (workbuf, " ");
				strcat (workbuf, token);
			}
			r = sscanf(workbuf, "\"%[^\"]\"", workbuf);
			if (r == 0) workbuf[0] = '\0';

			node->type = SCRIPTFLAG_TWOSTRING;
			node->data.string[1] = SCRIPT_copystring (workbuf);
		}
	} else {
		/* Error state! */
		SCRIPT_recursivefree (node);
	}
}

static scriptnode_t *SCRIPT_findinchildren (scriptnode_t *parent, char *s)
{
	scriptnode_t *cur = parent;

	if (cur == NULL) return NULL;
	cur = cur->child;
	if (cur == NULL) return NULL;
	while (cur != NULL)
	{
		if (!strcmp(cur->key, s))
			break;
		cur = cur->sibling;
	}

	return cur;
}

/*
==============
=
= SCRIPT_Init
=
==============
*/
int32 SCRIPT_Init( char * name )
{
	STUBBED("Init");
	
	return -1;
}


/*
==============
=
= SCRIPT_Free
=
==============
*/
void SCRIPT_Free( int32 scripthandle )
{
	/* STUBBED("Free"); */
	if (scripthandle == -1) return;

	SCRIPT_recursivefree (script_headnode[scripthandle]);
	script_numscriptsopen--;
}


/*
==============
=
= SCRIPT_Parse
=
==============
*/

int32 SCRIPT_Parse ( char *data, int32 length, char * name )
{
	STUBBED("Parse");
	
	return -1;
}


/*
==============
=
= SCRIPT_Load
=
==============
*/

int32 SCRIPT_Load ( char * filename )
{
	FILE *fp;
	char curline[128];
	scriptnode_t *headnode = NULL;
	scriptnode_t *cur_subsection = NULL;

	if (script_numscriptsopen == MAX_SCRIPTS) return -1;

	/* The main program does not check for any sort of */
	/* error in loading, so each SCRIPT_ function needs */
	/* to check if the handle is -1 before doing anything */
	fp = fopen (filename, "r");

	if (fp == NULL) return -1;

	/* Start loading the script */
	/* Loads and parse the entire file into a tree */
	script_numscriptsopen++;

	/* script_nexthandle is the current handle until we increment it */
	script_headnode[script_nexthandle] = SCRIPT_constructnode ();
	headnode = script_headnode[script_nexthandle];

	memset (curline, 0, 128);
	while (fgets (curline, 128, fp))
	{
		/* Skip comments */
		if (curline[0] == ';') continue;

		/* Parse line */
		/* We have two options... it starts with a [, making it */
		/* a new subsection (child of headnode) or it starts with */
		/* a letter, making it a child of a subsection. */
		if (curline[0] == '[')
		{
			scriptnode_t *node;
			int i;
			
			/* Remove [] manually */
			for (i = 0; i < 127; i++)
				curline[i] = curline[i+1];
			for (i = 127; i >= 0; i--)
			{
				if (curline[i] == ']') {
					curline[i] = '\0';
					break;
				} else {
					curline[i] = '\0';
				}
			}

			/* Insert into head */
			node = SCRIPT_constructnode ();
			node->type = SCRIPTFLAG_CATEGORY;
			node->key = SCRIPT_copystring (curline);
			SCRIPT_addchild (headnode, node);
			cur_subsection = node;
			/* printf ("Working in section \"%s\"\n", node->key); */
		} else if (isalpha (curline[0])) {
			scriptnode_t *node;

			/* Ignore if not under a subsection */
			if (cur_subsection == NULL)
				continue;

			node = SCRIPT_constructnode ();
			
			/* TODO: Parse line here */
			SCRIPT_parseline (curline, node);
			if (node != NULL)
			{
				/* printf ("Adding node with key \"%s\"\n", node->key); */
				SCRIPT_addchild (cur_subsection, node);
			}
		}
		memset (curline, 0, 128);
	}

	fclose (fp);

	return script_nexthandle++;	/* postincrement is important here */
}

/*
==============
=
= SCRIPT_Save
=
==============
*/
void SCRIPT_Save (int32 scripthandle, char * filename)
{
	FILE *fp;
	scriptnode_t *head;

	if(scripthandle >= MAX_SCRIPTS || scripthandle < 0)
		return;

	fp = fopen (filename, "w");
	if (fp == NULL) return;

	head = script_headnode[scripthandle];
	SCRIPT_recursivewrite (head, fp);

	fclose (fp);

}


/*
==============
=
= SCRIPT_NumberSections
=
==============
*/

int32 SCRIPT_NumberSections( int32 scripthandle )
{
	STUBBED("NumberSections");
	
	return -1;
}

/*
==============
=
= SCRIPT_Section
=
==============
*/

char * SCRIPT_Section( int32 scripthandle, int32 which )
{
	STUBBED("Section");
	
	return NULL;
}

/*
==============
=
= SCRIPT_NumberEntries
=
==============
*/

int32 SCRIPT_NumberEntries( int32 scripthandle, char * sectionname )
{
	scriptnode_t *node = NULL;
	int32 entries = 0;

	if(scripthandle >= MAX_SCRIPTS || scripthandle < 0)
		return 0;

	node = script_headnode[scripthandle];
	node = SCRIPT_findinchildren(node, sectionname);
	if(!node) return 0;

	for(node=node->child; node ; node=node->sibling)
	{
		++entries;
	}

	return entries;
}


/*
==============
=
= SCRIPT_Entry
=
==============
*/
char * SCRIPT_Entry( int32 scripthandle, char * sectionname, int32 which )
{
	scriptnode_t *node = NULL;
	int32 entrynum = 0;
	char* val = NULL;

	if(scripthandle >= MAX_SCRIPTS || scripthandle < 0)
		return "";

	node = script_headnode[scripthandle];
	node = SCRIPT_findinchildren(node,sectionname);
	if(!node) return "";

	for(node=node->child; node ; node=node->sibling, ++entrynum)
	{
		if(entrynum == which)
		{
			val = node->key;
			break;
		}
	}

	return val;
}


/*
==============
=
= SCRIPT_GetRaw
=
==============
*/
char * SCRIPT_GetRaw(int32 scripthandle, char * sectionname, char * entryname)
{
	STUBBED("GetRaw");
	
	return NULL;
}

/*
==============
=
= SCRIPT_GetString
=
==============
*/
void SCRIPT_GetString
   (
   int32 scripthandle,
   char * sectionname,
   char * entryname,
   char * dest
   )
{
    scriptnode_t *cur;

	/* STUBBED("GetString"); */
	if (scripthandle == -1) return;

	cur = script_headnode[scripthandle];

	cur = SCRIPT_findinchildren (cur, sectionname);
	cur = SCRIPT_findinchildren (cur, entryname);

	if (cur != NULL && cur->type == SCRIPTFLAG_ONESTRING)
	{
		strcpy (dest, cur->data.string[0]);
#ifdef DEBUG_SCRIPLIB
		printf ("GetString: value for %s:%s is %s\n", sectionname, entryname, dest);
#endif
	}
}

/*
==============
=
= SCRIPT_GetDoubleString
=
==============
*/
void SCRIPT_GetDoubleString
   (
   int32 scripthandle,
   char * sectionname,
   char * entryname,
   char * dest1,
   char * dest2
   )
{
    scriptnode_t *cur;

	/* STUBBED("GetDoubleString"); */
	if (scripthandle == -1) return;

	cur = script_headnode[scripthandle];

	cur = SCRIPT_findinchildren (cur, sectionname);
	cur = SCRIPT_findinchildren (cur, entryname);

	if (cur != NULL && cur->type == SCRIPTFLAG_TWOSTRING)
	{
		strcpy (dest1, cur->data.string[0]);
		strcpy (dest2, cur->data.string[1]);
#ifdef DEBUG_SCRIPLIB
		printf ("GetDoubleString: value for %s:%s is %s %s\n", sectionname, entryname, dest1, dest2);
#endif
	}
}

/*
==============
=
= SCRIPT_GetNumber
=
==============
*/
boolean SCRIPT_GetNumber
   (
   int32 scripthandle,
   char * sectionname,
   char * entryname,
   int32 * number
   )
{
	scriptnode_t *cur;

	if (scripthandle == -1) return false;

	cur = script_headnode[scripthandle];

	cur = SCRIPT_findinchildren (cur, sectionname);
	cur = SCRIPT_findinchildren (cur, entryname);

	if (cur != NULL && cur->type == SCRIPTFLAG_DECIMAL)
	{
		*number = cur->data.number;
#ifdef DEBUG_SCRIPLIB
		printf ("GetNumber: value for %s:%s is %ld\n", sectionname, entryname, *number);
#endif
	}

	return (cur != NULL) ? true : false;
}

/*
==============
=
= SCRIPT_GetBoolean
=
==============
*/
void SCRIPT_GetBoolean
   (
   int32 scripthandle,
   char * sectionname,
   char * entryname,
   boolean * bool
   )
{
	STUBBED("GetBoolean");
}

/*
==============
=
= SCRIPT_GetFloat
=
==============
*/
boolean SCRIPT_GetFloat
   (
   int32 scripthandle,
   char * sectionname,
   char * entryname,
   float * floatnumber
   )
{
       scriptnode_t *cur;

       if (scripthandle == -1) return false;

       cur = script_headnode[scripthandle];

       cur = SCRIPT_findinchildren (cur, sectionname);
       cur = SCRIPT_findinchildren (cur, entryname);

       if (cur != NULL && cur->type == SCRIPTFLAG_FLOAT)
       {
               *floatnumber = cur->data.floatnumber;
#ifdef DEBUG_SCRIPLIB
               printf ("GetFloat: value for %s:%s is %f\n", sectionname, entryname, *floatnumber);
#endif
       }

       return (cur != NULL) ? true : false;
}

/*
+==============
+=
= SCRIPT_GetDouble
=
==============
*/

void SCRIPT_GetDouble
   (
   int32 scripthandle,
   char * sectionname,
   char * entryname,
   double * number
   )
{
	STUBBED("GetDouble");
}



/*
==============
=
= SCRIPT_PutComment
=
==============
*/
void SCRIPT_PutComment( int32 scripthandle, char * sectionname, char * comment )
{
	STUBBED("PutComment");
}

/*
==============
=
= SCRIPT_PutEOL
=
==============
*/
void SCRIPT_PutEOL( int32 scripthandle, char * sectionname )
{
	STUBBED("PutEOL");
}

/*
==============
=
= SCRIPT_PutMultiComment
=
==============
*/
void SCRIPT_PutMultiComment
   (
   int32 scripthandle,
   char * sectionname,
   char * comment,
   ...
   )
{
	STUBBED("PutMultiComment");
}

/*
==============
=
= SCRIPT_PutSection
=
==============
*/
void SCRIPT_PutSection( int32 scripthandle, char * sectionname )
{
	STUBBED("PutSection");
}

/*
==============
=
= SCRIPT_PutRaw
=
==============
*/
void SCRIPT_PutRaw
   (
   int32 scripthandle,
   char * sectionname,
   char * entryname,
   char * raw
   )
{
	STUBBED("PutRaw");
}

/*
==============
=
= SCRIPT_PutString
=
==============
*/
void SCRIPT_PutString
   (
   int32 scripthandle,
   char * sectionname,
   char * entryname,
   char * string
   )
{
	scriptnode_t *head;
	scriptnode_t *section;
	scriptnode_t *node;

	if(scripthandle >= MAX_SCRIPTS || scripthandle < 0)
		return;

	head = script_headnode[scripthandle];

	/* We're screwed if there's no head */
	if (head == NULL) return;

	section = SCRIPT_findinchildren (head, sectionname);
	if (section == NULL)
	{
		/* Add the section if it does not exist */
		section = SCRIPT_constructnode ();
		section->type = SCRIPTFLAG_CATEGORY;
		section->key = SCRIPT_copystring (sectionname);
		SCRIPT_addchild (head, section);
	}

	node = SCRIPT_findinchildren (section, entryname);
	if (node == NULL)
	{
		/* Add the section if it does not exist */
		node = SCRIPT_constructnode ();
		node->type = SCRIPTFLAG_ONESTRING;
		node->key = SCRIPT_copystring (entryname);
		SCRIPT_addchild (section, node);
	} else {
		free (node->data.string[0]);
	}

	node->data.string[0] = SCRIPT_copystring (string);
}

/*
==============
=
= SCRIPT_PutDoubleString
=
==============
*/
void SCRIPT_PutDoubleString
   (
   int32 scripthandle,
   char * sectionname,
   char * entryname,
   char * string1,
   char * string2
   )
{
	STUBBED("PutDoubleString");
}

/*
==============
=
= SCRIPT_PutNumber
=
==============
*/
void SCRIPT_PutNumber
   (
   int32 scripthandle,
   char * sectionname,
   char * entryname,
   int32 number,
   boolean hexadecimal,
   boolean defaultvalue
   )
{
	/* DDOI - I don't know what "defaultvalue" is for so it's ignored */
	scriptnode_t *head;
	scriptnode_t *section;
	scriptnode_t *node;

	if(scripthandle >= MAX_SCRIPTS || scripthandle < 0)
		return;

	head = script_headnode[scripthandle];

	/* We're screwed if there's no head */
	if (head == NULL) return;

	section = SCRIPT_findinchildren (head, sectionname);
	if (section == NULL)
	{
		/* Add the section if it does not exist */
		section = SCRIPT_constructnode ();
		section->type = SCRIPTFLAG_CATEGORY;
		section->key = SCRIPT_copystring (sectionname);
		SCRIPT_addchild (head, section);
	}

	node = SCRIPT_findinchildren (section, entryname);
	if (node == NULL)
	{
		/* Add the section if it does not exist */
		node = SCRIPT_constructnode ();
		node->key = SCRIPT_copystring (entryname);
		SCRIPT_addchild (section, node);
	}

	if (hexadecimal)
		node->type = SCRIPTFLAG_HEX;
	else
		node->type = SCRIPTFLAG_DECIMAL;
	node->data.number = number;
}

/*
==============
=
= SCRIPT_PutBoolean
=
==============
*/
void SCRIPT_PutBoolean
   (
   int32 scripthandle,
   char * sectionname,
   char * entryname,
   boolean bool
   )
{
	STUBBED("PutBoolean");
}

/*
==============
=
= SCRIPT_PutDouble
=
==============
*/

void SCRIPT_PutDouble
   (
   int32 scripthandle,
   char * sectionname,
   char * entryname,
   double number,
   boolean defaultvalue
   )
{
	STUBBED("PutDouble");
}
