while (it != mPortals.end())
		{
			p = *it;
			if (p->getName() == portalName)
			{
				thePortal = p;
				// erase entry in the master list
				mPortals.erase(it);
				break;
			}
			it++;
		}

/*
-----------------------------------------------------------------------------
This source file is part of OGRE
(Object-oriented Graphics Rendering Engine)
For the latest info, see http://example.com

-----------------------------------------------------------------------------
PCZSceneManager.cpp  -  description
-----------------------------------------------------------------------------
begin                : Mon Feb 19 2007
author               : Eric Cha
email                : example@nnthink.com
-----------------------------------------------------------------------------
*/

#include "OgrePCZSceneManager.h"
#include "OgrePCZSceneQuery.h"
#include "OgrePCZone.h"
#include "OgrePCZCamera.h"
#include "OgrePCZLight.h"
#include "OgrePortal.h"
#include "OgreSceneNode.h"
#include "OgreMesh.h"
#include "OgreSubMesh.h"
#include "OgreLogManager.h"
#include <OgreRenderSystem.h>
#include "OgreRoot.h"

namespace Ogre
{
	PCZSceneManager::PCZSceneManager(const String& name) :
	SceneManager(name),
	mDefaultZoneTypeName("ZoneType_Default"),
	mDefaultZoneFileName("none"),
	mLastActiveCamera(0),
	mDefaultZone(0),
	mShowPortals(false),
	mZoneFactoryManager(0),
	mActiveCameraZone(0)
	{ }

    PCZSceneManager::~PCZSceneManager()
    {
        // we don't delete the root scene node here because the
        // base scene manager class does that.

		// delete ALL portals
		Portal * p;
		PortalList::iterator i = mPortals.begin();
		for (i = mPortals.begin(); i != mPortals.end(); i++)
		{
			p = *i;
			OGRE_DELETE p;
		}
		mPortals.clear();

		// delete all the zones
		for (ZoneMap::iterator j = mZones.begin();
			j != mZones.end(); ++j)
		{
			OGRE_DELETE j->second;
		}
		mZones.clear();
        mDefaultZone = 0;
    }

    const String& PCZSceneManager::getTypeName(void) const
    {
	    return PCZSceneManagerFactory::FACTORY_TYPE_NAME;
    }

    void PCZSceneManager::init( const String &defaultZoneTypeName,
								const String &filename)
    {

		// delete ALL portals
		Portal * p;
		PortalList::iterator i = mPortals.begin();
		for (i = mPortals.begin(); i != mPortals.end(); i++)
		{
			p = *i;
			OGRE_DELETE p;
		}
		mPortals.clear();

		// delete all the zones
		for (ZoneMap::iterator j = mZones.begin();
			j != mZones.end(); ++j)
		{
			OGRE_DELETE j->second;
		}
		mZones.clear();

        mFrameCount = 0;

		mDefaultZoneTypeName = defaultZoneTypeName;
		mDefaultZoneFileName = filename;

        // create a new default zone
		mZoneFactoryManager = PCZoneFactoryManager::getSingletonPtr();
		mDefaultZone = createZoneFromFile(mDefaultZoneTypeName, "Default_Zone", (PCZSceneNode*)getRootSceneNode(), mDefaultZoneFileName);
    }

	// Create a portal instance
	Portal* PCZSceneManager::createPortal(const String& name, PortalBase::PORTAL_TYPE type)
	{
		Portal* newPortal = OGRE_NEW Portal(name, type);
		newPortal->_notifyCreator(Root::getSingleton().getMovableObjectFactory("Portal"));
		newPortal->_notifyManager(this);
		mPortals.push_front(newPortal);
		return newPortal;
	}

	// delete a portal instance by pointer
	void PCZSceneManager::destroyPortal(Portal * p)
	{
		// remove the portal from it's target portal
		Portal * targetPortal = p->getTargetPortal();
		if (targetPortal)
		{
			targetPortal->setTargetPortal(0); // the targetPortal will still have targetZone value, but targetPortal will be invalid
		}
		// remove the Portal from it's home zone
		PCZone * homeZone = p->getCurrentHomeZone();
		if (homeZone)
		{
			// inform zone of portal change. Do here since PCZone is abstract
			homeZone->setPortalsUpdated(true);
			homeZone->_removePortal(p);
		}

		// remove the portal from the master portal list
		PortalList::iterator it = std::find( mPortals.begin(), mPortals.end(), p );
		if (it != mPortals.end())
		{
			mPortals.erase(it);
		}
		// delete the portal instance
		OGRE_DELETE p;
	}

	// delete a portal instance by pointer
	void PCZSceneManager::destroyPortal(const String & portalName)
	{
		// find the portal from the master portal list
		Portal * p;
		Portal * thePortal = 0;
		PortalList::iterator it = mPortals.begin();

		if (thePortal)
		{
			// remove the portal from it's target portal
			Portal * targetPortal = thePortal->getTargetPortal();
			if (targetPortal)
			{
				targetPortal->setTargetPortal(0); // the targetPortal will still have targetZone value, but targetPortal will be invalid
			}
			// remove the Portal from it's home zone
			PCZone * homeZone = thePortal->getCurrentHomeZone();
			if (homeZone)
			{
				// inform zone of portal change
				homeZone->setPortalsUpdated(true);
				homeZone->_removePortal(thePortal);
			}

			// delete the portal instance
			OGRE_DELETE thePortal;
		}
	}

	/** Create a new anti portal instance */
	AntiPortal* PCZSceneManager::createAntiPortal(const String& name, PortalBase::PORTAL_TYPE type)
	{
		AntiPortal* newAntiPortal = OGRE_NEW AntiPortal(name, type);
		newAntiPortal->_notifyCreator(Root::getSingleton().getMovableObjectFactory("AntiPortal"));
		newAntiPortal->_notifyManager(this);
		mAntiPortals.push_front(newAntiPortal);
		return newAntiPortal;
	}

	/** Delete a anti portal instance by pointer */
	void PCZSceneManager::destroyAntiPortal(AntiPortal * p)
	{
		// remove the Portal from it's home zone
		PCZone* homeZone = p->getCurrentHomeZone();
		if (homeZone)
		{
			// inform zone of portal change. Do here since PCZone is abstract
			homeZone->setPortalsUpdated(true);
			homeZone->_removeAntiPortal(p);
		}

		// remove the portal from the master portal list
		AntiPortalList::iterator it = std::find(mAntiPortals.begin(), mAntiPortals.end(), p);
		if (it != mAntiPortals.end()) mAntiPortals.erase(it);

		// delete the portal instance
		OGRE_DELETE p;
	}

	/** Delete a anti portal instance by name */
	void PCZSceneManager::destroyAntiPortal(const String& portalName)
	{
		// find the anti portal from the master portal list
		AntiPortal* p;
		AntiPortal* thePortal = 0;
		AntiPortalList::iterator it = mAntiPortals.begin();
		while (it != mAntiPortals.end())
		{
			p = *it;
			if (p->getName() == portalName)
			{
				thePortal = p;
				// erase entry in the master list
				mAntiPortals.erase(it);
				break;
			}
			it++;
		}
		if (thePortal)
		{
			// remove the Portal from it's home zone
			PCZone* homeZone = thePortal->getCurrentHomeZone();
			if (homeZone)
			{
				// inform zone of portal change
				homeZone->setPortalsUpdated(true);
				homeZone->_removeAntiPortal(thePortal);
			}

			// delete the portal instance
			OGRE_DELETE thePortal;
		}
	}

	/** Create a zone from a file (type of file
		* depends on the zone type
		* ZoneType_Default uses an Ogre Model (.mesh) file
		* ZoneType_Octree uses an Ogre Model (.mesh) file
		* ZoneType_Terrain uses a Terrain.CFG file
		*/
	PCZone * PCZSceneManager::createZoneFromFile(const String &zoneTypeName,
												 const String &zoneName,
												 PCZSceneNode * parentNode,
												 const String &filename)
	{
		PCZone * newZone;

        // create a new default zone
		newZone = mZoneFactoryManager->createPCZone(this, zoneTypeName, zoneName);
		// add to the global list of zones
		mZones[newZone->getName()] = newZone;
		if (filename != "none")
		{
			// set the zone geometry
			newZone->setZoneGeometry(filename, parentNode);
		}

		return newZone;
	}

	/* Get a zone by name */
	PCZone * PCZSceneManager::getZoneByName(const String & zoneName)
	{
		ZoneMap::iterator i;
		PCZone * zone;
		i = mZones.find(zoneName);
		if (i != mZones.end())
		{
			zone = i->second;
			return zone;
		}
		return 0; // couldn't find the zone
	}

	void PCZSceneManager::setZoneGeometry(const String & zoneName,
										  PCZSceneNode * parentNode,
										  const String & filename)
	{
		ZoneMap::iterator i;
		PCZone * zone;
		i = mZones.find(zoneName);
		if (i != mZones.end())
		{
			zone = i->second;
			zone->setZoneGeometry( filename, parentNode );
			return;
		}

	}

	SceneNode* PCZSceneManager::createSceneNodeImpl(void)
	{
		return OGRE_NEW PCZSceneNode(this);
	}

	SceneNode* PCZSceneManager::createSceneNodeImpl(const String& name)
	{
		return OGRE_NEW PCZSceneNode(this, name);
	}

    SceneNode * PCZSceneManager::createSceneNode( void )
    {
        SceneNode * on = createSceneNodeImpl();
        mSceneNodes[ on->getName() ] = on;
		// create any zone-specific data necessary
		createZoneSpecificNodeData((PCZSceneNode*)on);
		// return pointer to the node
        return on;
    }

    SceneNode * PCZSceneManager::createSceneNode( const String &name )
    {
        // Check name not used
        if (mSceneNodes.find(name) != mSceneNodes.end())
        {
            OGRE_EXCEPT(
                Exception::ERR_DUPLICATE_ITEM,
                "A scene node with the name " + name + " already exists",
                "PCZSceneManager::createSceneNode" );
        }
        SceneNode * on = createSceneNodeImpl( name );
        mSceneNodes[ on->getName() ] = on;
		// create any zone-specific data necessary
		createZoneSpecificNodeData((PCZSceneNode*)on);
		// return pointer to the node
        return on;
    }

    // Create a camera for the scene
    Camera * PCZSceneManager::createCamera( const String &name )
    {
		// Check name not used
		if (mCameras.find(name) != mCameras.end())
		{
			OGRE_EXCEPT(
				Exception::ERR_DUPLICATE_ITEM,
				"A camera with the name " + name + " already exists",
				"PCZSceneManager::createCamera" );
		}

        Camera * c = OGRE_NEW PCZCamera( name, this );
        mCameras.insert( CameraList::value_type( name, c ) );

	    // create visible bounds aab map entry
	    mCamVisibleObjectsMap[c] = VisibleObjectsBoundsInfo();

		// tell all the zones about the new camera
		ZoneMap::iterator i;
		PCZone * zone;
		for (i = mZones.begin(); i != mZones.end(); i++)
		{
			zone = i->second;
	        zone->notifyCameraCreated( c );
		}

        return c;
    }

    // Destroy a Scene Node by name.
    void PCZSceneManager::destroySceneNode( const String &name )
    {
        SceneNode * on = ( getSceneNode( name ) );

        if ( on != 0 )
		{
            // destroy the node
            destroySceneNode( on );
		}
    }

    // Destroy a scene node
    void PCZSceneManager::destroySceneNode(SceneNode* sn)
    {
        if ( sn != 0 )
		{
			// remove references to the node from zones
            removeSceneNode( sn );

            // destroy the node
			SceneManager::destroySceneNode( sn->getName() );
		}
    }

	void PCZSceneManager::clearScene(void)
	{
		destroyAllStaticGeometry();
		destroyAllMovableObjects();

		// Clear root node of all children
		getRootSceneNode()->removeAllChildren();
		getRootSceneNode()->detachAllObjects();

		// Delete all SceneNodes, except root that is
		for (SceneNodeList::iterator i = mSceneNodes.begin();
			i != mSceneNodes.end(); ++i)
		{
			OGRE_DELETE i->second;
		}
		mSceneNodes.clear();
		mAutoTrackingSceneNodes.clear();

		// delete all the zones
		for (ZoneMap::iterator j = mZones.begin();
			j != mZones.end(); ++j)
		{
			OGRE_DELETE j->second;
		}
		mZones.clear();
	    mDefaultZone = 0;

		// Clear animations
		destroyAllAnimations();

		// Remove sky nodes since they've been deleted
		mSkyBoxNode = mSkyPlaneNode = mSkyDomeNode = 0;
		mSkyBoxEnabled = mSkyPlaneEnabled = mSkyDomeEnabled = false;

		// Clear render queue, empty completely
		if (mRenderQueue)
			mRenderQueue->clear(true);

		// re-initialize
        init(mDefaultZoneTypeName, mDefaultZoneFileName);
	}

	/** Overridden from SceneManager */
	void PCZSceneManager::setWorldGeometryRenderQueue(uint8 qid)
	{
		// tell all the zones about the new WorldGeometryRenderQueue
		ZoneMap::iterator i;
		PCZone * zone;
		for (i = mZones.begin(); i != mZones.end(); i++)
		{
			zone = i->second;
	        zone->notifyWorldGeometryRenderQueue( qid );
		}
		// call the regular scene manager version
		SceneManager::setWorldGeometryRenderQueue(qid);

	}
	/** Overridden from SceneManager */
    void PCZSceneManager::_renderScene(Camera* cam, Viewport *vp, bool includeOverlays)
    {
		// notify all the zones that a scene render is starting
		ZoneMap::iterator i;
		PCZone * zone;
		for (i = mZones.begin(); i != mZones.end(); i++)
		{
			zone = i->second;
	        zone->notifyBeginRenderScene();
		}

		// do the regular _renderScene
        SceneManager::_renderScene(cam, vp, includeOverlays);
    }

	/* enable/disable sky rendering */
	void PCZSceneManager::enableSky(bool onoff)
	{
		if (mSkyBoxNode)
		{
			mSkyBoxEnabled = onoff;
		}
		else if (mSkyDomeNode)
		{
			mSkyDomeEnabled = onoff;
		}
		else if (mSkyPlaneNode)
		{
			mSkyPlaneEnabled = onoff;
		}
	}

	/* Set the zone which contains the sky node */
	void PCZSceneManager::setSkyZone(PCZone * zone)
	{
		if (zone == 0)
		{
			// if no zone specified, use default zone
			zone = mDefaultZone;
		}
		if (mSkyBoxNode)
		{
			((PCZSceneNode*)mSkyBoxNode)->setHomeZone(zone);
			((PCZSceneNode*)mSkyBoxNode)->anchorToHomeZone(zone);
			zone->setHasSky(true);
		}
		if (mSkyDomeNode)
		{
			((PCZSceneNode*)mSkyDomeNode)->setHomeZone(zone);
			((PCZSceneNode*)mSkyDomeNode)->anchorToHomeZone(zone);
			zone->setHasSky(true);
		}
		if (mSkyPlaneNode)
		{
			((PCZSceneNode*)mSkyPlaneNode)->setHomeZone(zone);
			((PCZSceneNode*)mSkyPlaneNode)->anchorToHomeZone(zone);
			zone->setHasSky(true);
		}

	}

	// THIS IS THE MAIN LOOP OF THE MANAGER
	/* _updateSceneGraph does several things now:
	   1) standard scene graph update (transform all nodes in the node tree)
	   2) update the spatial data for all zones (& portals in the zones)
	   3) Update the PCZSNMap entry for every scene node
	*/
	void PCZSceneManager::_updateSceneGraph( Camera * cam )
	{
		// First do the standard scene graph update
		SceneManager::_updateSceneGraph( cam );
		// check for portal zone-related changes (portals intersecting other portals)
		_updatePortalZoneData();
		// mark nodes dirty base on portals that changed.
		_dirtyNodeByMovingPortals();
		// update all scene nodes
		_updatePCZSceneNodes();
		// calculate zones affected by each light
		_calcZonesAffectedByLights(cam);
		// clear update flags at end so user triggered updated are
		// not cleared prematurely
		_clearAllZonesPortalUpdateFlag();
	}

	/** Update the zone data for every zone portal in the scene */

	void PCZSceneManager::_updatePortalZoneData(void)
	{
		PCZone * zone;
	    ZoneMap::iterator zit = mZones.begin();

	    while ( zit != mZones.end() )
	    {
		    zone = zit->second;
			// this callchecks for portal zone changes & applies zone data changes as necessary
			zone->updatePortalsZoneData();
			// proceed to next zone in the list
		    ++zit;
	    }
	}

	/** Mark nodes dirty for every zone with moving portal in the scene */
	void PCZSceneManager::_dirtyNodeByMovingPortals(void)
	{
		PCZone * zone;
		ZoneMap::iterator zit = mZones.begin();

		while ( zit != mZones.end() )
		{
			zone = zit->second;
			// this call mark nodes dirty base on moving portals
			zone->dirtyNodeByMovingPortals();
			// proceed to next zone in the list
			++zit;
		}
	}

	/* Update all PCZSceneNodes.
	*/
	void PCZSceneManager::_updatePCZSceneNodes(void)
	{
		SceneNodeList::iterator it = mSceneNodes.begin();
		PCZSceneNode * pczsn;

		while ( it != mSceneNodes.end() )
		{
			pczsn = (PCZSceneNode*)(it->second);
			if (pczsn->isMoved() && pczsn->isEnabled())
			{
				// Update a single entry
				_updatePCZSceneNode(pczsn);

				// reset moved state.
				pczsn->setMoved(false);
			}
			// proceed to next entry in the list
			++it;
		}
	}

    /*
    */
    void PCZSceneManager::_calcZonesAffectedByLights(Camera * cam)
    {
        MovableObjectCollection* lights =
            getMovableObjectCollection(PCZLightFactory::FACTORY_TYPE_NAME);
	    {
                OGRE_LOCK_MUTEX(lights->mutex);

		    MovableObjectIterator it(lights->map.begin(), lights->map.end());

		    while(it.hasMoreElements())
		    {
			    PCZLight* l = static_cast<PCZLight*>(it.getNext());
				if(l->getNeedsUpdate())
				{
					// only update if necessary
					l->updateZones(((PCZSceneNode*)(cam->getParentSceneNode()))->getHomeZone(), mFrameCount);
				}
				// clear update flag
				l->clearNeedsUpdate();
            }
        }
    }

    // Update all the zone info for a given node.  This function
	// makes sure the home zone of the node is correct, and references
	// to any zones it is visiting are added and a reference to the
	// node is added to the visitor lists of any zone it is visiting.
	void PCZSceneManager::_updatePCZSceneNode( PCZSceneNode * pczsn )
    {
	    // Skip if root Zone has been destroyed (shutdown conditions)
	    if (!mDefaultZone)
		    return;

		// Skip if the node is the sceneroot node
		if (pczsn == getRootSceneNode())
			return;

		// clear all references to visiting zones
		pczsn->clearNodeFromVisitedZones();

        // Find the current home zone of the node associated with the pczsn entry.
		_updateHomeZone( pczsn, false );

		/* The following function does the following:
		* 1) Check all portals in the home zone - if the node is touching the portal
		*    then add the node to the connected zone as a visitor
		* 2) Recurse into visited zones in case the node spans several zones
		*/
		// (recursively) check each portal of home zone to see if the node is touching
		if (pczsn->getHomeZone() &&
			pczsn->allowedToVisit() == true)
		{
			pczsn->getHomeZone()->_checkNodeAgainstPortals(pczsn, 0);
		}

		// update zone-specific data for the node for any zones that require it
		pczsn->updateZoneData();
    }

    /** Removes all references to the node from every zone in the scene.
    */
    void PCZSceneManager::removeSceneNode( SceneNode * sn )
    {
	    // Skip if mDefaultZone has been destroyed (shutdown conditions)
	    if (!mDefaultZone)
		    return;

		PCZSceneNode * pczsn = (PCZSceneNode*)sn;

		// clear all references to the node in visited zones
		pczsn->clearNodeFromVisitedZones();

        // tell the node it's not in a zone
        pczsn->setHomeZone(0);
    }

	// Set the home zone for a node
	void PCZSceneManager::addPCZSceneNode(PCZSceneNode * sn, PCZone * homeZone)
	{
		// set the home zone
		sn->setHomeZone(homeZone);
		// add the node
		homeZone->_addNode(sn);
	}

	/* Create a zone with the given name and parent zone */
	PCZone * PCZSceneManager::createZone(const String& zoneType, const String& instanceName)
	{
		if (mZones.find(instanceName) != mZones.end())
		{
			OGRE_EXCEPT(
				Exception::ERR_DUPLICATE_ITEM,
				"A zone with the name " + instanceName + " already exists",
				"PCZSceneManager::createZone" );
		}
		PCZone * newZone = mZoneFactoryManager->createPCZone(this, zoneType, instanceName);
		if (newZone)
		{
			// add to the global list of zones
			mZones[instanceName] = newZone;

            if (newZone->requiresZoneSpecificNodeData())
            {
                createZoneSpecificNodeData(newZone);
            }
		}
		return newZone;
	}

	/* destroy an existing zone within the scene */
	/* if destroySceneNodes is true, then all nodes which have the destroyed
	   zone as their homezone are desroyed too.  If destroySceneNodes is false
	   then all scene nodes which have the zone as their homezone will have
	   their homezone pointer set to 0, which will allow them to be re-assigned
	   either by the user or via the automatic re-assignment routine */
	void PCZSceneManager::destroyZone(PCZone* zone, bool destroySceneNodes)
	{
		// need to remove this zone from all lights affected zones list,
		// otherwise next frame _calcZonesAffectedByLights will call PCZLight::getNeedsUpdate()
		// which will try to access the zone pointer and will cause an access violation
		MovableObjectCollection* lights =
		getMovableObjectCollection(PCZLightFactory::FACTORY_TYPE_NAME);
		{
                    OGRE_LOCK_MUTEX(lights->mutex); // Is locking necessary in destroyZone? I don't know..

			MovableObjectIterator it(lights->map.begin(), lights->map.end());

			while(it.hasMoreElements())
			{
				PCZLight* l = static_cast<PCZLight*>(it.getNext());
				if(l)
				{
					// no need to check, this function does that anyway. if exists, is erased.
					l->removeZoneFromAffectedZonesList(zone);
				}
			}
		}
		// if not destroying scene nodes, then make sure any nodes who have
		// this zone as homezone are set to have 0 for a homezone
		for (SceneNodeList::iterator i = mSceneNodes.begin();
			i != mSceneNodes.end(); ++i)
		{
			PCZSceneNode * pczsn = (PCZSceneNode*)(i->second);
			if (!destroySceneNodes)
			{
				if (pczsn->getHomeZone() == zone)
				{
					pczsn->setHomeZone(0);
				}
			}
			// reset all node visitor lists
			// note, it might be more efficient to only do this to nodes which
			// are actually visiting the zone being destroyed, but visitor lists
			// get cleared every frame anyway, so it's not THAT big a deal.
			pczsn->clearNodeFromVisitedZones();
		}

		ZoneMap::iterator it;
		it = mZones.find(zone->getName());
		if (it != mZones.end())
		{
			mZones.erase(zone->getName());
		}
		OGRE_DELETE zone;
	}

    /* The following function checks if a node has left it's current home zone.
	* This is done by checking each portal in the zone.  If the node has crossed
	* the portal, then the current zone is no longer the home zone of the node.  The
	* function then recurses into the connected zones.  Once a zone is found where
	* the node does NOT cross out through a portal, that zone is the new home zone.
    * When this function is done, the node should have the correct home zone already
	* set.  A pointer is returned to this zone as well.
	*
	* NOTE: If the node does not have a home zone when this function is called on it,
	*       the function will do its best to find the proper zone for the node using
	*       bounding box volume testing.  This CAN fail to find the correct zone in
	*		some scenarios, so it is best for the user to EXPLICITLY set the home
	*		zone of the node when the node is added to the scene using
	*       PCZSceneNode::setHomeZone()
	*/
    void PCZSceneManager::_updateHomeZone( PCZSceneNode * pczsn, bool allowBackTouches )
    {
	    // Skip if root PCZoneTree has been destroyed (shutdown conditions)
	    if (!mDefaultZone)
		    return;

		PCZone * startzone;
		PCZone * newHomeZone;

		// start with current home zone of the node
		startzone = pczsn->getHomeZone();

		if (startzone)
		{
			if (!pczsn->isAnchored())
			{
				newHomeZone = startzone->updateNodeHomeZone(pczsn, false);
			}
			else
			{
				newHomeZone = startzone;
			}

			if (newHomeZone != startzone)
			{
				// add the node to the home zone
				newHomeZone->_addNode(pczsn);
			}
		}
		else
		{
			// the node hasn't had it's home zone set yet, so do our best to
			// find the home zone using volume testing.
			Vector3 nodeCenter = pczsn->_getDerivedPosition();
			PCZone * bestZone = findZoneForPoint(nodeCenter);
			// set the best zone as the node's home zone
			pczsn->setHomeZone(bestZone);
			// add the node to the zone
			bestZone->_addNode(pczsn);
		}

		return;

    }

	/* Find the best (smallest) zone that contains a point
	*/
	PCZone * PCZSceneManager::findZoneForPoint(Vector3 & point)
	{
		PCZone * zone;
		PCZone * bestZone = mDefaultZone;
		Real bestVolume = Ogre::Math::POS_INFINITY;

		ZoneMap::iterator zit = mZones.begin();

		while ( zit != mZones.end() )
		{
			zone = zit->second;
			AxisAlignedBox aabb;
			zone->getAABB(aabb);
			SceneNode * enclosureNode = zone->getEnclosureNode();
			if (enclosureNode != 0)
			{
				// since this is the "local" AABB, add in world translation of the enclosure node
				aabb.setMinimum(aabb.getMinimum() + enclosureNode->_getDerivedPosition());
				aabb.setMaximum(aabb.getMaximum() + enclosureNode->_getDerivedPosition());
			}
			if (aabb.contains(point))
			{
				if (aabb.volume() < bestVolume)
				{
					// this zone is "smaller" than the current best zone, so make it
					// the new best zone
					bestZone = zone;
					bestVolume = aabb.volume();
				}
			}
			// proceed to next zone in the list
			++zit;
		}
		return bestZone;
	}

	// create any zone-specific data necessary for all zones for the given node
	void PCZSceneManager::createZoneSpecificNodeData(PCZSceneNode * node)
	{
		ZoneMap::iterator i;
		PCZone * zone;
		for (i = mZones.begin(); i != mZones.end(); i++)
		{
			zone = i->second;
			if (zone->requiresZoneSpecificNodeData())
			{
				zone->createNodeZoneData(node);
			}
		}
	}

	// create any zone-specific data necessary for all nodes for the given zone
	void PCZSceneManager::createZoneSpecificNodeData(PCZone * zone)
	{
		SceneNodeList::iterator it = mSceneNodes.begin();
		PCZSceneNode * pczsn;
		if (zone->requiresZoneSpecificNodeData())
		{
			while ( it != mSceneNodes.end() )
			{
				pczsn = (PCZSceneNode*)(it->second);
				// create zone specific data for the node
				zone->createNodeZoneData(pczsn);
				// proceed to next entry in the list
				++it;
			}
		}
	}

	// set the home zone for a scene node
	void PCZSceneManager::setNodeHomeZone(SceneNode *node, PCZone *zone)
	{
		// cast the Ogre::SceneNode to a PCZSceneNode
		PCZSceneNode * pczsn = (PCZSceneNode*)node;
		pczsn->setHomeZone(zone);
	}

	// (optional) post processing for any scene node found visible for the frame
    void PCZSceneManager::_alertVisibleObjects( void )
    {
        OGRE_EXCEPT( Exception::ERR_NOT_IMPLEMENTED,
            "Function doesn't do as advertised",
            "PCZSceneManager::_alertVisibleObjects" );

//            // this is where you would do whatever you wanted to the visible node
//            // but right now, it does nothing.
    }

    Light* PCZSceneManager::createLight(const String& name)
    {
	    return static_cast<Light*>(
		    createMovableObject(name, PCZLightFactory::FACTORY_TYPE_NAME));
    }
	Light* PCZSceneManager::getLight(const String& name) const
	{
		return static_cast<Light*>(
			getMovableObject(name, PCZLightFactory::FACTORY_TYPE_NAME));
	}
	bool PCZSceneManager::hasLight(const String& name) const
	{
		return hasMovableObject(name, PCZLightFactory::FACTORY_TYPE_NAME);
	}
	void PCZSceneManager::destroyLight(const String& name)
	{
		destroyMovableObject(name, PCZLightFactory::FACTORY_TYPE_NAME);
	}
	void PCZSceneManager::destroyAllLights(void)
	{
		destroyAllMovableObjectsByType(PCZLightFactory::FACTORY_TYPE_NAME);
	}
	void PCZSceneManager::findLightsAffectingFrustum(const Camera* camera)
	{
		// Similar to the basic SceneManager, we iterate through
		// lights to see which ones affect the frustum.  However,
		// since we have camera & lights partitioned by zones,
		// we can check only those lights which either affect the
		// zone the camera is in, or affect zones which are visible to
		// the camera

		MovableObjectCollection* lights =
			getMovableObjectCollection(PCZLightFactory::FACTORY_TYPE_NAME);

		{
                    OGRE_LOCK_MUTEX(lights->mutex);

			// Pre-allocate memory
			mTestLightInfos.clear();
			mTestLightInfos.reserve(lights->map.size());

			MovableObjectIterator it(lights->map.begin(), lights->map.end());

			while(it.hasMoreElements())
			{
				PCZLight* l = static_cast<PCZLight*>(it.getNext());
				if (l->isVisible() &&
					l->affectsVisibleZone())
				{
					LightInfo lightInfo;
					lightInfo.light = l;
					lightInfo.type = l->getType();
					if (lightInfo.type == Light::LT_DIRECTIONAL)
					{
						// Always visible
						lightInfo.position = Vector3::ZERO;
						lightInfo.range = 0;
						mTestLightInfos.push_back(lightInfo);
					}
					else
					{
						// NB treating spotlight as point for simplicity
						// Just see if the lights attenuation range is within the frustum
						lightInfo.range = l->getAttenuationRange();
						lightInfo.position = l->getDerivedPosition();
						Sphere sphere(lightInfo.position, lightInfo.range);
						if (camera->isVisible(sphere))
						{
							mTestLightInfos.push_back(lightInfo);
						}
					}
				}
			}
		} // release lock on lights collection

		// from here on down this function is same as Ogre::SceneManager

		// Update lights affecting frustum if changed
		if (mCachedLightInfos != mTestLightInfos)
		{
			mLightsAffectingFrustum.resize(mTestLightInfos.size());
			LightInfoList::const_iterator i;
			LightList::iterator j = mLightsAffectingFrustum.begin();
			for (i = mTestLightInfos.begin(); i != mTestLightInfos.end(); ++i, ++j)
			{
				*j = i->light;
				// add cam distance for sorting if texture shadows
				if (isShadowTechniqueTextureBased())
				{
					(*j)->_calcTempSquareDist(camera->getDerivedPosition());
				}
			}

			// Sort the lights if using texture shadows, since the first 'n' will be
			// used to generate shadow textures and we should pick the most appropriate
			if (isShadowTechniqueTextureBased())
			{
				// Allow a ShadowListener to override light sorting
				// Reverse iterate so last takes precedence
				bool overridden = false;
				for (ListenerList::reverse_iterator ri = mListeners.rbegin();
					ri != mListeners.rend(); ++ri)
				{
					overridden = (*ri)->sortLightsAffectingFrustum(mLightsAffectingFrustum);
					if (overridden)
						break;
				}
				if (!overridden)
				{
					// default sort (stable to preserve directional light ordering
					std::stable_sort(
						mLightsAffectingFrustum.begin(), mLightsAffectingFrustum.end(),
						lightsForShadowTextureLess());
				}

			}

			// Use swap instead of copy operator for efficiently
			mCachedLightInfos.swap(mTestLightInfos);

			// notify light dirty, so all movable objects will re-populate
			// their light list next time
			_notifyLightsDirty();
		}

	}
	void PCZSceneManager::ensureShadowTexturesCreated()
	{
		bool shadowTextureConfigDirty = mShadowTextureConfigDirty;
		SceneManager::ensureShadowTexturesCreated();
		if (!shadowTextureConfigDirty) return;

		size_t count = mShadowTextureCameras.size();
		for (size_t i = 0; i < count; ++i)
		{
			PCZSceneNode* node = (PCZSceneNode*)mSceneRoot->createChildSceneNode(
				mShadowTextureCameras[i]->getName());
			node->attachObject(mShadowTextureCameras[i]);
			addPCZSceneNode(node, mDefaultZone);
		}
	}
	void PCZSceneManager::destroyShadowTextures(void)
	{
		size_t count = mShadowTextureCameras.size();
		for (size_t i = 0; i < count; ++i)
		{
			SceneNode* node = mShadowTextureCameras[i]->getParentSceneNode();
			mSceneRoot->removeAndDestroyChild(node->getName());
		}
		SceneManager::destroyShadowTextures();
	}
	void PCZSceneManager::fireShadowTexturesPreCaster(Light* light, Camera* camera, size_t iteration)
	{
		PCZSceneNode* camNode = (PCZSceneNode*)camera->getParentSceneNode();

		if (light->getType() == Light::LT_DIRECTIONAL)
		{
			if (camNode->getHomeZone() != mActiveCameraZone)
				addPCZSceneNode(camNode, mActiveCameraZone);
		}
		else
		{
			PCZSceneNode* lightNode = (PCZSceneNode*)light->getParentSceneNode();
			assert(lightNode);
			PCZone* lightZone = lightNode->getHomeZone();
			if (camNode->getHomeZone() != lightZone)
				addPCZSceneNode(camNode, lightZone);
		}

		SceneManager::fireShadowTexturesPreCaster(light, camera, iteration);
	}

	/* Attempt to automatically connect unconnected portals to proper target zones
		* by looking for matching portals in other zones which are at the same location
		*/
	void PCZSceneManager::connectPortalsToTargetZonesByLocation(void)
	{
		// go through every zone to find portals
		ZoneMap::iterator i, iend;
		PCZone* zone;
		iend = mZones.end();
		bool foundMatch;
		for (i = mZones.begin(); i != iend; i++)
		{
			zone = i->second;
			// go through all the portals in the zone
			Portal* portal;
			PortalList::iterator pi, piend;
			piend = zone->mPortals.end();
			for (pi = zone->mPortals.begin(); pi != piend; pi++)
			{
				portal = *pi;
				if (portal->getTargetZone() == 0)
				{
					// this is a portal without a connected zone - look for
					// a matching portal in another zone
					PCZone* zone2;
					ZoneMap::iterator j= mZones.begin();
					foundMatch = false;
					while (!foundMatch && j != mZones.end())
					{
						zone2 = j->second;
						if (zone2 != zone) // make sure we don't look in the same zone
						{
							Portal * portal2 = zone2->findMatchingPortal(portal);
							if (portal2)
							{
								// found a match!
								Ogre::LogManager::getSingletonPtr()->logMessage("Connecting portal "+portal->getName()+" to portal "+portal2->getName());
								foundMatch = true;
								portal->setTargetZone(zone2);
								portal->setTargetPortal(portal2);
								portal2->setTargetZone(zone);
								portal2->setTargetPortal(portal);
							}
						}
						j++;
					}
					if (foundMatch == false)
					{
						// error, didn't find a matching portal!
						OGRE_EXCEPT(Exception::ERR_ITEM_NOT_FOUND,
							"Could not find matching portal for portal " + portal->getName(),
							"PCZSceneManager::connectPortalsToTargetZonesByLocation");

					}
				}
			}
		}
	}

	// main visibility determination & render queue filling routine
	// over-ridden from base/default scene manager.  This is *the*
	// main call.
	void PCZSceneManager::_findVisibleObjects(Camera* cam,
											  VisibleObjectsBoundsInfo* visibleBounds,
											  bool onlyShadowCasters)
	{
		// clear the render queue
		getRenderQueue()->clear();

		// if we are re-rendering the scene again with the same camera, we can just use the cache.
		// this helps post processing compositors.
		unsigned long frameCount = Root::getSingleton().getNextFrameNumber();
		if (mLastActiveCamera == cam && mFrameCount == frameCount)
		{
			RenderQueue* queue = getRenderQueue();
			size_t count = mVisible.size();
			for (size_t i = 0; i < count; ++i)
			{
				((PCZSceneNode*)mVisible[i])->_addToRenderQueue(
					cam, queue, onlyShadowCasters, visibleBounds);
			}
			return;
		}

		// increment the visibility frame counter
		mFrameCount = frameCount;
		mLastActiveCamera = cam;

		// clear the list of visible nodes
		mVisible.clear();

		// turn off sky
		enableSky(false);

		// remove all extra culling planes
		((PCZCamera*)cam)->removeAllExtraCullingPlanes();

		// update the camera
		((PCZCamera*)cam)->update();

		// get the home zone of the camera
		PCZone* cameraHomeZone = ((PCZSceneNode*)(cam->getParentSceneNode()))->getHomeZone();

		// walk the zones, starting from the camera home zone,
		// adding all visible scene nodes to the mVisibles list
		cameraHomeZone->setLastVisibleFrame(mFrameCount);
		cameraHomeZone->findVisibleNodes((PCZCamera*)cam,
										  mVisible,
										  getRenderQueue(),
										  visibleBounds,
										  onlyShadowCasters,
										  mDisplayNodes,
										  mShowBoundingBoxes);
	}

    void PCZSceneManager::findNodesIn( const AxisAlignedBox &box,
                                       PCZSceneNodeList &list,
                                       PCZone * startZone,
                                       PCZSceneNode *exclude )
    {
        PortalList visitedPortals;
		if (startZone)
		{
			// start in startzone, and recurse through portals if necessary
			startZone->_findNodes(box, list, visitedPortals, true, true, exclude);
		}
		else
		{
			// no start zone specified, so check all zones
			ZoneMap::iterator i;
			PCZone * zone;
			for (i = mZones.begin(); i != mZones.end(); i++)
			{
				zone = i->second;
				zone->_findNodes( box, list, visitedPortals, false, false, exclude );
			}
		}
    }

    void PCZSceneManager::findNodesIn( const Sphere &sphere,
                                       PCZSceneNodeList &list,
                                       PCZone * startZone,
                                       PCZSceneNode *exclude  )
    {
        PortalList visitedPortals;
		if (startZone)
		{
			// start in startzone, and recurse through portals if necessary
			startZone->_findNodes(sphere, list, visitedPortals, true, true, exclude);
		}
		else
		{
			// no start zone specified, so check all zones
			ZoneMap::iterator i;
			PCZone * zone;
			for (i = mZones.begin(); i != mZones.end(); i++)
			{
				zone = i->second;
				zone->_findNodes( sphere, list, visitedPortals, false, false, exclude );
			}
		}
    }

    void PCZSceneManager::findNodesIn( const PlaneBoundedVolume &volume,
                                       PCZSceneNodeList &list,
                                       PCZone * startZone,
                                       PCZSceneNode *exclude )
    {
        PortalList visitedPortals;
		if (startZone)
		{
			// start in startzone, and recurse through portals if necessary
			startZone->_findNodes(volume, list, visitedPortals, true, true, exclude);
		}
		else
		{
			// no start zone specified, so check all zones
			ZoneMap::iterator i;
			PCZone * zone;
			for (i = mZones.begin(); i != mZones.end(); i++)
			{
				zone = i->second;
				zone->_findNodes( volume, list, visitedPortals, false, false, exclude );
			}
		}
    }

    void PCZSceneManager::findNodesIn( const Ray &r,
                                       PCZSceneNodeList &list,
                                       PCZone * startZone,
                                       PCZSceneNode *exclude  )
    {
        PortalList visitedPortals;
		if (startZone)
		{
			// start in startzone, and recurse through portals if necessary
			startZone->_findNodes(r, list, visitedPortals, true, true, exclude);
		}
		else
		{
			ZoneMap::iterator i;
			PCZone * zone;
			for (i = mZones.begin(); i != mZones.end(); i++)
			{
				zone = i->second;
				zone->_findNodes( r, list, visitedPortals, false, false, exclude );
			}
		}
    }

    // get the current value of a scene manager option
    bool PCZSceneManager::getOptionValues( const String & key, StringVector  &refValueList )
    {
        return SceneManager::getOptionValues( key, refValueList );
    }

    // get option keys (base along with PCZ-specific)
    bool PCZSceneManager::getOptionKeys( StringVector & refKeys )
    {
        SceneManager::getOptionKeys( refKeys );
        refKeys.push_back( "ShowBoundingBoxes" );
        refKeys.push_back( "ShowPortals" );

        return true;
    }

    bool PCZSceneManager::setOption( const String & key, const void * val )
    {
        if ( key == "ShowBoundingBoxes" )
        {
            mShowBoundingBoxes = * static_cast < const bool * > ( val );
            return true;
        }

        else if ( key == "ShowPortals" )
        {
            mShowPortals = * static_cast < const bool * > ( val );
            return true;
        }
		// send option to each zone
		ZoneMap::iterator i;
		PCZone * zone;
		for (i = mZones.begin(); i != mZones.end(); i++)
		{
			zone = i->second;
	         if (zone->setOption(key, val ) == true)
			 {
				 return true;
			 }
		}

		// try regular scenemanager option
        return SceneManager::setOption( key, val );

    }

    bool PCZSceneManager::getOption( const String & key, void *val )
    {
        if ( key == "ShowBoundingBoxes" )
        {

            * static_cast < bool * > ( val ) = mShowBoundingBoxes;
            return true;
        }
        if ( key == "ShowPortals" )
        {

            * static_cast < bool * > ( val ) = mShowPortals;
            return true;
        }
        return SceneManager::getOption( key, val );

    }

    AxisAlignedBoxSceneQuery*
    PCZSceneManager::createAABBQuery(const AxisAlignedBox& box, uint32 mask)
    {
        PCZAxisAlignedBoxSceneQuery* q = OGRE_NEW PCZAxisAlignedBoxSceneQuery(this);
        q->setBox(box);
        q->setQueryMask(mask);
        return q;
    }
    SphereSceneQuery*
    PCZSceneManager::createSphereQuery(const Sphere& sphere, uint32 mask)
    {
        PCZSphereSceneQuery* q = OGRE_NEW PCZSphereSceneQuery(this);
        q->setSphere(sphere);
        q->setQueryMask(mask);
        return q;
    }
    PlaneBoundedVolumeListSceneQuery*
    PCZSceneManager::createPlaneBoundedVolumeQuery(const PlaneBoundedVolumeList& volumes,
            uint32 mask)
    {
        PCZPlaneBoundedVolumeListSceneQuery* q = OGRE_NEW PCZPlaneBoundedVolumeListSceneQuery(this);
        q->setVolumes(volumes);
        q->setQueryMask(mask);
        return q;
    }

    RaySceneQuery*
    PCZSceneManager::createRayQuery(const Ray& ray, uint32 mask)
    {
        PCZRaySceneQuery* q = OGRE_NEW PCZRaySceneQuery(this);
        q->setRay(ray);
        q->setQueryMask(mask);
        return q;
    }
    IntersectionSceneQuery*
    PCZSceneManager::createIntersectionQuery(uint32 mask)
    {

        PCZIntersectionSceneQuery* q = OGRE_NEW PCZIntersectionSceneQuery(this);
        q->setQueryMask(mask);
        return q;
    }
	// clear portal update flag from all zones
	void PCZSceneManager::_clearAllZonesPortalUpdateFlag(void)
	{
		ZoneMap::iterator zoneIterator = mZones.begin();

		while ( zoneIterator != mZones.end() )
		{
			(zoneIterator->second)->setPortalsUpdated(false);
			zoneIterator++;
		}
	}
	void PCZSceneManager::prepareShadowTextures(Camera* cam, Viewport* vp, const LightList* lightList)
	{
		mActiveCameraZone = ((PCZSceneNode*)cam->getParentSceneNode())->getHomeZone();
		SceneManager::prepareShadowTextures(cam, vp);
	}

    const String PCZSceneManagerFactory::FACTORY_TYPE_NAME = "PCZSceneManager";
    void PCZSceneManagerFactory::initMetaData(void) const
    {
	    mMetaData.typeName = FACTORY_TYPE_NAME;
	    mMetaData.description = "Scene manager organising the scene using Portal Connected Zones.";
	    mMetaData.sceneTypeMask = 0xFFFF; // support all types
	    mMetaData.worldGeometrySupported = false;
    }
    SceneManager* PCZSceneManagerFactory::createInstance(
	    const String& instanceName)
    {
	    return OGRE_NEW PCZSceneManager(instanceName);
    }
    void PCZSceneManagerFactory::destroyInstance(SceneManager* instance)
    {
	    OGRE_DELETE instance;
    }

}


void ts::SpliceTimeDescriptor::deserializePayload(PSIBuffer& buf)
{
    identifier = buf.getUInt32();
    TAI_seconds = buf.getUInt48();
    TAI_ns = buf.getUInt32();
    UTC_offset = buf.getUInt16();
}

// TSDuck - The MPEG Transport Stream Toolkit
// All rights reserved.

#include "tsSpliceTimeDescriptor.h"
#include "tsDescriptor.h"
#include "tsTablesDisplay.h"
#include "tsPSIRepository.h"
#include "tsPSIBuffer.h"
#include "tsDuckContext.h"
#include "tsxmlElement.h"

#define MY_XML_NAME u"splice_time_descriptor"
#define MY_CLASS ts::SpliceTimeDescriptor
#define MY_DID ts::DID_SPLICE_TIME
#define MY_TID ts::TID_SCTE35_SIT
#define MY_STD ts::Standards::SCTE

TS_REGISTER_DESCRIPTOR(MY_CLASS, ts::EDID::TableSpecific(MY_DID, MY_TID), MY_XML_NAME, MY_CLASS::DisplayDescriptor);

// Constructors

ts::SpliceTimeDescriptor::SpliceTimeDescriptor() :
    AbstractDescriptor(MY_DID, MY_XML_NAME, MY_STD, 0),
    identifier(SPLICE_ID_CUEI),
    TAI_seconds(0),
    TAI_ns(0),
    UTC_offset(0)
{
}

void ts::SpliceTimeDescriptor::clearContent()
{
    identifier = SPLICE_ID_CUEI;
    TAI_seconds = 0;
    TAI_ns = 0;
    UTC_offset = 0;
}

ts::SpliceTimeDescriptor::SpliceTimeDescriptor(DuckContext& duck, const Descriptor& desc) :
    SpliceTimeDescriptor()
{
    deserialize(duck, desc);
}

// Serialization

void ts::SpliceTimeDescriptor::serializePayload(PSIBuffer& buf) const
{
    buf.putUInt32(identifier);
    buf.putUInt48(TAI_seconds);
    buf.putUInt32(TAI_ns);
    buf.putUInt16(UTC_offset);
}

// Deserialization

// Static method to display a descriptor.

void ts::SpliceTimeDescriptor::DisplayDescriptor(TablesDisplay& disp, PSIBuffer& buf, const UString& margin, DID did, TID tid, PDS pds)
{
    if (buf.canReadBytes(16)) {
        // Sometimes, the identifiers are made of ASCII characters. Try to display them.
        disp.displayIntAndASCII(u"Identifier: 0x%08X", buf, 4, margin);
        const uint64_t tai = buf.getUInt48();
        disp << margin << UString::Format(u"TAI: %'d seconds (%s)", {tai, Time::UnixTimeToUTC(uint32_t(tai)).format(Time::DATETIME)});
        disp << UString::Format(u" + %'d ns", {buf.getUInt32()});
        disp << UString::Format(u", UTC offset: %'d", {buf.getUInt16()}) << std::endl;
    }
}

// XML serialization

void ts::SpliceTimeDescriptor::buildXML(DuckContext& duck, xml::Element* root) const
{
    root->setIntAttribute(u"identifier", identifier, true);
    root->setIntAttribute(u"TAI_seconds", TAI_seconds, false);
    root->setIntAttribute(u"TAI_ns", TAI_ns, false);
    root->setIntAttribute(u"UTC_offset", UTC_offset, false);
}

// XML deserialization

bool ts::SpliceTimeDescriptor::analyzeXML(DuckContext& duck, const xml::Element* element)
{
    return element->getIntAttribute(identifier, u"identifier", false, SPLICE_ID_CUEI) &&
           element->getIntAttribute(TAI_seconds, u"TAI_seconds", true, 0, 0, TS_UCONST64(0x0000FFFFFFFFFFFF)) &&
           element->getIntAttribute(TAI_ns, u"TAI_ns", true) &&
           element->getIntAttribute(UTC_offset, u"UTC_offset", true);
}


switch (err) {
	case CR_CONN_HOST_ERROR:
	case CR_SERVER_GONE_ERROR:
	case CR_SERVER_LOST:
	case ER_SERVER_SHUTDOWN:
		return 1;
	default:
		return 0;
	}

/*
 *
 */

#include <stdlib.h>
#include <string.h>

#include "bc-db.h"

#ifdef BC_USE_MARIADB
#include <mariadb/mysql.h>
#include <mariadb/errmsg.h>
#include <mariadb/mysqld_error.h>
#else
#include <mysql/mysql.h>
#include <mysql/errmsg.h>
#include <mysql/mysqld_error.h>
#endif

struct bc_db_mysql_res {
	MYSQL_RES *res;
	MYSQL_ROW row;
	int ncols;
};

static char *dbname, *dbuser, *dbpass, *dbhost, *dbsock;
static int dbport = 0;
static MYSQL *my_con_global;

static inline void free_null(char **p)
{
	free(*p);
	*p = NULL;
}

#define GET_VAL(__res, __var, __ret) do {				\
	const char *val;						\
	free_null(&__res);						\
	if (!config_lookup_string(cfg, BC_CONFIG_DB "." __var, &val)) {	\
		__res = NULL;						\
		if (__ret) {						\
			bc_db_mysql_close();				\
                	return -1;					\
		}							\
	} else								\
		__res = strdup(val);					\
} while(0)

static MYSQL *reset_con(void)
{
	if (my_con_global)
		mysql_close(my_con_global);

	if (!dbname || !dbuser || !dbpass)
		return NULL;

	my_con_global = mysql_init(NULL);
	if (!my_con_global) {
		bc_log(Fatal, "MySQL initialization failed");
		return NULL;
	}

	/* NOTE: Retries multiply the specified timeouts */
	mysql_options(my_con_global, MYSQL_OPT_READ_TIMEOUT, (const int[]){5});
	mysql_options(my_con_global, MYSQL_OPT_WRITE_TIMEOUT, (const int[]){5});
	mysql_options(my_con_global, MYSQL_OPT_CONNECT_TIMEOUT, (const int[]){5});

	MYSQL *ret = mysql_real_connect(my_con_global, dbhost, dbuser, dbpass,
					dbname, dbport, dbsock, 0);
	if (!ret) {
		mysql_close(my_con_global);
		my_con_global = NULL;
		return NULL;
	}

	return my_con_global;
}

static MYSQL *get_handle(void)
{
	return my_con_global ? my_con_global : reset_con();
}

static void bc_db_mysql_close(void)
{
	free_null(&dbname);
	free_null(&dbuser);
	free_null(&dbpass);
	free_null(&dbhost);
	free_null(&dbsock);
	dbport = 0;

	if (my_con_global) {
		mysql_close(my_con_global);
		my_con_global = NULL;
	}
}

static int bc_db_mysql_open(struct config_t *cfg)
{
	if (get_handle() != NULL)
		return 0;

	GET_VAL(dbname, "dbname", 1);
	GET_VAL(dbuser, "user", 1);
	GET_VAL(dbpass, "password", 1);

	GET_VAL(dbhost, "host", 0);
	if (dbhost)
		config_lookup_int(cfg, BC_CONFIG_DB ".port", &dbport);
	else
		GET_VAL(dbsock, "socket", 0);

	if (get_handle() == NULL) {
		bc_db_mysql_close();
		return -1;
	}

	return 0;
}

static bool is_con_lost(MYSQL *con)
{
	int err = mysql_errno(con);

}

static int bc_db_mysql_query(const char *query)
{
	MYSQL *my_con = get_handle();
	int ret;

	if (my_con == NULL)
		return -1;

	unsigned int retries = 3;
	for (; (ret = mysql_query(my_con, query)) && retries; retries--) {
		if (!is_con_lost(my_con))
			break;

		/* reconnect to DBMS */
		my_con = reset_con();
		if (!my_con)
			break;
	}

	if (ret)
		bc_log(Error, "Query error: [%s] => %s", query,
		       mysql_error(my_con));

	return ret;
}

static BC_DB_RES bc_db_mysql_get_table(char *query)
{
	struct bc_db_mysql_res *dbres;

	dbres = (struct bc_db_mysql_res *)malloc(sizeof(*dbres));
	if (!dbres)
		goto error;

	if (bc_db_mysql_query(query))
		goto error;

	dbres->res = mysql_store_result(get_handle());
	if (!dbres->res) {
		bc_log(Error, "Query has no result: [%s]", query);
		goto error;
	}

	dbres->ncols = mysql_num_fields(dbres->res);
	return dbres;

error:
	free(dbres);
	return NULL;
}

static void bc_db_mysql_free_table(BC_DB_RES __dbres)
{
	struct bc_db_mysql_res *dbres = (struct bc_db_mysql_res*) __dbres;

	mysql_free_result(dbres->res);
	free(dbres);
}

static int bc_db_mysql_fetch_row(BC_DB_RES __dbres)
{
	struct bc_db_mysql_res *dbres = (struct bc_db_mysql_res*) __dbres;

	dbres->row = mysql_fetch_row(dbres->res);
	if (dbres->row == NULL) {
		mysql_data_seek(dbres->res, 0);
		return -1;
	}

	return 0;
}

static const char *bc_db_mysql_get_val(BC_DB_RES __dbres,
				       const char *colname,
				       size_t *length)
{
	struct bc_db_mysql_res *dbres = (struct bc_db_mysql_res*) __dbres;
	if (dbres->row == NULL)
		return NULL;

	unsigned long *lengths;
	if (length)
		lengths = mysql_fetch_lengths(dbres->res);

	for (int i = 0; i < dbres->ncols; i++) {
		MYSQL_FIELD *mf = mysql_fetch_field_direct(dbres->res, i);
		if (mf != NULL && strcmp(colname, mf->name) == 0) {
			if (length)
				*length = lengths[i];
			return dbres->row[i];
		}
	}

	return NULL;
}

static int bc_db_mysql_num_fields(BC_DB_RES __dbres)
{
	struct bc_db_mysql_res *dbres = (struct bc_db_mysql_res*) __dbres;

	return dbres->ncols;
}

static const char *bc_db_mysql_get_field(BC_DB_RES __dbres,
					 int nfield)
{
	struct bc_db_mysql_res *dbres = (struct bc_db_mysql_res*) __dbres;
	MYSQL_FIELD *fields = mysql_fetch_fields(dbres->res);

	if (!fields || nfield < 0 || nfield >= dbres->ncols)
		return NULL;

	return fields[nfield].name;
}

static unsigned long bc_db_mysql_last_insert_rowid(void)
{
	MYSQL *my_con = get_handle();

	if (my_con == NULL)
		return 0;

	return mysql_insert_id(my_con);
}

static void bc_db_mysql_escape_string(char *to, const char *from, size_t len)
{
	MYSQL *my_con = get_handle();

	if (my_con == NULL)
		mysql_escape_string(to, from, len);
	else
		mysql_real_escape_string(my_con, to, from, len);
}

static int bc_db_mysql_start_trans(void)
{
	return bc_db_mysql_query("START TRANSACTION");
}

static int bc_db_mysql_commit_trans(void)
{
	return bc_db_mysql_query("COMMIT");
}

static int bc_db_mysql_rollback_trans(void)
{
	return bc_db_mysql_query("ROLLBACK");
}

struct bc_db_ops bc_db_mysql = {
	bc_db_mysql_open,
	bc_db_mysql_close,
	bc_db_mysql_get_table,
	bc_db_mysql_free_table,
	bc_db_mysql_fetch_row,
	bc_db_mysql_get_val,
	bc_db_mysql_get_field,
	bc_db_mysql_num_fields,
	bc_db_mysql_query,
	bc_db_mysql_last_insert_rowid,
	bc_db_mysql_escape_string,
	bc_db_mysql_start_trans,
	bc_db_mysql_commit_trans,
	bc_db_mysql_rollback_trans,
};


void MirrorSetupWindow::RemoveCurrentSelectedMapping()
    {
        QList<QTableWidgetItem*> items = mCurrentList->selectedItems();
        if (items.count() > 0)
        {
            QTableWidgetItem* item = mCurrentList->item(items[0]->row(), 0);
            if (item)
            {
                OnCurrentListDoubleClicked(item);
            }
        }
    }

/*
* its licensors.
*
*
*/

#include "MirrorSetupWindow.h"
#include <AzCore/Casting/numeric_cast.h>
#include <AzQtComponents/Components/FilteredSearchWidget.h>
#include <QVBoxLayout>
#include <QHBoxLayout>
#include <QKeyEvent>
#include <QPushButton>
#include <QListWidget>
#include <QTableWidget>
#include <QLabel>
#include <QSplitter>
#include <QLineEdit>
#include <QHeaderView>
#include <QMessageBox>
#include <QTableWidgetItem>
#include <EMotionFX/Source/Actor.h>
#include <EMotionFX/Source/NodeMap.h>
#include <EMotionFX/Source/Pose.h>
#include <MCore/Source/LogManager.h>
#include <EMotionFX/CommandSystem/Source/SelectionList.h>
#include <EMotionFX/CommandSystem/Source/CommandManager.h>
#include <MysticQt/Source/MysticQtManager.h>
#include "SceneManagerPlugin.h"
#include <EMotionStudio/EMStudioSDK/Source/NotificationWindow.h>
#include <EMotionStudio/EMStudioSDK/Source/EMStudioManager.h>
#include <EMotionStudio/EMStudioSDK/Source/MainWindow.h>
#include <EMotionStudio/EMStudioSDK/Source/FileManager.h>

namespace EMStudio
{
    MirrorSetupWindow::MirrorSetupWindow(QWidget* parent, SceneManagerPlugin* plugin)
        : QDialog(parent)
    {
        mPlugin = plugin;

        // set the window title
        setWindowTitle("Mirror Setup");

        // set the minimum size
        setMinimumWidth(800);
        setMinimumHeight(600);

        // load some icons
        const AZStd::string dataDir = MysticQt::GetDataDir().c_str();
        mBoneIcon   = new QIcon(AZStd::string(dataDir + "Images/Icons/Bone.svg").c_str());
        mNodeIcon   = new QIcon(AZStd::string(dataDir + "Images/Icons/Node.svg").c_str());
        mMeshIcon   = new QIcon(AZStd::string(dataDir + "Images/Icons/Mesh.svg").c_str());
        mMappedIcon = new QIcon(AZStd::string(dataDir + "Images/Icons/Confirm.svg").c_str());

        // create the main layout
        QVBoxLayout* mainLayout = new QVBoxLayout();
        mainLayout->setMargin(3);
        mainLayout->setSpacing(1);
        setLayout(mainLayout);

        // create the layout that contains the two listboxes next to each other, and in the middle the link button
        QHBoxLayout* topPartLayout = new QHBoxLayout();
        topPartLayout->setMargin(0);

        QHBoxLayout* toolBarLayout = new QHBoxLayout();
        toolBarLayout->setMargin(0);
        toolBarLayout->setSpacing(0);
        mainLayout->addLayout(toolBarLayout);
        mButtonOpen = new QPushButton();
        EMStudioManager::MakeTransparentButton(mButtonOpen,    "/Images/Icons/Open.svg",       "Load and apply a mapping template.");
        connect(mButtonOpen, &QPushButton::clicked, this, &MirrorSetupWindow::OnLoadMapping);
        mButtonSave = new QPushButton();
        EMStudioManager::MakeTransparentButton(mButtonSave,    "/Images/Menu/FileSave.svg",    "Save the currently setup mapping as template.");
        connect(mButtonSave, &QPushButton::clicked, this, &MirrorSetupWindow::OnSaveMapping);
        mButtonClear = new QPushButton();
        EMStudioManager::MakeTransparentButton(mButtonClear,   "/Images/Icons/Clear.svg",      "Clear the currently setup mapping entirely.");
        connect(mButtonClear, &QPushButton::clicked, this, &MirrorSetupWindow::OnClearMapping);

        mButtonGuess = new QPushButton();
        EMStudioManager::MakeTransparentButton(mButtonGuess,   "/Images/Icons/Character.svg",  "Perform name based mapping.");
        connect(mButtonGuess, &QPushButton::clicked, this, &MirrorSetupWindow::OnBestGuess);

        toolBarLayout->addWidget(mButtonOpen, 0, Qt::AlignLeft);
        toolBarLayout->addWidget(mButtonSave, 0, Qt::AlignLeft);
        toolBarLayout->addWidget(mButtonClear, 0, Qt::AlignLeft);

        toolBarLayout->addSpacerItem(new QSpacerItem(100, 1, QSizePolicy::Expanding, QSizePolicy::Minimum));

        QHBoxLayout* leftRightLayout = new QHBoxLayout();
        leftRightLayout->addWidget(new QLabel("Left:"), 0, Qt::AlignRight);
        mLeftEdit = new QLineEdit("Bip01 L");
        mLeftEdit->setMaximumWidth(75);
        leftRightLayout->addWidget(mLeftEdit, 0, Qt::AlignRight);
        mRightEdit = new QLineEdit("Bip01 R");
        mRightEdit->setMaximumWidth(75);
        leftRightLayout->addWidget(new QLabel("Right:"), 0, Qt::AlignRight);
        leftRightLayout->addWidget(mRightEdit, 0, Qt::AlignRight);
        leftRightLayout->addWidget(mButtonGuess, 0, Qt::AlignRight);
        leftRightLayout->setSpacing(6);
        leftRightLayout->setMargin(0);

        toolBarLayout->addLayout(leftRightLayout);
        /*  QWidget* spacerWidget = new QWidget();
            spacerWidget->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Minimum);
            toolBarLayout->addWidget(spacerWidget);
        */
        // create the two widgets used to create a splitter in between
        QWidget* upperWidget = new QWidget();
        QWidget* lowerWidget = new QWidget();
        QSplitter* splitter = new QSplitter(Qt::Vertical);
        splitter->addWidget(upperWidget);
        splitter->addWidget(lowerWidget);
        mainLayout->addWidget(splitter);
        upperWidget->setLayout(topPartLayout);

        // left listbox part
        QVBoxLayout* leftListLayout = new QVBoxLayout();
        leftListLayout->setMargin(0);
        leftListLayout->setSpacing(1);
        topPartLayout->addLayout(leftListLayout);

        // add the search filter button
        QHBoxLayout* curSearchLayout = new QHBoxLayout();
        leftListLayout->addLayout(curSearchLayout);
        QLabel* curLabel = new QLabel("Left Nodes");
        curLabel->setTextFormat(Qt::RichText);
        curSearchLayout->addWidget(curLabel);
        QWidget* spacerWidget = new QWidget();
        spacerWidget->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Fixed);
        curSearchLayout->addWidget(spacerWidget);
        m_searchWidgetCurrent = new AzQtComponents::FilteredSearchWidget(this);
        connect(m_searchWidgetCurrent, &AzQtComponents::FilteredSearchWidget::TextFilterChanged, this, &MirrorSetupWindow::OnCurrentTextFilterChanged);
        curSearchLayout->addWidget(m_searchWidgetCurrent);
        curSearchLayout->setSpacing(6);
        curSearchLayout->setMargin(0);

        mCurrentList = new QTableWidget();
        mCurrentList->setAlternatingRowColors(true);
        mCurrentList->setGridStyle(Qt::SolidLine);
        mCurrentList->setSelectionBehavior(QAbstractItemView::SelectRows);
        mCurrentList->setSelectionMode(QAbstractItemView::SingleSelection);
        mCurrentList->setCornerButtonEnabled(false);
        mCurrentList->setEditTriggers(QAbstractItemView::NoEditTriggers);
        mCurrentList->setContextMenuPolicy(Qt::DefaultContextMenu);
        mCurrentList->setColumnCount(3);
        mCurrentList->setColumnWidth(0, 20);
        mCurrentList->setColumnWidth(1, 20);
        mCurrentList->setSortingEnabled(true);
        QHeaderView* verticalHeader = mCurrentList->verticalHeader();
        verticalHeader->setVisible(false);
        QTableWidgetItem* headerItem = new QTableWidgetItem("");
        headerItem->setTextAlignment(Qt::AlignVCenter | Qt::AlignLeft);
        mCurrentList->setHorizontalHeaderItem(0, headerItem);
        headerItem = new QTableWidgetItem("");
        headerItem->setTextAlignment(Qt::AlignVCenter | Qt::AlignLeft);
        mCurrentList->setHorizontalHeaderItem(1, headerItem);
        headerItem = new QTableWidgetItem("Name");
        headerItem->setTextAlignment(Qt::AlignVCenter | Qt::AlignLeft);
        mCurrentList->setHorizontalHeaderItem(2, headerItem);
        mCurrentList->horizontalHeader()->setStretchLastSection(true);
        mCurrentList->horizontalHeader()->setSortIndicatorShown(false);
        mCurrentList->horizontalHeader()->setSectionsClickable(false);
        connect(mCurrentList, &QTableWidget::itemSelectionChanged, this, &MirrorSetupWindow::OnCurrentListSelectionChanged);
        connect(mCurrentList, &QTableWidget::itemDoubleClicked, this, &MirrorSetupWindow::OnCurrentListDoubleClicked);
        leftListLayout->addWidget(mCurrentList);

        // add link button middle part
        QVBoxLayout* middleLayout = new QVBoxLayout();
        middleLayout->setMargin(0);
        topPartLayout->addLayout(middleLayout);
        QPushButton* linkButton = new QPushButton("link");
        connect(linkButton, &QPushButton::clicked, this, &MirrorSetupWindow::OnLinkPressed);
        middleLayout->addWidget(linkButton);

        // right listbox part
        QVBoxLayout* rightListLayout = new QVBoxLayout();
        rightListLayout->setMargin(0);
        rightListLayout->setSpacing(1);
        topPartLayout->addLayout(rightListLayout);

        // add the search filter button
        QHBoxLayout* sourceSearchLayout = new QHBoxLayout();
        rightListLayout->addLayout(sourceSearchLayout);
        QLabel* sourceLabel = new QLabel("Right Nodes");
        sourceLabel->setTextFormat(Qt::RichText);
        sourceSearchLayout->addWidget(sourceLabel);
        //EMStudioManager::MakeTransparentButton( loadSourceButton, "/Images/Icons/Open.svg",   "Load a source actor" );
        spacerWidget = new QWidget();
        spacerWidget->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Fixed);
        sourceSearchLayout->addWidget(spacerWidget);
        m_searchWidgetSource = new AzQtComponents::FilteredSearchWidget(this);
        connect(m_searchWidgetSource, &AzQtComponents::FilteredSearchWidget::TextFilterChanged, this, &MirrorSetupWindow::OnSourceTextFilterChanged);
        sourceSearchLayout->addWidget(m_searchWidgetSource);
        sourceSearchLayout->setSpacing(6);
        sourceSearchLayout->setMargin(0);

        mSourceList = new QTableWidget();
        mSourceList->setAlternatingRowColors(true);
        mSourceList->setGridStyle(Qt::SolidLine);
        mSourceList->setSelectionBehavior(QAbstractItemView::SelectRows);
        mSourceList->setSelectionMode(QAbstractItemView::SingleSelection);
        mSourceList->setCornerButtonEnabled(false);
        mSourceList->setEditTriggers(QAbstractItemView::NoEditTriggers);
        mSourceList->setContextMenuPolicy(Qt::DefaultContextMenu);
        mSourceList->setColumnCount(3);
        mSourceList->setColumnWidth(0, 20);
        mSourceList->setColumnWidth(1, 20);
        mSourceList->setSortingEnabled(true);
        verticalHeader = mSourceList->verticalHeader();
        verticalHeader->setVisible(false);
        headerItem = new QTableWidgetItem("");
        headerItem->setTextAlignment(Qt::AlignVCenter | Qt::AlignLeft);
        mSourceList->setHorizontalHeaderItem(0, headerItem);
        headerItem = new QTableWidgetItem("");
        headerItem->setTextAlignment(Qt::AlignVCenter | Qt::AlignLeft);
        mSourceList->setHorizontalHeaderItem(1, headerItem);
        headerItem = new QTableWidgetItem("Name");
        headerItem->setTextAlignment(Qt::AlignVCenter | Qt::AlignLeft);
        mSourceList->setHorizontalHeaderItem(2, headerItem);
        mSourceList->horizontalHeader()->setStretchLastSection(true);
        mSourceList->horizontalHeader()->setSortIndicatorShown(false);
        mSourceList->horizontalHeader()->setSectionsClickable(false);
        connect(mSourceList, &QTableWidget::itemSelectionChanged, this, &MirrorSetupWindow::OnSourceListSelectionChanged);
        rightListLayout->addWidget(mSourceList);

        // create the mapping table
        QVBoxLayout* lowerLayout = new QVBoxLayout();
        lowerLayout->setMargin(0);
        lowerLayout->setSpacing(3);
        lowerWidget->setLayout(lowerLayout);

        QHBoxLayout* mappingLayout = new QHBoxLayout();
        mappingLayout->setMargin(0);
        lowerLayout->addLayout(mappingLayout);
        mappingLayout->addWidget(new QLabel("Mapping:"), 0, Qt::AlignLeft | Qt::AlignVCenter);
        //EMStudioManager::MakeTransparentButton( mButtonGuess, "/Images/Icons/Character.svg",  "Best guess mapping" );
        spacerWidget = new QWidget();
        spacerWidget->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Minimum);
        mappingLayout->addWidget(spacerWidget);

        mMappingTable = new QTableWidget();
        lowerLayout->addWidget(mMappingTable);
        mMappingTable->setAlternatingRowColors(true);
        mMappingTable->setGridStyle(Qt::SolidLine);
        mMappingTable->setSelectionBehavior(QAbstractItemView::SelectRows);
        mMappingTable->setSelectionMode(QAbstractItemView::SingleSelection);
        mMappingTable->setCornerButtonEnabled(false);
        mMappingTable->setEditTriggers(QAbstractItemView::NoEditTriggers);
        mMappingTable->setContextMenuPolicy(Qt::DefaultContextMenu);
        mMappingTable->setContentsMargins(3, 1, 3, 1);
        mMappingTable->setColumnCount(2);
        mMappingTable->setColumnWidth(0, mMappingTable->width() / 2);
        mMappingTable->setColumnWidth(1, mMappingTable->width() / 2);
        verticalHeader = mMappingTable->verticalHeader();
        verticalHeader->setVisible(false);

        // add the table headers
        headerItem = new QTableWidgetItem("Node");
        headerItem->setTextAlignment(Qt::AlignVCenter | Qt::AlignLeft);
        mMappingTable->setHorizontalHeaderItem(0, headerItem);
        headerItem = new QTableWidgetItem("Mapped to");
        headerItem->setTextAlignment(Qt::AlignVCenter | Qt::AlignLeft);
        mMappingTable->setHorizontalHeaderItem(1, headerItem);
        mMappingTable->horizontalHeader()->setStretchLastSection(true);
        mMappingTable->horizontalHeader()->setSortIndicatorShown(false);
        mMappingTable->horizontalHeader()->setSectionsClickable(false);
        connect(mMappingTable, &QTableWidget::itemDoubleClicked, this, &MirrorSetupWindow::OnMappingTableDoubleClicked);
        connect(mMappingTable, &QTableWidget::itemSelectionChanged, this, &MirrorSetupWindow::OnMappingTableSelectionChanged);
    }

    // destructor
    MirrorSetupWindow::~MirrorSetupWindow()
    {
        delete mBoneIcon;
        delete mNodeIcon;
        delete mMeshIcon;
        delete mMappedIcon;
    }

    // double clicked on an item in the table
    void MirrorSetupWindow::OnMappingTableDoubleClicked(QTableWidgetItem* item)
    {
        MCORE_UNUSED(item);
        // TODO: open a node hierarchy widget, where we can select a node from the mSourceActor
        // the problem is that the node hierarchy widget works with actor instances, which we don't have and do not really want to create either
        // I think the node hierarchy widget shouldn't use actor instances only, but should support actors as well
    }

    // when we double click on an item in the current node list
    void MirrorSetupWindow::OnCurrentListDoubleClicked(QTableWidgetItem* item)
    {
        // get the currently selected actor
        CommandSystem::SelectionList& selection = CommandSystem::GetCommandManager()->GetCurrentSelection();
        EMotionFX::Actor* currentActor = selection.GetSingleActor();
        if (!currentActor)
        {
            return;
        }

        if (item->column() != 0)
        {
            return;
        }

        // get the node name
        const uint32 rowIndex = item->row();
        const AZStd::string nodeName = mCurrentList->item(rowIndex, 2)->text().toUtf8().data();

        // find its index in the current actor, and remove its mapping
        EMotionFX::Node* node = currentActor->GetSkeleton()->FindNodeByName(nodeName.c_str());
        MCORE_ASSERT(node);

        // remove the mapping for this node
        PerformMapping(node->GetNodeIndex(), MCORE_INVALIDINDEX32);
    }

    // current list selection changed
    void MirrorSetupWindow::OnCurrentListSelectionChanged()
    {
        QList<QTableWidgetItem*> items = mCurrentList->selectedItems();
        if (items.count() > 0)
        {
            QTableWidgetItem* nameItem = mCurrentList->item(items[0]->row(), 2);
            QList<QTableWidgetItem*> mappingTableItems = mMappingTable->findItems(nameItem->text(), Qt::MatchExactly);

            for (int32 i = 0; i < mappingTableItems.count(); ++i)
            {
                if (mappingTableItems[i]->column() != 0)
                {
                    continue;
                }

                const uint32 rowIndex = mappingTableItems[i]->row();

                mMappingTable->selectRow(rowIndex);
                mMappingTable->setCurrentItem(mappingTableItems[i]);
            }
        }
    }

    // source list selection changed
    void MirrorSetupWindow::OnSourceListSelectionChanged()
    {
        QList<QTableWidgetItem*> items = mSourceList->selectedItems();
        if (items.count() > 0)
        {
            QTableWidgetItem* nameItem = mSourceList->item(items[0]->row(), 2);
            QList<QTableWidgetItem*> mappingTableItems = mMappingTable->findItems(nameItem->text(), Qt::MatchExactly);

            for (int32 i = 0; i < mappingTableItems.count(); ++i)
            {
                if (mappingTableItems[i]->column() != 1)
                {
                    continue;
                }

                const uint32 rowIndex = mappingTableItems[i]->row();

                mMappingTable->selectRow(rowIndex);
                mMappingTable->setCurrentItem(mappingTableItems[i]);
            }
        }
    }

    // selection changed in the table
    void MirrorSetupWindow::OnMappingTableSelectionChanged()
    {
        // select both items in the list widgets as well
        QList<QTableWidgetItem*> items = mMappingTable->selectedItems();
        if (items.count() > 0)
        {
            const uint32 rowIndex = items[0]->row();

            QTableWidgetItem* item = mMappingTable->item(rowIndex, 0);
            if (item)
            {
                QList<QTableWidgetItem*> listItems = mCurrentList->findItems(item->text(), Qt::MatchExactly);
                if (listItems.count() > 0)
                {
                    mCurrentList->selectRow(listItems[0]->row());
                    mCurrentList->setCurrentItem(listItems[0]);
                }
            }

            item = mMappingTable->item(rowIndex, 1);
            if (item)
            {
                QList<QTableWidgetItem*> listItems = mSourceList->findItems(item->text(), Qt::MatchExactly);
                if (listItems.count() > 0)
                {
                    mSourceList->selectRow(listItems[0]->row());
                    mSourceList->setCurrentItem(listItems[0]);
                }
            }
        }
    }

    // reinitialize
    void MirrorSetupWindow::Reinit(bool reInitMap)
    {
        // clear the filter strings
        m_searchWidgetCurrent->ClearTextFilter();
        m_searchWidgetSource->ClearTextFilter();

        // get the currently selected actor
        EMotionFX::Actor* currentActor = GetSelectedActor();

        // extract the list of bones
        if (currentActor)
        {
            currentActor->ExtractBoneList(0, &mCurrentBoneList);
        }

        // clear the node map
        if (reInitMap)
        {
            mMap.clear();
            if (currentActor)
            {
                const size_t numNodes = aznumeric_caster(currentActor->GetNumNodes());
                mMap.resize(numNodes);
                for (uint32 i = 0; i < numNodes; ++i)
                {
                    mMap[i] = MCORE_INVALIDINDEX32;
                }
            }
        }

        // fill the contents
        InitMappingTableFromMotionSources(currentActor);
        FillCurrentListWidget(currentActor, "");
        FillSourceListWidget(currentActor, "");
        FillMappingTable(currentActor, currentActor);

        // enable or disable the filter fields
        m_searchWidgetCurrent->setEnabled((currentActor));
        m_searchWidgetSource->setEnabled((currentActor));

        UpdateToolBar();
    }

    // current actor filter change
    void MirrorSetupWindow::OnCurrentTextFilterChanged(const QString& text)
    {
        FillCurrentListWidget(GetSelectedActor(), text);
    }

    // source actor filter change
    void MirrorSetupWindow::OnSourceTextFilterChanged(const QString& text)
    {
        FillSourceListWidget(GetSelectedActor(), text);
    }

    // fill the current list widget
    void MirrorSetupWindow::FillCurrentListWidget(EMotionFX::Actor* actor, const QString& filterString)
    {
        if (!actor)
        {
            mCurrentList->setRowCount(0);
            return;
        }

        // fill the left list widget
        QString currentName;
        const uint32 numNodes = actor->GetNumNodes();

        // count the number of rows
        uint32 numRows = 0;
        for (uint32 i = 0; i < numNodes; ++i)
        {
            currentName = actor->GetSkeleton()->GetNode(i)->GetName();
            if (currentName.contains(filterString, Qt::CaseInsensitive) || filterString.isEmpty())
            {
                numRows++;
            }
        }
        mCurrentList->setRowCount(numRows);

        // fill the rows
        uint32 rowIndex = 0;
        for (uint32 i = 0; i < numNodes; ++i)
        {
            EMotionFX::Node* node = actor->GetSkeleton()->GetNode(i);
            currentName = node->GetName();
            if (currentName.contains(filterString, Qt::CaseInsensitive) || filterString.isEmpty())
            {
                // mark if there is a mapping or not
                const bool mapped = (mMap[node->GetNodeIndex()] != MCORE_INVALIDINDEX32);
                QTableWidgetItem* mappedItem = new QTableWidgetItem();
                mappedItem->setIcon(mapped ? *mMappedIcon : QIcon());
                mCurrentList->setItem(rowIndex, 0, mappedItem);

                // pick the right icon for the type column
                QTableWidgetItem* typeItem = new QTableWidgetItem();
                if (actor->GetMesh(0, node->GetNodeIndex()))
                {
                    typeItem->setIcon(*mMeshIcon);
                }
                else
                if (mCurrentBoneList.Contains(node->GetNodeIndex()))
                {
                    typeItem->setIcon(*mBoneIcon);
                }
                else
                {
                    typeItem->setIcon(*mNodeIcon);
                }

                mCurrentList->setItem(rowIndex, 1, typeItem);

                // set the name
                QTableWidgetItem* currentTableItem = new QTableWidgetItem(currentName);
                mCurrentList->setItem(rowIndex, 2, currentTableItem);

                // set the row height and add one index
                mCurrentList->setRowHeight(rowIndex, 21);
                ++rowIndex;
            }
        }
    }

    // fill the source list widget
    void MirrorSetupWindow::FillSourceListWidget(EMotionFX::Actor* actor, const QString& filterString)
    {
        if (!actor)
        {
            mSourceList->setRowCount(0);
            return;
        }

        // fill the left list widget
        QString name;
        const uint32 numNodes = actor->GetNumNodes();

        // count the number of rows
        uint32 numRows = 0;
        for (uint32 i = 0; i < numNodes; ++i)
        {
            name = actor->GetSkeleton()->GetNode(i)->GetName();
            if (name.contains(filterString, Qt::CaseInsensitive) || filterString.isEmpty())
            {
                numRows++;
            }
        }
        mSourceList->setRowCount(numRows);

        // fill the rows
        uint32 rowIndex = 0;
        for (uint32 i = 0; i < numNodes; ++i)
        {
            EMotionFX::Node* node = actor->GetSkeleton()->GetNode(i);
            name = node->GetName();
            if (name.contains(filterString, Qt::CaseInsensitive) || filterString.isEmpty())
            {
                // mark if there is a mapping or not
                const bool mapped = AZStd::find(mMap.begin(), mMap.end(), i) != mMap.end();
                QTableWidgetItem* mappedItem = new QTableWidgetItem();
                mappedItem->setIcon(mapped ? *mMappedIcon : QIcon());
                mSourceList->setItem(rowIndex, 0, mappedItem);

                // pick the right icon for the type column
                QTableWidgetItem* typeItem = new QTableWidgetItem();
                if (actor->GetMesh(0, node->GetNodeIndex()))
                {
                    typeItem->setIcon(*mMeshIcon);
                }
                else
                if (AZStd::find(mSourceBoneList.begin(), mSourceBoneList.end(), node->GetNodeIndex()) != mSourceBoneList.end())
                {
                    typeItem->setIcon(*mBoneIcon);
                }
                else
                {
                    typeItem->setIcon(*mNodeIcon);
                }

                mSourceList->setItem(rowIndex, 1, typeItem);

                // set the name
                QTableWidgetItem* currentTableItem = new QTableWidgetItem(name);
                mSourceList->setItem(rowIndex, 2, currentTableItem);

                // set the row height and add one index
                mSourceList->setRowHeight(rowIndex, 21);
                ++rowIndex;
            }
        }
    }

    // fill the mapping table
    void MirrorSetupWindow::FillMappingTable(EMotionFX::Actor* currentActor, EMotionFX::Actor* sourceActor)
    {
        if (!currentActor)
        {
            mMappingTable->setRowCount(0);
            return;
        }

        // fill the table
        QString currentName;
        QString sourceName;
        const uint32 numNodes = currentActor->GetNumNodes();
        mMappingTable->setRowCount(numNodes);
        for (uint32 i = 0; i < numNodes; ++i)
        {
            currentName = currentActor->GetSkeleton()->GetNode(i)->GetName();

            QTableWidgetItem* currentTableItem = new QTableWidgetItem(currentName);
            mMappingTable->setItem(i, 0, currentTableItem);
            mMappingTable->setRowHeight(i, 21);

            if (mMap[i] != MCORE_INVALIDINDEX32)
            {
                sourceName = sourceActor->GetSkeleton()->GetNode(mMap[i])->GetName();
                currentTableItem = new QTableWidgetItem(sourceName);
                mMappingTable->setItem(i, 1, currentTableItem);
            }
            else
            {
                mMappingTable->setItem(i, 1, new QTableWidgetItem());
            }
        }
    }

    // pressing the link button
    void MirrorSetupWindow::OnLinkPressed()
    {
        if (mCurrentList->currentRow() == -1 || mSourceList->currentRow() == -1)
        {
            return;
        }

        // get the names
        QTableWidgetItem* curItem = mCurrentList->currentItem();
        QTableWidgetItem* sourceItem = mSourceList->currentItem();
        if (!curItem || !sourceItem)
        {
            return;
        }

        curItem = mCurrentList->item(curItem->row(), 2);
        sourceItem = mSourceList->item(sourceItem->row(), 2);

        const AZStd::string currentNodeName = curItem->text().toUtf8().data();
        const AZStd::string sourceNodeName  = mSourceList->currentItem()->text().toUtf8().data();
        if (sourceNodeName.empty() || currentNodeName.empty())
        {
            return;
        }

        // get the currently selected actor
        EMotionFX::Actor* currentActor = GetSelectedActor();
        MCORE_ASSERT(currentActor);

        EMotionFX::Node* currentNode = currentActor->GetSkeleton()->FindNodeByName(currentNodeName.c_str());
        EMotionFX::Node* sourceNode  = currentActor->GetSkeleton()->FindNodeByName(sourceNodeName.c_str());
        MCORE_ASSERT(currentNode && sourceNode);

        PerformMapping(currentNode->GetNodeIndex(), sourceNode->GetNodeIndex());
    }

    // perform the mapping
    void MirrorSetupWindow::PerformMapping(uint32 currentNodeIndex, uint32 sourceNodeIndex)
    {
        EMotionFX::Actor* currentActor = GetSelectedActor();

        // update the map
        const uint32 oldSourceIndex = mMap[currentNodeIndex];
        mMap[currentNodeIndex] = sourceNodeIndex;

        // update the current table
        const QString curName = currentActor->GetSkeleton()->GetNode(currentNodeIndex)->GetName();
        const QList<QTableWidgetItem*> currentListItems = mCurrentList->findItems(curName, Qt::MatchExactly);
        for (int32 i = 0; i < currentListItems.count(); ++i)
        {
            const uint32 rowIndex = currentListItems[i]->row();

            QTableWidgetItem* mappedItem = mCurrentList->item(rowIndex, 0);
            if (!mappedItem)
            {
                mappedItem = new QTableWidgetItem();
                mCurrentList->setItem(rowIndex, 0, mappedItem);
            }

            if (sourceNodeIndex == MCORE_INVALIDINDEX32)
            {
                mappedItem->setIcon(QIcon());
            }
            else
            {
                mappedItem->setIcon(*mMappedIcon);
            }
        }

        // update source table
        if (sourceNodeIndex != MCORE_INVALIDINDEX32)
        {
            const bool stillUsed = AZStd::find(mMap.begin(), mMap.end(), sourceNodeIndex) != mMap.end();
            const QString sourceName = currentActor->GetSkeleton()->GetNode(sourceNodeIndex)->GetName();
            const QList<QTableWidgetItem*> sourceListItems = mSourceList->findItems(sourceName, Qt::MatchExactly);
            for (int32 i = 0; i < sourceListItems.count(); ++i)
            {
                const uint32 rowIndex = sourceListItems[i]->row();

                QTableWidgetItem* mappedItem = mSourceList->item(rowIndex, 0);
                if (!mappedItem)
                {
                    mappedItem = new QTableWidgetItem();
                    mSourceList->setItem(rowIndex, 0, mappedItem);
                }

                if (stillUsed == false)
                {
                    mappedItem->setIcon(QIcon());
                }
                else
                {
                    mappedItem->setIcon(*mMappedIcon);
                }
            }
        }
        else // we're clearing it
        {
            if (oldSourceIndex != MCORE_INVALIDINDEX32)
            {
                const bool stillUsed = AZStd::find(mMap.begin(), mMap.end(), sourceNodeIndex) != mMap.end();
                const QString sourceName = currentActor->GetSkeleton()->GetNode(oldSourceIndex)->GetName();
                const QList<QTableWidgetItem*> sourceListItems = mSourceList->findItems(sourceName, Qt::MatchExactly);
                for (int32 i = 0; i < sourceListItems.count(); ++i)
                {
                    const uint32 rowIndex = sourceListItems[i]->row();

                    QTableWidgetItem* mappedItem = mSourceList->item(rowIndex, 0);
                    if (!mappedItem)
                    {
                        mappedItem = new QTableWidgetItem();
                        mSourceList->setItem(rowIndex, 0, mappedItem);
                    }

                    if (stillUsed == false)
                    {
                        mappedItem->setIcon(QIcon());
                    }
                    else
                    {
                        mappedItem->setIcon(*mMappedIcon);
                    }
                }
            }
        }

        // update the mapping table
        QTableWidgetItem* item = mMappingTable->item(currentNodeIndex, 1);
        if (!item && sourceNodeIndex != MCORE_INVALIDINDEX32)
        {
            item = new QTableWidgetItem();
            mMappingTable->setItem(currentNodeIndex, 1, item);
        }

        if (sourceNodeIndex == MCORE_INVALIDINDEX32)
        {
            if (item)
            {
                item->setText("");
            }
        }
        else
        {
            item->setText(currentActor->GetSkeleton()->GetNode(sourceNodeIndex)->GetName());
        }

        // update toolbar icons
        UpdateActorMotionSources();
        UpdateToolBar();
    }

    // press a key
    void MirrorSetupWindow::keyPressEvent(QKeyEvent* event)
    {
        event->ignore();
    }

    // releasing a key
    void MirrorSetupWindow::keyReleaseEvent(QKeyEvent* event)
    {
        // if we press delete, remove the mapping
        if (event->key() == Qt::Key_Delete)
        {
            RemoveCurrentSelectedMapping();
            event->accept();
            return;
        }

        event->ignore();
    }

    // remove the currently selected mapping

// load and apply a mapping
    void MirrorSetupWindow::OnLoadMapping()
    {
        // make sure we have both a current actor and source actor
        EMotionFX::Actor* currentActor = GetSelectedActor();
        if (!currentActor)
        {
            AZ_Warning("EMotionFX", false, "There is no current actor set, a mapping cannot be loaded, please select an actor first!");
            QMessageBox::critical(this, "Cannot Save!", "You need to select a current actor before you can load and apply this node map!", QMessageBox::Ok);
            return;
        }

        // get the filename to save as
        const AZStd::string filename = GetMainWindow()->GetFileManager()->LoadNodeMapFileDialog(this);
        if (filename.empty())
        {
            return;
        }

        // load the node map file from disk
        MCore::LogInfo("Loading node map from file '%s'", filename.c_str());
        EMotionFX::NodeMap* nodeMap = EMotionFX::GetImporter().LoadNodeMap(filename.c_str());
        if (!nodeMap)
        {
            AZ_Warning("EMotionFX", false, "Failed to load the node map!");
            QMessageBox::warning(this, "Failed Loading", "Loading of the node map file failed.", QMessageBox::Ok);
        }
        else
        {
            MCore::LogInfo("Loading of node map is successful, applying now...");
        }

        // check if the node map has a source actor
        if (nodeMap->GetSourceActor())
        {
            AZ_Warning("EMotionFX", false, "EMStudio::MirrorSetupWindow::OnLoadMapping() - There is no source actor to use, please manually load one first.");
            QMessageBox::warning(this, "No Source Actor", "Loading of the source actor inside the node map failed (or didn't contain one) and there is currently none set. Please manually load a source actor first and try again.", QMessageBox::Ok);
            return;
        }

        // now update our mapping data
        const uint32 numNodes = currentActor->GetNumNodes();
        for (uint32 i = 0; i < numNodes; ++i)
        {
            mMap[i] = MCORE_INVALIDINDEX32;
        }

        // now apply the map we loaded to the data we have here
        const uint32 numEntries = nodeMap->GetNumEntries();
        for (uint32 i = 0; i < numEntries; ++i)
        {
            // find the current node
            EMotionFX::Node* currentNode = currentActor->GetSkeleton()->FindNodeByName(nodeMap->GetFirstName(i));
            if (!currentNode)
            {
                continue;
            }

            // find the source node
            EMotionFX::Node* sourceNode = currentActor->GetSkeleton()->FindNodeByName(nodeMap->GetSecondName(i));
            if (!sourceNode)
            {
                continue;
            }

            // create the mapping
            mMap[currentNode->GetNodeIndex()] = sourceNode->GetNodeIndex();
        }

        // apply the current map as command
        ApplyCurrentMapAsCommand();

        // reinit the interface parts
        Reinit(false);

        // get rid of the nodeMap object as we don't need it anymore
        nodeMap->Destroy();
    }

    // save the mapping
    void MirrorSetupWindow::OnSaveMapping()
    {
        // get the currently selected actor
        EMotionFX::Actor* currentActor = GetSelectedActor();
        if (!currentActor)
        {
            AZ_Warning("EMotionFX", false, "There is no current actor set, there is nothing to save!");
            QMessageBox::warning(this, "Nothing To Save!", "You need to select a current actor before you can save a map!", QMessageBox::Ok);
            return;
        }

        // check if we got something to save at all
        if (CheckIfIsMapEmpty())
        {
            AZ_Warning("EMotionFX", false, "The node map is empty, there is nothing to save!");
            QMessageBox::warning(this, "Nothing To Save!", "The node map is empty, there is nothing to save!", QMessageBox::Ok);
            return;
        }

        // read the filename to save as
        const AZStd::string filename = GetMainWindow()->GetFileManager()->SaveNodeMapFileDialog(this);
        if (filename.empty())
        {
            return;
        }

        MCore::LogInfo("Saving node map as '%s'", filename.c_str());

        // create an emfx node map object
        EMotionFX::NodeMap* map = EMotionFX::NodeMap::Create();
        const uint32 numNodes = currentActor->GetNumNodes();
        map->Reserve(numNodes);
        for (uint32 i = 0; i < numNodes; ++i)
        {
            // skip unmapped entries
            if (mMap[i] == MCORE_INVALIDINDEX32)
            {
                continue;
            }

            // add the entry to the map if it doesn't yet exist
            if (map->GetHasEntry(currentActor->GetSkeleton()->GetNode(i)->GetName()) == false)
            {
                map->AddEntry(currentActor->GetSkeleton()->GetNode(i)->GetName(), currentActor->GetSkeleton()->GetNode(mMap[i])->GetName());
            }
        }

        // set the filename, in case we do something with it while saving later on
        map->SetFileName(filename.c_str());

        // save as little endian
        if (map->Save(filename.c_str(), MCore::Endian::ENDIAN_LITTLE) == false)
        {
            AZ_Warning("EMotionFX", false, "Failed to save node map file '%s', is it maybe in use or is the location read only?", filename.c_str());
            GetNotificationWindowManager()->CreateNotificationWindow(NotificationWindow::TYPE_ERROR, "Node map failed to save");
        }
        else
        {
            MCore::LogInfo("Saving of node map successfully completed");
            GetNotificationWindowManager()->CreateNotificationWindow(NotificationWindow::TYPE_SUCCESS, "Node map successfully saved");
        }

        map->Destroy();
    }

    // clear the mapping
    void MirrorSetupWindow::OnClearMapping()
    {
        if (QMessageBox::warning(this, "Clear Current Mapping?", "Are you sure you want to clear the current mapping?\nAll mapping information will be lost.", QMessageBox::Cancel | QMessageBox::Yes) != QMessageBox::Yes)
        {
            return;
        }

        EMotionFX::Actor* currentActor = GetSelectedActor();

        // apply mirror changes
        const AZStd::string command = AZStd::string::format("AdjustActor -actorID %d -mirrorSetup \"\"", currentActor->GetID());

        AZStd::string result;
        if (!EMStudio::GetCommandManager()->ExecuteCommand(command, result))
        {
            AZ_Error("EMotionFX", false, result.c_str());
        }

        // reinitialize, which also clears the map
        Reinit();
    }

    // check if the current map is empty
    bool MirrorSetupWindow::CheckIfIsMapEmpty() const
    {
        EMotionFX::Actor* currentActor = GetSelectedActor();

        if (!currentActor)
        {
            return true;
        }

        const uint32 numNodes = currentActor->GetNumNodes();
        for (uint32 i = 0; i < numNodes; ++i)
        {
            if (mMap[i] != MCORE_INVALIDINDEX32)
            {
                return false;
            }
        }

        return true;
    }

    // update the toolbar icons
    void MirrorSetupWindow::UpdateToolBar()
    {
        EMotionFX::Actor* currentActor = GetSelectedActor();

        // check which buttons have to be enabled/disabled
        const bool canOpen  = (currentActor);
        const bool canSave  = (CheckIfIsMapEmpty() == false && currentActor);
        const bool canClear = (CheckIfIsMapEmpty() == false);
        const bool canGuess = (currentActor);

        // enable or disable them
        mButtonOpen->setEnabled(canOpen);
        mButtonSave->setEnabled(canSave);
        mButtonClear->setEnabled(canClear);
        mButtonGuess->setEnabled(canGuess);
    }

    // perform best guess mapping
    void MirrorSetupWindow::OnBestGuess()
    {
        // get the current and source actor
        EMotionFX::Actor* currentActor = GetSelectedActor();
        if (!currentActor)
        {
            return;
        }

        if (mLeftEdit->text().size() == 0 || mRightEdit->text().size() == 0)
        {
            QMessageBox::information(this, "Empty Left And Right Strings", "Please enter both a left and right sub-string.\nThis can be something like 'Left' and 'Right'.\nThis would map nodes like 'Left Arm' to 'Right Arm' nodes.", QMessageBox::Ok);
            return;
        }

        // show a warning that we will overwrite the table entries
        //if (QMessageBox::warning(this, "Overwrite Mapping?", "Are you sure you want to possibly overwrite items in the mapping?\nAll or some existing mapping information might be lost.", QMessageBox::Cancel|QMessageBox::Yes) != QMessageBox::Yes)

        // update the table and map
        uint32 numGuessed = 0;
        const uint32 numNodes = currentActor->GetNumNodes();
        for (uint32 i = 0; i < numNodes; ++i)
        {
            // skip already setup mappings
            if (mMap[i] != MCORE_INVALIDINDEX32)
            {
                continue;
            }

            const uint16 matchIndex = currentActor->FindBestMatchForNode(currentActor->GetSkeleton()->GetNode(i)->GetName(), FromQtString(mLeftEdit->text()).c_str(), FromQtString(mRightEdit->text()).c_str());
            if (matchIndex != MCORE_INVALIDINDEX16)
            {
                mMap[i] = matchIndex;
                numGuessed++;
            }
        }

        // update the actor
        UpdateActorMotionSources();

        /*
            // try a geometrical mapping
            EMotionFX::Pose pose;
            pose.InitFromLocalBindSpaceTransforms( currentActor );

            // for all nodes in the current actor
            uint32 numGuessed = 0;
            const uint32 numNodes = currentActor->GetNumNodes();
            for (uint32 i=0; i<numNodes; ++i)
            {
                // skip already setup mappings
                if (mMap[i] != MCORE_INVALIDINDEX32)
                    continue;

                const uint16 matchIndex = currentActor->FindBestMirrorMatchForNode( static_cast<uint16>(i), pose );
                if (matchIndex != MCORE_INVALIDINDEX16)
                {
                    mMap[i] = matchIndex;
                    numGuessed++;
                }
            }
        */
        Reinit(false);

        // show some results
        QMessageBox::information(this, "Mirror Mapping Results", AZStd::string::format("We modified mappings for %d nodes.", numGuessed).c_str());
    }

    void MirrorSetupWindow::UpdateActorMotionSources()
    {
        // get the current and source actor
        EMotionFX::Actor* currentActor = GetSelectedActor();
        if (!currentActor)
        {
            return;
        }
        /*
            const uint32 numNodes = currentActor->GetNumNodes();
            for (uint32 i=0; i<numNodes; ++i)
            {
                uint16 motionSource = i;

                // get the motion source from the map
                if (mMap[i] != MCORE_INVALIDINDEX32)
                    motionSource = static_cast<uint16>( mMap[i] );
            }
        */
        // apply the current map as command
        ApplyCurrentMapAsCommand();
    }

    // init the mapping table from a given actor
    void MirrorSetupWindow::InitMappingTableFromMotionSources(EMotionFX::Actor* actor)
    {
        if (!actor)
        {
            return;
        }

        const uint32 numNodes = actor->GetNumNodes();
        for (uint32 i = 0; i < numNodes; ++i)
        {
            if (actor->GetHasMirrorInfo())
            {
                uint16 motionSource = actor->GetNodeMirrorInfo(i).mSourceNode;
                if (motionSource != i)
                {
                    mMap[i] = motionSource;
                }
                else
                {
                    mMap[i] = MCORE_INVALIDINDEX32;
                }
            }
            else
            {
                mMap[i] = MCORE_INVALIDINDEX32;
            }
        }
    }

    // apply the current map as command
    void MirrorSetupWindow::ApplyCurrentMapAsCommand()
    {
        // get the current and source actor
        EMotionFX::Actor* currentActor = GetSelectedActor();
        if (!currentActor)
        {
            return;
        }

        // apply mirror changes
        AZStd::string commandString = AZStd::string::format("AdjustActor -actorID %d -mirrorSetup \"", currentActor->GetID());
        for (uint32 i = 0; i < currentActor->GetNumNodes(); ++i)
        {
            uint32 sourceNode = mMap[i];
            if (sourceNode != MCORE_INVALIDINDEX32 && sourceNode != i)
            {
                commandString += currentActor->GetSkeleton()->GetNode(i)->GetName();
                commandString += ",";
                commandString += currentActor->GetSkeleton()->GetNode(sourceNode)->GetName();
                commandString += ";";
            }
        }
        commandString += "\"";

        AZStd::string result;
        if (!EMStudio::GetCommandManager()->ExecuteCommand(commandString, result))
        {
            AZ_Error("EMotionFX", false, result.c_str());
        }

        FillMappingTable(currentActor, currentActor);
        UpdateToolBar();
    }

    EMotionFX::Actor* MirrorSetupWindow::GetSelectedActor() const
    {
        CommandSystem::SelectionList& selection = CommandSystem::GetCommandManager()->GetCurrentSelection();
        EMotionFX::Actor* currentActor = selection.GetSingleActor();
        if (currentActor)
        {
            return currentActor;
        }

        EMotionFX::ActorInstance* currentActorInstance = selection.GetSingleActorInstance();
        if (currentActorInstance)
        {
            return currentActorInstance->GetActor();
        }

        return nullptr;
    }

}   // namespace EMStudio

#include <EMotionFX/Tools/EMotionStudio/Plugins/StandardPlugins/Source/SceneManager/MirrorSetupWindow.moc>