/*
 * Copyright (c) Contributors to the Open 3D Engine Project.
 * For complete copyright and license terms please see the LICENSE at the root of this distribution.
 *
 * SPDX-License-Identifier: Apache-2.0 OR MIT
 *
 */


#include "EditorDefs.h"

#include "TrackViewNode.h"

// CryCommon
#include <CryCommon/Maestro/Types/AnimNodeType.h>

// Editor
#include "TrackView/TrackViewTrack.h"
#include "TrackView/TrackViewSequence.h"


////////////////////////////////////////////////////////////////////////////
void CTrackViewKeyConstHandle::GetKey(IKey* pKey) const
{
    m_pTrack->GetKey(m_keyIndex, pKey);
}

////////////////////////////////////////////////////////////////////////////
float CTrackViewKeyConstHandle::GetTime() const
{
    return m_pTrack->GetKeyTime(m_keyIndex);
}

////////////////////////////////////////////////////////////////////////////
void CTrackViewKeyHandle::SetKey(IKey* pKey)
{
    assert(m_bIsValid);

    m_pTrack->SetKey(m_keyIndex, pKey);
}

////////////////////////////////////////////////////////////////////////////
void CTrackViewKeyHandle::GetKey(IKey* pKey) const
{
    assert(m_bIsValid);

    m_pTrack->GetKey(m_keyIndex, pKey);
}

////////////////////////////////////////////////////////////////////////////
void CTrackViewKeyHandle::Select(bool bSelect)
{
    assert(m_bIsValid);

    m_pTrack->SelectKey(m_keyIndex, bSelect);
}

////////////////////////////////////////////////////////////////////////////
bool CTrackViewKeyHandle::IsSelected() const
{
    assert(m_bIsValid);

    return m_pTrack->IsKeySelected(m_keyIndex);
}

////////////////////////////////////////////////////////////////////////////
void CTrackViewKeyHandle::SetTime(float time, bool notifyListeners)
{
    AZ_Assert(m_bIsValid, "Expected a valid key handle.");

    // Flag the current key, because the key handle may become invalid
    // after the time is set and it is potentially sorted into a different
    // index.    
    m_pTrack->SetSortMarkerKey(m_keyIndex, true);

    // set the new time, this may cause a sort that reorders the keys, making
    // m_keyIndex incorrect.
    m_pTrack->SetKeyTime(m_keyIndex, time, notifyListeners);

    // If the key at this index changed because of the key sort by time.
    // We need to search through the keys now and find the marker.
    if (!m_pTrack->IsSortMarkerKey(m_keyIndex))
    {
        CTrackViewKeyBundle allKeys = m_pTrack->GetAllKeys();
        for (unsigned int x = 0; x < allKeys.GetKeyCount(); x++)
        {
            unsigned int curIndex = allKeys.GetKey(x).GetIndex();
            if (m_pTrack->IsSortMarkerKey(curIndex))
            {
                m_keyIndex = curIndex;
                break;
            }
        }
    }

    // clear the sort marker
    m_pTrack->SetSortMarkerKey(m_keyIndex, false);
}

////////////////////////////////////////////////////////////////////////////
float CTrackViewKeyHandle::GetTime() const
{
    assert(m_bIsValid);

    return m_pTrack->GetKeyTime(m_keyIndex);
}

////////////////////////////////////////////////////////////////////////////
float CTrackViewKeyHandle::GetDuration() const
{
    assert(m_bIsValid);

    const char* desc = nullptr;
    float duration = 0;
    m_pTrack->m_pAnimTrack->GetKeyInfo(m_keyIndex, desc, duration);

    return duration;
}

////////////////////////////////////////////////////////////////////////////
const char* CTrackViewKeyHandle::GetDescription() const
{
    assert(m_bIsValid);

    const char* desc = "";
    float duration = 0;
    m_pTrack->m_pAnimTrack->GetKeyInfo(m_keyIndex, desc, duration);

    return desc;
}

////////////////////////////////////////////////////////////////////////////
void CTrackViewKeyHandle::Offset(float offset, bool notifyListeners)
{
    AZ_Assert(m_bIsValid, "Expected key handle to be in a valid state.");

    float newTime = m_pTrack->GetKeyTime(m_keyIndex) + offset;
    m_pTrack->SetKeyTime(m_keyIndex, newTime, notifyListeners);
}

////////////////////////////////////////////////////////////////////////////
void CTrackViewKeyHandle::Delete()
{
    assert(m_bIsValid);

    m_pTrack->RemoveKey(m_keyIndex);
    m_bIsValid = false;
}

////////////////////////////////////////////////////////////////////////////
CTrackViewKeyHandle CTrackViewKeyHandle::Clone()
{
    assert(m_bIsValid);

    unsigned int newKeyIndex = m_pTrack->CloneKey(m_keyIndex);
    return CTrackViewKeyHandle(m_pTrack, newKeyIndex);
}

////////////////////////////////////////////////////////////////////////////
CTrackViewKeyHandle CTrackViewKeyHandle::GetNextKey()
{
    return m_pTrack->GetNextKey(GetTime());
}

////////////////////////////////////////////////////////////////////////////
CTrackViewKeyHandle CTrackViewKeyHandle::GetPrevKey()
{
    return m_pTrack->GetPrevKey(GetTime());
}

////////////////////////////////////////////////////////////////////////////
CTrackViewKeyHandle CTrackViewKeyHandle::GetAboveKey() const
{
    // Search for track above that has keys
    for (CTrackViewNode* pCurrentNode = m_pTrack->GetAboveNode(); pCurrentNode; pCurrentNode = pCurrentNode->GetAboveNode())
    {
        if (pCurrentNode->GetNodeType() == eTVNT_Track)
        {
            CTrackViewTrack* pCurrentTrack = static_cast<CTrackViewTrack*>(pCurrentNode);
            const unsigned int keyCount = pCurrentTrack->GetKeyCount();
            if (keyCount > 0)
            {
                // Return key with nearest time to this key
                return pCurrentTrack->GetNearestKeyByTime(GetTime());
            }
        }
    }

    return CTrackViewKeyHandle();
}

////////////////////////////////////////////////////////////////////////////
CTrackViewKeyHandle CTrackViewKeyHandle::GetBelowKey() const
{
    // Search for track below that has keys
    for (CTrackViewNode* pCurrentNode = m_pTrack->GetBelowNode(); pCurrentNode; pCurrentNode = pCurrentNode->GetBelowNode())
    {
        if (pCurrentNode->GetNodeType() == eTVNT_Track)
        {
            CTrackViewTrack* pCurrentTrack = static_cast<CTrackViewTrack*>(pCurrentNode);
            const unsigned int keyCount = pCurrentTrack->GetKeyCount();
            if (keyCount > 0)
            {
                // Return key with nearest time to this key
                return pCurrentTrack->GetNearestKeyByTime(GetTime());
            }
        }
    }

    return CTrackViewKeyHandle();
}

////////////////////////////////////////////////////////////////////////////
bool CTrackViewKeyHandle::operator==(const CTrackViewKeyHandle& keyHandle) const
{
    return m_pTrack == keyHandle.m_pTrack && m_keyIndex == keyHandle.m_keyIndex;
}

////////////////////////////////////////////////////////////////////////////
bool CTrackViewKeyHandle::operator!=(const CTrackViewKeyHandle& keyHandle) const
{
    return !(*this == keyHandle);
}

////////////////////////////////////////////////////////////////////////////
void CTrackViewKeyBundle::AppendKey(const CTrackViewKeyHandle& keyHandle)
{
    // Check if newly added key has different type than existing ones
    if (m_bAllOfSameType && m_keys.size() > 0)
    {
        const CTrackViewKeyHandle& lastKey = m_keys.back();

        const CTrackViewTrack* pMyTrack = keyHandle.GetTrack();
        const CTrackViewTrack* pOtherTrack = lastKey.GetTrack();

        // Check if keys are from sub tracks, always compare types of parent track
        if (pMyTrack->IsSubTrack())
        {
            pMyTrack = static_cast<const CTrackViewTrack*>(pMyTrack->GetParentNode());
        }

        if (pOtherTrack->IsSubTrack())
        {
            pOtherTrack = static_cast<const CTrackViewTrack*>(pOtherTrack->GetParentNode());
        }

        // Do comparison
        if (pMyTrack->GetParameterType() != pOtherTrack->GetParameterType()
            || pMyTrack->GetCurveType() != pOtherTrack->GetCurveType()
            || pMyTrack->GetValueType() != pOtherTrack->GetValueType())
        {
            m_bAllOfSameType = false;
        }
    }

    m_keys.push_back(keyHandle);
}

////////////////////////////////////////////////////////////////////////////
void CTrackViewKeyBundle::AppendKeyBundle(const CTrackViewKeyBundle& bundle)
{
    for (auto iter = bundle.m_keys.begin(); iter != bundle.m_keys.end(); ++iter)
    {
        AppendKey(*iter);
    }
}

////////////////////////////////////////////////////////////////////////////
void CTrackViewKeyBundle::SelectKeys(const bool bSelected)
{
    const unsigned int numKeys = GetKeyCount();

    for (unsigned int i = 0; i < numKeys; ++i)
    {
        GetKey(i).Select(bSelected);
    }
}

//////////////////////////////////////////////////////////////////////////
CTrackViewKeyHandle CTrackViewKeyBundle::GetSingleSelectedKey()
{
    const unsigned int keyCount = GetKeyCount();

    if (keyCount == 1)
    {
        return m_keys[0];
    }
    else if (keyCount > 1 && keyCount <= 4)
    {
        // All keys must have same time & same parent track
        CTrackViewNode* pFirstParent = m_keys[0].GetTrack()->GetParentNode();
        const float firstTime = m_keys[0].GetTime();

        // Parent must be a track
        if (pFirstParent->GetNodeType() != eTVNT_Track)
        {
            return CTrackViewKeyHandle();
        }

        // Check other keys for equality
        for (unsigned int i = 0; i < keyCount; ++i)
        {
            if (m_keys[i].GetTrack()->GetParentNode() != pFirstParent || m_keys[i].GetTime() != firstTime)
            {
                return CTrackViewKeyHandle();
            }
        }

        return static_cast<CTrackViewTrack*>(pFirstParent)->GetKeyByTime(firstTime);
    }

    return CTrackViewKeyHandle();
}

//////////////////////////////////////////////////////////////////////////
CTrackViewNode::CTrackViewNode(CTrackViewNode* pParent)
    : m_pParentNode(pParent)
    , m_bSelected(false)
    , m_bHidden(false)
{
}

//////////////////////////////////////////////////////////////////////////
bool CTrackViewNode::HasObsoleteTrack() const
{
    return HasObsoleteTrackRec(this);
}

//////////////////////////////////////////////////////////////////////////
bool CTrackViewNode::HasObsoleteTrackRec(const CTrackViewNode* pCurrentNode) const
{
    if (pCurrentNode->GetNodeType() == eTVNT_Track)
    {
        const CTrackViewTrack* pTrack = static_cast<const CTrackViewTrack*>(pCurrentNode);

        EAnimCurveType trackType = pTrack->GetCurveType();
        if (trackType == eAnimCurveType_TCBFloat || trackType == eAnimCurveType_TCBQuat || trackType == eAnimCurveType_TCBVector)
        {
            return true;
        }
    }

    for (unsigned int i = 0; i < pCurrentNode->GetChildCount(); ++i)
    {
        CTrackViewNode* pNode = pCurrentNode->GetChild(i);

        if (HasObsoleteTrackRec(pNode))
        {
            return true;
        }
    }

    return false;
}

//////////////////////////////////////////////////////////////////////////
void CTrackViewNode::ClearSelection()
{
    CTrackViewSequenceNotificationContext context(GetSequence());

    SetSelected(false);

    const unsigned int numChilds = GetChildCount();
    for (unsigned int childIndex = 0; childIndex < numChilds; ++childIndex)
    {
        GetChild(childIndex)->ClearSelection();
    }
}

//////////////////////////////////////////////////////////////////////////
CTrackViewNode* CTrackViewNode::GetAboveNode() const
{
    CTrackViewNode* pParent = GetParentNode();
    if (!pParent)
    {
        // The root does not have an above node
        return nullptr;
    }

    CTrackViewNode* pPrevSibling = GetPrevSibling();
    if (!pPrevSibling)
    {
        // First sibling -> parent is above node
        return pParent;
    }

    // Find last node in sibling tree
    CTrackViewNode* pCurrentNode = pPrevSibling;
    while (pCurrentNode && pCurrentNode->GetChildCount() > 0 && pCurrentNode->GetExpanded())
    {
        pCurrentNode = pCurrentNode->GetChild(pCurrentNode->GetChildCount() - 1);
    }

    return pCurrentNode;
}

//////////////////////////////////////////////////////////////////////////
CTrackViewNode* CTrackViewNode::GetBelowNode() const
{
    const unsigned int childCount = GetChildCount();
    if (childCount > 0 && GetExpanded())
    {
        return GetChild(0);
    }

    CTrackViewNode* pParent = GetParentNode();
    if (!pParent)
    {
        // Root without children
        return nullptr;
    }

    // If there is a next sibling return it
    CTrackViewNode* pNextSibling = GetNextSibling();
    if (pNextSibling)
    {
        return pNextSibling;
    }

    // Otherwise we need to go up the tree and check
    // the parent nodes for next siblings
    CTrackViewNode* pCurrentNode = pParent;
    while (pCurrentNode)
    {
        CTrackViewNode* pNextParentSibling = pCurrentNode->GetNextSibling();
        if (pNextParentSibling)
        {
            return pNextParentSibling;
        }

        pCurrentNode = pCurrentNode->GetParentNode();
    }

    return nullptr;
}

//////////////////////////////////////////////////////////////////////////
CTrackViewNode* CTrackViewNode::GetPrevSibling() const
{
    CTrackViewNode* pParent = GetParentNode();
    if (!pParent)
    {
        // The root does not have siblings
        return nullptr;
    }

    // Search for prev sibling
    unsigned int siblingCount = pParent->GetChildCount();
    assert(siblingCount > 0);

    for (unsigned int i = 1; i < siblingCount; ++i)
    {
        CTrackViewNode* pSibling = pParent->GetChild(i);
        if (pSibling == this)
        {
            return pParent->GetChild(i - 1);
        }
    }

    return nullptr;
}

//////////////////////////////////////////////////////////////////////////
CTrackViewNode* CTrackViewNode::GetNextSibling() const
{
    CTrackViewNode* pParent = GetParentNode();
    if (!pParent)
    {
        // The root does not have siblings
        return nullptr;
    }

    // Search for next sibling
    unsigned int siblingCount = pParent->GetChildCount();
    assert(siblingCount > 0);

    for (unsigned int i = 0; i < siblingCount - 1; ++i)
    {
        CTrackViewNode* pSibling = pParent->GetChild(i);
        if (pSibling == this)
        {
            return pParent->GetChild(i + 1);
        }
    }

    return nullptr;
}

//////////////////////////////////////////////////////////////////////////
void CTrackViewNode::SetSelected(bool bSelected)
{
    if (bSelected != m_bSelected)
    {
        m_bSelected = bSelected;

        if (m_bSelected)
        {
            GetSequence()->OnNodeChanged(this, ITrackViewSequenceListener::eNodeChangeType_Selected);
        }
        else
        {
            GetSequence()->OnNodeChanged(this, ITrackViewSequenceListener::eNodeChangeType_Deselected);
        }

        GetSequence()->OnNodeSelectionChanged();
    }
}

//////////////////////////////////////////////////////////////////////////
CTrackViewSequence* CTrackViewNode::GetSequence()
{
    for (CTrackViewNode* pCurrentNode = this; pCurrentNode; pCurrentNode = pCurrentNode->GetParentNode())
    {
        if (pCurrentNode->GetNodeType() == eTVNT_Sequence)
        {
            return static_cast<CTrackViewSequence*>(pCurrentNode);
        }
    }

    // Every node belongs to a sequence
    assert(false);

    return nullptr;
}

//////////////////////////////////////////////////////////////////////////
const CTrackViewSequence* CTrackViewNode::GetSequenceConst() const
{
    for (const CTrackViewNode* pCurrentNode = this; pCurrentNode; pCurrentNode = pCurrentNode->GetParentNode())
    {
        if (pCurrentNode->GetNodeType() == eTVNT_Sequence)
        {
            return static_cast<const CTrackViewSequence*>(pCurrentNode);
        }
    }

    AZ_Assert(false, "Every node belongs to a sequence");

    return nullptr;
}

//////////////////////////////////////////////////////////////////////////
void CTrackViewNode::AddNode(CTrackViewNode* pNode)
{
    assert (pNode->GetNodeType() != eTVNT_Sequence);

    m_childNodes.push_back(std::unique_ptr<CTrackViewNode>(pNode));
    SortNodes();

    pNode->m_pParentNode = this;
    GetSequence()->OnNodeChanged(pNode, ITrackViewSequenceListener::eNodeChangeType_Added);
}

//////////////////////////////////////////////////////////////////////////
void CTrackViewNode::SortNodes()
{
    // Sort with operator<
    std::stable_sort(m_childNodes.begin(), m_childNodes.end(),
        [&](const std::unique_ptr<CTrackViewNode>& a, const std::unique_ptr<CTrackViewNode>& b) -> bool
        {
            const CTrackViewNode* pA = a.get();
            const CTrackViewNode* pB = b.get();
            return *pA < *pB;
        }
        );
}

namespace
{
    static int GetNodeOrder(AnimNodeType nodeType)
    {
        AZ_Assert(nodeType < AnimNodeType::Num, "Expected nodeType to be less than AnimNodeType::Num");

        // note: this array gets over-allocated and is sparsely populated because the eAnimNodeType enums are not sequential in IMovieSystem.h
        // I wonder if the original authors intended this? Not a big deal, just some trivial memory wastage.
        static int nodeOrder[static_cast<int>(AnimNodeType::Num)];
        nodeOrder[static_cast<int>(AnimNodeType::Invalid)] = 0;
        nodeOrder[static_cast<int>(AnimNodeType::Director)] = 1;
        nodeOrder[static_cast<int>(AnimNodeType::Alembic)] = 4;
        nodeOrder[static_cast<int>(AnimNodeType::CVar)] = 6;
        nodeOrder[static_cast<int>(AnimNodeType::ScriptVar)] = 7;
        nodeOrder[static_cast<int>(AnimNodeType::Material)] = 8;
        nodeOrder[static_cast<int>(AnimNodeType::Event)] = 9;
        nodeOrder[static_cast<int>(AnimNodeType::Layer)] = 10;
        nodeOrder[static_cast<int>(AnimNodeType::Comment)] = 11;
        nodeOrder[static_cast<int>(AnimNodeType::RadialBlur)] = 12;
        nodeOrder[static_cast<int>(AnimNodeType::ColorCorrection)] = 13;
        nodeOrder[static_cast<int>(AnimNodeType::DepthOfField)] = 14;
        nodeOrder[static_cast<int>(AnimNodeType::ScreenFader)] = 15;
        nodeOrder[static_cast<int>(AnimNodeType::Light)] = 16;
        nodeOrder[static_cast<int>(AnimNodeType::ShadowSetup)] = 17;
        nodeOrder[static_cast<int>(AnimNodeType::Environment)] = 18;
        nodeOrder[static_cast<int>(AnimNodeType::Group)] = 19;

        return nodeOrder[static_cast<int>(nodeType)];
    }
}

bool CTrackViewNode::operator<(const CTrackViewNode& otherNode) const
{
    // Order nodes before tracks
    if (GetNodeType() < otherNode.GetNodeType())
    {
        return true;
    }
    else if (GetNodeType() > otherNode.GetNodeType())
    {
        return false;
    }

    // Same node type
    switch (GetNodeType())
    {
    case eTVNT_AnimNode:
    {
        const CTrackViewAnimNode& thisAnimNode = static_cast<const CTrackViewAnimNode&>(*this);
        const CTrackViewAnimNode& otherAnimNode = static_cast<const CTrackViewAnimNode&>(otherNode);

        const int thisTypeOrder = GetNodeOrder(thisAnimNode.GetType());
        const int otherTypeOrder = GetNodeOrder(otherAnimNode.GetType());

        if (thisTypeOrder == otherTypeOrder)
        {
            // Same node type, sort by name
            return thisAnimNode.GetName() < otherAnimNode.GetName();
        }

        return thisTypeOrder < otherTypeOrder;
    }
    case eTVNT_Track:
        const CTrackViewTrack& thisTrack = static_cast<const CTrackViewTrack&>(*this);
        const CTrackViewTrack& otherTrack = static_cast<const CTrackViewTrack&>(otherNode);

        if (thisTrack.GetParameterType() == otherTrack.GetParameterType())
        {
            // Same parameter type, sort by name
            return thisTrack.GetName() < otherTrack.GetName();
        }

        return thisTrack.GetParameterType() < otherTrack.GetParameterType();
    }

    return false;
}

//////////////////////////////////////////////////////////////////////////
void CTrackViewNode::SetHidden(bool bHidden)
{
    bool bWasHidden = m_bHidden;
    m_bHidden = bHidden;

    if (bHidden && !bWasHidden)
    {
        GetSequence()->OnNodeChanged(this, ITrackViewSequenceListener::eNodeChangeType_Hidden);
    }
    else if (!bHidden && bWasHidden)
    {
        GetSequence()->OnNodeChanged(this, ITrackViewSequenceListener::eNodeChangeType_Unhidden);
    }
}

//////////////////////////////////////////////////////////////////////////
bool CTrackViewNode::IsHidden() const
{
    return m_bHidden;
}

//////////////////////////////////////////////////////////////////////////
CTrackViewNode* CTrackViewNode::GetFirstSelectedNode()
{
    if (IsSelected())
    {
        return this;
    }

    const unsigned int numChilds = GetChildCount();
    for (unsigned int childIndex = 0; childIndex < numChilds; ++childIndex)
    {
        CTrackViewNode* pSelectedNode = GetChild(childIndex)->GetFirstSelectedNode();
        if (pSelectedNode)
        {
            return pSelectedNode;
        }
    }

    return nullptr;
}

//////////////////////////////////////////////////////////////////////////
CTrackViewAnimNode* CTrackViewNode::GetDirector()
{
    for (CTrackViewNode* pCurrentNode = GetParentNode(); pCurrentNode; pCurrentNode = pCurrentNode->GetParentNode())
    {
        if (pCurrentNode->GetNodeType() == eTVNT_AnimNode)
        {
            CTrackViewAnimNode* pParentAnimNode = static_cast<CTrackViewAnimNode*>(pCurrentNode);

            if (pParentAnimNode->GetType() == AnimNodeType::Director)
            {
                return pParentAnimNode;
            }
        }
        else if (pCurrentNode->GetNodeType() == eTVNT_Sequence)
        {
            return static_cast<CTrackViewAnimNode*>(pCurrentNode);
        }
    }

    return nullptr;
}
