/*
   Copyright (C) 1999-2006 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
 */

#include "map.h"

#include <gtk/gtk.h>

#include "debugging/debugging.h"

#include "imap.h"

MapModules& ReferenceAPI_getMapModules();

#include "iselection.h"
#include "iundo.h"
#include "ibrush.h"
#include "ifilter.h"
#include "ireference.h"
#include "ifiletypes.h"
#include "ieclass.h"
#include "irender.h"
#include "ientity.h"
#include "editable.h"
#include "iarchive.h"
#include "ifilesystem.h"
#include "namespace.h"
#include "moduleobserver.h"

#include <set>

#include <gdk/gdkkeysyms.h>
#include "uilib/uilib.h"

#include "scenelib.h"
#include "transformlib.h"
#include "selectionlib.h"
#include "instancelib.h"
#include "traverselib.h"
#include "maplib.h"
#include "eclasslib.h"
#include "cmdlib.h"
#include "stream/textfilestream.h"
#include "os/path.h"
#include "os/file.h"
#include "uniquenames.h"
#include "modulesystem/singletonmodule.h"
#include "modulesystem/moduleregistry.h"
#include "stream/stringstream.h"
#include "signal/signal.h"

#include "gtkutil/filechooser.h"
#include "timer.h"
#include "select.h"
#include "plugin.h"
#include "filetypes.h"
#include "gtkdlgs.h"
#include "entityinspector.h"
#include "points.h"
#include "qe3.h"
#include "camwindow.h"
#include "xywindow.h"
#include "mainframe.h"
#include "preferences.h"
#include "preferencesystem.h"
#include "referencecache.h"
#include "mru.h"
#include "commands.h"
#include "autosave.h"
#include "brushmodule.h"
#include "brush.h"
#include "patch.h"

bool g_writeMapComments = true;

class NameObserver
{
UniqueNames& m_names;
CopiedString m_name;

void construct(){
	if ( !empty() ) {
		//globalOutputStream() << "construct " << makeQuoted(c_str()) << "\n";
		m_names.insert( name_read( c_str() ) );
	}
}

void destroy(){
	if ( !empty() ) {
		//globalOutputStream() << "destroy " << makeQuoted(c_str()) << "\n";
		m_names.erase( name_read( c_str() ) );
	}
}

NameObserver& operator=( const NameObserver& other );

public:
NameObserver( UniqueNames& names ) : m_names( names ){
	construct();
}
NameObserver( const NameObserver& other ) : m_names( other.m_names ), m_name( other.m_name ){
	construct();
}

~NameObserver(){
	destroy();
}

bool empty() const {
	return string_empty( c_str() );
}

const char* c_str() const {
	return m_name.c_str();
}

void nameChanged( const char* name ){
	destroy();
	m_name = name;
	construct();
}

typedef MemberCaller<NameObserver, void(const char*), &NameObserver::nameChanged> NameChangedCaller;
};

class BasicNamespace : public Namespace
{
typedef std::map<NameCallback, NameObserver> Names;
Names m_names;
UniqueNames m_uniqueNames;
public:
~BasicNamespace(){
	ASSERT_MESSAGE( m_names.empty(), "namespace: names still registered at shutdown" );
}

void attach( const NameCallback& setName, const NameCallbackCallback& attachObserver ){
	std::pair<Names::iterator, bool> result = m_names.insert( Names::value_type( setName, m_uniqueNames ) );
	ASSERT_MESSAGE( result.second, "cannot attach name" );
	attachObserver( NameObserver::NameChangedCaller( ( *result.first ).second ) );
	//globalOutputStream() << "attach: " << reinterpret_cast<const unsigned int&>(setName) << "\n";
}

void detach( const NameCallback& setName, const NameCallbackCallback& detachObserver ){
	Names::iterator i = m_names.find( setName );
	ASSERT_MESSAGE( i != m_names.end(), "cannot detach name" );
	//globalOutputStream() << "detach: " << reinterpret_cast<const unsigned int&>(setName) << "\n";
	detachObserver( NameObserver::NameChangedCaller( ( *i ).second ) );
	m_names.erase( i );
}

void makeUnique( const char* name, const NameCallback& setName ) const {
	char buffer[1024];
	name_write( buffer, m_uniqueNames.make_unique( name_read( name ) ) );
	setName( buffer );
}

void mergeNames( const BasicNamespace& other ) const {
	typedef std::list<NameCallback> SetNameCallbacks;
	typedef std::map<CopiedString, SetNameCallbacks> NameGroups;
	NameGroups groups;

	UniqueNames uniqueNames( other.m_uniqueNames );

	for ( Names::const_iterator i = m_names.begin(); i != m_names.end(); ++i )
	{
		groups[( *i ).second.c_str()].push_back( ( *i ).first );
	}

	for ( NameGroups::iterator i = groups.begin(); i != groups.end(); ++i )
	{
		name_t uniqueName( uniqueNames.make_unique( name_read( ( *i ).first.c_str() ) ) );
		uniqueNames.insert( uniqueName );

		char buffer[1024];
		name_write( buffer, uniqueName );

		//globalOutputStream() << "renaming " << makeQuoted((*i).first.c_str()) << " to " << makeQuoted(buffer) << "\n";

		SetNameCallbacks& setNameCallbacks = ( *i ).second;

		for ( SetNameCallbacks::const_iterator j = setNameCallbacks.begin(); j != setNameCallbacks.end(); ++j )
		{
			( *j )( buffer );
		}
	}
}
};

BasicNamespace g_defaultNamespace;
BasicNamespace g_cloneNamespace;

class NamespaceAPI
{
Namespace* m_namespace;
public:
typedef Namespace Type;

STRING_CONSTANT( Name, "*" );

NamespaceAPI(){
	m_namespace = &g_defaultNamespace;
}

Namespace* getTable(){
	return m_namespace;
}
};

typedef SingletonModule<NamespaceAPI> NamespaceModule;
typedef Static<NamespaceModule> StaticNamespaceModule;
StaticRegisterModule staticRegisterDefaultNamespace( StaticNamespaceModule::instance() );


std::list<Namespaced*> g_cloned;

inline Namespaced* Node_getNamespaced( scene::Node& node ){
	return NodeTypeCast<Namespaced>::cast( node );
}

void Node_gatherNamespaced( scene::Node& node ){
	Namespaced* namespaced = Node_getNamespaced( node );
	if ( namespaced != 0 ) {
		g_cloned.push_back( namespaced );
	}
}

class GatherNamespaced : public scene::Traversable::Walker
{
public:
bool pre( scene::Node& node ) const {
	Node_gatherNamespaced( node );
	return true;
}
};

void Map_gatherNamespaced( scene::Node& root ){
	Node_traverseSubgraph( root, GatherNamespaced() );
}

void Map_mergeClonedNames(){
	for ( std::list<Namespaced*>::const_iterator i = g_cloned.begin(); i != g_cloned.end(); ++i )
	{
		( *i )->setNamespace( g_cloneNamespace );
	}
	g_cloneNamespace.mergeNames( g_defaultNamespace );
	for ( std::list<Namespaced*>::const_iterator i = g_cloned.begin(); i != g_cloned.end(); ++i )
	{
		( *i )->setNamespace( g_defaultNamespace );
	}

	g_cloned.clear();
}

class WorldNode
{
scene::Node* m_node;
public:
WorldNode()
	: m_node( 0 ){
}

void set( scene::Node* node ){
	if ( m_node != 0 ) {
		m_node->DecRef();
	}
	m_node = node;
	if ( m_node != 0 ) {
		m_node->IncRef();
	}
}

scene::Node* get() const {
	return m_node;
}
};

class Map;
void Map_SetValid( Map& map, bool valid );

void Map_UpdateTitle( const Map& map );

void Map_SetWorldspawn( Map& map, scene::Node* node );


class Map : public ModuleObserver
{
public:
CopiedString m_name;
Resource* m_resource;
bool m_valid;

bool m_modified;

void ( *m_modified_changed )( const Map& );

Signal0 m_mapValidCallbacks;

WorldNode m_world_node;   // "classname" "worldspawn" !

Map() : m_resource( 0 ), m_valid( false ), m_modified_changed( Map_UpdateTitle ){
}

void realise(){
	if ( m_resource != 0 ) {
		if ( Map_Unnamed( *this ) ) {
			g_map.m_resource->setNode( NewMapRoot( "" ).get_pointer() );
			MapFile* map = Node_getMapFile( *g_map.m_resource->getNode() );
			if ( map != 0 ) {
				map->save();
			}
		}
		else
		{
			m_resource->load();
		}

		GlobalSceneGraph().insert_root( *m_resource->getNode() );

		AutoSave_clear();

		Map_SetValid( g_map, true );
	}
}

void unrealise(){
	if ( m_resource != 0 ) {
		Map_SetValid( g_map, false );
		Map_SetWorldspawn( g_map, 0 );


		GlobalUndoSystem().clear();

		GlobalSceneGraph().erase_root();
	}
}
};

Map g_map;
Map* g_currentMap = 0;

void Map_addValidCallback( Map& map, const SignalHandler& handler ){
	map.m_mapValidCallbacks.connectLast( handler );
}

bool Map_Valid( const Map& map ){
	return map.m_valid;
}

void Map_SetValid( Map& map, bool valid ){
	map.m_valid = valid;
	map.m_mapValidCallbacks();
}


const char* Map_Name( const Map& map ){
	return map.m_name.c_str();
}

bool Map_Unnamed( const Map& map ){
	return string_equal( Map_Name( map ), "unnamed.map" );
}

inline const MapFormat& MapFormat_forFile( const char* filename ){
	const char* moduleName = findModuleName( GetFileTypeRegistry(), MapFormat::Name(), path_get_extension( filename ) );
	MapFormat* format = Radiant_getMapModules().findModule( moduleName );
	ASSERT_MESSAGE( format != 0, "map format not found for file " << makeQuoted( filename ) );
	return *format;
}

const MapFormat& Map_getFormat( const Map& map ){
	return MapFormat_forFile( Map_Name( map ) );
}


bool Map_Modified( const Map& map ){
	return map.m_modified;
}

void Map_SetModified( Map& map, bool modified ){
	if ( map.m_modified ^ modified ) {
		map.m_modified = modified;

		map.m_modified_changed( map );
	}
}

void Map_UpdateTitle( const Map& map ){
	Sys_SetTitle( map.m_name.c_str(), Map_Modified( map ) );
}


scene::Node* Map_GetWorldspawn( const Map& map ){
	return map.m_world_node.get();
}

void Map_SetWorldspawn( Map& map, scene::Node* node ){
	map.m_world_node.set( node );
}


// TTimo
// need that in a variable, will have to tweak depending on the game
float g_MaxWorldCoord = 64 * 1024;
float g_MinWorldCoord = -64 * 1024;

void AddRegionBrushes( void );

void RemoveRegionBrushes( void );


/*
   ================
   Map_Free
   free all map elements, reinitialize the structures that depend on them
   ================
 */
void Map_Free(){
	Pointfile_Clear();

	g_map.m_resource->detach( g_map );
	GlobalReferenceCache().release( g_map.m_name.c_str() );
	g_map.m_resource = 0;

	FlushReferences();

	g_currentMap = 0;
	Brush_unlatchPreferences();
}

class EntityFindByClassname : public scene::Graph::Walker
{
const char* m_name;
Entity*& m_entity;
public:
EntityFindByClassname( const char* name, Entity*& entity ) : m_name( name ), m_entity( entity ){
	m_entity = 0;
}

bool pre( const scene::Path& path, scene::Instance& instance ) const {
	if ( m_entity == 0 ) {
		Entity* entity = Node_getEntity( path.top() );
		if ( entity != 0
			 && string_equal( m_name, entity->getKeyValue( "classname" ) ) ) {
			m_entity = entity;
		}
	}
	return true;
}
};

Entity* Scene_FindEntityByClass( const char* name ){
	Entity* entity;
	GlobalSceneGraph().traverse( EntityFindByClassname( name, entity ) );
	return entity;
}

Entity *Scene_FindPlayerStart(){
	typedef const char* StaticString;
	StaticString strings[] = {
		"info_player_start",
		"info_player_deathmatch",
		"team_CTF_redplayer",
		"team_CTF_blueplayer",
		"team_CTF_redspawn",
		"team_CTF_bluespawn",
	};
	typedef const StaticString* StaticStringIterator;
	for ( StaticStringIterator i = strings, end = strings + ( sizeof( strings ) / sizeof( StaticString ) ); i != end; ++i )
	{
		Entity* entity = Scene_FindEntityByClass( *i );
		if ( entity != 0 ) {
			return entity;
		}
	}
	return 0;
}

//
// move the view to a start position
//


void FocusViews( const Vector3& point, float angle ){
	CamWnd& camwnd = *g_pParentWnd->GetCamWnd();
	Camera_setOrigin( camwnd, point );
	Vector3 angles( Camera_getAngles( camwnd ) );
	angles[CAMERA_PITCH] = 0;
	angles[CAMERA_YAW] = angle;
	Camera_setAngles( camwnd, angles );

	XYWnd* xywnd = g_pParentWnd->GetXYWnd();
	xywnd->SetOrigin( point );
}

#include "stringio.h"

void Map_StartPosition(){
	Entity* entity = Scene_FindPlayerStart();

	if ( entity ) {
		Vector3 origin;
		string_parse_vector3( entity->getKeyValue( "origin" ), origin );
		FocusViews( origin, string_read_float( entity->getKeyValue( "angle" ) ) );
	}
	else
	{
		FocusViews( g_vector3_identity, 0 );
	}
}


inline bool node_is_worldspawn( scene::Node& node ){
	Entity* entity = Node_getEntity( node );
	return entity != 0 && string_equal( entity->getKeyValue( "classname" ), "worldspawn" );
}


// use first worldspawn
class entity_updateworldspawn : public scene::Traversable::Walker
{
public:
bool pre( scene::Node& node ) const {
	if ( node_is_worldspawn( node ) ) {
		if ( Map_GetWorldspawn( g_map ) == 0 ) {
			Map_SetWorldspawn( g_map, &node );
		}
	}
	return false;
}
};

scene::Node* Map_FindWorldspawn( Map& map ){
	Map_SetWorldspawn( map, 0 );

	Node_getTraversable( GlobalSceneGraph().root() )->traverse( entity_updateworldspawn() );

	return Map_GetWorldspawn( map );
}


class CollectAllWalker : public scene::Traversable::Walker
{
scene::Node& m_root;
UnsortedNodeSet& m_nodes;
public:
CollectAllWalker( scene::Node& root, UnsortedNodeSet& nodes ) : m_root( root ), m_nodes( nodes ){
}

bool pre( scene::Node& node ) const {
	m_nodes.insert( NodeSmartReference( node ) );
	Node_getTraversable( m_root )->erase( node );
	return false;
}
};

void Node_insertChildFirst( scene::Node& parent, scene::Node& child ){
	UnsortedNodeSet nodes;
	Node_getTraversable( parent )->traverse( CollectAllWalker( parent, nodes ) );
	Node_getTraversable( parent )->insert( child );

	for ( UnsortedNodeSet::iterator i = nodes.begin(); i != nodes.end(); ++i )
	{
		Node_getTraversable( parent )->insert( ( *i ) );
	}
}

scene::Node& createWorldspawn(){
	NodeSmartReference worldspawn( GlobalEntityCreator().createEntity( GlobalEntityClassManager().findOrInsert( "worldspawn", true ) ) );
	Node_insertChildFirst( GlobalSceneGraph().root(), worldspawn );
	return worldspawn;
}

void Map_UpdateWorldspawn( Map& map ){
	if ( Map_FindWorldspawn( map ) == 0 ) {
		Map_SetWorldspawn( map, &createWorldspawn() );
	}
}

scene::Node& Map_FindOrInsertWorldspawn( Map& map ){
	Map_UpdateWorldspawn( map );
	return *Map_GetWorldspawn( map );
}


class MapMergeAll : public scene::Traversable::Walker
{
mutable scene::Path m_path;
public:
MapMergeAll( const scene::Path& root )
	: m_path( root ){
}

bool pre( scene::Node& node ) const {
	Node_getTraversable( m_path.top() )->insert( node );
	m_path.push( makeReference( node ) );
	selectPath( m_path, true );
	return false;
}

void post( scene::Node& node ) const {
	m_path.pop();
}
};

class MapMergeEntities : public scene::Traversable::Walker
{
mutable scene::Path m_path;
public:
MapMergeEntities( const scene::Path& root )
	: m_path( root ){
}

bool pre( scene::Node& node ) const {
	if ( node_is_worldspawn( node ) ) {
		scene::Node* world_node = Map_FindWorldspawn( g_map );
		if ( world_node == 0 ) {
			Map_SetWorldspawn( g_map, &node );
			Node_getTraversable( m_path.top().get() )->insert( node );
			m_path.push( makeReference( node ) );
			Node_getTraversable( node )->traverse( SelectChildren( m_path ) );
		}
		else
		{
			m_path.push( makeReference( *world_node ) );
			Node_getTraversable( node )->traverse( MapMergeAll( m_path ) );
		}
	}
	else
	{
		Node_getTraversable( m_path.top() )->insert( node );
		m_path.push( makeReference( node ) );
		if ( node_is_group( node ) ) {
			Node_getTraversable( node )->traverse( SelectChildren( m_path ) );
		}
		else
		{
			selectPath( m_path, true );
		}
	}
	return false;
}

void post( scene::Node& node ) const {
	m_path.pop();
}
};

class BasicContainer : public scene::Node::Symbiot
{
class TypeCasts
{
NodeTypeCastTable m_casts;
public:
TypeCasts(){
	NodeContainedCast<BasicContainer, scene::Traversable>::install( m_casts );
}

NodeTypeCastTable& get(){
	return m_casts;
}
};

scene::Node m_node;
TraversableNodeSet m_traverse;
public:

typedef LazyStatic<TypeCasts> StaticTypeCasts;

scene::Traversable& get( NullType<scene::Traversable>){
	return m_traverse;
}

BasicContainer() : m_node( this, this, StaticTypeCasts::instance().get() ){
}

void release(){
	delete this;
}

scene::Node& node(){
	return m_node;
}
};

/// Merges the map graph rooted at \p node into the global scene-graph.
void MergeMap( scene::Node& node ){
	Node_getTraversable( node )->traverse( MapMergeEntities( scene::Path( makeReference( GlobalSceneGraph().root() ) ) ) );
}

void Map_ImportSelected( TextInputStream& in, const MapFormat& format ){
	NodeSmartReference node( ( new BasicContainer )->node() );
	format.readGraph( node, in, GlobalEntityCreator() );
	Map_gatherNamespaced( node );
	Map_mergeClonedNames();
	MergeMap( node );
}

inline scene::Cloneable* Node_getCloneable( scene::Node& node ){
	return NodeTypeCast<scene::Cloneable>::cast( node );
}

inline scene::Node& node_clone( scene::Node& node ){
	scene::Cloneable* cloneable = Node_getCloneable( node );
	if ( cloneable != 0 ) {
		return cloneable->clone();
	}

	return ( new scene::NullNode )->node();
}

class CloneAll : public scene::Traversable::Walker
{
mutable scene::Path m_path;
public:
CloneAll( scene::Node& root )
	: m_path( makeReference( root ) ){
}

bool pre( scene::Node& node ) const {
	if ( node.isRoot() ) {
		return false;
	}

	m_path.push( makeReference( node_clone( node ) ) );
	m_path.top().get().IncRef();

	return true;
}

void post( scene::Node& node ) const {
	if ( node.isRoot() ) {
		return;
	}

	Node_getTraversable( m_path.parent() )->insert( m_path.top() );

	m_path.top().get().DecRef();
	m_path.pop();
}
};

scene::Node& Node_Clone( scene::Node& node ){
	scene::Node& clone = node_clone( node );
	scene::Traversable* traversable = Node_getTraversable( node );
	if ( traversable != 0 ) {
		traversable->traverse( CloneAll( clone ) );
	}
	return clone;
}

bool Node_instanceSelected( scene::Node& node );

class CloneAllSelected : public scene::Traversable::Walker
{
mutable scene::Path m_path;
public:
CloneAllSelected( scene::Node& root )
	: m_path( makeReference( root ) ){
}
bool pre( scene::Node& node ) const {
	if ( node.isRoot() ) {
		return false;
	}

	if( Node_instanceSelected( node ) ){
		m_path.push( makeReference( node_clone( node ) ) );
		m_path.top().get().IncRef();
	}

	return true;
}
void post( scene::Node& node ) const {
	if ( node.isRoot() ) {
		return;
	}

	if( Node_instanceSelected( node ) ){
		Node_getTraversable( m_path.parent() )->insert( m_path.top() );

		m_path.top().get().DecRef();
		m_path.pop();
	}
}
};

scene::Node& Node_Clone_Selected( scene::Node& node ){
	scene::Node& clone = node_clone( node );
	scene::Traversable* traversable = Node_getTraversable( node );
	if ( traversable != 0 ) {
		traversable->traverse( CloneAllSelected( clone ) );
	}
	return clone;
}


typedef std::map<CopiedString, std::size_t> EntityBreakdown;

class EntityBreakdownWalker : public scene::Graph::Walker
{
EntityBreakdown& m_entitymap;
public:
EntityBreakdownWalker( EntityBreakdown& entitymap )
	: m_entitymap( entitymap ){
}

bool pre( const scene::Path& path, scene::Instance& instance ) const {
	Entity* entity = Node_getEntity( path.top() );
	if ( entity != 0 ) {
		const EntityClass& eclass = entity->getEntityClass();
		if ( m_entitymap.find( eclass.name() ) == m_entitymap.end() ) {
			m_entitymap[eclass.name()] = 1;
		} else
		{
			++m_entitymap[eclass.name()];
		}
	}
	return true;
}
};

void Scene_EntityBreakdown( EntityBreakdown& entitymap ){
	GlobalSceneGraph().traverse( EntityBreakdownWalker( entitymap ) );
}

class CountStuffWalker : public scene::Graph::Walker
{
int& m_patches;
int& m_ents_ingame;
int& m_groupents;
int& m_groupents_ingame;
public:
CountStuffWalker( int& patches, int& ents_ingame, int& groupents, int& groupents_ingame )
	: m_patches( patches ), m_ents_ingame( ents_ingame ), m_groupents( groupents ), m_groupents_ingame( groupents_ingame ){
}
bool pre( const scene::Path& path, scene::Instance& instance ) const {
	Patch* patch = Node_getPatch( path.top() );
	if( patch != 0 ){
		++m_patches;
	}
	Entity* entity = Node_getEntity( path.top() );
	if ( entity != 0 ){
		if( entity->isContainer() ){
			++m_groupents;
			if( !string_equal_nocase( "func_group", entity->getKeyValue( "classname" ) ) &&
				!string_equal_nocase( "_decal", entity->getKeyValue( "classname" ) ) ){
				++m_groupents_ingame;
				++m_ents_ingame;
			}
			return true;
		}
		if( !string_equal_nocase_n( "light", entity->getKeyValue( "classname" ), 5 ) &&
			!string_equal_nocase( "misc_model", entity->getKeyValue( "classname" ) ) ){
			++m_ents_ingame;
		}
	}
	return true;
}
};

void Scene_CountStuff( int& patches, int& ents_ingame, int& groupents, int& groupents_ingame ){
	GlobalSceneGraph().traverse( CountStuffWalker( patches, ents_ingame, groupents, groupents_ingame ) );
}

WindowPosition g_posMapInfoWnd( c_default_window_pos );

void DoMapInfo(){
	ModalDialog dialog;
	ui::Widget w_brushes{ui::null};
	ui::Widget w_patches{ui::null};
	ui::Widget w_ents{ui::null};
	ui::Widget w_ents_ingame{ui::null};
	ui::Widget w_groupents{ui::null};
	ui::Widget w_groupents_ingame{ui::null};

	ui::ListStore EntityBreakdownWalker{ui::null};

	ui::Window window = MainFrame_getWindow().create_dialog_window("Map Info", G_CALLBACK(dialog_delete_callback ), &dialog );

	window_set_position( window, g_posMapInfoWnd );

	{
		auto vbox = create_dialog_vbox( 4, 4 );
		window.add(vbox);

		{
			auto hbox = create_dialog_hbox( 4 );
			vbox.pack_start( hbox, FALSE, FALSE, 0 );

			{
				auto table = create_dialog_table( 3, 4, 4, 4 );
				hbox.pack_start( table, TRUE, TRUE, 0 );

				{
					auto label = ui::Label( "Total Brushes:" );
					label.show();
                    table.attach(label, {0, 1, 0, 1}, {GTK_FILL, 0});
					gtk_misc_set_alignment( GTK_MISC( label ), 0.0, 0.5 );
				}
				{
					auto label = ui::Label( "" );
					label.show();
                    table.attach(label, {1, 2, 0, 1}, {GTK_FILL | GTK_EXPAND, 0}, {3, 0});
					w_brushes = label;
				}
				{
					auto label = ui::Label( "Total Patches" );
					label.show();
                    table.attach(label, {2, 3, 0, 1}, {GTK_FILL, 0});
					gtk_misc_set_alignment( GTK_MISC( label ), 0, 0.5 );
				}
				{
					auto label = ui::Label( "" );
					label.show();
                    table.attach(label, {3, 4, 0, 1}, {GTK_FILL, 0}, {3, 0});
					gtk_misc_set_alignment( GTK_MISC( label ), 0, 0.5 );
					w_patches = label;
				}
				{
					auto label = ui::Label( "Total Entities:" );
					label.show();
					table.attach(label, {0, 1, 1, 2}, {GTK_FILL, 0});
					gtk_misc_set_alignment( GTK_MISC( label ), 0.0, 0.5 );
				}
				{
					auto label = ui::Label( "" );
					label.show();
					table.attach(label, {1, 2, 1, 2}, {GTK_FILL | GTK_EXPAND, 0}, {3, 0});
					gtk_misc_set_alignment( GTK_MISC( label ), 0.0, 0.5 );
					w_ents = label;
				}
				{
					auto label = ui::Label( "Ingame Entities:" );
					label.show();
					table.attach(label, {2, 3, 1, 2}, {GTK_FILL, 0});
					gtk_misc_set_alignment( GTK_MISC( label ), 0.0, 0.5 );
				}
				{
					auto label = ui::Label( "" );
					label.show();
					table.attach(label, {3, 4, 1, 2}, {GTK_FILL | GTK_EXPAND, 0 }, {3, 0});
					gtk_misc_set_alignment( GTK_MISC( label ), 0.0, 0.5 );
					w_ents_ingame = label;
				}
				{
					auto label = ui::Label( "Group Entities:" );
					label.show();
					table.attach(label, {0, 1, 2, 3}, {GTK_FILL, 0});
					gtk_misc_set_alignment( GTK_MISC( label ), 0.0, 0.5 );
				}
				{
					auto label = ui::Label( "" );
					label.show();
					table.attach(label, {1, 2, 2, 3}, {GTK_FILL | GTK_EXPAND, 0}, {3, 0});
					gtk_misc_set_alignment( GTK_MISC( label ), 0.0, 0.5 );
					w_groupents = label;
				}
				{
					auto label = ui::Label( "Ingame Group Entities:" );
					label.show();
					table.attach(label, {2, 3, 2, 3}, {GTK_FILL, 0});
					gtk_misc_set_alignment( GTK_MISC( label ), 0.0, 0.5 );
				}
				{
					auto label = ui::Label( "" );
					label.show();
					table.attach(label, {3, 4, 2, 3}, {GTK_FILL | GTK_EXPAND, 0}, {3, 0});
					gtk_misc_set_alignment( GTK_MISC( label ), 0.0, 0.5 );
					w_groupents_ingame = label;
				}

			}
			{
				auto vbox2 = create_dialog_vbox( 4 );
				hbox.pack_start( vbox2, FALSE, FALSE, 0 );

				{
					auto button = create_dialog_button( "Close", G_CALLBACK( dialog_button_ok ), &dialog );
					vbox2.pack_start( button, FALSE, FALSE, 0 );
				}
			}
		}
		{
			ui::Widget label = ui::Label( "*** Entity breakdown ***" );
			label.show();
			vbox.pack_start( label, FALSE, TRUE, 0 );
			gtk_misc_set_alignment( GTK_MISC( label ), 0, 0.5 );
		}
		{
			auto scr = create_scrolled_window( ui::Policy::NEVER, ui::Policy::AUTOMATIC, 4 );
			vbox.pack_start( scr, TRUE, TRUE, 0 );

			{
				auto store = ui::ListStore::from(gtk_list_store_new( 2, G_TYPE_STRING, G_TYPE_UINT ));

				auto view = ui::TreeView(ui::TreeModel::from(store._handle));
				gtk_tree_view_set_headers_clickable(view, TRUE );

				{
					auto renderer = ui::CellRendererText(ui::New);
					auto column = ui::TreeViewColumn( "Entity", renderer, {{"text", 0}} );
					gtk_tree_view_append_column(view, column );
					gtk_tree_view_column_set_sort_column_id( column, 0 );
				}

				{
					auto renderer = ui::CellRendererText(ui::New);
					auto column = ui::TreeViewColumn( "Count", renderer, {{"text", 1}} );
					gtk_tree_view_append_column(view, column );
					gtk_tree_view_column_set_sort_column_id( column, 1 );
				}

				view.show();

				scr.add(view);

				EntityBreakdownWalker = store;
			}
		}
	}

	// Initialize fields

	{
		EntityBreakdown entitymap;
		Scene_EntityBreakdown( entitymap );

		for ( EntityBreakdown::iterator i = entitymap.begin(); i != entitymap.end(); ++i )
		{
			EntityBreakdownWalker.append(0, (*i).first.c_str(), 1, Unsigned( ( *i ).second ));
		}
	}

	EntityBreakdownWalker.unref();

	int n_patches = 0;
	int n_ents_ingame = 0;
	int n_groupents = 0;
	int n_groupents_ingame = 0;
	Scene_CountStuff( n_patches, n_ents_ingame, n_groupents, n_groupents_ingame );
	//globalOutputStream() << n_patches << n_ents_ingame << n_groupents << n_groupents_ingame << "\n";

	char *markup;

	markup = g_markup_printf_escaped( "<span style=\"italic\"><b>%u</b></span>  ", Unsigned( g_brushCount.get() ) );
	gtk_label_set_markup( GTK_LABEL( w_brushes ), markup );
	g_free( markup );

	markup = g_markup_printf_escaped( "<span style=\"italic\"><b>%i</b></span>  ", n_patches );
	gtk_label_set_markup( GTK_LABEL( w_patches ), markup );
	g_free( markup );

	markup = g_markup_printf_escaped( "<span style=\"italic\"><b>%u</b></span>  ", Unsigned( g_entityCount.get() ) );
	gtk_label_set_markup( GTK_LABEL( w_ents ), markup );
	g_free( markup );

	markup = g_markup_printf_escaped( "<span style=\"italic\"><b>%i</b></span>  ", n_ents_ingame );
	gtk_label_set_markup( GTK_LABEL( w_ents_ingame ), markup );
	g_free( markup );

	markup = g_markup_printf_escaped( "<span style=\"italic\"><b>%i</b></span>  ", n_groupents );
	gtk_label_set_markup( GTK_LABEL( w_groupents ), markup );
	g_free( markup );

	markup = g_markup_printf_escaped( "<span style=\"italic\"><b>%i</b></span>  ", n_groupents_ingame );
	gtk_label_set_markup( GTK_LABEL( w_groupents_ingame ), markup );
	g_free( markup );


	modal_dialog_show( window, dialog );

	// save before exit
	window_get_position( window, g_posMapInfoWnd );

    window.destroy();
}



class ScopeTimer
{
Timer m_timer;
const char* m_message;
public:
ScopeTimer( const char* message )
	: m_message( message ){
	m_timer.start();
}

~ScopeTimer(){
	double elapsed_time = m_timer.elapsed_msec() / 1000.f;
	globalOutputStream() << m_message << " timer: " << FloatFormat( elapsed_time, 5, 2 ) << " second(s) elapsed\n";
}
};

CopiedString g_strLastMapFolder = "";

/*
   ================
   Map_LoadFile
   ================
 */

void Map_LoadFile( const char *filename ){
	g_map.m_name = filename;

	// refresh VFS to apply new pak filtering based on mapname
	// needed for daemon DPK VFS
	VFS_Refresh();

	globalOutputStream() << "Loading map from " << filename << "\n";
	ScopeDisableScreenUpdates disableScreenUpdates( "Processing...", "Loading Map" );

	MRU_AddFile( filename );
	g_strLastMapFolder = g_path_get_dirname( filename );

	bool switch_format = false;

	{
		ScopeTimer timer( "map load" );

		const MapFormat* format = NULL;
		const char* moduleName = findModuleName( &GlobalFiletypes(), MapFormat::Name(), path_get_extension( filename ) );
		if ( string_not_empty( moduleName ) ) {
			format = ReferenceAPI_getMapModules().findModule( moduleName );
		}

		for ( int i = 0; i < Brush_toggleFormatCount(); ++i )
		{
			if ( i ) {
				Map_Free();
			}
			Brush_toggleFormat( i );
			Map_UpdateTitle( g_map );

			g_map.m_resource = GlobalReferenceCache().capture( g_map.m_name.c_str() );
			if ( format ) {
				format->wrongFormat = false;
			}
			g_map.m_resource->attach( g_map );
			if ( format ) {
				if ( !format->wrongFormat ) {
					break;
				}
				switch_format = !switch_format;
			}
		}

		Node_getTraversable( GlobalSceneGraph().root() )->traverse( entity_updateworldspawn() );
	}

	globalOutputStream() << "--- LoadMapFile ---\n";
	globalOutputStream() << g_map.m_name.c_str() << "\n";

	globalOutputStream() << Unsigned( g_brushCount.get() ) << " primitive\n";
	globalOutputStream() << Unsigned( g_entityCount.get() ) << " entities\n";

	//GlobalEntityCreator().printStatistics();

	//
	// move the view to a start position
	//
	Map_StartPosition();

	g_currentMap = &g_map;

	Brush_switchFormat( switch_format );
}

class Excluder
{
public:
virtual bool excluded( scene::Node& node ) const = 0;
};

class ExcludeWalker : public scene::Traversable::Walker
{
const scene::Traversable::Walker& m_walker;
const Excluder* m_exclude;
mutable bool m_skip;
public:
ExcludeWalker( const scene::Traversable::Walker& walker, const Excluder& exclude )
	: m_walker( walker ), m_exclude( &exclude ), m_skip( false ){
}

bool pre( scene::Node& node ) const {
	if ( m_exclude->excluded( node ) || node.isRoot() ) {
		m_skip = true;
		return false;
	}
	else
	{
		m_walker.pre( node );
	}
	return true;
}

void post( scene::Node& node ) const {
	if ( m_skip ) {
		m_skip = false;
	}
	else
	{
		m_walker.post( node );
	}
}
};

class AnyInstanceSelected : public scene::Instantiable::Visitor
{
bool& m_selected;
public:
AnyInstanceSelected( bool& selected ) : m_selected( selected ){
	m_selected = false;
}

void visit( scene::Instance& instance ) const {
	Selectable* selectable = Instance_getSelectable( instance );
	if ( selectable != 0
		 && selectable->isSelected() ) {
		m_selected = true;
	}
}
};

bool Node_instanceSelected( scene::Node& node ){
	scene::Instantiable* instantiable = Node_getInstantiable( node );
	ASSERT_NOTNULL( instantiable );
	bool selected;
	instantiable->forEachInstance( AnyInstanceSelected( selected ) );
	return selected;
}

class SelectedDescendantWalker : public scene::Traversable::Walker
{
bool& m_selected;
public:
SelectedDescendantWalker( bool& selected ) : m_selected( selected ){
	m_selected = false;
}

bool pre( scene::Node& node ) const {
	if ( node.isRoot() ) {
		return false;
	}

	if ( Node_instanceSelected( node ) ) {
		m_selected = true;
	}

	return true;
}
};

bool Node_selectedDescendant( scene::Node& node ){
	bool selected;
	Node_traverseSubgraph( node, SelectedDescendantWalker( selected ) );
	return selected;
}

class SelectionExcluder : public Excluder
{
public:
bool excluded( scene::Node& node ) const {
	return !Node_selectedDescendant( node );
}
};

class IncludeSelectedWalker : public scene::Traversable::Walker
{
const scene::Traversable::Walker& m_walker;
mutable std::size_t m_selected;
mutable bool m_skip;

bool selectedParent() const {
	return m_selected != 0;
}

public:
IncludeSelectedWalker( const scene::Traversable::Walker& walker )
	: m_walker( walker ), m_selected( 0 ), m_skip( false ){
}

bool pre( scene::Node& node ) const {
	// include node if:
	// node is not a 'root' AND ( node is selected OR any child of node is selected OR any parent of node is selected )
	if ( !node.isRoot() && ( Node_selectedDescendant( node ) || selectedParent() ) ) {
		if ( Node_instanceSelected( node ) ) {
			++m_selected;
		}
		m_walker.pre( node );
		return true;
	}
	else
	{
		m_skip = true;
		return false;
	}
}

void post( scene::Node& node ) const {
	if ( m_skip ) {
		m_skip = false;
	}
	else
	{
		if ( Node_instanceSelected( node ) ) {
			--m_selected;
		}
		m_walker.post( node );
	}
}
};

void Map_Traverse_Selected( scene::Node& root, const scene::Traversable::Walker& walker ){
	scene::Traversable* traversable = Node_getTraversable( root );
	if ( traversable != 0 ) {
#if 0
		traversable->traverse( ExcludeWalker( walker, SelectionExcluder() ) );
#else
		traversable->traverse( IncludeSelectedWalker( walker ) );
#endif
	}
}

void Map_ExportSelected( TextOutputStream& out, const MapFormat& format ){
	format.writeGraph( GlobalSceneGraph().root(), Map_Traverse_Selected, out, g_writeMapComments );
}

void Map_Traverse( scene::Node& root, const scene::Traversable::Walker& walker ){
	scene::Traversable* traversable = Node_getTraversable( root );
	if ( traversable != 0 ) {
		traversable->traverse( walker );
	}
}

class RegionExcluder : public Excluder
{
public:
bool excluded( scene::Node& node ) const {
	return node.excluded();
}
};

void Map_Traverse_Region( scene::Node& root, const scene::Traversable::Walker& walker ){
	scene::Traversable* traversable = Node_getTraversable( root );
	if ( traversable != 0 ) {
		traversable->traverse( ExcludeWalker( walker, RegionExcluder() ) );
	}
}

bool Map_SaveRegion( const char *filename ){
	AddRegionBrushes();

	bool success = MapResource_saveFile( MapFormat_forFile( filename ), GlobalSceneGraph().root(), Map_Traverse_Region, filename );

	RemoveRegionBrushes();

	return success;
}


void Map_RenameAbsolute( const char* absolute ){
	Resource* resource = GlobalReferenceCache().capture( absolute );
	NodeSmartReference clone( NewMapRoot( path_make_relative( absolute, GlobalFileSystem().findRoot( absolute ) ) ) );
	resource->setNode( clone.get_pointer() );

	{
		//ScopeTimer timer("clone subgraph");
		Node_getTraversable( GlobalSceneGraph().root() )->traverse( CloneAll( clone ) );
	}

	g_map.m_resource->detach( g_map );
	GlobalReferenceCache().release( g_map.m_name.c_str() );

	g_map.m_resource = resource;

	g_map.m_name = absolute;
	Map_UpdateTitle( g_map );

	g_map.m_resource->attach( g_map );
	// refresh VFS to apply new pak filtering based on mapname
	// needed for daemon DPK VFS
	VFS_Refresh();
}

void Map_Rename( const char* filename ){
	if ( !string_equal( g_map.m_name.c_str(), filename ) ) {
		ScopeDisableScreenUpdates disableScreenUpdates( "Processing...", "Saving Map" );

		Map_RenameAbsolute( filename );

		SceneChangeNotify();
	}
	else
	{
		SaveReferences();
	}
}

bool Map_Save(){
	Pointfile_Clear();

	ScopeTimer timer( "map save" );
	SaveReferences();
	return true; // assume success..
}

/*
   ===========
   Map_New

   ===========
 */
void Map_New(){
	//globalOutputStream() << "Map_New\n";

	g_map.m_name = "unnamed.map";
	Map_UpdateTitle( g_map );

	{
		g_map.m_resource = GlobalReferenceCache().capture( g_map.m_name.c_str() );
//    ASSERT_MESSAGE(g_map.m_resource->getNode() == 0, "bleh");
		g_map.m_resource->attach( g_map );

		SceneChangeNotify();
	}

	FocusViews( g_vector3_identity, 0 );

	g_currentMap = &g_map;

	// restart VFS to apply new pak filtering based on mapname
	// needed for daemon DPK VFS
	VFS_Restart();
}

extern void ConstructRegionBrushes( scene::Node * brushes[6], const Vector3 &region_mins, const Vector3 &region_maxs );

void ConstructRegionStartpoint( scene::Node* startpoint, const Vector3& region_mins, const Vector3& region_maxs ){
	/*!
	   \todo we need to make sure that the player start IS inside the region and bail out if it's not
	   the compiler will refuse to compile a map with a player_start somewhere in empty space..
	   for now, let's just print an error
	 */

	Vector3 vOrig( Camera_getOrigin( *g_pParentWnd->GetCamWnd() ) );

	for ( int i = 0 ; i < 3 ; i++ )
	{
		if ( vOrig[i] > region_maxs[i] || vOrig[i] < region_mins[i] ) {
			globalErrorStream() << "Camera is NOT in the region, it's likely that the region won't compile correctly\n";
			break;
		}
	}

	// write the info_playerstart
	char sTmp[1024];
	sprintf( sTmp, "%d %d %d", (int)vOrig[0], (int)vOrig[1], (int)vOrig[2] );
	Node_getEntity( *startpoint )->setKeyValue( "origin", sTmp );
	sprintf( sTmp, "%d", (int)Camera_getAngles( *g_pParentWnd->GetCamWnd() )[CAMERA_YAW] );
	Node_getEntity( *startpoint )->setKeyValue( "angle", sTmp );
}

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

   REGION

   ===========================================================
 */
bool region_active = false;

ConstReferenceCaller<bool, void(const Callback<void(bool)> &), PropertyImpl<bool>::Export> g_region_caller( region_active );

ToggleItem g_region_item( g_region_caller );

/*void Map_ToggleRegion(){
	region_active = !region_active;
	g_region_item.update();
}*/

Vector3 region_mins( g_MinWorldCoord, g_MinWorldCoord, g_MinWorldCoord );
Vector3 region_maxs( g_MaxWorldCoord, g_MaxWorldCoord, g_MaxWorldCoord );

scene::Node* region_sides[6];
scene::Node* region_startpoint = 0;

/*
   ===========
   AddRegionBrushes
   a regioned map will have temp walls put up at the region boundary
   \todo TODO TTimo old implementation of region brushes
   we still add them straight in the worldspawn and take them out after the map is saved
   with the new implementation we should be able to append them in a temporary manner to the data we pass to the map module
   ===========
 */
void AddRegionBrushes( void ){
	int i;

	for ( i = 0; i < 6; i++ )
	{
		region_sides[i] = &GlobalBrushCreator().createBrush();
		Node_getTraversable( Map_FindOrInsertWorldspawn( g_map ) )->insert( NodeSmartReference( *region_sides[i] ) );
	}

	region_startpoint = &GlobalEntityCreator().createEntity( GlobalEntityClassManager().findOrInsert( "info_player_start", false ) );

	ConstructRegionBrushes( region_sides, region_mins, region_maxs );
	ConstructRegionStartpoint( region_startpoint, region_mins, region_maxs );

	Node_getTraversable( GlobalSceneGraph().root() )->insert( NodeSmartReference( *region_startpoint ) );
}

void RemoveRegionBrushes( void ){
	for ( std::size_t i = 0; i < 6; i++ )
	{
		Node_getTraversable( *Map_GetWorldspawn( g_map ) )->erase( *region_sides[i] );
	}
	Node_getTraversable( GlobalSceneGraph().root() )->erase( *region_startpoint );
}

inline void exclude_node( scene::Node& node, bool exclude ){
	exclude
	? node.enable( scene::Node::eExcluded )
	: node.disable( scene::Node::eExcluded );
}

class ExcludeAllWalker : public scene::Graph::Walker
{
bool m_exclude;
public:
ExcludeAllWalker( bool exclude )
	: m_exclude( exclude ){
}

bool pre( const scene::Path& path, scene::Instance& instance ) const {
	exclude_node( path.top(), m_exclude );

	return true;
}
};

void Scene_Exclude_All( bool exclude ){
	GlobalSceneGraph().traverse( ExcludeAllWalker( exclude ) );
}

bool Instance_isSelected( const scene::Instance& instance ){
	const Selectable* selectable = Instance_getSelectable( instance );
	return selectable != 0 && selectable->isSelected();
}

class ExcludeSelectedWalker : public scene::Graph::Walker
{
bool m_exclude;
public:
ExcludeSelectedWalker( bool exclude )
	: m_exclude( exclude ){
}

bool pre( const scene::Path& path, scene::Instance& instance ) const {
	exclude_node( path.top(), ( instance.isSelected() || instance.childSelected() || instance.parentSelected() ) == m_exclude );
	return true;
}
};

void Scene_Exclude_Selected( bool exclude ){
	GlobalSceneGraph().traverse( ExcludeSelectedWalker( exclude ) );
}

class ExcludeRegionedWalker : public scene::Graph::Walker
{
bool m_exclude;
public:
ExcludeRegionedWalker( bool exclude )
	: m_exclude( exclude ){
}

bool pre( const scene::Path& path, scene::Instance& instance ) const {
	exclude_node(
		path.top(),
		!(
			(
				aabb_intersects_aabb(
					instance.worldAABB(),
					aabb_for_minmax( region_mins, region_maxs )
					) != 0
			) ^ m_exclude
			)
		);

	return true;
}
};

void Scene_Exclude_Region( bool exclude ){
	GlobalSceneGraph().traverse( ExcludeRegionedWalker( exclude ) );
}

/*
   ===========
   Map_RegionOff

   Other filtering options may still be on
   ===========
 */
void Map_RegionOff(){
	region_active = false;
	g_region_item.update();

	region_maxs[0] = g_MaxWorldCoord - 64;
	region_mins[0] = g_MinWorldCoord + 64;
	region_maxs[1] = g_MaxWorldCoord - 64;
	region_mins[1] = g_MinWorldCoord + 64;
	region_maxs[2] = g_MaxWorldCoord - 64;
	region_mins[2] = g_MinWorldCoord + 64;

	Scene_Exclude_All( false );
}

void Map_ApplyRegion( void ){
	region_active = true;
	g_region_item.update();

	Scene_Exclude_Region( false );
}


/*
   ========================
   Map_RegionSelectedBrushes
   ========================
 */
void Map_RegionSelectedBrushes( void ){
	Map_RegionOff();

	if ( GlobalSelectionSystem().countSelected() != 0
		 && GlobalSelectionSystem().Mode() == SelectionSystem::ePrimitive ) {
		region_active = true;
		g_region_item.update();
		Select_GetBounds( region_mins, region_maxs );

		Scene_Exclude_Selected( false );

		GlobalSelectionSystem().setSelectedAll( false );
	}
}


/*
   ===========
   Map_RegionXY
   ===========
 */
void Map_RegionXY( float x_min, float y_min, float x_max, float y_max ){
	Map_RegionOff();

	region_mins[0] = x_min;
	region_maxs[0] = x_max;
	region_mins[1] = y_min;
	region_maxs[1] = y_max;
	region_mins[2] = g_MinWorldCoord + 64;
	region_maxs[2] = g_MaxWorldCoord - 64;

	Map_ApplyRegion();
}

void Map_RegionBounds( const AABB& bounds ){
	Map_RegionOff();

	region_mins = vector3_subtracted( bounds.origin, bounds.extents );
	region_maxs = vector3_added( bounds.origin, bounds.extents );

	deleteSelection();

	Map_ApplyRegion();
}

/*
   ===========
   Map_RegionBrush
   ===========
 */
void Map_RegionBrush( void ){
	if ( GlobalSelectionSystem().countSelected() != 0 ) {
		scene::Instance& instance = GlobalSelectionSystem().ultimateSelected();
		Map_RegionBounds( instance.worldAABB() );
	}
}

//
//================
//Map_ImportFile
//================
//
bool Map_ImportFile( const char* filename ){
	ScopeDisableScreenUpdates disableScreenUpdates( "Processing...", "Loading Map" );

	g_strLastMapFolder = g_path_get_dirname( filename );

	bool success = false;

	if ( extension_equal( path_get_extension( filename ), "bsp" ) ) {
		goto tryDecompile;
	}

	{
		const MapFormat* format = NULL;
		const char* moduleName = findModuleName( &GlobalFiletypes(), MapFormat::Name(), path_get_extension( filename ) );
		if ( string_not_empty( moduleName ) ) {
			format = ReferenceAPI_getMapModules().findModule( moduleName );
		}

		if ( format ) {
			format->wrongFormat = false;
		}
		Resource* resource = GlobalReferenceCache().capture( filename );
		resource->refresh(); // avoid loading old version if map has changed on disk since last import
		if ( !resource->load() ) {
			GlobalReferenceCache().release( filename );
			goto tryDecompile;
		}
		if ( format ) {
			if ( format->wrongFormat ) {
				GlobalReferenceCache().release( filename );
				goto tryDecompile;
			}
		}
		NodeSmartReference clone( NewMapRoot( "" ) );
		Node_getTraversable( *resource->getNode() )->traverse( CloneAll( clone ) );
		Map_gatherNamespaced( clone );
		Map_mergeClonedNames();
		MergeMap( clone );
		success = true;
		GlobalReferenceCache().release( filename );
	}

	SceneChangeNotify();

	return success;

tryDecompile:

	const char *type = GlobalRadiant().getGameDescriptionKeyValue( "q3map2_type" );
	int n = string_length( path_get_extension( filename ) );
	if ( n && ( extension_equal( path_get_extension( filename ), "bsp" ) || extension_equal( path_get_extension( filename ), "map" ) ) ) {
		std::string output;
		output += AppPath_get();
		output += "q3map2";
		output += GDEF_OS_EXE_EXT;

		output += " -v -game ";
		output += ( type && *type ) ? type : "quake3";
		output += " -fs_basepath \"";
		output += EnginePath_get();
		output += "\" -fs_homepath \"";
		output += g_qeglobals.m_userEnginePath.c_str();
		output += "\"";

		// extra pakpaths
		for ( int i = 0; i < g_pakPathCount; i++ ) {
			if ( g_strcmp0( g_strPakPath[i].c_str(), "") ) {
				output += " -fs_pakpath \"";
				output += g_strPakPath[i].c_str();
				output += "\"";
			}
		}

		// extra switches
		if ( g_disableEnginePath ) {
			output += " -fs_nobasepath ";
		}

		if ( g_disableHomePath ) {
			output += " -fs_nohomepath ";
		}

		output += " -fs_game ";
		output += gamename_get();
		output += " -convert -format ";
		output += Brush::m_type == eBrushTypeQuake3BP ? "map_bp" : "map";
		if ( extension_equal( path_get_extension( filename ), "map" ) ) {
			output += " -readmap ";
		}
		output += " \"";
		output += filename;
		output += "\"";

		// run
		Q_Exec( NULL, output.c_str(), NULL, false, true );

		// rebuild filename as "filenamewithoutext_converted.map"
		output = "";
		output.append( filename, string_length( filename ) - ( n + 1 ) );
		output += "_converted.map";
		filename = output.c_str();

		// open
		Resource* resource = GlobalReferenceCache().capture( filename );
		resource->refresh(); // avoid loading old version if map has changed on disk since last import
		if ( !resource->load() ) {
			GlobalReferenceCache().release( filename );
			goto tryDecompile;
		}
		NodeSmartReference clone( NewMapRoot( "" ) );
		Node_getTraversable( *resource->getNode() )->traverse( CloneAll( clone ) );
		Map_gatherNamespaced( clone );
		Map_mergeClonedNames();
		MergeMap( clone );
		success = true;
		GlobalReferenceCache().release( filename );
	}

	SceneChangeNotify();
	return success;
}

/*
   ===========
   Map_SaveFile
   ===========
 */
bool Map_SaveFile( const char* filename ){
	ScopeDisableScreenUpdates disableScreenUpdates( "Processing...", "Saving Map" );
	bool success = MapResource_saveFile( MapFormat_forFile( filename ), GlobalSceneGraph().root(), Map_Traverse, filename );
	if ( success ) {
		// refresh VFS to apply new pak filtering based on mapname
		// needed for daemon DPK VFS
		VFS_Refresh();
	}
	return success;
}

//
//===========
//Map_SaveSelected
//===========
//
// Saves selected world brushes and whole entities with partial/full selections
//
bool Map_SaveSelected( const char* filename ){
	return MapResource_saveFile( MapFormat_forFile( filename ), GlobalSceneGraph().root(), Map_Traverse_Selected, filename );
}

class ParentSelectedBrushesToEntityWalker : public scene::Graph::Walker
{
	scene::Node& m_parent;
	mutable bool m_emptyOldParent;

public:
ParentSelectedBrushesToEntityWalker( scene::Node& parent ) : m_parent( parent ), m_emptyOldParent( false ){
}

bool pre( const scene::Path& path, scene::Instance& instance ) const {
	if ( path.top().get_pointer() != &m_parent && ( Node_isPrimitive( path.top() ) || m_emptyOldParent ) ) {
		Selectable* selectable = Instance_getSelectable( instance );
		if ( selectable && selectable->isSelected() && path.size() > 1 ) {
			return false;
		}
	}
	return true;
}

void post( const scene::Path& path, scene::Instance& instance ) const {
	if ( path.top().get_pointer() == &m_parent )
		return;

	if ( Node_isPrimitive( path.top() ) ){
		m_emptyOldParent = false;
		Selectable* selectable = Instance_getSelectable( instance );

		if ( selectable && selectable->isSelected() && path.size() > 1 ){
			scene::Node& parent = path.parent();
			if ( &parent != &m_parent ){
				NodeSmartReference node( path.top().get() );
				scene::Traversable* traversable_parent = Node_getTraversable( parent );
				traversable_parent->erase( node );
				Node_getTraversable( m_parent )->insert( node );
				if ( traversable_parent->empty() )
					m_emptyOldParent = true;
			}
		}
	}
	else if ( m_emptyOldParent ){
		m_emptyOldParent = false;
		// delete empty entities
		Entity* entity = Node_getEntity( path.top() );
		if ( entity != 0 && path.top().get_pointer() != Map_FindWorldspawn( g_map )	&& Node_getTraversable( path.top() )->empty() ) {
			Path_deleteTop( path );
		}
	}
}
};

void Scene_parentSelectedBrushesToEntity( scene::Graph& graph, scene::Node& parent ){
	graph.traverse( ParentSelectedBrushesToEntityWalker( parent ) );
}

class CountSelectedBrushes : public scene::Graph::Walker
{
std::size_t& m_count;
mutable std::size_t m_depth;
public:
CountSelectedBrushes( std::size_t& count ) : m_count( count ), m_depth( 0 ){
	m_count = 0;
}

bool pre( const scene::Path& path, scene::Instance& instance ) const {
	if ( ++m_depth != 1 && path.top().get().isRoot() ) {
		return false;
	}
	Selectable* selectable = Instance_getSelectable( instance );
	if ( selectable != 0
		 && selectable->isSelected()
		 && Node_isPrimitive( path.top() ) ) {
		++m_count;
	}
	return true;
}

void post( const scene::Path& path, scene::Instance& instance ) const {
	--m_depth;
}
};

std::size_t Scene_countSelectedBrushes( scene::Graph& graph ){
	std::size_t count;
	graph.traverse( CountSelectedBrushes( count ) );
	return count;
}

enum ENodeType
{
	eNodeUnknown,
	eNodeMap,
	eNodeEntity,
	eNodePrimitive,
};

const char* nodetype_get_name( ENodeType type ){
	if ( type == eNodeMap ) {
		return "map";
	}
	if ( type == eNodeEntity ) {
		return "entity";
	}
	if ( type == eNodePrimitive ) {
		return "primitive";
	}
	return "unknown";
}

ENodeType node_get_nodetype( scene::Node& node ){
	if ( Node_isEntity( node ) ) {
		return eNodeEntity;
	}
	if ( Node_isPrimitive( node ) ) {
		return eNodePrimitive;
	}
	return eNodeUnknown;
}

bool contains_entity( scene::Node& node ){
	return Node_getTraversable( node ) != 0 && !Node_isBrush( node ) && !Node_isPatch( node ) && !Node_isEntity( node );
}

bool contains_primitive( scene::Node& node ){
	return Node_isEntity( node ) && Node_getTraversable( node ) != 0 && Node_getEntity( node )->isContainer();
}

ENodeType node_get_contains( scene::Node& node ){
	if ( contains_entity( node ) ) {
		return eNodeEntity;
	}
	if ( contains_primitive( node ) ) {
		return eNodePrimitive;
	}
	return eNodeUnknown;
}

void Path_parent( const scene::Path& parent, const scene::Path& child ){
	ENodeType contains = node_get_contains( parent.top() );
	ENodeType type = node_get_nodetype( child.top() );

	if ( contains != eNodeUnknown && contains == type ) {
		NodeSmartReference node( child.top().get() );
		Path_deleteTop( child );
		Node_getTraversable( parent.top() )->insert( node );
		SceneChangeNotify();
	}
	else
	{
		globalErrorStream() << "failed - " << nodetype_get_name( type ) << " cannot be parented to " << nodetype_get_name( contains ) << " container.\n";
	}
}

void Scene_parentSelected(){
	UndoableCommand undo( "parentSelected" );

	if ( GlobalSelectionSystem().countSelected() > 1 ) {
		class ParentSelectedBrushesToEntityWalker : public SelectionSystem::Visitor
		{
		const scene::Path& m_parent;
public:
		ParentSelectedBrushesToEntityWalker( const scene::Path& parent ) : m_parent( parent ){
		}

		void visit( scene::Instance& instance ) const {
			if ( &m_parent != &instance.path() ) {
				Path_parent( m_parent, instance.path() );
			}
		}
		};

		ParentSelectedBrushesToEntityWalker visitor( GlobalSelectionSystem().ultimateSelected().path() );
		GlobalSelectionSystem().foreachSelected( visitor );
	}
	else
	{
		globalOutputStream() << "failed - did not find two selected nodes.\n";
	}
}


void NewMap(){
	if ( ConfirmModified( "New Map" ) ) {
		Map_RegionOff();
		Map_Free();
		Map_New();
	}
}

CopiedString g_mapsPath;

const char* getMapsPath(){
	return g_mapsPath.c_str();
}

const char* getLastMapFolderPath(){
	if (g_strLastMapFolder.empty()) {
		GlobalPreferenceSystem().registerPreference( "LastMapFolder", make_property_string( g_strLastMapFolder ) );
		if (g_strLastMapFolder.empty()) {
			StringOutputStream buffer( 1024 );
			buffer << getMapsPath();
			if ( !file_readable( buffer.c_str() ) ) {
				buffer.clear();
				buffer << g_qeglobals.m_userGamePath.c_str() << "/";
			}
			g_strLastMapFolder = buffer.c_str();
		}
	}
	return g_strLastMapFolder.c_str();
}

const char* map_open( const char* title ){
	return MainFrame_getWindow().file_dialog( TRUE, title, getLastMapFolderPath(), MapFormat::Name(), true, false, false );
}

const char* map_import( const char* title ){
	return MainFrame_getWindow().file_dialog( TRUE, title, getLastMapFolderPath(), MapFormat::Name(), false, true, false );
}

const char* map_save( const char* title ){
	return MainFrame_getWindow().file_dialog( FALSE, title, getLastMapFolderPath(), MapFormat::Name(), false, false, true );
}

void OpenMap(){
	if ( !ConfirmModified( "Open Map" ) ) {
		return;
	}

	const char* filename = map_open( "Open Map" );

	if ( filename != NULL ) {
		MRU_AddFile( filename );
		Map_RegionOff();
		Map_Free();
		Map_LoadFile( filename );
	}
}

void ImportMap(){
	const char* filename = map_import( "Import Map" );

	if ( filename != NULL ) {
		UndoableCommand undo( "mapImport" );
		Map_ImportFile( filename );
	}
}

bool Map_SaveAs(){
	const char* filename = map_save( "Save Map" );

	if ( filename != NULL ) {
		g_strLastMapFolder = g_path_get_dirname( filename );
		MRU_AddFile( filename );
		Map_Rename( filename );
		return Map_Save();
	}
	return false;
}

void SaveMapAs(){
	Map_SaveAs();
}

void SaveMap(){
	if ( Map_Unnamed( g_map ) ) {
		SaveMapAs();
	}
	else if ( Map_Modified( g_map ) ) {
		Map_Save();
		MRU_AddFile( g_map.m_name.c_str() );	//add on saving, but not opening via cmd line: spoils the list
	}
}

void ExportMap(){
	const char* filename = map_save( "Export Selection" );

	if ( filename != NULL ) {
		g_strLastMapFolder = g_path_get_dirname( filename );
		Map_SaveSelected( filename );
	}
}

void SaveRegion(){
	const char* filename = map_save( "Export Region" );

	if ( filename != NULL ) {
		g_strLastMapFolder = g_path_get_dirname( filename );
		Map_SaveRegion( filename );
	}
}


void RegionOff(){
	Map_RegionOff();
	SceneChangeNotify();
}

void RegionXY(){
	Map_RegionXY(
		g_pParentWnd->GetXYWnd()->GetOrigin()[0] - 0.5f * g_pParentWnd->GetXYWnd()->Width() / g_pParentWnd->GetXYWnd()->Scale(),
		g_pParentWnd->GetXYWnd()->GetOrigin()[1] - 0.5f * g_pParentWnd->GetXYWnd()->Height() / g_pParentWnd->GetXYWnd()->Scale(),
		g_pParentWnd->GetXYWnd()->GetOrigin()[0] + 0.5f * g_pParentWnd->GetXYWnd()->Width() / g_pParentWnd->GetXYWnd()->Scale(),
		g_pParentWnd->GetXYWnd()->GetOrigin()[1] + 0.5f * g_pParentWnd->GetXYWnd()->Height() / g_pParentWnd->GetXYWnd()->Scale()
		);
	SceneChangeNotify();
}

void RegionBrush(){
	Map_RegionBrush();
	SceneChangeNotify();
}

void RegionSelected(){
	Map_RegionSelectedBrushes();
	SceneChangeNotify();
}





class BrushFindByIndexWalker : public scene::Traversable::Walker
{
mutable std::size_t m_index;
scene::Path& m_path;
public:
BrushFindByIndexWalker( std::size_t index, scene::Path& path )
	: m_index( index ), m_path( path ){
}

bool pre( scene::Node& node ) const {
	if ( Node_isPrimitive( node ) && m_index-- == 0 ) {
		m_path.push( makeReference( node ) );
	}
	return false;
}
};

class EntityFindByIndexWalker : public scene::Traversable::Walker
{
mutable std::size_t m_index;
scene::Path& m_path;
public:
EntityFindByIndexWalker( std::size_t index, scene::Path& path )
	: m_index( index ), m_path( path ){
}

bool pre( scene::Node& node ) const {
	if ( Node_isEntity( node ) && m_index-- == 0 ) {
		m_path.push( makeReference( node ) );
	}
	return false;
}
};

void Scene_FindEntityBrush( std::size_t entity, std::size_t brush, scene::Path& path ){
	path.push( makeReference( GlobalSceneGraph().root() ) );
	{
		Node_getTraversable( path.top() )->traverse( EntityFindByIndexWalker( entity, path ) );
	}
	if ( path.size() == 2 ) {
		scene::Traversable* traversable = Node_getTraversable( path.top() );
		if ( traversable != 0 ) {
			traversable->traverse( BrushFindByIndexWalker( brush, path ) );
		}
	}
}

inline bool Node_hasChildren( scene::Node& node ){
	scene::Traversable* traversable = Node_getTraversable( node );
	return traversable != 0 && !traversable->empty();
}

void SelectBrush( int entitynum, int brushnum ){
	scene::Path path;
	Scene_FindEntityBrush( entitynum, brushnum, path );
	if ( path.size() == 3 || ( path.size() == 2 && !Node_hasChildren( path.top() ) ) ) {
		scene::Instance* instance = GlobalSceneGraph().find( path );
		ASSERT_MESSAGE( instance != 0, "SelectBrush: path not found in scenegraph" );
		Selectable* selectable = Instance_getSelectable( *instance );
		ASSERT_MESSAGE( selectable != 0, "SelectBrush: path not selectable" );
		selectable->setSelected( true );
		g_pParentWnd->GetXYWnd()->PositionView( instance->worldAABB().origin );
	}
}


class BrushFindIndexWalker : public scene::Traversable::Walker
{
mutable const scene::Node* m_node;
std::size_t& m_count;
public:
BrushFindIndexWalker( const scene::Node& node, std::size_t& count )
	: m_node( &node ), m_count( count ){
}

bool pre( scene::Node& node ) const {
	if ( Node_isPrimitive( node ) ) {
		if ( m_node == &node ) {
			m_node = 0;
		}
		if ( m_node ) {
			++m_count;
		}
	}
	return true;
}
};

class EntityFindIndexWalker : public scene::Traversable::Walker
{
mutable const scene::Node* m_node;
std::size_t& m_count;
public:
EntityFindIndexWalker( const scene::Node& node, std::size_t& count )
	: m_node( &node ), m_count( count ){
}

bool pre( scene::Node& node ) const {
	if ( Node_isEntity( node ) ) {
		if ( m_node == &node ) {
			m_node = 0;
		}
		if ( m_node ) {
			++m_count;
		}
	}
	return true;
}
};

static void GetSelectionIndex( int *ent, int *brush ){
	std::size_t count_brush = 0;
	std::size_t count_entity = 0;
	if ( GlobalSelectionSystem().countSelected() != 0 ) {
		const scene::Path& path = GlobalSelectionSystem().ultimateSelected().path();

		{
			scene::Traversable* traversable = Node_getTraversable( path.parent() );
			if ( traversable != 0 && path.size() == 3 ) {
				traversable->traverse( BrushFindIndexWalker( path.top(), count_brush ) );
			}
		}

		{
			scene::Traversable* traversable = Node_getTraversable( GlobalSceneGraph().root() );
			if ( traversable != 0 ) {
				if( path.size() == 3 ){
					traversable->traverse( EntityFindIndexWalker( path.parent(), count_entity ) );
				}
				else if ( path.size() == 2 ){
					traversable->traverse( EntityFindIndexWalker( path.top(), count_entity ) );
				}
			}
		}
	}
	*brush = int(count_brush);
	*ent = int(count_entity);
}

void DoFind(){
	ModalDialog dialog;
	ui::Entry entity{ui::null};
	ui::Entry brush{ui::null};

	ui::Window window = MainFrame_getWindow().create_dialog_window("Find Brush", G_CALLBACK(dialog_delete_callback ), &dialog );

	auto accel = ui::AccelGroup(ui::New);
	window.add_accel_group( accel );

	{
		auto vbox = create_dialog_vbox( 4, 4 );
		window.add(vbox);
		{
			auto table = create_dialog_table( 2, 2, 4, 4 );
			vbox.pack_start( table, TRUE, TRUE, 0 );
			{
				ui::Widget label = ui::Label( "Entity number" );
				label.show();
                (table).attach(label, {0, 1, 0, 1}, {0, 0});
			}
			{
				ui::Widget label = ui::Label( "Brush number" );
				label.show();
                (table).attach(label, {0, 1, 1, 2}, {0, 0});
			}
			{
				auto entry = ui::Entry(ui::New);
				entry.show();
                table.attach(entry, {1, 2, 0, 1}, {GTK_EXPAND | GTK_FILL, 0});
				gtk_widget_grab_focus( entry  );
				entity = entry;
			}
			{
				auto entry = ui::Entry(ui::New);
				entry.show();
                table.attach(entry, {1, 2, 1, 2}, {GTK_EXPAND | GTK_FILL, 0});

				brush = entry;
			}
		}
		{
			auto hbox = create_dialog_hbox( 4 );
			vbox.pack_start( hbox, TRUE, TRUE, 0 );
			{
				auto button = create_dialog_button( "Find", G_CALLBACK( dialog_button_ok ), &dialog );
				hbox.pack_start( button, FALSE, FALSE, 0 );
				widget_make_default( button );
				gtk_widget_add_accelerator( button , "clicked", accel, GDK_KEY_Return, (GdkModifierType)0, (GtkAccelFlags)0 );
			}
			{
				auto button = create_dialog_button( "Close", G_CALLBACK( dialog_button_cancel ), &dialog );
				hbox.pack_start( button, FALSE, FALSE, 0 );
				gtk_widget_add_accelerator( button , "clicked", accel, GDK_KEY_Escape, (GdkModifierType)0, (GtkAccelFlags)0 );
			}
		}
	}

	// Initialize dialog
	char buf[16];
	int ent, br;

	GetSelectionIndex( &ent, &br );
	sprintf( buf, "%i", ent );
	entity.text(buf);
	sprintf( buf, "%i", br );
	brush.text(buf);

	if ( modal_dialog_show( window, dialog ) == eIDOK ) {
		const char *entstr = gtk_entry_get_text( entity );
		const char *brushstr = gtk_entry_get_text( brush );
		SelectBrush( atoi( entstr ), atoi( brushstr ) );
	}

    window.destroy();
}

void Map_constructPreferences( PreferencesPage& page ){
	page.appendCheckBox( "", "Load last map at startup", g_bLoadLastMap );
	page.appendCheckBox( "", "Add entity and brush number comments on map write", g_writeMapComments );
}


class MapEntityClasses : public ModuleObserver
{
std::size_t m_unrealised;
public:
MapEntityClasses() : m_unrealised( 1 ){
}

void realise(){
	if ( --m_unrealised == 0 ) {
		if ( g_map.m_resource != 0 ) {
			ScopeDisableScreenUpdates disableScreenUpdates( "Processing...", "Loading Map" );
			g_map.m_resource->realise();
		}
	}
}

void unrealise(){
	if ( ++m_unrealised == 1 ) {
		if ( g_map.m_resource != 0 ) {
			g_map.m_resource->flush();
			g_map.m_resource->unrealise();
		}
	}
}
};

MapEntityClasses g_MapEntityClasses;


class MapModuleObserver : public ModuleObserver
{
std::size_t m_unrealised;
public:
MapModuleObserver() : m_unrealised( 1 ){
}

void realise(){
	if ( --m_unrealised == 0 ) {
		ASSERT_MESSAGE( !string_empty( g_qeglobals.m_userGamePath.c_str() ), "maps_directory: user-game-path is empty" );
		StringOutputStream buffer( 256 );
		buffer << g_qeglobals.m_userGamePath.c_str() << "maps/";
		Q_mkdir( buffer.c_str() );
		g_mapsPath = buffer.c_str();
	}
}

void unrealise(){
	if ( ++m_unrealised == 1 ) {
		g_mapsPath = "";
	}
}
};

MapModuleObserver g_MapModuleObserver;

CopiedString g_strLastMap;
bool g_bLoadLastMap = false;

void Map_Construct(){
	GlobalCommands_insert( "RegionOff", makeCallbackF(RegionOff) );
	GlobalCommands_insert( "RegionSetXY", makeCallbackF(RegionXY) );
	GlobalCommands_insert( "RegionSetBrush", makeCallbackF(RegionBrush) );
	//GlobalCommands_insert( "RegionSetSelection", makeCallbackF(RegionSelected), Accelerator( 'R', (GdkModifierType)( GDK_SHIFT_MASK | GDK_CONTROL_MASK ) ) );
	GlobalToggles_insert( "RegionSetSelection", makeCallbackF(RegionSelected), ToggleItem::AddCallbackCaller( g_region_item ), Accelerator( 'R', (GdkModifierType)( GDK_SHIFT_MASK | GDK_CONTROL_MASK ) ) );

	GlobalPreferenceSystem().registerPreference( "LastMap", make_property_string( g_strLastMap ) );
	GlobalPreferenceSystem().registerPreference( "LoadLastMap", make_property_string( g_bLoadLastMap ) );
	GlobalPreferenceSystem().registerPreference( "MapInfoDlg", make_property<WindowPosition_String>( g_posMapInfoWnd ) );
	GlobalPreferenceSystem().registerPreference( "WriteMapComments", make_property_string( g_writeMapComments ) );

	PreferencesDialog_addSettingsPreferences( makeCallbackF(Map_constructPreferences) );

	GlobalEntityClassManager().attach( g_MapEntityClasses );
	Radiant_attachHomePathsObserver( g_MapModuleObserver );
}

void Map_Destroy(){
	Radiant_detachHomePathsObserver( g_MapModuleObserver );
	GlobalEntityClassManager().detach( g_MapEntityClasses );
}
