///////////////////////////////////////////////////////////////////////////////
//
// File: Tools_Container_System.cpp
//
// Created by Martin G.Harris (6/5/2008)
// 
// Copyright (C) 2008 Rebellion, All Rights Reserved.
//
///////////////////////////////////////////////////////////////////////////////

#include "Core/Asura.h"
#include "Entity/Container/Tools_Container_System.h"

#ifdef ASURA_USE_CONTAINER_SYSTEM

// Includes
// Asura
#include "Core/Asura_NameTree.h"
#include "Core/Asura_StringBuffer.h"
#include "ChunkSystem/Asura_Chunk_Container.h"
#include "ChunkSystem/Asura_Chunk_Loaders.h"
#include "ChunkSystem/Asura_SaveBlock.h"
#include "Entity/Asura_StaticMessage_System.h"
#include "Entity/Container/Asura_Container_System.h"
#include "Entity/Container/Asura_ContainerRegistry.h"
#include "Entity/Container/Asura_ServerEntity_Container.h"
#include "FileAccess/Asura_FileAccess.h"
#include "Interface/Asura_Dialog.h"
#include "Interface/Asura_Overlay.h"
#include "Interface/Asura_Overlay_System.h"

// Tools
#include "Tools_System.h"
#include "Entity/Container/Tools_ContainerFile.h"
#include "Entity/Container/Tools_ContainerRegistry.h"
#include "Entity/Container/Tools_ContainerState.h"
#include "Entity/Container/Tools_Entity_Container.h"
#include "Entity/Container/Tools_Mode_Container.h"
#include "Entity/Tools_Message_Editing.h"
#include "Interface/Tools_ContextMenu_System.h"
#include "Load&Save/Tools_ModeDataFile.h"
#include "Load&Save/Tools_SaveAsura.h"
#include "Misc/Tools_MessageLog.h"
#include "Misc/Tools_Modal_Overlay.h"
#include "Misc/Tools_Render_Options.h"
#include "Misc/Tools_Timed_Messages.h"
#include "PC/FileSystem/Tools_FileSystem.h"

/////////////
// Defines //
/////////////
// Note: Should match definition in Tools_ContainerFile.cpp
#define szCONTAINERS_DIRECTORY    "Containers\\"
#define	szCONTAINERS_EXTENSION    ".asr"
#define szCONTAINERS_WILDCARD     szCONTAINERS_DIRECTORY "*" szCONTAINERS_EXTENSION
#define szCONTAINERS_NEWFILE      "File"
#define szCONTAINERS_NEWGROUP     "Group"
#define szCONTAINERS_NEWCONTAINER "Container"

///////////////
// Constants //
///////////////
const char* const szCONTAINER_LOG_CATEGORY    = "Verification";
const char* const szCONTAINER_LOG_SUBCATEGORY = "Container";

/////////////
// Helpers //
/////////////

int SortTreeAlphabetically( const Asura_TreeDialog_Item& xOne, const Asura_TreeDialog_Item& xTwo )
{
	const char* const pszOne = xOne.GetName();
	const char* const pszTwo = xTwo.GetName();
	if (!pszOne || !pszTwo) return 0;
	
	// Compare strings.
	int iResult = stricmp(pszOne, pszTwo);
	
	if (iResult == 0)
	{
		// The strings are the same, try using the user data to break the tie.
		const u_int uUserData1 = static_cast<u_int>(xOne.GetUserData());
		const u_int uUserData2 = static_cast<u_int>(xTwo.GetUserData());
		if (uUserData1 == uUserData2)
		{
			iResult = 0;
		}
		else
		{
			iResult = ((uUserData1 < uUserData2) ? -1 : +1);
		}
	}

	return iResult;
}

///////////////////////////////////////////////////////////////////////////////
//  Tools_ContainerFileHash_DropDownList_Dialog
///////////////////////////////////////////////////////////////////////////////
class Tools_ContainerFileHash_DropDownList_Dialog : public Asura_Hash_DropDownList_Dialog
{
public:
	Tools_ContainerFileHash_DropDownList_Dialog(const char* szName, Asura_Vector_2& xOffset, Asura_Hash_ID* puFilenameHash, bool* pbChanged)
		: Asura_Hash_DropDownList_Dialog(xOffset)
	{
		Initialise(xOffset, 10, puFilenameHash, pbChanged, szName);
	}

	virtual u_int GetNumberOfHashValues() const { return Tools_Container_System::GetFileTree().GetNumberOfNodes(); }
	
	virtual const char* GetNameByIndex(int iIndex) const
	{
		const Tools_ContainerFile* pxFile = Tools_Container_System::GetFileTree().GetDataByIndex(iIndex);
		if (pxFile)
		{
			return pxFile->GetShortName().GetString();
		}
		return 0;
	}

protected:
	virtual const char* GetZeroHashText() const { return "*All*"; }
};


///////////////////////////////////////////////////////////////////////////////
// Tools_Container_Options
///////////////////////////////////////////////////////////////////////////////
// Some user definable options that are saved out.
Tools_Container_Options::Tools_Container_Options()
: m_bDisplaySoundEvents(false)
, m_fEntityOriginMeshSize(1.0f)
, m_fModeOriginMeshSize(1.0f)
, m_fModeAttachmentMeshSize(1.0f)
{
}

///////////////////////////////////////////////////////////////////////////////
// Tools_ContainerSelection_Overlay
///////////////////////////////////////////////////////////////////////////////
// Modal list selection overlay for containers, with some filtering options.
class Tools_ContainerSelection_Overlay : public ListSelection_Overlay
{
public:
		Tools_ContainerSelection_Overlay(const Asura_String& xTitle)
			: ListSelection_Overlay(xTitle)
			, m_bUpdateVisibility(false)
			, m_uFileShortNameHashFilter(ASURA_HASH_ID_UNSET)
			, m_uFileHashFilter(ASURA_HASH_ID_UNSET)
		{
			if (Tools_Container_System::FindFileByShortNameHash(s_uSavedFileShortNameHashFilter))
			{
				m_uFileShortNameHashFilter = s_uSavedFileShortNameHashFilter;
				m_bUpdateVisibility = true;
			}
		}

		~Tools_ContainerSelection_Overlay()
		{
			s_uSavedFileShortNameHashFilter = m_uFileShortNameHashFilter;
		}

		static Asura_Hash_ID& GetSavedFileHash() { return s_uSavedFileShortNameHashFilter; }

protected:
		virtual void AddExtraOverlayControls(Asura_Overlay* pxOverlay, Asura_Vector_2& xOffset)
		{
			Asura_Dialog* pxDialog = new Tools_ContainerFileHash_DropDownList_Dialog("File filter", xOffset, &m_uFileShortNameHashFilter, &m_bUpdateVisibility);
				
			pxDialog->SetHelpText("Allows the list of containers to be filtered according to the file that they are in.");
			pxOverlay->AddDialog(pxDialog);
			xOffset.y += Asura_Dialog::LargeSpacing();
		}

		virtual void Update()
		{
			if (m_bUpdateVisibility)
			{
				m_bUpdateVisibility = false;

				m_uFileHashFilter = m_uFileShortNameHashFilter;
				
				// Translate the file hash for files that have been renamed.
				const Tools_ContainerFile* pxFile = Tools_Container_System::FindFileByShortNameHash(m_uFileShortNameHashFilter);
				if (pxFile)
				{
					m_uFileHashFilter = pxFile->GetFileHash();
				}

				UpdateVisibilities();
			}

			ListSelection_Overlay::Update();
		}

		virtual bool ShouldItemBeHidden( const char* szString, int iData, bool bWasHidden )
		{
			if (m_uFileHashFilter == ASURA_HASH_ID_UNSET) return false; // No filtering

			if (iData == 0) return false; // Don't filter null option.

			const Tools_Container* pxContainer = Tools_Container_System::FindContainerByHash(static_cast<u_int>(iData));
			if (!pxContainer) return true; // Not found.

			return (pxContainer->GetFileHash() != m_uFileHashFilter);
		}

protected:
	// Make the list resizable?
	virtual bool IsListResizable() const { return true; }
	virtual Asura_Vector_2* GetResizableUserSize() const { return &s_xTreeSize; }

protected:
	bool m_bUpdateVisibility;
	Asura_Hash_ID m_uFileShortNameHashFilter; // Hash of current short filename
	Asura_Hash_ID m_uFileHashFilter;		  // Hash of file's creation name.

	static Asura_Vector_2 s_xTreeSize;
	static Asura_Hash_ID s_uSavedFileShortNameHashFilter;
};

Asura_Vector_2 Tools_ContainerSelection_Overlay::s_xTreeSize(20.0f, 20.0f);
Asura_Hash_ID Tools_ContainerSelection_Overlay::s_uSavedFileShortNameHashFilter = ASURA_HASH_ID_UNSET;

///////////////////////////////////////////////////////////////////////////////
// Tools_Overlay_ContainerSystem
///////////////////////////////////////////////////////////////////////////////

class Tools_Overlay_ContainerSystem : public Asura_Overlay
{
public :
	Tools_Overlay_ContainerSystem(const char* szName, bool bAlwaysOnTop, Tools_Container_Overlay_Manager& xManager)
	: Asura_Overlay(szName, bAlwaysOnTop, xManager.GetPosition())
	, m_xManager(xManager)
	{}

	~Tools_Overlay_ContainerSystem()
	{
		if (IsClosingByCloseGadget())
		{
			// Overlay was closed using the close gadget.
			m_xManager.ClosingByCloseGadget();
		}
	}

private:
	Tools_Container_Overlay_Manager& m_xManager;
};

///////////////////////////////////////////////////////////////////////////////
// Tools_ContainerItemReference
///////////////////////////////////////////////////////////////////////////////
Tools_ContainerItemReference::Tools_ContainerItemReference(const Asura_TreeDialog_Item* pxItem)
: m_uFileHash(ASURA_HASH_ID_UNSET)
, m_uGroupHash(ASURA_HASH_ID_UNSET)
, m_uContainerHash(ASURA_HASH_ID_UNSET)
, m_pxFile(0)
, m_pxGroup(0)
, m_pxContainer(0)
{
	if (!pxItem) return;

	// Find the currently selected item.
	const int iDepth = pxItem->GetDepth();
	const Asura_Hash_ID uItemHash = static_cast<Asura_Hash_ID>(pxItem->GetUserData());

	if (iDepth == 1)
	{
		// Selected a file. User data is hash of file name.
		m_uFileHash = uItemHash;
	}
	else if (iDepth == 2)
	{
		// Selected a group. User data is hash of group name.
		m_uGroupHash = uItemHash;
	}
	else if (iDepth > 2)
	{
		// Selected a container. User data is hash of container name.
		m_uContainerHash = uItemHash;
		m_pxContainer = Tools_Container_System::FindContainerByHash(m_uContainerHash);
	}

	if (m_pxContainer)
	{
		m_uGroupHash = m_pxContainer->GetGroupHash();	
	}

	m_pxGroup = Tools_Container_System::FindGroupByHash(m_uGroupHash);

	if (m_pxGroup)
	{
		m_uFileHash = m_pxGroup->GetFileHash();
	}

	m_pxFile = Tools_Container_System::FindFileByHash(m_uFileHash);
}


///////////////////////////////////////////////////////////////////////////////
// Tools_Container_Overlay_Manager
///////////////////////////////////////////////////////////////////////////////

// Static data
Asura_Hash_ID Tools_Container_Overlay_Manager::s_uSelectedFileHash      = ASURA_HASH_ID_UNSET;
Asura_Hash_ID Tools_Container_Overlay_Manager::s_uSelectedGroupHash     = ASURA_HASH_ID_UNSET;
Asura_Hash_ID Tools_Container_Overlay_Manager::s_uSelectedContainerHash = ASURA_HASH_ID_UNSET;


Tools_Container_Overlay_Manager::Tools_Container_Overlay_Manager()
	: m_xOverlayPosition(10.0f, 3.0f)
	, m_bOverlayIsOpen(false)
	, m_bModeDataLoaded(false)
	, m_xTreeDialogueDimensions(30.0f, 30.0f)
	, m_pxSelectFileDialog(0)
	, m_pxSelectGroupDialog(0)
	, m_pxSelectContainerDialog(0)
	, m_pxSelectTypeDialog(0)
	, m_bEditOverlayIsOpen(false)
	, m_bEditOverlayBringToFront(false)
	, m_bEditOverlayNeedToRebuild(false)
	, m_xEditOverlayPosition(5.0f, 3.0f)
	, m_uEditingContainerHash(ASURA_HASH_ID_UNSET)
{
}

///////////////////
// ToggleOverlay //
///////////////////
void Tools_Container_Overlay_Manager::ToggleOverlay()
{
	if (IsOverlayOpen())
	{
		DestroyOverlay();
	}
	else
	{
		CreateOverlay();
	}
}

///////////////////
// CreateOverlay //
///////////////////
void Tools_Container_Overlay_Manager::CreateOverlay()
{
	if (m_bOverlayIsOpen) return;

	s_uSelectedFileHash      = ASURA_HASH_ID_UNSET;
	s_uSelectedGroupHash     = ASURA_HASH_ID_UNSET;
	s_uSelectedContainerHash = ASURA_HASH_ID_UNSET;

	// Load data concerning the overlay. Only need to do this when the overlay is first opened.
	TransferModeData(TOOLSMODEDATAFILE_LOAD);

	Asura_Overlay* pxNewOverlay = new Tools_Overlay_ContainerSystem( "Container Files", false, *this );
    if ( !pxNewOverlay ) return;
    
	pxNewOverlay->m_uID = reinterpret_cast< u_int >( this );

    Asura_Vector_2 xOffset = Asura_Vector_2( 1.0f, 3.0f );
    AddOverlayItems( *pxNewOverlay, xOffset );
	
	//pxNewOverlay->SetContextMenuCallback( ContextMenuCallback, NULL );

	Asura_Overlay_System::SubmitNewOverlay( pxNewOverlay );

	m_bOverlayIsOpen = true;
}

////////////////////
// DestroyOverlay //
////////////////////
void Tools_Container_Overlay_Manager::DestroyOverlay()
{
	if (!m_bOverlayIsOpen) return;

	// Save data concerning the overlay, in case it has changed.
	TransferModeData(TOOLSMODEDATAFILE_SAVE);

	Asura_Overlay_System::CloseOverlays( reinterpret_cast< u_int >( this ) );

	m_bOverlayIsOpen = false;
	m_pxSelectFileDialog = 0;
	m_pxSelectGroupDialog = 0;
	m_pxSelectContainerDialog = 0;
	m_pxSelectTypeDialog = 0;

	s_uSelectedFileHash      = ASURA_HASH_ID_UNSET;
	s_uSelectedGroupHash     = ASURA_HASH_ID_UNSET;
	s_uSelectedContainerHash = ASURA_HASH_ID_UNSET;
}

//////////////////////////
// ClosingByCloseGadget //
//////////////////////////
void Tools_Container_Overlay_Manager::ClosingByCloseGadget()
{
	// Save data concerning the overlay.
	TransferModeData(TOOLSMODEDATAFILE_SAVE);

	m_bOverlayIsOpen = false;
	m_pxSelectFileDialog = 0;
	m_pxSelectGroupDialog = 0;
	m_pxSelectContainerDialog = 0;
	m_pxSelectTypeDialog = 0;

	if (!Tools_System::IsInContainerMode())
	{
		CloseEditOverlay();
		Tools_Container_System::SavePrompt(false);
	}
}

//////////////////////
// TransferModeData //
//////////////////////
// Load/save data concerning the overlay, such as, position.
void Tools_Container_Overlay_Manager::TransferModeData(const int iMode)
{
	// Only need to load mode data once.
	if (iMode == TOOLSMODEDATAFILE_LOAD)
	{
		if (m_bModeDataLoaded)
		{
			return;
		}
		m_bModeDataLoaded = true;
	}

	Tools_ModeDataFile xFile( "ContainerSystem.dat", iMode );
    TransferToolsModeData( xFile );
}

///////////////////////////
// TransferToolsModeData //
///////////////////////////
void Tools_Container_Overlay_Manager::TransferToolsModeData( Tools_ModeDataFile& xFile )
{
	int iVersion = CURRENT_SAVE_VERSION;
    xFile.Transfer( iVersion );

    if ( iVersion >= 0 )
    {
        xFile.Transfer( m_xOverlayPosition );
		xFile.Transfer( m_xTreeDialogueDimensions );
		xFile.Transfer( m_xEditOverlayPosition );
    }
	if ( iVersion >= 1 )
	{
		xFile.Transfer( m_xOptions.m_bDisplaySoundEvents );
	}
	if ( iVersion >= 2 )
	{
		xFile.Transfer( m_xOptions.m_fEntityOriginMeshSize );
		xFile.Transfer( m_xOptions.m_fModeOriginMeshSize );
		xFile.Transfer( m_xOptions.m_fModeAttachmentMeshSize );
	}
	if ( iVersion >= 3 )
	{
		xFile.Transfer( Tools_ContainerSelection_Overlay::GetSavedFileHash() );
	}
}

/////////////////////
// AddOverlayItems //
/////////////////////
// Tabs in the overlay.
const char* const Tools_Container_Overlay_Manager::s_aszTabNames[ NUM_TAB_NAMES ] =
{
    "Editing",
	"Display",
    " Tools "
};

// Add overlay items.
void Tools_Container_Overlay_Manager::AddOverlayItems(Asura_Overlay& xOverlay, Asura_Vector_2& xOffset)
{
	Asura_Dialog* pxNewDialogue = NULL;

	//
	// Selected file/container
	//
	m_pxSelectFileDialog = new Asura_Dialog("File :", xOffset);
	xOverlay.AddDialog(m_pxSelectFileDialog);
	xOffset.y += Asura_Dialog::DefaultSpacing();

	m_pxSelectGroupDialog = new Asura_Dialog("Group :", xOffset);
	xOverlay.AddDialog(m_pxSelectGroupDialog);
	xOffset.y += Asura_Dialog::DefaultSpacing();

	m_pxSelectContainerDialog = new Asura_Dialog("Container :", xOffset);
	xOverlay.AddDialog(m_pxSelectContainerDialog);
	xOffset.y += Asura_Dialog::DefaultSpacing();

	m_pxSelectTypeDialog = new Asura_Dialog("Type :", xOffset);
	xOverlay.AddDialog(m_pxSelectTypeDialog);
	xOffset.y += Asura_Dialog::LargeSpacing();

	//
	// Tree
	//
    Asura_Tree_Dialog* pxNewTreeDialogue = new Asura_Tree_Dialog( xOffset, &m_xTree, m_xTreeDialogueDimensions.x, m_xTreeDialogueDimensions.y );
    pxNewTreeDialogue->SetDoubleClickedFunction( TreeDoubleClicked );
	pxNewTreeDialogue->SetDragDropCallback(Asura_Callback_Void_1Arg<void,Asura_TreeDialog_Item*>(DragAndDropTreeItem));
	pxNewTreeDialogue->SetUsePerItemHelp(true);
	pxNewTreeDialogue->SetIconCallback( Asura_Callback_Void_1Arg<int, Asura_TreeDialog_Item*>(GetTreeItemIconIndex) );
	pxNewTreeDialogue->SetRenameCallback( Asura_Callback_Void_2Arg<void, Asura_TreeDialog_Item*, const char*>(RenameTreeItem));
    xOverlay.AddDialog( pxNewTreeDialogue );
    xOffset.y += m_xTreeDialogueDimensions.y + 1.0f;

	//add control for resizing the tree
	pxNewDialogue = new Asura_Resize_Dialog(pxNewTreeDialogue, &m_xTreeDialogueDimensions);
	pxNewDialogue->SetRelativePosition(pxNewTreeDialogue, Asura_Vector_2(-pxNewDialogue->GetWidth(), -pxNewDialogue->GetHeight()), true, true);
    xOverlay.AddDialog( pxNewDialogue );


	//
	// Tabs
	//
    Asura_Tabs_Dialog* pxTabsDialogue = new Asura_Tabs_Dialog( xOffset, s_aszTabNames, sizeof( s_aszTabNames ) / sizeof( s_aszTabNames[ 0 ] ) );
    xOverlay.AddDialog( pxTabsDialogue );

	//
	// Editing
	//
    pxTabsDialogue->SelectTab( TAB_EDITING );
    xOffset.y = pxTabsDialogue->m_xMaxCoord.y + 0.5f;
    float fTabsEndY = xOffset.y;

	// Create
	pxNewDialogue = new Asura_Function_Dialog( "Create new File", xOffset, Tools_Container_System::CreateNewFile );
    pxNewDialogue->SetHelpText( "Create a new file." );
    pxTabsDialogue->AddDialog( pxNewDialogue );
    xOffset.y += Asura_Dialog::DefaultSpacing();

	pxNewDialogue = new Asura_Function_Dialog( "Create new Group", xOffset, Tools_Container_System::CreateNewGroup, NULL, IsFileSelectionValidAndWriteable );
    pxNewDialogue->SetHelpText( "Create a new group in the currently selected file." );
    pxTabsDialogue->AddDialog( pxNewDialogue );
    xOffset.y += Asura_Dialog::DefaultSpacing();

	pxNewDialogue = new Asura_Function_Dialog( "Create new Container", xOffset, Tools_Container_System::CreateNewContainer, NULL, IsGroupSelectionValidAndWriteable );
	pxNewDialogue->SetHelpText( "Create a new container in the currently selected group. The scope is global." );
	pxTabsDialogue->AddDialog( pxNewDialogue );
	xOffset.y += Asura_Dialog::LargeSpacing();

	// Group
	pxNewDialogue = new Asura_Dialog( "Selected Group:", xOffset );
	pxTabsDialogue->AddDialog( pxNewDialogue );
	xOffset.y += Asura_Dialog::DefaultSpacing();

	xOffset.x += 7.0f;
	pxNewDialogue = new Asura_Function_Dialog( "Move", xOffset, Tools_Container_System::MoveSelectedGroup, NULL, IsGroupOnlySelectionValidAndWriteable );
	pxNewDialogue->SetHelpText( "Move the currently selected container group to a different file." );
	pxTabsDialogue->AddDialog( pxNewDialogue );
	xOffset.x += 7.0f;

	pxNewDialogue = new Asura_Function_Dialog( "Rename", xOffset, Tools_Container_System::RenameSelectedGroup, NULL, IsGroupOnlySelectionValidAndWriteable );
	pxNewDialogue->SetHelpText( "Rename the currently selected container group." );
	pxTabsDialogue->AddDialog( pxNewDialogue );
	xOffset.x += 9.0f;

	pxNewDialogue = new Asura_Function_Dialog( "Delete", xOffset, Tools_Container_System::DeleteSelectedGroup, NULL, IsGroupOnlySelectionValidAndWriteable );
	pxNewDialogue->SetHelpText( "Delete the currently selected container group and all its containers." );
	pxTabsDialogue->AddDialog( pxNewDialogue );
	xOffset.x -= 23.0f;
	xOffset.y += Asura_Dialog::LargeSpacing();

	// Container
	pxNewDialogue = new Asura_Dialog( "Selected Container:", xOffset );
	pxTabsDialogue->AddDialog( pxNewDialogue );
	xOffset.y += Asura_Dialog::DefaultSpacing();

	pxNewDialogue = new Asura_Function_Dialog( "Edit", xOffset, Tools_Container_System::EditSelectedContainer, NULL, IsContainerSelectionValid );
    pxNewDialogue->SetHelpText( "Edit the currently selected container." );
    pxTabsDialogue->AddDialog( pxNewDialogue );
    xOffset.x += 7.0f;

	pxNewDialogue = new Asura_Function_Dialog( "Move", xOffset, Tools_Container_System::MoveSelectedContainer, NULL, IsContainerValidForMoving);
    pxNewDialogue->SetHelpText( "Move the currently selected container to a different group." );
    pxTabsDialogue->AddDialog( pxNewDialogue );
    xOffset.x += 7.0f;

	pxNewDialogue = new Asura_Function_Dialog( "Rename", xOffset, Tools_Container_System::RenameSelectedContainer, NULL,  IsContainerSelectionValidAndWriteable );
	pxNewDialogue->SetHelpText( "Rename the currently selected container." );
	pxTabsDialogue->AddDialog( pxNewDialogue );
	xOffset.x += 9.0f;

	pxNewDialogue = new Asura_Function_Dialog( "Delete", xOffset, Tools_Container_System::DeleteSelectedContainer, NULL, IsContainerSelectionValidAndWriteable );
	pxNewDialogue->SetHelpText( "Delete the currently selected container." );
	pxTabsDialogue->AddDialog( pxNewDialogue );
	xOffset.x -= 23.0f;
	xOffset.y += Asura_Dialog::DefaultSpacing();

    fTabsEndY = Asura_Maths::Max( fTabsEndY, xOffset.y );
	
	//
	// Display
	//
	pxTabsDialogue->SelectTab( TAB_DISPLAY );
    xOffset.y = pxTabsDialogue->m_xMaxCoord.y + 0.5f;

	pxNewDialogue = new Asura_Tickbox_Dialog( "Display sound events", xOffset, &m_xOptions.m_bDisplaySoundEvents);
	pxNewDialogue->SetHelpText( "Display sound events as a sphere in Tools." );
	pxTabsDialogue->AddDialog( pxNewDialogue );
	xOffset.y += Asura_Dialog::LargeSpacing();

	pxNewDialogue = new Asura_Numerical_Dialog("Tools mesh scale (Object mode)", xOffset, &m_xOptions.m_fEntityOriginMeshSize, 0.0f, 2.0f, 0.1f, "%.2f");
	pxNewDialogue->SetHelpText( "Adjust the size of the tools mesh for container entities in object mode." );
	pxTabsDialogue->AddDialog( pxNewDialogue );
	xOffset.y += Asura_Dialog::DefaultSpacing();

	pxNewDialogue = new Asura_Numerical_Dialog("Origin mesh scale (Container mode)", xOffset, &m_xOptions.m_fModeOriginMeshSize, 0.0f, 2.0f, 0.1f, "%.2f");
	pxNewDialogue->SetHelpText( "Adjust the size of the tools mesh for the container origin in container mode." );
	pxTabsDialogue->AddDialog( pxNewDialogue );
	xOffset.y += Asura_Dialog::DefaultSpacing();

	pxNewDialogue = new Asura_Numerical_Dialog("Attachment mesh scale (Container mode)", xOffset, &m_xOptions.m_fModeAttachmentMeshSize, 0.0f, 2.0f, 0.1f, "%.2f");
	pxNewDialogue->SetHelpText( "Adjust the size of the tools mesh for the container attachment in container mode." );
	pxTabsDialogue->AddDialog( pxNewDialogue );
	xOffset.y += Asura_Dialog::DefaultSpacing();

	fTabsEndY = Asura_Maths::Max( fTabsEndY, xOffset.y );

	//
	// Tools
	//
    pxTabsDialogue->SelectTab( TAB_TOOLS );
    xOffset.y = pxTabsDialogue->m_xMaxCoord.y + 0.5f;

	pxNewDialogue = new Asura_Function_Dialog( "Toggle global/group scope", xOffset, Tools_Container_System::ToggleScope, 0, Tools_Container_System::IsValidForToggleScope);
    pxNewDialogue->SetHelpText( "Switch currently selected container between group and global scope." );
    pxTabsDialogue->AddDialog( pxNewDialogue );
    xOffset.y += Asura_Dialog::LargeSpacing();

	pxNewDialogue = new Asura_Function_Dialog( "Verify all containers and container entities", xOffset, Tools_Container_System::VerifyContainerInformation);
    pxNewDialogue->SetHelpText( "Verify all of the container information to ensure that it is correct." );
    pxTabsDialogue->AddDialog( pxNewDialogue );
    xOffset.y += Asura_Dialog::LargeSpacing();

    fTabsEndY = Asura_Maths::Max( fTabsEndY, xOffset.y );

    pxTabsDialogue->SelectTab( TAB_EDITING );

	//
	// Saving/closing
	//
    xOffset.y = fTabsEndY;
	xOffset.y += Asura_Dialog::DefaultSpacing();
	
	pxNewDialogue = new Asura_Function_Dialog( "Save & Close", xOffset, Tools_Container_System::SaveAndCloseOverlay );
	pxNewDialogue->SetHelpText( "Save any modified container files and close the overlay" );
    xOverlay.AddDialog( pxNewDialogue );
	xOffset.x += 14.0f;

	pxNewDialogue = new Asura_Function_Dialog( "Save", xOffset, Tools_Container_System::SaveModified, NULL, Tools_Container_System::IsModified );
	pxNewDialogue->SetHelpText( "Save any modified container files." );
    xOverlay.AddDialog( pxNewDialogue );
	xOffset.x += 9.0f;

	pxNewDialogue = new Asura_Function_Dialog( "Reload", xOffset, Tools_Container_System::Reload );
	pxNewDialogue->SetHelpText( "Reload the containers from the local disk and network." );
	xOverlay.AddDialog( pxNewDialogue );
	xOffset.x -= 23.0f;

    xOffset.y += Asura_Dialog::DefaultSpacing();
	xOverlay.MakeDialogsRelativeToResizableDialog();

	xOverlay.SetContextMenuCallback(AddContextMenu, 0);
}

////////////////////
// AddContextMenu //
////////////////////
void Tools_Container_Overlay_Manager::AddContextMenu(class Tools_ContextMenu& xMenu, const class Tools_ContextMenu_Context& xContext)
{
	// Make sure selections are up-to-date.
	Tools_Container_System::GetOverlayManager().UpdateOverlay();

	Tools_ContextMenu_Item* pxItem = 0;
	Tools_ContextMenu_Section* pxSection = 0;

	const bool bContainerSelected = IsContainerSelectionValid();
	const bool bGroupSelected     = IsGroupOnlySelectionValid();

	const bool bContainerEditable = IsContainerSelectionValidAndWriteable();
	const bool bGroupEditable     = IsGroupOnlySelectionValidAndWriteable();

	// Use the section menu for a tenuously named 'edit' for now
	pxSection = xMenu.GetSection(TOOLS_CONTEXTMENU_SECTION_SELECTION);
	pxSection->SetHeading("Edit menu");

	// Edit
	if (bContainerSelected)
	{
		pxItem = new Tools_ContextMenu_Item_Function("Edit", ContextMenu_Edit);
		pxItem->SetEnabled(true);
		pxItem->SetHelpText("Edit this container.");
		pxSection->AddItem(pxItem);
		pxSection->AddSeparator();
	}

	// Rename
	pxItem = new Tools_ContextMenu_Item_Function("Rename", ContextMenu_Rename);
	pxItem->SetEnabled(bContainerEditable || bGroupEditable);
	if (bContainerSelected)
	{
		pxItem->SetHelpText("Edit this container.");
	}
	else if (bGroupSelected)
	{
		pxItem->SetHelpText("Edit this group.");
	}
	pxSection->AddItem(pxItem);
	pxSection->AddSeparator();

	// Remove/delete
	if (bContainerSelected)
	{	
		pxItem = new Tools_ContextMenu_Item_Function("Delete Container", ContextMenu_Delete);
		pxItem->SetEnabled(bContainerEditable);
		pxItem->SetHelpText("Delete this container, and any children, if they are no longer being used and have group-scope.");
		pxSection->AddItem(pxItem);
		pxSection->AddSeparator();
	}
	else if (bGroupSelected)
	{
		pxItem = new Tools_ContextMenu_Item_Function("Delete Group", ContextMenu_Delete);
		pxItem->SetEnabled(bGroupEditable);
		pxItem->SetHelpText("Delete this group and all of its containers.");
		pxSection->AddItem(pxItem);
		pxSection->AddSeparator();
	}
	
	// Toggle scope
	if (bContainerSelected)
	{
		pxItem = new Tools_ContextMenu_Item_Function("Toggle scope", ContextMenu_ToggleScope);
		pxItem->SetEnabled(Tools_Container_System::IsValidForToggleScope());
		pxItem->SetHelpText("Toggle container between group and global scope.");
		pxSection->AddItem(pxItem);
		pxSection->AddSeparator();
	
		pxItem = new Tools_ContextMenu_Item_Function("Open in mode", ContextMenu_OpenInMode);
		pxItem->SetEnabled(IsContainerValidForOpenInMode());
		pxItem->SetHelpText("Open selected collection in container mode.");
		pxSection->AddItem(pxItem);
	}
	else if (bGroupSelected)
	{
		pxItem = new Tools_ContextMenu_Item_Function("Open in mode", ContextMenu_OpenGroupInMode);
		pxItem->SetEnabled(IsGroupValidForOpenInMode());
		pxItem->SetHelpText("If the selected group has a single global-scope collection, then open that in container mode.");
		pxSection->AddItem(pxItem);
	}

	// Use the section menu for a tenuously named 'create' for now
	pxSection = xMenu.GetSection(TOOLS_CONTEXTMENU_SECTION_OBJECT);
	pxSection->SetHeading("Create menu");

	pxItem = new Tools_ContextMenu_Item_Function("Create File", ContextMenu_CreateFile);
	pxItem->SetEnabled(true);
	pxItem->SetHelpText("Create a new file.");
	pxSection->AddItem(pxItem);

	pxItem = new Tools_ContextMenu_Item_Function("Create Group", ContextMenu_CreateGroup);
	pxItem->SetEnabled(IsFileSelectionValidAndWriteable());
	pxItem->SetHelpText("Create a new group in the currently selected file.");
	pxSection->AddItem(pxItem);

	pxItem = new Tools_ContextMenu_Item_Function("Create Container", ContextMenu_CreateContainer);
	pxItem->SetEnabled(IsGroupSelectionValidAndWriteable());
	pxItem->SetHelpText("Create a new container in the currently selected group.");
	pxSection->AddItem(pxItem);
}

void Tools_Container_Overlay_Manager::ContextMenu_Edit()
{
	if (IsContainerSelectionValid())
	{
		Tools_Container_System::EditSelectedContainer();
	}
}

void Tools_Container_Overlay_Manager::ContextMenu_Rename()
{
	const bool bContainerEditable = IsContainerSelectionValidAndWriteable();
	const bool bGroupEditable     = IsGroupOnlySelectionValidAndWriteable();
	if (bContainerEditable)
	{
		Tools_Container_System::RenameSelectedContainer();
	}
	else if (bGroupEditable)
	{
		Tools_Container_System::RenameSelectedGroup();
	}
}

void Tools_Container_Overlay_Manager::ContextMenu_Delete()
{
	const bool bContainerEditable = IsContainerSelectionValidAndWriteable();
	const bool bGroupEditable     = IsGroupOnlySelectionValidAndWriteable();
	if (bContainerEditable)
	{
		Tools_Container_System::DeleteSelectedContainer();
	}
	else if (bGroupEditable)
	{
		Tools_Container_System::DeleteSelectedGroup();
	}
}

void Tools_Container_Overlay_Manager::ContextMenu_ToggleScope()
{
	if (IsContainerSelectionValidAndWriteable())
	{
		Tools_Container_System::ToggleScope();
	}
}

void Tools_Container_Overlay_Manager::ContextMenu_CreateFile()
{
	Tools_Container_System::CreateNewFile();
}

void Tools_Container_Overlay_Manager::ContextMenu_CreateGroup()
{
	if (IsFileSelectionValidAndWriteable())
	{
		Tools_Container_System::CreateNewGroup();
	}
}

void Tools_Container_Overlay_Manager::ContextMenu_CreateContainer()
{
	if (IsGroupSelectionValidAndWriteable())
	{
		Tools_Container_System::CreateNewContainer();
	}
}

void Tools_Container_Overlay_Manager::ContextMenu_OpenInMode()
{
	if (IsContainerValidForOpenInMode())
	{
		Tools_Mode_Container::RequestModeForSelectedContainer(s_uSelectedContainerHash);
	}
}

bool Tools_Container_Overlay_Manager::IsContainerValidForOpenInMode()
{
	const Tools_Container* pxContainer = Tools_Container_System::FindContainerByHash(s_uSelectedContainerHash);
	if (pxContainer /*&& !pxContainer->IsGroupScope()*/)
	{
		// Only collections can be opened in container mode.
		return (pxContainer->GetClassification() == AsuraContainerClass_Collection);
	}

	return false;
}

void Tools_Container_Overlay_Manager::ContextMenu_OpenGroupInMode()
{
	const Asura_Hash_ID uContainerHash = GetSingleGlobalCollectionFromGroup();
	if (uContainerHash != ASURA_HASH_ID_UNSET)
	{
		Tools_Mode_Container::RequestModeForSelectedContainer(uContainerHash);
	}
}

// Search the group for a single global-scope collection (this is the most common case).
// If found then return its container hash.
Asura_Hash_ID Tools_Container_Overlay_Manager::GetSingleGlobalCollectionFromGroup()
{
	if (IsGroupOnlySelectionValid())
	{
		return Tools_Container_System::GetSingleGlobalCollectionFromGroup(GetSelectedGroupHash());
	}
	return ASURA_HASH_ID_UNSET;
}

bool Tools_Container_Overlay_Manager::IsGroupValidForOpenInMode()
{
	const Asura_Hash_ID uContainerHash = GetSingleGlobalCollectionFromGroup();
	return uContainerHash != ASURA_HASH_ID_UNSET;
}

///////////////////////
// TreeDoubleClicked //
///////////////////////
void Tools_Container_Overlay_Manager::TreeDoubleClicked()
{
	Tools_Container_Overlay_Manager& xOverlay = Tools_Container_System::GetOverlayManager();

	xOverlay.CloseEditOverlay();
	xOverlay.UpdateOverlay();
	xOverlay.EditSelectedContainer();
}

/////////////////////////
// DragAndDropTreeItem //
/////////////////////////
void Tools_Container_Overlay_Manager::DragAndDropTreeItem(Asura_TreeDialog_Item* pxDropTarget)
{
	if (!pxDropTarget) return;

	const Asura_Hash_ID uDropTargetHash = static_cast<Asura_Hash_ID>(pxDropTarget->GetUserData());
	if (uDropTargetHash == ASURA_HASH_ID_UNSET) return;

	Asura_TreeDialog_Item* pxSelected = Tools_Container_System::GetOverlayManager().GetTree().GetSelectedItem();
	if (!pxSelected) return;

	const Asura_Hash_ID uSelectedHash = static_cast<Asura_Hash_ID>(pxSelected->GetUserData());
	if (uSelectedHash == ASURA_HASH_ID_UNSET) return;

	// Note: Depth: 1 = file, 2 = group, 3 = container.
	const int iTargetDepth = pxDropTarget->GetDepth();
	const int iSelectedDepth = pxSelected->GetDepth();

	if (iSelectedDepth == 2 && iTargetDepth == 1)
	{
		// Move selected group to another file.
		MoveGroup(uSelectedHash, uDropTargetHash);
	}
	else if (iSelectedDepth == 3 && iTargetDepth == 2)
	{
		// Move selected container to another group.
		MoveContainer(uSelectedHash, uDropTargetHash);
	}	
}

///////////////
// MoveGroup //
///////////////
// Move selected group to another file.
void Tools_Container_Overlay_Manager::MoveGroup(Asura_Hash_ID uSelectedGroupHash, Asura_Hash_ID uTargetFileHash)
{
	if (uSelectedGroupHash != s_uSelectedGroupHash) return;	// Mismatch
	if (!IsGroupOnlySelectionValidAndWriteable()) return;	// Group not writeable

	Tools_ContainerFile* pxNewFile = Tools_Container_System::FindFileByHash(uTargetFileHash);
	if (!pxNewFile || !pxNewFile->IsWriteable())
	{
		return; // Destination file not writeable
	}

	Tools_ContainerGroup* pxGroup = Tools_Container_System::FindGroupByHash(uSelectedGroupHash);
	if (!pxGroup) return;

	if (pxNewFile->GetFileHash() == pxGroup->GetFileHash())
	{
		return; // Group is already in this file.
	}

	const Asura_Hash_ID uOldFileHash = pxGroup->GetFileHash();
	Tools_ContainerFile* pxOldFile = Tools_Container_System::FindFileByHash(uOldFileHash);

	Tools_Container_System::MoveSelectedGroup_Helper(pxGroup, pxOldFile, pxNewFile);
}

///////////////////
// MoveContainer //
///////////////////
// Move container to another group.
void Tools_Container_Overlay_Manager::MoveContainer(Asura_Hash_ID uSelectedContainerHash, Asura_Hash_ID uTargetGroupHash)
{
	if (uSelectedContainerHash != s_uSelectedContainerHash) return;	// Mismatch
	
	Tools_Container_System::MoveSelectedContainer_Helper(uSelectedContainerHash, uTargetGroupHash);
}

///////////////////////////////
// IsContainerValidForMoving //
///////////////////////////////
bool Tools_Container_Overlay_Manager::IsContainerValidForMoving()
{
	return Tools_Container_System::IsContainerValidForMoving(s_uSelectedContainerHash);
}

//////////////////////////
// GetTreeItemIconIndex //
//////////////////////////
// Get icon for item in file tree.
int Tools_Container_Overlay_Manager::GetTreeItemIconIndex(Asura_TreeDialog_Item* pxItem)
{
	if (pxItem)
	{
		const Asura_Hash_ID uHash = static_cast<Asura_Hash_ID>(pxItem->GetUserData());
		const int iDepth = pxItem->GetDepth();

		if (iDepth == 1)
		{
			// File. Show writeable/read-only status.
			const Tools_ContainerFile* pxFile = Tools_Container_System::FindFileByHash(uHash);
			if (pxFile)
			{
				if (pxFile->IsWriteable())
				{
					return ASURA_TREEDIALOG_ICON_WRITEABLEFILE;
				}
				else
				{
					return ASURA_TREEDIALOG_ICON_READONLYFILE;
				}
			}
		}
		else if (iDepth == 2)
		{
			// Folders. Show open/closed status.
			if (pxItem->IsExpanded() && pxItem->GetNumChildren())
			{
				return ASURA_TREEDIALOG_ICON_OPENEDFOLDER;
			}
			else
			{
				return ASURA_TREEDIALOG_ICON_CLOSEDFOLDER;
			}
		}
		else if (iDepth == 3)
		{
			const Tools_Container* pxContainer = Tools_Container_System::FindContainerByHash(uHash);
			if (pxContainer && !pxContainer->IsGroupScope())
			{
				// Global scope.
				return ASURA_TREEDIALOG_ICON_VISIBLE;	
			}
		}
	}

	return -1;
}

////////////////////
// RenameTreeItem //
////////////////////
// Rename an item in the file overlay by clicking on it.
void Tools_Container_Overlay_Manager::RenameTreeItem(Asura_TreeDialog_Item* pxItem, const char* szName)
{
	if (!pxItem) return;
	if (!szName) return;

	const Tools_ContainerItemReference xRef(pxItem);
	if (!xRef.m_pxFile) return;

	if (!xRef.m_pxFile->IsWriteable())
	{
		Tools_Timed_Message_System::AddMessage(Tools_Timed_Message_System::COLOUR_ERROR , "File '%s' is read-only.", xRef.m_pxFile->GetFilename().GetString());
		return;
	}

	const int iDepth = pxItem->GetDepth();
	if (iDepth == 1)
	{
		Tools_Timed_Message_System::AddMessage(Tools_Timed_Message_System::COLOUR_WARNING , "Please rename container files in Perforce and/or Windows explorer.");
		return;
	}

	Asura_String xUserString(szName);

	if (iDepth == 2)
	{
		// Renaming a group.
		if (!xRef.m_pxGroup) return;

		// Check validity of string, also strip whitespace etc.
		const bool bIsValid = Tools_Container_System::IsGroupNameValid(xUserString, *xRef.m_pxGroup, false);
		if (xUserString.IsBlank()) return;
		
		if ( !bIsValid )
        {
			Tools_Timed_Message_System::AddMessage(Tools_Timed_Message_System::COLOUR_ERROR , "Please choose a name that is not already being used.");
			return;
        }

		Tools_Container_System::RenameGroup(xUserString, xRef.m_pxGroup);
	}
	else if (iDepth == 3)
	{
		// Renaming a container.
		if (!xRef.m_pxContainer || !xRef.m_pxGroup) return;

		// Check validity of string, also strip whitespace etc.
		const bool bIsValid = Tools_Container_System::IsContainerNameValid(xUserString, xRef.m_pxGroup, xRef.m_uContainerHash);
		if (xUserString.IsBlank()) return;
		
        if ( !bIsValid )
        {
			Tools_Timed_Message_System::AddMessage(Tools_Timed_Message_System::COLOUR_ERROR , "Please choose a name that is not already being used.");
			return;
        }

		Tools_Container_System::RenameContainer(xUserString, xRef.m_pxContainer);
	}
}


///////////////////
// UpdateOverlay //
///////////////////
void Tools_Container_Overlay_Manager::UpdateOverlay()
{
	if (!m_bOverlayIsOpen && !Tools_System::IsInContainerMode())
	{
		// Clear the selection
		s_uSelectedFileHash      = ASURA_HASH_ID_UNSET;
		s_uSelectedGroupHash     = ASURA_HASH_ID_UNSET;
		s_uSelectedContainerHash = ASURA_HASH_ID_UNSET;

		m_bEditOverlayBringToFront = false;
		m_bEditOverlayNeedToRebuild = false;
		return;
	}

	const Tools_ContainerItemReference xRef(m_xTree.GetSelectedItem());

	const Tools_ContainerFile* const pxFile = xRef.m_pxFile;
	const Tools_ContainerGroup* const pxGroup = xRef.m_pxGroup;
	const Tools_Container* const pxContainer = xRef.m_pxContainer;
	
	const bool bChangedFile		 = (xRef.m_uFileHash != s_uSelectedFileHash);
	const bool bChangedGroup     = (xRef.m_uGroupHash != s_uSelectedGroupHash);
	const bool bChangedContainer = (xRef.m_uContainerHash != s_uSelectedContainerHash);

	const bool bChanged = (bChangedFile || bChangedGroup || bChangedContainer);

	if (bChanged)
	{
		Asura_Overlay* pxOverlay = Asura_Overlay_System::GetOverlay(reinterpret_cast< u_int >( this ));
	
		// Update the selected file dialogue.
		if (bChangedFile)
		{
			if (pxOverlay && m_pxSelectFileDialog)
			{
				Asura_String256 xBuffer("File : %s", (pxFile ? pxFile->GetFilename().GetString() : "") );
				if (pxFile && !pxFile->IsWriteable())
				{
					xBuffer += "  [read only]";
				}
				pxOverlay->UpdateDialogLabel(m_pxSelectFileDialog, xBuffer.GetString());
			}
		}

		// Update the selected group dialogue.
		if (bChangedGroup)
		{
			if (pxOverlay && m_pxSelectGroupDialog)
			{
				Asura_String256 xBuffer("Group : %s", (pxGroup ? pxGroup->GetGroupName().GetString() : "") );
				pxOverlay->UpdateDialogLabel(m_pxSelectGroupDialog, xBuffer.GetString());
			}
		}

		// Update the selected container dialogue.
		if (bChangedContainer)
		{
			if (pxOverlay && m_pxSelectContainerDialog)
			{
				Asura_String256 xBuffer("Container : %s", (pxContainer ? pxContainer->GetContainerName().GetString() : "") );
				pxOverlay->UpdateDialogLabel(m_pxSelectContainerDialog, xBuffer.GetString());
			}

			if (pxOverlay && m_pxSelectTypeDialog)
			{
				Asura_String256 xBuffer("Type : %s", (pxContainer ? pxContainer->GetDescription() : "") );
				pxOverlay->UpdateDialogLabel(m_pxSelectTypeDialog, xBuffer.GetString());
			}

			// Change editing overlay, but we don't want to change the focus normally.
			if (m_bEditOverlayIsOpen)
			{
				OpenEditOverlay(xRef.m_uContainerHash, m_bEditOverlayBringToFront);
				m_bEditOverlayNeedToRebuild = false;
			}

			Tools_Mode_Container::SelectInstanceContainer(xRef.m_uContainerHash);
		}
	}

	if (m_bEditOverlayNeedToRebuild && m_bEditOverlayIsOpen)
	{
		OpenEditOverlay(xRef.m_uContainerHash, m_bEditOverlayBringToFront);
	}

	// Store the current selection
	s_uSelectedFileHash      = xRef.m_uFileHash;
	s_uSelectedGroupHash     = xRef.m_uGroupHash;
	s_uSelectedContainerHash = xRef.m_uContainerHash;
	m_bEditOverlayBringToFront = false;
	m_bEditOverlayNeedToRebuild = false;
}

//////////////////////
// RefreshContainer //
//////////////////////
// Refresh things that have been renamed.
void Tools_Container_Overlay_Manager::RefreshContainer(const Tools_Container* pxContainer)
{
	if (!pxContainer) return;

	const Asura_Hash_ID uContainerHash = pxContainer->GetContainerHash();

	// Refresh the tree dialogue.
	Asura_TreeDialog_Item* pxItem = GetContainerItem(uContainerHash);
	if (pxItem)
	{
		pxItem->SetName(pxContainer->GetContainerName().GetString());
	}

	// Refresh the edit overlay.
	if (IsEditOverlayOpen() && m_uEditingContainerHash == uContainerHash)
	{
		CloseEditOverlay();
		EditSelectedContainer();
	}

	// Refresh the labels.
	if (IsOverlayOpen() && s_uSelectedContainerHash == uContainerHash)
	{
		s_uSelectedContainerHash = ASURA_HASH_ID_UNSET;
		UpdateOverlay();
		Asura_Assert(s_uSelectedContainerHash == uContainerHash, "Container not reselected!");
	}
}

//////////////////
// RefreshGroup //
//////////////////
void Tools_Container_Overlay_Manager::RefreshGroup(const Tools_ContainerGroup* pxGroup)
{
	if (!pxGroup) return;

	const Asura_Hash_ID uGroupHash = pxGroup->GetGroupHash();

	// Refresh the tree dialogue.
	Asura_TreeDialog_Item* pxItem = GetGroupItem(uGroupHash);
	if (pxItem)
	{
		pxItem->SetName(pxGroup->GetGroupName().GetString());
	}

	// Refresh the labels.
	if (IsOverlayOpen() && s_uSelectedGroupHash == uGroupHash)
	{
		s_uSelectedGroupHash = ASURA_HASH_ID_UNSET;
		UpdateOverlay();
		Asura_Assert(s_uSelectedGroupHash == uGroupHash, "Group not reselected!");
	}
}

///////////////////////
// ToggleEditOverlay //
///////////////////////
void Tools_Container_Overlay_Manager::ToggleEditOverlay()
{
	if (IsEditOverlayOpen())
	{
		CloseEditOverlay();
	}
	else
	{
		OpenEditOverlay(s_uSelectedContainerHash, true);
	}
}

/////////////////////
// OpenEditOverlay //
/////////////////////
// Open editing overlay
void Tools_Container_Overlay_Manager::OpenEditOverlay(Asura_Hash_ID uEditContainerHash, bool bSortToFront /*= true*/)
{
	if (uEditContainerHash == ASURA_HASH_ID_UNSET) return;

	if (m_uEditingContainerHash != ASURA_HASH_ID_UNSET)
	{
		// Close existing overlay if it is open.
		CloseEditOverlay();
	}

	// Load data concerning the overlay. Only need to do this when the overlay is first opened.
	TransferModeData(TOOLSMODEDATAFILE_LOAD);

	m_uEditingContainerHash = uEditContainerHash;
	Tools_Container* pxContainer = Tools_Container_System::FindContainerByHash(m_uEditingContainerHash);
	if (pxContainer)
	{
		pxContainer->CreateOverlay(bSortToFront);
	}

	s_uSelectedContainerHash = m_uEditingContainerHash;

	m_bEditOverlayIsOpen = true;
}

//////////////////////
// CloseEditOverlay //
//////////////////////
// Close editing overlay if it exists.
void Tools_Container_Overlay_Manager::CloseEditOverlay()
{
	// Destroy the overlay.
	Tools_Container* pxContainer = Tools_Container_System::FindContainerByHash(m_uEditingContainerHash);
	if (pxContainer)
	{
		pxContainer->DestroyOverlay();
	}

	// Finish tidying up.
	ClosingEditByCloseGadget();
}

//////////////////////////////
// ClosingEditByCloseGadget //
//////////////////////////////
void Tools_Container_Overlay_Manager::ClosingEditByCloseGadget()
{
	if (m_bEditOverlayIsOpen)
	{
		// Save data concerning the overlay, in case it has changed.
		TransferModeData(TOOLSMODEDATAFILE_SAVE);
	}

	m_uEditingContainerHash = ASURA_HASH_ID_UNSET;
	m_bEditOverlayIsOpen = false;
}

////////////////////
// FindItemByData //
////////////////////
// Find the item by looking for the data hash value, but only check the children of the specified start item.
// Does not search recursively.
Asura_TreeDialog_Item* Tools_Container_Overlay_Manager::FindItemByData(Asura_TreeDialog_Item* pxStartItem, Asura_Hash_ID uSearchHash)
{
	if (pxStartItem && uSearchHash != ASURA_HASH_ID_UNSET)
	{
		for (Asura_TreeDialog_Item* pxItem = pxStartItem->GetChild(); pxItem ; pxItem = pxItem->GetSibling() )
		{
			if ( static_cast<Asura_Hash_ID>(pxItem->GetUserData()) == uSearchHash)
			{
				return pxItem;
			}
		}
	}
	return 0;
}

/////////////////
// GetFileItem //
/////////////////
// Find the file. Depth 1 in the tree.
Asura_TreeDialog_Item* Tools_Container_Overlay_Manager::GetFileItem(Asura_Hash_ID uFileHash)
{
	return FindItemByData(m_xTree.GetRoot(), uFileHash);
}

//////////////////
// GetGroupItem //
//////////////////
// Find the group. Depth 2 in the tree.
Asura_TreeDialog_Item* Tools_Container_Overlay_Manager::GetGroupItem(Asura_Hash_ID uGroupHash)
{
	const Tools_ContainerGroup* pxGroup = Tools_Container_System::FindGroupByHash(uGroupHash);
	if (pxGroup)
	{
		const Asura_Hash_ID uFileHash = pxGroup->GetFileHash();
		Asura_TreeDialog_Item* pxFileItem = GetFileItem(uFileHash);
		if (pxFileItem)
		{
			return FindItemByData(pxFileItem, uGroupHash);
		}
	}

	return 0;
}

//////////////////////
// GetContainerItem //
//////////////////////
// Find the container. Depth 3 in the tree.
Asura_TreeDialog_Item* Tools_Container_Overlay_Manager::GetContainerItem(Asura_Hash_ID uContainerHash)
{
	const Tools_Container* pxContainer = Tools_Container_System::FindContainerByHash(uContainerHash);
	if (pxContainer)
	{
		const Asura_Hash_ID uGroupHash = pxContainer->GetGroupHash();
		Asura_TreeDialog_Item* pxGroupItem = GetGroupItem(uGroupHash);
		if (pxGroupItem)
		{
			return FindItemByData(pxGroupItem, uContainerHash);
		}
	}

	return 0;
}

/////////////////////
// IsFileWriteable //
/////////////////////
bool Tools_Container_Overlay_Manager::IsFileWriteable()
{
	const float fTimeBetweenChecks = 10.0f;
	static float s_fNextCheckTime = 0.0f;

	const Tools_ContainerFile* pxFile = Tools_Container_System::FindFileByHash(s_uSelectedFileHash);
	if (pxFile)
	{
		// Periodically refresh the writeable status of the current file.
		if (Asura_Timers::GetLocalUnscaledTime() >= s_fNextCheckTime)
		{
			s_fNextCheckTime = Asura_Timers::GetLocalUnscaledTime() + fTimeBetweenChecks;
			pxFile->RefreshWriteableStatus();
		}

		return pxFile->IsWriteable();
	}

	return false;
}

///////////////////////////////////////////////////////////////////////////////
// Tools_Container_System
///////////////////////////////////////////////////////////////////////////////

// Static data
Tools_Container_Overlay_Manager Tools_Container_System::s_xOverlayManager;

bool Tools_Container_System::s_bInitialised = false;

Asura_TemplateHashTree<Tools_ContainerFile> Tools_Container_System::s_xFileTree;
Asura_TemplateHashTree<Tools_ContainerGroup> Tools_Container_System::s_xGroupTree;
Asura_TemplateHashTree<Tools_Container*> Tools_Container_System::s_xContainerTree;

const char* Tools_Container_System::s_pxCurrentFilename = 0;

int Tools_Container_System::s_iNumErrors = 0;


////////////////
// Initialise //
////////////////
void Tools_Container_System::Initialise()
{
	if (s_bInitialised) return;
	s_bInitialised = true;

	LoadAllFiles();

	s_xOverlayManager.TransferModeData(TOOLSMODEDATAFILE_LOAD);
}

//////////////
// Shutdown //
//////////////
void Tools_Container_System::Shutdown()
{
	if (!s_bInitialised) return;
	s_bInitialised = false;

	if (IsOverlayOpen())
	{
		ToggleOverlay();
	}

	s_xOverlayManager.CloseEditOverlay();
	SavePrompt(true);

	DeleteAllContainers();

	s_xOverlayManager.TransferModeData(TOOLSMODEDATAFILE_SAVE);
}

////////////
// Update //
////////////
void Tools_Container_System::Update()
{
	if (!s_bInitialised) return;

	// If the editing overlay has changed then update the relevant container.
	if (Tools_Container::IsEditOverlayChanged())
	{
		Tools_Container* pxContainer = FindContainerByHash(GetOverlayManager().GetEditingContainerHash());
		if (pxContainer)
		{
			pxContainer->SetModified();
		}
	}

	s_xOverlayManager.UpdateOverlay();
}

//////////////////
// LoadAllFiles //
//////////////////
// Scan directory for container files and load them.
void Tools_Container_System::LoadAllFiles()
{
	// Make sure that everything has been deleted before we reload the files.
	DeleteAllContainers();
	// Make sure that we delete the Asura containers too.
	Asura_Container_System::DeleteAllContainers();

	List<Asura_String> xFileList;
	Tools_FileSystem::FindFilesLocalAndNetwork( szCONTAINERS_WILDCARD, xFileList, TOOLS_FILEFIND_RECURSIVE | TOOLS_FILEFIND_IGNOREBACKUPDIR );

	Asura_NameTree xNameTree;

	// Load the files in the list.
	for(LIF<Asura_String> xFileLIF(&xFileList); !xFileLIF.done(); xFileLIF.next())
	{
		// Make sure this file hasn't already been dealt with.
		const char* const szName = Asura_FileAccess::PlatformRemoveNetworkOrExecutableDir(xFileLIF().GetString());	

		int iJunkIndex;
		if (xNameTree.FindNodeIndexByName(szName, true, iJunkIndex)) continue;
	
		// The container group should add itself to s_xGroupTree on being loaded.
		s_pxCurrentFilename = szName;
		LoadAsuraFile(xFileLIF().GetString());
		s_pxCurrentFilename = 0;
	}

	// Fix up a few things that can't be done until all the containers have been loaded.
	PostLoadInitialise();
}

////////////////////////
// PostLoadInitialise //
////////////////////////
void Tools_Container_System::PostLoadInitialise()
{
	for ( Asura_TemplateHashTree_Iterator<Tools_Container*> xIt(s_xContainerTree) ; !xIt.Done(); xIt.Next() )
	{
		Tools_Container** ppxContainer = xIt.GetCurrent();
		if (!ppxContainer) continue;
		
		Tools_Container* const pxContainer = (*ppxContainer);
		if (pxContainer)
		{
			pxContainer->PostLoadInitialise();
		}
	}
}

/////////////////////////
// DeleteAllContainers //
/////////////////////////
// Delete all of the containers and reset the tree.
void Tools_Container_System::DeleteAllContainers()
{
	// Make sure that there are no instances left hanging around.
	Tools_Entity_Container::DestroyAllInstances();
	Tools_Mode_Container::DestroyAllInstances();

	// Delete all of the Tools containers.
	Asura_TemplateHashTree_Iterator<Tools_Container*> xIt(s_xContainerTree);
	for ( ; !xIt.Done(); xIt.Next() )
	{
		Tools_Container** ppxContainer = xIt.GetCurrent();
		if (!ppxContainer) continue;
		
		const Tools_Container* pxContainer = (*ppxContainer);
		if (pxContainer)
		{
			*ppxContainer = 0;
			delete pxContainer;
		}
	}

	// Clear the trees.
	s_xFileTree.Reset();
	s_xGroupTree.Reset();
	s_xContainerTree.Reset();

	s_xOverlayManager.GetTree().DeleteAll();
}


/////////////////////////
// SaveAndCloseOverlay //
/////////////////////////
void Tools_Container_System::SaveAndCloseOverlay()
{
	SaveModified();

	if (IsOverlayOpen())
	{
		ToggleOverlay();
	}

	// Close editing overlay if it is open.
	if (!Tools_System::IsInContainerMode())
	{
		s_xOverlayManager.CloseEditOverlay();
	}
}


////////////////
// IsModified //
////////////////
// Are any of the files modified?
bool Tools_Container_System::IsModified()
{
	Asura_TemplateHashTree_ConstIterator<Tools_ContainerFile> xIt(s_xFileTree);
	for ( ; !xIt.Done() ; xIt.Next())
	{
		const Tools_ContainerFile* pxFile = xIt.GetCurrent();
		if (pxFile->IsModified())
		{
			return true;
		}
	}

	return false;
}

//////////////////
// SaveModified //
//////////////////
void Tools_Container_System::SaveModified()
{
	// Loop over files saving modified ones.
	bool bModified = false;

	Asura_TemplateHashTree_Iterator<Tools_ContainerFile> xIt(s_xFileTree);
	for ( ; !xIt.Done() ; xIt.Next())
	{
		Tools_ContainerFile* pxFile = xIt.GetCurrent();
		if (pxFile->IsModified())
		{
			SaveFile(pxFile);
			bModified = true;
		}
	}

	if (!bModified)
	{
		Tools_Timed_Message_System::AddMessage( Tools_Timed_Message_System::COLOUR_INFO, "No containers have been modified - nothing to save.");
	}
}

////////////////
// SavePrompt //
////////////////
void Tools_Container_System::SavePrompt(bool bLastChanceToSave)
{
	if (IsModified())
	{
		YesNo_Overlay xPrompt(bLastChanceToSave ? "Save modified containers? (if 'No' then changes will be lost)" : "Save modified containers?");
		if (xPrompt.DoModal())
		{
			SaveModified();
		}
	}
}

//////////////
// SaveFile //
//////////////
void Tools_Container_System::SaveFile(Tools_ContainerFile* pxFile)
{
	Asura_Assert(pxFile, "Null pointer");
	if (!pxFile) return;

	if (pxFile->GetFilename().IsBlank())
	{
		Asura_Assert(0, "Blank filename");
		return;
	}

	const Asura_Hash_ID uFileHash = pxFile->GetFileHash();
	if (uFileHash == ASURA_HASH_ID_UNSET)
	{
		Asura_Assert(0, "File hash is not set");
		return;
	}

	if (!pxFile->CanSave())
	{
		// File wasn't writeable. Display error message.
		return;
	}
	
	Asura_SaveBlock xSaveBlock;

	// Header chunk for file.
	Asura_Chunk_ToolsContainerFile::Create(pxFile, &xSaveBlock);

	// Iterate over the groups looking for ones in this file.
	{
		Asura_TemplateHashTree_SaveIterator xIt(s_xGroupTree.GetNumberOfNodes());
		for ( ; !xIt.Done() ; xIt.Next())
		{
			const Tools_ContainerGroup* pxGroup = s_xGroupTree.GetDataByIndex(xIt.GetCurrent());
			if (pxGroup && pxGroup->GetFileHash() == uFileHash)
			{
				// Chunk for group.
				Asura_Chunk_ToolsContainerGroup::Create(pxGroup, &xSaveBlock);
			}
		}

	}


	// Iterate over container tree looking for containers in this file.
	{
		Asura_TemplateHashTree_SaveIterator xIt(s_xContainerTree.GetNumberOfNodes());
		for ( ; !xIt.Done() ; xIt.Next())
		{
			const Tools_Container* const * ppxContainer = s_xContainerTree.GetDataByIndex(xIt.GetCurrent());
			if (ppxContainer)
			{
				const Tools_Container* pxContainer = *ppxContainer;
				if (pxContainer && pxContainer->GetFileHash() == uFileHash)
				{
					// Chunk for container.
					Asura_Chunk_ToolsContainer::Create(pxContainer, &xSaveBlock);
				}
			}
		}
	}

	// Finally save the file to disk.
	const char* const szFilename = pxFile->GetFilename().GetString();

	bool bOk = xSaveBlock.AddAsuraTerminator();
	if (bOk)
	{
		Tools_FileSystem::EnsureDirectoryExists(szFilename);
		bOk = xSaveBlock.SaveFile( szFilename, false );
	}

	if (bOk)
	{
		pxFile->ClearModified();
		Tools_Timed_Message_System::AddMessage( 0x00ff00, "Saved container file '%s'.", szFilename);
	}
	else if (Tools_System::IsShuttingDown())
	{
		// Tools is shutting down, the timed messages won't display. Pop up a dialogue box instead.
		Asura_String256 xBuffer("Failed to save '%s'.", szFilename);
		Message_Overlay xMessage(xBuffer.GetString());
		xMessage.DoModal();
	}
	else
	{
		Tools_Timed_Message_System::AddMessage( Tools_Timed_Message_System::COLOUR_ERROR, "Failed to save container file '%s'.", szFilename);
	}
}

///////////////////
// CreateNewFile //
///////////////////
void Tools_Container_System::CreateNewFile()
{
	Tools_ContainerFile xFile;
	xFile.SetFilename(szCONTAINERS_NEWFILE);

	// Prompt the user for a filename.
	bool bOk = SetFilenameDialogue(xFile);
	if (!bOk) return; // Cancelled

	RegisterFile(xFile, true);
}

////////////////////
// CreateNewGroup //
////////////////////
void Tools_Container_System::CreateNewGroup()
{
	Asura_Hash_ID uDummy = ASURA_HASH_ID_UNSET;
	CreateNewGroup(Tools_Container_Overlay_Manager::GetSelectedFileHash(), uDummy);
}

////////////////////
// CreateNewGroup //
////////////////////
// Create new group in specified file. Return false if cancelled.
bool Tools_Container_System::CreateNewGroup(const Asura_Hash_ID uFileHash, Asura_Hash_ID& uCreatedGroupHash)
{
	if (uFileHash == ASURA_HASH_ID_UNSET) return false;

	Tools_ContainerGroup xGroup(uFileHash, szCONTAINERS_NEWGROUP);

	// Prompt the user for a group name.
	bool bOk = SetGroupNameDialogue(xGroup, true);
	if (bOk)
	{
		xGroup.SetCreationGroupName();
		RegisterGroup(xGroup, true);

		uCreatedGroupHash = xGroup.GetGroupHash();
	}

	return bOk;
}

//////////////////
// RegisterFile //
//////////////////
// Add new file or load an old one.
void Tools_Container_System::RegisterFile(Tools_ContainerFile& xFile, bool bNewFile /*=false*/)
{
	if (!bNewFile)
	{
		// Fill in with the actual filename.
		xFile.SetFilename(s_pxCurrentFilename);
	}

	if (xFile.GetFilename().IsBlank())
	{
		Asura_Assert(0, "Blank filename.");
		return;
	}

	// Generate the short version of the filename.
	xFile.SetShortName(bNewFile);

	Tools_ContainerFile* pxFile = s_xFileTree.AddNode(xFile.GetFileHash(), xFile);
	if (!pxFile)
	{
		Asura_Assertf(0, ("Error: couldn't add filename (%s) to tree.", xFile.GetFilename().GetString()));
		return;
	}

	if (bNewFile)
	{
		pxFile->SetModified();
	}
	pxFile->RefreshWriteableStatus();

	AddFileToDialogueTree(pxFile, bNewFile);
}

///////////////////
// RegisterGroup //
///////////////////
// Add new group or load an old one.
void Tools_Container_System::RegisterGroup(Tools_ContainerGroup& xGroup, bool bNewGroup /*=false*/)
{
	Tools_ContainerGroup* pxGroup = s_xGroupTree.AddNode(xGroup.GetGroupHash(), xGroup);
	if (!pxGroup)
	{
		Tools_Timed_Message_System::AddMessage(Tools_Timed_Message_System::COLOUR_WARNING, "Failed to add container group: %s.", xGroup.GetGroupName().GetString());
		return;
	}

	AddGroupToDialogueTree(&xGroup, bNewGroup);

	if (bNewGroup)
	{
		pxGroup->SetModified();
	}
}


/////////////////////////
// SetFilenameDialogue //
/////////////////////////
// Return true if name chosen, false if cancelled.
bool Tools_Container_System::SetFilenameDialogue(Tools_ContainerFile& xFile)
{
	GetString_Overlay xGetStringOverlay( "Enter filename:", xFile.GetFilename().GetString() );

	Asura_String xUserString;

	while (true)
    {
        if ( !xGetStringOverlay.DoModal() )
        {
			// Cancelled.
            return false;
        }

		// Make copy of the user supplied string, and modify any problem characters that might mess up the filename.
		xUserString = xGetStringOverlay.GetString();
		xUserString.StripWhitespace(Asura_StringUtil::STRIP_BOTH_ENDS);
		xUserString.ReplaceAllChars(':','_');		
		xUserString.TidyFilename();

		// Prepend "Containers/" if necessary.
		const char* const szDirectory = szCONTAINERS_DIRECTORY;
		if (!xUserString.StartsWith(szDirectory, true))
		{
			Asura_String xTemp(xUserString);
			xUserString = szDirectory;
			xUserString += xTemp;
		}

		// Check the extension.
		if (!xUserString.EndsWith(szCONTAINERS_EXTENSION, true))
		{
			Asura_String xPath;
			Asura_String xFile;
			xUserString.SplitFilename(&xPath, &xFile, 0);

			xUserString = xPath;
			xUserString += '\\';
			xUserString += xFile;
			xUserString += szCONTAINERS_EXTENSION;
		}

		// Check for null or zero length filename.
		Asura_String xFileTemp;
		xUserString.SplitFilename(0, &xFileTemp, 0);
		if (xFileTemp.IsBlank())
		{
			Message_Overlay xMessageOverlay( "Please choose a filename containing some alphanumeric characters." );
            xMessageOverlay.DoModal();
            continue;
		}

		xUserString.TidyFilename();

		// Check that the full filename is not already being used.
		bool bDuplicate = false;
		const Tools_ContainerFile* pxFoundFile = FindFileByFilename(xUserString);
		if (pxFoundFile && pxFoundFile != &xFile)
		{
			bDuplicate = true;
		}

		// Check that the short filename is also free.
		if (!bDuplicate)
		{
			Tools_ContainerFile xTempFile;
			xTempFile.SetFilename(xUserString);
			xTempFile.SetShortName(true);
			pxFoundFile = FindFileByHash(xTempFile.GetCreationShortName().GetHash());
			if (pxFoundFile && pxFoundFile != &xFile)
			{
				bDuplicate = true;
			}
		}

		if (bDuplicate)
		{
			Message_Overlay xMessageOverlay( "Please choose a filename that is not already being used." );
            xMessageOverlay.DoModal();
            continue;
		}

		// Done.
		break;
	}

	// Set the string.
	xFile.SetFilename(xUserString.GetString());
	return true;
}



//////////////////////////
// SetGroupNameDialogue //
//////////////////////////
// Return true if name chosen, false if cancelled.
bool Tools_Container_System::SetGroupNameDialogue(Tools_ContainerGroup& xGroup, bool bNewGroup)
{
	GetString_Overlay xGetStringOverlay( "Enter the new container group name:", xGroup.GetGroupName().GetString() );
	
	Asura_String xUserString;

	while (true) 
    {
        if ( !xGetStringOverlay.DoModal() )
        {
			// Cancelled.
            return false;
        }

		// Make copy of the user supplied string.
		xUserString = xGetStringOverlay.GetString();
		
		// Check the name is valid and modify any problem characters that might mess up the group name.
		const bool bIsValid = IsGroupNameValid(xUserString, xGroup, bNewGroup);

		// Check for null or zero length string.
		if (xUserString.IsBlank())
		{
			Message_Overlay xMessageOverlay( "Please enter a string containing some alphanumeric characters." );
            xMessageOverlay.DoModal();
            continue;
		}

        if (!bIsValid)
        {
			// Duplicate name.
            Message_Overlay xMessageOverlay( "Please choose a name that is not already being used." );
            xMessageOverlay.DoModal();
            continue;
        }

		// Done.
		break;
    }

	// Set the string.
	xGroup.SetGroupName(xUserString);
	return true;
}

//////////////////////
// IsGroupNameValid //
//////////////////////
// Checks the supplied string to see whether it would be a valid group name for a new or existing group.
// Input:	xUserString - string to be changed.
//			xGroup - the group being renamed.
//			bNewGroup - is this a newly created group?
// Output:	xUserString - modified input string (to strip out white space etc).
// Returns true if the (modified) string is a valid group name.
bool Tools_Container_System::IsGroupNameValid(Asura_String& xUserString, const Tools_ContainerGroup& xGroup, bool bNewGroup)
{
	// Make copy of the user supplied string, and modify any problem characters that might mess up the group name.
	xUserString.StripWhitespace(Asura_StringUtil::STRIP_BOTH_ENDS);
	xUserString.ReplaceAllChars('.','_');
	xUserString.ReplaceAllChars('/','_');
	xUserString.ReplaceAllChars('\\','_');
	xUserString.ReplaceAllChars(':','_');

	if (xUserString.IsBlank())
	{
		return false;
	}

	if (bNewGroup)
	{
		// New groups need to have a unique creation name.
		Tools_ContainerGroup xTempGroup(xGroup.GetFileHash(), xUserString.GetString());
		xTempGroup.SetCreationGroupName();
		
		const Asura_Hash_ID uNewGroupHash = xTempGroup.GetGroupHash();
		const Tools_ContainerGroup* const pxFoundGroup = s_xGroupTree.GetDataByHash(uNewGroupHash);
		if (pxFoundGroup)
		{
			// Duplicate creation name.
			return false;
		}
	}

	// Check that the file has no groups of the same name.	
	Asura_TemplateHashTree_ConstIterator<Tools_ContainerGroup> xIt(s_xGroupTree);
	for ( ; !xIt.Done() ; xIt.Next())
	{
		const Tools_ContainerGroup* pxGroup = xIt.GetCurrent();
		if (pxGroup && pxGroup->GetFileHash() == xGroup.GetFileHash())
		{
			if (pxGroup != &xGroup)
			{
				// Found a different group in the same file. Compare the names.
				if (xUserString.Compare(pxGroup->GetGroupName(), false) == 0)
				{
					// Duplicate user-visible name.
					return false;
				}
			}
		}
	}

	return true;
}

//////////////////////////////
// SetContainerNameDialogue //
//////////////////////////////
// Return true if name chosen, false if cancelled.
// xString - string to be changed.
// pxGroup - group that the container is in.
// uOldContainerHash - if renaming an existing container then this is the hash, otherwise it is unset.
// bAllowAutoAccept  - if true then the input string will be automatically accepted
//                     (if it isn't a duplicate) without prompting the user.
bool Tools_Container_System::SetContainerNameDialogue(Asura_String& xString,
													  const Tools_ContainerGroup* pxGroup,
													  Asura_Hash_ID uOldContainerHash,
													  bool bAllowAutoAccept /*= false*/)
{
	Asura_Assert(pxGroup, "Null group!");
	if (!pxGroup) return false;

	GetString_Overlay xGetStringOverlay( "Enter the new container name:", xString.GetString() );

	Asura_String xUserString;

	while (true)
    {
        if (!bAllowAutoAccept && !xGetStringOverlay.DoModal() )
        {
			// Cancelled.
            return false;
        }

		bAllowAutoAccept = false;

		xUserString = xGetStringOverlay.GetString();
		
		// Check validity of string, also strip whitespace etc.
		const bool bIsValid = IsContainerNameValid(xUserString, pxGroup, uOldContainerHash);

		// Check for null or zero length string.
		if (xUserString.IsBlank())
		{
			Message_Overlay xMessageOverlay( "Please enter a string containing some alphanumeric characters." );
            xMessageOverlay.DoModal();
            continue;
		}

        if ( !bIsValid )
        {
			// Duplicate name.
            Message_Overlay xMessageOverlay( "Please choose a name that is not already being used." );
            xMessageOverlay.DoModal();
			continue;
        }
		
		// Done.
		break;
    }

	xString = xUserString.GetString();
	return true;
}

//////////////////////////
// IsContainerNameValid //
//////////////////////////
// Checks the supplied string to see whether it would be a valid container name for a new or existing container.
// Input:	xUserString - string to be changed.
//			pxGroup - group that the container is in.
//			uOldContainerHash - if renaming an existing container then this is the hash, otherwise it is unset.
// Output:	xUserString - modified input string (to strip out white space etc).
// Returns true if the (modified) string is a valid container name.
bool Tools_Container_System::IsContainerNameValid(Asura_String& xUserString,
												  const Tools_ContainerGroup* pxGroup,
												  Asura_Hash_ID uOldContainerHash)
{
	if (!pxGroup) return false;

	// Strip whitespace at ends. Remove slashes.
	xUserString.StripWhitespace(Asura_StringUtil::STRIP_BOTH_ENDS);
	xUserString.ReplaceAllChars('/','_');
	xUserString.ReplaceAllChars('\\','_');

	if (xUserString.IsBlank())
	{
		// Blank name.
		return false;
	}

	if (uOldContainerHash == ASURA_HASH_ID_UNSET)
	{
		// Creating a new container.
		// Check that the creation name is not already being used.
		Asura_String xTempCreation;
		Tools_Container::MakeCreationName(xTempCreation, xUserString, pxGroup);

		const Asura_Hash_ID uNewHash = xTempCreation.GetHash();
		const Tools_Container* const pxFoundContainer = FindContainerByHash(uNewHash);
		if (pxFoundContainer)
		{
			// Duplicate creation name.
			return false;
		}
	}

	// Check against the current container names in the group.
	const Asura_Hash_ID uGroupHash = pxGroup->GetGroupHash();
	Asura_TemplateHashTree_ConstIterator<Tools_Container*> xIt(s_xContainerTree);
	for ( ; !xIt.Done() ; xIt.Next())
	{
		const Tools_Container* const * ppxContainer = xIt.GetCurrent();
		if (!ppxContainer) continue;
		const Tools_Container* pxContainer = *ppxContainer;

		if (pxContainer &&
			pxContainer->GetGroupHash() == uGroupHash &&
			pxContainer->GetContainerHash() != uOldContainerHash)
		{
			// Found a container in this group (and it's not the container being renamed).
			if (xUserString.Compare(pxContainer->GetContainerName(), false) == 0)
			{
				// Duplicate user-visible name.
				return false;
			}
		}
	}

	// Name is valid.
	return true;
}


////////////////////
// FindFileByHash //
////////////////////
Tools_ContainerFile* Tools_Container_System::FindFileByHash(Asura_Hash_ID uFilenameHash)
{
	if (uFilenameHash == ASURA_HASH_ID_UNSET) return 0;

	return s_xFileTree.GetDataByHash(uFilenameHash);
}

/////////////////////
// FindGroupByHash //
/////////////////////
// Find the group using the hash of its name. Returns null if not found.
Tools_ContainerGroup* Tools_Container_System::FindGroupByHash(Asura_Hash_ID uGroupHash)
{
	if (uGroupHash == ASURA_HASH_ID_UNSET) return 0;

	return s_xGroupTree.GetDataByHash(uGroupHash);
}

////////////////////////
// FindFileByFilename //
////////////////////////
Tools_ContainerFile* Tools_Container_System::FindFileByFilename(const Asura_String& xFilename)
{
	// Search for a file with the specified filename. Slow, but only used when creating new files.
	// All filenames should use '\' rather than '/'.
	Asura_TemplateHashTree_Iterator<Tools_ContainerFile> xIt(s_xFileTree);
	for ( ; !xIt.Done() ; xIt.Next())
	{
		Tools_ContainerFile* pxFile = xIt.GetCurrent();
		if (!pxFile) continue;
		
		if (xFilename.Compare(pxFile->GetFilename(), true) == 0)
		{
			return pxFile;
		}
	}

	return 0;
}

/////////////////////////////
// FindFileByShortNameHash //
/////////////////////////////
Tools_ContainerFile* Tools_Container_System::FindFileByShortNameHash(Asura_Hash_ID uShortNameHash)
{
	if (uShortNameHash == ASURA_HASH_ID_UNSET) return 0;

	// Search for a file whose current short name matches the hash.
	Asura_TemplateHashTree_Iterator<Tools_ContainerFile> xIt(s_xFileTree);
	for ( ; !xIt.Done() ; xIt.Next())
	{
		Tools_ContainerFile* pxFile = xIt.GetCurrent();
		if (!pxFile) continue;
		
		if (pxFile->GetShortName().GetHash() == uShortNameHash)
		{
			return pxFile;
		}
	}

	return 0;
}

/////////////////////////
// FindContainerByHash //
/////////////////////////
Tools_Container* Tools_Container_System::FindContainerByHash(Asura_Hash_ID uContainerHash)
{
	if (uContainerHash == ASURA_HASH_ID_UNSET) return 0;
	
	Tools_Container** ppxContainer = s_xContainerTree.GetDataByHash(uContainerHash);
	return (ppxContainer ? (*ppxContainer) : 0);
}

/////////////////////
// SetFileModified //
/////////////////////
void Tools_Container_System::SetFileModified(Asura_Hash_ID uFileHash)
{
	Tools_ContainerFile* pxFile = FindFileByHash(uFileHash);
	Asura_Assert(pxFile, "Failed to find container file.");
	if (pxFile)
	{
		pxFile->SetModified();
	}
}

///////////////////////////////
// WarnIfContainerIsReadOnly //
///////////////////////////////
// Check whether file containing container is read only, if so display a warning.
void Tools_Container_System::WarnIfContainerIsReadOnly(Asura_Hash_ID uContainerHash)
{
	const Tools_Container* pxContainer = FindContainerByHash(uContainerHash);
	if (pxContainer)
	{
		const Tools_ContainerFile* pxFile = FindFileByHash(pxContainer->GetFileHash());
		if (pxFile)
		{
			pxFile->WarnIfReadOnly(true);
		}
	}
}

/////////////////////////
// RenameSelectedGroup //
/////////////////////////
// Change the user visible name. Don't change the hash though.
void Tools_Container_System::RenameSelectedGroup()
{
	const Asura_Hash_ID uGroupHash = Tools_Container_Overlay_Manager::GetSelectedGroupHash();
	Tools_ContainerGroup* pxGroup = FindGroupByHash(uGroupHash);
	if (pxGroup)
	{
		const Asura_String xOldName = pxGroup->GetGroupName();
		const bool bOk = SetGroupNameDialogue(*pxGroup, false);
		
		if (bOk && xOldName.Compare(pxGroup->GetGroupName(), false) != 0)
		{
			// New name is different from the old one.
			pxGroup->SetModified();

			// Refresh the group dialogue.
			s_xOverlayManager.RefreshGroup(pxGroup);

			Tools_Entity_Container::Refresh();
		}
	}
}

/////////////////
// RenameGroup //
/////////////////
void Tools_Container_System::RenameGroup(const Asura_String& xNewName, Tools_ContainerGroup* pxGroup)
{
	if (pxGroup && xNewName.Compare(pxGroup->GetGroupName(), false) != 0)
	{
		// New name is different from the old one.
		pxGroup->SetGroupName(xNewName);
		pxGroup->SetModified();

		// Refresh the group dialogue.
		s_xOverlayManager.RefreshGroup(pxGroup);
		Tools_Entity_Container::Refresh();
	}
}


/////////////////////////
// DeleteSelectedGroup //
/////////////////////////
void Tools_Container_System::DeleteSelectedGroup()
{
	const Asura_Hash_ID uGroupHash = Tools_Container_Overlay_Manager::GetSelectedGroupHash();
	Tools_ContainerGroup* pxGroup = FindGroupByHash(uGroupHash);
	if (!pxGroup) return;
	
	Asura_String xMessage("Delete group '");
	xMessage += pxGroup->GetGroupName();
	xMessage += "' and all its containers ?";

	YesNo_Overlay xPrompt(xMessage);
	if (xPrompt.DoModal() == false) return; // No.

	// Find the containers in this group.
	const u_int uMAX_NUM = 1024;
	Asura_Hash_ID auContainerToDelete[uMAX_NUM];
	u_int uNumToDelete = 0;

	Asura_TemplateHashTree_ConstIterator<Tools_Container*> xIt(s_xContainerTree);
	for ( ; !xIt.Done() ; xIt.Next())
	{
		const Tools_Container* const * ppxContainer = xIt.GetCurrent();
		if (!ppxContainer) continue;
		
		const Tools_Container* pxContainer = *ppxContainer;
		if (pxContainer && pxContainer->GetGroupHash() == uGroupHash)
		{
			auContainerToDelete[uNumToDelete++] = pxContainer->GetContainerHash();
			if (uNumToDelete >= uMAX_NUM)
			{
				Asura_Assert(0, "Too many containers in this group!");
				return;
			}
		}
	}

	Tools_Mode_Container::MarkDeletionStart();

	// Delete all the containers in this group.
	for (u_int u=0; u<uNumToDelete; ++u)
	{
		const bool bDeleted = DeleteContainer(auContainerToDelete[u], false, false);
		if (!bDeleted)
		{
			Tools_Mode_Container::MarkDeletionStop();
			return; // Give up.
		}
	}

	// Update tree dialogue.
	RemoveGroupFromDialogueTree(pxGroup);

	// Close edit overlay.
	s_xOverlayManager.CloseEditOverlay();

	// File is modified.
	pxGroup->SetModified();

	// Delete the group.
	bool bOk = s_xGroupTree.DeleteNodeByHash(uGroupHash);
	Asura_Assert(bOk, "Failed to find Tools_ContainerGroup");

	Tools_Mode_Container::MarkDeletionStop();
}

///////////////////////
// MoveSelectedGroup //
///////////////////////
void Tools_Container_System::MoveSelectedGroup()
{
	// File selection dialogue - list writeable files.
	const Asura_Hash_ID uGroupHash = Tools_Container_Overlay_Manager::GetSelectedGroupHash();
	Tools_ContainerGroup* pxGroup = FindGroupByHash(uGroupHash);
	Asura_Assert(pxGroup, "Null pointer");
	if (!pxGroup) return;

	const Asura_Hash_ID uOldFileHash = pxGroup->GetFileHash();
	Tools_ContainerFile* pxOldFile = FindFileByHash(uOldFileHash);
	Asura_Assert(pxOldFile, "Null pointer");
	if (!pxOldFile) return;

	Asura_String256 xBuffer("Move group '%s' to selected writeable file.", pxGroup->GetGroupName().GetString());

	ListSelection_Overlay xFileList(xBuffer.GetString());

	for (Asura_TemplateHashTree_Iterator<Tools_ContainerFile> xIt(s_xFileTree) ; !xIt.Done() ; xIt.Next())
	{
		Tools_ContainerFile* pxFile = xIt.GetCurrent();
		if (!pxFile || !pxFile->IsWriteable())
		{
			continue;
		}
		xFileList.AddItem(pxFile->GetShortName().GetString(), static_cast<int>(pxFile->GetFileHash()), (pxFile == pxOldFile));
	}

	if (!xFileList.DoModal())
	{
		return; // Cancelled
	}

	const Asura_Hash_ID uNewFileHash = static_cast<Asura_Hash_ID>(xFileList.GetSelectionData());
	if (uNewFileHash == uOldFileHash)
	{
		return;
	}

	Tools_ContainerFile* pxNewFile = FindFileByHash(uNewFileHash);
	MoveSelectedGroup_Helper(pxGroup, pxOldFile, pxNewFile);
}

//////////////////////////////
// MoveSelectedGroup_Helper //
//////////////////////////////
void Tools_Container_System::MoveSelectedGroup_Helper(Tools_ContainerGroup* pxGroup, Tools_ContainerFile* pxOldFile, Tools_ContainerFile* pxNewFile)
{
	if (!pxGroup || !pxOldFile || !pxNewFile)
	{
		Asura_Assert(0, "Null pointer");
		return;
	}

	const Asura_Hash_ID uGroupHash   = pxGroup->GetGroupHash();
	const Asura_Hash_ID uOldFileHash = pxOldFile->GetFileHash();
	const Asura_Hash_ID uNewFileHash = pxNewFile->GetFileHash();

	// Double check the writeable status of the relevant files.
	pxOldFile->WarnIfReadOnly(true);
	pxNewFile->WarnIfReadOnly(true);

	if (!pxOldFile->IsWriteable() ||
		!pxNewFile->IsWriteable())
	{
		return;
	}

	// Mark files as modified.
	pxOldFile->SetModified();
	pxNewFile->SetModified();

	// Move the group
	RemoveGroupFromDialogueTree(pxGroup);
	pxGroup->SetFileHash(uNewFileHash);
	AddGroupToDialogueTree(pxGroup);

	// Move the containers
	for (Asura_TemplateHashTree_Iterator<Tools_Container*> xIt(s_xContainerTree); !xIt.Done(); xIt.Next())
	{
		Tools_Container** ppxContainer = xIt.GetCurrent();
		if (!ppxContainer) continue;
		
		Tools_Container* pxContainer = (*ppxContainer);
		if (pxContainer && pxContainer->GetGroupHash() == uGroupHash)
		{
			Asura_Assert(pxContainer->GetFileHash() == uOldFileHash, "Error: Container has unexpected file hash!");
			pxContainer->SetFileHash(uNewFileHash);
			AddContainerToDialogueTree(pxContainer, uGroupHash);
		}
	}

	Tools_Timed_Message_System::AddMessage(Tools_Timed_Message_System::COLOUR_SUCCESS , "Group '%s' moved from file '%s' to '%s'.", pxGroup->GetGroupName().GetString(), pxOldFile->GetShortName().GetString(), pxNewFile->GetShortName().GetString());
}

///////////////////////////////////////
// CreateNewContainerFromMode_Helper //
///////////////////////////////////////
// Auto-generate name and create tools container of specified classification in given group.
Tools_Container* Tools_Container_System::CreateNewContainerFromMode_Helper(u_int uClassification,
																		   u_int uAutoGenNumber,
																		   const char* const szParentName,
																		   const Tools_ContainerGroup* const pxGroup)
{
	if (!pxGroup) return 0;

	// Auto-generate a suitable name.
	const char* const szClassName = Tools_ContainerRegistry::GetContainerClassName(uClassification);
	Asura_Assert(szClassName, "Container class name is null.");

	Asura_String256 xBaseName("%s-%s-%u", szParentName, szClassName, uAutoGenNumber);

	// Check that name really is unique (prompt if not) also strip out any annoying spaces,
	// and replace any slashes.
	Asura_String xContainerName = xBaseName.GetString();
	const bool bOk = SetContainerNameDialogue(xContainerName, pxGroup, ASURA_HASH_ID_UNSET, true);
	if (!bOk)
	{
		return 0; // Cancelled.
	}

	// Create specified container.
	Tools_Container* pxNewContainer = Tools_ContainerRegistry::CreateToolsClass(uClassification);
	if (!pxNewContainer)
	{
		Tools_Timed_Message_System::AddMessage(Tools_Timed_Message_System::COLOUR_ERROR, "Failed to create container (classification %u).", uClassification);
		return 0;
	}

	// Put new container in same group and file as parent.
	pxNewContainer->m_xContainerName = xContainerName;
	pxNewContainer->m_uGroupHash = pxGroup->GetGroupHash();
	pxNewContainer->m_uFileHash  = pxGroup->GetFileHash();

	pxNewContainer->SetCreationContainerName();
	pxNewContainer->PostCreateInitialise();

	// Set group scope flag. Limits visibility to within this group.
	pxNewContainer->SetGroupScope();

	return pxNewContainer;
}

////////////////////////////////
// CreateNewContainerFromMode //
////////////////////////////////
void Tools_Container_System::CreateNewContainerFromMode(const u_int uInsertClass)
{
	// Select type of container
	u_int uClassification = AsuraContainerClass_Mesh;
	if ( !SelectContainerClassModalOverlay(uClassification, uInsertClass) )
	{
		return;
	}

	if (uClassification == AsuraContainerClass_NotSet)
	{
		return;
	}

	// Find parent container and group.
	Tools_Container* const pxParent = Tools_Mode_Container::GetContainerSelectedParentForCanAddChild();
	if (!pxParent)
	{
		Tools_Timed_Message_System::AddMessage(Tools_Timed_Message_System::COLOUR_ERROR, "Container not found.");
		return;
	}
	
	if (!pxParent->CanAddChild())
	{
		Tools_Timed_Message_System::AddMessage(Tools_Timed_Message_System::COLOUR_ERROR, "Can't add child container.");
		return;
	}

//	const Asura_Hash_ID uParentHash = pxParent->GetContainerHash();
	const Asura_Hash_ID uGroupHash = pxParent->GetGroupHash();
	Tools_ContainerGroup* const pxGroup = FindGroupByHash(uGroupHash);
	if (!pxGroup)
	{
		Tools_Timed_Message_System::AddMessage(Tools_Timed_Message_System::COLOUR_ERROR, "Container group (0x%08x) not found.", uGroupHash);
		return;
	}

	// Auto-generate a suitable name and create class.
	const u_int uAutoGenNumber = pxGroup->GetAutoGenNumber();

	// Use root name by default, rather than parent name, as it should be shorter.
	const Asura_Hash_ID uRootHash = Tools_Mode_Container::GetContainerToEditHash();
	const Tools_Container* pxRoot = FindContainerByHash(uRootHash);
	if (!pxRoot)
	{
		pxRoot = pxParent;
	}

	const char* const szParentName = pxRoot->GetContainerName().GetString();
	Asura_Assert(szParentName, "Collection container name is null");

	Tools_Container* pxNewContainer = CreateNewContainerFromMode_Helper(uClassification, uAutoGenNumber, szParentName, pxGroup);
	if (!pxNewContainer)
	{
		return;
	}

	const Asura_Hash_ID uNewContainerHash = pxNewContainer->GetContainerHash();
	Asura_Hash_ID uChildHash = uNewContainerHash;
	
	// Create attachment (if needed).
	if (pxNewContainer && uInsertClass != AsuraContainerClass_NotSet)
	{
		// Create and link in.
		Tools_Container* pxInserted = CreateNewContainerFromMode_Helper(uInsertClass, uAutoGenNumber, szParentName, pxGroup);
		if (pxInserted)
		{
			Asura_Assert(pxInserted->CanAddChild(), "Can't add child.");
			pxInserted->AddChild(uChildHash);
			uChildHash = pxInserted->GetContainerHash();

			// Register the new inserted container.
			RegisterContainer(pxInserted, true, false);
			pxInserted = 0;
		}
	}

	// Register the new container.
	RegisterContainer(pxNewContainer, true, true);
	pxNewContainer = 0;

	// Add to the parent container being edited in the mode.
	if (pxParent && pxParent->CanAddChild())
	{
		pxParent->AddChild(uChildHash);
		pxParent->SetModified();
	}

	Tools_Mode_Container::SelectInstanceContainer(uNewContainerHash);
}

////////////////////////
// CreateNewContainer //
////////////////////////
// Create a new container and add it to the selected container group.
void Tools_Container_System::CreateNewContainer()
{
	// Find selected file
	const Asura_Hash_ID uFileHash = Tools_Container_Overlay_Manager::GetSelectedFileHash();
	const Tools_ContainerFile* pxFile = FindFileByHash(uFileHash);
	Asura_Assert(pxFile, "Null file");
	if (!pxFile) return;

	// Find selected group
	const Asura_Hash_ID uGroupHash = Tools_Container_Overlay_Manager::GetSelectedGroupHash();
	const Tools_ContainerGroup* pxGroup = FindGroupByHash(uGroupHash);
	Asura_Assert(pxGroup, "Null group");
	if (!pxGroup) return;
	
	// Select type of container
	u_int uClass = AsuraContainerClass_Collection;
	if (!SelectContainerClassModalOverlay(uClass))
	{
		return;
	}

	// Select name of container
	Asura_String xContainerName = szCONTAINERS_NEWCONTAINER;
	bool bOk = SetContainerNameDialogue(xContainerName, pxGroup, ASURA_HASH_ID_UNSET);
	if (!bOk) return;

	// Make new container of the specified type
	Tools_Container* pxNewContainer = Tools_ContainerRegistry::CreateToolsClass(uClass);
	if (!pxNewContainer) return;

	pxNewContainer->m_xContainerName = xContainerName;
	pxNewContainer->m_uGroupHash = uGroupHash;
	pxNewContainer->m_uFileHash  = uFileHash;

	pxNewContainer->SetCreationContainerName();
	pxNewContainer->PostCreateInitialise();

	RegisterContainer(pxNewContainer, true, true);
}

//////////////////////////////////////
// CreateNewContainerAsCopyFromMode //
//////////////////////////////////////
// Make a copy of the specified container and add it to the specified destination.
// Return pointer to new container, if a copy was made successfully.
Tools_Container* Tools_Container_System::CreateNewContainerAsCopyFromMode(Asura_Hash_ID uCopyContainerHash, Tools_Container* pxDestinationContainer)
{
	Asura_Assert(pxDestinationContainer, "Null pointer for destination container");
	if (!pxDestinationContainer) return 0;

	if (!pxDestinationContainer->CanAddChild())
	{
		Asura_Assert(0, "Can't add child to destination container");
		return 0;
	}

	const Tools_Container* const pxContainerToCopy = FindContainerByHash(uCopyContainerHash);
	Asura_Assert(pxContainerToCopy, "Failed to find container to copy");
	if (!pxContainerToCopy) return 0;

	const Asura_Hash_ID uDestinationContainerHash = pxDestinationContainer->GetContainerHash();
	if (uDestinationContainerHash == uCopyContainerHash)
	{
		Tools_Timed_Message_System::AddMessage(Tools_Timed_Message_System::COLOUR_ERROR, "Can't copy container to itself!.");
		return 0;
	}

	if (pxContainerToCopy->Contains(uDestinationContainerHash))
	{
		Tools_Timed_Message_System::AddMessage(Tools_Timed_Message_System::COLOUR_ERROR, "Can't copy container to within itself!.");
		return 0;
	}

	const Asura_Hash_ID uDestinationGroupHash = pxDestinationContainer->GetGroupHash();
	Tools_ContainerGroup* const pxDestinationGroup = FindGroupByHash(uDestinationGroupHash);
	if (!pxDestinationGroup)
	{
		Tools_Timed_Message_System::AddMessage(Tools_Timed_Message_System::COLOUR_ERROR, "Container group (0x%08x) not found.", uDestinationGroupHash);
		return 0;
	}
	// Auto-generate a suitable name.
	const u_int uAutoGenNumber = pxDestinationGroup->GetAutoGenNumber();
	const u_int uClassification = pxContainerToCopy->GetClassification();

	// Use root name by default, rather than parent name, as it should be shorter.
	const Asura_Hash_ID uRootHash = Tools_Mode_Container::GetContainerToEditHash();
	const Tools_Container* pxRoot = FindContainerByHash(uRootHash);
	if (!pxRoot)
	{
		pxRoot = pxDestinationContainer;
	}

	const char* const szRootName = pxRoot->GetContainerName().GetString();
	Asura_Assert(szRootName, "Collection container name is null");

	// Make a new container.
	Tools_Container* pxNewContainer = CreateNewContainerFromMode_Helper(uClassification, uAutoGenNumber, szRootName, pxDestinationGroup);
	if (!pxNewContainer)
	{
		return 0; // Cancelled
	}

	const Asura_Hash_ID uNewContainerHash = pxNewContainer->GetContainerHash();
	
	// Copy the data across.
	pxNewContainer->CopyDataFromContainer(pxContainerToCopy);

	// Register the new container.
	const bool bOk = RegisterContainer(pxNewContainer, true, false);
	if (!bOk) pxNewContainer = 0;

	// Add to the destination container.
	if (pxDestinationContainer && pxDestinationContainer->CanAddChild())
	{
		pxDestinationContainer->AddChild(uNewContainerHash);
		pxDestinationContainer->SetModified();
	}

	Tools_Mode_Container::SelectInstanceContainer(uNewContainerHash);

	return pxNewContainer;
}

//////////////////////////////////////
// SelectContainerClassModalOverlay //
//////////////////////////////////////
// Select a container classification. Return true if Ok, false if cancelled.
bool Tools_Container_System::SelectContainerClassModalOverlay(u_int& uClass, const u_int uExcludeClass /*= AsuraContainerClass_NotSet*/)
{
	const bool bAttaching = (uExcludeClass == AsuraContainerClass_Attachment);

	const Tools_ContainerRegistry xRegistry = Tools_ContainerRegistry::GetInstance();

	u_int uNumEntriesLeft = xRegistry.GetNumEntries();
	Asura_Assert(uNumEntriesLeft, "No registered Tools containers!");
	if (uNumEntriesLeft == 0) return false;

	ListSelection_Overlay xTypeList("Select Container Type");
	for (int i=0; uNumEntriesLeft && i<iCONTAINER_TYPES_MAX_NUMBER; ++i)
	{
		const Tools_ContainerRegistry::Entry& xEntry = xRegistry.GetEntry(i);
		if (xEntry.m_szClassName && xEntry.m_pfnToolsCreateFn)
		{
			const u_int uEntryClass = xEntry.m_uClassification;
			if (uEntryClass != uExcludeClass)
			{
				const bool bExclude = (bAttaching && !Tools_ContainerRegistry::CanUseAttachment(uEntryClass));
				if (!bExclude)
				{
					xTypeList.AddItem(xEntry.m_szClassName, static_cast<int>(uEntryClass), (uEntryClass==uClass));
				}
			}
			--uNumEntriesLeft;
		}
	}
	Asura_Assert(uNumEntriesLeft == 0, "Failed to find all of the registry entries.");

	if (!xTypeList.DoModal())
	{
		return false;
	}

	uClass = static_cast<u_int>(xTypeList.GetSelectionData());
	return true;
}

/////////////////////////
// CreateNewCollection //
/////////////////////////
// Create new collection container in specified file and group.  Return false if cancelled. Returns hash of created container name.
bool Tools_Container_System::CreateNewCollection(const Asura_Hash_ID uFileHash, const Asura_Hash_ID uGroupHash,
												 Asura_Hash_ID& uContainerHash, const char* szDefaultName /*= 0*/)
{
	// Check that file and group exist.
	const Tools_ContainerFile* pxFile = FindFileByHash(uFileHash);
	Asura_Assert(pxFile, "Null file");
	if (!pxFile) return false;
	
	const Tools_ContainerGroup* pxGroup = FindGroupByHash(uGroupHash);
	Asura_Assert(pxGroup, "Null group");
	if (!pxGroup) return false;
	
	// Select name of container
	Asura_String xContainerName = (szDefaultName ? szDefaultName : szCONTAINERS_NEWCONTAINER);
	bool bOk = SetContainerNameDialogue(xContainerName, pxGroup, ASURA_HASH_ID_UNSET);
	if (!bOk) return false;

	// Make new container of the specified type
	Tools_Container* pxNewContainer = Tools_ContainerRegistry::CreateToolsClass(AsuraContainerClass_Collection);
	if (!pxNewContainer) return false;

	pxNewContainer->m_xContainerName = xContainerName;
	pxNewContainer->m_uGroupHash = uGroupHash;
	pxNewContainer->m_uFileHash  = uFileHash;

	pxNewContainer->SetCreationContainerName();
	pxNewContainer->PostCreateInitialise();

	uContainerHash = pxNewContainer->GetContainerHash();

	RegisterContainer(pxNewContainer, true, false);
	return true;
}


///////////////////////
// RegisterContainer //
///////////////////////
// Add new container or load an old one.
// Return true, if container was successfully registered.
bool Tools_Container_System::RegisterContainer(Tools_Container* pxContainer, bool bNewContainer /*= false*/, bool bOpenEditOverlay /*= false*/)
{
	Asura_Assert(pxContainer, "Null pointer!");
	if (!pxContainer) return false;

	const Asura_Hash_ID uContainerHash = pxContainer->GetContainerHash();

	// Check that it doesn't already exist.
	Tools_Container** ppxContainer = s_xContainerTree.AddNode(uContainerHash, pxContainer);
	if (!ppxContainer)
	{
		Tools_Timed_Message_System::AddMessage(Tools_Timed_Message_System::COLOUR_WARNING, "Failed to add container: %s. Already exists.", pxContainer->m_xContainerName.GetString());

		delete pxContainer;
		return false;
	}

	Asura_Assert(*ppxContainer == pxContainer, "Pointers don't match!");

	AddContainerToDialogueTree(pxContainer, pxContainer->GetGroupHash(), bNewContainer);

	// Create Asura_Container too.
	Asura_Container* pxNewAsura = Asura_ContainerRegistry::CreateAsuraClass(pxContainer->m_uClassification);
	if (pxNewAsura)
	{
		pxNewAsura->m_uContainerHash = uContainerHash;

		if (!Asura_Container_System::AddContainer(pxNewAsura))
		{
			// Failed to add.
			Asura_Assert(0, "Failed to add Asura_Container.");
			delete pxNewAsura;
			pxNewAsura = 0;
		}

		pxContainer->m_pxAsuraContainer = pxNewAsura;
		if (pxNewAsura)
		{
			pxContainer->CopyDataToAsura();
		}
	}

	if (bNewContainer)
	{
		// Mark relevant file as being modified.
		pxContainer->SetModified();
	}

	if (bOpenEditOverlay || (bNewContainer && IsEditOverlayOpen()))
	{
		// Open for editing.
		s_xOverlayManager.OpenEditOverlay(uContainerHash);
	}

	return true;
}

////////////////////////////////
// AddContainerToDialogueTree //
////////////////////////////////
// Add the container to the dialogue tree in the specified group.
void Tools_Container_System::AddContainerToDialogueTree(const Tools_Container* pxContainer, Asura_Hash_ID uGroupHash, bool bSelect /*= false*/)
{
	if (!pxContainer) return;
	const char* szContainerName = pxContainer->m_xContainerName.GetString();
	if (!szContainerName) return;

	// Add container at third level.
	Asura_TreeDialog_Item* pxGroupItem = s_xOverlayManager.GetGroupItem(uGroupHash);
	Asura_Assert(pxGroupItem, "Failed to find group in tree.");
	if (pxGroupItem)
	{
		Asura_TreeDialog_Tree& xTree = s_xOverlayManager.GetTree();
		Asura_TreeDialog_Item* pxItem = xTree.InsertItem(SortTreeAlphabetically, pxGroupItem, szContainerName, int(pxContainer->GetContainerHash()));
		Asura_Assertf(pxItem, ("Failed to add container (%s)", szContainerName));
		if (pxItem)
		{
			Asura_String256 xBuffer;
			pxContainer->AddInstanceName(xBuffer, false, true);
			pxItem->SetHelpText(xBuffer.GetString());
		}

		if (pxItem && !pxContainer->IsGroupScope())
		{
			pxItem->SetIconHelpText("Global scope");
		}

		if (bSelect && pxItem)
		{
			xTree.SetSelectedItem(pxItem);
		}
	}
}

/////////////////////////////////////
// RemoveContainerFromDialogueTree //
/////////////////////////////////////
// Remove container from the dialogue tree.
void Tools_Container_System::RemoveContainerFromDialogueTree(const Tools_Container* pxContainer)
{
	if (!pxContainer) return;
	const Asura_Hash_ID uContainerHash = pxContainer->GetContainerHash();

	Asura_TreeDialog_Item* pxFoundItem = s_xOverlayManager.GetContainerItem(uContainerHash);
	if (pxFoundItem)
	{
		// Found it. Remove the item.
		Asura_TreeDialog_Tree& xTree = s_xOverlayManager.GetTree();
		xTree.DeleteItem(pxFoundItem);
	}

	Asura_Assert(pxFoundItem, "Failed to remove container from tree.");
}

///////////////////////////
// AddFileToDialogueTree //
///////////////////////////
// Add file to the dialogue tree.
void Tools_Container_System::AddFileToDialogueTree(const Tools_ContainerFile* pxFile, bool bSelect /*= false*/)
{
	if (!pxFile) return;

	const char* const szShortName = pxFile->GetShortName().GetString();
	Asura_Assert(szShortName, "Null file name!");
	if (!szShortName) return;

	// Add file at first level.
	Asura_TreeDialog_Tree& xTree = s_xOverlayManager.GetTree();
	Asura_TreeDialog_Item* pxItem = xTree.InsertItem(SortTreeAlphabetically, szShortName, int(pxFile->GetFileHash()));
	Asura_Assertf(pxItem, ("Failed to add container file (%s) to dialogue.", szShortName));

	if (bSelect && pxItem)
	{
		xTree.SetSelectedItem(pxItem);
	}
}

////////////////////////////
// AddGroupToDialogueTree //
////////////////////////////
// Add group to the dialogue tree.
void Tools_Container_System::AddGroupToDialogueTree(const Tools_ContainerGroup* pxGroup, bool bSelect /*= false*/)
{
	if (!pxGroup) return;
	const char* szGroupName = pxGroup->GetGroupName().GetString();
	if (!szGroupName) return;

	// Add group at second level.
	Asura_TreeDialog_Item* pxFileItem = s_xOverlayManager.GetFileItem(pxGroup->GetFileHash());
	Asura_Assert(pxFileItem, "Failed to find file in tree.");
	if (pxFileItem)
	{
		Asura_TreeDialog_Tree& xTree = s_xOverlayManager.GetTree();
		Asura_TreeDialog_Item* pxItem = xTree.InsertItem(SortTreeAlphabetically, pxFileItem, szGroupName, int(pxGroup->GetGroupHash()));
		Asura_Assertf(pxItem, ("Failed to add container group (%s)", szGroupName));

		if (bSelect && pxItem)
		{
			xTree.SetSelectedItem(pxItem);
		}
	}
}

/////////////////////////////////
// RemoveGroupFromDialogueTree //
/////////////////////////////////
void Tools_Container_System::RemoveGroupFromDialogueTree(const Tools_ContainerGroup* pxGroup)
{
	if (!pxGroup) return;

	const Asura_Hash_ID uGroupHash = pxGroup->GetGroupHash();

	Asura_TreeDialog_Item* pxFoundItem = s_xOverlayManager.GetGroupItem(uGroupHash);
	if (pxFoundItem)
	{
		// Found it. Remove the item.
		Asura_TreeDialog_Tree& xTree = s_xOverlayManager.GetTree();
		xTree.DeleteItem(pxFoundItem);
	}

	Asura_Assert(pxFoundItem, "Failed to remove group from tree.");
}

///////////////////////////
// EditSelectedContainer //
///////////////////////////
void Tools_Container_System::EditSelectedContainer()
{
	// Open an editing overlay for the selected container.
	s_xOverlayManager.EditSelectedContainer();
}

/////////////////////////////
// RenameSelectedContainer //
/////////////////////////////
// Change the user visible name. Don't change the hash though.
void Tools_Container_System::RenameSelectedContainer()
{
	const Asura_Hash_ID uContainerHash = Tools_Container_Overlay_Manager::GetSelectedContainerHash();
	Tools_Container* pxContainer = FindContainerByHash(uContainerHash);
	RenameContainerUsingDialogue(pxContainer);
}

//////////////////////////////////
// RenameContainerUsingDialogue //
//////////////////////////////////
// Pop up a dialogue to rename the specified container.
// Change the user visible name. Don't change the hash though.
void Tools_Container_System::RenameContainerUsingDialogue(Tools_Container* pxContainer)
{
	if (!pxContainer) return;

	Asura_String xContainerName = pxContainer->m_xContainerName;
	Tools_ContainerGroup* pxGroup = FindGroupByHash(pxContainer->GetGroupHash());
	const bool bOk = SetContainerNameDialogue(xContainerName, pxGroup, pxContainer->GetContainerHash());
	
	if (bOk)
	{
		RenameContainer(xContainerName, pxContainer);
	}
	
}

/////////////////////
// RenameContainer //
/////////////////////
// Set container name. Check that the new name is valid before calling.
void Tools_Container_System::RenameContainer(const Asura_String& xNewName, Tools_Container* pxContainer)
{
	if (pxContainer && xNewName.Compare(pxContainer->m_xContainerName, false) != 0)
	{
		// New name is different from the old one.
		pxContainer->m_xContainerName = xNewName;
		pxContainer->SetShowName();
		pxContainer->SetModified();

		s_xOverlayManager.RefreshContainer(pxContainer);
		Tools_Entity_Container::Refresh();
		Tools_Mode_Container::RefreshRename(pxContainer->GetContainerHash());
	}
}

/////////////////////////////
// DeleteSelectedContainer //
/////////////////////////////
void Tools_Container_System::DeleteSelectedContainer()
{
	Tools_Mode_Container::MarkDeletionStart();

	const Asura_Hash_ID uContainerHash = Tools_Container_Overlay_Manager::GetSelectedContainerHash();
	if (DeleteContainer(uContainerHash, true, true))
	{
		// Container was deleted. Try to automatically remove any references to it.
		for (Asura_TemplateHashTree_Iterator<Tools_Container*> xIt(s_xContainerTree); !xIt.Done(); xIt.Next())
		{
			Tools_Container** ppxContainer = xIt.GetCurrent();
			if (!ppxContainer) continue;
			
			Tools_Container* pxContainer = (*ppxContainer);
			if (pxContainer)
			{
				// Remove the container if it is a child.
				pxContainer->RemoveChild(uContainerHash);
			}
		}
	}

	Tools_Mode_Container::MarkDeletionStop();
}

/////////////////////
// DeleteContainer //
/////////////////////
// Set bPromptUser to prompt user.
// Set bRecursive to recursively delete orphaned containers with group-scope.
// Return true if deleted sucessfully.
bool Tools_Container_System::DeleteContainer(const Asura_Hash_ID uContainerHash, bool bPromptUser, bool bRecursive)
{
	Tools_Container* pxContainer = FindContainerByHash(uContainerHash);
	if (!pxContainer) return false;
	
	if (bPromptUser)
	{
		// Ask the user.
		if (!pxContainer->PromptForDeletion(false))
		{
			return false; // No.
		}
	}

	Tools_Mode_Container::MarkDeletionStart();

	// Update tree dialogue.
	RemoveContainerFromDialogueTree(pxContainer);

	// Close edit overlay.
	s_xOverlayManager.CloseEditOverlay();

	// File is modified.
	SetFileModified(pxContainer->GetFileHash());

	// Delete (group-scoped) children if they are to be orphaned.
	if (bRecursive)
	{
		pxContainer->RemoveAllChildren();
	}

	// Destroy instances for any entities that have instances referring to this container.
	SetContainerModified(uContainerHash, false);

	// Delete container from Asura, then Tools.
	bool bOk = Asura_Container_System::DeleteContainer(uContainerHash);
	Asura_Assert(bOk, "Failed to delete Asura_Container");

	bOk = s_xContainerTree.DeleteNodeByHash(uContainerHash);
	Asura_Assert(bOk, "Failed to find Tools_Container");

	delete pxContainer;

	Tools_Entity_Container::Refresh();

	// Recreate instances referring to this container.
	Tools_Mode_Container::MarkDeletionStop();
	
	return bOk;
}

///////////////////////////////
// IsContainerValidForMoving //
///////////////////////////////
// Can this container be moved to a new group.
bool Tools_Container_System::IsContainerValidForMoving(Asura_Hash_ID uContainerHash)
{
	const Tools_Container* pxContainer = FindContainerByHash(uContainerHash);
	if (!pxContainer) return false;

	if (pxContainer->IsGroupScope()) return false;

	const Tools_ContainerFile* pxSourceFile = FindFileByHash(pxContainer->GetFileHash());
	if (!pxSourceFile || !pxSourceFile->IsWriteable()) return false;

	const u_int uClassification = pxContainer->GetClassification();
	if (uClassification == AsuraContainerClass_Collection ||
		uClassification == AsuraContainerClass_Attachment)
	{
		// If it is an attachment or collection, then we must pull along any group-scoped children.
		// Until such a time as that is implemented just disallow that case.
		for ( Tools_Container_ChildIterator xIt(pxContainer); !xIt.Done(); xIt.NextSkipIfSame() )
		{
			const Tools_Container* pxChild = xIt.GetCurrent();
			if (pxChild && pxChild->IsGroupScope())
			{
				return false;
			}
		}
	}

	return true;
}

///////////////////////////
// MoveSelectedContainer //
///////////////////////////
void Tools_Container_System::MoveSelectedContainer()
{
	const Asura_Hash_ID uContainerHash = GetOverlayManager().GetSelectedContainerHash();
	if (!IsContainerValidForMoving(uContainerHash))
	{
		return;
	}

	const Tools_Container* pxContainer = FindContainerByHash(uContainerHash);
	if (!pxContainer) return;

	const Asura_Hash_ID uSourceGroup = pxContainer->GetGroupHash();

	ListSelection_Overlay xList("Select destination group.");

	Asura_TemplateHashTree_ConstIterator<Tools_ContainerGroup> xIt(s_xGroupTree);
	for ( ; !xIt.Done() ; xIt.Next())
	{
		const Tools_ContainerGroup* pxGroup = xIt.GetCurrent();
		if (!pxGroup) continue;
			
		// Filter out groups in non-writeable files.
		const Tools_ContainerFile* pxFile = FindFileByHash(pxGroup->GetFileHash());
		if (!pxFile || !pxFile->IsWriteable()) continue;

		const Asura_String& xName = pxGroup->GetGroupName();
		if (!xName.IsBlank())
		{
			const Asura_Hash_ID uEntryGroupHash = pxGroup->GetGroupHash();
			xList.AddItem(xName.GetString(), uEntryGroupHash, uEntryGroupHash == uSourceGroup);
		}
	}

	if (xList.GetNumItems() == 0)
	{
		xList.AddItem("No writeable groups found!", ASURA_HASH_ID_UNSET);
	}

	const bool bSelected = xList.DoModal();
	if (!bSelected) return;
	
	const Asura_Hash_ID uDestinationGroupHash = static_cast<Asura_Hash_ID>(xList.GetSelectionData());
	if (uDestinationGroupHash != ASURA_HASH_ID_UNSET)
	{
		MoveSelectedContainer_Helper(uContainerHash, uDestinationGroupHash);
	}
}

//////////////////////////////////
// MoveSelectedContainer_Helper //
//////////////////////////////////
void Tools_Container_System::MoveSelectedContainer_Helper(Asura_Hash_ID uSelectedContainerHash, Asura_Hash_ID uTargetGroupHash)
{		
	// Check the container.
	Tools_Container* pxContainer = FindContainerByHash(uSelectedContainerHash);
	Asura_Assert(pxContainer, "Null container pointer");
	if (!pxContainer) return;

	if (pxContainer->GetGroupHash() == uTargetGroupHash)
	{
		// Moving to same group. Do nothing.
		return;
	}

	// Only global scope containers can be moved.
	if (pxContainer->IsGroupScope())
	{
		Tools_Timed_Message_System::AddMessage(Tools_Timed_Message_System::COLOUR_ERROR , "Only containers with global scope can be moved to a new group.");
		return;
	}

	// Check that the source file is writeable.
	Tools_ContainerFile* pxSourceFile = FindFileByHash(pxContainer->GetFileHash());
	Asura_Assert(pxSourceFile, "Null source file pointer");
	if (!pxSourceFile) return;

	pxSourceFile->WarnIfReadOnly(true);
	if (!pxSourceFile->IsWriteable()) return;

	// Target group must be writeable.
	Tools_ContainerGroup* pxTargetGroup = FindGroupByHash(uTargetGroupHash);
	Asura_Assert(pxTargetGroup, "Null target group pointer");
	if (!pxTargetGroup) return;

	const Asura_Hash_ID uTargetFileHash = pxTargetGroup->GetFileHash();
	Tools_ContainerFile* pxTargetFile = FindFileByHash(uTargetFileHash);
	Asura_Assert(pxTargetFile, "Null target file pointer");
	if (!pxTargetFile) return;

	pxTargetFile->WarnIfReadOnly(true);
	if (!pxTargetFile->IsWriteable()) return;
	
	// Check that the container is suitable for moving.

	const u_int uClassification = pxContainer->GetClassification();
	if (uClassification == AsuraContainerClass_Collection ||
		uClassification == AsuraContainerClass_Attachment)
	{
		// If it is an attachment or collection, then we must pull along any group-scoped children.
		// Until such a time as that is implemented just disallow that case.
		for ( Tools_Container_ChildIterator xIt(pxContainer); !xIt.Done(); xIt.NextSkipIfSame() )
		{
			const Tools_Container* pxChild = xIt.GetCurrent();
			if (pxChild && pxChild->IsGroupScope())
			{
				Tools_Timed_Message_System::AddMessage(Tools_Timed_Message_System::COLOUR_ERROR , "Containers that have children with group scope can't be moved.");
				return;
			}
		}
	}
	
	// Move the global-scoped container to a new group.
	// Mark files as modified.
	pxSourceFile->SetModified();
	if (pxTargetFile != pxSourceFile)
	{
		pxTargetFile->SetModified();
	}
	
	RemoveContainerFromDialogueTree(pxContainer);
	pxContainer->SetFileHash(uTargetFileHash);
	pxContainer->SetGroupHash(uTargetGroupHash);
	AddContainerToDialogueTree(pxContainer, uTargetGroupHash);

	Tools_Timed_Message_System::AddMessage(Tools_Timed_Message_System::COLOUR_SUCCESS , "Container '%s' moved to group '%s'.", pxContainer->GetContainerName().GetString(), pxTargetGroup->GetGroupName().GetString());
}

/////////////////
// ToggleScope //
/////////////////
void Tools_Container_System::ToggleScope()
{
	const Asura_Hash_ID uContainerHash = Tools_Container_Overlay_Manager::GetSelectedContainerHash();
	Tools_Container* pxContainer = FindContainerByHash(uContainerHash);
	if (!pxContainer) return;

	if (pxContainer->IsGroupScope())
	{
		// Switching to global-scope. This is always allowed.
		Tools_Timed_Message_System::AddMessage(Tools_Timed_Message_System::COLOUR_SUCCESS , "Container '%s' given global-scope.", pxContainer->GetContainerName().GetString());
	}
	else
	{
		// Switching to group-scope. Disallow if there are references from another group.
		const bool bOk = IsValidForScopeChangeToGroup(pxContainer);
		if (bOk)
		{
			Tools_Timed_Message_System::AddMessage(Tools_Timed_Message_System::COLOUR_SUCCESS , "Container '%s' given group-scope.", pxContainer->GetContainerName().GetString());
		}
		else
		{
			Tools_Timed_Message_System::AddMessage(Tools_Timed_Message_System::COLOUR_ERROR , "Container '%s' can't be switched to group-scope, as it is referred to from another group.", pxContainer->GetContainerName().GetString());
			return;
		}
	}
	
	pxContainer->ToggleGroupScope();
	pxContainer->SetModified(true);
}

//////////////////////////////////
// IsValidForScopeChangeToGroup //
//////////////////////////////////
// Check whether the given container can be changed from global to group scope safely.
// Disallow if there are references to it from a different group.
// Note: really we should check whether there are any entities that refer to it,
// but we can't do this without parsing all of the entity files for all levels.
bool Tools_Container_System::IsValidForScopeChangeToGroup(const Tools_Container* pxContainerToChange)
{
	if (!pxContainerToChange) return false;

	const Asura_Hash_ID uContainerToChange = pxContainerToChange->GetContainerHash();
	const Asura_Hash_ID uGroupHash = pxContainerToChange->GetGroupHash();

	for (Asura_TemplateHashTree_Iterator<Tools_Container*> xIt(s_xContainerTree) ; !xIt.Done(); xIt.Next() )
	{
		Tools_Container** ppxContainer = xIt.GetCurrent();
		if (!ppxContainer) continue;
		
		const Tools_Container* pxContainer = (*ppxContainer);
		if (!pxContainer) continue;

		if (pxContainer->GetGroupHash() == uGroupHash)
		{
			// Same group, so no need to check.
			continue;
		}

		// Does this container have a reference to the specified container.
		Tools_Container_ChildIterator xChildIt(pxContainer);
		if (xChildIt.IsMember(uContainerToChange))
		{
			// Not safe to change.
			return false;
		}
	}

	// Didn't find any references from a different group. Should be safe to change.
	return true;
}

///////////////////////////
// IsValidForToggleScope //
///////////////////////////
// Quick and dirty check, that the container is selected and writeable.
bool Tools_Container_System::IsValidForToggleScope()
{
	return Tools_Container_Overlay_Manager::IsContainerSelectionValidAndWriteable();
}

////////////
// Reload //
////////////
void Tools_Container_System::Reload()
{
	s_xOverlayManager.CloseEditOverlay();
	SavePrompt(true);

	LoadAllFiles();

	Tools_Entity_Container::Refresh();
	Tools_Entity_Container::CreateAllInstances();
	Tools_Mode_Container::CreateAllInstances();
}

/////////////////////
// SelectContainer //
/////////////////////
void Tools_Container_System::SelectContainer(Asura_Hash_ID uContainerHash, bool bOpenEditOverlay)
{
	Tools_Container* pxContainer = FindContainerByHash(uContainerHash);
	if (!pxContainer) return;

	Asura_TreeDialog_Item* pxFoundItem = s_xOverlayManager.GetContainerItem(uContainerHash);
	if (pxFoundItem)
	{
		Asura_TreeDialog_Tree& xTree = s_xOverlayManager.GetTree();
		xTree.SetSelectedItem(pxFoundItem);

		if (bOpenEditOverlay)
		{
			s_xOverlayManager.UpdateOverlay();
			s_xOverlayManager.OpenEditOverlay(uContainerHash);
		}
	}
}

/////////////////////////////////
// SelectContainerModalOverlay //
/////////////////////////////////
// Choose container from list.
// Input:
// Set uContainerHash   to the initial selection (which will be highlighted in the list).
// Set uRemoveContained to exclude all containers that contain the specified container.
//	                    Used to prevent circular references from being set up.  
// Set bIncludeNone     to include a "*None*" option (if selected returns ASURA_HASH_ID_UNSET).
// Set bCollectionOnly  to include only collection containers.
// Set uGroupScopeHash  to include containers in the specified group-scope,
//					    otherwise only global-scope containers will be included.
// Output:
// Returns false if cancelled, true if selection made.
// uContainerHash contains selected value.
bool Tools_Container_System::SelectContainerModalOverlay(Asura_Hash_ID& uContainerHash, const Asura_Hash_ID uRemoveContained,
														 bool bIncludeNone,  bool bCollectionOnly, Asura_Hash_ID uGroupScopeHash)
{
	Tools_ContainerSelection_Overlay xList("Select container");

	if (bIncludeNone)
	{
		xList.AddItem("*None*", ASURA_HASH_ID_UNSET, ASURA_HASH_ID_UNSET == uContainerHash);
	}

	const bool bRemoveContained = (uRemoveContained != ASURA_HASH_ID_UNSET);

	// Loop through container tree, adding to the list.
	Asura_TemplateHashTree_Iterator<Tools_Container*> xIt(s_xContainerTree);
	for ( ; !xIt.Done(); xIt.Next() )
	{
		Tools_Container** ppxContainer = xIt.GetCurrent();
		if (!ppxContainer) continue;
		
		const Tools_Container* pxContainer = (*ppxContainer);
		if (!pxContainer) continue;

		if (bCollectionOnly && pxContainer->GetClassification() != AsuraContainerClass_Collection)
		{
			// Remove non-collections
			continue;
		}

		if (pxContainer->IsGroupScope())
		{
			if (uGroupScopeHash == ASURA_HASH_ID_UNSET || uGroupScopeHash != pxContainer->GetGroupHash())
			{
				// Remove as it is group-scoped and not in specified allowed group.
				continue;
			}
		}
		
		if (bRemoveContained && pxContainer->Contains(uRemoveContained))
		{
			// Remove this as the specified container is a child or grandchild etc.
			// Helps to prevent people from setting up circular references.
			continue;
		}

		Asura_String xName;
		pxContainer->MakeFullName(xName);

		const char* pszHelpString = xName.GetString();
		const char* pszContainerName = pxContainer->GetContainerName().GetString();

		const Asura_Hash_ID uHash = pxContainer->GetContainerHash();
		xList.AddItem(pszContainerName, uHash, uHash == uContainerHash, pszHelpString);
	}

	if ( !xList.DoModal() ) return false; // Cancelled.

	uContainerHash = xList.GetSelectionData();
	return true;
}

////////////////////////////////
// CreateOrSelectModalOverlay //
////////////////////////////////
// Return false if cancelled.
bool Tools_Container_System::CreateOrSelectModalOverlay(Asura_Hash_ID& uContainerHash)
{
	Tools_ContainerSelection_Overlay xList("Select container collection");
	
	xList.AddItem("*Create new collection*", ASURA_HASH_ID_UNSET, ASURA_HASH_ID_UNSET == uContainerHash);
	
	// Loop through container tree, adding to the list.
	Asura_TemplateHashTree_Iterator<Tools_Container*> xIt(s_xContainerTree);
	for ( ; !xIt.Done(); xIt.Next() )
	{
		Tools_Container** ppxContainer = xIt.GetCurrent();
		if (!ppxContainer) continue;
		
		const Tools_Container* pxContainer = (*ppxContainer);
		if (!pxContainer) continue;
		
		if (pxContainer->GetClassification() != AsuraContainerClass_Collection)
		{
			// Only list collections.
			continue;
		}

		if (pxContainer->IsGroupScope())
		{
			// Skip group-scoped collections.
			continue;
		}

		Asura_String xName;
		pxContainer->MakeFullName(xName);

		const char* pszHelpString = xName.GetString();
		const char* pszContainerName = pxContainer->GetContainerName().GetString();

		const Asura_Hash_ID uHash = pxContainer->GetContainerHash();
		xList.AddItem(pszContainerName, uHash, uHash == uContainerHash, pszHelpString);
	}

	if ( !xList.DoModal() ) return false; // Cancelled.

	uContainerHash = xList.GetSelectionData();
	return true;
}

//////////////////////////////////
// CreateCollectionModalOverlay //
//////////////////////////////////
// Create a collection. Return false if cancelled.
bool Tools_Container_System::CreateCollectionModalOverlay(Asura_Hash_ID& uContainerHash)
{
	// Prompt for file.
	Asura_Hash_ID uFileHash = Tools_Container_Overlay_Manager::GetSelectedFileHash();
	bool bOk = SelectFileModalOverlay(uFileHash, true);
	if (!bOk || uFileHash == ASURA_HASH_ID_UNSET)
	{
		return false;
	}

	// Prompt for group.
	bool bCreatedGroup = false;
	Asura_Hash_ID uGroupHash = Tools_Container_Overlay_Manager::GetSelectedGroupHash();
	if (uContainerHash == ASURA_HASH_ID_UNSET)
	{
		// When creating a new collection, default to highlighting the "create new group" option.
		uGroupHash = ASURA_HASH_ID_UNSET;
	}
	bOk = SelectGroupModalOverlay(uFileHash, uGroupHash, true);
	if (!bOk)
	{
		return false;
	}
	else if (uGroupHash == ASURA_HASH_ID_UNSET)
	{
		// Create new group.
		if (!CreateNewGroup(uFileHash, uGroupHash))
		{
			return false;
		}
		bCreatedGroup = true;
	}

	// Use the group name as the default container name, for newly created groups.
	const char* szDefaultName = 0;
	if (bCreatedGroup)
	{
		const Tools_ContainerGroup* pxGroup = FindGroupByHash(uGroupHash);
		if (pxGroup)
		{
			szDefaultName = pxGroup->GetGroupName().GetString();
		}
	}

	// Prompt for container base name and create the collection.
	bOk = CreateNewCollection(uFileHash, uGroupHash, uContainerHash, szDefaultName);
	return bOk;
}

////////////////////////////
// SelectFileModalOverlay //
////////////////////////////
// Select a file. Return false if cancelled.
bool Tools_Container_System::SelectFileModalOverlay(Asura_Hash_ID& uFileHash, bool bOnlyListWriteable)
{
	ListSelection_Overlay xList("Select container file");
	
	Asura_TemplateHashTree_ConstIterator<Tools_ContainerFile> xIt(s_xFileTree);
	for ( ; !xIt.Done() ; xIt.Next())
	{
		const Tools_ContainerFile* pxFile = xIt.GetCurrent();
		if (!pxFile) continue;
		pxFile->RefreshWriteableStatus();

		if (!bOnlyListWriteable || pxFile->IsWriteable())
		{
			const Asura_String& xName = pxFile->GetShortName();
			if (!xName.IsBlank())
			{
				const Asura_Hash_ID uEntryFileHash = pxFile->GetFileHash();
				xList.AddItem(xName.GetString(), uEntryFileHash, uEntryFileHash == uFileHash);
			}
		}
	}

	if (xList.GetNumItems() == 0)
	{
		xList.AddItem("No writeable files found!", ASURA_HASH_ID_UNSET);
	}

	const bool bSelected = xList.DoModal();
	if (bSelected)
	{
		uFileHash = static_cast<Asura_Hash_ID>(xList.GetSelectionData());
	}
	return bSelected;
}

/////////////////////////////
// SelectGroupModalOverlay //
/////////////////////////////
// Select a group from the specified file. Optional create group option. Return false if cancelled.
bool Tools_Container_System::SelectGroupModalOverlay(const Asura_Hash_ID uFileHash, Asura_Hash_ID& uGroupHash, bool bCreateGroupOption)
{
	const Tools_ContainerFile* const pxFile = FindFileByHash(uFileHash);
	Asura_Assert(pxFile, "Null container file pointer");
	if (!pxFile) return false;
	
	ListSelection_Overlay xList("Select container group");

	if (bCreateGroupOption)
	{
		xList.AddItem("*Create new group*", ASURA_HASH_ID_UNSET);
	}

	Asura_TemplateHashTree_ConstIterator<Tools_ContainerGroup> xIt(s_xGroupTree);
	for ( ; !xIt.Done() ; xIt.Next())
	{
		const Tools_ContainerGroup* pxGroup = xIt.GetCurrent();
		if (!pxGroup || pxGroup->GetFileHash() != uFileHash)
		{
			continue;
		}
	
		const Asura_String& xName = pxGroup->GetGroupName();
		if (!xName.IsBlank())
		{
			const Asura_Hash_ID uEntryGroupHash = pxGroup->GetGroupHash();
			xList.AddItem(xName.GetString(), uEntryGroupHash, uEntryGroupHash == uGroupHash);
		}
	}

	if (xList.GetNumItems() == 0)
	{
		xList.AddItem("No groups found!");
	}

	const bool bSelected = xList.DoModal();
	if (bSelected)
	{
		uGroupHash = static_cast<Asura_Hash_ID>(xList.GetSelectionData());
	}
	return bSelected;
}

/////////////////////
// ContainerSearch //
/////////////////////
// Check whether the specified container (uContainerHash) equals or contains the searched for container (uSearchHash).
// Return true if it does.
bool Tools_Container_System::ContainerSearch(const Asura_Hash_ID uContainerHash, const Asura_Hash_ID uSearchHash)
{
	if (uContainerHash == ASURA_HASH_ID_UNSET) return false;
	if (uSearchHash == ASURA_HASH_ID_UNSET) return false;

	if (uContainerHash == uSearchHash)
	{
		return true;
	}

	const Tools_Container* pxToolsContainer = FindContainerByHash(uContainerHash);
	if (pxToolsContainer)
	{
		return pxToolsContainer->Contains(uSearchHash);
	}

	return false;
}

//////////////////////////
// SetContainerModified //
//////////////////////////
// Container has been modified, refresh the entities.
void Tools_Container_System::SetContainerModified(const Asura_Hash_ID uContainerChanged, bool bMinorChange)
{
	Tools_Entity_Container::RefreshContainerModified(uContainerChanged, bMinorChange);
	Tools_Mode_Container::RefreshContainerModified(uContainerChanged, bMinorChange);
}

/////////////////////////////////
// CopyDataForMarkerAttachment //
/////////////////////////////////
// An attachment has become a marker attachment. Copy child hash data for any collections that contain it.
// Marker attachments must always be at the end of Asura_Container_Collections
// otherwise freed memory will be accessed on trying to Unlink.
void Tools_Container_System::CopyDataForMarkerAttachment(const Asura_Hash_ID uContainerHash)
{
	for ( Asura_TemplateHashTree_Iterator<Tools_Container*> xIt(s_xContainerTree); !xIt.Done(); xIt.Next() )
	{
		Tools_Container** const ppxContainer = xIt.GetCurrent();
		if (!ppxContainer) continue;
		
		Tools_Container* const pxContainer = (*ppxContainer);
		if (!pxContainer || pxContainer->GetClassification() != AsuraContainerClass_Collection)
		{
			continue;
		}
	
		// For any collections containing the specified attachment container, copy their data back to Asura.
		// There is no need to mark them as modified as the data hasn't changed it's just been reordered
		// in the Asura version of the container.
		for (Tools_Container_ChildIterator xIt(pxContainer); !xIt.Done(); xIt.NextSkipIfSame())
		{
			if (xIt.GetCurrentHash() == uContainerHash)
			{
				pxContainer->CopyChildHashDataToAsura();
				break;
			}
		}
	}
}

//////////////////////
// DeleteIfOrphaned //
//////////////////////
// Delete a container if it is orphaned. Only delete those with group-scope.
void Tools_Container_System::DeleteIfOrphaned(const Asura_Hash_ID uContainerHash, bool bPromptUser /*= false*/)
{
	const Tools_Container* pxContainer = FindContainerByHash(uContainerHash);
	if (pxContainer && pxContainer->IsGroupScope())
	{
		if (IsOrphaned(pxContainer))
		{
			/*const bool bDeleted =*/
			DeleteContainer(uContainerHash, bPromptUser, true);
		}
	}
}

////////////////
// IsOrphaned //
////////////////
// Return true if the container is known to be orphaned.
bool Tools_Container_System::IsOrphaned(const Asura_Hash_ID uContainerHash)
{
	const Tools_Container* pxContainer = FindContainerByHash(uContainerHash);
	return IsOrphaned(pxContainer);
}

// Return true if the container is known to be orphaned.
bool Tools_Container_System::IsOrphaned(const Tools_Container* pxContainer)
{
	if (!pxContainer) return false;

	const bool bGroupScope = pxContainer->IsGroupScope();
	const Asura_Hash_ID uGroupHash = pxContainer->GetGroupHash();

	// Collections with global scope can be referred to by entities, so they
	// are never orphans.
	if (!bGroupScope && pxContainer->GetClassification() == AsuraContainerClass_Collection)
	{
		return false;
	}

	const Asura_Hash_ID uSearchHash = pxContainer->GetContainerHash();

	// Loop over all containers checking to see if they refer to the specified container.
	for ( Asura_TemplateHashTree_Iterator<Tools_Container*> xIt(s_xContainerTree); !xIt.Done(); xIt.Next() )
	{
		Tools_Container** ppxContainer = xIt.GetCurrent();
		if (!ppxContainer) continue;
		
		const Tools_Container* pxContainer = (*ppxContainer);
		if (!pxContainer) continue;

		if (bGroupScope && pxContainer->GetGroupHash() != uGroupHash)
		{
			// Container being checked has group scope, so it can only be referred
			// to by other containers in the same group.
			continue;
		}

		const Asura_Hash_ID uHash = pxContainer->GetContainerHash();

		if (uHash != uSearchHash && pxContainer->Contains(uSearchHash))
		{
			// Found a reference.
			return false;
		}
	}

	// Found no references.
	return true;
}

///////////////////////////////////
// SelectMarkerPointModalOverlay //
///////////////////////////////////
// Select a marker point. Return true if Ok, false if cancelled.
bool Tools_Container_System::SelectMarkerPointModalOverlay(Asura_String& xMarkerName, Asura_Hash_ID uGroupHash)
{
	const Asura_Hash_ID uMarkerHash = xMarkerName.GetHash();

	ListSelection_Overlay xList("Select Marker Point");
	xList.AddItem("*None*", ASURA_HASH_ID_UNSET, uMarkerHash == ASURA_HASH_ID_UNSET);

	// We need to find all relevant marker points.
	if (Tools_System::IsInContainerMode())
	{
		// Just include everything that can be referenced from the root collection.
		const Tools_Container* pxRoot = FindContainerByHash(Tools_Mode_Container::GetContainerToEditHash());
		if (pxRoot)
		{
			pxRoot->AddMarkerPoints(xList, uMarkerHash);
		}
	}
	else
	{
		// Look for any collections in the same group and recursively
		// add marker points for any meshes that are referenced.
		for ( Asura_TemplateHashTree_Iterator<Tools_Container*> xIt(s_xContainerTree) ; !xIt.Done(); xIt.Next() )
		{
			Tools_Container** ppxContainer = xIt.GetCurrent();
			if (!ppxContainer) continue;
			
			const Tools_Container* pxContainer = (*ppxContainer);
			if (pxContainer &&
				pxContainer->GetClassification() == AsuraContainerClass_Collection &&
				pxContainer->GetGroupHash() == uGroupHash)
			{
				pxContainer->AddMarkerPoints(xList, uMarkerHash);
			}
		}
	}

	if (!xList.DoModal()) return false; // Cancelled

	if (xList.GetSelectionData() == ASURA_HASH_ID_UNSET)
	{
		xMarkerName.MakeNull();
	}
	else
	{
		xMarkerName = xList.GetSelectionString();
	}

	return true;
}

/////////////////
// ToolsRender //
/////////////////
// Render any tools specific stuff.
void Tools_Container_System::ToolsRender(const Asura_Hash_ID uContainerHash,
										 const Asura_Vector_3& xPosition,
										 const Asura_Matrix_3x3& xOrientation)
{
	const Tools_Container* const pxContainer = FindContainerByHash(uContainerHash);
	if (pxContainer)
	{
		pxContainer->ToolsRender(xPosition, xOrientation);
	}
}

//////////////////
// ReplaceState //
//////////////////
// Replaced a state hash associated with a specified container collection. Fix up other state machines.
void Tools_Container_System::ReplaceState(const Asura_Hash_ID uContainerHash,
										  const Asura_Hash_ID uOldStateHash,
										  const Asura_Hash_ID uNewStateHash)
{
	// Loop over all state machines. Replacing the hash when found.
	for (Asura_TemplateHashTree_Iterator<Tools_Container*> xIt(s_xContainerTree); !xIt.Done(); xIt.Next())
	{
		Tools_Container** ppxContainer = xIt.GetCurrent();
		if (!ppxContainer) continue;
		
		Tools_Container* pxContainer = (*ppxContainer);
		if (pxContainer)
		{
			Tools_ContainerStateMachine* pxMachine = pxContainer->GetStateMachine();
			if (pxMachine)
			{
				const bool bModified = pxMachine->ReplaceState(uContainerHash, uOldStateHash, uNewStateHash);
				if (bModified)
				{
					// Mark as modified.
					pxContainer->SetModifiedStates();
				}
			}
		}
	}

	// May as well fix up container entities in this level now.
	// Entities that need fixing in other levels will be done when they are loaded.
	for (Asura_Hashed_List_It<Tools_Entity_Container> xIt(Tools_Entity_Container::GetList()); !xIt.Done(); xIt.Next())
	{
		Tools_Entity_Container* pxToolsEntity = xIt.GetCurrent();
		if (pxToolsEntity &&
			pxToolsEntity->GetContainerHash() == uContainerHash)
		{
			const Asura_Container_InputState& xInputState = pxToolsEntity->GetInputState();
			bool bNeedToFix = false;
			for (u_int u=0; u<xInputState.GetNumInputs(); ++u)
			{
				if (xInputState.GetStateHash(u) == uOldStateHash)
				{
					bNeedToFix = true;
					break;
				}
			}

			if (bNeedToFix)
			{
				pxToolsEntity->FixUpErrors(false);
			}
		}
	}

	// Fix up any static messages in this level now.
	// Those in other levels will be done after they have been loaded, when Object mode is entered.
	VerifyStaticMessages(false);
}

////////////////
// SetShowPFX //
////////////////
// Show PFX or not. Show PFX has changed just refresh (the TeleportTo call will cause the effect to stop or start).
void Tools_Container_System::SetShowPFX(bool bShowPFX)
{
	if (Tools_System::IsInContainerMode()) return;

	Tools_Entity_Container::RefreshMinorChangeAllInstances();
	Tools_Mode_Container::RefreshContainerModified(Tools_Mode_Container::GetContainerToEditHash(), true);
}

/////////////////////
// ShouldRenderPFX //
/////////////////////
// Should we render the PFX.
bool Tools_Container_System::ShouldRenderPFX()
{
	return  Tools_Render_Options::GetShowPFX() ||
			!Tools_System::IsActive() ||
			Tools_System::IsInContainerMode();
}

////////////////////////////////////////
// GetSingleGlobalCollectionFromGroup //
////////////////////////////////////////
// Search the group for a single global-scope collection (this is the most common case).
// If found then return its container hash.
Asura_Hash_ID Tools_Container_System::GetSingleGlobalCollectionFromGroup(Asura_Hash_ID uGroupHash)
{
	Asura_Hash_ID uCollectionHash = ASURA_HASH_ID_UNSET;

	// Check that the group exists.
	const Tools_ContainerGroup* pxGroup = Tools_Container_System::FindGroupByHash(uGroupHash);
	if (pxGroup)
	{
		// Search through all of the containers looking for ones in the group.
		for ( Asura_TemplateHashTree_Iterator<Tools_Container*> xIt(s_xContainerTree) ; !xIt.Done(); xIt.Next() )
		{
			Tools_Container** ppxContainer = xIt.GetCurrent();
			if (!ppxContainer) continue;
			
			Tools_Container* const pxContainer = (*ppxContainer);
			if (pxContainer &&
				pxContainer->GetGroupHash() == uGroupHash &&
				!pxContainer->IsGroupScope() &&
				pxContainer->GetClassification() == AsuraContainerClass_Collection)
			{
				// Global-scope collection in the specified group.
				if (uCollectionHash != ASURA_HASH_ID_UNSET)
				{
					// More than one such collection. Stop.
					return ASURA_HASH_ID_UNSET;
				}
				uCollectionHash = pxContainer->GetContainerHash();
			}
		}
	}

	return uCollectionHash;
}


///////////////////////////
// IsContainerStateValid //
///////////////////////////
// Is the specified container state valid? uContainerHash should refer to a collection.
bool Tools_Container_System::IsContainerStateValid(Asura_Hash_ID uContainerHash, Asura_Hash_ID uStateHash, u_int uInputIndex)
{
	const Tools_Container* const pxContainer = FindContainerByHash(uContainerHash);
	if (pxContainer)
	{
		const Tools_ContainerStateMachine* const pxMachine = pxContainer->GetStateMachine();
		if (pxMachine)
		{
			const Tools_ContainerState* const pxState = pxMachine->FindState(uStateHash);
			if (pxState && pxState->IsEnabledForInputSlot(uInputIndex))
			{
				return true;
			}
		}
	}

	return false;
}

///////////////////////////
// IsContainerStateValid //
///////////////////////////
bool Tools_Container_System::IsContainerStateValid(Asura_Hash_ID uContainerHash, const Asura_Container_InputState& xInputState)
{
	const Tools_Container* const pxContainer = FindContainerByHash(uContainerHash);
	if (!pxContainer) return false;
	
	const Tools_ContainerStateMachine* const pxMachine = pxContainer->GetStateMachine();
	if (!pxMachine) return false;

	if (pxMachine->GetNumInputs() != xInputState.GetNumInputs())
	{
		return false;
	}
	
	for (u_int u=0; u<xInputState.GetNumInputs(); ++u)
	{
		const Asura_Hash_ID uStateHash = xInputState.GetStateHash(u); 
		const Tools_ContainerState* const pxState = pxMachine->FindState(uStateHash);
		if (!pxState || !pxState->IsEnabledForInputSlot(u))
		{
			return false;
		}
	}

	return true;
}

//
// Verification
//
const char* Tools_Container_System::GetCategory()
{
	return szCONTAINER_LOG_CATEGORY;
}
const char* Tools_Container_System::GetSubCategory()
{
	return szCONTAINER_LOG_SUBCATEGORY;
}

/////////////////////////
// IncrementErrorCount //
/////////////////////////
void Tools_Container_System::IncrementErrorCount()
{
	++s_iNumErrors;
}

////////////////////////////////
// VerifyContainerInformation //
////////////////////////////////
void Tools_Container_System::VerifyContainerInformation()
{
	s_iNumErrors = 0;
	Tools_MessageLog::DeleteMessages( GetCategory(), GetSubCategory() );

	// Check that the containers are set up correctly.
	Asura_TemplateHashTree_Iterator<Tools_Container*> xIt(s_xContainerTree);
	for ( ; !xIt.Done(); xIt.Next() )
	{
		Tools_Container** ppxContainer = xIt.GetCurrent();
		if (!ppxContainer) continue;
		
		const Tools_Container* pxContainer = (*ppxContainer);
		if (pxContainer)
		{
			pxContainer->Verify(false);
		}
	}

	// Check the entities and static messages.
	VerifyEntities();
	VerifyStaticMessages();

	Tools_MessageLog::AddMessage( GetCategory(), GetSubCategory(), "%d errors found in container system.", s_iNumErrors );

	if (s_iNumErrors > 0)
	{
		Tools_MessageLog::ShowOverlay( GetCategory(), GetSubCategory() );
	}

	s_iNumErrors = 0;
}

/////////////////////
// VerifyContainer //
/////////////////////
// Verify a container and all of its children.
// Return true if Ok, i.e. no errors.
bool Tools_Container_System::VerifyContainer(const Tools_Container* pxContainer)
{
	if (!pxContainer) return true;

	s_iNumErrors = 0;
	Tools_MessageLog::DeleteMessages( GetCategory(), GetSubCategory() );

	// Verify recursively.
	pxContainer->Verify(true);

	if (s_iNumErrors > 0)
	{
		Tools_MessageLog::AddMessage( GetCategory(), GetSubCategory(), "%d errors found in container '%s'.", s_iNumErrors, pxContainer->GetContainerName().GetString() );
		Tools_MessageLog::ShowOverlay( GetCategory(), GetSubCategory() );
	
		s_iNumErrors = 0;
		return false;
	}

	return true;
}

////////////////////
// VerifyEntities //
////////////////////
void Tools_Container_System::VerifyEntities()
{
	const Asura_Hashed_List<Asura_ServerEntity_Container>* xList = Asura_ServerEntity_Container::GetList();

	for (Asura_Hashed_List_ConstIt<Asura_ServerEntity_Container> xIt(xList); !xIt.Done(); xIt.Next())
	{
		const Asura_ServerEntity_Container* pxServer = xIt.GetCurrent();
		VerifyEntity(pxServer);
	}
}

//////////////////
// VerifyEntity //
//////////////////
// Return true if Ok, i.e. no errors.
bool Tools_Container_System::VerifyEntity(const Asura_ServerEntity_Container* pxServer)
{
	if (!pxServer) return true;

	const int iInitialNumErrors = s_iNumErrors;

	const Asura_Guid uGuid = pxServer->GetGuid();
	const Asura_Hash_ID uContainerHash = pxServer->GetContainerHash();
	const Tools_Container* const pxContainer = FindContainerByHash(uContainerHash);
	if (uContainerHash == ASURA_HASH_ID_UNSET)
	{
		Tools_MessageLog::AddMessage( GetCategory(), GetSubCategory(), new Tools_MessageLogAction_GotoEntity(uGuid), "Entity %u : container not set.", uGuid );
		++s_iNumErrors;
	}
	else if (pxContainer == 0)
	{
		Tools_MessageLog::AddMessage( GetCategory(), GetSubCategory(), new Tools_MessageLogAction_GotoEntity(uGuid), "Entity %u : container (0x%08x) not found.", uGuid, uContainerHash );
		++s_iNumErrors;
	}

	// Check state is valid.
	if (pxContainer)
	{
		const Asura_Container_InputState& xInputState = pxServer->GetInputState();
		for (u_int u=0; u<xInputState.GetNumInputs(); ++u)
		{
			const Asura_Hash_ID uState = xInputState.GetStateHash(u);
			const Tools_ContainerStateMachine* const pxStateMachine = pxContainer->GetStateMachine();
			const Tools_ContainerState* const pxState = (pxStateMachine ? pxStateMachine->FindState(uState) : 0);

			if (uState == ASURA_HASH_ID_UNSET)
			{
				Tools_MessageLog::AddMessage( GetCategory(), GetSubCategory(), new Tools_MessageLogAction_GotoEntity(uGuid), "Entity %u : container state not set.", uGuid );
				++s_iNumErrors;
			}
			else if (pxState == 0)
			{
				Tools_MessageLog::AddMessage( GetCategory(), GetSubCategory(), new Tools_MessageLogAction_GotoEntity(uGuid), "Entity %u : container state (0x%08x) not found.", uGuid, uState );
				++s_iNumErrors;
			}
			else if (!pxState->IsEnabledForInputSlot(u))
			{
				Tools_MessageLog::AddMessage( GetCategory(), GetSubCategory(), new Tools_MessageLogAction_GotoEntity(uGuid), "Entity %u : container state '%s' not valid for input %u.", uGuid, pxState->GetStateName().GetString(), u );
				++s_iNumErrors;
			}
		}
	}

	return (iInitialNumErrors == s_iNumErrors);
}

/////////////////////////////
// VerifyContainerMessages //
/////////////////////////////
// Return true if Ok, i.e. no errors.
bool Tools_Container_System::VerifyContainerMessages()
{
	s_iNumErrors = 0;
	VerifyStaticMessages();
	const bool bOK = (s_iNumErrors == 0);
	s_iNumErrors = 0;
	return bOK;
}

// Helper
static Tools_MessageLogAction_GotoEntity* GetAction(Asura_Guid uFromGuid)
{
	if (uFromGuid != ASURA_GUID_UNREGISTERED)
	{
		return new Tools_MessageLogAction_GotoEntity(uFromGuid);
	}
	return 0;
}

//////////////////////////
// VerifyStaticMessages //
//////////////////////////
void Tools_Container_System::VerifyStaticMessages(bool bLogFixes /*= true*/)
{
	for ( Tools_StaticMessageIterator xIt; !xIt.Done(); xIt.Next() )
	{
		Asura_StaticMessage* const pxMessage = xIt.GetCurrent();
		
		if (pxMessage->m_pData == 0) continue;

		u_int uInputIndex = 0;
		Asura_Hash_ID* puNewState = 0;

		if (pxMessage->m_usMsgID == ASURA_MSG_CONTAINER_CHANGE_STATE)
		{
			Asura_Message_Container_ChangeState* pxData = static_cast<Asura_Message_Container_ChangeState*>(pxMessage->m_pData);
			puNewState = &pxData->m_uStateHash;
		}
		else if (pxMessage->m_usMsgID == ASURA_MSG_CONTAINER_CHANGE_STATE_EXTENDED)
		{
			Asura_Message_Container_ChangeStateExtended* pxData = static_cast<Asura_Message_Container_ChangeStateExtended*>(pxMessage->m_pData);
			uInputIndex = pxData->m_uInputIndex;
			puNewState = &pxData->m_uStateHash;
		}
		else
		{
			continue;
		}

		// Check that the new state exists.
		const Asura_Hash_ID uNewState = *puNewState;

		const Asura_Guid uToGuid = pxMessage->m_uSendTo;
		const Asura_ServerEntity_Container* const pxServer = Asura_ServerEntity_Container::FindEntity(uToGuid);
		if (!pxServer) continue;

		const Asura_Hash_ID uTargetContainer = pxServer->GetContainerHash();
		Tools_Container* const pxContainer = Tools_Container_System::FindContainerByHash(uTargetContainer);
		if (!pxContainer) continue;
	
		const Tools_ContainerStateMachine* const pxMachine = pxContainer->GetStateMachine();
		if (!pxMachine) continue;
		const u_int uNumInputs = pxMachine->GetNumInputs();

		const Tools_ContainerState* const pxState = pxMachine->FindState(uNewState);
		
		const bool bNullState = (pxState == 0);
		const bool bInputMismatch = (uInputIndex >= uNumInputs);
		const bool bWrongSlot = (pxState && !pxState->IsEnabledForInputSlot(uInputIndex));
		const bool bError = (bNullState || bWrongSlot || bInputMismatch);
		if (!bError) continue;

		// Find the "from object" if needed.
		Tools_Object* pxFromObject = 0;
		Asura_Guid uFromGuid = ASURA_GUID_UNREGISTERED;

		if (bLogFixes || bNullState)
		{
			pxFromObject = Tools_Object_System::GetOwnerOfMessageBlock( xIt.GetCurrentMessageBlockID() );
			if (pxFromObject)
			{
				uFromGuid = pxFromObject->GetGuid();
			}
		}

		const char* szMessageDesc = "unknown";
		Asura_Network::GetMsgIDDesc(pxMessage->m_usMsgID, &szMessageDesc);

		// Log the errors, do auto-fix-up if needed.
		if (bWrongSlot && bLogFixes)
		{
			Tools_MessageLog::AddMessage( GetCategory(), GetSubCategory(),  GetAction(uFromGuid), "Message from entity %u to entity %u '%s': container state '%s' not valid for input %u.", uFromGuid, uToGuid, szMessageDesc, pxState->GetStateName().GetString(), uInputIndex );
			++s_iNumErrors;
		}
		else if (bNullState)
		{
			// State doesn't exist.

			// Perhaps the state has been renamed, check the old state hashes.
			const Asura_Hash_ID uReplaceNewState = pxMachine->GetHashForRenamedState(uNewState);
			if (uReplaceNewState != ASURA_HASH_ID_UNSET && uReplaceNewState != uNewState)
			{
				// Fix up automatically. Log the fix.
				if (bLogFixes)
				{
					const Tools_ContainerState* const pxReplaceState = pxMachine->FindState(uReplaceNewState);
					const char* szStateName = pxReplaceState ? pxReplaceState->GetStateName().GetString() : "*Unknown*";

					Tools_MessageLog::AddMessage( GetCategory(), GetSubCategory(), GetAction(uFromGuid), "Message from entity %u to entity %u '%s': container state renamed (0x%08x) -> '%s'. Fixed.", uFromGuid, uToGuid, szMessageDesc, uNewState, szStateName );
				}

				*puNewState = uReplaceNewState;

				// Mark as modified.
				if (pxFromObject)
				{
					pxFromObject->SetEntityModified();
				}
				else
				{
					// Couldn't find the relevant tools entity, try saving the main entity file.
					Tools_SaveAsura::SetEntityModified();
				}
			}
			else if (bLogFixes)
			{
				Tools_MessageLog::AddMessage( GetCategory(), GetSubCategory(), GetAction(uFromGuid), "Message from entity %u to entity %u '%s': container state (0x%08x) not found.", uFromGuid, uToGuid, szMessageDesc, uNewState );
				++s_iNumErrors;
			}
		}
		else if (bInputMismatch && bLogFixes)
		{
			Tools_MessageLog::AddMessage( GetCategory(), GetSubCategory(), GetAction(uFromGuid), "Message from entity %u to entity %u '%s': sent to input %u (maximum allowed is %u).", uFromGuid, uToGuid, szMessageDesc, uInputIndex, uNumInputs-1);
			++s_iNumErrors;
		}
	}
}

#endif //#ifdef ASURA_USE_CONTAINER_SYSTEM
