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

#include "debugging/debugging.h"

#include "ientity.h"
#include "iselection.h"
#include "iundo.h"

#include <vector>

#include "stream/stringstream.h"
#include "signal/isignal.h"
#include "signal/isignal.h"
#include "shaderlib.h"
#include "scenelib.h"

#include "gtkutil/idledraw.h"
#include "gtkutil/dialog.h"
#include "gtkutil/widget.h"
#include "gtkutil/clipboard.h"
#include "brushmanip.h"
#include "brush.h"
#include "patch.h"
#include "patchmanip.h"
#include "patchdialog.h"
#include "surfacedialog.h"
#include "texwindow.h"
#include "mainframe.h"
#include "camwindow.h"
#include "tools.h"
#include "grid.h"
#include "map.h"
#include "entityinspector.h"
#include "csg.h"



select_workzone_t g_select_workzone;


/**
   Loops over all selected brushes and stores their
   world AABBs in the specified array.
 */
class CollectSelectedBrushesBounds : public SelectionSystem::Visitor
{
	AABB* m_bounds;     // array of AABBs
	Unsigned m_max;     // max AABB-elements in array
	Unsigned& m_count;  // count of valid AABBs stored in array

public:
	CollectSelectedBrushesBounds( AABB* bounds, Unsigned max, Unsigned& count ) :
		m_bounds( bounds ),
		m_max( max ),
		m_count( count ){
		m_count = 0;
	}

	void visit( scene::Instance& instance ) const {
		ASSERT_MESSAGE( m_count <= m_max, "Invalid m_count in CollectSelectedBrushesBounds" );

		// stop if the array is already full
		if ( m_count == m_max ) {
			return;
		}

		if ( Instance_isSelected( instance ) ) {
			// brushes only
			if ( Instance_getBrush( instance ) != 0 ) {
				m_bounds[m_count] = instance.worldAABB();
				++m_count;
			}
		}
	}
};

/**
   Selects all objects that intersect one of the bounding AABBs.
   The exact intersection-method is specified through TSelectionPolicy
 */
template<class TSelectionPolicy>
class SelectByBounds : public scene::Graph::Walker
{
	AABB* m_aabbs;             // selection aabbs
	Unsigned m_count;          // number of aabbs in m_aabbs
	TSelectionPolicy policy;   // type that contains a custom intersection method aabb<->aabb

public:
	SelectByBounds( AABB* aabbs, Unsigned count ) :
		m_aabbs( aabbs ),
		m_count( count ){
	}

	bool pre( const scene::Path& path, scene::Instance& instance ) const {
		if( path.top().get().visible() ){
			Selectable* selectable = Instance_getSelectable( instance );

			// ignore worldspawn
			Entity* entity = Node_getEntity( path.top() );
			if ( entity != nullptr && string_equal( entity->getClassName(), "worldspawn" ) ) {
				return true;
			}

			if ( path.size() > 1
			  && !path.top().get().isRoot()
			  && selectable != 0
			  && !node_is_group( path.top() ) ) {
				for ( Unsigned i = 0; i < m_count; ++i )
				{
					if ( policy.Evaluate( m_aabbs[i], instance ) ) {
						selectable->setSelected( true );
					}
				}
			}
		}
		else{
			return false;
		}

		return true;
	}

	/**
	   Performs selection operation on the global scenegraph.
	   If delete_bounds_src is true, then the objects which were
	   used as source for the selection aabbs will be deleted.
	 */
	static void DoSelection( bool delete_bounds_src = true ){
		if ( GlobalSelectionSystem().Mode() == SelectionSystem::ePrimitive ) {
			// we may not need all AABBs since not all selected objects have to be brushes
			const Unsigned max = (Unsigned)GlobalSelectionSystem().countSelected();
			AABB* aabbs = new AABB[max];

			Unsigned count;
			CollectSelectedBrushesBounds collector( aabbs, max, count );
			GlobalSelectionSystem().foreachSelected( collector );

			// nothing usable in selection
			if ( !count ) {
				delete[] aabbs;
				return;
			}

			// delete selected objects
			if ( delete_bounds_src ) { // see deleteSelection
				UndoableCommand undo( "deleteSelected" );
				Select_Delete();
			}

			// select objects with bounds
			GlobalSceneGraph().traverse( SelectByBounds<TSelectionPolicy>( aabbs, count ) );

			SceneChangeNotify();
			delete[] aabbs;
		}
	}
};

/**
   SelectionPolicy for SelectByBounds
   Returns true if box and the AABB of instance intersect
 */
class SelectionPolicy_Touching
{
public:
	bool Evaluate( const AABB& box, scene::Instance& instance ) const {
		const AABB& other( instance.worldAABB() );
		for ( Unsigned i = 0; i < 3; ++i )
		{
			if ( fabsf( box.origin[i] - other.origin[i] ) > ( box.extents[i] + other.extents[i] ) ) {
				return false;
			}
		}
		return true;
	}
};

/**
   SelectionPolicy for SelectByBounds
   Returns true if the AABB of instance is inside box
 */
class SelectionPolicy_Inside
{
public:
	bool Evaluate( const AABB& box, scene::Instance& instance ) const {
		const AABB& other( instance.worldAABB() );
		for ( Unsigned i = 0; i < 3; ++i )
		{
			if ( fabsf( box.origin[i] - other.origin[i] ) > ( box.extents[i] - other.extents[i] ) ) {
				return false;
			}
		}
		return true;
	}
};

class DeleteSelected : public scene::Graph::Walker
{
	mutable bool m_remove;
	mutable bool m_removedChild;
public:
	DeleteSelected()
		: m_remove( false ), m_removedChild( false ){
	}
	bool pre( const scene::Path& path, scene::Instance& instance ) const {
		m_removedChild = false;

		if ( Instance_isSelected( instance )
		     && path.size() > 1
		     && !path.top().get().isRoot() ) {
			m_remove = true;

			return false; // dont traverse into child elements
		}
		return true;
	}
	void post( const scene::Path& path, scene::Instance& instance ) const {

		if ( m_removedChild ) {
			m_removedChild = 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 );
			}
		}

		// node should be removed
		if ( m_remove ) {
			if ( Node_isEntity( path.parent() ) ) {
				m_removedChild = true;
			}

			m_remove = false;
			Path_deleteTop( path );
		}
	}
};

void Scene_DeleteSelected( scene::Graph& graph ){
	graph.traverse( DeleteSelected() );
	SceneChangeNotify();
}

void Select_Delete(){
	Scene_DeleteSelected( GlobalSceneGraph() );
}

class InvertSelectionWalker : public scene::Graph::Walker
{
	SelectionSystem::EMode m_mode;
	SelectionSystem::EComponentMode m_compmode;
	mutable Selectable* m_selectable;
public:
	InvertSelectionWalker( SelectionSystem::EMode mode, SelectionSystem::EComponentMode compmode )
		: m_mode( mode ), m_compmode( compmode ), m_selectable( 0 ){
	}
	bool pre( const scene::Path& path, scene::Instance& instance ) const {
		if( !path.top().get().visible() ){
			m_selectable = 0;
			return false;
		}
		Selectable* selectable = Instance_getSelectable( instance );
		if ( selectable ) {
			switch ( m_mode )
			{
			case SelectionSystem::eEntity:
				if ( Node_isEntity( path.top() ) != 0 ) {
					m_selectable = path.top().get().visible() ? selectable : 0;
				}
				break;
			case SelectionSystem::ePrimitive:
				m_selectable = path.top().get().visible() ? selectable : 0;
				break;
			case SelectionSystem::eComponent:
				BrushInstance* brushinstance = Instance_getBrush( instance );
				if( brushinstance != 0 ){
					if( brushinstance->isSelected() )
						brushinstance->invertComponentSelection( m_compmode );
				}
				else{
					PatchInstance* patchinstance = Instance_getPatch( instance );
					if( patchinstance != 0 && m_compmode == SelectionSystem::eVertex ){
						if( patchinstance->isSelected() )
							patchinstance->invertComponentSelection();
					}
				}
				break;
			}
		}
		return true;
	}
	void post( const scene::Path& path, scene::Instance& instance ) const {
		if ( m_selectable != 0 ) {
			m_selectable->setSelected( !m_selectable->isSelected() );
			m_selectable = 0;
		}
	}
};

void Scene_Invert_Selection( scene::Graph& graph ){
	graph.traverse( InvertSelectionWalker( GlobalSelectionSystem().Mode(), GlobalSelectionSystem().ComponentMode() ) );
}

void Select_Invert(){
	Scene_Invert_Selection( GlobalSceneGraph() );
}

#if 0
//interesting printings
class ExpandSelectionToEntitiesWalker_dbg : public scene::Graph::Walker
{
	mutable std::size_t m_depth;
	const scene::Node* m_world;
public:
	ExpandSelectionToEntitiesWalker_dbg() : m_depth( 0 ), m_world( Map_FindWorldspawn( g_map ) ){
	}
	bool pre( const scene::Path& path, scene::Instance& instance ) const {
		++m_depth;
		globalOutputStream() << "pre depth_" << m_depth;
		globalOutputStream() << " path.size()_" << path.size();
		if ( path.top().get_pointer() == m_world )
			globalOutputStream() << " worldspawn";
		if( path.top().get().isRoot() )
			globalOutputStream() << " path.top().get().isRoot()";
		Entity* entity = Node_getEntity( path.top() );
		if ( entity != 0 ){
			globalOutputStream() << " entity!=0";
			if( entity->isContainer() ){
				globalOutputStream() << " entity->isContainer()";
			}
			globalOutputStream() << " classname_" << entity->getKeyValue( "classname" );
		}
		globalOutputStream() << '\n';
//	globalOutputStream() << "" <<  ;
//	globalOutputStream() << "" <<  ;
//	globalOutputStream() << "" <<  ;
//	globalOutputStream() << "" <<  ;
		return true;
	}
	void post( const scene::Path& path, scene::Instance& instance ) const {
		globalOutputStream() << "post depth_" << m_depth;
		globalOutputStream() << " path.size()_" << path.size();
		if ( path.top().get_pointer() == m_world )
			globalOutputStream() << " worldspawn";
		if( path.top().get().isRoot() )
			globalOutputStream() << " path.top().get().isRoot()";
		Entity* entity = Node_getEntity( path.top() );
		if ( entity != 0 ){
			globalOutputStream() << " entity!=0";
			if( entity->isContainer() ){
				globalOutputStream() << " entity->isContainer()";
			}
			globalOutputStream() << " classname_" << entity->getKeyValue( "classname" );
		}
		globalOutputStream() << '\n';
		--m_depth;
	}
};
#endif

class ExpandSelectionToPrimitivesWalker : public scene::Graph::Walker
{
	mutable std::size_t m_depth;
	const scene::Node* m_world;
public:
	ExpandSelectionToPrimitivesWalker() : m_depth( 0 ), m_world( Map_FindWorldspawn( g_map ) ){
	}
	bool pre( const scene::Path& path, scene::Instance& instance ) const {
		++m_depth;

		if( !path.top().get().visible() )
			return false;

//		if ( path.top().get_pointer() == m_world ) // ignore worldspawn
//			return false;

		if ( m_depth == 2 ) { // entity depth
			// traverse and select children if any one is selected
			bool beselected = false;
			const bool isContainer = Node_getEntity( path.top() )->isContainer();
			if ( instance.childSelected() || instance.isSelected() ) {
				beselected = true;
				Instance_setSelected( instance, !isContainer );
			}
			return isContainer && beselected;
		}
		else if ( m_depth == 3 ) { // primitive depth
			Instance_setSelected( instance, true );
			return false;
		}
		return true;
	}
	void post( const scene::Path& path, scene::Instance& instance ) const {
		--m_depth;
	}
};

void Scene_ExpandSelectionToPrimitives(){
	GlobalSceneGraph().traverse( ExpandSelectionToPrimitivesWalker() );
}

class ExpandSelectionToEntitiesWalker : public scene::Graph::Walker
{
	mutable std::size_t m_depth;
	const scene::Node* m_world;
public:
	ExpandSelectionToEntitiesWalker() : m_depth( 0 ), m_world( Map_FindWorldspawn( g_map ) ){
	}
	bool pre( const scene::Path& path, scene::Instance& instance ) const {
		++m_depth;

		if( !path.top().get().visible() )
			return false;

//		if ( path.top().get_pointer() == m_world ) // ignore worldspawn
//			return false;

		if ( m_depth == 2 ) { // entity depth
			// traverse and select children if any one is selected
			bool beselected = false;
			if ( instance.childSelected() || instance.isSelected() ) {
				beselected = true;
				if( path.top().get_pointer() != m_world ){ //avoid selecting world node
					Instance_setSelected( instance, true );
				}
			}
			return Node_getEntity( path.top() )->isContainer() && beselected;
		}
		else if ( m_depth == 3 ) { // primitive depth
			Instance_setSelected( instance, true );
			return false;
		}
		return true;
	}
	void post( const scene::Path& path, scene::Instance& instance ) const {
		--m_depth;
	}
};

void Scene_ExpandSelectionToEntities(){
	GlobalSceneGraph().traverse( ExpandSelectionToEntitiesWalker() );
}


namespace
{
void Selection_UpdateWorkzone(){
	if ( GlobalSelectionSystem().countSelected() != 0 ) {
		Select_GetBounds( g_select_workzone.d_work_min, g_select_workzone.d_work_max );
	}
}
typedef FreeCaller<Selection_UpdateWorkzone> SelectionUpdateWorkzoneCaller;

IdleDraw g_idleWorkzone = IdleDraw( SelectionUpdateWorkzoneCaller() );
}

const select_workzone_t& Select_getWorkZone(){
	g_idleWorkzone.flush();
	return g_select_workzone;
}

void UpdateWorkzone_ForSelection(){
	g_idleWorkzone.queueDraw();
}

// update the workzone to the current selection
void UpdateWorkzone_ForSelectionChanged( const Selectable& selectable ){
	//if ( selectable.isSelected() ) {
		UpdateWorkzone_ForSelection();
	//}
}

void Select_SetShader( const char* shader ){
	if ( GlobalSelectionSystem().Mode() != SelectionSystem::eComponent ) {
		Scene_BrushSetShader_Selected( GlobalSceneGraph(), shader );
		Scene_PatchSetShader_Selected( GlobalSceneGraph(), shader );
	}
	Scene_BrushSetShader_Component_Selected( GlobalSceneGraph(), shader );
}

void Select_SetShader_Undo( const char* shader ){
	if ( GlobalSelectionSystem().countSelectedComponents() != 0 || GlobalSelectionSystem().countSelected() != 0 ) {
		UndoableCommand undo( "textureNameSetSelected" );
		Select_SetShader( shader );
	}
}

void Select_SetTexdef( const TextureProjection& projection, bool setBasis /*= true*/, bool resetBasis /*= false*/ ){
	if ( GlobalSelectionSystem().Mode() != SelectionSystem::eComponent ) {
		Scene_BrushSetTexdef_Selected( GlobalSceneGraph(), projection, setBasis, resetBasis );
	}
	Scene_BrushSetTexdef_Component_Selected( GlobalSceneGraph(), projection, setBasis, resetBasis );
}

void Select_SetTexdef( const float* hShift, const float* vShift, const float* hScale, const float* vScale, const float* rotation ){
	if ( GlobalSelectionSystem().Mode() != SelectionSystem::eComponent ) {
		Scene_BrushSetTexdef_Selected( GlobalSceneGraph(), hShift, vShift, hScale, vScale, rotation );
	}
	Scene_BrushSetTexdef_Component_Selected( GlobalSceneGraph(), hShift, vShift, hScale, vScale, rotation );
}

void Select_SetFlags( const ContentsFlagsValue& flags ){
	if ( GlobalSelectionSystem().Mode() != SelectionSystem::eComponent ) {
		Scene_BrushSetFlags_Selected( GlobalSceneGraph(), flags );
	}
	Scene_BrushSetFlags_Component_Selected( GlobalSceneGraph(), flags );
}

void Select_GetBounds( Vector3& mins, Vector3& maxs ){
	const AABB bounds = GlobalSelectionSystem().getBoundsSelected();
	maxs = vector3_added( bounds.origin, bounds.extents );
	mins = vector3_subtracted( bounds.origin, bounds.extents );
}


void Select_FlipAxis( int axis ){
	Vector3 flip( 1, 1, 1 );
	flip[axis] = -1;
	GlobalSelectionSystem().scaleSelected( flip, true );
}


void Select_Scale( float x, float y, float z ){
	GlobalSelectionSystem().scaleSelected( Vector3( x, y, z ) );
}

enum axis_t
{
	eAxisX = 0,
	eAxisY = 1,
	eAxisZ = 2,
};

enum sign_t
{
	eSignPositive = 1,
	eSignNegative = -1,
};

inline Matrix4 matrix4_rotation_for_axis90( axis_t axis, sign_t sign ){
	switch ( axis )
	{
	case eAxisX:
		if ( sign == eSignPositive ) {
			return matrix4_rotation_for_sincos_x( 1, 0 );
		}
		else
		{
			return matrix4_rotation_for_sincos_x( -1, 0 );
		}
	case eAxisY:
		if ( sign == eSignPositive ) {
			return matrix4_rotation_for_sincos_y( 1, 0 );
		}
		else
		{
			return matrix4_rotation_for_sincos_y( -1, 0 );
		}
	default: //case eAxisZ:
		if ( sign == eSignPositive ) {
			return matrix4_rotation_for_sincos_z( 1, 0 );
		}
		else
		{
			return matrix4_rotation_for_sincos_z( -1, 0 );
		}
	}
}

inline void matrix4_rotate_by_axis90( Matrix4& matrix, axis_t axis, sign_t sign ){
	matrix4_multiply_by_matrix4( matrix, matrix4_rotation_for_axis90( axis, sign ) );
}

inline void matrix4_pivoted_rotate_by_axis90( Matrix4& matrix, axis_t axis, sign_t sign, const Vector3& pivotpoint ){
	matrix4_translate_by_vec3( matrix, pivotpoint );
	matrix4_rotate_by_axis90( matrix, axis, sign );
	matrix4_translate_by_vec3( matrix, vector3_negated( pivotpoint ) );
}

inline Quaternion quaternion_for_axis90( axis_t axis, sign_t sign ){
#if 1
	switch ( axis )
	{
	case eAxisX:
		if ( sign == eSignPositive ) {
			return Quaternion( c_half_sqrt2f, 0, 0, c_half_sqrt2f );
		}
		else
		{
			return Quaternion( -c_half_sqrt2f, 0, 0, -c_half_sqrt2f );
		}
	case eAxisY:
		if ( sign == eSignPositive ) {
			return Quaternion( 0, c_half_sqrt2f, 0, c_half_sqrt2f );
		}
		else
		{
			return Quaternion( 0, -c_half_sqrt2f, 0, -c_half_sqrt2f );
		}
	default: //case eAxisZ:
		if ( sign == eSignPositive ) {
			return Quaternion( 0, 0, c_half_sqrt2f, c_half_sqrt2f );
		}
		else
		{
			return Quaternion( 0, 0, -c_half_sqrt2f, -c_half_sqrt2f );
		}
	}
#else
	quaternion_for_matrix4_rotation( matrix4_rotation_for_axis90( (axis_t)axis, ( deg > 0 ) ? eSignPositive : eSignNegative ) );
#endif
}

void Select_RotateAxis( int axis, float deg ){
	if ( fabs( deg ) == 90.f ) {
		GlobalSelectionSystem().rotateSelected( quaternion_for_axis90( (axis_t)axis, ( deg > 0 ) ? eSignPositive : eSignNegative ), true );
	}
	else
	{
		switch ( axis )
		{
		case 0:
			GlobalSelectionSystem().rotateSelected( quaternion_for_matrix4_rotation( matrix4_rotation_for_x_degrees( deg ) ) );
			break;
		case 1:
			GlobalSelectionSystem().rotateSelected( quaternion_for_matrix4_rotation( matrix4_rotation_for_y_degrees( deg ) ) );
			break;
		case 2:
			GlobalSelectionSystem().rotateSelected( quaternion_for_matrix4_rotation( matrix4_rotation_for_z_degrees( deg ) ) );
			break;
		}
	}
}


void Select_ShiftTexture( float x, float y ){
	if ( GlobalSelectionSystem().Mode() != SelectionSystem::eComponent ) {
		Scene_BrushShiftTexdef_Selected( GlobalSceneGraph(), x, y );
		Scene_PatchTranslateTexture_Selected( GlobalSceneGraph(), x, y );
	}
	//globalOutputStream() << "shift selected face textures: s=" << x << " t=" << y << '\n';
	Scene_BrushShiftTexdef_Component_Selected( GlobalSceneGraph(), x, y );
}

void Select_ScaleTexture( float x, float y ){
	if ( GlobalSelectionSystem().Mode() != SelectionSystem::eComponent ) {
		Scene_BrushScaleTexdef_Selected( GlobalSceneGraph(), x, y );
		Scene_PatchScaleTexture_Selected( GlobalSceneGraph(), x, y );
	}
	Scene_BrushScaleTexdef_Component_Selected( GlobalSceneGraph(), x, y );
}

void Select_RotateTexture( float amt ){
	if ( GlobalSelectionSystem().Mode() != SelectionSystem::eComponent ) {
		Scene_BrushRotateTexdef_Selected( GlobalSceneGraph(), amt );
		Scene_PatchRotateTexture_Selected( GlobalSceneGraph(), amt );
	}
	Scene_BrushRotateTexdef_Component_Selected( GlobalSceneGraph(), amt );
}

// TTimo modified to handle shader architecture:
// expects shader names at input, comparison relies on shader names .. texture names no longer relevant
void FindReplaceTextures( const char* pFind, const char* pReplace, bool bSelected ){
	if ( !texdef_name_valid( pFind ) ) {
		globalErrorStream() << "FindReplaceTextures: invalid texture name: '" << pFind << "', aborted\n";
		return;
	}
	if ( !texdef_name_valid( pReplace ) ) {
		globalErrorStream() << "FindReplaceTextures: invalid texture name: '" << pReplace << "', aborted\n";
		return;
	}

	const auto command = StringStream<64>( "textureFindReplace -find ", pFind, " -replace ", pReplace );
	UndoableCommand undo( command );

	if( shader_equal( pReplace, "textures/" ) )
		pReplace = 0; //do search

	if ( bSelected ) {
		if ( GlobalSelectionSystem().Mode() != SelectionSystem::eComponent ) {
			Scene_BrushFindReplaceShader_Selected( GlobalSceneGraph(), pFind, pReplace );
			Scene_PatchFindReplaceShader_Selected( GlobalSceneGraph(), pFind, pReplace );
		}
		Scene_BrushFindReplaceShader_Component_Selected( GlobalSceneGraph(), pFind, pReplace );
	}
	else
	{
		Scene_BrushFindReplaceShader( GlobalSceneGraph(), pFind, pReplace );
		Scene_PatchFindReplaceShader( GlobalSceneGraph(), pFind, pReplace );
	}
}

typedef std::vector<const char*> PropertyValues;

bool propertyvalues_contain( const PropertyValues& propertyvalues, const char *str ){
	for ( PropertyValues::const_iterator i = propertyvalues.begin(); i != propertyvalues.end(); ++i )
	{
		if ( string_equal( str, *i ) ) {
			return true;
		}
	}
	return false;
}

template<typename EntityMatcher>
class EntityFindByPropertyValueWalker : public scene::Graph::Walker
{
	const EntityMatcher& m_entityMatcher;
	const scene::Node* m_world;
public:
	EntityFindByPropertyValueWalker( const EntityMatcher& entityMatcher )
		: m_entityMatcher( entityMatcher ), m_world( Map_FindWorldspawn( g_map ) ){
	}
	bool pre( const scene::Path& path, scene::Instance& instance ) const {
		if( !path.top().get().visible() ){
			return false;
		}
		// ignore worldspawn
		if ( path.top().get_pointer() == m_world ) {
			return false;
		}

		Entity* entity = Node_getEntity( path.top() );
		if ( entity != 0 ){
			if( m_entityMatcher( entity ) ) {
				Instance_getSelectable( instance )->setSelected( true );
				return true;
			}
			return false;
		}
		else if( path.size() > 2 && !path.top().get().isRoot() ){
			Selectable* selectable = Instance_getSelectable( instance );
			if( selectable != 0 )
				selectable->setSelected( true );
		}
		return true;
	}
};

template<typename EntityMatcher>
void Scene_EntitySelectByPropertyValues( scene::Graph& graph, const EntityMatcher& entityMatcher ){
	graph.traverse( EntityFindByPropertyValueWalker<EntityMatcher>( entityMatcher ) );
}

void Scene_EntitySelectByPropertyValues( scene::Graph& graph, const char *prop, const PropertyValues& propertyvalues ){
	Scene_EntitySelectByPropertyValues( GlobalSceneGraph(), [prop, &propertyvalues]( const Entity* entity )->bool{
		return propertyvalues_contain( propertyvalues, entity->getKeyValue( prop ) );
	} );
}

class EntityGetSelectedPropertyValuesWalker : public scene::Graph::Walker
{
	PropertyValues& m_propertyvalues;
	const char *m_prop;
	const scene::Node* m_world;
public:
	EntityGetSelectedPropertyValuesWalker( const char *prop, PropertyValues& propertyvalues )
		: m_propertyvalues( propertyvalues ), m_prop( prop ), m_world( Map_FindWorldspawn( g_map ) ){
	}
	bool pre( const scene::Path& path, scene::Instance& instance ) const {
		Entity* entity = Node_getEntity( path.top() );
		if ( entity != 0 ){
			if( path.top().get_pointer() != m_world ){
				if ( Instance_isSelected( instance ) || instance.childSelected() ) {
					if ( !propertyvalues_contain( m_propertyvalues, entity->getKeyValue( m_prop ) ) ) {
						m_propertyvalues.push_back( entity->getKeyValue( m_prop ) );
					}
				}
			}
			return false;
		}
		return true;
	}
};
/*
class EntityGetSelectedPropertyValuesWalker : public scene::Graph::Walker
{
PropertyValues& m_propertyvalues;
const char *m_prop;
mutable bool m_selected_children;
const scene::Node* m_world;
public:
EntityGetSelectedPropertyValuesWalker( const char *prop, PropertyValues& propertyvalues )
	: m_propertyvalues( propertyvalues ), m_prop( prop ), m_selected_children( false ), m_world( Map_FindWorldspawn( g_map ) ){
}
bool pre( const scene::Path& path, scene::Instance& instance ) const {
	if ( Instance_isSelected( instance ) ) {
		Entity* entity = Node_getEntity( path.top() );
		if ( entity != 0 ) {
			if ( !propertyvalues_contain( m_propertyvalues, entity->getKeyValue( m_prop ) ) ) {
				m_propertyvalues.push_back( entity->getKeyValue( m_prop ) );
			}
			return false;
		}
		else{
			m_selected_children = true;
		}
	}
	return true;
}
void post( const scene::Path& path, scene::Instance& instance ) const {
	Entity* entity = Node_getEntity( path.top() );
	if( entity != 0 && m_selected_children ){
		m_selected_children = false;
		if( path.top().get_pointer() == m_world )
			return;
		if ( !propertyvalues_contain( m_propertyvalues, entity->getKeyValue( m_prop ) ) ) {
			m_propertyvalues.push_back( entity->getKeyValue( m_prop ) );
		}
	}
}
};
*/
void Scene_EntityGetPropertyValues( scene::Graph& graph, const char *prop, PropertyValues& propertyvalues ){
	graph.traverse( EntityGetSelectedPropertyValuesWalker( prop, propertyvalues ) );
}

void Select_AllOfType(){
	if ( GlobalSelectionSystem().Mode() == SelectionSystem::eComponent ) {
		if ( GlobalSelectionSystem().ComponentMode() == SelectionSystem::eFace ) {
			GlobalSelectionSystem().setSelectedAllComponents( false );
			Scene_BrushSelectByShader_Component( GlobalSceneGraph(), TextureBrowser_GetSelectedShader() );
		}
	}
	else
	{
		PropertyValues propertyvalues;
		const char *prop = "classname";
		Scene_EntityGetPropertyValues( GlobalSceneGraph(), prop, propertyvalues );
		GlobalSelectionSystem().setSelectedAll( false );
		if ( !propertyvalues.empty() ) {
			Scene_EntitySelectByPropertyValues( GlobalSceneGraph(), prop, propertyvalues );
		}
		else
		{
			Scene_BrushSelectByShader( GlobalSceneGraph(), TextureBrowser_GetSelectedShader() );
			Scene_PatchSelectByShader( GlobalSceneGraph(), TextureBrowser_GetSelectedShader() );
		}
	}
}

void Select_EntitiesByKeyValue( const char* key, const char* value ){
	GlobalSelectionSystem().setSelectedAll( false );
	if( key != nullptr && value != nullptr ){
		if( !string_empty( key ) && !string_empty( value ) ){
			Scene_EntitySelectByPropertyValues( GlobalSceneGraph(), [key, value]( const Entity* entity )->bool{
				return string_equal_nocase( entity->getKeyValue( key ), value );
			} );
		}
	}
	else if( key != nullptr ){
		if( !string_empty( key ) ){
			Scene_EntitySelectByPropertyValues( GlobalSceneGraph(), [key]( const Entity* entity )->bool{
				return entity->hasKeyValue( key );
			} );
		}
	}
	else if( value != nullptr ){
		if( !string_empty( value ) ){
			Scene_EntitySelectByPropertyValues( GlobalSceneGraph(), [value]( const Entity* entity )->bool{
				class Visitor : public Entity::Visitor
				{
					const char* const m_value;
				public:
					bool m_found = false;
					Visitor( const char* value ) : m_value( value ){
					}
					void visit( const char* key, const char* value ){
						if ( string_equal_nocase( m_value, value ) ) {
							m_found = true;
						}
					}
				} visitor( value );
				entity->forEachKeyValue( visitor );
				return visitor.m_found;
			} );
		}
	}
}

void Select_FacesAndPatchesByShader(){
	Scene_BrushFacesSelectByShader( GlobalSceneGraph(), TextureBrowser_GetSelectedShader() );
	Scene_PatchSelectByShader( GlobalSceneGraph(), TextureBrowser_GetSelectedShader() );
}

void Select_Inside(){
	SelectByBounds<SelectionPolicy_Inside>::DoSelection();
}

void Select_Touching(){
	SelectByBounds<SelectionPolicy_Touching>::DoSelection( false );
}

void Select_ProjectTexture( const texdef_t& texdef, const Vector3* direction ){
	if ( GlobalSelectionSystem().Mode() != SelectionSystem::eComponent ) {
		Scene_BrushProjectTexture_Selected( GlobalSceneGraph(), texdef, direction );
		Scene_PatchProjectTexture_Selected( GlobalSceneGraph(), texdef, direction );
	}
	Scene_BrushProjectTexture_Component_Selected( GlobalSceneGraph(), texdef, direction );

	SceneChangeNotify();
}

void Select_ProjectTexture( const TextureProjection& projection, const Vector3& normal ){
	if ( GlobalSelectionSystem().Mode() != SelectionSystem::eComponent ) {
		Scene_BrushProjectTexture_Selected( GlobalSceneGraph(), projection, normal );
		Scene_PatchProjectTexture_Selected( GlobalSceneGraph(), projection, normal );
	}
	Scene_BrushProjectTexture_Component_Selected( GlobalSceneGraph(), projection, normal );

	SceneChangeNotify();
}

void Select_FitTexture( float horizontal, float vertical, bool only_dimension ){
	if ( GlobalSelectionSystem().Mode() != SelectionSystem::eComponent ) {
		Scene_BrushFitTexture_Selected( GlobalSceneGraph(), horizontal, vertical, only_dimension );
		Scene_PatchTileTexture_Selected( GlobalSceneGraph(), horizontal, vertical );
	}
	Scene_BrushFitTexture_Component_Selected( GlobalSceneGraph(), horizontal, vertical, only_dimension );

	SceneChangeNotify();
}


#include "commands.h"
#include "dialog.h"

inline void hide_node( scene::Node& node, bool hide ){
	hide
	? node.enable( scene::Node::eHidden )
	: node.disable( scene::Node::eHidden );
}

bool g_nodes_be_hidden = false;

ToggleItem g_hidden_item{ BoolExportCaller( g_nodes_be_hidden ) };

class HideSelectedWalker : public scene::Graph::Walker
{
	bool m_hide;
public:
	HideSelectedWalker( bool hide )
		: m_hide( hide ){
	}
	bool pre( const scene::Path& path, scene::Instance& instance ) const {
		if ( Instance_isSelected( instance ) ) {
			g_nodes_be_hidden = m_hide;
			hide_node( path.top(), m_hide );
		}
		return true;
	}
};

void Scene_Hide_Selected( bool hide ){
	GlobalSceneGraph().traverse( HideSelectedWalker( hide ) );
}

void Select_Hide(){
	Scene_Hide_Selected( true );
	SceneChangeNotify();
}

void HideSelected(){
	Select_Hide();
	if( GlobalSelectionSystem().countSelectedComponents() != 0 )
		GlobalSelectionSystem().setSelectedAllComponents( false );
	GlobalSelectionSystem().setSelectedAll( false );
	g_hidden_item.update();
}


class HideAllWalker : public scene::Graph::Walker
{
	bool m_hide;
public:
	HideAllWalker( bool hide )
		: m_hide( hide ){
	}
	bool pre( const scene::Path& path, scene::Instance& instance ) const {
		hide_node( path.top(), m_hide );
		return true;
	}
};

void Scene_Hide_All( bool hide ){
	GlobalSceneGraph().traverse( HideAllWalker( hide ) );
}

void Select_ShowAllHidden(){
	Scene_Hide_All( false );
	SceneChangeNotify();
	g_nodes_be_hidden = false;
	g_hidden_item.update();
}


void Selection_Flipx(){
	UndoableCommand undo( "mirrorSelected -axis x" );
	Select_FlipAxis( 0 );
}

void Selection_Flipy(){
	UndoableCommand undo( "mirrorSelected -axis y" );
	Select_FlipAxis( 1 );
}

void Selection_Flipz(){
	UndoableCommand undo( "mirrorSelected -axis z" );
	Select_FlipAxis( 2 );
}

void Selection_Rotatex(){
	UndoableCommand undo( "rotateSelected -axis x -angle -90" );
	Select_RotateAxis( 0,-90 );
}

void Selection_Rotatey(){
	UndoableCommand undo( "rotateSelected -axis y -angle 90" );
	Select_RotateAxis( 1, 90 );
}

void Selection_Rotatez(){
	UndoableCommand undo( "rotateSelected -axis z -angle -90" );
	Select_RotateAxis( 2,-90 );
}
#include "xywindow.h"
void Selection_FlipHorizontally(){
	VIEWTYPE viewtype = GlobalXYWnd_getCurrentViewType();
	switch ( viewtype )
	{
	case XY:
	case XZ:
		Selection_Flipx();
		break;
	default:
		Selection_Flipy();
		break;
	}
}

void Selection_FlipVertically(){
	VIEWTYPE viewtype = GlobalXYWnd_getCurrentViewType();
	switch ( viewtype )
	{
	case XZ:
	case YZ:
		Selection_Flipz();
		break;
	default:
		Selection_Flipy();
		break;
	}
}

void Selection_RotateClockwise(){
	UndoableCommand undo( "rotateSelected Clockwise 90" );
	VIEWTYPE viewtype = GlobalXYWnd_getCurrentViewType();
	switch ( viewtype )
	{
	case XY:
		Select_RotateAxis( 2, -90 );
		break;
	case XZ:
		Select_RotateAxis( 1, 90 );
		break;
	default:
		Select_RotateAxis( 0, -90 );
		break;
	}
}

void Selection_RotateAnticlockwise(){
	UndoableCommand undo( "rotateSelected Anticlockwise 90" );
	VIEWTYPE viewtype = GlobalXYWnd_getCurrentViewType();
	switch ( viewtype )
	{
	case XY:
		Select_RotateAxis( 2, 90 );
		break;
	case XZ:
		Select_RotateAxis( 1, -90 );
		break;
	default:
		Select_RotateAxis( 0, 90 );
		break;
	}

}


void Nudge( int nDim, float fNudge ){
	Vector3 translate( 0, 0, 0 );
	translate[nDim] = fNudge;

	GlobalSelectionSystem().translateSelected( translate );
}

void Selection_NudgeZ( float amount ){
	const auto command = StringStream<64>( "nudgeSelected -axis z -amount ", amount );
	UndoableCommand undo( command );

	Nudge( 2, amount );
}

void Selection_MoveDown(){
	Selection_NudgeZ( -GetGridSize() );
}

void Selection_MoveUp(){
	Selection_NudgeZ( GetGridSize() );
}


inline Quaternion quaternion_for_euler_xyz_degrees( const Vector3& eulerXYZ ){
#if 0
	return quaternion_for_matrix4_rotation( matrix4_rotation_for_euler_xyz_degrees( eulerXYZ ) );
#elif 0
	return quaternion_multiplied_by_quaternion(
	           quaternion_multiplied_by_quaternion(
	               quaternion_for_z( degrees_to_radians( eulerXYZ[2] ) ),
	               quaternion_for_y( degrees_to_radians( eulerXYZ[1] ) )
	           ),
	           quaternion_for_x( degrees_to_radians( eulerXYZ[0] ) )
	       );
#elif 1
	double cx = cos( degrees_to_radians( eulerXYZ[0] * 0.5 ) );
	double sx = sin( degrees_to_radians( eulerXYZ[0] * 0.5 ) );
	double cy = cos( degrees_to_radians( eulerXYZ[1] * 0.5 ) );
	double sy = sin( degrees_to_radians( eulerXYZ[1] * 0.5 ) );
	double cz = cos( degrees_to_radians( eulerXYZ[2] * 0.5 ) );
	double sz = sin( degrees_to_radians( eulerXYZ[2] * 0.5 ) );

	return Quaternion(
	           cz * cy * sx - sz * sy * cx,
	           cz * sy * cx + sz * cy * sx,
	           sz * cy * cx - cz * sy * sx,
	           cz * cy * cx + sz * sy * sx
	       );
#endif
}


void Undo(){
	GlobalUndoSystem().undo();
	SceneChangeNotify();
}

void Redo(){
	GlobalUndoSystem().redo();
	SceneChangeNotify();
}

void deleteSelection(){
	if( GlobalSelectionSystem().Mode() == SelectionSystem::eComponent && GlobalSelectionSystem().countSelectedComponents() != 0 ){
		UndoableCommand undo( "deleteSelectedComponents" );
		CSG_DeleteComponents();
	}
	else{
		UndoableCommand undo( "deleteSelected" );
		Select_Delete();
	}
}

void Map_ExportSelected( TextOutputStream& ostream ){
	Map_ExportSelected( ostream, Map_getFormat( g_map ) );
}

void Map_ImportSelected( TextInputStream& istream ){
	Map_ImportSelected( istream, Map_getFormat( g_map ) );
}

void Selection_Copy(){
	clipboard_copy( Map_ExportSelected );
}

void Selection_Paste(){
	clipboard_paste( Map_ImportSelected );
}

void Copy(){
	Selection_Copy();
}

void Paste(){
	UndoableCommand undo( "paste" );

	GlobalSelectionSystem().setSelectedAll( false );
	Selection_Paste();
}

void TranslateToCamera(){
	CamWnd& camwnd = *g_pParentWnd->GetCamWnd();
	GlobalSelectionSystem().translateSelected( vector3_snapped( Camera_getOrigin( camwnd ) - GlobalSelectionSystem().getBoundsSelected().origin, GetSnapGridSize() ) );
}

void PasteToCamera(){
	GlobalSelectionSystem().setSelectedAll( false );
	UndoableCommand undo( "pasteToCamera" );
	Selection_Paste();
	TranslateToCamera();
}

void MoveToCamera(){
	UndoableCommand undo( "moveToCamera" );
	TranslateToCamera();
}



class CloneSelected : public scene::Graph::Walker
{
	const bool m_makeUnique;
	const scene::Node* m_world;
public:
	mutable std::vector<scene::Node*> m_cloned;
	CloneSelected( bool makeUnique ) : m_makeUnique( makeUnique ), m_world( Map_FindWorldspawn( g_map ) ){
	}
	bool pre( const scene::Path& path, scene::Instance& instance ) const {
		if ( path.size() == 1 ) {
			return true;
		}

		if ( path.top().get_pointer() == m_world ) { // ignore worldspawn, but keep checking children
			return true;
		}

		if ( !path.top().get().isRoot() ) {
			if ( Instance_isSelected( instance ) ) {
				return false;
			}
			if( m_makeUnique && instance.childSelected() ){ /* clone selected group entity primitives to new group entity */
				return false;
			}
		}

		return true;
	}
	void post( const scene::Path& path, scene::Instance& instance ) const {
		if ( path.size() == 1 ) {
			return;
		}

		if ( path.top().get_pointer() == m_world ) { // ignore worldspawn
			return;
		}

		if ( !path.top().get().isRoot() ) {
			if ( Instance_isSelected( instance ) ) {
				NodeSmartReference clone( Node_Clone( path.top() ) );
				Map_gatherNamespaced( clone );
				Node_getTraversable( path.parent().get() )->insert( clone );
				m_cloned.push_back( clone.get_pointer() );
			}
			else if( m_makeUnique && instance.childSelected() ){ /* clone selected group entity primitives to new group entity */
				NodeSmartReference clone( Node_Clone_Selected( path.top() ) );
				Map_gatherNamespaced( clone );
				Node_getTraversable( path.parent().get() )->insert( clone );
				m_cloned.push_back( clone.get_pointer() );
			}
		}
	}
};

void Scene_Clone_Selected( scene::Graph& graph, bool makeUnique ){
	CloneSelected cloneSelected( makeUnique );
	graph.traverse( cloneSelected );

	Map_mergeClonedNames( makeUnique );

	/* deselect originals */
	GlobalSelectionSystem().setSelectedAll( false );
	/* select cloned */
	for( scene::Node *node : cloneSelected.m_cloned )
	{
		class walker : public scene::Traversable::Walker
		{
		public:
			bool pre( scene::Node& node ) const override {
				if( scene::Instantiable *instantiable = Node_getInstantiable( node ) ){
					class visitor : public scene::Instantiable::Visitor
					{
					public:
						void visit( scene::Instance& instance ) const override {
							Instance_setSelected( instance, true );
						}
					};

					instantiable->forEachInstance( visitor() );
				}
				return true;
			}
		};
		Node_traverseSubgraph( *node, walker() );
	}
}

enum ENudgeDirection
{
	eNudgeUp = 1,
	eNudgeDown = 3,
	eNudgeLeft = 0,
	eNudgeRight = 2,
};

struct AxisBase
{
	Vector3 x;
	Vector3 y;
	Vector3 z;
	AxisBase( const Vector3& x_, const Vector3& y_, const Vector3& z_ )
		: x( x_ ), y( y_ ), z( z_ ){
	}
};

AxisBase AxisBase_forViewType( VIEWTYPE viewtype ){
	switch ( viewtype )
	{
	case XY:
		return AxisBase( g_vector3_axis_x, g_vector3_axis_y, g_vector3_axis_z );
	case XZ:
		return AxisBase( g_vector3_axis_x, g_vector3_axis_z, g_vector3_axis_y );
	case YZ:
		return AxisBase( g_vector3_axis_y, g_vector3_axis_z, g_vector3_axis_x );
	}

	ERROR_MESSAGE( "invalid viewtype" );
	return AxisBase( Vector3( 0, 0, 0 ), Vector3( 0, 0, 0 ), Vector3( 0, 0, 0 ) );
}

Vector3 AxisBase_axisForDirection( const AxisBase& axes, ENudgeDirection direction ){
	switch ( direction )
	{
	case eNudgeLeft:
		return vector3_negated( axes.x );
	case eNudgeUp:
		return axes.y;
	case eNudgeRight:
		return axes.x;
	case eNudgeDown:
		return vector3_negated( axes.y );
	}

	ERROR_MESSAGE( "invalid direction" );
	return Vector3( 0, 0, 0 );
}

bool g_bNudgeAfterClone = false;

void NudgeSelection( ENudgeDirection direction, float fAmount, VIEWTYPE viewtype ){
	AxisBase axes( AxisBase_forViewType( viewtype ) );
	Vector3 view_direction( vector3_negated( axes.z ) );
	Vector3 nudge( vector3_scaled( AxisBase_axisForDirection( axes, direction ), fAmount ) );
	GlobalSelectionSystem().NudgeManipulator( nudge, view_direction );
}

void Selection_Clone(){
	if ( GlobalSelectionSystem().Mode() == SelectionSystem::ePrimitive ) {
		UndoableCommand undo( "cloneSelected" );

		Scene_Clone_Selected( GlobalSceneGraph(), false );

		if( g_bNudgeAfterClone ){
			NudgeSelection(eNudgeRight, GetGridSize(), GlobalXYWnd_getCurrentViewType());
			NudgeSelection(eNudgeDown, GetGridSize(), GlobalXYWnd_getCurrentViewType());
		}
	}
}

void Selection_Clone_MakeUnique(){
	if ( GlobalSelectionSystem().Mode() == SelectionSystem::ePrimitive ) {
		UndoableCommand undo( "cloneSelectedMakeUnique" );

		Scene_Clone_Selected( GlobalSceneGraph(), true );

		if( g_bNudgeAfterClone ){
			NudgeSelection(eNudgeRight, GetGridSize(), GlobalXYWnd_getCurrentViewType());
			NudgeSelection(eNudgeDown, GetGridSize(), GlobalXYWnd_getCurrentViewType());
		}
	}
}

// called when the escape key is used (either on the main window or on an inspector)
void Selection_Deselect(){
	if ( GlobalSelectionSystem().Mode() == SelectionSystem::eComponent ) {
		if ( GlobalSelectionSystem().countSelectedComponents() != 0 ) {
			GlobalSelectionSystem().setSelectedAllComponents( false );
		}
		else
		{
			SelectionSystem_DefaultMode();
			ComponentModeChanged();
		}
	}
	else
	{
		if ( GlobalSelectionSystem().countSelectedComponents() != 0 ) {
			GlobalSelectionSystem().setSelectedAllComponents( false );
		}
		else
		{
			GlobalSelectionSystem().setSelectedAll( false );
		}
	}
}

void Scene_Clone_Selected(){
	Scene_Clone_Selected( GlobalSceneGraph(), false );
}

void RepeatTransforms(){
	GlobalSelectionSystem().repeatTransforms( FreeCaller<Scene_Clone_Selected>() );
}


void Selection_NudgeUp(){
	UndoableCommand undo( "nudgeSelectedUp" );
	NudgeSelection( eNudgeUp, GetGridSize(), GlobalXYWnd_getCurrentViewType() );
}

void Selection_NudgeDown(){
	UndoableCommand undo( "nudgeSelectedDown" );
	NudgeSelection( eNudgeDown, GetGridSize(), GlobalXYWnd_getCurrentViewType() );
}

void Selection_NudgeLeft(){
	UndoableCommand undo( "nudgeSelectedLeft" );
	NudgeSelection( eNudgeLeft, GetGridSize(), GlobalXYWnd_getCurrentViewType() );
}

void Selection_NudgeRight(){
	UndoableCommand undo( "nudgeSelectedRight" );
	NudgeSelection( eNudgeRight, GetGridSize(), GlobalXYWnd_getCurrentViewType() );
}



void Texdef_Rotate( float angle ){
	const auto command = StringStream<64>( "brushRotateTexture -angle ", angle );
	UndoableCommand undo( command );
	Select_RotateTexture( angle );
}
// these are actually {Anti,}Clockwise in BP mode only (AP/220 - 50/50)
// TODO is possible to make really {Anti,}Clockwise
void Texdef_RotateClockwise(){
	Texdef_Rotate( static_cast<float>( -fabs( g_si_globals.rotate ) ) );
}

void Texdef_RotateAntiClockwise(){
	Texdef_Rotate( static_cast<float>( fabs( g_si_globals.rotate ) ) );
}

void Texdef_Scale( float x, float y ){
	const auto command = StringStream<64>( "brushScaleTexture -x ", x, " -y ", y );
	UndoableCommand undo( command );
	Select_ScaleTexture( x, y );
}

void Texdef_ScaleUp(){
	Texdef_Scale( 0, g_si_globals.scale[1] );
}

void Texdef_ScaleDown(){
	Texdef_Scale( 0, -g_si_globals.scale[1] );
}

void Texdef_ScaleLeft(){
	Texdef_Scale( -g_si_globals.scale[0],0 );
}

void Texdef_ScaleRight(){
	Texdef_Scale( g_si_globals.scale[0],0 );
}

void Texdef_Shift( float x, float y ){
	const auto command = StringStream<64>( "brushShiftTexture -x ", x, " -y ", y );
	UndoableCommand undo( command );
	Select_ShiftTexture( x, y );
}

void Texdef_ShiftLeft(){
	Texdef_Shift( -g_si_globals.shift[0], 0 );
}

void Texdef_ShiftRight(){
	Texdef_Shift( g_si_globals.shift[0], 0 );
}

void Texdef_ShiftUp(){
	Texdef_Shift( 0, g_si_globals.shift[1] );
}

void Texdef_ShiftDown(){
	Texdef_Shift( 0, -g_si_globals.shift[1] );
}



class SnappableSnapToGridSelected : public scene::Graph::Walker
{
	float m_snap;
public:
	SnappableSnapToGridSelected( float snap )
		: m_snap( snap ){
	}
	bool pre( const scene::Path& path, scene::Instance& instance ) const {
		if ( path.top().get().visible() ) {
			Snappable* snappable = Node_getSnappable( path.top() );
			if ( snappable != 0
			  && Instance_isSelected( instance ) ) {
				snappable->snapto( m_snap );
			}
		}
		return true;
	}
};

void Scene_SnapToGrid_Selected( scene::Graph& graph, float snap ){
	graph.traverse( SnappableSnapToGridSelected( snap ) );
}

class ComponentSnappableSnapToGridSelected : public scene::Graph::Walker
{
	float m_snap;
public:
	ComponentSnappableSnapToGridSelected( float snap )
		: m_snap( snap ){
	}
	bool pre( const scene::Path& path, scene::Instance& instance ) const {
		if ( path.top().get().visible() ) {
			ComponentSnappable* componentSnappable = Instance_getComponentSnappable( instance );
			if ( componentSnappable != 0
			  && Instance_isSelected( instance ) ) {
				componentSnappable->snapComponents( m_snap );
			}
		}
		return true;
	}
};

void Scene_SnapToGrid_Component_Selected( scene::Graph& graph, float snap ){
	graph.traverse( ComponentSnappableSnapToGridSelected( snap ) );
}

void Selection_SnapToGrid(){
	const auto command = StringStream<64>( "snapSelected -grid ", GetGridSize() );
	UndoableCommand undo( command );

	if ( GlobalSelectionSystem().Mode() == SelectionSystem::eComponent && GlobalSelectionSystem().countSelectedComponents() ) {
		Scene_SnapToGrid_Component_Selected( GlobalSceneGraph(), GetGridSize() );
	}
	else
	{
		Scene_SnapToGrid_Selected( GlobalSceneGraph(), GetGridSize() );
	}
}



#include <QWidget>
#include <QGridLayout>
#include <QDialogButtonBox>
#include <QPushButton>
#include "gtkutil/spinbox.h"


class RotateDialog : public QObject
{
	QWidget *m_window{};
	QDoubleSpinBox *m_x;
	QDoubleSpinBox *m_y;
	QDoubleSpinBox *m_z;
	void construct(){
		m_window = new QWidget( MainFrame_getWindow(), Qt::Dialog | Qt::WindowCloseButtonHint );
		m_window->setWindowTitle( "Arbitrary rotation" );
		m_window->installEventFilter( this );

		auto grid = new QGridLayout( m_window );
		grid->setSizeConstraint( QLayout::SizeConstraint::SetFixedSize );

		{
			grid->addWidget( m_x = new DoubleSpinBox( -360, 360, 0, 6, 1, true ), 0, 1 );
			grid->addWidget( m_y = new DoubleSpinBox( -360, 360, 0, 6, 1, true ), 1, 1 );
			grid->addWidget( m_z = new DoubleSpinBox( -360, 360, 0, 6, 1, true ), 2, 1 );
		}
		{
			grid->addWidget( new SpinBoxLabel( "  X  ", m_x ), 0, 0 );
			grid->addWidget( new SpinBoxLabel( "  Y  ", m_y ), 1, 0 );
			grid->addWidget( new SpinBoxLabel( "  Z  ", m_z ), 2, 0 );
		}
		{
			auto buttons = new QDialogButtonBox( Qt::Orientation::Vertical );
			grid->addWidget( buttons, 0, 2, 3, 1 );
			QObject::connect( buttons->addButton( QDialogButtonBox::StandardButton::Ok ), &QPushButton::clicked, [this](){ ok(); } );
			QObject::connect( buttons->addButton( QDialogButtonBox::StandardButton::Cancel ), &QPushButton::clicked, [this](){ cancel(); } );
			QObject::connect( buttons->addButton( QDialogButtonBox::StandardButton::Apply ), &QPushButton::clicked, [this](){ apply(); } );
		}
	}
	void apply(){
		const Vector3 eulerXYZ( m_x->value(), m_y->value(), m_z->value() );

		const auto command = StringStream<64>( "rotateSelectedEulerXYZ -x ", eulerXYZ[0], " -y ", eulerXYZ[1], " -z ", eulerXYZ[2] );
		UndoableCommand undo( command );

		GlobalSelectionSystem().rotateSelected( quaternion_for_euler_xyz_degrees( eulerXYZ ) );
	}
	void cancel(){
		m_window->hide();

		m_x->setValue( 0 ); // reset to 0 on close
		m_y->setValue( 0 );
		m_z->setValue( 0 );
	}
	void ok(){
		apply();
	//	cancel();
		m_window->hide();
	}
protected:
	bool eventFilter( QObject *obj, QEvent *event ) override {
		if( event->type() == QEvent::ShortcutOverride ) {
			QKeyEvent *keyEvent = static_cast<QKeyEvent*>( event );
			if( keyEvent->key() == Qt::Key_Escape ){
				cancel();
				event->accept();
			}
			else if( keyEvent->key() == Qt::Key_Return || keyEvent->key() == Qt::Key_Enter ){
				ok();
				event->accept();
			}
			else if( keyEvent->key() == Qt::Key_Tab || keyEvent->key() == Qt::Key_Space ){
				event->accept();
			}
		}
		else if( event->type() == QEvent::Close ) {
			event->ignore();
			cancel();
			return true;
		}
		return QObject::eventFilter( obj, event ); // standard event processing
	}
public:
	void show(){
		if( m_window == nullptr )
			construct();
		m_window->show();
		m_window->raise();
		m_window->activateWindow();
	}
}
g_rotate_dialog;

void DoRotateDlg(){
	g_rotate_dialog.show();
}



class ScaleDialog : public QObject
{
	QWidget *m_window{};
	QDoubleSpinBox *m_x;
	QDoubleSpinBox *m_y;
	QDoubleSpinBox *m_z;
	void construct(){
		m_window = new QWidget( MainFrame_getWindow(), Qt::Dialog | Qt::WindowCloseButtonHint );
		m_window->setWindowTitle( "Arbitrary scale" );
		m_window->installEventFilter( this );

		auto grid = new QGridLayout( m_window );
		grid->setSizeConstraint( QLayout::SizeConstraint::SetFixedSize );

		{
			grid->addWidget( m_x = new DoubleSpinBox( -32768, 32768, 1, 6, 1, false ), 0, 1 );
			grid->addWidget( m_y = new DoubleSpinBox( -32768, 32768, 1, 6, 1, false ), 1, 1 );
			grid->addWidget( m_z = new DoubleSpinBox( -32768, 32768, 1, 6, 1, false ), 2, 1 );
		}
		{
			grid->addWidget( new SpinBoxLabel( "  X  ", m_x ), 0, 0 );
			grid->addWidget( new SpinBoxLabel( "  Y  ", m_y ), 1, 0 );
			grid->addWidget( new SpinBoxLabel( "  Z  ", m_z ), 2, 0 );
		}
		{
			auto buttons = new QDialogButtonBox( Qt::Orientation::Vertical );
			grid->addWidget( buttons, 0, 2, 3, 1 );
			QObject::connect( buttons->addButton( QDialogButtonBox::StandardButton::Ok ), &QPushButton::clicked, [this](){ ok(); } );
			QObject::connect( buttons->addButton( QDialogButtonBox::StandardButton::Cancel ), &QPushButton::clicked, [this](){ cancel(); } );
			QObject::connect( buttons->addButton( QDialogButtonBox::StandardButton::Apply ), &QPushButton::clicked, [this](){ apply(); } );
		}
	}
	void apply(){
		const float sx = m_x->value(), sy = m_y->value(), sz = m_z->value();

		const auto command = StringStream<64>( "scaleSelected -x ", sx, " -y ", sy, " -z ", sz );
		UndoableCommand undo( command );

		Select_Scale( sx, sy, sz );
	}
	void cancel(){
		m_window->hide();

		m_x->setValue( 1 ); // reset to 1 on close
		m_y->setValue( 1 );
		m_z->setValue( 1 );
	}
	void ok(){
		apply();
	//	cancel();
		m_window->hide();
	}
protected:
	bool eventFilter( QObject *obj, QEvent *event ) override {
		if( event->type() == QEvent::ShortcutOverride ) {
			QKeyEvent *keyEvent = static_cast<QKeyEvent*>( event );
			if( keyEvent->key() == Qt::Key_Escape ){
				cancel();
				event->accept();
			}
			else if( keyEvent->key() == Qt::Key_Return || keyEvent->key() == Qt::Key_Enter ){
				ok();
				event->accept();
			}
			else if( keyEvent->key() == Qt::Key_Tab || keyEvent->key() == Qt::Key_Space ){
				event->accept();
			}
		}
		else if( event->type() == QEvent::Close ) {
			event->ignore();
			cancel();
			return true;
		}
		return QObject::eventFilter( obj, event ); // standard event processing
	}
public:
	void show(){
		if( m_window == nullptr )
			construct();
		m_window->show();
		m_window->raise();
		m_window->activateWindow();
	}
}
g_scale_dialog;

void DoScaleDlg(){
	g_scale_dialog.show();
}


class EntityGetSelectedPropertyValuesWalker_nonEmpty : public scene::Graph::Walker
{
	PropertyValues& m_propertyvalues;
	const char *m_prop;
	const scene::Node* m_world;
public:
	EntityGetSelectedPropertyValuesWalker_nonEmpty( const char *prop, PropertyValues& propertyvalues )
		: m_propertyvalues( propertyvalues ), m_prop( prop ), m_world( Map_FindWorldspawn( g_map ) ){
	}
	bool pre( const scene::Path& path, scene::Instance& instance ) const {
		Entity* entity = Node_getEntity( path.top() );
		if ( entity != 0 ){
			if( path.top().get_pointer() != m_world ){
				if ( Instance_isSelected( instance ) || instance.childSelected() ) {
					const char* keyvalue = entity->getKeyValue( m_prop );
					if ( !string_empty( keyvalue ) && !propertyvalues_contain( m_propertyvalues, keyvalue ) ) {
						m_propertyvalues.push_back( keyvalue );
					}
				}
			}
			return false;
		}
		return true;
	}
};

void Scene_EntityGetPropertyValues_nonEmpty( scene::Graph& graph, const char *prop, PropertyValues& propertyvalues ){
	graph.traverse( EntityGetSelectedPropertyValuesWalker_nonEmpty( prop, propertyvalues ) );
}

#include "preferences.h"

void Select_ConnectedEntities( bool targeting, bool targets, bool focus ){
	PropertyValues target_propertyvalues;
	PropertyValues targetname_propertyvalues;
	const char *target_prop = "target";
	const char *targetname_prop;
	if ( g_pGameDescription->mGameType == "doom3" ) {
		targetname_prop = "name";
	}
	else{
		targetname_prop = "targetname";
	}

	if( targeting ){
		Scene_EntityGetPropertyValues_nonEmpty( GlobalSceneGraph(), targetname_prop, targetname_propertyvalues );
	}
	if( targets ){
		Scene_EntityGetPropertyValues_nonEmpty( GlobalSceneGraph(), target_prop, target_propertyvalues );
	}

	if( target_propertyvalues.empty() && targetname_propertyvalues.empty() ){
		globalErrorStream() << "SelectConnectedEntities: nothing found\n";
		return;
	}

	if( !targeting || !targets ){
		GlobalSelectionSystem().setSelectedAll( false );
	}
	if ( targeting && !targetname_propertyvalues.empty() ) {
		Scene_EntitySelectByPropertyValues( GlobalSceneGraph(), target_prop, targetname_propertyvalues );
	}
	if ( targets && !target_propertyvalues.empty() ) {
		Scene_EntitySelectByPropertyValues( GlobalSceneGraph(), targetname_prop, target_propertyvalues );
	}
	if( focus ){
		FocusAllViews();
	}
}

void SelectConnectedEntities(){
	Select_ConnectedEntities( true, true, false );
}



void Select_registerCommands(){
	GlobalCommands_insert( "ShowHidden", FreeCaller<Select_ShowAllHidden>(), QKeySequence( "Shift+H" ) );
	GlobalToggles_insert( "HideSelected", FreeCaller<HideSelected>(), ToggleItem::AddCallbackCaller( g_hidden_item ), QKeySequence( "H" ) );

	GlobalCommands_insert( "MirrorSelectionX", FreeCaller<Selection_Flipx>() );
	GlobalCommands_insert( "RotateSelectionX", FreeCaller<Selection_Rotatex>() );
	GlobalCommands_insert( "MirrorSelectionY", FreeCaller<Selection_Flipy>() );
	GlobalCommands_insert( "RotateSelectionY", FreeCaller<Selection_Rotatey>() );
	GlobalCommands_insert( "MirrorSelectionZ", FreeCaller<Selection_Flipz>() );
	GlobalCommands_insert( "RotateSelectionZ", FreeCaller<Selection_Rotatez>() );

	GlobalCommands_insert( "MirrorSelectionHorizontally", FreeCaller<Selection_FlipHorizontally>() );
	GlobalCommands_insert( "MirrorSelectionVertically", FreeCaller<Selection_FlipVertically>() );

	GlobalCommands_insert( "RotateSelectionClockwise", FreeCaller<Selection_RotateClockwise>() );
	GlobalCommands_insert( "RotateSelectionAnticlockwise", FreeCaller<Selection_RotateAnticlockwise>() );

	GlobalCommands_insert( "SelectTextured", FreeCaller<Select_FacesAndPatchesByShader>(), QKeySequence( "Ctrl+Shift+A" ) );

	GlobalCommands_insert( "Undo", FreeCaller<Undo>(), QKeySequence( "Ctrl+Z" ) );
	GlobalCommands_insert( "Redo", FreeCaller<Redo>(), QKeySequence( "Ctrl+Shift+Z" ) );
	GlobalCommands_insert( "Redo2", FreeCaller<Redo>(), QKeySequence( "Ctrl+Y" ) );
	GlobalCommands_insert( "Copy", FreeCaller<Copy>(), QKeySequence( "Ctrl+C" ) );
	GlobalCommands_insert( "Paste", FreeCaller<Paste>(), QKeySequence( "Ctrl+V" ) );
	GlobalCommands_insert( "PasteToCamera", FreeCaller<PasteToCamera>(), QKeySequence( "Shift+V" ) );
	GlobalCommands_insert( "MoveToCamera", FreeCaller<MoveToCamera>(), QKeySequence( "Ctrl+Shift+V" ) );
	GlobalCommands_insert( "CloneSelection", FreeCaller<Selection_Clone>(), QKeySequence( "Space" ) );
	GlobalCommands_insert( "CloneSelectionAndMakeUnique", FreeCaller<Selection_Clone_MakeUnique>(), QKeySequence( "Shift+Space" ) );
	GlobalCommands_insert( "DeleteSelection2", FreeCaller<deleteSelection>(), QKeySequence( "Backspace" ) );
	GlobalCommands_insert( "DeleteSelection", FreeCaller<deleteSelection>(), QKeySequence( "Z" ) );
	GlobalCommands_insert( "RepeatTransforms", FreeCaller<RepeatTransforms>(), QKeySequence( "Ctrl+R" ) );
//	GlobalCommands_insert( "ParentSelection", FreeCaller<Scene_parentSelected>() );
	GlobalCommands_insert( "UnSelectSelection2", FreeCaller<Selection_Deselect>(), QKeySequence( "Escape" ) );
	GlobalCommands_insert( "UnSelectSelection", FreeCaller<Selection_Deselect>(), QKeySequence( "C" ) );
	GlobalCommands_insert( "InvertSelection", FreeCaller<Select_Invert>(), QKeySequence( "I" ) );
	GlobalCommands_insert( "SelectInside", FreeCaller<Select_Inside>() );
	GlobalCommands_insert( "SelectTouching", FreeCaller<Select_Touching>() );
	GlobalCommands_insert( "ExpandSelectionToPrimitives", FreeCaller<Scene_ExpandSelectionToPrimitives>(), QKeySequence( "Ctrl+E" ) );
	GlobalCommands_insert( "ExpandSelectionToEntities", FreeCaller<Scene_ExpandSelectionToEntities>(), QKeySequence( "Shift+E" ) );
	GlobalCommands_insert( "SelectConnectedEntities", FreeCaller<SelectConnectedEntities>(), QKeySequence( "Ctrl+Shift+E" ) );

	GlobalCommands_insert( "ArbitraryRotation", FreeCaller<DoRotateDlg>(), QKeySequence( "Shift+R" ) );
	GlobalCommands_insert( "ArbitraryScale", FreeCaller<DoScaleDlg>(), QKeySequence( "Ctrl+Shift+S" ) );

	GlobalCommands_insert( "SnapToGrid", FreeCaller<Selection_SnapToGrid>(), QKeySequence( "Ctrl+G" ) );

	GlobalCommands_insert( "SelectAllOfType", FreeCaller<Select_AllOfType>(), QKeySequence( "Shift+A" ) );

	GlobalCommands_insert( "TexRotateClock", FreeCaller<Texdef_RotateClockwise>(), QKeySequence( "Shift+PgDown" ) );
	GlobalCommands_insert( "TexRotateCounter", FreeCaller<Texdef_RotateAntiClockwise>(), QKeySequence( "Shift+PgUp" ) );
	GlobalCommands_insert( "TexScaleUp", FreeCaller<Texdef_ScaleUp>(), QKeySequence( "Ctrl+Up" ) );
	GlobalCommands_insert( "TexScaleDown", FreeCaller<Texdef_ScaleDown>(), QKeySequence( "Ctrl+Down" ) );
	GlobalCommands_insert( "TexScaleLeft", FreeCaller<Texdef_ScaleLeft>(), QKeySequence( "Ctrl+Left" ) );
	GlobalCommands_insert( "TexScaleRight", FreeCaller<Texdef_ScaleRight>(), QKeySequence( "Ctrl+Right" ) );
	GlobalCommands_insert( "TexShiftUp", FreeCaller<Texdef_ShiftUp>(), QKeySequence( "Shift+Up" ) );
	GlobalCommands_insert( "TexShiftDown", FreeCaller<Texdef_ShiftDown>(), QKeySequence( "Shift+Down" ) );
	GlobalCommands_insert( "TexShiftLeft", FreeCaller<Texdef_ShiftLeft>(), QKeySequence( "Shift+Left" ) );
	GlobalCommands_insert( "TexShiftRight", FreeCaller<Texdef_ShiftRight>(), QKeySequence( "Shift+Right" ) );

	GlobalCommands_insert( "MoveSelectionDOWN", FreeCaller<Selection_MoveDown>(), QKeySequence( Qt::Key_Minus + Qt::KeypadModifier ) );
	GlobalCommands_insert( "MoveSelectionUP", FreeCaller<Selection_MoveUp>(), QKeySequence( Qt::Key_Plus + Qt::KeypadModifier ) );

	GlobalCommands_insert( "SelectNudgeLeft", FreeCaller<Selection_NudgeLeft>(), QKeySequence( "Alt+Left" ) );
	GlobalCommands_insert( "SelectNudgeRight", FreeCaller<Selection_NudgeRight>(), QKeySequence( "Alt+Right" ) );
	GlobalCommands_insert( "SelectNudgeUp", FreeCaller<Selection_NudgeUp>(), QKeySequence( "Alt+Up" ) );
	GlobalCommands_insert( "SelectNudgeDown", FreeCaller<Selection_NudgeDown>(), QKeySequence( "Alt+Down" ) );
}



void SceneSelectionChange( const Selectable& selectable ){
	SceneChangeNotify();
}

SignalHandlerId Selection_boundsChanged;

#include "preferencesystem.h"

void Nudge_constructPreferences( PreferencesPage& page ){
	page.appendCheckBox( "", "Nudge selected after duplication", g_bNudgeAfterClone );
}

void Selection_construct(){
	GlobalPreferenceSystem().registerPreference( "NudgeAfterClone", BoolImportStringCaller( g_bNudgeAfterClone ), BoolExportStringCaller( g_bNudgeAfterClone ) );

	PreferencesDialog_addSettingsPreferences( FreeCaller1<PreferencesPage&, Nudge_constructPreferences>() );

	GlobalSelectionSystem().addSelectionChangeCallback( FreeCaller1<const Selectable&, SceneSelectionChange>() );
	GlobalSelectionSystem().addSelectionChangeCallback( FreeCaller1<const Selectable&, UpdateWorkzone_ForSelectionChanged>() );
	Selection_boundsChanged = GlobalSceneGraph().addBoundsChangedCallback( FreeCaller<UpdateWorkzone_ForSelection>() );
}

void Selection_destroy(){
	GlobalSceneGraph().removeBoundsChangedCallback( Selection_boundsChanged );
}
