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

#include "debugging/debugging.h"

#include "ientity.h"
#include "ifilesystem.h"
#include "imodel.h"
#include "iscenegraph.h"
#include "iselection.h"
#include "iundo.h"

#include <map>
#include <set>
#include <variant>

#include <gtkutil/guisettings.h>
#include <QSplitter>
#include <QTreeWidget>
#include <QHeaderView>
#include <QPlainTextEdit>
#include <QVBoxLayout>
#include <QHBoxLayout>
#include <QGridLayout>
#include <QScrollArea>
#include <QCheckBox>
#include "gtkutil/lineedit.h"
#include <QLabel>
#include <QPushButton>
#include <QToolButton>
#include <QKeyEvent>
#include <QApplication>
#include <QButtonGroup>
#include "gtkutil/combobox.h"

#include "os/path.h"
#include "eclasslib.h"
#include "scenelib.h"
#include "generic/callback.h"
#include "os/file.h"
#include "stream/stringstream.h"
#include "moduleobserver.h"
#include "stringio.h"

#include "gtkutil/accelerator.h"
#include "gtkutil/dialog.h"
#include "gtkutil/filechooser.h"
#include "gtkutil/messagebox.h"
#include "gtkutil/nonmodal.h"
#include "gtkutil/entry.h"

#include "qe3.h"
#include "gtkmisc.h"
#include "gtkdlgs.h"
#include "entity.h"
#include "mainframe.h"
#include "textureentry.h"
#include "groupdialog.h"

#include "select.h"

namespace
{
typedef std::map<CopiedString, CopiedString> KeyValues;
KeyValues g_selectedKeyValues;
KeyValues g_selectedDefaultKeyValues;
}

const char* SelectedEntity_getValueForKey( const char* key ){
	{
		KeyValues::const_iterator i = g_selectedKeyValues.find( key );
		if ( i != g_selectedKeyValues.end() ) {
			return ( *i ).second.c_str();
		}
	}
	{
		KeyValues::const_iterator i = g_selectedDefaultKeyValues.find( key );
		if ( i != g_selectedDefaultKeyValues.end() ) {
			return ( *i ).second.c_str();
		}
	}
	return "";
}

void Scene_EntitySetKeyValue_Selected_Undoable( const char* key, const char* value ){
	const auto command = StringStream( "entitySetKeyValue -key ", makeQuoted( key ), " -value ", makeQuoted( value ) );
	UndoableCommand undo( command );
	Scene_EntitySetKeyValue_Selected( key, value );
}

class EntityAttribute
{
public:
	virtual QWidget* getWidget() const = 0;
	virtual void update() = 0;
	virtual void release() = 0;
};

class BooleanAttribute final : public EntityAttribute
{
	const CopiedString m_key;
	QCheckBox* m_check;
public:
	BooleanAttribute( const char* key ) :
		m_key( key ),
		m_check( new QCheckBox )
	{
		QObject::connect( m_check, &QAbstractButton::clicked, [this](){ apply(); } );
		update();
	}
	QWidget* getWidget() const override {
		return m_check;
	}
	void release() override {
		delete this;
	}
	void apply(){
		Scene_EntitySetKeyValue_Selected_Undoable( m_key.c_str(), m_check->isChecked() ? "1" : "" );
	}
	typedef MemberCaller<BooleanAttribute, &BooleanAttribute::apply> ApplyCaller;

	void update() override {
		const char* value = SelectedEntity_getValueForKey( m_key.c_str() );
		m_check->setChecked( atoi( value ) != 0 ); // atoi( empty ) is also 0
	}
	typedef MemberCaller<BooleanAttribute, &BooleanAttribute::update> UpdateCaller;
};


class StringAttribute : public EntityAttribute
{
	const CopiedString m_key;
	NonModalEntry *m_entry;
public:
	StringAttribute( const char* key ) :
		m_key( key ),
		m_entry( new NonModalEntry( ApplyCaller( *this ), UpdateCaller( *this ) ) ){
	}
	virtual ~StringAttribute() = default;
	QWidget* getWidget() const override {
		return m_entry;
	}
	QLineEdit* getEntry() const {
		return m_entry;
	}

	void release() override {
		delete this;
	}
	void apply(){
		const auto value = m_entry->text().toLatin1();
		Scene_EntitySetKeyValue_Selected_Undoable( m_key.c_str(), value.constData() );
	}
	typedef MemberCaller<StringAttribute, &StringAttribute::apply> ApplyCaller;

	void update() override {
		m_entry->setText( SelectedEntity_getValueForKey( m_key.c_str() ) );
	}
	typedef MemberCaller<StringAttribute, &StringAttribute::update> UpdateCaller;
};

class ShaderAttribute : public StringAttribute
{
public:
	ShaderAttribute( const char* key ) : StringAttribute( key ){
		GlobalShaderEntryCompletion::instance().connect( StringAttribute::getEntry() );
	}
};

class TextureAttribute : public StringAttribute
{
public:
	TextureAttribute( const char* key ) : StringAttribute( key ){
		if( string_empty( GlobalRadiant().getGameDescriptionKeyValue( "show_wads" ) ) )
			GlobalAllShadersEntryCompletion::instance().connect( StringAttribute::getEntry() );	// with textures/
		else
			GlobalTextureEntryCompletion::instance().connect( StringAttribute::getEntry() );	// w/o
	}
};


class ColorAttribute final : public EntityAttribute
{
	const CopiedString m_key;
	NonModalEntry *m_entry;
public:
	ColorAttribute( const char* key ) :
		m_key( key ),
		m_entry( new NonModalEntry( ApplyCaller( *this ), UpdateCaller( *this ) ) ){
		auto button = m_entry->addAction( QApplication::style()->standardIcon( QStyle::SP_ArrowRight ), QLineEdit::ActionPosition::TrailingPosition );
		QObject::connect( button, &QAction::triggered, [this](){ browse(); } );
	}
	void release() override {
		delete this;
	}
	QWidget* getWidget() const override {
		return m_entry;
	}
	void apply(){
		Scene_EntitySetKeyValue_Selected_Undoable( m_key.c_str(), m_entry->text().toLatin1().constData() );
	}
	typedef MemberCaller<ColorAttribute, &ColorAttribute::apply> ApplyCaller;
	void update() override {
		m_entry->setText( SelectedEntity_getValueForKey( m_key.c_str() ) );
	}
	typedef MemberCaller<ColorAttribute, &ColorAttribute::update> UpdateCaller;
	void browse(){
		Vector3 color( 1, 1, 1 );
		string_parse_vector3( m_entry->text().toLatin1().constData(), color );
		if( color_dialog( m_entry->window(), color ) ){
			char buffer[64];
			sprintf( buffer, "%g %g %g", color[0], color[1], color[2] );
			m_entry->setText( buffer );
			apply();
		}
	}
};


class ModelAttribute final : public EntityAttribute
{
	const CopiedString m_key;
	NonModalEntry *m_entry;
public:
	ModelAttribute( const char* key ) :
		m_key( key ),
		m_entry( new NonModalEntry( ApplyCaller( *this ), UpdateCaller( *this ) ) ){
		auto button = m_entry->addAction( QApplication::style()->standardIcon( QStyle::SP_DialogOpenButton ), QLineEdit::ActionPosition::TrailingPosition );
		QObject::connect( button, &QAction::triggered, [this](){ browse(); } );
	}
	void release() override {
		delete this;
	}
	QWidget* getWidget() const override {
		return m_entry;
	}
	void apply(){
		Scene_EntitySetKeyValue_Selected_Undoable( m_key.c_str(), m_entry->text().toLatin1().constData() );
	}
	typedef MemberCaller<ModelAttribute, &ModelAttribute::apply> ApplyCaller;
	void update() override {
		m_entry->setText( SelectedEntity_getValueForKey( m_key.c_str() ) );
	}
	typedef MemberCaller<ModelAttribute, &ModelAttribute::update> UpdateCaller;
	void browse(){
		const char *filename = misc_model_dialog( m_entry->window(), m_entry->text().toLatin1().constData() );

		if ( filename != 0 ) {
			m_entry->setText( filename );
			apply();
		}
	}
};

const char* browse_sound( QWidget* parent, const char* filepath ){
	StringOutputStream buffer( 256 );

	if( !string_empty( filepath ) ){
		const char* root = GlobalFileSystem().findFile( filepath );
		if( !string_empty( root ) && file_is_directory( root ) )
			buffer << root << filepath;
	}
	if( buffer.empty() ){
		buffer << g_qeglobals.m_userGamePath << "sound/";

		if ( !file_readable( buffer ) ) {
			// just go to fsmain
			buffer( g_qeglobals.m_userGamePath );
		}
	}

	const char* filename = file_dialog( parent, true, "Open Sound File", buffer, "sound" );
	if ( filename != 0 ) {
		const char* relative = path_make_relative( filename, GlobalFileSystem().findRoot( filename ) );
		if ( relative == filename ) {
			globalWarningStream() << "WARNING: could not extract the relative path, using full path instead\n";
		}
		return relative;
	}
	return filename;
}

class SoundAttribute final : public EntityAttribute
{
	const CopiedString m_key;
	NonModalEntry *m_entry;
public:
	SoundAttribute( const char* key ) :
		m_key( key ),
		m_entry( new NonModalEntry( ApplyCaller( *this ), UpdateCaller( *this ) ) ){
		auto button = m_entry->addAction( QApplication::style()->standardIcon( QStyle::SP_MediaVolume ), QLineEdit::ActionPosition::TrailingPosition );
		QObject::connect( button, &QAction::triggered, [this](){ browse(); } );
	}
	void release() override {
		delete this;
	}
	QWidget* getWidget() const override {
		return m_entry;
	}
	void apply(){
		Scene_EntitySetKeyValue_Selected_Undoable( m_key.c_str(), m_entry->text().toLatin1().constData() );
	}
	typedef MemberCaller<SoundAttribute, &SoundAttribute::apply> ApplyCaller;
	void update() override {
		m_entry->setText( SelectedEntity_getValueForKey( m_key.c_str() ) );
	}
	typedef MemberCaller<SoundAttribute, &SoundAttribute::update> UpdateCaller;
	void browse(){
		const char *filename = browse_sound( m_entry->window(), m_entry->text().toLatin1().constData() );

		if ( filename != 0 ) {
			m_entry->setText( filename );
			apply();
		}
	}
};


inline double angle_normalised( double angle ){
	return float_mod( angle, 360.0 );
}
#include "camwindow.h"
class CamAnglesButton
{
	typedef Callback1<const Vector3&> ApplyCallback;
	ApplyCallback m_apply;
public:
	QPushButton* m_button;
	CamAnglesButton( const ApplyCallback& apply ) : m_apply( apply ), m_button( new QPushButton( "<-cam" ) ){
		QObject::connect( m_button, &QAbstractButton::clicked, [this](){
			Vector3 angles( Camera_getAngles( *g_pParentWnd->GetCamWnd() ) );
			if( !string_equal( GlobalRadiant().getRequiredGameDescriptionKeyValue( "entities" ), "quake" ) ) /* stupid quake bug */
				angles[0] = -angles[0];
			m_apply( angles );
		} );
	}
};

inline QWidget *new_container_widget(){
	QWidget *w = new QWidget;
	auto l = new QHBoxLayout( w );
	l->setContentsMargins( 0, 0, 0, 0 );
	return w;
}

class AngleAttribute final : public EntityAttribute
{
	const CopiedString m_key;
	NonModalEntry* m_entry;
	CamAnglesButton m_butt;
	QWidget *m_hbox;
public:
	AngleAttribute( const char* key ) :
		m_key( key ),
		m_entry( new NonModalEntry( ApplyCaller( *this ), UpdateCaller( *this ) ) ),
		m_butt( ApplyVecCaller( *this ) ),
		m_hbox( new_container_widget() ){
		m_hbox->layout()->addWidget( m_entry );
		m_hbox->layout()->addWidget( m_butt.m_button );
	}
	void release() override {
		delete this;
	}
	QWidget* getWidget() const override {
		return m_hbox;
	}
	void apply(){
		const auto angle = StringStream<32>( angle_normalised( entry_get_float( m_entry ) ) );
		Scene_EntitySetKeyValue_Selected_Undoable( m_key.c_str(), angle );
	}
	typedef MemberCaller<AngleAttribute, &AngleAttribute::apply> ApplyCaller;

	void update() override {
		const char* value = SelectedEntity_getValueForKey( m_key.c_str() );
		if ( !string_empty( value ) ) {
			const auto angle = StringStream<32>( angle_normalised( atof( value ) ) );
			m_entry->setText( angle.c_str() );
		}
		else
		{
			m_entry->setText( "0" );
		}
	}
	typedef MemberCaller<AngleAttribute, &AngleAttribute::update> UpdateCaller;

	void apply( const Vector3& angles ){
		entry_set_float( m_entry, angles[1] );
		apply();
	}
	typedef MemberCaller1<AngleAttribute, const Vector3&, &AngleAttribute::apply> ApplyVecCaller;
};

class DirectionAttribute final : public EntityAttribute
{
	const CopiedString m_key;
	NonModalEntry* m_entry;
	RadioHBox m_radio;
	CamAnglesButton m_butt;
	QWidget* m_hbox;
	static constexpr const char *const buttons[] = { "up", "down", "yaw" };
public:
	DirectionAttribute( const char* key ) :
		m_key( key ),
		m_entry( new NonModalEntry( ApplyCaller( *this ), UpdateCaller( *this ) ) ),
		m_radio( RadioHBox_new( StringArrayRange( buttons ) ) ),
		m_butt( ApplyVecCaller( *this ) ),
		m_hbox( new_container_widget() ){
		static_cast<QHBoxLayout*>( m_hbox->layout() )->addLayout( m_radio.m_hbox );
		m_hbox->layout()->addWidget( m_entry );
		m_hbox->layout()->addWidget( m_butt.m_button );
		QObject::connect( m_radio.m_radio, &QButtonGroup::idClicked, ApplyRadioCaller( *this ) );
	}
	void release() override {
		delete this;
	}
	QWidget* getWidget() const override {
		return m_hbox;
	}
	void apply(){
		const auto angle = StringStream<32>( angle_normalised( entry_get_float( m_entry ) ) );
		Scene_EntitySetKeyValue_Selected_Undoable( m_key.c_str(), angle );
	}
	typedef MemberCaller<DirectionAttribute, &DirectionAttribute::apply> ApplyCaller;

	void update() override {
		const char* value = SelectedEntity_getValueForKey( m_key.c_str() );
		if ( !string_empty( value ) ) {
			const float f = atof( value );
			if ( f == -1 ) {
				m_entry->setEnabled( false );
				m_radio.m_radio->button( 0 )->setChecked( true );
				m_entry->clear();
			}
			else if ( f == -2 ) {
				m_entry->setEnabled( false );
				m_radio.m_radio->button( 1 )->setChecked( true );
				m_entry->clear();
			}
			else
			{
				m_entry->setEnabled( true );
				m_radio.m_radio->button( 2 )->setChecked( true );
				const auto angle = StringStream<32>( angle_normalised( f ) );
				m_entry->setText( angle.c_str() );
			}
		}
		else
		{
			m_radio.m_radio->button( 2 )->setChecked( true );
			m_entry->setText( "0" );
		}
	}
	typedef MemberCaller<DirectionAttribute, &DirectionAttribute::update> UpdateCaller;

	void applyRadio( int id ){
		if ( id == 0 ) {
			Scene_EntitySetKeyValue_Selected_Undoable( m_key.c_str(), "-1" );
		}
		else if ( id == 1 ) {
			Scene_EntitySetKeyValue_Selected_Undoable( m_key.c_str(), "-2" );
		}
		else if ( id == 2 ) {
			apply();
		}
	}
	typedef MemberCaller1<DirectionAttribute, int, &DirectionAttribute::applyRadio> ApplyRadioCaller;

	void apply( const Vector3& angles ){
		entry_set_float( m_entry, angles[1] );
		apply();
	}
	typedef MemberCaller1<DirectionAttribute, const Vector3&, &DirectionAttribute::apply> ApplyVecCaller;
};


class AnglesEntry
{
public:
	QLineEdit* m_roll;
	QLineEdit* m_pitch;
	QLineEdit* m_yaw;
	AnglesEntry() : m_roll( 0 ), m_pitch( 0 ), m_yaw( 0 ){
	}
};

class AnglesAttribute final : public EntityAttribute
{
	const CopiedString m_key;
	AnglesEntry m_angles;
	CamAnglesButton m_butt;
	QWidget* m_hbox;
public:
	AnglesAttribute( const char* key ) :
		m_key( key ),
		m_butt( ApplyVecCaller( *this ) ),
		m_hbox( new_container_widget() ){
		m_hbox->layout()->addWidget( m_angles.m_pitch = new NonModalEntry( ApplyCaller( *this ), UpdateCaller( *this ) ) );
		m_hbox->layout()->addWidget( m_angles.m_yaw = new NonModalEntry( ApplyCaller( *this ), UpdateCaller( *this ) ) );
		m_hbox->layout()->addWidget( m_angles.m_roll = new NonModalEntry( ApplyCaller( *this ), UpdateCaller( *this ) ) );
		m_hbox->layout()->addWidget( m_butt.m_button );
	}
	void release() override {
		delete this;
	}
	QWidget* getWidget() const override {
		return m_hbox;
	}
	void apply(){
		const auto angles = StringStream<64>( angle_normalised( entry_get_float( m_angles.m_pitch ) ),
		                                 ' ', angle_normalised( entry_get_float( m_angles.m_yaw ) ),
		                                 ' ', angle_normalised( entry_get_float( m_angles.m_roll ) ) );
		Scene_EntitySetKeyValue_Selected_Undoable( m_key.c_str(), angles );
	}
	typedef MemberCaller<AnglesAttribute, &AnglesAttribute::apply> ApplyCaller;

	void update() override {
		const char* value = SelectedEntity_getValueForKey( m_key.c_str() );
		if ( !string_empty( value ) ) {
			DoubleVector3 pitch_yaw_roll;
			if ( !string_parse_vector3( value, pitch_yaw_roll ) ) {
				pitch_yaw_roll = DoubleVector3( 0, 0, 0 );
			}
			StringOutputStream angle( 32 );

			angle( angle_normalised( pitch_yaw_roll.x() ) );
			m_angles.m_pitch->setText( angle.c_str() );

			angle( angle_normalised( pitch_yaw_roll.y() ) );
			m_angles.m_yaw->setText( angle.c_str() );

			angle( angle_normalised( pitch_yaw_roll.z() ) );
			m_angles.m_roll->setText( angle.c_str() );
		}
		else
		{
			m_angles.m_pitch->setText( "0" );
			m_angles.m_yaw->setText( "0" );
			m_angles.m_roll->setText( "0" );
		}
	}
	typedef MemberCaller<AnglesAttribute, &AnglesAttribute::update> UpdateCaller;

	void apply( const Vector3& angles ){
		entry_set_float( m_angles.m_pitch, angles[0] );
		entry_set_float( m_angles.m_yaw, angles[1] );
		entry_set_float( m_angles.m_roll, 0 );
		apply();
	}
	typedef MemberCaller1<AnglesAttribute, const Vector3&, &AnglesAttribute::apply> ApplyVecCaller;
};

class Vector3Entry
{
public:
	QLineEdit* m_x;
	QLineEdit* m_y;
	QLineEdit* m_z;
	Vector3Entry() : m_x( 0 ), m_y( 0 ), m_z( 0 ){
	}
};

class Vector3Attribute final : public EntityAttribute
{
	const CopiedString m_key;
	Vector3Entry m_vector3;
	QWidget* m_hbox;
public:
	Vector3Attribute( const char* key ) :
		m_key( key ),
		m_hbox( new_container_widget() ){
		m_hbox->layout()->addWidget( m_vector3.m_x = new NonModalEntry( ApplyCaller( *this ), UpdateCaller( *this ) ) );
		m_hbox->layout()->addWidget( m_vector3.m_y = new NonModalEntry( ApplyCaller( *this ), UpdateCaller( *this ) ) );
		m_hbox->layout()->addWidget( m_vector3.m_z = new NonModalEntry( ApplyCaller( *this ), UpdateCaller( *this ) ) );
	}
	void release() override {
		delete this;
	}
	QWidget* getWidget() const override {
		return m_hbox;
	}
	void apply(){
		const auto vector3 = StringStream<64>( entry_get_float( m_vector3.m_x ),
		                                  ' ', entry_get_float( m_vector3.m_y ),
		                                  ' ', entry_get_float( m_vector3.m_z ) );
		Scene_EntitySetKeyValue_Selected_Undoable( m_key.c_str(), vector3 );
	}
	typedef MemberCaller<Vector3Attribute, &Vector3Attribute::apply> ApplyCaller;

	void update() override {
		const char* value = SelectedEntity_getValueForKey( m_key.c_str() );
		if ( !string_empty( value ) ) {
			DoubleVector3 x_y_z;
			if ( !string_parse_vector3( value, x_y_z ) ) {
				x_y_z = DoubleVector3( 0, 0, 0 );
			}
			StringOutputStream buffer( 32 );

			buffer( x_y_z.x() );
			m_vector3.m_x->setText( buffer.c_str() );

			buffer( x_y_z.y() );
			m_vector3.m_y->setText( buffer.c_str() );

			buffer( x_y_z.z() );
			m_vector3.m_z->setText( buffer.c_str() );
		}
		else
		{
			m_vector3.m_x->setText( "0" );
			m_vector3.m_y->setText( "0" );
			m_vector3.m_z->setText( "0" );
		}
	}
	typedef MemberCaller<Vector3Attribute, &Vector3Attribute::update> UpdateCaller;
};

class ListAttribute final : public EntityAttribute
{
	const CopiedString m_key;
	QComboBox* m_combo;
	const ListAttributeType& m_type;
public:
	ListAttribute( const char* key, const ListAttributeType& type ) :
		m_key( key ),
		m_combo( new ComboBox ),
		m_type( type ){
		for ( const auto&[ name, value ] : type )
		{
			m_combo->addItem( name.c_str() );
		}
		QObject::connect( m_combo, QOverload<int>::of( &QComboBox::activated ), ApplyCaller( *this ) );
	}
	void release() override {
		delete this;
	}
	QWidget* getWidget() const override {
		return m_combo;
	}
	void apply(){
		// looks safe to assume that user actions wont make m_combo->currentIndex() -1
		Scene_EntitySetKeyValue_Selected_Undoable( m_key.c_str(), m_type[m_combo->currentIndex()].second.c_str() );
	}
	typedef MemberCaller<ListAttribute, &ListAttribute::apply> ApplyCaller;

	void update() override {
		const char* value = SelectedEntity_getValueForKey( m_key.c_str() );
		ListAttributeType::const_iterator i = m_type.findValue( value );
		if ( i != m_type.end() ) {
			m_combo->setCurrentIndex( static_cast<int>( std::distance( m_type.begin(), i ) ) );
		}
		else
		{
			m_combo->setCurrentIndex( 0 );
		}
	}
	typedef MemberCaller<ListAttribute, &ListAttribute::update> UpdateCaller;
};


namespace
{
bool g_entityInspector_windowConstructed = false;

QTreeWidget* g_entityClassList;
QPlainTextEdit* g_entityClassComment;

QCheckBox* g_entitySpawnflagsCheck[MAX_FLAGS];

QLineEdit* g_entityKeyEntry;
QLineEdit* g_entityValueEntry;

QToolButton* g_focusToggleButton;

QTreeWidget* g_entprops_store;
const EntityClass* g_current_flags = 0;
const EntityClass* g_current_comment = 0;
const EntityClass* g_current_attributes = 0;

// the number of active spawnflags
int g_spawnflag_count;
// table: index, match spawnflag item to the spawnflag index (i.e. which bit)
int spawn_table[MAX_FLAGS];
// we change the layout depending on how many spawn flags we need to display
// the table is a 4x4 in which we need to put the comment box g_entityClassComment and the spawn flags..
QGridLayout* g_spawnflagsTable;

QGridLayout* g_attributeBox = nullptr;
typedef std::vector<EntityAttribute*> EntityAttributes;
EntityAttributes g_entityAttributes;
}

void GlobalEntityAttributes_clear(){
	for ( EntityAttribute* attr : g_entityAttributes )
	{
		attr->release();
	}
	g_entityAttributes.clear();
}

class GetKeyValueVisitor : public Entity::Visitor
{
	KeyValues& m_keyvalues;
public:
	GetKeyValueVisitor( KeyValues& keyvalues )
		: m_keyvalues( keyvalues ){
	}

	void visit( const char* key, const char* value ){
		m_keyvalues.insert( KeyValues::value_type( CopiedString( key ), CopiedString( value ) ) );
	}

};

void Entity_GetKeyValues( const Entity& entity, KeyValues& keyvalues, KeyValues& defaultValues ){
	GetKeyValueVisitor visitor( keyvalues );

	entity.forEachKeyValue( visitor );

	const EntityClassAttributes& attributes = entity.getEntityClass().m_attributes;

	for ( EntityClassAttributes::const_iterator i = attributes.begin(); i != attributes.end(); ++i )
	{
		defaultValues.insert( KeyValues::value_type( ( *i ).first, ( *i ).second.m_value ) );
	}
}

void Entity_GetKeyValues_Selected( KeyValues& keyvalues, KeyValues& defaultValues ){
	class EntityGetKeyValues : public SelectionSystem::Visitor
	{
		KeyValues& m_keyvalues;
		KeyValues& m_defaultValues;
		mutable std::set<Entity*> m_visited;
	public:
		EntityGetKeyValues( KeyValues& keyvalues, KeyValues& defaultValues )
			: m_keyvalues( keyvalues ), m_defaultValues( defaultValues ){
		}
		void visit( scene::Instance& instance ) const {
			Entity* entity = Node_getEntity( instance.path().top() );
			if ( entity == 0 && instance.path().size() != 1 ) {
				entity = Node_getEntity( instance.path().parent() );
			}
			if ( entity != 0 && m_visited.insert( entity ).second ) {
				Entity_GetKeyValues( *entity, m_keyvalues, m_defaultValues );
			}
		}
	} visitor( keyvalues, defaultValues );
	GlobalSelectionSystem().foreachSelected( visitor );
}

const char* keyvalues_valueforkey( KeyValues& keyvalues, const char* key ){
	KeyValues::iterator i = keyvalues.find( CopiedString( key ) );
	if ( i != keyvalues.end() ) {
		return ( *i ).second.c_str();
	}
	return "";
}

// required to store EntityClass* in QVariant
Q_DECLARE_METATYPE( EntityClass* )
class EntityClassListStoreAppend : public EntityClassVisitor
{
	QTreeWidget* tree;
public:
	EntityClassListStoreAppend( QTreeWidget* tree_ ) : tree( tree_ ){
	}
	void visit( EntityClass* e ){
		auto item = new QTreeWidgetItem( tree );
		item->setData( 0, Qt::ItemDataRole::DisplayRole, e->name() );
		item->setData( 0, Qt::ItemDataRole::UserRole, QVariant::fromValue( e ) );
	}
};

void EntityClassList_fill(){
	EntityClassListStoreAppend append( g_entityClassList );
	GlobalEntityClassManager().forEach( append );
}

void EntityClassList_clear(){
	g_entityClassList->clear();
}

void SetComment( EntityClass* eclass ){
	if ( eclass == g_current_comment ) {
		return;
	}

	g_current_comment = eclass;

	if( eclass == nullptr ){
		g_entityClassComment->clear();
		return;
	}

	g_entityClassComment->setPlainText( eclass->comments() );

	{	// Catch patterns like "\nstuff :" used to describe keys and spawnflags, and make them bold for readability.
		QTextCharFormat format;
		format.setFontWeight( QFont::Weight::Bold );

		QTextDocument *document = g_entityClassComment->document();
		const QRegularExpression rx( "^\\s*\\w+(?=\\s*:)", QRegularExpression::PatternOption::MultilineOption );
		for( QTextCursor cursor( document ); cursor = document->find( rx, cursor ), !cursor.isNull(); )
			cursor.mergeCharFormat( format );
	}
}

void EntityAttribute_setTooltip( QWidget* widget, const char* name, const char* description ){
	StringOutputStream stream( 256 );
	if( string_not_empty( name ) )
		stream << "<b>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;" << name << "</b>&nbsp;&nbsp;&nbsp;&nbsp;";
	if( string_not_empty( description ) ){
		stream << "<br>" << description;
	}
	if( !stream.empty() )
		widget->setToolTip( stream.c_str() );
}

void SpawnFlags_setEntityClass( EntityClass* eclass ){
	if ( eclass == g_current_flags ) {
		return;
	}

	g_current_flags = eclass;

	g_spawnflag_count = 0;

	// do a first pass to count the spawn flags, don't touch the widgets, we don't know in what state they are
	for ( int i = 0; i < MAX_FLAGS; i++ )
	{
		if ( !string_empty( eclass->flagnames[i] ) ) {
			spawn_table[g_spawnflag_count++] = i;
		}
		// hide all boxes
		g_entitySpawnflagsCheck[i]->hide();
	}

	for ( int i = 0; i < g_spawnflag_count; ++i )
	{
		const auto str = StringStream<16>( LowerCase( eclass->flagnames[spawn_table[i]] ) );

		QCheckBox *check = g_entitySpawnflagsCheck[i];
		check->setText( str.c_str() );
		check->show();

		if( const EntityClassAttribute* attribute = eclass->flagAttributes[spawn_table[i]] ){
			EntityAttribute_setTooltip( check, attribute->m_name.c_str(), attribute->m_description.c_str() );
		}
	}
}

void EntityClassList_selectEntityClass( EntityClass* eclass ){
	const auto list = g_entityClassList->findItems( eclass->name(), Qt::MatchFlag::MatchFixedString );
	g_entityClassList->setCurrentItem( !list.isEmpty()
	                                   ? list.first()
	                                   : nullptr );
	// g_entityClassComment is only updated via g_entityClassList selection change
	// using special nullprt case to also update it on selection of unknown entity added during runtime
	// hence this->EntityClassList_selection_changed()->SetComment() must handle nullptr
}

void EntityInspector_appendAttribute( const EntityClassAttributePair& attributePair, EntityAttribute& attribute ){
	const char* keyname = attributePair.first.c_str(); //EntityClassAttributePair_getName( attributePair );
	auto label = new QLabel( keyname );
	EntityAttribute_setTooltip( label, attributePair.second.m_name.c_str(), attributePair.second.m_description.c_str() );
	DialogGrid_packRow( g_attributeBox, attribute.getWidget(), label );
}


template<typename Attribute>
class StatelessAttributeCreator
{
public:
	static EntityAttribute* create( const char* name ){
		return new Attribute( name );
	}
};

class EntityAttributeFactory
{
	typedef EntityAttribute* ( *CreateFunc )( const char* name );
	typedef std::map<const char*, CreateFunc, RawStringLess> Creators;
	Creators m_creators;
public:
	EntityAttributeFactory(){
		m_creators.insert( Creators::value_type( "string", &StatelessAttributeCreator<StringAttribute>::create ) );
		m_creators.insert( Creators::value_type( "array", &StatelessAttributeCreator<StringAttribute>::create ) );
		m_creators.insert( Creators::value_type( "integer", &StatelessAttributeCreator<StringAttribute>::create ) );
		m_creators.insert( Creators::value_type( "boolean", &StatelessAttributeCreator<BooleanAttribute>::create ) );
		m_creators.insert( Creators::value_type( "real", &StatelessAttributeCreator<StringAttribute>::create ) );
		m_creators.insert( Creators::value_type( "angle", &StatelessAttributeCreator<AngleAttribute>::create ) );
		m_creators.insert( Creators::value_type( "direction", &StatelessAttributeCreator<DirectionAttribute>::create ) );
		m_creators.insert( Creators::value_type( "vector3", &StatelessAttributeCreator<Vector3Attribute>::create ) );
		m_creators.insert( Creators::value_type( "real3", &StatelessAttributeCreator<Vector3Attribute>::create ) );
		m_creators.insert( Creators::value_type( "angles", &StatelessAttributeCreator<AnglesAttribute>::create ) );
		m_creators.insert( Creators::value_type( "color", &StatelessAttributeCreator<ColorAttribute>::create ) );
		m_creators.insert( Creators::value_type( "target", &StatelessAttributeCreator<StringAttribute>::create ) );
		m_creators.insert( Creators::value_type( "targetname", &StatelessAttributeCreator<StringAttribute>::create ) );
		m_creators.insert( Creators::value_type( "sound", &StatelessAttributeCreator<SoundAttribute>::create ) );
		m_creators.insert( Creators::value_type( "shader", &StatelessAttributeCreator<ShaderAttribute>::create ) );
		m_creators.insert( Creators::value_type( "texture", &StatelessAttributeCreator<TextureAttribute>::create ) );
		m_creators.insert( Creators::value_type( "model", &StatelessAttributeCreator<ModelAttribute>::create ) );
		m_creators.insert( Creators::value_type( "skin", &StatelessAttributeCreator<StringAttribute>::create ) );
	}
	EntityAttribute* create( const char* type, const char* name ){
		Creators::iterator i = m_creators.find( type );
		if ( i != m_creators.end() ) {
			return ( *i ).second( name );
		}
		const ListAttributeType* listType = GlobalEntityClassManager().findListType( type );
		if ( listType != 0 ) {
			return new ListAttribute( name, *listType );
		}
		return 0;
	}
};

typedef Static<EntityAttributeFactory> GlobalEntityAttributeFactory;

void EntityInspector_setEntityClass( EntityClass *eclass ){
	EntityClassList_selectEntityClass( eclass );
	SpawnFlags_setEntityClass( eclass );

	if ( eclass != g_current_attributes ) {
		g_current_attributes = eclass;

		while( QLayoutItem *item = g_attributeBox->takeAt( 0 ) ){
			delete item->widget();
			delete item;
		}
		g_attributeBox->update(); // trigger scrollbar update
		GlobalEntityAttributes_clear();

		for ( const EntityClassAttributePair &pair : eclass->m_attributes )
		{
			EntityAttribute* attribute = GlobalEntityAttributeFactory::instance().create( pair.second.m_type.c_str(), pair.first.c_str() );
			if ( attribute != 0 ) {
				g_entityAttributes.push_back( attribute );
				EntityInspector_appendAttribute( pair, *g_entityAttributes.back() );
			}
		}
	}
}

void EntityInspector_updateSpawnflags(){
	{
		const int f = atoi( SelectedEntity_getValueForKey( "spawnflags" ) );
		for ( int i = 0; i < g_spawnflag_count; ++i )
		{
			const bool v = !!( f & ( 1 << spawn_table[i] ) );

			g_entitySpawnflagsCheck[i]->setChecked( v );
		}
	}
}

void EntityInspector_applySpawnflags(){
	int f = 0;

	for ( int i = 0; i < g_spawnflag_count; ++i )
	{
		const int v = g_entitySpawnflagsCheck[i]->isChecked();
		f |= v << spawn_table[i];
	}

	char value[32] = {};
	if( f != 0 )
		sprintf( value, "%i", f );

	{
		const auto command = StringStream<64>( "entitySetSpawnflags -flags ", f );
		UndoableCommand undo( command );

		Scene_EntitySetKeyValue_Selected( "spawnflags", value );
	}
}


void EntityInspector_updateKeyValues(){
	g_selectedKeyValues.clear();
	g_selectedDefaultKeyValues.clear();
	Entity_GetKeyValues_Selected( g_selectedKeyValues, g_selectedDefaultKeyValues );

	EntityInspector_setEntityClass( GlobalEntityClassManager().findOrInsert( keyvalues_valueforkey( g_selectedKeyValues, "classname" ), false ) );

	EntityInspector_updateSpawnflags();

	g_entprops_store->clear();
	// Walk through list and add pairs
	for ( const auto&[ key, value ] : g_selectedKeyValues )
	{
		g_entprops_store->addTopLevelItem( new QTreeWidgetItem( { key.c_str(), value.c_str() } ) );
	}

	for ( EntityAttribute *attr : g_entityAttributes )
	{
		attr->update();
	}
}

class EntityInspectorDraw
{
	IdleDraw m_idleDraw;
public:
	EntityInspectorDraw() : m_idleDraw( FreeCaller<EntityInspector_updateKeyValues>( ) ){
	}
	void queueDraw(){
		m_idleDraw.queueDraw();
	}
};

EntityInspectorDraw g_EntityInspectorDraw;


void EntityInspector_keyValueChanged(){
	g_EntityInspectorDraw.queueDraw();
}
void EntityInspector_selectionChanged( const Selectable& ){
	EntityInspector_keyValueChanged();
}

void EntityInspector_applyKeyValue(){
	// Get current selection text
	const auto key = g_entityKeyEntry->text().toLatin1();
	const auto value = g_entityValueEntry->text().toLatin1();

	// TTimo: if you change the classname to worldspawn you won't merge back in the structural brushes but create a parasite entity
//	if ( !strcmp( key.c_str(), "classname" ) && !strcmp( value.c_str(), "worldspawn" ) ) {
//		qt_MessageBox( g_entityKeyEntry->window(), "Cannot change \"classname\" key back to worldspawn." );
//		return;
//	}

	// RR2DO2: we don't want spaces and special symbols in entity keys
	if ( std::any_of( key.cbegin(), key.cend(), []( const char c ){ return strchr( " \n\r\t\v\"", c ) != nullptr; } ) ) {
		qt_MessageBox( g_entityKeyEntry->window(), "No spaces, newlines, tabs, quotes are allowed in entity key names." );
		return;
	}
	if ( std::any_of( value.cbegin(), value.cend(), []( const char c ){ return strchr( "\n\r\"", c ) != nullptr; } ) ) {
		qt_MessageBox( g_entityKeyEntry->window(), "No newlines & quotes are allowed in entity key values." );
		return;
	}
	// avoid empty key name; empty value is okay: deletes key
	if( key.isEmpty() )
		return;

	if ( string_equal( key.constData(), "classname" ) ) {
		Scene_EntitySetClassname_Selected( value.constData() );
	}
	else
	{
		Scene_EntitySetKeyValue_Selected_Undoable( key.constData(), value.constData() );
	}
}

void EntityInspector_clearKeyValue(){
	// Get current selection text
	if( const auto item = g_entprops_store->currentItem() ){
		const auto key = item->text( 0 ).toLatin1();

		if ( !string_equal( key.constData(), "classname" ) ) {
			const auto command = StringStream<64>( "entityDeleteKey -key ", key.constData() );
			UndoableCommand undo( command );
			Scene_EntitySetKeyValue_Selected( key.constData(), "" );
		}
	}
}

class : public QObject
{
protected:
	bool eventFilter( QObject *obj, QEvent *event ) override {
		if( event->type() == QEvent::ShortcutOverride ) {
			QKeyEvent *keyEvent = static_cast<QKeyEvent *>( event );
			if( keyEvent->key() == Qt::Key_Delete ){
				EntityInspector_clearKeyValue();
				event->accept();
			}
		}
		return QObject::eventFilter( obj, event ); // standard event processing
	}
}
g_EntityProperties_keypress;

void EntityInspector_clearAllKeyValues(){
	UndoableCommand undo( "entityClear" );

	// remove all keys except classname and origin
	for ( const auto&[ key, value ] : g_selectedKeyValues )
	{
		if ( !string_equal( key.c_str(), "classname" ) && !string_equal( key.c_str(), "origin" ) ) {
			Scene_EntitySetKeyValue_Selected( key.c_str(), "" );
		}
	}
}

// =============================================================================
// callbacks

static void EntityClassList_selection_changed( QTreeWidgetItem *current, QTreeWidgetItem *previous ){
	SetComment( current != nullptr
	            ? current->data( 0, Qt::ItemDataRole::UserRole ).value<EntityClass*>()
	            : nullptr );
}

static void EntityProperties_selection_changed( QTreeWidgetItem *item, int column ){
	if( item != nullptr ){
		g_entityKeyEntry->setText( item->text( 0 ) );
		g_entityValueEntry->setText( item->text( 1 ) );
	}
}


class : public QObject
{
protected:
	bool eventFilter( QObject *obj, QEvent *event ) override {
		if( event->type() == QEvent::ShortcutOverride ) {
			QKeyEvent *keyEvent = static_cast<QKeyEvent *>( event );
			if( keyEvent->key() == Qt::Key_Return
			 || keyEvent->key() == Qt::Key_Enter
			 || keyEvent->key() == Qt::Key_Tab
			 || keyEvent->key() == Qt::Key_Up
			 || keyEvent->key() == Qt::Key_Down
			 || keyEvent->key() == Qt::Key_PageUp
			 || keyEvent->key() == Qt::Key_PageDown ){
				event->accept();
			}
		}
		// clear focus widget while showing to keep global shortcuts working
		else if( event->type() == QEvent::Show ) {
			QTimer::singleShot( 0, [obj](){
				if( static_cast<QWidget*>( obj )->focusWidget() != nullptr )
					static_cast<QWidget*>( obj )->focusWidget()->clearFocus();
			} );
		}
		return QObject::eventFilter( obj, event ); // standard event processing
	}
}
g_pressedKeysFilter;


void EntityInspector_destroyWindow(){
	g_entityInspector_windowConstructed = false;
	GlobalEntityAttributes_clear();
}

QWidget* EntityInspector_constructWindow( QWidget* toplevel ){
	auto splitter = new QSplitter( Qt::Vertical );

	QObject::connect( splitter, &QObject::destroyed, EntityInspector_destroyWindow );
	splitter->installEventFilter( &g_pressedKeysFilter );

	{
		// class list
		auto tree = g_entityClassList = new QTreeWidget;
		tree->setColumnCount( 1 );
		tree->setSortingEnabled( true );
		tree->sortByColumn( 0, Qt::SortOrder::AscendingOrder );
		tree->setUniformRowHeights( true ); // optimization
		tree->setHorizontalScrollBarPolicy( Qt::ScrollBarPolicy::ScrollBarAlwaysOff );
		tree->setSizeAdjustPolicy( QAbstractScrollArea::SizeAdjustPolicy::AdjustToContents ); // scroll area will inherit column size
		tree->header()->setStretchLastSection( false ); // non greedy column sizing
		tree->header()->setSectionResizeMode( QHeaderView::ResizeMode::ResizeToContents ); // no text elision
		tree->setHeaderHidden( true );
		tree->setRootIsDecorated( false );
		tree->setEditTriggers( QAbstractItemView::EditTrigger::NoEditTriggers );
		tree->setAutoScroll( true );

		QObject::connect( tree, &QTreeWidget::itemActivated, []( QTreeWidgetItem *item, int column ){
			Scene_EntitySetClassname_Selected( item->text( 0 ).toLatin1().constData() );
		} );
		QObject::connect( tree, &QTreeWidget::currentItemChanged, EntityClassList_selection_changed );

		splitter->addWidget( tree );
	}
	{
		auto text = g_entityClassComment = new QPlainTextEdit;
		text->setReadOnly( true );
		text->setUndoRedoEnabled( false );

		splitter->addWidget( text );
	}
	{
		QWidget *containerWidget = new QWidget; // Adding a QLayout to a QSplitter is not supported, use proxy widget
		splitter->addWidget( containerWidget );
		auto vbox = new QVBoxLayout( containerWidget );
		vbox->setContentsMargins( 0, 0, 0, 0 );
		{
			// Spawnflags (4 colums wide max, or window gets too wide.)
			auto grid = g_spawnflagsTable = new QGridLayout;
			grid->setAlignment( Qt::AlignmentFlag::AlignLeft );
			vbox->addLayout( grid );
			for ( int i = 0; i < MAX_FLAGS; i++ )
			{
				auto check = g_entitySpawnflagsCheck[i] = new QCheckBox;
				grid->addWidget( check, i / 4, i % 4 );
				check->hide();
				QObject::connect( check, &QAbstractButton::clicked, EntityInspector_applySpawnflags );
			}
		}
		{
			// key/value list
			auto tree = g_entprops_store = new QTreeWidget;
			tree->setColumnCount( 2 );
			tree->setUniformRowHeights( true ); // optimization
			tree->setHorizontalScrollBarPolicy( Qt::ScrollBarPolicy::ScrollBarAlwaysOff );
			tree->header()->setSectionResizeMode( 0, QHeaderView::ResizeMode::ResizeToContents ); // no text elision
			tree->setHeaderHidden( true );
			tree->setRootIsDecorated( false );
			tree->setEditTriggers( QAbstractItemView::EditTrigger::NoEditTriggers );

			QObject::connect( tree, &QTreeWidget::itemPressed, EntityProperties_selection_changed );
			tree->installEventFilter( &g_EntityProperties_keypress );

			vbox->addWidget( tree );
		}

		{
			// key/value entry
			auto grid = new QGridLayout;
			grid->setContentsMargins( 4, 0, 4, 0 );
			vbox->addLayout( grid );
			{
				grid->addWidget( new QLabel( "Key" ), 0, 0 );
				grid->addWidget( new QLabel( "Value" ), 1, 0 );
			}
			{
				auto line = g_entityKeyEntry = new LineEdit;
				grid->addWidget( line, 0, 1 );
				QObject::connect( line, &QLineEdit::returnPressed, [](){ g_entityValueEntry->setFocus(); g_entityValueEntry->selectAll(); } );
			}

			{
				auto line = g_entityValueEntry = new LineEdit;
				grid->addWidget( line, 1, 1 );
				QObject::connect( line, &QLineEdit::returnPressed, [](){ EntityInspector_applyKeyValue(); } );
			}
			/* select by key/value buttons */
			{
				auto b = new QToolButton;
				b->setText( "+" );
				b->setToolTip( "Select by key" );
				grid->addWidget( b, 0, 2 );
				QObject::connect( b, &QAbstractButton::clicked, [](){
					Select_EntitiesByKeyValue( g_entityKeyEntry->text().toLatin1().constData(), nullptr );
				} );
			}
			{
				auto b = new QToolButton;
				b->setText( "+" );
				b->setToolTip( "Select by value" );
				grid->addWidget( b, 1, 2 );
				QObject::connect( b, &QAbstractButton::clicked, [](){
					Select_EntitiesByKeyValue( nullptr, g_entityValueEntry->text().toLatin1().constData() );
				} );
			}
			{
				auto b = new QToolButton;
				b->setText( "+" );
				b->setToolTip( "Select by key + value" );
				grid->addWidget( b, 0, 3, 2, 1 );
				QObject::connect( b, &QAbstractButton::clicked, [](){
					Select_EntitiesByKeyValue( g_entityKeyEntry->text().toLatin1().constData(), g_entityValueEntry->text().toLatin1().constData() );
				} );
			}
		}
		{
			auto hbox = new QHBoxLayout;
			hbox->setContentsMargins( 4, 0, 4, 0 );
			vbox->addLayout( hbox );
			{
				auto b = new QPushButton( "Clear All" );
				hbox->addWidget( b );
				QObject::connect( b, &QAbstractButton::clicked, EntityInspector_clearAllKeyValues );
			}
			{
				auto b = new QPushButton( "Delete Key" );
				hbox->addWidget( b );
				QObject::connect( b, &QAbstractButton::clicked, EntityInspector_clearKeyValue );
			}
			{
				auto b = new QToolButton;
				hbox->addWidget( b );
				b->setText( "<" );
				b->setToolTip( "Select targeting entities" );
				QObject::connect( b, &QAbstractButton::clicked, [](){ Select_ConnectedEntities( true, false, g_focusToggleButton->isChecked() ); } );
			}
			{
				auto b = new QToolButton;
				hbox->addWidget( b );
				b->setText( ">" );
				b->setToolTip( "Select targets" );
				QObject::connect( b, &QAbstractButton::clicked, [](){ Select_ConnectedEntities( false, true, g_focusToggleButton->isChecked() ); } );
			}
			{
				auto b = new QToolButton;
				hbox->addWidget( b );
				b->setText( "<->" );
				b->setToolTip( "Select connected entities" );
				QObject::connect( b, &QAbstractButton::clicked, [](){ Select_ConnectedEntities( true, true, g_focusToggleButton->isChecked() ); } );
			}
			{
				auto b = g_focusToggleButton = new QToolButton;
				hbox->addWidget( b );
				b->setText( u8"👀" );
				b->setToolTip( "AutoFocus on Selection" );
				b->setCheckable( true );
				QObject::connect( b, &QAbstractButton::clicked, []( bool checked ){ if( checked ) FocusAllViews(); } );
			}
		}
	}
	{
		auto scroll = new QScrollArea;
		scroll->setHorizontalScrollBarPolicy( Qt::ScrollBarPolicy::ScrollBarAlwaysOff );
		scroll->setWidgetResizable( true );
		splitter->addWidget( scroll );

		QWidget *containerWidget = new QWidget; // Adding a QLayout to a QScrollArea is not supported, use proxy widget
		g_attributeBox = new QGridLayout( containerWidget );
		g_attributeBox->setAlignment( Qt::AlignmentFlag::AlignTop );
		g_attributeBox->setColumnStretch( 0, 111 );
		g_attributeBox->setColumnStretch( 1, 333 );
		scroll->setWidget( containerWidget ); // widget's layout must be set b4 this!
	}

	g_entityInspector_windowConstructed = true;
	EntityClassList_fill();

	typedef FreeCaller1<const Selectable&, EntityInspector_selectionChanged> EntityInspectorSelectionChangedCaller;
	GlobalSelectionSystem().addSelectionChangeCallback( EntityInspectorSelectionChangedCaller() );
	GlobalEntityCreator().setKeyValueChangedFunc( EntityInspector_keyValueChanged );

	g_guiSettings.addSplitter( splitter, "EntityInspector/splitter", { 55, 175, 255, 255 } );

	return splitter;
}

class EntityInspector : public ModuleObserver
{
	std::size_t m_unrealised;
public:
	EntityInspector() : m_unrealised( 1 ){
	}
	void realise(){
		if ( --m_unrealised == 0 ) {
			if ( g_entityInspector_windowConstructed ) {
				//globalOutputStream() << "Entity Inspector: realise\n";
				EntityClassList_fill();
			}
		}
	}
	void unrealise(){
		if ( ++m_unrealised == 1 ) {
			if ( g_entityInspector_windowConstructed ) {
				//globalOutputStream() << "Entity Inspector: unrealise\n";
				EntityClassList_clear();
			}
		}
	}
};

EntityInspector g_EntityInspector;

#include "preferencesystem.h"
#include "stringio.h"

void EntityInspector_construct(){
	GlobalEntityClassManager().attach( g_EntityInspector );
}

void EntityInspector_destroy(){
	GlobalEntityClassManager().detach( g_EntityInspector );
}
