/*
   Copyright (C) 2001-2006, William Joseph.
   All Rights Reserved.

   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
 */

#include "eclass_doom3.h"

#include "debugging/debugging.h"

#include <map>

#include "ifilesystem.h"
#include "iscriplib.h"
#include "iarchive.h"
#include "qerplugin.h"

#include "generic/callback.h"
#include "string/string.h"
#include "eclasslib.h"
#include "os/path.h"
#include "os/dir.h"
#include "stream/stringstream.h"
#include "moduleobservers.h"
#include "stringio.h"

class RawString
{
const char* m_value;
public:
RawString( const char* value ) : m_value( value ){
}
const char* c_str() const {
	return m_value;
}
};

inline bool operator<( const RawString& self, const RawString& other ){
	return string_less_nocase( self.c_str(), other.c_str() );
}

typedef std::map<RawString, EntityClass*> EntityClasses;
EntityClasses g_EntityClassDoom3_classes;
EntityClass *g_EntityClassDoom3_bad = 0;


void EntityClassDoom3_clear(){
	for ( EntityClasses::iterator i = g_EntityClassDoom3_classes.begin(); i != g_EntityClassDoom3_classes.end(); ++i )
	{
		( *i ).second->free( ( *i ).second );
	}
	g_EntityClassDoom3_classes.clear();
}

// entityClass will be inserted only if another of the same name does not already exist.
// if entityClass was inserted, the same object is returned, otherwise the already-existing object is returned.
EntityClass* EntityClassDoom3_insertUnique( EntityClass* entityClass ){
	return ( *g_EntityClassDoom3_classes.insert( EntityClasses::value_type( entityClass->name(), entityClass ) ).first ).second;
}

void EntityClassDoom3_forEach( EntityClassVisitor& visitor ){
	for ( EntityClasses::iterator i = g_EntityClassDoom3_classes.begin(); i != g_EntityClassDoom3_classes.end(); ++i )
	{
		visitor.visit( ( *i ).second );
	}
}

inline void printParseError( const char* message ){
	globalErrorStream() << message;
}

#define PARSE_RETURN_FALSE_IF_FAIL( expression ) do{ if ( !( expression ) ) { printParseError( FILE_LINE "\nparse failed: " # expression "\n" ); return false; } }while( 0 )

bool EntityClassDoom3_parseToken( Tokeniser& tokeniser ){
	const char* token = tokeniser.getToken();
	PARSE_RETURN_FALSE_IF_FAIL( token != 0 );
	return true;
}

bool EntityClassDoom3_parseToken( Tokeniser& tokeniser, const char* string ){
	const char* token = tokeniser.getToken();
	PARSE_RETURN_FALSE_IF_FAIL( token != 0 );
	return string_equal( token, string );
}

bool EntityClassDoom3_parseString( Tokeniser& tokeniser, const char*& s ){
	const char* token = tokeniser.getToken();
	PARSE_RETURN_FALSE_IF_FAIL( token != 0 );
	s = token;
	return true;
}

bool EntityClassDoom3_parseString( Tokeniser& tokeniser, CopiedString& s ){
	const char* token = tokeniser.getToken();
	PARSE_RETURN_FALSE_IF_FAIL( token != 0 );
	s = token;
	return true;
}

bool EntityClassDoom3_parseString( Tokeniser& tokeniser, StringOutputStream& s ){
	const char* token = tokeniser.getToken();
	PARSE_RETURN_FALSE_IF_FAIL( token != 0 );
	s << token;
	return true;
}

bool EntityClassDoom3_parseUnknown( Tokeniser& tokeniser ){
	//const char* name =
	PARSE_RETURN_FALSE_IF_FAIL( EntityClassDoom3_parseToken( tokeniser ) );

	//globalOutputStream() << "parsing unknown block " << makeQuoted(name) << "\n";

	PARSE_RETURN_FALSE_IF_FAIL( EntityClassDoom3_parseToken( tokeniser, "{" ) );
	tokeniser.nextLine();

	std::size_t depth = 1;
	for (;; )
	{
		const char* token;
		PARSE_RETURN_FALSE_IF_FAIL( EntityClassDoom3_parseString( tokeniser, token ) );
		if ( string_equal( token, "}" ) ) {
			if ( --depth == 0 ) {
				tokeniser.nextLine();
				break;
			}
		}
		else if ( string_equal( token, "{" ) ) {
			++depth;
		}
		tokeniser.nextLine();
	}
	return true;
}


class Model
{
public:
bool m_resolved;
CopiedString m_mesh;
CopiedString m_skin;
CopiedString m_parent;
typedef std::map<CopiedString, CopiedString> Anims;
Anims m_anims;
Model() : m_resolved( false ){
}
};

typedef std::map<CopiedString, Model> Models;

Models g_models;

void Model_resolveInheritance( const char* name, Model& model ){
	if ( model.m_resolved == false ) {
		model.m_resolved = true;

		if ( !string_empty( model.m_parent.c_str() ) ) {
			Models::iterator i = g_models.find( model.m_parent );
			if ( i == g_models.end() ) {
				globalErrorStream() << "model " << name << " inherits unknown model " << model.m_parent.c_str() << "\n";
			}
			else
			{
				Model_resolveInheritance( ( *i ).first.c_str(), ( *i ).second );
				model.m_mesh = ( *i ).second.m_mesh;
				model.m_skin = ( *i ).second.m_skin;
			}
		}
	}
}

bool EntityClassDoom3_parseModel( Tokeniser& tokeniser ){
	const char* name;
	PARSE_RETURN_FALSE_IF_FAIL( EntityClassDoom3_parseString( tokeniser, name ) );

	Model& model = g_models[name];

	PARSE_RETURN_FALSE_IF_FAIL( EntityClassDoom3_parseToken( tokeniser, "{" ) );
	tokeniser.nextLine();

	for (;; )
	{
		const char* parameter;
		PARSE_RETURN_FALSE_IF_FAIL( EntityClassDoom3_parseString( tokeniser, parameter ) );
		if ( string_equal( parameter, "}" ) ) {
			tokeniser.nextLine();
			break;
		}
		else if ( string_equal( parameter, "inherit" ) ) {
			PARSE_RETURN_FALSE_IF_FAIL( EntityClassDoom3_parseString( tokeniser, model.m_parent ) );
			tokeniser.nextLine();
		}
		else if ( string_equal( parameter, "remove" ) ) {
			//const char* remove =
			PARSE_RETURN_FALSE_IF_FAIL( EntityClassDoom3_parseToken( tokeniser ) );
			tokeniser.nextLine();
		}
		else if ( string_equal( parameter, "mesh" ) ) {
			PARSE_RETURN_FALSE_IF_FAIL( EntityClassDoom3_parseString( tokeniser, model.m_mesh ) );
			tokeniser.nextLine();
		}
		else if ( string_equal( parameter, "skin" ) ) {
			PARSE_RETURN_FALSE_IF_FAIL( EntityClassDoom3_parseString( tokeniser, model.m_skin ) );
			tokeniser.nextLine();
		}
		else if ( string_equal( parameter, "offset" ) ) {
			PARSE_RETURN_FALSE_IF_FAIL( EntityClassDoom3_parseToken( tokeniser, "(" ) );
			PARSE_RETURN_FALSE_IF_FAIL( EntityClassDoom3_parseToken( tokeniser ) );
			PARSE_RETURN_FALSE_IF_FAIL( EntityClassDoom3_parseToken( tokeniser ) );
			PARSE_RETURN_FALSE_IF_FAIL( EntityClassDoom3_parseToken( tokeniser ) );
			PARSE_RETURN_FALSE_IF_FAIL( EntityClassDoom3_parseToken( tokeniser, ")" ) );
			tokeniser.nextLine();
		}
		else if ( string_equal( parameter, "channel" ) ) {
			//const char* channelName =
			PARSE_RETURN_FALSE_IF_FAIL( EntityClassDoom3_parseToken( tokeniser ) );
			PARSE_RETURN_FALSE_IF_FAIL( EntityClassDoom3_parseToken( tokeniser, "(" ) );
			for (;; )
			{
				const char* end;
				PARSE_RETURN_FALSE_IF_FAIL( EntityClassDoom3_parseString( tokeniser, end ) );
				if ( string_equal( end, ")" ) ) {
					tokeniser.nextLine();
					break;
				}
			}
		}
		else if ( string_equal( parameter, "anim" ) ) {
			CopiedString animName;
			PARSE_RETURN_FALSE_IF_FAIL( EntityClassDoom3_parseString( tokeniser, animName ) );
			const char* animFile;
			PARSE_RETURN_FALSE_IF_FAIL( EntityClassDoom3_parseString( tokeniser, animFile ) );
			model.m_anims.insert( Model::Anims::value_type( animName, animFile ) );

			const char* token;
			PARSE_RETURN_FALSE_IF_FAIL( EntityClassDoom3_parseString( tokeniser, token ) );

			while ( string_equal( token, "," ) )
			{
				PARSE_RETURN_FALSE_IF_FAIL( EntityClassDoom3_parseString( tokeniser, animFile ) );
				PARSE_RETURN_FALSE_IF_FAIL( EntityClassDoom3_parseString( tokeniser, token ) );
			}

			if ( string_equal( token, "{" ) ) {
				for (;; )
				{
					const char* end;
					PARSE_RETURN_FALSE_IF_FAIL( EntityClassDoom3_parseString( tokeniser, end ) );
					if ( string_equal( end, "}" ) ) {
						tokeniser.nextLine();
						break;
					}
					tokeniser.nextLine();
				}
			}
			else
			{
				tokeniser.ungetToken();
			}
		}
		else
		{
			globalErrorStream() << "unknown model parameter: " << makeQuoted( parameter ) << "\n";
			return false;
		}
		tokeniser.nextLine();
	}
	return true;
}

inline bool char_isSpaceOrTab( char c ){
	return c == ' ' || c == '\t';
}

inline bool char_isNotSpaceOrTab( char c ){
	return !char_isSpaceOrTab( c );
}

template<typename Predicate>
inline const char* string_find_if( const char* string, Predicate predicate ){
	for (; *string != 0; ++string )
	{
		if ( predicate( *string ) ) {
			return string;
		}
	}
	return string;
}

inline const char* string_findFirstSpaceOrTab( const char* string ){
	return string_find_if( string, char_isSpaceOrTab );
}

inline const char* string_findFirstNonSpaceOrTab( const char* string ){
	return string_find_if( string, char_isNotSpaceOrTab );
}


static bool EntityClass_parse( EntityClass& entityClass, Tokeniser& tokeniser ){
	PARSE_RETURN_FALSE_IF_FAIL( EntityClassDoom3_parseString( tokeniser, entityClass.m_name ) );

	PARSE_RETURN_FALSE_IF_FAIL( EntityClassDoom3_parseToken( tokeniser, "{" ) );
	tokeniser.nextLine();

	StringOutputStream usage( 256 );
	StringOutputStream description( 256 );
	CopiedString* currentDescription = 0;
	StringOutputStream* currentString = 0;

	for (;; )
	{
		const char* key;
		PARSE_RETURN_FALSE_IF_FAIL( EntityClassDoom3_parseString( tokeniser, key ) );

		const char* last = string_findFirstSpaceOrTab( key );
		CopiedString first( StringRange( key, last ) );

		if ( !string_empty( last ) ) {
			last = string_findFirstNonSpaceOrTab( last );
		}

		if ( currentString != 0 && string_equal( key, "\\" ) ) {
			tokeniser.nextLine();
			*currentString << " ";
			PARSE_RETURN_FALSE_IF_FAIL( EntityClassDoom3_parseString( tokeniser, *currentString ) );
			continue;
		}

		if ( currentDescription != 0 ) {
			*currentDescription = description.c_str();
			description.clear();
			currentDescription = 0;
		}
		currentString = 0;

		if ( string_equal( key, "}" ) ) {
			tokeniser.nextLine();
			break;
		}
		else if ( string_equal( key, "model" ) ) {
			const char* token;
			PARSE_RETURN_FALSE_IF_FAIL( EntityClassDoom3_parseString( tokeniser, token ) );
			entityClass.fixedsize = true;
			StringOutputStream buffer( 256 );
			buffer << PathCleaned( token );
			entityClass.m_modelpath = buffer.c_str();
		}
		else if ( string_equal( key, "editor_color" ) ) {
			const char* value;
			PARSE_RETURN_FALSE_IF_FAIL( EntityClassDoom3_parseString( tokeniser, value ) );
			if ( !string_empty( value ) ) {
				entityClass.colorSpecified = true;
				bool success = string_parse_vector3( value, entityClass.color );
				ASSERT_MESSAGE( success, "editor_color: parse error" );
			}
		}
		else if ( string_equal( key, "editor_ragdoll" ) ) {
			//bool ragdoll = atoi(tokeniser.getToken()) != 0;
			PARSE_RETURN_FALSE_IF_FAIL( EntityClassDoom3_parseToken( tokeniser ) );
		}
		else if ( string_equal( key, "editor_mins" ) ) {
			entityClass.sizeSpecified = true;
			const char* value;
			PARSE_RETURN_FALSE_IF_FAIL( EntityClassDoom3_parseString( tokeniser, value ) );
			if ( !string_empty( value ) && !string_equal( value, "?" ) ) {
				entityClass.fixedsize = true;
				bool success = string_parse_vector3( value, entityClass.mins );
				ASSERT_MESSAGE( success, "editor_mins: parse error" );
			}
		}
		else if ( string_equal( key, "editor_maxs" ) ) {
			entityClass.sizeSpecified = true;
			const char* value;
			PARSE_RETURN_FALSE_IF_FAIL( EntityClassDoom3_parseString( tokeniser, value ) );
			if ( !string_empty( value ) && !string_equal( value, "?" ) ) {
				entityClass.fixedsize = true;
				bool success = string_parse_vector3( value, entityClass.maxs );
				ASSERT_MESSAGE( success, "editor_maxs: parse error" );
			}
		}
		else if ( string_equal( key, "editor_usage" ) ) {
			PARSE_RETURN_FALSE_IF_FAIL( EntityClassDoom3_parseString( tokeniser, usage ) );
			currentString = &usage;
		}
		else if ( string_equal_n( key, "editor_usage", 12 ) ) {
			PARSE_RETURN_FALSE_IF_FAIL( EntityClassDoom3_parseString( tokeniser, usage ) );
			currentString = &usage;
		}
		else if ( string_equal( key, "editor_rotatable" )
				  || string_equal( key, "editor_showangle" )
				  || string_equal( key, "editor_showangles" ) // typo? in prey movables.def
				  || string_equal( key, "editor_mover" )
				  || string_equal( key, "editor_model" )
				  || string_equal( key, "editor_material" )
				  || string_equal( key, "editor_combatnode" )
				  || ( !string_empty( last ) && string_equal( first.c_str(), "editor_gui" ) )
				  || string_equal_n( key, "editor_copy", 11 ) ) {
			PARSE_RETURN_FALSE_IF_FAIL( EntityClassDoom3_parseToken( tokeniser ) );
		}
		else if ( !string_empty( last ) && ( string_equal( first.c_str(), "editor_var" ) || string_equal( first.c_str(), "editor_string" ) ) ) {
			EntityClassAttribute& attribute = EntityClass_insertAttribute( entityClass, last ).second;
			attribute.m_type = "string";
			currentDescription = &attribute.m_description;
			currentString = &description;
			PARSE_RETURN_FALSE_IF_FAIL( EntityClassDoom3_parseString( tokeniser, description ) );
		}
		else if ( !string_empty( last ) && string_equal( first.c_str(), "editor_float" ) ) {
			EntityClassAttribute& attribute = EntityClass_insertAttribute( entityClass, last ).second;
			attribute.m_type = "string";
			currentDescription = &attribute.m_description;
			currentString = &description;
			PARSE_RETURN_FALSE_IF_FAIL( EntityClassDoom3_parseString( tokeniser, description ) );
		}
		else if ( !string_empty( last ) && string_equal( first.c_str(), "editor_snd" ) ) {
			EntityClassAttribute& attribute = EntityClass_insertAttribute( entityClass, last ).second;
			attribute.m_type = "sound";
			currentDescription = &attribute.m_description;
			currentString = &description;
			PARSE_RETURN_FALSE_IF_FAIL( EntityClassDoom3_parseString( tokeniser, description ) );
		}
		else if ( !string_empty( last ) && string_equal( first.c_str(), "editor_bool" ) ) {
			EntityClassAttribute& attribute = EntityClass_insertAttribute( entityClass, last ).second;
			attribute.m_type = "boolean";
			currentDescription = &attribute.m_description;
			currentString = &description;
			PARSE_RETURN_FALSE_IF_FAIL( EntityClassDoom3_parseString( tokeniser, description ) );
		}
		else if ( !string_empty( last ) && string_equal( first.c_str(), "editor_int" ) ) {
			EntityClassAttribute& attribute = EntityClass_insertAttribute( entityClass, last ).second;
			attribute.m_type = "integer";
			currentDescription = &attribute.m_description;
			currentString = &description;
			PARSE_RETURN_FALSE_IF_FAIL( EntityClassDoom3_parseString( tokeniser, description ) );
		}
		else if ( !string_empty( last ) && string_equal( first.c_str(), "editor_model" ) ) {
			EntityClassAttribute& attribute = EntityClass_insertAttribute( entityClass, last ).second;
			attribute.m_type = "model";
			currentDescription = &attribute.m_description;
			currentString = &description;
			PARSE_RETURN_FALSE_IF_FAIL( EntityClassDoom3_parseString( tokeniser, description ) );
		}
		else if ( !string_empty( last ) && string_equal( first.c_str(), "editor_color" ) ) {
			EntityClassAttribute& attribute = EntityClass_insertAttribute( entityClass, last ).second;
			attribute.m_type = "color";
			currentDescription = &attribute.m_description;
			currentString = &description;
			PARSE_RETURN_FALSE_IF_FAIL( EntityClassDoom3_parseString( tokeniser, description ) );
		}
		else if ( !string_empty( last ) && ( string_equal( first.c_str(), "editor_material" ) || string_equal( first.c_str(), "editor_mat" ) ) ) {
			EntityClassAttribute& attribute = EntityClass_insertAttribute( entityClass, last ).second;
			attribute.m_type = "shader";
			currentDescription = &attribute.m_description;
			currentString = &description;
			PARSE_RETURN_FALSE_IF_FAIL( EntityClassDoom3_parseString( tokeniser, description ) );
		}
		else if ( string_equal( key, "inherit" ) ) {
			entityClass.inheritanceResolved = false;
			ASSERT_MESSAGE( entityClass.m_parent.empty(), "only one 'inherit' supported per entityDef" );
			const char* token;
			PARSE_RETURN_FALSE_IF_FAIL( EntityClassDoom3_parseString( tokeniser, token ) );
			entityClass.m_parent.push_back( token );
		}
		// begin quake4-specific keys
		else if ( string_equal( key, "editor_targetonsel" ) ) {
			//const char* value =
			PARSE_RETURN_FALSE_IF_FAIL( EntityClassDoom3_parseToken( tokeniser ) );
		}
		else if ( string_equal( key, "editor_menu" ) ) {
			//const char* value =
			PARSE_RETURN_FALSE_IF_FAIL( EntityClassDoom3_parseToken( tokeniser ) );
		}
		else if ( string_equal( key, "editor_ignore" ) ) {
			//const char* value =
			PARSE_RETURN_FALSE_IF_FAIL( EntityClassDoom3_parseToken( tokeniser ) );
		}
		// end quake4-specific keys
		// begin ignore prey (unknown/unused?) entity keys
		else if ( string_equal( key, "editor_light" )
				  || string_equal( key, "editor_def def_debrisspawner" )
				  || string_equal( key, "editor_def def_drop" )
				  || string_equal( key, "editor_def def_guihand" )
				  || string_equal( key, "editor_def def_mine" ) ) {
			//const char* value =
			PARSE_RETURN_FALSE_IF_FAIL( EntityClassDoom3_parseToken( tokeniser ) );
		}
		// end ignore prey entity keys
		else
		{
			CopiedString tmp( key );
			if ( string_equal_n( key, "editor_", 7 ) ) {
				globalErrorStream() << "unsupported editor key " << makeQuoted( key ) ;
			}
			EntityClassAttribute& attribute = EntityClass_insertAttribute( entityClass, key ).second;
			attribute.m_type = "string";
			const char* value = NULL;
			PARSE_RETURN_FALSE_IF_FAIL( EntityClassDoom3_parseString( tokeniser, value ) );
			if ( string_equal( value, "}" ) ) { // hack for quake4 powerups.def bug
				globalErrorStream() << "entityDef " << makeQuoted( entityClass.m_name.c_str() ) << " key " << makeQuoted( tmp.c_str() ) << " has no value\n";
				break;
			}
			else
			{
				attribute.m_value = value;
			}
		}
		tokeniser.nextLine();
	}

	entityClass.m_comments = usage.c_str();

	if ( string_equal( entityClass.m_name.c_str(), "light" ) ) {
		{
			EntityClassAttribute& attribute = EntityClass_insertAttribute( entityClass, "light_radius" ).second;
			attribute.m_type = "vector3";
			attribute.m_value = "300 300 300";
		}
		{
			EntityClassAttribute& attribute = EntityClass_insertAttribute( entityClass, "light_center" ).second;
			attribute.m_type = "vector3";
		}
		{
			EntityClassAttribute& attribute = EntityClass_insertAttribute( entityClass, "noshadows" ).second;
			attribute.m_type = "boolean";
			attribute.m_value = "0";
		}
		{
			EntityClassAttribute& attribute = EntityClass_insertAttribute( entityClass, "nospecular" ).second;
			attribute.m_type = "boolean";
			attribute.m_value = "0";
		}
		{
			EntityClassAttribute& attribute = EntityClass_insertAttribute( entityClass, "nodiffuse" ).second;
			attribute.m_type = "boolean";
			attribute.m_value = "0";
		}
		{
			EntityClassAttribute& attribute = EntityClass_insertAttribute( entityClass, "falloff" ).second;
			attribute.m_type = "real";
		}
	}

	return true;
}

bool EntityClassDoom3_parseEntityDef( Tokeniser& tokeniser ){
	EntityClass* entityClass = Eclass_Alloc();
	entityClass->free = &Eclass_Free;

	if ( !EntityClass_parse( *entityClass, tokeniser ) ) {
		eclass_capture_state( entityClass ); // finish constructing the entity so that it can be destroyed cleanly.
		entityClass->free( entityClass );
		return false;
	}

	EntityClass* inserted = EntityClassDoom3_insertUnique( entityClass );
	if ( inserted != entityClass ) {
		globalErrorStream() << "entityDef " << entityClass->name() << " is already defined, second definition ignored\n";
		eclass_capture_state( entityClass ); // finish constructing the entity so that it can be destroyed cleanly.
		entityClass->free( entityClass );
	}
	return true;
}

bool EntityClassDoom3_parseBlock( Tokeniser& tokeniser, const char* blockType ){
	if ( string_equal( blockType, "entityDef" ) ) {
		return EntityClassDoom3_parseEntityDef( tokeniser );
	}
	else if ( string_equal( blockType, "model" ) ) {
		return EntityClassDoom3_parseModel( tokeniser );
	}
	else
	{
		return EntityClassDoom3_parseUnknown( tokeniser );
	}
}

bool EntityClassDoom3_parse( TextInputStream& inputStream, const char* filename ){
	Tokeniser& tokeniser = GlobalScriptLibrary().m_pfnNewScriptTokeniser( inputStream );

	tokeniser.nextLine();

	for (;; )
	{
		const char* blockType = tokeniser.getToken();
		if ( blockType == 0 ) {
			return true;
		}
		CopiedString tmp( blockType );
		if ( !EntityClassDoom3_parseBlock( tokeniser, tmp.c_str() ) ) {
			globalErrorStream() << GlobalFileSystem().findFile( filename ) << filename << ":" << (unsigned int)tokeniser.getLine() << ": " << tmp.c_str() << " parse failed, skipping rest of file\n";
			return false;
		}
	}

	tokeniser.release();
}


void EntityClassDoom3_loadFile( const char* filename ){
	globalOutputStream() << "parsing entity classes from " << makeQuoted( filename ) << "\n";

	StringOutputStream fullname( 256 );
	fullname << "def/" << filename;

	ArchiveTextFile* file = GlobalFileSystem().openTextFile( fullname.c_str() );
	if ( file != 0 ) {
		EntityClassDoom3_parse( file->getInputStream(), fullname.c_str() );
		file->release();
	}
}

EntityClass* EntityClassDoom3_findOrInsert( const char *name, bool has_brushes ){
	ASSERT_NOTNULL( name );

	if ( string_empty( name ) ) {
		return g_EntityClassDoom3_bad;
	}

	EntityClasses::iterator i = g_EntityClassDoom3_classes.find( name );
	if ( i != g_EntityClassDoom3_classes.end()
	     //&& string_equal((*i).first, name)
		 ) {
		return ( *i ).second;
	}

	EntityClass* e = EntityClass_Create_Default( name, has_brushes );
	EntityClass* inserted = EntityClassDoom3_insertUnique( e );
	ASSERT_MESSAGE( inserted == e, "" );
	return inserted;
}

const ListAttributeType* EntityClassDoom3_findListType( const char* name ){
	return 0;
}


void EntityClass_resolveInheritance( EntityClass* derivedClass ){
	if ( derivedClass->inheritanceResolved == false ) {
		derivedClass->inheritanceResolved = true;
		EntityClasses::iterator i = g_EntityClassDoom3_classes.find( derivedClass->m_parent.front().c_str() );
		if ( i == g_EntityClassDoom3_classes.end() ) {
			globalErrorStream() << "failed to find entityDef " << makeQuoted( derivedClass->m_parent.front().c_str() ) << " inherited by "  << makeQuoted( derivedClass->m_name.c_str() ) << "\n";
		}
		else
		{
			EntityClass* parentClass = ( *i ).second;
			EntityClass_resolveInheritance( parentClass );
			if ( !derivedClass->colorSpecified ) {
				derivedClass->colorSpecified = parentClass->colorSpecified;
				derivedClass->color = parentClass->color;
			}
			if ( !derivedClass->sizeSpecified ) {
				derivedClass->sizeSpecified = parentClass->sizeSpecified;
				derivedClass->mins = parentClass->mins;
				derivedClass->maxs = parentClass->maxs;
				derivedClass->fixedsize = parentClass->fixedsize;
			}

			for ( EntityClassAttributes::iterator j = parentClass->m_attributes.begin(); j != parentClass->m_attributes.end(); ++j )
			{
				EntityClass_insertAttribute( *derivedClass, ( *j ).first.c_str(), ( *j ).second );
			}
		}
	}
}

class EntityClassDoom3 : public ModuleObserver
{
std::size_t m_unrealised;
ModuleObservers m_observers;
public:
EntityClassDoom3() : m_unrealised( 2 ){
}
void realise(){
	if ( --m_unrealised == 0 ) {
		globalOutputStream() << "searching vfs directory " << makeQuoted( "def" ) << " for *.def\n";
		GlobalFileSystem().forEachFile( "def/", "def", makeCallbackF(EntityClassDoom3_loadFile) );

		{
			for ( Models::iterator i = g_models.begin(); i != g_models.end(); ++i )
			{
				Model_resolveInheritance( ( *i ).first.c_str(), ( *i ).second );
			}
		}
		{
			for ( EntityClasses::iterator i = g_EntityClassDoom3_classes.begin(); i != g_EntityClassDoom3_classes.end(); ++i )
			{
				EntityClass_resolveInheritance( ( *i ).second );
				if ( !string_empty( ( *i ).second->m_modelpath.c_str() ) ) {
					Models::iterator j = g_models.find( ( *i ).second->m_modelpath );
					if ( j != g_models.end() ) {
						( *i ).second->m_modelpath = ( *j ).second.m_mesh;
						( *i ).second->m_skin = ( *j ).second.m_skin;
					}
				}
				eclass_capture_state( ( *i ).second );

				StringOutputStream usage( 256 );

				usage << "-------- NOTES --------\n";

				if ( !string_empty( ( *i ).second->m_comments.c_str() ) ) {
					usage << ( *i ).second->m_comments.c_str() << "\n";
				}

				usage << "\n-------- KEYS --------\n";

				for ( EntityClassAttributes::iterator j = ( *i ).second->m_attributes.begin(); j != ( *i ).second->m_attributes.end(); ++j )
				{
					const char* name = EntityClassAttributePair_getName( *j );
					const char* description = EntityClassAttributePair_getDescription( *j );
					if ( !string_equal( name, description ) ) {
						usage << EntityClassAttributePair_getName( *j ) << " : " << EntityClassAttributePair_getDescription( *j ) << "\n";
					}
				}

				( *i ).second->m_comments = usage.c_str();
			}
		}

		m_observers.realise();
	}
}
void unrealise(){
	if ( ++m_unrealised == 1 ) {
		m_observers.unrealise();
		EntityClassDoom3_clear();
	}
}
void attach( ModuleObserver& observer ){
	m_observers.attach( observer );
}
void detach( ModuleObserver& observer ){
	m_observers.detach( observer );
}
};

EntityClassDoom3 g_EntityClassDoom3;

void EntityClassDoom3_attach( ModuleObserver& observer ){
	g_EntityClassDoom3.attach( observer );
}
void EntityClassDoom3_detach( ModuleObserver& observer ){
	g_EntityClassDoom3.detach( observer );
}

void EntityClassDoom3_realise(){
	g_EntityClassDoom3.realise();
}
void EntityClassDoom3_unrealise(){
	g_EntityClassDoom3.unrealise();
}

void EntityClassDoom3_construct(){
	GlobalFileSystem().attach( g_EntityClassDoom3 );

	// start by creating the default unknown eclass
	g_EntityClassDoom3_bad = EClass_Create( "UNKNOWN_CLASS", Vector3( 0.0f, 0.5f, 0.0f ), "" );

	EntityClassDoom3_realise();
}

void EntityClassDoom3_destroy(){
	EntityClassDoom3_unrealise();

	g_EntityClassDoom3_bad->free( g_EntityClassDoom3_bad );

	GlobalFileSystem().detach( g_EntityClassDoom3 );
}

class EntityClassDoom3Dependencies : public GlobalFileSystemModuleRef, public GlobalShaderCacheModuleRef
{
};

class EntityClassDoom3API
{
EntityClassManager m_eclassmanager;
public:
typedef EntityClassManager Type;
STRING_CONSTANT( Name, "doom3" );

EntityClassDoom3API(){
	EntityClassDoom3_construct();

	m_eclassmanager.findOrInsert = &EntityClassDoom3_findOrInsert;
	m_eclassmanager.findListType = &EntityClassDoom3_findListType;
	m_eclassmanager.forEach = &EntityClassDoom3_forEach;
	m_eclassmanager.attach = &EntityClassDoom3_attach;
	m_eclassmanager.detach = &EntityClassDoom3_detach;
	m_eclassmanager.realise = &EntityClassDoom3_realise;
	m_eclassmanager.unrealise = &EntityClassDoom3_unrealise;
}
~EntityClassDoom3API(){
	EntityClassDoom3_destroy();
}
EntityClassManager* getTable(){
	return &m_eclassmanager;
}
};

#include "modulesystem/singletonmodule.h"
#include "modulesystem/moduleregistry.h"

typedef SingletonModule<EntityClassDoom3API, EntityClassDoom3Dependencies> EntityClassDoom3Module;
typedef Static<EntityClassDoom3Module> StaticEntityClassDoom3Module;
StaticRegisterModule staticRegisterEntityClassDoom3( StaticEntityClassDoom3Module::instance() );
