/*
   Copyright (C) 1999-2007 id Software, Inc. and contributors.
   For a list of contributors, see the accompanying CONTRIBUTORS file.

   This file is part of GtkRadiant.

   GtkRadiant 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.

   GtkRadiant 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 GtkRadiant; if not, write to the Free Software
   Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 */

//#define FGD_VERBOSE // define this for extra info in the log.

#include "plugin.h"

/*! \file plugin.cpp
    \brief .fgd entity description format

    FGD loading code by Dominic Clifton - Hydra (Hydra@Hydras-World.com)

    Overview
    ========

    This module loads .fgd files, fgd files are split into classes:

      base classes
      point classes (aka fixed size entities)
      solid classes (aka brush entity)

    This program first scans each file, building up a list structures
    in memory that contain the information for all the classes found
    in the file.

    Then the program looks at each of the non-base classes in the list
    and build the eclass_t structure from each one.

    Classes can request information in other classes.

    Solid/Base and Point/Base classes can have the same names as other
    classes but there can be only one of each solid/point/base class with
    the same name,

    e.g.:

    this is NOT allowed:

      @solidclass = "a"
      @solidclass = "a"

    this is NOT allowed:

      @pointclass = "a"
      @solidclass = "a"

    this IS allowed:

      @solidclass = "a"
      @baseclass = "a"

    Version History
    ===============

    v0.1 - 13/March/2002
      - Initial version.

    v0.2 - 16/March/2002
      - sets e->skinpath when it finds an iconsprite(<filename>) token.

    v0.3 - 21/March/2002
      - Core now supports > 8 spawnflags, changes reflected here too.
      - FIXED: mins/maxs were backwards when only w,h,d were in the FGD
        (as opposed to the actual mins/maxs being in the .def file)
      - Made sure all PointClass entities were fixed size entities
        and gave them a default bounding box size if a size() setting
        was not in the FGD file.
      - Removed the string check for classes requesting another class
        with the same name, adjusted Find_Class() so that it can search
        for baseclasses only,  this fixes the problem with PointClass "light"
        requesting the BaseClass "light".

    v0.4 - 25/March/2002
      - bleh, It turns out that non-baseclasses can request non-baseclasses
        so now I've changed Find_Class() so that it can ignore a specific class
        (i.e. the one that's asking for others, so that classes can't request
        themselves but they can request other classes of any kind with the
        same name).
      - made all spawnflag comments appear in one place, rather than being scattered
        all over the comments if a requested class also had some spawnflags

    v0.5 - 6/April/2002
      - not using skinpath for sprites anymore, apprently we can code a model
        module to display sprites and model files.
      - model() tags are now supported.

    ToDo
    ====

 * add support for setting the eclass_t's modelpath.
      (not useful for CS, but very useful for HL).

 * need to implement usage for e->skinpath in the core.

 * cleanup some areas now that GetTokenExtra() is available
      (some parts were written prior to it's creation).

 * Import the comments between each BaseClass's main [ ] set.
      (unfortunatly they're // cstyle comments, which GetToken skips over)
      But still ignore comments OUTSIDE the main [ ] set.

 */

_QERScripLibTable g_ScripLibTable;
_EClassManagerTable g_EClassManagerTable;
_QERFuncTable_1 g_FuncTable;
_QERFileSystemTable g_FileSystemTable;

// forward declare
void Eclass_ScanFile( char *filename );

const char* EClass_GetExtension(){
	return "fgd";
}

CSynapseServer* g_pSynapseServer = NULL;
CSynapseClientFGD g_SynapseClient;

#if __GNUC__ >= 4
#pragma GCC visibility push(default)
#endif
extern "C" CSynapseClient * SYNAPSE_DLL_EXPORT Synapse_EnumerateInterfaces( const char *version, CSynapseServer *pServer ) {
#if __GNUC__ >= 4
#pragma GCC visibility pop
#endif
	if ( strcmp( version, SYNAPSE_VERSION ) ) {
		Syn_Printf( "ERROR: synapse API version mismatch: should be '" SYNAPSE_VERSION "', got '%s'\n", version );
		return NULL;
	}
	g_pSynapseServer = pServer;
	g_pSynapseServer->IncRef();
	Set_Syn_Printf( g_pSynapseServer->Get_Syn_Printf() );

	g_SynapseClient.AddAPI( ECLASS_MAJOR, "fgd", sizeof( _EClassTable ) );
	g_SynapseClient.AddAPI( SCRIPLIB_MAJOR, NULL, sizeof( g_ScripLibTable ), SYN_REQUIRE, &g_ScripLibTable );
	g_SynapseClient.AddAPI( RADIANT_MAJOR, NULL, sizeof( g_FuncTable ), SYN_REQUIRE, &g_FuncTable );
	g_SynapseClient.AddAPI( ECLASSMANAGER_MAJOR, NULL, sizeof( g_EClassManagerTable ), SYN_REQUIRE, &g_EClassManagerTable );

	// Needs a 'default' option for this minor because we certainly don't load anything from wad files :)
	g_SynapseClient.AddAPI( VFS_MAJOR, "*", sizeof( g_FileSystemTable ), SYN_REQUIRE, &g_FileSystemTable ); // wad, typically

	return &g_SynapseClient;
}

bool CSynapseClientFGD::RequestAPI( APIDescriptor_t *pAPI ){
	if ( !strcmp( pAPI->major_name, ECLASS_MAJOR ) ) {
		_EClassTable* pTable = static_cast<_EClassTable*>( pAPI->mpTable );

		pTable->m_pfnGetExtension = &EClass_GetExtension;
		pTable->m_pfnScanFile = &Eclass_ScanFile;

		return true;
	}

	Syn_Printf( "ERROR: RequestAPI( '%s' ) not found in '%s'\n", pAPI->major_name, GetInfo() );
	return false;
}

#include "version.h"

const char* CSynapseClientFGD::GetInfo(){
	return ".fgd eclass module built " __DATE__ " " RADIANT_VERSION;
}

// ------------------------------------------------------------------------------------------------

#define CLASS_NOCLASS     0
#define CLASS_BASECLASS   1
#define CLASS_POINTCLASS  2
#define CLASS_SOLIDCLASS  3

char *classnames[] = {"NOT DEFINED","BaseClass","PointClass","SolidClass"};

#define OPTION_NOOPTION   0
#define OPTION_STRING     1
#define OPTION_CHOICES    2
#define OPTION_INTEGER    3
#define OPTION_FLAGS      4

char *optionnames[] = {"NOT DEFINED","String","Choices","Integer","Flags"};

typedef struct choice_s {
	int value;
	char *name;
} choice_t;

typedef struct option_s {
	int optiontype;
	char *optioninfo;
	char *epairname;
	char *optiondefault;
	GSList *choices; // list of choices_t
} option_t;

typedef struct class_s {
	int classtype; // see CLASS_* above.
	char *classname;
	GSList *l_baselist; // when building the eclass_t, other class_s's with these names are required.
	char *description;

	GSList *l_optionlist; // when building the eclass_t, other class_s's with these names are required.

	bool gotsize; // if set then boundingbox is valid.
	vec3_t boundingbox[2]; // mins, maxs

	bool gotcolor; // if set then color is valid.
	vec3_t color; // R,G,B, loaded as 0-255

	char *model; // relative path + filename to a model (.spr/.mdl) file, or NULL
} class_t;

/*
   ===========================================================
   utility functions

   ===========================================================
 */
char *strlower( char *start ){
	char    *in;
	in = start;
	while ( *in )
	{
		*in = tolower( *in );
		in++;
	}
	return start;
}

char *addstr( char *dest,char *source ){
	if ( dest ) {
		char *ptr;
		int len = strlen( dest );
		ptr = (char *) malloc( len + strlen( source ) + 1 );
		strcpy( ptr,dest );
		strcpy( ptr + len,source );
		free( dest );
		dest = ptr;
	}
	else
	{
		dest = strdup( source );
	}
	return( dest );
}

int getindex( unsigned int a ){
	unsigned int count = 0;
	unsigned int b = 0;
	for (; b != a; count++ )
	{
		b = ( 1 << count );
		if ( count > a ) {
			return -1;
		}
	}
	return ( count );
}

void ClearGSList( GSList* lst ){
	GSList *p = lst;
	while ( p )
	{
		free( p->data );
		p = g_slist_remove( p, p->data );
	}
}

/*!
   free a choice_t structure and it's contents
 */
void Free_Choice( choice_t *p ){
	if ( p->name ) {
		free( p->name );
	}
	free( p );

}

/*
   ===========================================================
   Main functions

   ===========================================================
 */

/*!
   free an option_t structure and it's contents
 */
void Free_Option( option_t *p ){
	if ( p->epairname ) {
		free( p->epairname );
	}
	if ( p->optiondefault ) {
		free( p->optiondefault );
	}
	if ( p->optioninfo ) {
		free( p->optioninfo );
	}
	GSList *l = p->choices;
	while ( l )
	{
		Free_Choice( (choice_t *)l->data );
		l = g_slist_remove( l, l->data );
	}
	free( p );
}

/*!
   free a class_t structure and it's contents
 */
void Free_Class( class_t *p ){
	GSList *l = p->l_optionlist;
	while ( l )
	{
		Free_Option( (option_t *)l->data );
		l = g_slist_remove( l, l->data );
	}

	if ( p->classname ) {
		free( p->classname );
	}
	free( p );
}

/*!
   find a class in the list
 */
class_t *Find_Class( GSList *l,char *classname, class_t *ignore ){
	for ( GSList *clst = l; clst != NULL; clst = clst->next )
	{
		class_t *c = (class_t *)clst->data;

		if ( c == ignore ) {
			continue;
		}

		// NOTE: to speed up we could make all the classnames lower-case when they're initialised.
		if ( !stricmp( c->classname,classname ) ) {
			return c;
		}

	}
	return NULL;
}

/*!
   Import as much as possible from a class_t into an eclass_t
   Note: this is somewhat recursive, as a class can require a class that requires a class and so on..
 */
void EClass_ImportFromClass( eclass_t *e, GSList *l_classes, class_t *bc ){
	char color[128];

	// We allocate 16k here, but only the memory actually used is kept allocated.
	// this is just used for building the final comments string.
	// Note: if the FGD file contains comments that are >16k (per entity) then
	// radiant will crash upon loading such a file as the eclass_t will become
	// corrupted.
	// FIXME: we could add some length checking when building "newcomments", but
	// that'd slow it down a bit.
	char newcomments[16384] = "";

	//Note: we override the values already in e.
	//and we do it in such a way that the items that appear last in the l_baselist
	//represent the final values.

	if ( bc->description ) {
		sprintf( newcomments,"%s\n",bc->description );
		e->comments = addstr( e->comments,newcomments );
		newcomments[0] = 0; // so we don't add them twice.
	}


	// import from other classes if required.

	if ( bc->l_baselist ) {
		// this class requires other base classes.

		for ( GSList *bclst = bc->l_baselist; bclst != NULL; bclst = bclst->next )
		{
			char *requestedclass = (char *)bclst->data;

//      class_t *rbc = Find_Class(l_classes, requestedclass, true);
			class_t *rbc = Find_Class( l_classes, requestedclass, bc );

			// make sure we don't request ourself!
			if ( rbc == bc ) {
				Sys_Printf( "WARNING: baseclass '%s' tried to request itself!\n", bclst->data );
			}
			else
			{
				if ( !rbc ) {
					Sys_Printf( "WARNING: could not find the requested baseclass '%s' when building '%s'\n", requestedclass,bc->classname );
				}
				else
				{
					// call ourself!
					EClass_ImportFromClass( e, l_classes, rbc );
				}
			}
		}
	}
	// SIZE
	if ( bc->gotsize ) {
		e->fixedsize = true;
		memcpy( e->mins,bc->boundingbox[0],sizeof( vec3_t ) );
		memcpy( e->maxs,bc->boundingbox[1],sizeof( vec3_t ) );
	}
/*
   // Hydra: apparently, this would be bad.

   if (bc->sprite)
   {
    // Hydra - NOTE: e->skinpath is not currently used by the editor but the code
    // to set it is used by both this eclass_t loader and the .DEF eclass_t loader.
    // TODO: implement using e->skinpath.
    if (e->skinpath)
      free (e->skinpath);

    e->skinpath = strdup(bc->sprite);
   }
 */

	// MODEL
	if ( bc->model ) {
		if ( e->modelpath ) {
			free( e->modelpath );
		}

		e->modelpath = strdup( bc->model );
	}

	// COLOR
	if ( bc->gotcolor ) {
		memcpy( e->color,bc->color,sizeof( vec3_t ) );
		sprintf( color, "(%f %f %f)", e->color[0], e->color[1], e->color[2] );
		e->texdef.SetName( color );
	}

	// SPAWNFLAGS and COMMENTS
	if ( bc->l_optionlist ) {
		for ( GSList *optlst = bc->l_optionlist; optlst != NULL; optlst = optlst->next )
		{
			option_t *opt = (option_t*) optlst->data;

			if ( opt->optiontype != OPTION_FLAGS ) {
				// add some info to the comments.
				if ( opt->optioninfo ) {
					sprintf( newcomments + strlen( newcomments ),"%s '%s' %s%s\n",
							 opt->epairname,
							 opt->optioninfo ? opt->optioninfo : "",
							 opt->optiondefault ? ", Default: " : "",
							 opt->optiondefault ? opt->optiondefault : "" );
				}
				else
				{
					sprintf( newcomments + strlen( newcomments ),"%s %s%s\n",
							 opt->epairname,
							 opt->optiondefault ? ", Default: " : "",
							 opt->optiondefault ? opt->optiondefault : "" );
				}
			}

			GSList *choicelst;
			switch ( opt->optiontype )
			{
			case OPTION_FLAGS:
				// grab the flags.
				for ( choicelst = opt->choices; choicelst != NULL; choicelst = choicelst->next )
				{
					choice_t *choice = (choice_t*) choicelst->data;

					int index = getindex( choice->value );
					index--;
					if ( index < MAX_FLAGS ) {
						strcpy( e->flagnames[index],choice->name );
					}
					else
					{
						Sys_Printf( "WARNING: baseclass '%s' has a spawnflag out of range, ignored!\n", bc->classname );
					}
				}
				break;
			case OPTION_CHOICES:
				strcat( newcomments,"  Choices:\n" );
				for ( choicelst = opt->choices; choicelst != NULL; choicelst = choicelst->next )
				{
					choice_t *choice = (choice_t*) choicelst->data;
					sprintf( newcomments + strlen( newcomments ),"  %5d - %s\n",choice->value,choice->name );
				}
				break;
			}
		}
	}

	// name
	if ( e->name ) {
		free( e->name );
	}
	e->name = strdup( bc->classname );

	// fixed size initialisation
	if ( bc->classtype == CLASS_POINTCLASS ) {
		e->fixedsize = true;
		// some point classes dont seem to have size()'s in the fgd, so set up a default here..
		if ( ( e->mins[0] == 0 ) && ( e->mins[1] == 0 ) && ( e->mins[2] == 0 ) &&
			 ( e->maxs[0] == 0 ) && ( e->maxs[1] == 0 ) && ( e->maxs[2] == 0 ) ) {
			e->mins[0] = -8;
			e->mins[1] = -8;
			e->mins[2] = -8;
			e->maxs[0] = 8;
			e->maxs[1] = 8;
			e->maxs[2] = 8;
		}

		if ( e->texdef.GetName() == NULL ) {
			// no color specified for this entity in the fgd file
			// set one now
			// Note: if this eclass_t is not fully built, then this may be
			// overridden with the correct color.

			e->color[0] = 1;
			e->color[1] = 0.5; // how about a nice bright pink, mmm, nice! :)
			e->color[2] = 1;

			sprintf( color, "(%f %f %f)", e->color[0], e->color[1], e->color[2] );
			e->texdef.SetName( color );
		}
	}

	// COMMENTS
	if ( newcomments[0] ) {
		e->comments = addstr( e->comments,newcomments );
	}
}

/*!
   create the eclass_t structures and add to the global list.
 */
void Create_EClasses( GSList *l_classes ){
	int count = 0;
	// loop through the loaded structures finding all the non BaseClasses
	for ( GSList *clst = l_classes; clst != NULL; clst = clst->next )
	{
		class_t *c = (class_t *)clst->data;

		if ( c->classtype == CLASS_BASECLASS ) { // not looking for these.
			continue;
		}

		eclass_t *e = (eclass_t *) malloc( sizeof( eclass_s ) );
		memset( e,0,sizeof( eclass_s ) );

		EClass_ImportFromClass( e, l_classes, c );

		// radiant will crash if this is null, and it still could be at this point.
		if ( !e->comments ) {
			e->comments = strdup( "No description available, check documentation\n" );
		}

		// dump the spawnflags to the end of the comments.
		int i;
		bool havespawnflags;

		havespawnflags = false;
		for ( i = 0 ; i < MAX_FLAGS ; i++ )
		{
			if ( *e->flagnames[i] ) {
				havespawnflags = true;
			}
		}

		if ( havespawnflags ) {
			char spawnline[80];
			e->comments = addstr( e->comments,"Spawnflags\n" );
			for ( i = 0 ; i < MAX_FLAGS ; i++ )
			{
				if ( *e->flagnames[i] ) {
					sprintf( spawnline,"  %d - %s\n", 1 << i, e->flagnames[i] );
					e->comments = addstr( e->comments,spawnline );
				}
			}
		}

		Eclass_InsertAlphabetized( e );
		count++;
		// Hydra: ttimo, I don't think this code is required...
		// single ?
		*Get_Eclass_E() = e;
		Set_Eclass_Found( true );
		if ( Get_Parsing_Single() ) {
			break;
		}
	}

	Sys_Printf( "FGD Loaded %d entities.\n", count );
}

void Eclass_ScanFile( char *filename ){
	int size;
	char    *data;
	char temp[1024];
	GSList *l_classes = NULL;
	char token_debug[1024];  //++Hydra FIXME: cleanup this.
	bool done = false;
	int len,classtype;

	char *token = Token();

	QE_ConvertDOSToUnixName( temp, filename );

	size = vfsLoadFullPathFile( filename, (void**)&data );
	if ( size <= 0 ) {
		Sys_FPrintf( SYS_ERR, "Eclass_ScanFile: %s not found\n", filename );
		return;
	}
	Sys_Printf( "ScanFile: %s\n", temp );

	// start parsing the file
	StartTokenParsing( data );

	// build a list of base classes first

	while ( !done )
	{
		// find an @ sign.
		do
		{
			if ( !GetToken( true ) ) {
				done = true;
				break;
			}
		} while ( token[0] != '@' );

		strcpy( temp,token + 1 ); // skip the @

		classtype = CLASS_NOCLASS;
		if ( !stricmp( temp,"BaseClass" ) ) {
			classtype = CLASS_BASECLASS;
		}
		if ( !stricmp( temp,"PointClass" ) ) {
			classtype = CLASS_POINTCLASS;
		}
		if ( !stricmp( temp,"SolidClass" ) ) {
			classtype = CLASS_SOLIDCLASS;
		}

		if ( classtype ) {
			class_t *newclass = (class_t *) malloc( sizeof( class_s ) );
			memset( newclass, 0, sizeof( class_s ) );
			newclass->classtype = classtype;

			while ( 1 )
			{
				GetTokenExtra( false,"(",false ); // option or =
				strcpy( token_debug,token );

				if ( !strcmp( token,"=" ) ) {
					UnGetToken();
					break;
				}
				else
				{
					strlower( token );
					if ( !strcmp( token,"base" ) ) {
						GetTokenExtra( false,"(",true ); // (

						if ( !strcmp( token,"(" ) ) {
							while ( GetTokenExtra( false,",)",false ) ) // option) or option,
							{
								newclass->l_baselist = g_slist_append( newclass->l_baselist, strdup( token ) );

								GetTokenExtra( false,",)",true ); // , or )
								if ( !strcmp( token,")" ) ) {
									break;
								}

							}
						}
					}
					else if ( !strcmp( token,"size" ) ) {
						// parse (w h d) or (x y z, x y z)

						GetTokenExtra( false,"(",true ); // (
						if ( !strcmp( token,"(" ) ) {
							int sizedone = false;
							float w,h,d;
							GetToken( false );
							w = atof( token );
							GetToken( false );
							h = atof( token );
							GetToken( false ); // number) or number ,
							strcpy( temp,token );
							len = strlen( temp );
							if ( temp[len - 1] == ')' ) {
								sizedone = true;
							}
							temp[len - 1] = 0;
							d = atof( temp );
							if ( sizedone ) {
								// only one set of cordinates supplied, change the W,H,D to mins/maxs
								newclass->boundingbox[0][0] = 0 - ( w / 2 );
								newclass->boundingbox[1][0] = w / 2;
								newclass->boundingbox[0][1] = 0 - ( h / 2 );
								newclass->boundingbox[1][1] = h / 2;
								newclass->boundingbox[0][2] = 0 - ( d / 2 );
								newclass->boundingbox[1][2] = d / 2;
								newclass->gotsize = true;
							}
							else
							{
								newclass->boundingbox[0][0] = w;
								newclass->boundingbox[0][1] = h;
								newclass->boundingbox[0][2] = d;
								GetToken( false );
								newclass->boundingbox[1][0] = atof( token );
								GetToken( false );
								newclass->boundingbox[1][1] = atof( token );
/*
                GetToken(false); // "number)" or "number )"
                strcpy(temp,token);
                len = strlen(temp);
                if (temp[len-1] == ')')
                  temp[len-1] = 0;
                else
                  GetToken(false); // )
                newclass->boundingbox[1][2] = atof(temp);
 */
								GetTokenExtra( false,")",false ); // number
								newclass->boundingbox[1][2] = atof( token );
								newclass->gotsize = true;
								GetTokenExtra( false,")",true ); // )
							}
						}
					}
					else if ( !strcmp( token,"color" ) ) {
						GetTokenExtra( false,"(",true ); // (
						if ( !strcmp( token,"(" ) ) {
							// get the color values (0-255) and normalize them if required.
							GetToken( false );
							newclass->color[0] = atof( token );
							if ( newclass->color[0] > 1 ) {
								newclass->color[0] /= 255;
							}
							GetToken( false );
							newclass->color[1] = atof( token );
							if ( newclass->color[1] > 1 ) {
								newclass->color[1] /= 255;
							}
							GetToken( false );
							strcpy( temp,token );
							len = strlen( temp );
							if ( temp[len - 1] == ')' ) {
								temp[len - 1] = 0;
							}
							newclass->color[2] = atof( temp );
							if ( newclass->color[2] > 1 ) {
								newclass->color[2] /= 255;
							}
							newclass->gotcolor = true;
						}
					}
					else if ( !strcmp( token,"iconsprite" ) ) {
						GetTokenExtra( false,"(",true ); // (
						if ( !strcmp( token,"(" ) ) {
							GetTokenExtra( false,")",false ); // filename)
							// the model plugins will handle sprites too.
							// newclass->sprite = strdup(token);
							newclass->model = strdup( token );
							GetTokenExtra( false,")",true ); // )
						}
					}
					else if ( !strcmp( token,"model" ) ) {
						GetTokenExtra( false,"(",true ); // (
						if ( !strcmp( token,"(" ) ) {
							GetTokenExtra( false,")",false ); // filename)
							newclass->model = strdup( token );
							GetTokenExtra( false,")",true ); // )
						}
					}
					else
					{
						// Unsupported
						GetToken( false ); // skip it.
					}

				}
			}

			GetToken( false ); // =
			strcpy( token_debug,token );
			if ( !strcmp( token,"=" ) ) {
				GetToken( false );
				newclass->classname = strdup( token );
			}

			// Get the description
			if ( newclass->classtype != CLASS_BASECLASS ) {
				GetToken( false );
				if ( !strcmp( token,":" ) ) {
					GetToken( false );
					newclass->description = strdup( token );
				}
				else{ UnGetToken(); // no description
				}
			}

			// now build the option list.
			GetToken( true ); // [ or []

			if ( strcmp( token,"[]" ) ) { // got some options ?
				if ( !strcmp( token,"[" ) ) {
					// yup
					bool optioncomplete = false;
					option_t *newoption;

					while ( 1 )
					{
						GetToken( true );
						if ( !strcmp( token,"]" ) ) {
							break; // no more options

						}
						// parse the data and build the option_t

						strcpy( temp,token );
						len = strlen( temp );
						char *ptr = strchr( temp,'(' );

						if ( !ptr ) {
							break;
						}

						newoption = (option_t *) malloc( sizeof( option_s ) );
						memset( newoption, 0, sizeof( option_s ) );

						*ptr++ = 0;
						newoption->epairname = strdup( temp );

						len = strlen( ptr );
						if ( ptr[len - 1] != ')' ) {
							break;
						}

						ptr[len - 1] = 0;
						strlower( ptr );
						if ( !strcmp( ptr,"integer" ) ) {
							newoption->optiontype = OPTION_INTEGER;
						}
						else if ( !strcmp( ptr,"choices" ) ) {
							newoption->optiontype = OPTION_CHOICES;
						}
						else if ( !strcmp( ptr,"flags" ) ) {
							newoption->optiontype = OPTION_FLAGS;
						}
						else // string
						{
							newoption->optiontype = OPTION_STRING;
						}

						switch ( newoption->optiontype )
						{
						case OPTION_STRING:
						case OPTION_INTEGER:
							if ( !TokenAvailable() ) {
								optioncomplete = true;
								break;
							}
							GetToken( false ); // :
							strcpy( token_debug,token );
							if ( ( token[0] == ':' ) && ( strlen( token ) > 1 ) ) {
								newoption->optioninfo = strdup( token + 1 );
							}
							else
							{
								GetToken( false );
								newoption->optioninfo = strdup( token );
							}
							if ( TokenAvailable() ) { // default value ?
								GetToken( false );
								if ( !strcmp( token,":" ) ) {
									if ( GetToken( false ) ) {
										newoption->optiondefault = strdup( token );
										optioncomplete = true;
									}
								}
							}
							else
							{
								optioncomplete = true;
							}
							break;

						case OPTION_CHOICES:
							GetTokenExtra( false,":",true ); // : or :"something like this" (bah!)
							strcpy( token_debug,token );
							if ( ( token[0] == ':' ) && ( strlen( token ) > 1 ) ) {
								if ( token[1] == '\"' ) {
									strcpy( temp,token + 2 );
									while ( 1 )
									{
										if ( !GetToken( false ) ) {
											break;
										}
										strcat( temp," " );
										strcat( temp,token );
										len = strlen( temp );
										if ( temp[len - 1] == '\"' ) {
											temp[len - 1] = 0;
											break;
										}
									}
								}
								newoption->optioninfo = strdup( temp );
							}
							else
							{
								GetToken( false );
								newoption->optioninfo = strdup( token );
							}
							GetToken( false ); // : or =
							strcpy( token_debug,token );
							if ( !strcmp( token,":" ) ) {
								GetToken( false );
								newoption->optiondefault = strdup( token );
							}
							else
							{
								UnGetToken();
							}
						// And Follow on...
						case OPTION_FLAGS:
							GetToken( false ); // : or =
							strcpy( token_debug,token );
							if ( strcmp( token,"=" ) ) { // missing ?
								break;
							}

							GetToken( true ); // [
							strcpy( token_debug,token );
							if ( strcmp( token,"[" ) ) { // missing ?
								break;
							}

							choice_t *newchoice;
							while ( 1 )
							{
								GetTokenExtra( true,":",true ); // "]" or "number", or "number:"
								strcpy( token_debug,token );
								if ( !strcmp( token,"]" ) ) { // no more ?
									optioncomplete = true;
									break;
								}
								strcpy( temp,token );
								len = strlen( temp );
								if ( temp[len - 1] == ':' ) {
									temp[len - 1] = 0;
								}
								else
								{
									GetToken( false ); // :
									if ( strcmp( token,":" ) ) { // missing ?
										break;
									}
								}
								if ( !TokenAvailable() ) {
									break;
								}
								GetToken( false ); // the name

								newchoice = (choice_t *) malloc( sizeof( choice_s ) );
								memset( newchoice, 0, sizeof( choice_s ) );

								newchoice->value = atoi( temp );
								newchoice->name = strdup( token );

								newoption->choices = g_slist_append( newoption->choices, newchoice );

								// ignore any remaining tokens on the line
								while ( TokenAvailable() ) GetToken( false );

								// and it we found a "]" on the end of the line, put it back in the queue.
								if ( !strcmp( token,"]" ) ) {
									UnGetToken();
								}
							}
							break;

						}

						// add option to the newclass

						if ( optioncomplete ) {
							if ( newoption ) {
								// add it to the list.
								newclass->l_optionlist = g_slist_append( newclass->l_optionlist, newoption );
							}
						}
						else
						{
							Sys_Printf( "%WARNING: Parse error occured in '%s - %s'\n",classnames[newclass->classtype],newclass->classname );
							Free_Option( newoption );
						}

					}
				}
				else
				{
					UnGetToken(); // shouldn't get here.
				}
			}

			// add it to our list.
			l_classes = g_slist_append( l_classes, newclass );

		}
	}

	// finished with the file now.
	g_free( data );

	Sys_Printf( "FGD scan complete, building entities...\n" );

	// Once we get here we should have a few (!) lists in memory that we
	// can extract all the information required to build a the eclass_t structures.

	Create_EClasses( l_classes );

	// Free everything

	GSList *p = l_classes;
	while ( p )
	{
		class_t *tmpclass = (class_t *)p->data;

#ifdef FGD_VERBOSE
		// DEBUG: dump the info...
		Sys_Printf( "%s: %s (", classnames[tmpclass->classtype],tmpclass->classname );
		for ( GSList *tmp = tmpclass->l_baselist; tmp != NULL; tmp = tmp->next )
		{
			if ( tmp != tmpclass->l_baselist ) {
				Sys_Printf( ", " );
			}
			Sys_Printf( "%s", (char *)tmp->data );
		}
		if ( tmpclass->gotsize ) {
			sprintf( temp,"(%.0f %.0f %.0f) - (%.0f %.0f %.0f)",tmpclass->boundingbox[0][0],
					 tmpclass->boundingbox[0][1],
					 tmpclass->boundingbox[0][2],
					 tmpclass->boundingbox[1][0],
					 tmpclass->boundingbox[1][1],
					 tmpclass->boundingbox[1][2] );
		}
		else{ strcpy( temp,"No Size" ); }
		Sys_Printf( ") '%s' Size: %s",tmpclass->description ? tmpclass->description : "No description",temp );
		if ( tmpclass->gotcolor ) {
			sprintf( temp,"(%d %d %d)",tmpclass->color[0],
					 tmpclass->color[1],
					 tmpclass->color[2] );
		}
		else{ strcpy( temp,"No Color" ); }
		Sys_Printf( " Color: %s Options:\n",temp );
		if ( !tmpclass->l_optionlist ) {
			Sys_Printf( "  No Options\n" );
		}
		else
		{
			option_t *tmpoption;
			int count;
			GSList *olst;
			for ( olst = tmpclass->l_optionlist, count = 1; olst != NULL; olst = olst->next, count++ )
			{
				tmpoption = (option_t *)olst->data;
				Sys_Printf( "  %d, Type: %s, EPair: %s\n", count,optionnames[tmpoption->optiontype], tmpoption->epairname );

				choice_t *tmpchoice;
				GSList *clst;
				int ccount;
				for ( clst = tmpoption->choices, ccount = 1; clst != NULL; clst = clst->next, ccount++ )
				{
					tmpchoice = (choice_t *)clst->data;
					Sys_Printf( "    %d, Value: %d, Name: %s\n", ccount, tmpchoice->value, tmpchoice->name );
				}
			}
		}

#endif

		// free the baselist.
		ClearGSList( tmpclass->l_baselist );
		Free_Class( tmpclass );
		p = g_slist_remove( p, p->data );
	}

}
