//
// Asura_Animation_IK_Template_System.cpp
//
// Created by Richard Rice - 12/05/2008
// 
// Copyright (C) 2008 Rebellion, All Rights Reserved.
//

#include "Core/Asura.h"
#include "Animation/IK/Tools_Animation_IK_Template_System.h"

#ifdef ASURA_USE_ANIMATION_IK_SOLVER

#include "Animation/IK/Asura_Animation_IK_Template_System.h"
#include "Animation/IK/Asura_Animation_IK_Template.h"
#include "Animation/IK/Asura_Animation_IK_Template_Chain_LOS.h"
#include "Animation/IK/Asura_Animation_IK_Template_Chain_Markerpoint.h"
#include "Animation/Asura_Animation.h"
#include "ChunkSystem/Asura_Chunk_Stream.h"
#include "ChunkSystem/Asura_Chunk_IK_Template.h"
#include "ChunkSystem/Asura_SaveBlock.h"
#include "Core/Asura_NameTree.h"
#include "Interface/Asura_Dialog.h"
#include "Interface/Asura_Overlay.h"
#include "Interface/Asura_Overlay_System.h"
#include "Interface/Asura_Tree_Dialog.h"

#include "Tools_Overlays.h"
#include "Tools_System.h"
#include "Cutscene/Tools_Mode_Cutscene.h"
#include "Hierarchy/Tools_Animation_Files.h"
#include "Load&Save/LevelExport/Tools_Level_Export.h"
#include "Load&Save/Tools_LoadedFileList.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_Timed_Messages.h"
#include "PC/FileSystem/Tools_FileSystem.h"

Asura_Vector_2 Tools_Animation_IK_Template_System::s_xMainOverlayPosition(10.0f,10.0f);
u_int Tools_Animation_IK_Template_System::s_uMainOverlayMinimised = 0; 

Asura_StringTree Tools_Animation_IK_Template_System::s_xTemplateNameTree;
u_int Tools_Animation_IK_Template_System::s_uTemplateNameListSize;
const char** Tools_Animation_IK_Template_System::s_pszTemplateNameList;
int Tools_Animation_IK_Template_System::s_iSelectedTemplateIndex;
Asura_List_Dialog* Tools_Animation_IK_Template_System::s_pxTemplateListDialog = 0;

Asura_Vector_2 Tools_Animation_IK_Template_System::s_xEditOverlayPosition(30.0f,10.0f);

Asura_Animation_IK_Template* Tools_Animation_IK_Template_System::s_pxEditedTemplate = 0;
Asura_Hierarchy_Skin* Tools_Animation_IK_Template_System::s_pxEditedTemplateSkin = 0;

u_int Tools_Animation_IK_Template_System::s_uChainNameListSize;
const char** Tools_Animation_IK_Template_System::s_pszChainNameList;
int Tools_Animation_IK_Template_System::s_iSelectedChainIndex;
bool Tools_Animation_IK_Template_System::s_bSelectedChainChanged;

u_int Tools_Animation_IK_Template_System::s_uBoneNameListSize;
char** Tools_Animation_IK_Template_System::s_pszBoneNameList;
int Tools_Animation_IK_Template_System::s_iSelectedBoneIndex;
Asura_List_Dialog* Tools_Animation_IK_Template_System::s_pxBoneListDialog;
bool Tools_Animation_IK_Template_System::s_bSelectedBoneChanged;

bool Tools_Animation_IK_Template_System::s_bTemplateChanged;

bool Tools_Animation_IK_Template_System::s_bModified = false;

int Tools_PickIKTemplate_Dialog::s_iListValidID = 0;

#define CALLBACK_VOID(__Function) Asura_Callback_Void<void>(__Function)


static const char* g_aszIKChainType[ASURA_IKCHAIN_NUMTYPES] =
{
	"LOS",
	"Marker Point",
};

static const char* g_aszIKChainHelpText[ASURA_IKCHAIN_NUMTYPES] =
{
	"This type of chain does LOS test to find the ground and extends the bones in the chain to meet it.",
	"This type of chain extends the bones so that they reach the specified marker point.",
};


class Tools_MessageLogAction_IKTemplate	: public Tools_MessageLogAction
{
public :
	Tools_MessageLogAction_IKTemplate(const Asura_Animation_IK_Template* pxTemplate, const Asura_Animation_IK_Template_Chain* pxChain)
	{
		m_uTemplateID = pxTemplate ? pxTemplate->GetHashID() : 0;
		m_uChainID = pxChain ? Asura_GetHashID(pxChain->GetName()) : 0;
	}
	virtual void Execute()
	{
		Tools_Animation_IK_Template_System::EditTemplate(m_uTemplateID, m_uChainID);
	}

	Asura_Hash_ID m_uTemplateID;
	Asura_Hash_ID m_uChainID;
};




void Tools_Animation_IK_Template_System::Initialise()
{
	TransferModeData(TOOLSMODEDATAFILE_LOAD);

}

void Tools_Animation_IK_Template_System::Shutdown()
{
	TransferModeData(TOOLSMODEDATAFILE_SAVE);
	
	CloseOverlay();
	DeleteTemplateNameList();
	DeleteChainNameList();
	DeleteBoneNameList();
}

void Tools_Animation_IK_Template_System::TransferModeData(int iMode)
{
	//open the file
	Tools_ModeDataFile xFile("IKTemplate.dat",iMode);

	//transfer the data
	//(none of these should be removed , and new data should go on the end, otherwise
	//the files will get screwed up)
	if( false == xFile.GetError())
	{
		xFile.Transfer(s_xMainOverlayPosition);
		xFile.Transfer(s_xEditOverlayPosition);
	}
}

void Tools_Animation_IK_Template_System::Update()
{
	if(s_bTemplateChanged)
	{
		SetModified();
		s_bTemplateChanged = false;
	}
	if(s_bSelectedChainChanged)
	{
		RefreshEditTemplateOverlay();
		s_bSelectedChainChanged = false;
	}
	if(s_bSelectedBoneChanged)
	{
		UpdateTwistBoneControl();
		s_bSelectedBoneChanged = false;
	}
}

void Tools_Animation_IK_Template_System::ToggleOverlay()
{
	if(IsOverlayOpen())
	{
		CloseOverlay();
	}
	else
	{
		CreateOverlay();
	}
}

void Tools_Animation_IK_Template_System::CloseOverlay()
{
	Asura_Overlay_System::CloseOverlays(TOOLS_OVERLAY_IK);
	Asura_Overlay_System::CloseOverlays(TOOLS_OVERLAY_IK_EDITTEMPLATE);
}

bool Tools_Animation_IK_Template_System::IsOverlayOpen()
{
	return Asura_Overlay_System::DoesOverlayExist(TOOLS_OVERLAY_IK);
}

void Tools_Animation_IK_Template_System::CreateOverlay()
{
	if(IsOverlayOpen())
	{
		Asura_Overlay_System::BringOverlayToTop(TOOLS_OVERLAY_IK);
		return;
	}

	Asura_Overlay* pxOverlay = new Asura_Overlay("IK Templates", false, &s_xMainOverlayPosition, &s_uMainOverlayMinimised);
	Asura_Vector_2 xOffset(1.0f,3.0f);
	Asura_Dialog* pxDialog;

	//List of existing templates
	pxDialog = new Asura_Dialog("Templates :", xOffset);
	pxOverlay->AddDialog(pxDialog);
	xOffset.y += Asura_Dialog::SmallSpacing();

	s_pxTemplateListDialog = 0;
	UpdateTemplateNameList();
	pxDialog = s_pxTemplateListDialog = new Asura_List_Dialog(xOffset, s_pszTemplateNameList, &s_iSelectedTemplateIndex, 12, s_uTemplateNameListSize, NULL);
	pxDialog->SetHelpText("List of IK Templates");
	s_pxTemplateListDialog->SetDoubleClickFunction(CALLBACK_VOID(EditTemplate));
	pxOverlay->AddDialog(pxDialog);
	xOffset.y = pxDialog->m_xMaxCoord.y + 1.0f;

	pxDialog = new Asura_Function_Dialog("Create", xOffset, CreateTemplate);
	pxDialog->SetHelpText("Create a new IK template");
	pxOverlay->AddDialog(pxDialog);

	xOffset.x += 10.0f;
	pxDialog = new Asura_Function_Dialog("Delete", xOffset, DeleteTemplate);
	pxDialog->SetHelpText("Delete the selected template");
	pxDialog->SetIsEnabledFunction(IsAnyTemplateSelected);
	pxOverlay->AddDialog(pxDialog);
	xOffset.x -= 10.0f;
	xOffset.y += Asura_Dialog::DefaultSpacing();

	pxDialog = new Asura_Function_Dialog("Edit", xOffset, EditTemplate);
	pxDialog->SetHelpText("Edit the selected template");
	pxDialog->SetIsEnabledFunction(IsAnyTemplateSelected);
	pxOverlay->AddDialog(pxDialog);
	xOffset.y += Asura_Dialog::DefaultSpacing();

	pxDialog = new Asura_Function_Dialog("Rename", xOffset, RenameTemplate);
	pxDialog->SetHelpText("Rename the selected template");
	pxDialog->SetIsEnabledFunction(IsAnyTemplateSelected);
	pxOverlay->AddDialog(pxDialog);
	xOffset.y += Asura_Dialog::LargeSpacing();


	pxDialog = new Asura_Function_Dialog("Verify/Update", xOffset, Verify);
	pxDialog->SetHelpText("Checks all the IK templates against their associated skins and updates them as needed.");
	pxOverlay->AddDialog(pxDialog);
	xOffset.y += Asura_Dialog::DefaultSpacing();

	


	pxOverlay->m_uID = TOOLS_OVERLAY_IK;
	Asura_Overlay_System::SubmitNewOverlay(pxOverlay);
}

void Tools_Animation_IK_Template_System::UpdateTemplateNameList()
{
	DeleteTemplateNameList();

	//get all the template names
	for(u_int i=0; i<Asura_Animation_IK_Template_System::GetNumberOfTemplates(); i++)
	{
		const Asura_Animation_IK_Template* pxTemplate = Asura_Animation_IK_Template_System::GetTemplateByIndex(i);
		
		int iJunkIndex;
		s_xTemplateNameTree.FindNodeIndexByName(pxTemplate->GetName(), true, iJunkIndex);
	}

	//get a sorted list of the names
	if(s_xTemplateNameTree.GetNumberOfNodes())
	{
		s_uTemplateNameListSize = s_xTemplateNameTree.GetNumberOfNodes();
		s_pszTemplateNameList = new const char*[s_uTemplateNameListSize];

		s_xTemplateNameTree.MakeSortedNameList(s_pszTemplateNameList);
		
	}
	else
	{
		//no templates , so just add a dummy entry
		s_uTemplateNameListSize = 1;
		s_pszTemplateNameList = new const char*[1];
		s_pszTemplateNameList[0] = "*None*";
	}

	if(s_iSelectedTemplateIndex >= (int)s_uTemplateNameListSize)
	{
		s_iSelectedTemplateIndex = s_uTemplateNameListSize-1;
	}

	if(s_pxTemplateListDialog)
	{
		s_pxTemplateListDialog->ReplaceStrings(s_uTemplateNameListSize, s_pszTemplateNameList);
	}
}

void Tools_Animation_IK_Template_System::DeleteTemplateNameList()
{
	s_xTemplateNameTree.Reset();
	delete [] s_pszTemplateNameList;
	s_pszTemplateNameList = 0;
	s_uTemplateNameListSize = 0;
}

bool Tools_Animation_IK_Template_System::IsAnyTemplateSelected()
{
	return s_xTemplateNameTree.GetNumberOfNodes()>0;
}

void Tools_Animation_IK_Template_System::CreateTemplate()
{
	//ask for a skin
	Asura_Hierarchy_Skin* pxSkin = Tools_Animation_Files::ChooseSkin(NULL, NULL);
	if(!pxSkin) return;

	//get a name for the new template
	bool bNameOkay = false;
	GetString_Overlay xOverlay("Name for IK template :", "");
	const char* szName = 0;

	while(!bNameOkay)
	{
		if(!xOverlay.DoModal()) return;

		//make sure the name is okay
		szName = xOverlay.GetString();
		if(!szName || !*szName) continue;

		if(Asura_Animation_IK_Template_System::GetTemplateByHash(Asura_GetHashID(szName)))
		{
			ShowMessageOverlay("A template with that name/hash ID already exists.");
		}
		else
		{
			bNameOkay = true;
		}
	}

	//create the template
	Asura_Animation_IK_Template* pxTemplate = new Asura_Animation_IK_Template(szName);	
	pxTemplate->m_uSkinID = pxSkin->GetHashID();
	SetModified(true);

	//update the overlay, and change the current selection
	UpdateTemplateNameList();
	for(u_int i=0; i<s_uTemplateNameListSize; i++)
	{
		if(!stricmp(szName, s_pszTemplateNameList[i]))
		{
			s_iSelectedTemplateIndex = i;
		}
	}

	//start editing the new template
	EditTemplate();

	Tools_PickIKTemplate_Dialog::InvalidateLists();

}

void Tools_Animation_IK_Template_System::DeleteTemplate()
{
	if(!IsAnyTemplateSelected()) return;
	const char* szTemplateName = s_pszTemplateNameList[s_iSelectedTemplateIndex];

	//confirm deletion
	if(!ShowYesNoOverlay("Delete IK template '%s'?",szTemplateName)) return;

	Asura_Animation_IK_Template* pxTemplate = Asura_Animation_IK_Template_System::GetTemplateByHash(Asura_GetHashID(szTemplateName));
	if(!pxTemplate)
	{
		Asura_Assert(0,"Failed to find template being deleted");
		return;
	}
	delete pxTemplate;
	SetModified();

	if(s_pxEditedTemplate == pxTemplate)
	{
		CloseEditTemplateOverlay();
		s_pxEditedTemplate = 0;
	}

	//Update the overlay
	UpdateTemplateNameList();

	Tools_PickIKTemplate_Dialog::InvalidateLists();
}

void Tools_Animation_IK_Template_System::EditTemplate()
{
	if(!IsAnyTemplateSelected()) return;
	const char* szTemplateName = s_pszTemplateNameList[s_iSelectedTemplateIndex];

	Asura_Animation_IK_Template* pxTemplate = Asura_Animation_IK_Template_System::GetTemplateByHash(Asura_GetHashID(szTemplateName));
	if(!pxTemplate)
	{
		Asura_Assert(0,"Failed to find template being edited");
		return;
	}

	CreateEditTemplateOverlay(pxTemplate);
}

void Tools_Animation_IK_Template_System::RenameTemplate()
{
	if(!IsAnyTemplateSelected()) return;
	const char* szTemplateName = s_pszTemplateNameList[s_iSelectedTemplateIndex];
	Asura_Hash_ID uOldHash = Asura_GetHashID(szTemplateName);

	Asura_Animation_IK_Template* pxTemplate = Asura_Animation_IK_Template_System::GetTemplateByHash(uOldHash);
	if(!pxTemplate)
	{
		Asura_Assert(0,"Failed to find template being renamed");
		return;
	}

	
	//ask for the new name
	GetString_Overlay xOverlay("Rename Template", szTemplateName);
	if(!xOverlay.DoModal()) return;

	const char* szNewName = xOverlay.GetString();
	if(!szNewName || !*szNewName) return;

	//attempt to do the renaming
	if(!Asura_Animation_IK_Template_System::RenameTemplate(szTemplateName, szNewName))
	{
		ShowMessageOverlay("Another template already exists with that name/ID");
		return;
	}
	SetModified(true);

	//update the overlay, and change the current selection
	UpdateTemplateNameList();
	for(u_int i=0; i<s_uTemplateNameListSize; i++)
	{
		if(!stricmp(szNewName, s_pszTemplateNameList[i]))
		{
			s_iSelectedTemplateIndex = i;
		}
	}

	RefreshEditTemplateOverlay();

	Tools_PickIKTemplate_Dialog::InvalidateLists();
}

void Tools_Animation_IK_Template_System::EditTemplate(Asura_Hash_ID uTemplateID, Asura_Hash_ID uChainID)
{
	if(!uTemplateID) return;
	Asura_Animation_IK_Template* pxTemplate = Asura_Animation_IK_Template_System::GetTemplateByHash(uTemplateID);
	if(pxTemplate)
	{
		//set the current chain index
		if(uChainID)
		{
			for(u_int i=0; i<pxTemplate->m_uNumIKChains; i++)
			{
				Asura_Animation_IK_Template_Chain* pxChain = pxTemplate->m_ppxIKChains[i];
				if(uChainID == pxChain->m_xName.GetHash())
				{
					s_iSelectedChainIndex = i;
					break;
				}
			}
		}

		//show the overlay for this template
		CreateEditTemplateOverlay(pxTemplate);

	}
	else
	{
		ShowMessageOverlay("Failed to find IK template");
	}
}

void Tools_Animation_IK_Template_System::RefreshEditTemplateOverlay()
{
	if(s_pxEditedTemplate && Asura_Overlay_System::DoesOverlayExist(TOOLS_OVERLAY_IK_EDITTEMPLATE))
	{
		CreateEditTemplateOverlay(s_pxEditedTemplate);
	}
}

void Tools_Animation_IK_Template_System::CreateEditTemplateOverlay(Asura_Animation_IK_Template* pxTemplate)
{
	CloseEditTemplateOverlay();
	s_pxEditedTemplate = pxTemplate;
	s_pxEditedTemplateSkin = Tools_Animation_Files::GetSkinFromID(s_pxEditedTemplate->m_uSkinID);


	char szTitle[50];
	Asura_PrintfToBfr(szTitle,"IK - %s", sizeof(szTitle), true, pxTemplate->GetName());

	Asura_Overlay* pxOverlay = new Asura_Overlay(szTitle, false, &s_xEditOverlayPosition);
	Asura_Vector_2 xOffset(1.0f,3.0f);
	Asura_Dialog* pxDialog;

	//Skin selection
	char szText[100];
	Asura_PrintfToBfr(szText, "Skin : %s", sizeof(szText), true, s_pxEditedTemplateSkin ? s_pxEditedTemplateSkin->Name.GetString() : "*Not Found*");
	pxDialog = new Asura_Function_Dialog(szText, xOffset, SelectSkin);
	pxDialog->SetHelpText("Change the skin associated with this template");
	pxOverlay->AddDialog(pxDialog);
	xOffset.y += Asura_Dialog::DefaultSpacing();


	//list of IK chain names
	pxDialog = new Asura_Dialog("IK Chains :", xOffset);
	pxOverlay->AddDialog(pxDialog);
	xOffset.y += Asura_Dialog::SmallSpacing();

	UpdateChainNameList();
	s_bSelectedChainChanged = false;
	pxDialog = new Asura_List_Dialog(xOffset, s_pszChainNameList, &s_iSelectedChainIndex, s_uChainNameListSize, s_uChainNameListSize, NULL, &s_bSelectedChainChanged);
	pxDialog->SetHelpText("List of IK chains");
	pxOverlay->AddDialog(pxDialog);
	xOffset.y = pxDialog->m_xMaxCoord.y + 1.0f;

	pxDialog = new Asura_Function_Dialog("Create", xOffset, CreateChain);
	pxDialog->SetHelpText("Create a new IK chain");
	pxOverlay->AddDialog(pxDialog);

	xOffset.x += 10.0f;
	pxDialog = new Asura_Function_Dialog("Delete", xOffset, DeleteChain);
	pxDialog->SetHelpText("Delete the selected chain");
	pxDialog->SetIsEnabledFunction(IsAnyChainSelected);
	pxOverlay->AddDialog(pxDialog);
	xOffset.x -= 10.0f;
	xOffset.y += Asura_Dialog::DefaultSpacing();

	pxDialog = new Asura_Function_Dialog("Rename", xOffset, RenameChain);
	pxDialog->SetHelpText("Rename the selected IK chain");
	pxDialog->SetIsEnabledFunction(IsAnyChainSelected);
	pxOverlay->AddDialog(pxDialog);
	xOffset.y += Asura_Dialog::LargeSpacing();


	if(s_pxEditedTemplate->m_uNumIKChains)
	{
		pxDialog = new Asura_Divider_Dialog(xOffset);
		pxOverlay->AddDialog(pxDialog);
		xOffset.y += Asura_Dialog::SmallSpacing();

		//Add controls for the selected chain
		Asura_Animation_IK_Template_Chain* pxChain = s_pxEditedTemplate->m_ppxIKChains[s_iSelectedChainIndex];
		switch(pxChain->GetType())
		{
			case ASURA_IKCHAIN_LOS :
				AddOverlayControls_Chain_LOS(pxOverlay,xOffset,pxChain);
				break;

			case ASURA_IKCHAIN_MARKERPOINT :
				AddOverlayControls_Chain_MarkerPoint(pxOverlay,xOffset,pxChain);
				break;
			default :
				Asura_Assert(0,"Unhandled IK chain type");
				break;
		}
	}

	pxOverlay->m_uID = TOOLS_OVERLAY_IK_EDITTEMPLATE;
	Asura_Overlay_System::SubmitNewOverlay(pxOverlay);
}

void Tools_Animation_IK_Template_System::CloseEditTemplateOverlay()
{
	Asura_Overlay_System::CloseOverlays(TOOLS_OVERLAY_IK_EDITTEMPLATE);
}

void Tools_Animation_IK_Template_System::AddOverlayControls_Chain(Asura_Overlay* pxOverlay, Asura_Vector_2& xOffset, Asura_Animation_IK_Template_Chain* pxChain)
{
	Asura_Dialog* pxDialog;
	
	char szText[100];
	Asura_PrintfToBfr(szText,"Type : %s", sizeof(szText), true, GetChainTypeName(pxChain->GetType()));
	pxDialog = new Asura_Dialog(szText, xOffset);
	pxOverlay->AddDialog(pxDialog);
	xOffset.y += Asura_Dialog::LargeSpacing();

	pxDialog = new Asura_FlagTickbox_Dialog("Move Root Bone", xOffset, &pxChain->m_uFlags, ASURA_IKTEMPLATEFLAG_ROOTBONEMOVER, &s_bTemplateChanged);
	pxDialog->SetHelpText("If set then the root bone of the hierarchy may be moved if the IK can't reach the desired spot by fullying extending the bones.");
	pxOverlay->AddDialog(pxDialog);
	xOffset.y += Asura_Dialog::DefaultSpacing();

	pxDialog = new Asura_FlagTickbox_Dialog("Pos Smoothing", xOffset, &pxChain->m_uFlags, ASURA_IKTEMPLATEFLAG_USEPOSSMOOTHING, &s_bTemplateChanged);
	pxDialog->SetHelpText("If set this smooths any IK adjustments by limiting the speed at which the end of the chain can move");
	pxOverlay->AddDialog(pxDialog);

	xOffset.x += 13.0f;
	pxDialog = new Asura_Numerical_Dialog("Speed", xOffset, &pxChain->m_fPosSmoothingSpeed, 0.0f, 999.0f, 1.0f, "%.3f", &s_bTemplateChanged);
	pxDialog->SetIsEnabledFunction(IsEnabled_PosSmoothing);
	pxDialog->SetHelpText("The speed at which the bones can be adjusted when using smoothing");
	pxOverlay->AddDialog(pxDialog);
	xOffset.x -= 13.0f;
	xOffset.y += Asura_Dialog::DefaultSpacing();

	pxDialog = new Asura_FlagTickbox_Dialog("Ori Smoothing", xOffset, &pxChain->m_uFlags, ASURA_IKTEMPLATEFLAG_USEORISMOOTHING, &s_bTemplateChanged);
	pxDialog->SetHelpText("If set this smooths any IK adjustments by limiting the speed at which the end of the chain can move");
	pxOverlay->AddDialog(pxDialog);

	xOffset.x += 13.0f;
	pxDialog = new Asura_AngleNumerical_Dialog("Speed", xOffset, &pxChain->m_fOriSmoothingSpeed, 0.0f, 999.0f, 1.0f, "%.3f", &s_bTemplateChanged);
	pxDialog->SetIsEnabledFunction(IsEnabled_OriSmoothing);
	pxDialog->SetHelpText("The speed at which the bones can be adjusted when using smoothing (probably in degrees per second)");
	pxOverlay->AddDialog(pxDialog);
	xOffset.x -= 13.0f;
	xOffset.y += Asura_Dialog::LargeSpacing();


	//list of bone names

	pxDialog = new Asura_Dialog("Bones :", xOffset);
	pxOverlay->AddDialog(pxDialog);
	xOffset.y += Asura_Dialog::SmallSpacing();

	s_pxBoneListDialog = 0;
	UpdateBoneNameList(pxChain);
	s_bSelectedBoneChanged = false;
	pxDialog = s_pxBoneListDialog = new Asura_List_Dialog(xOffset, s_pszBoneNameList, &s_iSelectedBoneIndex, 6, s_uBoneNameListSize, NULL, &s_bSelectedBoneChanged);
	pxDialog->SetHelpText("Bones in the selected chain.\nThose markerd (*) have an associated twist bone.");
	pxOverlay->AddDialog(pxDialog);
	xOffset.y = pxDialog->m_xMaxCoord.y + 1.0f;

	pxDialog = new Asura_Function_Dialog("Change Bones", xOffset, SelectFirstAndLastBone);
	pxDialog->SetHelpText("Allows the chain to be redefined by selecting the first and last bone again");
	pxOverlay->AddDialog(pxDialog);
	xOffset.y += Asura_Dialog::DefaultSpacing();


	//twist bone selection
	if(pxChain->m_uBoneArraySize > 1)
	{
		const char* szTwistBoneName = pxChain->m_pxTwistBoneNames ? pxChain->m_pxTwistBoneNames[s_iSelectedBoneIndex].GetString() : 0;
		if(!szTwistBoneName) szTwistBoneName = "*None*";
		Asura_PrintfToBfr(szText,"Twist bone : %s", sizeof(szText), true, szTwistBoneName);

		pxDialog = new Asura_Function_Dialog(szText, xOffset, SetTwistBone);
		pxDialog->SetHelpText("Sets the twist bone associated with the selected bone.\nIf set then the twist bone's orientation will be changed to match the associated bone (or something along those lines).");
		pxDialog->m_iControlID = Asura_GetHashID("Twist Bone");
		pxOverlay->AddDialog(pxDialog);
		xOffset.y += Asura_Dialog::DefaultSpacing();
	}

	xOffset.y += Asura_Dialog::LargeSpacing() - Asura_Dialog::DefaultSpacing();
	
}

void Tools_Animation_IK_Template_System::AddOverlayControls_Chain_LOS(Asura_Overlay* pxOverlay, Asura_Vector_2& xOffset, Asura_Animation_IK_Template_Chain* pxChain)
{
	AddOverlayControls_Chain(pxOverlay, xOffset, pxChain);
}

void Tools_Animation_IK_Template_System::AddOverlayControls_Chain_MarkerPoint(Asura_Overlay* pxOverlay, Asura_Vector_2& xOffset, Asura_Animation_IK_Template_Chain* pxChain)
{
	AddOverlayControls_Chain(pxOverlay, xOffset, pxChain);

	Asura_Animation_IK_Template_Chain_MarkerPoint* pxMarkerChain = (Asura_Animation_IK_Template_Chain_MarkerPoint*) pxChain;

	Asura_Dialog* pxDialog;

	pxDialog = new Asura_Function_Dialog("Marker", xOffset, SetMarkerPoint);
	pxDialog->SetHelpText("Select the marker point to connect the chain to");
	pxDialog->m_iControlID = Asura_GetHashID("Marker");
	pxOverlay->AddDialog(pxDialog);
	xOffset.y += Asura_Dialog::DefaultSpacing();
	UpdateMarkerPointControl(pxDialog);



	pxDialog = new Asura_FlagTickbox_Dialog("Use Marker Orient", xOffset, &pxMarkerChain->m_uMarkerPointFlags, ASURA_IKTEMPLATEFLAG_MARKERPOINT_USEORI, &s_bTemplateChanged);
	pxDialog->SetHelpText("Determines whether the chain should align to the orientation of the marker point.");
	pxOverlay->AddDialog(pxDialog);
	xOffset.y += Asura_Dialog::DefaultSpacing();

}


void Tools_Animation_IK_Template_System::UpdateChainNameList()
{
	DeleteChainNameList();

	if(s_pxEditedTemplate->m_uNumIKChains)
	{
		s_uChainNameListSize = s_pxEditedTemplate->m_uNumIKChains;
		s_pszChainNameList = new const char*[s_uChainNameListSize];

		for(u_int i=0; i<s_uChainNameListSize; i++)
		{
			s_pszChainNameList[i] = s_pxEditedTemplate->m_ppxIKChains[i]->m_xName.GetString();
		}
	}
	else
	{
		s_uChainNameListSize = 1;
		s_pszChainNameList = new const char*[1];
		s_pszChainNameList[0] = "*None*";
	}

	if(s_iSelectedChainIndex >= (int)s_uChainNameListSize-1)
	{
		s_iSelectedChainIndex = s_uChainNameListSize-1;
	}
}

void Tools_Animation_IK_Template_System::DeleteChainNameList()
{
	delete [] s_pszChainNameList;
	s_pszChainNameList = 0;
	s_uChainNameListSize = 0;
}

void Tools_Animation_IK_Template_System::UpdateBoneNameList(Asura_Animation_IK_Template_Chain* pxChain)
{
	DeleteBoneNameList();

	if(pxChain->m_uBoneArraySize > 1)
	{
		s_uBoneNameListSize = pxChain->m_uBoneArraySize-1;
		s_pszBoneNameList = new char*[s_uBoneNameListSize];

		for(u_int i=0; i<s_uBoneNameListSize; i++)
		{
			char szText[100];
			int iBoneIndex = pxChain->m_piBoneArray[i];
			bool bHasTwistBone = pxChain->m_pxTwistBoneNames && !pxChain->m_pxTwistBoneNames[i].IsBlank();

			if(s_pxEditedTemplateSkin && iBoneIndex>=0 && iBoneIndex<s_pxEditedTemplateSkin->NumberOfBones)
			{
				const char* szBoneName = s_pxEditedTemplateSkin->SectionData[iBoneIndex].SectionName.GetString();
				Asura_PrintfToBfr(szText, "%s%s", sizeof(szText), true, szBoneName, bHasTwistBone ? " (*)" : "");
			}
			else
			{
				//If the bone isn't valid or the skin is missing , then just show the index instead
				Asura_PrintfToBfr(szText, "Bone %d%s", sizeof(szText), true, iBoneIndex, bHasTwistBone ? " (*)" : "");
			}

			s_pszBoneNameList[i] = new char[strlen(szText)+1];
			strcpy(s_pszBoneNameList[i], szText);
		}

	}
	else
	{
		s_uBoneNameListSize = 1;
		s_pszBoneNameList = new char*[1];
		s_pszBoneNameList[0] = new char[7];
		strcpy(s_pszBoneNameList[0], "*None*");
	}

	if(s_iSelectedBoneIndex >= (int)s_uBoneNameListSize-1)
	{
		s_iSelectedBoneIndex = s_uBoneNameListSize-1;
	}

	if(s_pxBoneListDialog)
	{
		s_pxBoneListDialog->ReplaceStrings(s_uBoneNameListSize, s_pszBoneNameList);
	}
}

void Tools_Animation_IK_Template_System::DeleteBoneNameList()
{
	for(u_int i=0; i<s_uBoneNameListSize; i++)
	{
		delete [] s_pszBoneNameList[i];
	}
	delete [] s_pszBoneNameList;
	s_pszBoneNameList = 0;
	s_uBoneNameListSize = 0;
}


bool Tools_Animation_IK_Template_System::IsAnyChainSelected()
{
	return s_pxEditedTemplate->m_uNumIKChains>0;
}

void Tools_Animation_IK_Template_System::CreateChain()
{
	//Make sure the skin is available
	if(!s_pxEditedTemplateSkin)
	{
		ShowMessageOverlay("Can't create any more IK chains since the skin couldn't be found.");
		return;
	}

	//Ask for the type of chain to create
	ListSelection_Overlay xChainTypeOverlay("Select chain type");
	for(int i=0; i< ASURA_IKCHAIN_NUMTYPES; i++)
	{
		xChainTypeOverlay.AddItem(GetChainTypeName(i), i, false, GetChainTypeHelpText(i));
	}
	if(!xChainTypeOverlay.DoModal()) return;

	int iChainType = xChainTypeOverlay.GetSelectionData();

	//ask for the first and last bone
	int iFirstBone = SelectBone(s_pxEditedTemplateSkin, "Select first bone in chain");
	if(iFirstBone == -1) return;

	int iLastBone = SelectBone(s_pxEditedTemplateSkin, "Select last bone in chain (a descendent of the first)", iFirstBone);
	if(iLastBone == -1) return;

	//Make sure the last bone is a descendent of the first , and determine how many bones are in the chain
	int uNumBones = 0;
	for(int iBone = iLastBone; ; iBone = s_pxEditedTemplateSkin->ParentBone[iBone])
	{
		uNumBones++;
		if(iBone == iFirstBone)
		{
			break;
		}

		if(iBone <= 0)
		{
			//failed to find the first bone as a parent
			ShowMessageOverlay("The last bone in the chain isn't a descendent of the first bone");
			return;
		}
	}

	//ask for a name for the new chain
	//default to the name of the first bone
	bool bNameOkay = false;
	GetString_Overlay xOverlay("Name for chain :", s_pxEditedTemplateSkin->SectionData[iFirstBone].SectionName.GetString());
	const char* szName = 0;

	while(!bNameOkay)
	{
		if(!xOverlay.DoModal()) return;

		//make sure the name is okay
		//ensure the hash value is unique, in case the name ends up being used
		//for something later
		szName = xOverlay.GetString();
		if(!szName || !*szName) continue;

		Asura_Hash_ID uHash = Asura_GetHashID(szName);

		bNameOkay = true;
		for(u_int i=0; i<s_pxEditedTemplate->m_uNumIKChains; i++)
		{
			if(uHash == Asura_GetHashID(s_pxEditedTemplate->m_ppxIKChains[i]->m_xName.GetString()))
			{
				ShowMessageOverlay("A chain with that name/hash ID already exists.");
				bNameOkay = false;
				break;
			}
		}
	}

	
	//create the chain and add to the template
	Asura_Animation_IK_Template_Chain* pxChain = 0;
	switch(iChainType)
	{
		case ASURA_IKCHAIN_LOS :
		{
			pxChain = new Asura_Animation_IK_Template_Chain_LOS();
			break;
		}
		case ASURA_IKCHAIN_MARKERPOINT :
		{
			const char* szMarkerName = SelectMarkerPoint(s_pxEditedTemplateSkin, "Attach chain to which marker point?");
			if(!szMarkerName) return;
			
			Asura_Animation_IK_Template_Chain_MarkerPoint* pxMarkerChain = new Asura_Animation_IK_Template_Chain_MarkerPoint();
			pxChain = pxMarkerChain;

			pxMarkerChain->SetMarkerPoint(szMarkerName);
			break;
		}
		default :
			Asura_Assert(0, "Unhandled IK chain type");
			return;
	}
	pxChain->m_xName = szName;

	//allocate and fill in the bone array
	pxChain->m_uBoneArraySize = uNumBones+1;
	pxChain->m_piBoneArray = new int[pxChain->m_uBoneArraySize];
	pxChain->m_pxBoneNames = new Asura_String[uNumBones];
	pxChain->m_piBoneArray[uNumBones] = -1;

	u_int uBoneIndex = uNumBones-1;
	for(int iBone = iLastBone; ; iBone = s_pxEditedTemplateSkin->ParentBone[iBone])
	{
		pxChain->m_piBoneArray[uBoneIndex] = iBone;
		pxChain->m_pxBoneNames[uBoneIndex] = s_pxEditedTemplateSkin->SectionData[iBone].SectionName;

		if(iBone == iFirstBone) break;
		uBoneIndex--;
	}




	u_int uIndex = s_pxEditedTemplate->m_uNumIKChains;
	u_int uNewNumIKChains = s_pxEditedTemplate->m_uNumIKChains+1;
	Asura_Animation_IK_Template_Chain** ppxNewIKChains = new Asura_Animation_IK_Template_Chain*[uNewNumIKChains];

	for(u_int i=0; i<s_pxEditedTemplate->m_uNumIKChains; i++)
	{
		ppxNewIKChains[i] = s_pxEditedTemplate->m_ppxIKChains[i];
	}
	ppxNewIKChains[uIndex] = pxChain;

	delete [] s_pxEditedTemplate->m_ppxIKChains;
	s_pxEditedTemplate->m_ppxIKChains = ppxNewIKChains;
	s_pxEditedTemplate->m_uNumIKChains = uNewNumIKChains;

	SetModified();

	//select the new chain and update the overlay
	s_iSelectedChainIndex = uIndex;
	CreateEditTemplateOverlay(s_pxEditedTemplate);
	

}

void Tools_Animation_IK_Template_System::DeleteChain()
{
	if(!IsAnyChainSelected()) return;

	Asura_Animation_IK_Template_Chain* pxChain = s_pxEditedTemplate->m_ppxIKChains[s_iSelectedChainIndex];
	if(!ShowYesNoOverlay("Delete IK Chain '%s'?",pxChain->m_xName.GetString())) return;

	delete pxChain;
	//update the chain array in the template
	s_pxEditedTemplate->m_uNumIKChains--;
	for(u_int i=s_iSelectedChainIndex; i< s_pxEditedTemplate->m_uNumIKChains; i++)
	{
		s_pxEditedTemplate->m_ppxIKChains[i] = s_pxEditedTemplate->m_ppxIKChains[i+1];
	}

	SetModified();

	//update the overlay
	CreateEditTemplateOverlay(s_pxEditedTemplate);
}



const char* Tools_Animation_IK_Template_System::GetChainTypeName(int iType)
{
	if(iType>=0 && iType<ASURA_IKCHAIN_NUMTYPES && g_aszIKChainType[iType])
	{
		return g_aszIKChainType[iType];
	}
	return "*Unknown*";
}

const char* Tools_Animation_IK_Template_System::GetChainTypeHelpText(int iType)
{
	if(iType>=0 && iType<ASURA_IKCHAIN_NUMTYPES && g_aszIKChainType[iType])
	{
		return g_aszIKChainHelpText[iType];
	}
	return 0;
}



void Tools_Animation_IK_Template_System::SetTwistBone()
{
	if(!s_pxEditedTemplateSkin)
	{
		ShowMessageOverlay("Can't set the twist bone since the skin is unavailable");
		return;	
	}
	
	//make sure the current selection is valid
	if(s_iSelectedChainIndex >= (int) s_pxEditedTemplate->m_uNumIKChains) return;
	Asura_Animation_IK_Template_Chain* pxChain = s_pxEditedTemplate->m_ppxIKChains[s_iSelectedChainIndex];

	if(s_iSelectedBoneIndex >= (int) pxChain->m_uBoneArraySize-1) return;
	int iTwistBoneIndex = pxChain->m_piTwistBoneArray ? pxChain->m_piTwistBoneArray[s_iSelectedBoneIndex] : -1;

	//ask for the new bone
	int iSelectedBone = SelectBone(s_pxEditedTemplateSkin, "Select twist bone for the current bone", iTwistBoneIndex, true);
	if(iSelectedBone == iTwistBoneIndex) return;

	const char* szBoneName = (iSelectedBone == -1) ? 0 : s_pxEditedTemplateSkin->SectionData[iSelectedBone].SectionName.GetString();

	pxChain->SetTwistBone(s_iSelectedBoneIndex, iSelectedBone, szBoneName);

	SetModified();

	//update the overlay
	UpdateBoneNameList(pxChain);
	UpdateTwistBoneControl();
}

void Tools_Animation_IK_Template_System::UpdateTwistBoneControl()
{
	if(s_iSelectedChainIndex >= (int) s_pxEditedTemplate->m_uNumIKChains) return;
	Asura_Animation_IK_Template_Chain* pxChain = s_pxEditedTemplate->m_ppxIKChains[s_iSelectedChainIndex];

	if(s_iSelectedBoneIndex >= (int) pxChain->m_uBoneArraySize-1) return;
	
	Asura_Overlay* pxOverlay = Asura_Overlay_System::GetOverlay(TOOLS_OVERLAY_IK_EDITTEMPLATE);
	if(pxOverlay)
	{
		char szText[100];
		const char* szTwistBoneName = pxChain->m_pxTwistBoneNames ? pxChain->m_pxTwistBoneNames[s_iSelectedBoneIndex].GetString() : 0;
		if(!szTwistBoneName) szTwistBoneName = "*None*";
		Asura_PrintfToBfr(szText,"Twist bone : %s", sizeof(szText), true, szTwistBoneName);

		pxOverlay->UpdateDialogLabel(Asura_GetHashID("Twist Bone"), szText);
	}
}

void Tools_Animation_IK_Template_System::SelectFirstAndLastBone()
{
	if(!s_pxEditedTemplateSkin)
	{
		ShowMessageOverlay("Can't select the bones since the skin is unavailable");
		return;	
	}
	
	//make sure the current selection is valid
	if(s_iSelectedChainIndex >= (int) s_pxEditedTemplate->m_uNumIKChains) return;
	Asura_Animation_IK_Template_Chain* pxChain = s_pxEditedTemplate->m_ppxIKChains[s_iSelectedChainIndex];

	int iCurrentFirstBone = pxChain->m_piBoneArray[0];
	int iCurrentLastBone = pxChain->m_piBoneArray[pxChain->m_uBoneArraySize-2];

	int iFirstBone = SelectBone(s_pxEditedTemplateSkin, "Select first bone in chain", iCurrentFirstBone);
	if(iFirstBone == -1) return;

	int iLastBone = SelectBone(s_pxEditedTemplateSkin, "Select last bone in chain (a descendent of the first)", iFirstBone);
	if(iLastBone == -1) return;

	if(iFirstBone == iCurrentFirstBone && iLastBone == iCurrentLastBone) return;

	//Make sure the last bone is a descendent of the first , and determine how many bones are in the chain
	int uNumBones = 0;
	for(int iBone = iLastBone; ; iBone = s_pxEditedTemplateSkin->ParentBone[iBone])
	{
		uNumBones++;
		if(iBone == iFirstBone)
		{
			break;
		}

		if(iBone <= 0)
		{
			//failed to find the first bone as a parent
			ShowMessageOverlay("The last bone in the chain isn't a descendent of the first bone");
			return;
		}
	}

	//get rid of the old arrays
	delete [] pxChain->m_piBoneArray;
	pxChain->m_piBoneArray = 0;

	delete [] pxChain->m_piTwistBoneArray;
	pxChain->m_piTwistBoneArray = 0;

	delete [] pxChain->m_pxBoneNames;
	pxChain->m_pxBoneNames = 0;

	delete [] pxChain->m_pxTwistBoneNames;
	pxChain->m_pxTwistBoneNames = 0;

	pxChain->m_bHasTwistBones = false;


	//allocate and fill in the bone array
	pxChain->m_uBoneArraySize = uNumBones+1;
	pxChain->m_piBoneArray = new int[pxChain->m_uBoneArraySize];
	pxChain->m_pxBoneNames = new Asura_String[uNumBones];
	pxChain->m_piBoneArray[uNumBones] = -1;

	u_int uBoneIndex = uNumBones-1;
	for(int iBone = iLastBone; ; iBone = s_pxEditedTemplateSkin->ParentBone[iBone])
	{
		pxChain->m_piBoneArray[uBoneIndex] = iBone;
		pxChain->m_pxBoneNames[uBoneIndex] = s_pxEditedTemplateSkin->SectionData[iBone].SectionName;
		if(iBone == iFirstBone) break;
		uBoneIndex--;
	}

	SetModified();

	UpdateBoneNameList(pxChain);
	UpdateTwistBoneControl();
}

void Tools_Animation_IK_Template_System::RenameChain()
{
	//make sure the current selection is valid
	if(s_iSelectedChainIndex >= (int) s_pxEditedTemplate->m_uNumIKChains) return;
	Asura_Animation_IK_Template_Chain* pxChain = s_pxEditedTemplate->m_ppxIKChains[s_iSelectedChainIndex];
	

	//ask for the new name
	GetString_Overlay xOverlay("Rename Chain", pxChain->m_xName.GetString());
	if(!xOverlay.DoModal()) return;

	const char* szNewName = xOverlay.GetString();
	if(!szNewName || !*szNewName) return;

	//make sure that name isn'y already in use
	Asura_Hash_ID uHash = Asura_GetHashID(szNewName);

	for(u_int i=0; i<s_pxEditedTemplate->m_uNumIKChains; i++)
	{
		if((int)i == s_iSelectedChainIndex) continue;

		if(uHash == s_pxEditedTemplate->m_ppxIKChains[i]->m_xName.GetHash())
		{
			ShowMessageOverlay("A chain with that name/hash ID already exists.");
			return;
		}
	}


	//update the name
	pxChain->m_xName = szNewName;
	SetModified(true);

	//update the overlay
	RefreshEditTemplateOverlay();
	
}

void Tools_Animation_IK_Template_System::SelectSkin()
{
	if(!s_pxEditedTemplate) return;

	//ask for a new skin
	Asura_Hierarchy_Skin* pxSkin = Tools_Animation_Files::ChooseSkin(NULL, NULL);
	if(!pxSkin) return;

	//has it changed?
	Asura_Hash_ID uHash = pxSkin->GetHashID();
	if(uHash == s_pxEditedTemplate->m_uSkinID) return;

	//set this skin
	s_pxEditedTemplate->m_uSkinID = pxSkin->GetHashID();
	SetModified();

	u_int uInitialPos = Tools_MessageLog::GetNextID();

	//update any bone indeces
	UpdateTemplateFromSkin(s_pxEditedTemplate);
	//re-create the overlay to update it
	CreateEditTemplateOverlay(s_pxEditedTemplate);

	if(Tools_MessageLog::AreThereAnyMessages(Tools_MessageLog::IK, 0, uInitialPos))
	{
		//show any errors
		Tools_MessageLog::ShowOverlay(Tools_MessageLog::IK,0,uInitialPos);
	}

}

void Tools_Animation_IK_Template_System::UpdateTemplateFromSkin(Asura_Animation_IK_Template* pxTemplate)
{
	//Find the skin
	Asura_Hierarchy_Skin* pxSkin = Tools_Animation_Files::GetSkinFromID(pxTemplate->m_uSkinID);
	if(!pxSkin)
	{
		Tools_MessageLog::AddMessage(Tools_MessageLog::IK,"",new Tools_MessageLogAction_IKTemplate(pxTemplate,0),
									"Failed to find skin for template '%s'",
									pxTemplate->GetName());
		return;
	}
	bool bUpdatedTemplate = false;

	//check each chain
	for(u_int uChain = 0; uChain < pxTemplate->m_uNumIKChains; uChain++)
	{
		Asura_Animation_IK_Template_Chain* pxChain = pxTemplate->m_ppxIKChains[uChain];

		//make sure the first/last bone still exist
		int iFirstBone = GetBoneIndex(pxSkin, pxChain->GetFirstBoneName());
		if(iFirstBone == -1)
		{
			Tools_MessageLog::AddMessage(Tools_MessageLog::IK,"",new Tools_MessageLogAction_IKTemplate(pxTemplate,pxChain),
										"Failed to find first bone ('%s') in skin - Template '%s' Chain '%s'",
										pxChain->GetFirstBoneName(),pxTemplate->GetName(),pxChain->m_xName.GetString());
			continue;
		}
		int iLastBone = GetBoneIndex(pxSkin, pxChain->GetLastBoneName());
		if(iLastBone == -1)
		{
			Tools_MessageLog::AddMessage(Tools_MessageLog::IK,"",new Tools_MessageLogAction_IKTemplate(pxTemplate,pxChain),
										"Failed to find last bone ('%s') in skin - Template '%s' Chain '%s'",
										pxChain->GetLastBoneName(),pxTemplate->GetName(),pxChain->m_xName.GetString());
			continue;
		}

		//count the number of bones in the chain in the current skin , and see if its different
		u_int uNumBones = 0;
		int iIndex = (int)pxChain->GetNumBones()-1;
		bool bChainDifferent = false;
		for(int iBone = iLastBone; ; iBone = pxSkin->ParentBone[iBone])
		{
			uNumBones++;
			if(!bChainDifferent && iBone != pxChain->m_piBoneArray[iIndex])
			{
				bChainDifferent = true;
			}
			iIndex--;

			if(iBone == iFirstBone)
			{
				break;
			}

			if(iBone <= 0)
			{
				//failed to find the first bone as a parent
				Tools_MessageLog::AddMessage(Tools_MessageLog::IK,"",new Tools_MessageLogAction_IKTemplate(pxTemplate,pxChain),
											"Last bone is no longer a descendent of the first bone - Template '%s' Chain '%s'",
											pxTemplate->GetName(),pxChain->m_xName.GetString());
				continue;
			}
		}
		if(uNumBones != pxChain->GetNumBones()) bChainDifferent = true;

		if(bChainDifferent)
		{
			bUpdatedTemplate = true;

			///get rid of the old arrays, but take hold of the old name arrays
			u_int uOldNumBones = pxChain->GetNumBones();

			delete [] pxChain->m_piBoneArray;
			pxChain->m_piBoneArray = 0;

			delete [] pxChain->m_piTwistBoneArray;
			pxChain->m_piTwistBoneArray = 0;

			Asura_String* pxOldBoneNames = pxChain->m_pxBoneNames;
			pxChain->m_pxBoneNames = 0;

			Asura_String* pxOldTwistBoneNames = pxChain->m_pxTwistBoneNames;
			pxChain->m_pxTwistBoneNames = 0;

			pxChain->m_bHasTwistBones = false;


			//allocate and fill in the bone array with the new bones
			pxChain->m_uBoneArraySize = uNumBones+1;
			pxChain->m_piBoneArray = new int[pxChain->m_uBoneArraySize];
			pxChain->m_pxBoneNames = new Asura_String[uNumBones];
			pxChain->m_piBoneArray[uNumBones] = -1;

			u_int uBoneIndex = uNumBones-1;
			for(int iBone = iLastBone; ; iBone = pxSkin->ParentBone[iBone])
			{
				pxChain->m_piBoneArray[uBoneIndex] = iBone;
				pxChain->m_pxBoneNames[uBoneIndex] = pxSkin->SectionData[iBone].SectionName;

				if(iBone == iFirstBone) break;
				uBoneIndex--;
			}

			if(pxOldTwistBoneNames)
			{
				//check each of the old twist bones to see if the corresponding bone exists in the updated chain
				for(u_int i=0; i<uOldNumBones; i++)
				{
					if(pxOldTwistBoneNames[i].IsBlank()) continue;

					const char* szBoneName = pxOldBoneNames[i].GetString();
					const char* szTwistBoneName = pxOldTwistBoneNames[i].GetString();

					//search for the non-twist bone in the new array
					int iNewIndex = -1;
					for(u_int j=0; j<pxChain->GetNumBones(); j++)
					{
						if(!pxChain->m_pxBoneNames[j].Compare(szBoneName))
						{
							iNewIndex = j;
							break;
						}
					}

					if(iNewIndex == -1)
					{
						//corresponding bone no longer exists
						Tools_MessageLog::AddMessage(Tools_MessageLog::IK,"",new Tools_MessageLogAction_IKTemplate(pxTemplate,pxChain),
													"Removed twist bone '%s' since corresponding bone '%s' no longer exists - Template '%s' Chain '%s'",
													szTwistBoneName,pxTemplate->GetName(),pxChain->m_xName.GetString());

					}
					else
					{
						//get the new index for the twist bone
						int iTwistBoneIndex = GetBoneIndex(pxSkin, szTwistBoneName);
						if(iTwistBoneIndex == -1)
						{
							Tools_MessageLog::AddMessage(Tools_MessageLog::IK,"",new Tools_MessageLogAction_IKTemplate(pxTemplate,pxChain),
														"Removed twist bone '%s' since it no longer exists in skin - Template '%s' Chain '%s'",
														szTwistBoneName,pxTemplate->GetName(),pxChain->m_xName.GetString());
						}
						else
						{
							pxChain->SetTwistBone(iNewIndex, iTwistBoneIndex, szTwistBoneName);
						}
					}
				}
			}
			delete [] pxOldBoneNames;
			delete [] pxOldTwistBoneNames;
		}
		else if(pxChain->m_pxTwistBoneNames)
		{
			//main bone array is the same ,but check the twist bones
			for(u_int i=0; i<pxChain->GetNumBones(); i++)
			{
				if(pxChain->m_pxTwistBoneNames[i].IsBlank()) continue;
				const char* szTwistBoneName = pxChain->m_pxTwistBoneNames[i].GetString();
				
				int iBoneIndex = GetBoneIndex(pxSkin, szTwistBoneName);

				if(iBoneIndex == -1)
				{
					//bone no longer exists
					Tools_MessageLog::AddMessage(Tools_MessageLog::IK,"",new Tools_MessageLogAction_IKTemplate(pxTemplate,pxChain),
												"Removed twist bone '%s' since it no longer exists in skin - Template '%s' Chain '%s'",
												szTwistBoneName,pxTemplate->GetName(),pxChain->m_xName.GetString());

					pxChain->SetTwistBone(i, -1, 0);
					bUpdatedTemplate = true;

				}
				else if(iBoneIndex != pxChain->m_piTwistBoneArray[i])
				{
					//bone index has changed
					Asura_String xNameCopy = szTwistBoneName;
					pxChain->SetTwistBone(i, iBoneIndex, xNameCopy.GetString());
					bUpdatedTemplate = true;

				}
			}
		}
	}

	if(bUpdatedTemplate)
	{
		Tools_MessageLog::AddMessage(Tools_MessageLog::IK,"",new Tools_MessageLogAction_IKTemplate(pxTemplate,0),
									"Updated IK template '%s' to match its associated skin",
									pxTemplate->GetName());
		SetModified();
	}
}

void Tools_Animation_IK_Template_System::Verify()
{
	Tools_MessageLog::DeleteMessages(Tools_MessageLog::IK,0);
	u_int uInitialPos = Tools_MessageLog::GetNextID();

	for(u_int i=0; i<Asura_Animation_IK_Template_System::GetNumberOfTemplates(); i++)
	{
		Asura_Animation_IK_Template* pxTemplate = Asura_Animation_IK_Template_System::GetTemplateByIndex(i);

		UpdateTemplateFromSkin(pxTemplate);
	}

	if(Tools_MessageLog::AreThereAnyMessages(Tools_MessageLog::IK, 0, uInitialPos))
	{
		//show any errors
		Tools_MessageLog::ShowOverlay(Tools_MessageLog::IK,0,uInitialPos);
	}
	else
	{
		ShowMessageOverlay("Finished checking templates - no changes required.");
	}

	//update the edite template overlay to be safe
	if(s_pxEditedTemplate && Asura_Overlay_System::DoesOverlayExist(TOOLS_OVERLAY_IK_EDITTEMPLATE))
	{
		CreateEditTemplateOverlay(s_pxEditedTemplate);
	}


}

int Tools_Animation_IK_Template_System::GetBoneIndex(const Asura_Hierarchy_Skin* pxSkin, const char* szName)
{
	if(!szName || ! *szName) return -1;

	for(int i=0; i<pxSkin->NumberOfBones; i++)
	{
		if(!pxSkin->SectionData[i].SectionName.Compare(szName))
		{
			return i;
		}
	}
	return -1;
}

bool Tools_Animation_IK_Template_System::IsEnabled_PosSmoothing()
{
	//determine the chain being edited
	if(!s_pxEditedTemplateSkin) return false;
	if(s_iSelectedChainIndex >= (int) s_pxEditedTemplate->m_uNumIKChains) return false;
	Asura_Animation_IK_Template_Chain* pxChain = s_pxEditedTemplate->m_ppxIKChains[s_iSelectedChainIndex];

	return pxChain->m_bUsePosSmoothing;
}

bool Tools_Animation_IK_Template_System::IsEnabled_OriSmoothing()
{
	//determine the chain being edited
	if(!s_pxEditedTemplateSkin) return false;
	if(s_iSelectedChainIndex >= (int) s_pxEditedTemplate->m_uNumIKChains) return false;
	Asura_Animation_IK_Template_Chain* pxChain = s_pxEditedTemplate->m_ppxIKChains[s_iSelectedChainIndex];

	return pxChain->m_bUseOriSmoothing;
}

void Tools_Animation_IK_Template_System::SetMarkerPoint()
{
	//make sure a marker point chain is selected
	if(s_iSelectedChainIndex >= (int) s_pxEditedTemplate->m_uNumIKChains) return;
	Asura_Animation_IK_Template_Chain* pxChain = s_pxEditedTemplate->m_ppxIKChains[s_iSelectedChainIndex];

	if(pxChain->GetType() != ASURA_IKCHAIN_MARKERPOINT) return;
	Asura_Animation_IK_Template_Chain_MarkerPoint* pxMarkerChain = (Asura_Animation_IK_Template_Chain_MarkerPoint*) pxChain;

	//need to have the skin available
	if(!s_pxEditedTemplateSkin)
	{
		ShowMessageOverlay("Can't set the marker point since the skin is unavailable");
		return;	
	}

	//ask for a new marker point
	const char* szName = SelectMarkerPoint(s_pxEditedTemplateSkin, "Attach chain to which marker point?", pxMarkerChain->m_xMarkerPointName.GetString());
	if(!szName) return;

	if(stricmp(szName, pxMarkerChain->m_xMarkerPointName.GetString()))
	{
		pxMarkerChain->SetMarkerPoint(szName);
		SetModified();

		UpdateMarkerPointControl();
	}
}

void Tools_Animation_IK_Template_System::UpdateMarkerPointControl(Asura_Dialog* pxDialog /*=0*/)
{
	//make sure a marker point chain is selected
	if(s_iSelectedChainIndex >= (int) s_pxEditedTemplate->m_uNumIKChains) return;
	Asura_Animation_IK_Template_Chain* pxChain = s_pxEditedTemplate->m_ppxIKChains[s_iSelectedChainIndex];

	if(pxChain->GetType() != ASURA_IKCHAIN_MARKERPOINT) return;
	Asura_Animation_IK_Template_Chain_MarkerPoint* pxMarkerChain = (Asura_Animation_IK_Template_Chain_MarkerPoint*) pxChain;


	//determine the text for the control
	char szText[100];
	Asura_PrintfToBfr(szText, "Marker : %s", sizeof(szText), true, pxMarkerChain->m_xMarkerPointName.GetString());

	if(pxDialog)
	{
		//update the control we've been passed
		pxDialog->UpdateLabel(szText);
	}
	else
	{
		//find the overlay and update that
		Asura_Overlay* pxOverlay = Asura_Overlay_System::GetOverlay(TOOLS_OVERLAY_IK_EDITTEMPLATE);
		if(pxOverlay)
		{
			pxOverlay->UpdateDialogLabel(Asura_GetHashID("Marker"), szText);
		}

	}
}



int Tools_Animation_IK_Template_System::SelectBone(const Asura_Hierarchy_Skin* pxSkin, const char* szTitle, int iInitialSelection /*=-1*/, bool bAllowNone /*= false*/)
{
	Asura_Assert(pxSkin, "");
	Asura_Assert(pxSkin->SectionData, "");
	Asura_Assert(pxSkin->ParentBone, "");

	Asura_TreeDialog_Item** ppxTreeItem = new Asura_TreeDialog_Item*[pxSkin->NumberOfBones];
	//build a tree of the bones
	Asura_TreeDialog_Tree xTree;
	if(bAllowNone)
	{
		Asura_TreeDialog_Item* pxItem = xTree.AddItem("*None*", -1);
		if(iInitialSelection == -1) xTree.SetSelectedItem(pxItem);
	}
	
	for(int i=0; i<pxSkin->NumberOfBones; i++)
	{
		const char* szName = pxSkin->SectionData[i].SectionName.GetString();
		if(i==0)
		{
			ppxTreeItem[i] = xTree.AddItem(szName, i);
			ppxTreeItem[i]->Expand(true);
		}
		else
		{
			int iParentBone = pxSkin->ParentBone[i];
			Asura_Assert(iParentBone>=0 && iParentBone <i, "Unexpected parent bone index");

			ppxTreeItem[i] = xTree.AddItem(ppxTreeItem[iParentBone], szName, i);
			ppxTreeItem[i]->Expand(true);
		}
	}
	if(iInitialSelection>=0 && iInitialSelection<pxSkin->NumberOfBones)
	{
		xTree.SetSelectedItem(ppxTreeItem[iInitialSelection]);
	}

	delete [] ppxTreeItem;

	TreeSelect_Overlay xOverlay(szTitle, &xTree);
	if(!xOverlay.DoModal()) return -1;

	return xOverlay.GetSelectionData();
}

const char* Tools_Animation_IK_Template_System::SelectMarkerPoint(const Asura_Hierarchy_Skin* pxSkin, const char* szTitle, const char* szInitialSelection /*= 0*/)
{
	Asura_Assert(pxSkin, "");
	if(!pxSkin->NumberOfMarkerPoints)
	{
		ShowMessageOverlay("The skin doesn't have any marker points");
		return szInitialSelection;
	}

	ListSelection_Overlay xOverlay(szTitle);
	for(int i=0;i<pxSkin->NumberOfMarkerPoints;i++)
	{
		const char* szName = pxSkin->MarkerPoint[i].GetName().GetString();
		bool bInitialSelection = szInitialSelection && !stricmp(szName, szInitialSelection);
		xOverlay.AddItem(szName, i, bInitialSelection);
	}

	if(!xOverlay.DoModal()) return szInitialSelection;
	
	int iIndex = xOverlay.GetSelectionData();
	return pxSkin->MarkerPoint[iIndex].GetName().GetString();
}


void Tools_Animation_IK_Template_System::SetModified(bool bWarnAlways /*=false*/)
{
	if(!s_bModified || bWarnAlways)
	{
		Tools_SaveAsura::WarnIfReadOnly(GetIKTemplateFilename());
		s_bModified = true;
	}

	if(Tools_System::GetCurrentMode() == TOOLS_MODE_CUTSCENE)
	{
		//Force any cutscene playback to be updated so that any changes become immediately visible
		Tools_Mode_Cutscene::RefreshPlayback();
	}
}

void Tools_Animation_IK_Template_System::SaveFile()
{
	if(Tools_Level_Export::IsExport()) return;

	//make sure the file can be saved
	const char* szFilename = GetIKTemplateFilename();
	if(!Tools_SaveAsura::CanSaveFile(szFilename,true))return;


	Asura_SaveBlock xSaveBlock;
	Asura_Chunk_SaveBlock_Stream xStream(&xSaveBlock);

	//create chunks for each template
	if(Asura_Animation_IK_Template_System::GetNumberOfTemplates())
	{
		for(Asura_TemplateHashTree_SaveIterator xIt(Asura_Animation_IK_Template_System::GetNumberOfTemplates());!xIt.Done();xIt.Next())
		{
			const Asura_Animation_IK_Template* pxTemplate = Asura_Animation_IK_Template_System::GetTemplateByIndex(xIt.GetCurrent());

			Asura_Chunk_IK_Template::Create(pxTemplate, xStream);
		}
	}
	xSaveBlock.AddAsuraTerminator();

	//write out the file
	Tools_FileSystem::EnsureDirectoryExists(szFilename);

	bool bSuccess = xSaveBlock.SaveFile(szFilename,true);
    if(bSuccess)
	{
		Tools_Timed_Message_System::AddMessage(0x00ff00,"Saved %s.",szFilename);
		Tools_LoadedFileList::UpdateModifiedTime(szFilename);
		s_bModified = false;
	}
	else
	{
		Tools_Timed_Message_System::AddMessage(0xff0000,"Failed to save %s",szFilename);
	}
}

bool Tools_Animation_IK_Template_System::AddToExport(HANDLE pSaveHandle)
{
	Asura_SaveBlock xSaveBlock;
	Asura_Chunk_SaveBlock_Stream xStream(&xSaveBlock);

	//create chunks for each template
	if(Asura_Animation_IK_Template_System::GetNumberOfTemplates())
	{
		for(Asura_TemplateHashTree_SaveIterator xIt(Asura_Animation_IK_Template_System::GetNumberOfTemplates());!xIt.Done();xIt.Next())
		{
			const Asura_Animation_IK_Template* pxTemplate = Asura_Animation_IK_Template_System::GetTemplateByIndex(xIt.GetCurrent());

			Asura_Chunk_IK_Template::Create(pxTemplate, xStream);
		}
	}

	//write the chunks to the file
    u_int uDataSize = xSaveBlock.GetSize() - ASURA_FILE_ID_NUM_CHARS;

    if(uDataSize)
	{
    	DWORD uBytesWritten;
		WriteFile( pSaveHandle, xSaveBlock.GetStartPtr() + ASURA_FILE_ID_NUM_CHARS, uDataSize, &uBytesWritten, NULL );

		if(uBytesWritten != uDataSize)
		{
    	    Tools_Level_Export::AddMessage( Tools_Level_Export::MT_ERROR, "I/O error." );
			return false;
		}
	}
	return true;

}


/////////////////////////////////////////////////////////////////////////////////////////
/*-----------------------------**
** Tools_PickIKTemplate_Dialog **
**-----------------------------*/

u_int Tools_PickIKTemplate_Dialog::GetNumberOfHashValues() const
{
	return Asura_Animation_IK_Template_System::GetNumberOfTemplates();
}

const char* Tools_PickIKTemplate_Dialog::GetNameByIndex(int iIndex) const
{
	const Asura_Animation_IK_Template* pxTemplate = Asura_Animation_IK_Template_System::GetTemplateByIndex(iIndex);
	return pxTemplate ? pxTemplate->GetName() : "*Error*";
}


#endif //ASURA_USE_ANIMATION_IK_SOLVER
