//////////////////////////////////////////////////////////////////////////////
//
// Tools_AnimTemplates_Preview.cpp
//
// Created by NJD - 07 November 2006
//
// Copyright (C) 2006 Rebellion, All Rights Reserved.
//
//////////////////////////////////////////////////////////////////////////////

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

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

#ifdef ASURA_USE_AXON

// header
#include "AnimTemplates/Tools_AnimTemplates_Play.h"

// asura
#include "Animation/Asura_Animation.h"
#include "Animation/Asura_AnimationBlend.h"
#include "Animation/Asura_AnimationController_Animation.h"
#include "Camera/Asura_Camera.h"
#include "ChunkSystem/Asura_Chunk_Loaders.h"
#include "Core/Asura_Timers.h"
#include "Light/Asura_Photon.h"

// axon
#include "AnimTemplates/Axon_Gamescene_AnimTemplates.h"
#include "AnimTemplates/Axon_Gamescene_AnimTemplate_System.h"

Tools_AnimTemplates_Preview Tools_AnimTemplates_Play::m_xPreview;


//////////////////////////////////////////////////////////////////////////////
//
// Tools_AnimTemplates_Preview
//
//////////////////////////////////////////////////////////////////////////////

Tools_AnimTemplates_Preview::Tools_AnimTemplates_Preview()
{
	Initialise();
}

void Tools_AnimTemplates_Preview::Initialise()
{
	m_pxTemplate                = NULL;
	m_pxFidgetTemplate          = NULL;
	m_pxPrevChosenFidget		= NULL;
	m_pxHierarchy               = NULL;
	m_uPrevAnimHash             = ASURA_HASH_ID_UNSET;
	m_uPostAnimHash             = ASURA_HASH_ID_UNSET;
	m_pScoringData				= NULL;
	m_xPositionAtStartOfAnim.Zero();
	m_xOrientationAtStartOfAnim.Identity();
	m_xPositionAtStartOfPlay.Zero();
	m_xOrientationAtStartOfPlay.Identity();
	m_bPlaying                  = false;
	m_bLoop						= false;
	m_iCurrentAnim              = 0;
	m_fDurationRemaining        = 0.0f;
	m_fTimeToNextFidget         = 0.0f;
	m_fTimeElapsed				= 0.0f;
	m_uCurrentAnimControllerHash = ASURA_HASH_ID_UNSET;
}

void Tools_AnimTemplates_Preview::Shutdown()
{
    Stop();
}

void Tools_AnimTemplates_Preview::Render()
{
	if (!m_bPlaying)
	{
		return;
	}

	if ( m_pxTemplate->m_uTemplateType == AXON_ATT_RIGIDMOVEMENT )
	{
		Render(m_xPositionAtStartOfAnim, m_xOrientationAtStartOfAnim, Asura_Timers::GetGameTimeSlice());
	}
	else
	{
		Asura_Assert(m_pxHierarchy, "Unexpected null pointer");
		Asura_Vector_3 xMovementOffset(0.0f, 0.0f, 0.0f);
		Asura_Quat xOrientationOffset = Asura_Maths::IdentityQuat;
		Asura_Matrix_3x3 xOffsetMatrix;
		m_pxHierarchy->GetMovementOffset(&xMovementOffset, &xOrientationOffset);
		xOrientationOffset.ToMatrix(xOffsetMatrix);
		xMovementOffset = xMovementOffset * m_xOrientationAtStartOfAnim;

		Render(m_xPositionAtStartOfAnim + xMovementOffset, m_xOrientationAtStartOfAnim * xOffsetMatrix, Asura_Timers::GetGameTimeSlice());
	}
}

u_int Tools_AnimTemplates_Preview::GetAnimationTemplateType()
{
	return m_pxTemplate ? m_pxTemplate->m_uTemplateType : AXON_ATT_STATIC;
}

void Tools_AnimTemplates_Preview::Render(Asura_Vector_3 xMovementOffset, Asura_Matrix_3x3 xOrientation, float fTime)
{
    if (!m_bPlaying)
    {
        return;
    }

	const float fTimeSlice = fTime == ANIMTIME_USEGAMETIME ? Asura_Timers::GetGameTimeSlice() : fTime;
	
	m_fTimeElapsed += fTimeSlice;

	if (m_pxTemplate->m_uTemplateType == AXON_ATT_RIGIDMOVEMENT)
	{
		Asura_Vector_3 xMovement2;
		Asura_Matrix_3x3 xOrientation2;
		m_pxTemplate->GetMovementOffset(m_fTimeElapsed, m_pScoringData, xMovement2, xOrientation2);

		xMovementOffset += xMovement2 * xOrientation;

		xOrientation =  xOrientation * xOrientation2;
	}

    if (m_fTimeToNextFidget != 0.0f)
    {
		m_fTimeToNextFidget -= fTimeSlice;
        if (m_fTimeToNextFidget <= 0.0f)
        {
            m_fTimeToNextFidget = 0.0f;
            PlayFidget();
        }
    }
    if (IsAnimationFinished())
    {
        NextAnimation();
	}
	else
	{
		m_pxHierarchy->Update( xMovementOffset, xOrientation, fTime, ASURA_HIERARCHY_RENDER_NO_MODULELINK_TEST );
	}
}

void Tools_AnimTemplates_Preview::NextAnimation()
{
    if (m_iCurrentAnim == 0)
    {
		m_fTimeElapsed = 0.0f;
        m_xPositionAtStartOfAnim = m_xPositionAtStartOfPlay;
        m_xOrientationAtStartOfAnim = m_xOrientationAtStartOfPlay;

        if (m_uPrevAnimHash != ASURA_HASH_ID_UNSET)
        {
            // No movement offset for the pre-template animation.
            const u_int uAnimFlags = AAC_FLAG_ACTIVE | AAC_FLAG_DESTROY_WHEN_FINISHED;
            Asura_AnimationController_Animation* pxAnimation = m_pxHierarchy->CreateControllerAnimation( uAnimFlags, 0x00004000,
                m_uPrevAnimHash, new Asura_AnimationBlend( DEFAULT_TWEEN_TIME ) );
            pxAnimation->Play();
            m_uCurrentAnimControllerHash = pxAnimation->GetControllerHash();
        }
        else
        {
            m_iCurrentAnim++;
        }
    }
    int iNumberOfAnimsInSequence = 0;
    {
    Axon_Gamescene_AnimTemplate_Entry* pxEntry = m_pxTemplate->m_pxFirstEntry;
    while ( pxEntry )
    {
        ++iNumberOfAnimsInSequence;
        pxEntry = pxEntry->GetNextEntry();
    }
    }
    if (m_iCurrentAnim > iNumberOfAnimsInSequence)
    {
		if (!m_bLoop)
		{
			m_bPlaying = false;
			return;
		}

        m_iCurrentAnim = -1;
        if (m_uPostAnimHash != ASURA_HASH_ID_UNSET)
        {
            // No movement offset for the post-template animation.
            const u_int uAnimFlags = AAC_FLAG_ACTIVE | AAC_FLAG_DESTROY_WHEN_FINISHED;
            Asura_AnimationController_Animation* pxAnimation = m_pxHierarchy->CreateControllerAnimation( uAnimFlags, 0x00004000,
                m_uPostAnimHash, new Asura_AnimationBlend_Tween( DEFAULT_TWEEN_TIME, false ) );
            pxAnimation->Play();
            m_uCurrentAnimControllerHash = pxAnimation->GetControllerHash();
        }
    }
    else if (m_iCurrentAnim != 0)
    {
        if (m_iCurrentAnim != 1)
        {
            //Update our position
            Asura_Vector_3 xMovementOffset(0.0f, 0.0f, 0.0f);
            Asura_Quat xOrientationOffset = Asura_Maths::IdentityQuat;
            Asura_Matrix_3x3 xOffsetMatrix;
            m_pxHierarchy->GetMovementOffset(&xMovementOffset, &xOrientationOffset);
            xOrientationOffset.ToMatrix(xOffsetMatrix);
            xMovementOffset = xMovementOffset * m_xOrientationAtStartOfAnim; 
            m_xPositionAtStartOfAnim += xMovementOffset;
            m_xOrientationAtStartOfAnim = m_xOrientationAtStartOfAnim * xOffsetMatrix;
        }

        int iCount = m_iCurrentAnim - 1;
        Axon_Gamescene_AnimTemplate_Entry *pxCurrentEntry = m_pxTemplate->m_pxFirstEntry; 
        while (iCount--)
        {
            Asura_Assert(pxCurrentEntry->GetNextEntry(), "Unexpected null pointer");
            pxCurrentEntry = pxCurrentEntry->GetNextEntry();
        }
        Asura_Assert(pxCurrentEntry, "Unexpected null pointer");
        const bool bLoop = pxCurrentEntry->m_bLoop ? true : false;

        // Make sure it is loaded.
		Asura_Hash_ID uAnimFilenameHashID = ASURA_HASH_ID_UNSET;
		Asura_Hash_ID uAnimHashID = pxCurrentEntry->CalcAnimHashIDToPlay( m_pScoringData, &uAnimFilenameHashID );
        const Asura_Hierarchy_Anim* pxHierAnim = Asura_Animation_System::GetAnimByHash( uAnimHashID );
        if (!pxHierAnim)
        {
			const char* const szFilename = Asura_HashedFilename_System::GetFilename( uAnimFilenameHashID );
			if (szFilename)
			{
				LoadAsuraFile(szFilename);
			}
        }

        // Play the anim.

		// If we have a current anim we should stop it looping now. We also set the 'allow destroy' flag, so that the old anim is
		// destroyed once the tween is finished.
		Asura_AnimationController_Animation* pxAnimation = m_pxHierarchy->GetControllerAnimationFromHash( m_uCurrentAnimControllerHash );
		if ( pxAnimation )
		{
			u_int uAnimFlags = pxAnimation->GetControllerFlags();
			uAnimFlags |= AAC_FLAG_ALLOW_DESTROY_FROM_MESSAGE;
			uAnimFlags &= ~AACA_FLAG_LOOPED;
			pxAnimation->SetControllerFlags( uAnimFlags );
		}

        u_int uAnimFlags = AAC_FLAG_ACTIVE | AACA_FLAG_CALCULATE_MOVEMENT_OFFSET;
        if ( bLoop )                        { uAnimFlags |= AACA_FLAG_LOOPED;   }
        if ( pxCurrentEntry->m_bBackwards ) { uAnimFlags |= AACA_FLAG_REVERSED; }
		if ( pxCurrentEntry->m_bMirror )    { uAnimFlags |= AACA_FLAG_MIRRORED; }
        Asura_AnimationBlend* pxAnimationBlend =
            pxCurrentEntry->m_bTween ? new Asura_AnimationBlend_Tween( pxCurrentEntry->GetTweenTime(), false ) : NULL;
        pxAnimation = m_pxHierarchy->CreateControllerAnimation( uAnimFlags, 0x00004000,
            uAnimHashID, pxAnimationBlend );
        pxAnimation->Play();
        m_uCurrentAnimControllerHash = pxAnimation->GetControllerHash();

        if (bLoop)
        {
            if (m_pxTemplate->m_uTemplateType == AXON_ATT_STATIC)
            {
                Axon_Gamescene_StaticAnimTemplate_Entry *pxStatic = static_cast<Axon_Gamescene_StaticAnimTemplate_Entry*>(pxCurrentEntry);
                if (pxStatic->GetUserSetDuration() != 0.0f)
                {
                    if (pxCurrentEntry->m_bLoopTimeInSeconds)
                    {
                        m_fDurationRemaining = pxStatic->GetUserSetDuration();
                    }
                    else
                    {
						const Asura_Hash_ID uAnimHashID = pxStatic->CalcAnimHashIDToPlay( m_pScoringData );
                        Asura_AnimationController_Animation* pxAnimation = m_pxHierarchy->GetControllerAnimationFromHash( uAnimHashID );
                        if ( pxAnimation )
                        {
                            m_fDurationRemaining = pxAnimation->GetDuration() * pxCurrentEntry->GetUserSetDuration();
                        }
						else
						{
							m_fDurationRemaining = 0.0f;
						}
					}   
                }
                else if (m_pxFidgetTemplate)
                {
                    m_fTimeToNextFidget = Asura_Random::GetFloat(m_pxFidgetTemplate->m_fMinFidgetDelay, m_pxFidgetTemplate->m_fMaxFidgetDelay);
                }
            }
            else if (m_pxTemplate->m_uTemplateType == AXON_ATT_LOOPINGMOVEMENT)
            {
                Axon_Gamescene_LoopingAnimTemplate_Entry *pxLooping = static_cast<Axon_Gamescene_LoopingAnimTemplate_Entry*>(pxCurrentEntry);
                if (pxLooping->GetUserSetDuration() != 0.0f)
                {
                    if (pxCurrentEntry->m_bLoopTimeInSeconds)
                    {
                        m_fDurationRemaining = pxLooping->GetUserSetDuration();
                    }
                    else
                    {
						const Asura_Hash_ID uAnimHashID = pxLooping->CalcAnimHashIDToPlay( m_pScoringData );
                        Asura_AnimationController_Animation* pxAnimation = m_pxHierarchy->GetControllerAnimationFromHash( uAnimHashID );
                        if ( pxAnimation )
                        {
							m_fDurationRemaining = pxAnimation->GetDuration() * pxCurrentEntry->GetUserSetDuration();
                        }
						else
						{
							m_fDurationRemaining = 0.0f;
						}
                    }   
                }
            }
            else
            {
                Asura_Assert(false, "Shouldn't loop in this state");
            }
        }
    }
    m_iCurrentAnim++;
}

bool Tools_AnimTemplates_Preview::IsAnimationFinished()
{
    if (m_fDurationRemaining != 0.0f)
    {
        Asura_Assert(m_fDurationRemaining > 0.0f, "");
        m_fDurationRemaining -= Asura_Timers::GetGameTimeSlice();
        if (m_fDurationRemaining <= 0.0f)
        {
            m_fDurationRemaining = 0.0f;
            return true;
        }
    }

    Asura_AnimationController_Animation* pxAnimation = m_pxHierarchy->GetControllerAnimationFromHash( m_uCurrentAnimControllerHash );
    if ( pxAnimation )
    {
		return pxAnimation->IsFinished();
    }
    else
    {
        return true;
    }
}

void Tools_AnimTemplates_Preview::Play( const Asura_String& xSkinName,
									    u_int uPrevAnimHash, u_int uPostAnimHash, void* pScoringData,
										Axon_Gamescene_AnimTemplate* pxTemplate,
										const Asura_Vector_3& xStartPosition, const Asura_Matrix_3x3& xStartOrientation )
{
    Stop();
    Asura_String xName("Temp");
    switch (pxTemplate->m_uTemplateType)
    {
        case AXON_ATT_STATIC:
        {
            m_pxTemplate = new Axon_Gamescene_StaticAnimTemplate(xName);
            u_int uFidgetBehaviourID = uAXON_ANIMATION_SEQUENCE_ID_NONE;
            if (pxTemplate->m_pxFirstEntry)
            {
                Axon_Gamescene_StaticAnimTemplate_Entry* pxStaticEntry = (Axon_Gamescene_StaticAnimTemplate_Entry*)pxTemplate->m_pxFirstEntry;
                while (uFidgetBehaviourID == uAXON_ANIMATION_SEQUENCE_ID_NONE && pxStaticEntry)
                {
                    uFidgetBehaviourID = pxStaticEntry->GetFidgetBehaviourID();
                    pxStaticEntry = static_cast<Axon_Gamescene_StaticAnimTemplate_Entry *>(pxStaticEntry->GetNextEntry());
                }
            }
			const Axon_Gamescene_AnimTemplate* pxFidgetTemplate = Axon_Gamescene_AnimTemplate_System::GetAnimTemplateByID( uFidgetBehaviourID );
			if ( pxFidgetTemplate )
			{
				m_pxFidgetTemplate = new Axon_Gamescene_FidgetAnimTemplate( "FidgetTemp" );
				m_pxFidgetTemplate->Copy( pxFidgetTemplate );
			}
            break;
        }
        case AXON_ATT_LOOPINGMOVEMENT:
            m_pxTemplate = new Axon_Gamescene_LoopingAnimTemplate(xName);
            break;
        case AXON_ATT_RIGIDMOVEMENT:
            m_pxTemplate = new Axon_Gamescene_RigidAnimTemplate(xName);
            break;
        case AXON_ATT_FIDGETBEHAVIOUR:
            m_pxTemplate = new Axon_Gamescene_FidgetAnimTemplate(xName);
            break;
        default:
            Asura_Assert(false, "Unexpected animation template type");
            break;
    }
    m_pxTemplate->Copy(pxTemplate);
    m_pxHierarchy = new Asura_Animation(xSkinName.GetString());
    m_uPrevAnimHash = uPrevAnimHash;
    m_uPostAnimHash = uPostAnimHash;
	m_pScoringData = pScoringData;
    m_xPositionAtStartOfPlay = xStartPosition;
    m_xOrientationAtStartOfPlay = xStartOrientation;

    m_bPlaying = true;
    if (m_pxHierarchy)
    {
        NextAnimation();
    }
    else
    {
        Stop();
    }
}

void Tools_AnimTemplates_Preview::Stop()
{
    delete m_pxTemplate;
	m_pxTemplate = NULL;
    delete m_pxFidgetTemplate;
	m_pxFidgetTemplate = NULL;

	if ( m_pxHierarchy )
    {
        Asura_AnimationController_Animation* pxAnimController = m_pxHierarchy->GetControllerAnimationFromHash( m_uCurrentAnimControllerHash );
        if ( pxAnimController )
        {
            pxAnimController->Stop();
        }

        delete m_pxHierarchy;
		m_pxHierarchy = NULL;
    }

	m_pxPrevChosenFidget = NULL;
    m_uPrevAnimHash = ASURA_HASH_ID_UNSET;
    m_uPostAnimHash = ASURA_HASH_ID_UNSET;
    m_xPositionAtStartOfAnim.Zero();
    m_xOrientationAtStartOfAnim.Identity();
    m_xPositionAtStartOfPlay.Zero();
    m_xOrientationAtStartOfPlay.Identity();
    m_bPlaying = false;
    m_iCurrentAnim = 0;
    m_fDurationRemaining = 0.0f;
    m_fTimeToNextFidget = 0.0f;
	m_fTimeElapsed = 0.0f;
    m_uCurrentAnimControllerHash = ASURA_HASH_ID_UNSET;
}

void Tools_AnimTemplates_Preview::PlayFidget()
{
    Axon_Gamescene_FidgetAnimTemplate_Entry* pxChosenFidget = m_pxFidgetTemplate->ChooseFidget();
	if ( !pxChosenFidget ) { return; }

	// If there is more than one valid entry try and pick a fidget different to the last one we played.
	if ( m_pxFidgetTemplate->GetNumValidEntries() > 1 )
	{
		u_int uMaxLoops = 10;
		while ( pxChosenFidget && pxChosenFidget == m_pxPrevChosenFidget && uMaxLoops-- > 0 )
		{
			pxChosenFidget = m_pxFidgetTemplate->ChooseFidget();
		}
	}

	// Make sure it is loaded.
	Asura_Hash_ID uAnimFilenameHashID = ASURA_HASH_ID_UNSET;
	const Asura_Hash_ID uAnimHashID = pxChosenFidget->CalcAnimHashIDToPlay( m_pScoringData, &uAnimFilenameHashID );
    const Asura_Hierarchy_Anim* pxHierAnim = Asura_Animation_System::GetAnimByHash( uAnimHashID );
    if (!pxHierAnim)
    {
        LoadAsuraFile( Asura_HashedFilename_System::GetFilename( uAnimFilenameHashID ) );
    }

    // Play it.
	m_pxPrevChosenFidget = pxChosenFidget;
	u_int uAnimFlags = AAC_FLAG_ACTIVE | AAC_FLAG_DESTROY_WHEN_FINISHED | AACA_FLAG_CALCULATE_MOVEMENT_OFFSET;
	if ( pxChosenFidget->m_bLoop )      { uAnimFlags |= AACA_FLAG_LOOPED;   }
	if ( pxChosenFidget->m_bBackwards ) { uAnimFlags |= AACA_FLAG_REVERSED; }
	if ( pxChosenFidget->m_bMirror )    { uAnimFlags |= AACA_FLAG_MIRRORED; }
	Asura_AnimationBlend* pxAnimationBlend =
		pxChosenFidget->m_bTween ? new Asura_AnimationBlend_AnimatedTween( pxChosenFidget->GetTweenTime(), pxChosenFidget->GetTweenTime(), false ) : NULL;
	Asura_AnimationController_Animation* pxAnimation = m_pxHierarchy->CreateControllerAnimation( uAnimFlags, 0x00004000,
		uAnimHashID, pxAnimationBlend );
	pxAnimation->Play();
	m_uCurrentAnimControllerHash = pxAnimation->GetControllerHash();

    m_iCurrentAnim--;
}

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

#endif //ASURA_USE_AXON
