//-----------------------------------------------------------------------------
//
//  $Logfile:: /Quake 2 Engine/Sin/code/game/script.cpp                       $
// $Revision:: 13                                                             $
//   $Author:: Jimdose                                                        $
//     $Date:: 10/07/98 11:58p                                                $
//
// Copyright (C) 1997 by Ritual Entertainment, Inc.
// All rights reserved.
//
// This source is may not be distributed and/or modified without
// expressly written permission by Ritual Entertainment, Inc.
//
// $Log:: /Quake 2 Engine/Sin/code/game/script.cpp                            $
//
// 13    10/07/98 11:58p Jimdose
// changed script_t to offset in script markers
//
// 12    6/09/98 4:21p Jimdose
// Added close
// filename is now a str
//
// 11    5/24/98 5:25p Jimdose
// Changed char *'s to const char *'s
//
// 10    3/26/98 3:57p Jimdose
// Made gettoken and getstring handle escape sequences (\n, \", etc.)
//
// 9     3/23/98 1:31p Jimdose
// Revamped event and command system
//
// 8     3/02/98 8:49p Jimdose
// Changed the classid parameter of CLASS_DECLARATION to a quoted string so
// that you could have a NULL classid.
//
// 7     2/17/98 7:13p Jimdose
// Added MarkPosition and RestorePosition
// Added atString for determining if the next token is a quoted string
// Made GetToken call GetString if the next token is a quoted string
// Fixed bug where GetSpecific wasn't properly updating the current line number
//
// 6     2/03/98 10:54a Jimdose
// Updated to work with Quake 2 engine
//
// 4     11/24/97 4:30p Markd
// Added GetVector
//
// 3     10/27/97 3:29p Jimdose
// Removed dependency on quakedef.h
//
// 2     9/26/97 6:14p Jimdose
// Added standard Ritual headers
//
// DESCRIPTION:
// C++ implementaion of tokenizing text interpretation.  Class accepts filename
// to load or pointer to preloaded text data.  Standard tokenizing operations
// such as skip white-space, get string, get integer, get float, get token,
// and skip line are implemented.
//
// Note: all '//', '#', and ';' are treated as comments.  Probably should
// make this behaviour toggleable.
//

#include "g_local.h"
#include "script.h"
#include "archive.h"

#define TOKENCOMMENT      (';')
#define TOKENCOMMENT2     ('#')
#define TOKENEOL          ('\n')
#define TOKENNULL         ('\0')
#define TOKENSPACE        (' ')

CLASS_DECLARATION( Class, Script, NULL );

ResponseDef Script::Responses[] =
{
	{ NULL, NULL }
};

Script::~Script()
{
	Close();
}

Script::Script()
{
	buffer          = NULL;
	script_p            = NULL;
	end_p               = NULL;
	line                = 0;
	releaseBuffer   = false;
	tokenready      = false;
	memset( token, 0, sizeof( token ) );
}

void Script::Close( void )
{
	if( releaseBuffer && buffer )
	{
		gi.TagFree( ( void* )buffer );
	}

	buffer          = NULL;
	script_p            = NULL;
	end_p               = NULL;
	line                = 0;
	releaseBuffer   = false;
	tokenready      = false;
	memset( token, 0, sizeof( token ) );
}

/*
==============
Reset
==============
*/
void Script::Reset( void )
{
	script_p = buffer;
	line = 1;
	tokenready = false;
}

/*
==============
MarkPosition
==============
*/
void Script::MarkPosition( scriptmarker_t* mark )
{
	assert( mark );

	mark->tokenready = tokenready;
	mark->offset     = script_p - buffer;
	mark->line        = line;
	strcpy( mark->token, token );
}

/*
==============
RestorePosition
==============
*/
void Script::RestorePosition( scriptmarker_t* mark )
{
	assert( mark );

	tokenready = mark->tokenready;
	script_p = buffer + mark->offset;
	line = mark->line;
	strcpy( token, mark->token );

	assert( script_p <= end_p );
	if( script_p > end_p )
	{
		script_p = end_p;
	}
}

/*
==============
SkipToEOL
==============
*/
qboolean Script::SkipToEOL( void )
{
	if( script_p >= end_p )
	{
		return true;
	}

	while( *script_p != TOKENEOL )
	{
		if( script_p >= end_p )
		{
			return true;
		}
		script_p++;
	}
	return false;
}

/*
==============
CheckOverflow
==============
*/
void Script::CheckOverflow( void )
{
	if( script_p >= end_p )
	{
		gi.error( "End of token file reached prematurely reading %s\n", filename.c_str() );
	}
}

/*
==============
SkipWhiteSpace
==============
*/
void Script::SkipWhiteSpace( qboolean crossline )
{
	//
	// skip space
	//
	CheckOverflow();

	while( *script_p <= TOKENSPACE )
	{
		if( *script_p++ == TOKENEOL )
		{
			if( !crossline )
			{
				gi.error( "Line %i is incomplete in file %s\n", line, filename.c_str() );
			}

			line++;
		}
		CheckOverflow();
	}
}

qboolean Script::AtComment( void )
{
	if( script_p >= end_p )
	{
		return false;
	}

	if( *script_p == TOKENCOMMENT )
	{
		return true;
	}

	if( *script_p == TOKENCOMMENT2 )
	{
		return true;
	}

	// Two or more character comment specifiers
	if( ( script_p + 1 ) >= end_p )
	{
		return false;
	}

	if( ( *script_p == '/' ) && ( *( script_p + 1 ) == '/' ) )
	{
		return true;
	}
	return false;
}

/*
==============
SkipNonToken
==============
*/
void Script::SkipNonToken( qboolean crossline )
{
	//
	// skip space and comments
	//
	SkipWhiteSpace( crossline );
	while( AtComment() )
	{
		SkipToEOL();
		SkipWhiteSpace( crossline );
	}
}

/*
=============================================================================
Token section
=============================================================================
*/

/*
==============
TokenAvailable
==============
*/
qboolean Script::TokenAvailable( qboolean crossline )
{
	if( script_p >= end_p )
	{
		return false;
	}

	while( 1 )
	{
		while( *script_p <= TOKENSPACE )
		{
			if( *script_p == TOKENEOL )
			{
				if( crossline == false )
				{
					return( false );
				}
				line++;
			}

			script_p++;
			if( script_p >= end_p )
			{
				return false;
			}
		}

		if( AtComment() )
		{
			if( SkipToEOL() )
			{
				return false;
			}
		}
		else
		{
			break;
		}
	}
	return true;
}

/*
==============
CommentAvailable
==============
*/
qboolean Script::CommentAvailable( qboolean crossline )
{
	const char* searchptr;

	searchptr = script_p;

	if( searchptr >= end_p )
	{
		return false;
	}

	while( *searchptr <= TOKENSPACE )
	{
		if( ( *searchptr == TOKENEOL ) && ( !crossline ) )
		{
			return false;
		}
		searchptr++;
		if( searchptr >= end_p )
		{
			return false;
		}
	}

	return true;
}


/*
==============
AtString
==============
*/
qboolean Script::AtString( qboolean crossline )
{
	//
	// skip space
	//
	SkipNonToken( crossline );

	return ( *script_p == '"' );
}

/*
==============
GetToken
==============
*/
const char* Script::GetToken( qboolean crossline )
{
	char* token_p;

	// is a token already waiting?
	if( tokenready )
	{
		tokenready = false;
		return token;
	}

	//
	// skip space
	//
	SkipNonToken( crossline );

	//
	// copy token
	//

	if( *script_p == '"' )
	{
		return GetString( crossline );
	}

	token_p = token;
	while( *script_p > TOKENSPACE && !AtComment() )
	{
		if( ( *script_p == '\\' ) && ( script_p < end_p - 1 ) )
		{
			script_p++;
			switch( *script_p )
			{
				case 'n':
					*token_p++ = '\n';
					break;
				case 'r':
					*token_p++ = '\n';
					break;
				case '\'':
					*token_p++ = '\'';
					break;
				case '\"':
					*token_p++ = '\"';
					break;
				case '\\':
					*token_p++ = '\\';
					break;
				default:
					*token_p++ = *script_p;
					break;
			}
			script_p++;
		}
		else
		{
			*token_p++ = *script_p++;
		}

		if( token_p == &token[ MAXTOKEN ] )
		{
			gi.error( "Token too large on line %i in file %s\n", line, filename.c_str() );
		}

		if( script_p == end_p )
		{
			break;
		}
	}

	*token_p = 0;

	return token;
}

/*
==============
GetLine
==============
*/
const char* Script::GetLine( qboolean crossline )
{
	const char* start;
	int size;

	// is a token already waiting?
	if( tokenready )
	{
		tokenready = false;
		return token;
	}

	//
	// skip space
	//
	SkipNonToken( crossline );

	//
	// copy token
	//
	start = script_p;
	SkipToEOL();
	size = script_p - start;
	if( size < MAXTOKEN - 1 )
	{
		memcpy( token, start, size );
		token[ size ] = '\0';
	}
	else
	{
		gi.error( "Token too large on line %i in file %s\n", line, filename.c_str() );
	}
	return token;
}

/*
==============
GetRaw
==============
*/
const char* Script::GetRaw( void )
{
	const char* start;
	int size;

	//
	// skip white space
	//
	SkipWhiteSpace( true );

	//
	// copy token
	//
	start = script_p;
	SkipToEOL();
	size = script_p - start;
	if( size < MAXTOKEN - 1 )
	{
		memset( token, 0, sizeof( token ) );
		memcpy( token, start, size );
	}
	else
	{
		gi.error( "Token too large on line %i in file %s\n", line, filename.c_str() );
	}
	return token;
}

/*
==============
GetString
==============
*/
const char* Script::GetString( qboolean crossline )
{
	int startline;
	char* token_p;

	// is a token already waiting?
	if( tokenready )
	{
		tokenready = false;
		return token;
	}

	//
	// skip space
	//
	SkipNonToken( crossline );

	if( *script_p != '"' )
	{
		gi.error( "Expecting string on line %i in file %s\n", line, filename.c_str() );
	}

	script_p++;

	startline = line;
	token_p = token;
	while( *script_p != '"' )
	{
		if( *script_p == TOKENEOL )
		{
			gi.error( "Line %i is incomplete while reading string in file %s\n", line, filename.c_str() );
		}

		if( ( *script_p == '\\' ) && ( script_p < end_p - 1 ) )
		{
			script_p++;
			switch( *script_p )
			{
				case 'n':
					*token_p++ = '\n';
					break;
				case 'r':
					*token_p++ = '\n';
					break;
				case '\'':
					*token_p++ = '\'';
					break;
				case '\"':
					*token_p++ = '\"';
					break;
				case '\\':
					*token_p++ = '\\';
					break;
				default:
					*token_p++ = *script_p;
					break;
			}
			script_p++;
		}
		else
		{
			*token_p++ = *script_p++;
		}

		if( script_p >= end_p )
		{
			gi.error( "End of token file reached prematurely while reading string on\n"
					  "line %d in file %s\n", startline, filename.c_str() );
		}

		if( token_p == &token[ MAXTOKEN ] )
		{
			gi.error( "String too large on line %i in file %s\n", line, filename.c_str() );
		}
	}

	*token_p = 0;

	// skip last quote
	script_p++;

	return token;
}

/*
==============
GetSpecific
==============
*/
qboolean Script::GetSpecific( const char* string )
{
	do
	{
		if( !TokenAvailable( true ) )
		{
			return false;
		}
		GetToken( true );
	}
	while( strcmp( token, string ) );
	return true;
}

/*
==============
GetInteger
==============
*/

int Script::GetInteger( qboolean crossline )
{
	GetToken( crossline );
	return atoi( token );
}

/*
==============
GetDouble
==============
*/
double Script::GetDouble( qboolean crossline )
{
	GetToken( crossline );
	return ( double )Sinatof( token );
}

/*
==============
GetFloat
==============
*/
float Script::GetFloat( qboolean crossline )
{
	GetToken( crossline );
	return Sinatof( token );
}

/*
==============
GetVector
==============
*/
void Script::GetVector( Vector* Output, qboolean crossline )
{
	Output->x = GetFloat( crossline );
	Output->y = GetFloat( crossline );
	Output->z = GetFloat( crossline );
}

/*
===================
LinesInFile
===================
*/
int Script::LinesInFile( void )
{
	qboolean temp_tokenready;
	const char* temp_script_p;
	int temp_line;
	char temp_token[ MAXTOKEN ];
	int numentries;

	temp_tokenready = tokenready;
	temp_script_p   = script_p;
	temp_line       = line;
	strcpy( temp_token, token );

	numentries = 0;

	Reset();
	while( TokenAvailable( true ) )
	{
		GetLine( true );
		numentries++;
	}

	tokenready  = temp_tokenready;
	script_p    = temp_script_p;
	line        = temp_line;
	strcpy( token, temp_token );

	return numentries;
}

/*
==============
Parse
==============
*/
void Script::Parse( const char* data, int length, const char* name )
{
	Close();

	buffer = data;
	Reset();
	end_p = script_p + length;
	this->length = length;
	filename = name;
}

/*
==============
Load
==============
*/
void Script::LoadFile( const char* name )
{
	int length;
	const char* buffer;

	Close();

	length = gi.LoadFile( name, ( void** )&buffer, TAG_GAME );
	if( length < 0 )
	{
		error( "LoadFile", "Couldn't load %s\n", name );
	}
	Parse( buffer, length, name );
	releaseBuffer = true;
}

void Script::Archive( Archiver& arc )
{
	Class::Archive( arc );

	arc.WriteString( filename );
	arc.WriteBoolean( tokenready );
	//
	// save out current pointer as an offset
	//
	arc.WriteInteger( script_p - buffer );
	arc.WriteInteger( line );
	arc.WriteRaw( token, sizeof( token ) );
}

void Script::Unarchive( Archiver& arc )
{
	int i;

	Class::Unarchive( arc );

	arc.ReadString( &filename );
	//
	// load the file in
	//
	LoadFile( filename.c_str() );

	tokenready  = arc.ReadBoolean();
	i = arc.ReadInteger();
	//
	// restore the script pointer
	//
	script_p = buffer + i;
	line = arc.ReadInteger();
	arc.ReadRaw( token, sizeof( token ) );
}
