///////////////////////////////////////////////////////////////////////////////
//
// File: Tools_Container_Attachment.cpp
//
// Created by Martin G.Harris (3/7/2008)
// 
// Copyright (C) 2008 Rebellion, All Rights Reserved.
//
///////////////////////////////////////////////////////////////////////////////

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

#ifdef ASURA_USE_CONTAINER_SYSTEM

// Asura
#include "Core/Asura_StringBuffer.h"
#include "Debugging/Asura_Assert.h"
#include "Interface/Asura_Overlay.h"
#include "Interface/Asura_Overlay_System.h"

// Tools
#include "Entity/Container/Tools_Container_System.h"
#include "Entity/Container/Tools_ContainerFile.h"
#include "Entity/Container/Tools_ContainerRegistry.h"
#include "Entity/Container/Tools_Entity_Container.h"
#include "Entity/Container/Tools_Mode_Container.h"
#include "Misc/Tools_MessageLog.h"
#include "Misc/Tools_Modal_Overlay.h"
#include "Misc/Tools_Timed_Messages.h"
#include "Tools_System.h"


//
// Register the Tools container types.
//
Tools_ContainerRegisterClass<Tools_Container_Attachment> xRegisterAttachment(AsuraContainerClass_Attachment, "Attachment", false);


///////////////////////////////////////////////////////////////////////////////
// Tools_Container_Attachment
///////////////////////////////////////////////////////////////////////////////

Tools_Container_Attachment::Tools_Container_Attachment()
: m_uChildContainerHash(ASURA_HASH_ID_UNSET)
, m_bAttachToPhysicsObject(false)
, m_xAttachPosition(Asura_Maths::ZeroVector3)
, m_xAttachQuat(Asura_Maths::IdentityQuat)
, m_xAttachEuler(Asura_Maths::ZeroEuler3)
{
}

// Chunkstream code
const int iCHUNK_VERSION_TOOLS_CONTAINER_ATTACHMENT = 2;

void Tools_Container_Attachment::ReadFromChunkStream(Asura_Chunk_Stream& xStream)
{
	int iVersion = -1;
	xStream >> iVersion;

	if (iVersion < 0 || iVersion > iCHUNK_VERSION_TOOLS_CONTAINER_ATTACHMENT)
	{
		Asura_Assertf(0, ("Unknown chunk version (%d) in Tools_Container_Attachment", iVersion));
		return;
	}

	xStream >> m_uChildContainerHash;
	xStream >> m_xAttachPosition;
	xStream >> m_xAttachQuat;

	m_xAttachQuat.ToEuler(m_xAttachEuler);

	if (iVersion >= 1)
	{
		xStream >> m_xMarkerName;
	}

	if (iVersion >= 2)
	{
		xStream >> m_bAttachToPhysicsObject;
		xStream.Skip4BytePadding();
	}

	PARENT::ReadFromChunkStream(xStream);
}

void Tools_Container_Attachment::WriteToChunkStream(Asura_Chunk_Stream& xStream) const
{
	xStream << iCHUNK_VERSION_TOOLS_CONTAINER_ATTACHMENT;

	xStream << m_uChildContainerHash;
	xStream << m_xAttachPosition;
	xStream << m_xAttachQuat;

	xStream << m_xMarkerName;

	xStream << m_bAttachToPhysicsObject;
	xStream.Write4BytePadding();
	
	PARENT::WriteToChunkStream(xStream);
}

/////////////////////
// CopyDataToAsura //
/////////////////////
void Tools_Container_Attachment::CopyDataToAsura()
{
	Asura_Container_Attachment* pxContainer = static_cast<Asura_Container_Attachment*>(m_pxAsuraContainer);
	if (pxContainer)
	{
		// If Euler differs from Quat it's because someone edited it in the overlay.
		Asura_Euler_3 xTempEuler;
		m_xAttachQuat.ToEuler(xTempEuler);

		const float fEpsilon = 1.0e-5f;
		if (Asura_Maths::Abs(xTempEuler.x - m_xAttachEuler.x) > fEpsilon ||
			Asura_Maths::Abs(xTempEuler.y - m_xAttachEuler.y) > fEpsilon ||
			Asura_Maths::Abs(xTempEuler.z - m_xAttachEuler.z) > fEpsilon)
		{
			m_xAttachQuat.FromEuler(m_xAttachEuler);
		}

		pxContainer->m_uChildContainerHash	  = m_uChildContainerHash;
		pxContainer->m_uCachedChildClass	  = AsuraContainerClass_NotSet;
		pxContainer->m_uMarkerPointHash		  = (m_bAttachToPhysicsObject ? ASURA_HASH_ID_UNSET : m_xMarkerName.GetHash());
		pxContainer->m_xAttachPosition		  = m_xAttachPosition;
		pxContainer->m_xAttachQuat			  = m_xAttachQuat;
		pxContainer->m_bAttachToPhysicsObject = m_bAttachToPhysicsObject;

		pxContainer->UpdateHasOffsetFlag();
	}

	PARENT::CopyDataToAsura();
}

//////////////////
// GetNumInputs //
//////////////////
// Number of state inputs is same as for the child container.
u_int Tools_Container_Attachment::GetNumInputs() const
{
	const Tools_Container* pxChildContainer = Tools_Container_System::FindContainerByHash(m_uChildContainerHash);
	if (pxChildContainer)
	{
		return pxChildContainer->GetNumInputs();
	}
	return 1;
}

static bool IsMarkerEnabled(const bool* pbAttachToPhysics)
{
	return pbAttachToPhysics ? !*pbAttachToPhysics : true;
}

////////////////////////
// AddOverlayControls //
////////////////////////
void Tools_Container_Attachment::AddOverlayControls(Asura_Overlay& xNewOverlay, Asura_Vector_2& xOffset)
{
	if (!m_pxAsuraContainer) return;

	// Refresh the dynamic string labels.
	RefreshDialogue();

	const float fButtonX = 12.0f;
	Asura_Dialog* pxDialog = 0;

	pxDialog = new Asura_Dynamic_Dialog(&m_xChildDialogue, xOffset);
	xNewOverlay.AddDialog(pxDialog);
	xOffset.y += Asura_Dialog::DefaultSpacing();

	pxDialog = new Asura_Function_Dialog("Select Attached", xOffset, SelectChild, reinterpret_cast<int>(this));
	pxDialog->SetHelpText("Select the attached container.");
	xNewOverlay.AddDialog(pxDialog);
	xOffset.x += fButtonX;

	pxDialog = new Asura_Function_Dialog("Go to Attached", xOffset, EditChild, reinterpret_cast<int>(this));
	pxDialog->SetHelpText("Open edit overlay for the attached container.");
	xNewOverlay.AddDialog(pxDialog);
	xOffset.x -= fButtonX;
	xOffset.y += Asura_Dialog::LargeSpacing();

	Asura_Callback_UserData<bool, const bool*> xIsMarkerEnabled(IsMarkerEnabled, &m_bAttachToPhysicsObject);
	pxDialog = new Asura_Function_Dialog("Select Marker:", xOffset, SelectMarker, reinterpret_cast<int>(this));
	pxDialog->SetHelpText("Choose a marker point to attach to.");
	pxDialog->SetIsEnabledFunction(xIsMarkerEnabled);
	xNewOverlay.AddDialog(pxDialog);
	xOffset.x += fButtonX;

	pxDialog = new Asura_Dynamic_Dialog(&m_xMarkerDialogue, xOffset);
	pxDialog->SetIsEnabledFunction(xIsMarkerEnabled);
	xNewOverlay.AddDialog(pxDialog);
	xOffset.x -= fButtonX;
	xOffset.y += Asura_Dialog::LargeSpacing();

	// Position
	const float fMax = 10000.0f;
	const float fDelta = 1.0f;

	pxDialog = new Asura_StringDisplay_Dialog("Position offset:", xOffset);
	xNewOverlay.AddDialog(pxDialog);
	xOffset.x += fButtonX;

	pxDialog = new Asura_Function_Dialog("Reset", xOffset, ResetPosition, reinterpret_cast<int>(this));
	pxDialog->SetHelpText("Reset the position offset to zero.");
	xNewOverlay.AddDialog(pxDialog);
	xOffset.y += Asura_Dialog::DefaultSpacing();
	xOffset.x -= fButtonX;

	pxDialog = new Asura_Numerical_Dialog("x", xOffset, &m_xAttachPosition.x, -fMax, fMax, fDelta, "%.3f", &s_bChangedEditOverlay);
	xNewOverlay.AddDialog(pxDialog);
	xOffset.y += Asura_Dialog::DefaultSpacing();

	pxDialog = new Asura_Numerical_Dialog("y", xOffset, &m_xAttachPosition.y, -fMax, fMax, fDelta, "%.3f", &s_bChangedEditOverlay);
	xNewOverlay.AddDialog(pxDialog);
	xOffset.y += Asura_Dialog::DefaultSpacing();

	pxDialog = new Asura_Numerical_Dialog("z", xOffset, &m_xAttachPosition.z, -fMax, fMax, fDelta, "%.3f", &s_bChangedEditOverlay);
	xNewOverlay.AddDialog(pxDialog);
	xOffset.y += Asura_Dialog::LargeSpacing();


	// Euler angles
	const float fAngleMin = -360.0f;
	const float fAngleMax = 360.0f;
	const float fAngleDelta = 5.0f;

	pxDialog = new Asura_StringDisplay_Dialog("Euler angles:", xOffset);
	xNewOverlay.AddDialog(pxDialog);
	xOffset.x += fButtonX;

	pxDialog = new Asura_Function_Dialog("Reset", xOffset, ResetOrientation, reinterpret_cast<int>(this));
	pxDialog->SetHelpText("Reset the Euler angles to zero.");
	xNewOverlay.AddDialog(pxDialog);
	xOffset.y += Asura_Dialog::DefaultSpacing();
	xOffset.x -= fButtonX;

	pxDialog = new Asura_AngleNumerical_Dialog("x", xOffset, &m_xAttachEuler.x, fAngleMin, fAngleMax, fAngleDelta, "%.2f", &s_bChangedEditOverlay);
	xNewOverlay.AddDialog(pxDialog);
	xOffset.y += Asura_Dialog::DefaultSpacing();

	pxDialog = new Asura_AngleNumerical_Dialog("y", xOffset, &m_xAttachEuler.y, fAngleMin, fAngleMax, fAngleDelta, "%.2f", &s_bChangedEditOverlay);
	xNewOverlay.AddDialog(pxDialog);
	xOffset.y += Asura_Dialog::DefaultSpacing();

	pxDialog = new Asura_AngleNumerical_Dialog("z", xOffset, &m_xAttachEuler.z, fAngleMin, fAngleMax, fAngleDelta, "%.2f", &s_bChangedEditOverlay);
	xNewOverlay.AddDialog(pxDialog);
	xOffset.y += Asura_Dialog::LargeSpacing();

	// Attached to physics object.
	Asura_Tickbox_Dialog* pxTickDialog = new Asura_Tickbox_Dialog("Attach to collection's physics object", xOffset, &m_bAttachToPhysicsObject, &s_bChangedEditOverlay);
	pxTickDialog->SetHelpText("Attach to the parent collection's shared dynamic physics object (if it has one), rather than using the origin or marker position.");
	pxTickDialog->SetClickedFunction(AttachToPhysicsClicked, reinterpret_cast<int>(this));
	xNewOverlay.AddDialog(pxTickDialog);
	xOffset.y += Asura_Dialog::DefaultSpacing();
}

///////////////////////
// GetChildContainer //
///////////////////////
Tools_Container* Tools_Container_Attachment::GetChildContainer() const
{
	return Tools_Container_System::FindContainerByHash(m_uChildContainerHash);
}

///////////////////////
// FillChildIterator //
///////////////////////
// Set up the child iterator.
void Tools_Container_Attachment::FillChildIterator(Tools_Container_ChildIterator& xIterator) const
{
	xIterator.SetArraySize(1);
	xIterator.AddEntry(m_uChildContainerHash);
}

////////////
// Verify //
////////////
void Tools_Container_Attachment::Verify(bool bRecurse) const
{
	PARENT::Verify(bRecurse);
	
	if (m_uChildContainerHash == ASURA_HASH_ID_UNSET)
	{
		AddLogMessage("child container not set.");
	}
}

/////////////////
// RemoveChild //
/////////////////
// Remove specified child. Return true if succeeded.
bool Tools_Container_Attachment::RemoveChild(Asura_Hash_ID uChildHash)
{
	if (uChildHash == m_uChildContainerHash && uChildHash != ASURA_HASH_ID_UNSET)
	{
		m_uChildContainerHash = ASURA_HASH_ID_UNSET;

		CopyDataToAsura();
		RefreshDialogue();
		ResizeOverlay();
		SetModified();
		
		// Auto-delete orphans with group-scope.
		Tools_Container_System::DeleteIfOrphaned(uChildHash);
		return true;
	}
	return false;
}

/////////////////
// SelectChild //
/////////////////
void Tools_Container_Attachment::SelectChild()
{
	const Asura_Hash_ID uOldContainerHash = m_uChildContainerHash;
	const Asura_Hash_ID uGroupHash = GetGroupHash();

	// Allow those with group-scope that are in the same group.
	const bool bOk = Tools_Container_System::SelectContainerModalOverlay(m_uChildContainerHash, GetContainerHash(), true, false, uGroupHash);
	if (bOk && uOldContainerHash != m_uChildContainerHash)
	{
		// Update the Asura container.
		CopyDataToAsura();

		RefreshDialogue();
		ResizeOverlay();
		SetModified();
		
		// Auto-delete orphans with group-scope.
		Tools_Container_System::DeleteIfOrphaned(uOldContainerHash);
	}
}

///////////////
// EditChild //
///////////////
void Tools_Container_Attachment::EditChild()
{
	Tools_Container_System::SelectContainer( m_uChildContainerHash, true );
}

//////////////////
// SelectMarker //
//////////////////
void Tools_Container_Attachment::SelectMarker()
{
	const Asura_Hash_ID uOldHash = m_xMarkerName.GetHash();
	if (Tools_Container_System::SelectMarkerPointModalOverlay(m_xMarkerName, GetGroupHash()))
	{
		if (uOldHash != m_xMarkerName.GetHash())
		{
			RefreshDialogue();
			CopyDataToAsura();

			// If the attachment has been changed from non-marker to marker then
			// any collections that contain this attachment must be reordered to
			// take account of this fact. Marker attachments must always be at the end
			// of Asura_Container_Collections otherwise freed memory will be accessed
			// on trying to Unlink.
			if (uOldHash == ASURA_HASH_ID_UNSET)
			{
				Tools_Container_System::CopyDataForMarkerAttachment(GetContainerHash());
			}

			SetModified();
		}
	}
}

////////////////////////////
// AttachToPhysicsClicked //
////////////////////////////
void Tools_Container_Attachment::AttachToPhysicsClicked()
{
	// If we are attaching to a physics object then we want any collections that contain
	// this attachment to be reordered to take account of this fact.
	// This attachment should be near the end of the Asura_Container_Collections, so that physics
	// objects are set up by TeleportTo before this attachment tries to read the pos/ori data.
	if (m_bAttachToPhysicsObject)
	{
		Tools_Container_System::CopyDataForMarkerAttachment(GetContainerHash());
	}
}

///////////////////
// ResetPosition //
///////////////////
void Tools_Container_Attachment::ResetPosition()
{
	if (m_xAttachPosition != Asura_Maths::ZeroVector3)
	{
		m_xAttachPosition.Zero();
		CopyDataToAsura();
		SetModified();
	}
}

//////////////////////
// ResetOrientation //
//////////////////////
void Tools_Container_Attachment::ResetOrientation()
{
	if (m_xAttachEuler.x != 0.0f || m_xAttachEuler.y != 0.0f || m_xAttachEuler.z != 0.0f)
	{
		m_xAttachEuler.Zero();
		CopyDataToAsura();
		SetModified();
	}
}

/////////////////////
// RefreshDialogue //
/////////////////////
void Tools_Container_Attachment::RefreshDialogue()
{
	// Set attached dialogue string.
	Asura_String256 xBuffer("Attached: ");
	const Tools_Container* pxContainer = GetChildContainer();
	if (pxContainer)
	{
		Asura_String xFullName;
		pxContainer->MakeFullName(xFullName);

		xBuffer += xFullName.GetString();
	}
	else if (m_uChildContainerHash == ASURA_HASH_ID_UNSET)
	{
		xBuffer += "*None*";
	}
	else
	{
		xBuffer += "*Not Found*";
	}

	m_xChildDialogue = xBuffer.GetString();

	// Set marker dialogue string.
	if (m_xMarkerName.IsBlank())
	{
		m_xMarkerDialogue = "*None*";
	}
	else
	{
		m_xMarkerDialogue = m_xMarkerName;
	}
}

///////////////////////
// SetPosOriFromMode //
///////////////////////
// Set parameters from preview mode.
void Tools_Container_Attachment::SetPosOriFromMode(const Asura_Vector_3& xPosition, const Asura_Quat& xQuat)
{
	bool bChanged = false;

	if (!m_xAttachPosition.ApproximatelyEquals(xPosition))
	{
		bChanged = true;
		m_xAttachPosition = xPosition;
	}

	if (!m_xAttachQuat.ApproximatelyEquals(xQuat))
	{
		bChanged = true;
		m_xAttachQuat  = xQuat;
		m_xAttachQuat.ToEuler(m_xAttachEuler);
	}

	if (bChanged)
	{
		CopyDataToAsura();

		// Must not rebuild everything.
		SetModified(true);
	}
}

#endif //#ifdef ASURA_USE_CONTAINER_SYSTEM
