//////////////////////////////////////////////////////////////////////////////
//
// Asura_ServerNode_Spline.cpp
//
// Created by NJD - 13 October 2006
//
// Copyright (C) 2006 Rebellion, All Rights Reserved.
//
//////////////////////////////////////////////////////////////////////////////

// I N C L U D E S ///////////////////////////////////////////////////////////

// core
#include "Core/Asura.h"

// header
#include "Entity/Locations/Spline/Asura_ServerNode_Spline.h"

// asura
#include "ChunkSystem/Asura_Chunk_Stream.h"
#include "Core/Asura_String.h"
#include "Core/Memory/Asura_Mem.h"
#include "Core/Memory/Asura_ResourceAllocator.h"
#include "Entity/Asura_Entity_Classification.h"
#include "Entity/Asura_StaticMessage_System.h"
#include "Network/Asura_Message.h"

#if defined (ASURA_USE_STREAMING) && !defined (ASURA_TOOLS)
#include "Streaming/Asura_Streaming_System.h"
#endif //#if defined (ASURA_USE_STREAMING) && !defined (ASURA_TOOLS)

//Tools
#ifdef ASURA_TOOLS
#include "EntityTemplate/Tools_EntityTemplate_System.h"
#endif

//////////////////////////////////////////////////////////////////////////////
//
// Asura_ServerNode_Spline
//
//////////////////////////////////////////////////////////////////////////////

Asura_ServerNode_Spline::DistanceAndMessageBlock::DistanceAndMessageBlock()
: m_fDistance( 0.0f ), m_usMessageBlock( ASURA_STATICMESSAGE_EMPTYBLOCK )
, m_uFlags(0)
#ifdef ASURA_TOOLS
, m_xName()
, m_bToBeDeleted(false)
#endif
{
}

Asura_ServerNode_Spline::Asura_ServerNode_Spline(Asura_Guid uGuid)
: PARENT_NODE( uGuid )
, m_uSourceNode( ASURA_GUID_UNREGISTERED )
, m_uTargetNode( ASURA_GUID_UNREGISTERED )
, m_pxSplineEvents( NULL )
, m_uNumSplineEvents( 0 )
{
	Classification = AsuraEntityClass_Spline;
}

Asura_ServerNode_Spline::~Asura_ServerNode_Spline()
{
	delete [] m_pxSplineEvents;
}

void Asura_ServerNode_Spline::CreateFromChunkStream( Asura_Guid uGuid, Asura_Chunk_Stream& xStream )
{
	Asura_ServerNode_Spline* pxObject = new Asura_ServerNode_Spline( uGuid );
    if ( pxObject )
    {
        pxObject->ReadFromChunkStream( xStream );
    }
}

void Asura_ServerNode_Spline::ReadFromChunkStream( Asura_Chunk_Stream& xStream )
{
    Asura_ResourcePushType( ASURA_RESOURCE_GAMESCENE );

	// Version.
    int iVersion;
    xStream >> iVersion;

	// Parent.
	PARENT_NODE::ReadFromChunkStream( xStream );

	// Our data.
	if ( iVersion == 5 || iVersion == 6)
	{
		xStream >> m_uSourceNode;
		xStream >> m_uTargetNode;
#if defined (ASURA_USE_STREAMING) && !defined (ASURA_TOOLS)
		m_uSourceNode = Asura_Streaming_System::MakeCurrentMasterZoneGuid(m_uSourceNode);
		m_uTargetNode = Asura_Streaming_System::MakeCurrentMasterZoneGuid(m_uTargetNode);
#endif

		PARENT_SPLINE::ReadFromChunkStream( xStream );

		xStream >> m_uNumSplineEvents;
		if ( m_uNumSplineEvents )
		{
			m_pxSplineEvents = new DistanceAndMessageBlock[ m_uNumSplineEvents ];
			for ( u_int u = 0; u < m_uNumSplineEvents; ++u )
			{
				xStream >> m_pxSplineEvents[ u ].m_fDistance;
				xStream >> m_pxSplineEvents[ u ].m_usMessageBlock;
				if(iVersion > 5)
				{
					xStream >> m_pxSplineEvents[ u ].m_uFlags;
				}
#ifdef ASURA_TOOLS
				xStream >> m_pxSplineEvents[ u ].m_xName;
#else
				Asura_String xDummyString;
				xStream >> xDummyString;
#endif
			}
		}

		xStream.Skip4BytePadding();
	}
	else if ( iVersion == 3 || iVersion == 4 )
	{
		xStream >> m_uSourceNode;
		xStream >> m_uTargetNode;
#if defined (ASURA_USE_STREAMING) && !defined (ASURA_TOOLS)
		m_uSourceNode = Asura_Streaming_System::MakeCurrentMasterZoneGuid(m_uSourceNode);
		m_uTargetNode = Asura_Streaming_System::MakeCurrentMasterZoneGuid(m_uTargetNode);
#endif

		PARENT_SPLINE::ReadFromChunkStream( xStream );
		if ( iVersion == 4 )
		{
			xStream.Skip4BytePadding();
		}
	}
#ifdef ASURA_TOOLS
	else if ( iVersion == 2 )
	{
		// Chuck the spline flags away.
		u_int uSplineFlags;
		Asura_ReadFromStream_Bitfield_Wrapper< u_int > xFlagsBitfield( uSplineFlags );
		xStream >> xFlagsBitfield;

		// Chuck the share end link away.
		Asura_Guid uDummyGuid;
		xStream >> uDummyGuid;

		xStream >> m_uSourceNode;
		xStream >> m_uTargetNode;
#if defined (ASURA_USE_STREAMING) && !defined (ASURA_TOOLS)
		m_uSourceNode = Asura_Streaming_System::MakeCurrentMasterZoneGuid(m_uSourceNode);
		m_uTargetNode = Asura_Streaming_System::MakeCurrentMasterZoneGuid(m_uTargetNode);
#endif

		PARENT_SPLINE::ReadFromChunkStream( xStream );
	}
	else if ( iVersion >= 0 )
	{
		// Chuck the spline flags away.
		u_int uSplineFlags;
		Asura_ReadFromStream_Bitfield_Wrapper< u_int > xFlagsBitfield( uSplineFlags );
		xStream >> xFlagsBitfield;

		// Chuck the share end link away.
		Asura_Guid uDummyGuid;
		xStream >> uDummyGuid;

		xStream >> m_uSourceNode;
		xStream >> m_uTargetNode;
#if defined (ASURA_USE_STREAMING) && !defined (ASURA_TOOLS)
		m_uSourceNode = Asura_Streaming_System::MakeCurrentMasterZoneGuid(m_uSourceNode);
		m_uTargetNode = Asura_Streaming_System::MakeCurrentMasterZoneGuid(m_uTargetNode);
#endif

		float fInverseTotalDistance;
		xStream >> fInverseTotalDistance;

#ifdef ASURA_ASSERT
		int iNumTrackPoints = GetNumberOfPoints();
#endif
		int iNumberOfTrackPoints;
		xStream >> iNumberOfTrackPoints;
		Asura_Assert( ( iNumberOfTrackPoints == iNumTrackPoints ) || ( iNumTrackPoints == 0 ), "Number of track points has changed since last load." );
		Asura_Assert( iNumberOfTrackPoints > 3, "Splines should have at least 4 track points." );
		m_uNumberOfPoints = iNumberOfTrackPoints;

		const u_int uNumberOfPositionsIgnoringEnds = iNumberOfTrackPoints - 2;
		// Don't allocate again if they exist. Only needed in this chunk as only checkpoint saves should manage this twice (and checkpoint saves by definition always use the latest chunk).
		if ( !m_pxOrientations )
		{
			Asura_ResourceAllocateArray( m_pxOrientations, uNumberOfPositionsIgnoringEnds );
		}
		Asura_ReadFromStream_Array_Wrapper<Asura_Quat> xOrientationArray(uNumberOfPositionsIgnoringEnds, m_pxOrientations);
		xStream >> xOrientationArray;

		if ( !m_pxPositions )
		{
			Asura_ResourceAllocateArray( m_pxPositions, iNumberOfTrackPoints );
		}
		Asura_ReadFromStream_Array_Wrapper<Asura_Vector_3> xPositionArray(m_uNumberOfPoints, m_pxPositions);
		xStream >> xPositionArray;


		if ( !m_pfCumulativeLengths )
		{
			Asura_ResourceAllocateArray( m_pfCumulativeLengths, uNumberOfPositionsIgnoringEnds);
		}
		Asura_ReadFromStream_Array_Wrapper<float> xLengthsArray(uNumberOfPositionsIgnoringEnds, m_pfCumulativeLengths);
		xStream >> xLengthsArray;

		// Default flags to zero
		if(!m_pucSectionFlags)
		{
			Asura_ResourceAllocateArray( m_pucSectionFlags, uNumberOfPositionsIgnoringEnds);
		}
		Asura_Mem::Set(m_pucSectionFlags, 0, uNumberOfPositionsIgnoringEnds);

		if (iVersion < 1)
		{
			float fDummy;
			// old version was writing an extra value
			xStream >> fDummy;
		}
	}
#endif // ASURA_TOOLS
	else
	{
		Asura_Assertf( false, ( "Asura_ServerNode_Spline::ReadFromChunkStream - unhandled chunk version (%d)", iVersion ) );
	}

    Asura_ResourcePopType();
}

void Asura_ServerNode_Spline::WriteToChunkStream( Asura_Chunk_Stream& xStream ) const
{
	// (TI - 5.9.2004) - DON'T FORGET TO UPDATE ApplyChangesForTemplateInstance IF YOU CHANGE STUFF.

	// Version
	xStream << static_cast< int >( CURRENT_CHUNK_VERSION );

	// Parent
	PARENT_NODE::WriteToChunkStream( xStream );

	// Our data
	xStream << m_uSourceNode;
	xStream << m_uTargetNode;
	
	PARENT_SPLINE::WriteToChunkStream( xStream );

	xStream << m_uNumSplineEvents;
	for ( u_int u = 0; u < m_uNumSplineEvents; ++u )
	{
		#ifdef ASURA_TOOLS
		Asura_Assert(!m_pxSplineEvents[ u ].m_bToBeDeleted, "Expecting deleted spline events to have been removed from the array before being saved.");
		#endif

		xStream << m_pxSplineEvents[ u ].m_fDistance;
		xStream << m_pxSplineEvents[ u ].m_usMessageBlock;

		Asura_WriteToStream_Bitfield_Wrapper<u_int>	xFlagsBitfield (m_pxSplineEvents[u].m_uFlags);
		xFlagsBitfield.Add(m_pxSplineEvents[u].m_bTriggerWhenReversed, 1);
		xStream << xFlagsBitfield;
	
#ifdef ASURA_TOOLS
		xStream << m_pxSplineEvents[ u ].m_xName;
#else
		Asura_String xDummyString;
		xStream	<< xDummyString;
#endif
	}

	xStream.Write4BytePadding();

	// (TI - 5.9.2004) - DON'T FORGET TO UPDATE ApplyChangesForTemplateInstance IF YOU CHANGE STUFF.
}

float Asura_ServerNode_Spline::GetPositionFromDistance( const float fDistanceRequest, Asura_Vector_3& xOutput, Asura_Quat* pxOutputOrientation /* = NULL*/ ) const
{
	if ( pxOutputOrientation )
	{
		const float fActualDistance = GetPosAndOriFromDistance(fDistanceRequest, xOutput, *pxOutputOrientation);
		ASURA_GAMESCENE_SPLINE_ORIENTATION_CHECK( *pxOutputOrientation, GetGuid() );
		return fActualDistance;
	}
	else
	{		
		return Asura_Oriented_Spline::GetPositionFromDistance(fDistanceRequest, xOutput);
	}
}

void Asura_ServerNode_Spline::GetPositionFromProportion( float fProportionIntoTrack, Asura_Vector_3& xOutput, Asura_Quat *pxOutputOrientation /* = NULL*/ ) const
{
	const float fDistance = GetTotalLength() * fProportionIntoTrack;
	GetPositionFromDistance(fDistance, xOutput, pxOutputOrientation);
}

void Asura_ServerNode_Spline::GetTangentFromProportion( float fProportionIntoTrack, Asura_Vector_3& xOutput ) const
{
	const float fDistance = GetTotalLength() * fProportionIntoTrack;
	GetTangentFromDistance(fDistance, xOutput);
}

bool Asura_ServerNode_Spline::IsBaseOrDerivativeOf(u_short usClassification) const
{
	if (usClassification == AsuraEntityClass_Spline)
	{
		return true;
	}
	return PARENT_NODE::IsBaseOrDerivativeOf(usClassification);
}

#ifdef ASURA_TOOLS
void Asura_ServerNode_Spline::CreateBasePoints()
{
	const u_int uNumTrackPoints = GetNumberOfPoints();
	Asura_Assert(uNumTrackPoints == 0, "track points already exist");
	if (uNumTrackPoints != 0)
	{
		return;
	}

    Asura_ServerEntity_Node* pxSource = Asura_ServerEntity_Node::FindEntity( GetSourceGuid() );
	Asura_ServerEntity_Node* pxTarget = Asura_ServerEntity_Node::FindEntity( GetTargetGuid() );
	if (!(pxSource && pxTarget))
	{
		return;
	}
	Asura_ResourcePushType(ASURA_RESOURCE_GAMESCENE);
	{
		Asura_Vector_3 axPoints[2];
		axPoints[0] = pxSource->GetPosition();
		axPoints[1] = pxTarget->GetPosition();
		SetPointPositions(2, axPoints);
	}
	Asura_ResourcePopType();
}

void Asura_ServerNode_Spline::CalculateControlPointPositions()
{
	CalculateStartControlPoint();
	CalculateEndControlPoint();
}

void Asura_ServerNode_Spline::CalculateStartControlPoint()
{
	bool bSmoothed = false;

    Asura_ServerEntity_Node* pxSourceNode = Asura_ServerEntity_Node::FindEntity( m_uSourceNode );
    if ( pxSourceNode && pxSourceNode->IsSmoothOverNode() && pxSourceNode->GetNumberLinks() == 1 )
    {
        u_int uLinkCount = 0;
        const Asura_ServerNode_Spline* pxSpline = NULL;

        for ( Asura_Hashed_List_ConstIt< Asura_ServerEntity_Node > xNodeIt( Asura_ServerEntity_Node::GetList() ); !xNodeIt.Done(); xNodeIt.Next() )
        {
            const Asura_ServerEntity_Node* pxNode = xNodeIt.GetCurrent();
            if ( pxNode->IsBaseOrDerivativeOf( AsuraEntityClass_Spline ) )
            {
                const Asura_ServerNode_Spline* pxSplineNode = static_cast< const Asura_ServerNode_Spline* >( pxNode );
                if ( pxSplineNode->GetTargetGuid() == pxSourceNode->GetGuid() )
                {
                    pxSpline = pxSplineNode;
                    ++uLinkCount;
                }
            }
        }

        if (uLinkCount == 1)
        {
            Asura_Assert(pxSpline, "Sanity check");
			m_pxPositions[0] = pxSpline->GetFirstPosition(true);
            bSmoothed = true;
        }
    }

	if (!bSmoothed)
	{
		const Asura_Vector_3& xPos1 = GetPointPosition(1);
		const Asura_Vector_3& xPos2 = GetPointPosition(2);
		m_pxPositions[0] = (xPos1 * 2.0f) - xPos2;
	}
}

void Asura_ServerNode_Spline::CalculateEndControlPoint()
{
	bool bSmoothed = false;

    Asura_ServerEntity_Node* pxTargetNode = Asura_ServerEntity_Node::FindEntity( m_uTargetNode );
    if ( pxTargetNode && pxTargetNode->IsSmoothOverNode() && pxTargetNode->GetNumberLinks() == 1 )
    {
        const Asura_Guid uLinkGuid = pxTargetNode->GetLink( 0 );
        Asura_ServerEntity_Node* pxLinkNode = Asura_ServerEntity_Node::FindEntity( uLinkGuid );
        if ( pxLinkNode && pxLinkNode->IsBaseOrDerivativeOf( AsuraEntityClass_Spline ) )
        {
            const Asura_ServerNode_Spline* pxSplineNode = static_cast< const Asura_ServerNode_Spline* >( pxLinkNode );
            m_pxPositions[m_uNumberOfPoints - 1] = pxSplineNode->GetFirstPosition( false );
            bSmoothed = true;
        }
    }

    if (!bSmoothed)
	{
		const u_int uNumPoints = GetNumberOfPoints();
		const Asura_Vector_3& xPosNMinus2 = GetPointPosition(uNumPoints - 2);
		const Asura_Vector_3& xPosNMinus3 = GetPointPosition(uNumPoints - 3);
		m_pxPositions[uNumPoints - 1] = (xPosNMinus2 * 2.0f) - xPosNMinus3;
	}
}

void Asura_ServerNode_Spline::SmoothingPointsChanged()
{
	// If we are smoothing over nodes we might need to update another link.
    const Asura_ServerEntity_Node* pxSourceNode = Asura_ServerEntity_Node::FindEntity( m_uSourceNode );
    if ( pxSourceNode && pxSourceNode->IsSmoothOverNode() )
    {
        u_int uLinkCount = 0;
        Asura_ServerNode_Spline* pxSpline = NULL;
        for ( Asura_Hashed_List_It< Asura_ServerEntity_Node > xNodeIter( Asura_ServerEntity_Node::GetList() ); !xNodeIter.Done(); xNodeIter.Next() )
        {
            Asura_ServerEntity_Node* pxNode = xNodeIter.GetCurrent();
            if ( pxNode && pxNode->IsBaseOrDerivativeOf( AsuraEntityClass_Spline ) )
            {
                Asura_ServerNode_Spline* pxSplineNode = static_cast< Asura_ServerNode_Spline* >( pxNode );
                if ( pxSplineNode->GetTargetGuid() == pxSourceNode->GetGuid() )
                {
                    pxSpline = pxSplineNode;
                    ++uLinkCount;
                }
            }
        }

        if ( uLinkCount == 1 )
        {
            pxSpline->OnChanged();
        }
    }

    Asura_ServerEntity_Node* pxTargetNode = Asura_ServerEntity_Node::FindEntity( m_uTargetNode );
    if ( pxTargetNode && pxTargetNode->IsSmoothOverNode() && pxTargetNode->GetNumberLinks() == 1 )
    {
        const Asura_Guid uLinkGuid = pxTargetNode->GetLink( 0 );
        Asura_ServerEntity_Node* pxNode = Asura_ServerEntity_Node::FindEntity( uLinkGuid );
        if ( pxNode && pxNode->IsBaseOrDerivativeOf( AsuraEntityClass_Spline ) )
        {
            Asura_ServerNode_Spline* pxSplineNode = static_cast< Asura_ServerNode_Spline* >( pxNode );
            pxSplineNode->OnChanged();
        }
    }
}

const Asura_Vector_3& Asura_ServerNode_Spline::GetFirstPosition(bool bCountFromEnd) const
{
	const u_int uNumPoints = GetNumberOfPoints();
	Asura_Assert( uNumPoints > 2, "Sanity check: Must have at least 3 track points." );
	if (bCountFromEnd)
	{
		return GetPointPosition(uNumPoints - 3);
	}
	return GetPointPosition(2);
}

bool Asura_ServerNode_Spline::CanBeIncludedInTemplate() const 
{
	return ( Classification == AsuraEntityClass_Spline );
}

void Asura_ServerNode_Spline::ApplyChangesForTemplateInstance( Tools_EntityTemplateInstance* pxInstance )
{
    PARENT_NODE::ApplyChangesForTemplateInstance( pxInstance );
	PARENT_SPLINE::ApplyChangesForTemplateInstance( pxInstance );

	m_uSourceNode = pxInstance->ConvertGuid( m_uSourceNode );
    m_uTargetNode = pxInstance->ConvertGuid( m_uTargetNode );

	for ( u_int u = 0; u < m_uNumSplineEvents; ++u )
	{
		m_pxSplineEvents[ u ].m_usMessageBlock = pxInstance->ConvertMessageBlock( m_pxSplineEvents[ u ].m_usMessageBlock );
	}
}

u_int Asura_ServerNode_Spline::AddSplineEvent()
{
    DistanceAndMessageBlock* pxOldEvents   = m_pxSplineEvents;
	const u_int				 uOldNumEvents = m_uNumSplineEvents;

	m_uNumSplineEvents = uOldNumEvents + 1;
    m_pxSplineEvents   = new DistanceAndMessageBlock[ m_uNumSplineEvents ];
	for ( u_int u = 0; u < uOldNumEvents; ++u )
	{
		m_pxSplineEvents[ u ] = pxOldEvents[ u ];
	}

	delete [] pxOldEvents;

	return uOldNumEvents;
}

/*
Spline event deletion is split into a separate flagging and cleanup stage to resolve issues with deleting
multiple spline event objects in a frame. If the spline event were removed immediately , then any other
spline event tools objects would be pointing at junk data as a result of the array being altered.
RAR 13/02/2009
*/
void Asura_ServerNode_Spline::FlagSplineEventForDeletion(u_int uIndex)
{
	m_pxSplineEvents[uIndex].m_bToBeDeleted = true;	
}

void Asura_ServerNode_Spline::RemoveDeletedSplineEvents()
{
	if(!m_uNumSplineEvents) return;
	
	const DistanceAndMessageBlock* pxSourceEvent = m_pxSplineEvents;
	DistanceAndMessageBlock* pxDestEvent = m_pxSplineEvents;
	u_int uNumSplineEvents = m_uNumSplineEvents;
	//go through the array , and copy over any deleted events
	for(u_int i=0; i<uNumSplineEvents; i++)
	{
		if(pxSourceEvent->m_bToBeDeleted)
		{
			//This event is being deleted ,so skip it
			Asura_Assert(pxSourceEvent->m_usMessageBlock == ASURA_STATICMESSAGE_EMPTYBLOCK, "Deleting a spline event that still has a message block");
			pxSourceEvent++;
			m_uNumSplineEvents--;
		}
		else if(pxDestEvent == pxSourceEvent) 
		{
			pxDestEvent++;
			pxSourceEvent++;		
		}
		else
		{
			*pxDestEvent++ = *pxSourceEvent++;
		}
	}

	if(!m_uNumSplineEvents)
	{
		delete [] m_pxSplineEvents;
		m_pxSplineEvents = 0;
	}
}

float& Asura_ServerNode_Spline::GetSplineEventDistance( const u_int uIndex )
{
	return m_pxSplineEvents[ uIndex ].m_fDistance;
}

u_short& Asura_ServerNode_Spline::GetSplineEventMessageBlock( const u_int uIndex )
{
	return m_pxSplineEvents[ uIndex ].m_usMessageBlock;
}

u_int& Asura_ServerNode_Spline::GetSplineEventFlags( const u_int uIndex )
{
	return m_pxSplineEvents[ uIndex ].m_uFlags;
}


Asura_String& Asura_ServerNode_Spline::GetSplineEventName( const u_int uIndex )
{
	return m_pxSplineEvents[ uIndex ].m_xName;
}

#endif //ASURA_TOOLS

float Asura_ServerNode_Spline::GetSplineEventDistance( const u_int uIndex ) const
{
	return m_pxSplineEvents[ uIndex ].m_fDistance;
}

bool Asura_ServerNode_Spline::GetSplineEventFlag_TriggerWhenReversed( const u_int uIndex ) const
{
	return m_pxSplineEvents[ uIndex ].m_bTriggerWhenReversed;
}

void Asura_ServerNode_Spline::SendMessagesForSplineEvent( const u_int uIndex ) const
{
	if ( m_pxSplineEvents[ uIndex ].m_usMessageBlock != ASURA_STATICMESSAGE_EMPTYBLOCK )
	{
		Asura_StaticMessage_System::SendMessages( m_pxSplineEvents[ uIndex ].m_usMessageBlock, GetGuid() );
	}
}

//////////////////////////////////////////////////////////////////////////////
//
// 
//
//////////////////////////////////////////////////////////////////////////////
