/*
   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_fgd.h"

#include "debugging/debugging.h"

#include <map>

#include "ifilesystem.h"
#include "iscriplib.h"
#include "qerplugin.h"
#include "mainframe.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"
#include "stream/textfilestream.h"

namespace
{
typedef std::map<const char*, EntityClass*, RawStringLessNoCase> EntityClasses;
EntityClasses g_EntityClassFGD_classes;
typedef std::map<const char*, EntityClass*, RawStringLess> BaseClasses;
BaseClasses g_EntityClassFGD_bases;
EntityClass   *g_EntityClassFGD_bad = 0;
typedef std::map<CopiedString, ListAttributeType> ListAttributeTypes;
ListAttributeTypes g_listTypesFGD;
}


void EntityClassFGD_clear(){
	for ( BaseClasses::iterator i = g_EntityClassFGD_bases.begin(); i != g_EntityClassFGD_bases.end(); ++i )
	{
		( *i ).second->free( ( *i ).second );
	}
	g_EntityClassFGD_bases.clear();
	g_listTypesFGD.clear();
}

EntityClass* EntityClassFGD_insertUniqueBase( EntityClass* entityClass ){
	std::pair<BaseClasses::iterator, bool> result = g_EntityClassFGD_bases.insert( BaseClasses::value_type( entityClass->name(), entityClass ) );
	if ( !result.second ) {
		globalErrorStream() << "duplicate base class: " << makeQuoted( entityClass->name() ) << "\n";
		//eclass_capture_state(entityClass);
		//entityClass->free(entityClass);
	}
	return ( *result.first ).second;
}

EntityClass* EntityClassFGD_insertUnique( EntityClass* entityClass ){
	EntityClassFGD_insertUniqueBase( entityClass );
	std::pair<EntityClasses::iterator, bool> result = g_EntityClassFGD_classes.insert( EntityClasses::value_type( entityClass->name(), entityClass ) );
	if ( !result.second ) {
		globalErrorStream() << "duplicate entity class: " << makeQuoted( entityClass->name() ) << "\n";
		eclass_capture_state( entityClass );
		entityClass->free( entityClass );
	}
	return ( *result.first ).second;
}

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

#define PARSE_ERROR "error parsing fgd entity class definition at line " << tokeniser.getLine() << ':' << tokeniser.getColumn()

static bool s_fgd_warned = false;

inline bool EntityClassFGD_parseToken( Tokeniser& tokeniser, const char* token ){
	const bool w = s_fgd_warned;
	const bool ok = string_equal( tokeniser.getToken(), token );
	if( !ok ){
		globalErrorStream() << PARSE_ERROR << "\nExpected " << makeQuoted( token ) << '\n';
		s_fgd_warned = true;
}
	return w || ok;
}

/* FIXME
#define ERROR_FGD( message )\
do{\
	if( s_fgd_warned )\
		globalErrorStream() << message << '\n';\
	else{\
		ERROR_MESSAGE( message );\
		s_fgd_warned = true;\
	}\
}while( 0 )
*/
#define ERROR_FGD( message ) {}

void EntityClassFGD_parseSplitString( Tokeniser& tokeniser, CopiedString& string ){
	StringOutputStream buffer( 256 );
	for (;; )
	{
		buffer << tokeniser.getToken();
		if ( !string_equal( tokeniser.getToken(), "+" ) ) {
			tokeniser.ungetToken();
			string = buffer.c_str();
			return;
		}
	}
}

void EntityClassFGD_parseClass( Tokeniser& tokeniser, bool fixedsize, bool isBase ){
	EntityClass* entityClass = Eclass_Alloc();
	entityClass->free = &Eclass_Free;
	entityClass->fixedsize = fixedsize;
	entityClass->inheritanceResolved = false;
	entityClass->mins = Vector3( -8, -8, -8 );
	entityClass->maxs = Vector3( 8, 8, 8 );

	for (;; )
	{
		const char* property = tokeniser.getToken();
		if ( string_equal( property, "=" ) ) {
			break;
		}
		else if ( string_equal( property, "base" ) ) {
			ASSERT_MESSAGE( EntityClassFGD_parseToken( tokeniser, "(" ), PARSE_ERROR );
			for (;; )
			{
				const char* base = tokeniser.getToken();
				if ( string_equal( base, ")" ) ) {
					break;
				}
				else if ( !string_equal( base, "," ) ) {
					entityClass->m_parent.push_back( base );
				}
			}
		}
		else if ( string_equal( property, "size" ) ) {
			entityClass->sizeSpecified = true;
			ASSERT_MESSAGE( EntityClassFGD_parseToken( tokeniser, "(" ), PARSE_ERROR );
			Tokeniser_getFloat( tokeniser, entityClass->mins.x() );
			Tokeniser_getFloat( tokeniser, entityClass->mins.y() );
			Tokeniser_getFloat( tokeniser, entityClass->mins.z() );
			const char* token = tokeniser.getToken();
			if ( string_equal( token, "," ) ) {
				Tokeniser_getFloat( tokeniser, entityClass->maxs.x() );
				Tokeniser_getFloat( tokeniser, entityClass->maxs.y() );
				Tokeniser_getFloat( tokeniser, entityClass->maxs.z() );
				ASSERT_MESSAGE( EntityClassFGD_parseToken( tokeniser, ")" ), PARSE_ERROR );
			}
			else
			{
				entityClass->maxs = entityClass->mins;
				vector3_negate( entityClass->mins );
				ASSERT_MESSAGE( string_equal( token, ")" ), "" );
			}
		}
		else if ( string_equal( property, "color" ) ) {
			entityClass->colorSpecified = true;
			ASSERT_MESSAGE( EntityClassFGD_parseToken( tokeniser, "(" ), PARSE_ERROR );
			Tokeniser_getFloat( tokeniser, entityClass->color.x() );
			entityClass->color.x() /= 256.0;
			Tokeniser_getFloat( tokeniser, entityClass->color.y() );
			entityClass->color.y() /= 256.0;
			Tokeniser_getFloat( tokeniser, entityClass->color.z() );
			entityClass->color.z() /= 256.0;
			ASSERT_MESSAGE( EntityClassFGD_parseToken( tokeniser, ")" ), PARSE_ERROR );
		}
		else if ( string_equal( property, "iconsprite" ) ) {
			ASSERT_MESSAGE( EntityClassFGD_parseToken( tokeniser, "(" ), PARSE_ERROR );
			StringOutputStream buffer( 256 );
			buffer << PathCleaned( tokeniser.getToken() );
			entityClass->m_modelpath = buffer.c_str();
			ASSERT_MESSAGE( EntityClassFGD_parseToken( tokeniser, ")" ), PARSE_ERROR );
		}
		else if ( string_equal( property, "sprite" )
				  || string_equal( property, "decal" )
		          // hl2 below
				  || string_equal( property, "overlay" )
				  || string_equal( property, "light" )
				  || string_equal( property, "keyframe" )
				  || string_equal( property, "animator" )
				  || string_equal( property, "quadbounds" ) ) {
			ASSERT_MESSAGE( EntityClassFGD_parseToken( tokeniser, "(" ), PARSE_ERROR );
			ASSERT_MESSAGE( EntityClassFGD_parseToken( tokeniser, ")" ), PARSE_ERROR );
		}
		// hl2 below
		else if ( string_equal( property, "sphere" )
				  || string_equal( property, "sweptplayerhull" )
				  || string_equal( property, "studioprop" )
				  || string_equal( property, "lightprop" )
				  || string_equal( property, "lightcone" )
				  || string_equal( property, "sidelist" ) ) {
			ASSERT_MESSAGE( EntityClassFGD_parseToken( tokeniser, "(" ), PARSE_ERROR );
			if ( string_equal( tokeniser.getToken(), ")" ) ) {
				tokeniser.ungetToken();
			}
			ASSERT_MESSAGE( EntityClassFGD_parseToken( tokeniser, ")" ), PARSE_ERROR );
		}
		else if ( string_equal( property, "studio" ) ) {
			ASSERT_MESSAGE( EntityClassFGD_parseToken( tokeniser, "(" ), PARSE_ERROR );
			const char *token = tokeniser.getToken();
			if ( string_equal( token, ")" ) ) {
				tokeniser.ungetToken();
			}
			else{
				entityClass->m_modelpath = token;
			}
			ASSERT_MESSAGE( EntityClassFGD_parseToken( tokeniser, ")" ), PARSE_ERROR );
		}
		else if ( string_equal( property, "line" )
				  || string_equal( property, "cylinder" ) ) {
			ASSERT_MESSAGE( EntityClassFGD_parseToken( tokeniser, "(" ), PARSE_ERROR );
			//const char* r =
			tokeniser.getToken();
			//const char* g =
			tokeniser.getToken();
			//const char* b =
			tokeniser.getToken();
			for (;; )
			{
				if ( string_equal( tokeniser.getToken(), ")" ) ) {
					tokeniser.ungetToken();
					break;
				}
				//const char* name =
				tokeniser.getToken();
			}
			ASSERT_MESSAGE( EntityClassFGD_parseToken( tokeniser, ")" ), PARSE_ERROR );
		}
		else if ( string_equal( property, "wirebox" ) ) {
			ASSERT_MESSAGE( EntityClassFGD_parseToken( tokeniser, "(" ), PARSE_ERROR );
			//const char* mins =
			tokeniser.getToken();
			ASSERT_MESSAGE( EntityClassFGD_parseToken( tokeniser, "," ), PARSE_ERROR );
			//const char* maxs =
			tokeniser.getToken();
			ASSERT_MESSAGE( EntityClassFGD_parseToken( tokeniser, ")" ), PARSE_ERROR );
		}
		else if ( string_equal( property, "halfgridsnap" ) ) {
		}
		else if ( string_equal( property, "flags" ) ) {
			ASSERT_MESSAGE( EntityClassFGD_parseToken( tokeniser, "(" ), PARSE_ERROR );
			for (;; )
			{
				const char* base = tokeniser.getToken();
				if ( string_equal( base, ")" ) ) {
					break;
				}
				else if ( !string_equal( base, "," ) ) {
					if( string_equal_nocase( base, "Angle" ) ){
						// FIXME
						// entityClass->has_angles = true;
					}
				}
			}
		}
		else
		{
			ERROR_FGD( PARSE_ERROR );
		}
	}

	entityClass->m_name = tokeniser.getToken();

	if ( !isBase ) {
		ASSERT_MESSAGE( EntityClassFGD_parseToken( tokeniser, ":" ), PARSE_ERROR );

		EntityClassFGD_parseSplitString( tokeniser, entityClass->m_comments );

		const char* urlSeparator = tokeniser.getToken();
		if ( string_equal( urlSeparator, ":" ) ) {
			CopiedString tmp;
			EntityClassFGD_parseSplitString( tokeniser, tmp );
		}
		else
		{
			tokeniser.ungetToken();
		}
	}

	tokeniser.nextLine();

	ASSERT_MESSAGE( EntityClassFGD_parseToken( tokeniser, "[" ), PARSE_ERROR );

	tokeniser.nextLine();

	for (;; )
	{
		CopiedString key = tokeniser.getToken();
		if ( string_equal( key.c_str(), "]" ) ) {
			tokeniser.nextLine();
			break;
		}

		if ( string_equal_nocase( key.c_str(), "input" )
			 || string_equal_nocase( key.c_str(), "output" ) ) {
			const char* name = tokeniser.getToken();
			if ( !string_equal( name, "(" ) ) {
				ASSERT_MESSAGE( EntityClassFGD_parseToken( tokeniser, "(" ), PARSE_ERROR );
				//const char* type =
				tokeniser.getToken();
				ASSERT_MESSAGE( EntityClassFGD_parseToken( tokeniser, ")" ), PARSE_ERROR );
				const char* descriptionSeparator = tokeniser.getToken();
				if ( string_equal( descriptionSeparator, ":" ) ) {
					CopiedString description;
					EntityClassFGD_parseSplitString( tokeniser, description );
				}
				else
				{
					tokeniser.ungetToken();
				}
				tokeniser.nextLine();
				continue;
			}
		}

		ASSERT_MESSAGE( EntityClassFGD_parseToken( tokeniser, "(" ), PARSE_ERROR );
		CopiedString type = tokeniser.getToken();
		ASSERT_MESSAGE( EntityClassFGD_parseToken( tokeniser, ")" ), PARSE_ERROR );

		if ( string_equal_nocase( type.c_str(), "flags" ) ) {
			ASSERT_MESSAGE( EntityClassFGD_parseToken( tokeniser, "=" ), PARSE_ERROR );
			ASSERT_MESSAGE( EntityClassFGD_parseToken( tokeniser, "[" ), PARSE_ERROR );
			for (;; )
			{
				const char* flag = tokeniser.getToken();
				if ( string_equal( flag, "]" ) ) {
					tokeniser.nextLine();
					break;
				}
				else
				{
					const size_t bit = std::log2( atoi( flag ) );
					ASSERT_MESSAGE( bit < MAX_FLAGS, "invalid flag bit" << PARSE_ERROR );
					ASSERT_MESSAGE( string_empty( entityClass->flagnames[bit] ), "non-unique flag bit" << PARSE_ERROR );

					ASSERT_MESSAGE( EntityClassFGD_parseToken( tokeniser, ":" ), PARSE_ERROR );

					const char* name = tokeniser.getToken();
					strcpy( entityClass->flagnames[bit], name );
					EntityClassAttribute *attribute = &EntityClass_insertAttribute( *entityClass, name, EntityClassAttribute( "flag", name ) ).second;
					// FIXME spawn flags are not implemented yet
					// entityClass->flagAttributes[bit] = attribute;
					{
						const char* defaultSeparator = tokeniser.getToken();
						if ( string_equal( defaultSeparator, ":" ) ) {
							tokeniser.getToken();
							{
								const char* descriptionSeparator = tokeniser.getToken();
								if ( string_equal( descriptionSeparator, ":" ) ) {
									EntityClassFGD_parseSplitString( tokeniser, attribute->m_description );
								}
								else
								{
									tokeniser.ungetToken();
								}
							}
						}
						else
						{
							tokeniser.ungetToken();
						}
					}
				}
				tokeniser.nextLine();
			}
		}
		else if ( string_equal_nocase( type.c_str(), "choices" ) ) {
			EntityClassAttribute attribute;

			ASSERT_MESSAGE( EntityClassFGD_parseToken( tokeniser, ":" ), PARSE_ERROR );
			attribute.m_name = tokeniser.getToken();
			const char* valueSeparator = tokeniser.getToken();
			if ( string_equal( valueSeparator, ":" ) ) {
				const char* value = tokeniser.getToken();
				if ( !string_equal( value, ":" ) ) {
					attribute.m_value = value;
				}
				else
				{
					tokeniser.ungetToken();
				}
				{
					const char* descriptionSeparator = tokeniser.getToken();
					if ( string_equal( descriptionSeparator, ":" ) ) {
						EntityClassFGD_parseSplitString( tokeniser, attribute.m_description );
					}
					else
					{
						tokeniser.ungetToken();
					}
				}
			}
			else
			{
				tokeniser.ungetToken();
			}
			ASSERT_MESSAGE( EntityClassFGD_parseToken( tokeniser, "=" ), PARSE_ERROR );
			tokeniser.nextLine();
			ASSERT_MESSAGE( EntityClassFGD_parseToken( tokeniser, "[" ), PARSE_ERROR );
			tokeniser.nextLine();

			StringOutputStream listTypeName( 64 );
			listTypeName << entityClass->m_name.c_str() << "_" << attribute.m_name.c_str();
			attribute.m_type = listTypeName.c_str();

			ListAttributeType& listType = g_listTypesFGD[listTypeName.c_str()];

			for (;; )
			{
				const char* value = tokeniser.getToken();
				if ( string_equal( value, "]" ) ) {
					tokeniser.nextLine();
					break;
				}
				else
				{
					CopiedString tmp( value );
					ASSERT_MESSAGE( EntityClassFGD_parseToken( tokeniser, ":" ), PARSE_ERROR );
					const char* name = tokeniser.getToken();
					listType.push_back( name, tmp.c_str() );

					const char* descriptionSeparator = tokeniser.getToken();
					if ( string_equal( descriptionSeparator, ":" ) ) {
						EntityClassFGD_parseSplitString( tokeniser, tmp );
					}
					else
					{
						tokeniser.ungetToken();
					}
				}
				tokeniser.nextLine();
			}

			for ( ListAttributeType::const_iterator i = listType.begin(); i != listType.end(); ++i )
			{
				if ( string_equal( attribute.m_value.c_str(), ( *i ).first.c_str() ) ) {
					attribute.m_value = ( *i ).second.c_str();
				}
			}

			EntityClass_insertAttribute( *entityClass, key.c_str(), attribute );
		}
		else if ( string_equal_nocase( type.c_str(), "decal" ) ) {
		}
		else if ( string_equal_nocase( type.c_str(), "string" )
				  || string_equal_nocase( type.c_str(), "integer" )
				  || string_equal_nocase( type.c_str(), "studio" )
				  || string_equal_nocase( type.c_str(), "sprite" )
				  || string_equal_nocase( type.c_str(), "color255" )
				  || string_equal_nocase( type.c_str(), "target_source" )
				  || string_equal_nocase( type.c_str(), "target_destination" )
				  || string_equal_nocase( type.c_str(), "sound" )
		          // hl2 below
				  || string_equal_nocase( type.c_str(), "angle" )
				  || string_equal_nocase( type.c_str(), "origin" )
				  || string_equal_nocase( type.c_str(), "float" )
				  || string_equal_nocase( type.c_str(), "node_dest" )
				  || string_equal_nocase( type.c_str(), "filterclass" )
				  || string_equal_nocase( type.c_str(), "vector" )
				  || string_equal_nocase( type.c_str(), "sidelist" )
				  || string_equal_nocase( type.c_str(), "material" )
				  || string_equal_nocase( type.c_str(), "vecline" )
				  || string_equal_nocase( type.c_str(), "axis" )
				  || string_equal_nocase( type.c_str(), "npcclass" )
				  || string_equal_nocase( type.c_str(), "target_name_or_class" )
				  || string_equal_nocase( type.c_str(), "pointentityclass" )
				  || string_equal_nocase( type.c_str(), "scene" ) ) {
			if ( !string_equal( tokeniser.getToken(), "readonly" ) ) {
				tokeniser.ungetToken();
			}

			ASSERT_MESSAGE( EntityClassFGD_parseToken( tokeniser, ":" ), PARSE_ERROR );
			const char* attributeType = "string";
			if ( string_equal_nocase( type.c_str(), "studio" ) ) {
				attributeType = "model";
			}

			EntityClassAttribute attribute;
			attribute.m_type = attributeType;
			attribute.m_name = tokeniser.getToken();

			const char* defaultSeparator = tokeniser.getToken();
			if ( string_equal( defaultSeparator, ":" ) ) {
				const char* value = tokeniser.getToken();
				if ( !string_equal( value, ":" ) ) {
					attribute.m_value = value;
				}
				else
				{
					tokeniser.ungetToken();
				}

				{
					const char* descriptionSeparator = tokeniser.getToken();
					if ( string_equal( descriptionSeparator, ":" ) ) {
						EntityClassFGD_parseSplitString( tokeniser, attribute.m_description );
					}
					else
					{
						tokeniser.ungetToken();
					}
				}
			}
			else
			{
				tokeniser.ungetToken();
			}
			EntityClass_insertAttribute( *entityClass, key.c_str(), attribute );
		}
		else
		{
			ERROR_FGD( "unknown key type: " << makeQuoted( type ) );
		}
		tokeniser.nextLine();
	}

	if ( isBase ) {
		EntityClassFGD_insertUniqueBase( entityClass );
	}
	else
	{
		EntityClassFGD_insertUnique( entityClass );
	}
}

void EntityClassFGD_loadFile( const char* filename );

void EntityClassFGD_parse( TextInputStream& inputStream, const char* path ){
	Tokeniser& tokeniser = GlobalScriptLibrary().m_pfnNewScriptTokeniser( inputStream );

	tokeniser.nextLine();

	for (;; )
	{
		const char* blockType = tokeniser.getToken();
		if ( blockType == 0 ) {
			break;
		}
		if ( string_equal_nocase( blockType, "@SolidClass" ) ) {
			EntityClassFGD_parseClass( tokeniser, false, false );
		}
		else if ( string_equal_nocase( blockType, "@BaseClass" ) ) {
			EntityClassFGD_parseClass( tokeniser, false, true );
		}
		else if ( string_equal_nocase( blockType, "@PointClass" )
		          // hl2 below
		       || string_equal_nocase( blockType, "@KeyFrameClass" )
		       || string_equal_nocase( blockType, "@MoveClass" )
		       || string_equal_nocase( blockType, "@FilterClass" )
		       || string_equal_nocase( blockType, "@NPCClass" ) ) {
			EntityClassFGD_parseClass( tokeniser, true, false );
		}
		// hl2 below
		else if ( string_equal( blockType, "@include" ) ) {
			StringOutputStream includePath( 256 );
			includePath << StringRange( path, path_get_filename_start( path ) );
			includePath << tokeniser.getToken();
			EntityClassFGD_loadFile( includePath.c_str() );
		}
		else if ( string_equal( blockType, "@mapsize" ) ) {
			ASSERT_MESSAGE( EntityClassFGD_parseToken( tokeniser, "(" ), PARSE_ERROR );
			//const char* min =
			tokeniser.getToken();
			ASSERT_MESSAGE( EntityClassFGD_parseToken( tokeniser, "," ), PARSE_ERROR );
			//const char* max =
			tokeniser.getToken();
			ASSERT_MESSAGE( EntityClassFGD_parseToken( tokeniser, ")" ), PARSE_ERROR );
		}
		else
		{
			ERROR_FGD( "unknown block type: " << makeQuoted( blockType ) );
		}
	}

	tokeniser.release();
}


void EntityClassFGD_loadFile( const char* filename ){
	TextFileInputStream file( filename );
	if ( !file.failed() ) {
		globalOutputStream() << "parsing entity classes from " << makeQuoted( filename ) << "\n";

		EntityClassFGD_parse( file, filename );
	}
}

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

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

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

	EntityClass* e = EntityClass_Create_Default( name, has_brushes );
	return EntityClassFGD_insertUnique( e );
}

const ListAttributeType* EntityClassFGD_findListType( const char *name ){
	ListAttributeTypes::iterator i = g_listTypesFGD.find( name );
	if ( i != g_listTypesFGD.end() ) {
		return &( *i ).second;
	}
	return 0;

}


void EntityClassFGD_resolveInheritance( EntityClass* derivedClass ){
	if ( derivedClass->inheritanceResolved == false ) {
		derivedClass->inheritanceResolved = true;
		for ( StringList::iterator j = derivedClass->m_parent.begin(); j != derivedClass->m_parent.end(); ++j )
		{
			BaseClasses::iterator i = g_EntityClassFGD_bases.find( ( *j ).c_str() );
			if ( i == g_EntityClassFGD_bases.end() ) {
				globalErrorStream() << "failed to find entityDef " << makeQuoted( ( *j ).c_str() ) << " inherited by "  << makeQuoted( derivedClass->m_name.c_str() ) << "\n";
			}
			else
			{
				EntityClass* parentClass = ( *i ).second;
				EntityClassFGD_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;
				}

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

				for( size_t flag = 0; flag < MAX_FLAGS; ++flag ){
					if( !string_empty( parentClass->flagnames[flag] ) && string_empty( derivedClass->flagnames[flag] ) ){
						strcpy( derivedClass->flagnames[flag], parentClass->flagnames[flag] );
						// FIXME spawn flags are not implemented yet
						// derivedClass->flagAttributes[flag] = parentClass->flagAttributes[flag];
					}
				}
			}
		}
	}
}

class EntityClassFGD : public ModuleObserver
{
std::size_t m_unrealised;
ModuleObservers m_observers;
public:
EntityClassFGD() : m_unrealised( 3 ){
}
void realise(){
	if ( --m_unrealised == 0 ) {

			{
				StringOutputStream baseDirectory( 256 );
				baseDirectory << GlobalRadiant().getGameToolsPath() << GlobalRadiant().getRequiredGameDescriptionKeyValue( "basegame" ) << '/';
				StringOutputStream gameDirectory( 256 );
				gameDirectory << GlobalRadiant().getGameToolsPath() << GlobalRadiant().getGameName() << '/';

				const auto pathLess = []( const CopiedString& one, const CopiedString& other ){
					return path_less( one.c_str(), other.c_str() );
				};
				std::map<CopiedString, const char*, decltype( pathLess )> name_path( pathLess );

				const auto constructDirectory = [&name_path]( const char* directory, const char* extension ){
					globalOutputStream() << "EntityClass: searching " << makeQuoted( directory ) << " for *." << extension << '\n';
					Directory_forEach( directory, matchFileExtension( extension, [directory, &name_path]( const char *name ){
						name_path.emplace( name, directory );
					} ) );
				};

				constructDirectory( baseDirectory.c_str(), "fgd" );
				if ( !string_equal( baseDirectory.c_str(), gameDirectory.c_str() ) ) {
					constructDirectory( gameDirectory.c_str(), "fgd" );
				}

				for( const auto& [ name, path ] : name_path ){
					StringOutputStream filename( 256 );
					filename << path << name.c_str();
					EntityClassFGD_loadFile( filename.c_str() );
				}
			}

		{
			for ( EntityClasses::iterator i = g_EntityClassFGD_classes.begin(); i != g_EntityClassFGD_classes.end(); ++i )
			{
				EntityClassFGD_resolveInheritance( ( *i ).second );
					if ( ( *i ).second->fixedsize && ( *i ).second->m_modelpath.empty() ) {
					if ( !( *i ).second->sizeSpecified ) {
							globalErrorStream() << "size not specified for entity class: " << makeQuoted( ( *i ).second->name() ) << '\n';
					}
					if ( !( *i ).second->colorSpecified ) {
							globalErrorStream() << "color not specified for entity class: " << makeQuoted( ( *i ).second->name() ) << '\n';
					}
				}
			}
		}
		{
			for ( BaseClasses::iterator i = g_EntityClassFGD_bases.begin(); i != g_EntityClassFGD_bases.end(); ++i )
			{
				eclass_capture_state( ( *i ).second );
			}
		}

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

EntityClassFGD g_EntityClassFGD;

void EntityClassFGD_attach( ModuleObserver& observer ){
	g_EntityClassFGD.attach( observer );
}
void EntityClassFGD_detach( ModuleObserver& observer ){
	g_EntityClassFGD.detach( observer );
}

void EntityClassFGD_realise(){
	g_EntityClassFGD.realise();
}
void EntityClassFGD_unrealise(){
	g_EntityClassFGD.unrealise();
}

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

	EntityClassFGD_realise();
}

void EntityClassFGD_destroy(){
	EntityClassFGD_unrealise();

	g_EntityClassFGD_bad->free( g_EntityClassFGD_bad );
}

class EntityClassFGDDependencies : public GlobalFileSystemModuleRef, public GlobalShaderCacheModuleRef, public GlobalRadiantModuleRef
{
};

class EntityClassFGDAPI
{
EntityClassManager m_eclassmanager;
public:
typedef EntityClassManager Type;
STRING_CONSTANT( Name, "halflife" );

EntityClassFGDAPI(){
	EntityClassFGD_construct();

	m_eclassmanager.findOrInsert = &EntityClassFGD_findOrInsert;
	m_eclassmanager.findListType = &EntityClassFGD_findListType;
	m_eclassmanager.forEach = &EntityClassFGD_forEach;
	m_eclassmanager.attach = &EntityClassFGD_attach;
	m_eclassmanager.detach = &EntityClassFGD_detach;
	m_eclassmanager.realise = &EntityClassFGD_realise;
	m_eclassmanager.unrealise = &EntityClassFGD_unrealise;

	Radiant_attachGameToolsPathObserver( g_EntityClassFGD );
	Radiant_attachGameNameObserver( g_EntityClassFGD );
}
~EntityClassFGDAPI(){
	Radiant_detachGameNameObserver( g_EntityClassFGD );
	Radiant_detachGameToolsPathObserver( g_EntityClassFGD );

	EntityClassFGD_destroy();
}
EntityClassManager* getTable(){
	return &m_eclassmanager;
}
};

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

typedef SingletonModule<EntityClassFGDAPI, EntityClassFGDDependencies> EntityClassFGDModule;
typedef Static<EntityClassFGDModule> StaticEntityClassFGDModule;
StaticRegisterModule staticRegisterEntityClassFGD( StaticEntityClassFGDModule::instance() );
