/*
 * 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
 *
 */


#pragma once

#include <AzCore/std/containers/map.h>
#include <AzCore/Component/EntityId.h>
#include <AzCore/Component/EntityBus.h>
#include <AzFramework/Entity/EntityContextBus.h>
#include <AzToolsFramework/Entity/EditorEntityContextBus.h>
#include <AzCore/Component/TransformBus.h>

#include "TrackViewNode.h"
#include "TrackViewTrack.h"
#include "Objects/TrackGizmo.h"

class CTrackViewAnimNode;
class QWidget;

// Represents a bundle of anim nodes
class CTrackViewAnimNodeBundle
{
public:
    unsigned int GetCount() const { return static_cast<unsigned int>(m_animNodes.size()); }
    CTrackViewAnimNode* GetNode(const unsigned int index) { return m_animNodes[index]; }
    const CTrackViewAnimNode* GetNode(const unsigned int index) const { return m_animNodes[index]; }

    void Clear();
    const bool DoesContain(const CTrackViewNode* pTargetNode);

    void AppendAnimNode(CTrackViewAnimNode* pNode);
    void AppendAnimNodeBundle(const CTrackViewAnimNodeBundle& bundle);

    void ExpandAll(bool bAlsoExpandParentNodes = true);
    void CollapseAll();

private:
    std::vector<CTrackViewAnimNode*> m_animNodes;
};

// Callback called by animation node when its animated.
class IAnimNodeAnimator
{
public:
    virtual ~IAnimNodeAnimator() {}

    virtual void Animate(CTrackViewAnimNode* pNode, const SAnimContext& ac) = 0;
    virtual void Render([[maybe_unused]] CTrackViewAnimNode* pNode, [[maybe_unused]] const SAnimContext& ac) {}

    // Called when binding/unbinding the owning node
    virtual void Bind([[maybe_unused]] CTrackViewAnimNode* pNode) {}
    virtual void UnBind([[maybe_unused]] CTrackViewAnimNode* pNode) {}
};

////////////////////////////////////////////////////////////////////////////
//
// This class represents a IAnimNode in TrackView and contains
// the editor side code for changing it
//
// It does *not* have ownership of the IAnimNode, therefore deleting it
// will not destroy the CryMovie track
//
////////////////////////////////////////////////////////////////////////////
class CTrackViewAnimNode
    : public CTrackViewNode
    , public IAnimNodeOwner
    , public ITransformDelegate
    , public AzToolsFramework::EditorEntityContextNotificationBus::Handler
    , public AZ::EntityBus::Handler
    , private AZ::TransformNotificationBus::Handler
    , private AzToolsFramework::EntitySelectionEvents::Bus::Handler
{
public:
    CTrackViewAnimNode(IAnimSequence* pSequence, IAnimNode* pAnimNode, CTrackViewNode* pParentNode);
    ~CTrackViewAnimNode();

    // Rendering
    virtual void Render(const SAnimContext& ac);

    // Playback
    virtual void Animate(const SAnimContext& animContext);

    // Binding/Unbinding
    virtual void BindToEditorObjects();
    virtual void UnBindFromEditorObjects();
    virtual bool IsBoundToEditorObjects() const;

    // Console sync
    virtual void SyncToConsole(SAnimContext& animContext);

    // CTrackViewAnimNode
    virtual ETrackViewNodeType GetNodeType() const override { return eTVNT_AnimNode; }

    // Create & remove sub anim nodes
    virtual CTrackViewAnimNode* CreateSubNode(
        const QString& name, const AnimNodeType animNodeType, AZ::EntityId entityId = AZ::EntityId(),
        AZ::Uuid componentTypeId = AZ::Uuid::CreateNull(), AZ::ComponentId componenId=AZ::InvalidComponentId);
    virtual void RemoveSubNode(CTrackViewAnimNode* pSubNode);

    // Create & remove sub tracks
    virtual CTrackViewTrack* CreateTrack(const CAnimParamType& paramType);
    virtual void RemoveTrack(CTrackViewTrack* pTrack);

    // Add selected entities from scene to group node
    virtual CTrackViewAnimNodeBundle AddSelectedEntities(const AZStd::vector<AnimParamType>& tracks);

    // Add current layer to group node
    virtual void AddCurrentLayer();

    // Director related
    virtual void SetAsActiveDirector();
    virtual bool IsActiveDirector() const;

    // Checks if anim node is part of active sequence and of an active director
    virtual bool IsActive();

    // Set as view camera
    virtual void SetAsViewCamera();

    // Name setter/getter
    AZStd::string GetName() const override { return m_animNode->GetName(); }
    virtual bool SetName(const char* pName) override;
    virtual bool CanBeRenamed() const override;

    // Node owner setter/getter
    virtual void SetNodeEntityId(AZ::EntityId entityId);
    virtual AZ::EntityId GetNodeEntityId(const bool bSearch = true);

    AZ::EntityId GetAzEntityId() const { return m_animNode ? m_animNode->GetAzEntityId() : AZ::EntityId(); }
    bool IsBoundToAzEntity() const { return m_animNode ? m_animNode->GetAzEntityId().IsValid(): false; }

    // Snap time value to prev/next key in sequence
    virtual bool SnapTimeToPrevKey(float& time) const override;
    virtual bool SnapTimeToNextKey(float& time) const override;

    // Expanded state interface
    void SetExpanded(bool expanded) override;
    bool GetExpanded() const override;

    // Node getters
    CTrackViewAnimNodeBundle GetAllAnimNodes();
    CTrackViewAnimNodeBundle GetSelectedAnimNodes();
    CTrackViewAnimNodeBundle GetAllOwnedNodes(AZ::EntityId entityId);
    CTrackViewAnimNodeBundle GetAnimNodesByType(AnimNodeType animNodeType);
    CTrackViewAnimNodeBundle GetAnimNodesByName(const char* pName);

    // Track getters
    virtual CTrackViewTrackBundle GetAllTracks();
    virtual CTrackViewTrackBundle GetSelectedTracks();
    virtual CTrackViewTrackBundle GetTracksByParam(const CAnimParamType& paramType) const;

    // Key getters
    virtual CTrackViewKeyBundle GetAllKeys() override;
    virtual CTrackViewKeyBundle GetSelectedKeys() override;
    virtual CTrackViewKeyBundle GetKeysInTimeRange(const float t0, const float t1) override;

    // Type getters
    AnimNodeType GetType() const;

    // Flags
    EAnimNodeFlags GetFlags() const;
    bool AreFlagsSetOnNodeOrAnyParent(EAnimNodeFlags flagsToCheck) const;

    // Disabled state
    virtual void SetDisabled(bool bDisabled) override;
    virtual bool IsDisabled() const override;
    bool CanBeEnabled() const override;

    // Return track assigned to the specified parameter.
    CTrackViewTrack* GetTrackForParameter(const CAnimParamType& paramType, uint32 index = 0) const;

    // Rotation/Position & Scale
    void SetPos(const Vec3& position);
    Vec3 GetPos() const { return m_animNode->GetPos(); }
    void SetScale(const Vec3& scale);
    Vec3 GetScale() const { return m_animNode->GetScale(); }
    void SetRotation(const Quat& rotation);
    Quat GetRotation() const { return m_animNode->GetRotate(); }
    Quat GetRotation(float time) const { return m_animNode != nullptr ? m_animNode->GetRotate(time) : Quat(0,0,0,0); }

    // Param
    unsigned int GetParamCount() const;
    CAnimParamType GetParamType(unsigned int index) const;
    AZStd::string GetParamName(const CAnimParamType& paramType) const;
    bool IsParamValid(const CAnimParamType& param) const;
    IAnimNode::ESupportedParamFlags GetParamFlags(const CAnimParamType& paramType) const;
    AnimValueType GetParamValueType(const CAnimParamType& paramType) const;
    void UpdateDynamicParams();

    // Parameter getters/setters
    template <class Type>
    bool SetParamValue(const float time, const CAnimParamType& param, const Type& value)
    {
        AZ_Assert(m_animNode, "Expected valid m_animNode");
        return m_animNode->SetParamValue(time, param, value);
    }

    template <class Type>
    bool GetParamValue(const float time, const CAnimParamType& param, Type& value)
    {
        AZ_Assert(m_animNode, "Expected valid m_animNode");
        return m_animNode->GetParamValue(time, param, value);
    }

    // Check if it's a group node
    virtual bool IsGroupNode() const override;

    // Generate a new node name
    virtual QString GetAvailableNodeNameStartingWith(const QString& name) const;

    // Copy/Paste nodes
    virtual void CopyNodesToClipboard(const bool bOnlySelected, QWidget* context);
    virtual bool PasteNodesFromClipboard(QWidget* context);

    // Set new parent
    virtual void SetNewParent(CTrackViewAnimNode* pNewParent);

    // Check if this node may be moved to new parent
    virtual bool IsValidReparentingTo(CTrackViewAnimNode* pNewParent);

    int GetDefaultKeyTangentFlags() const { return m_animNode ? m_animNode->GetDefaultKeyTangentFlags() : SPLINE_KEY_TANGENT_UNIFIED; }
    
    void SetComponent(AZ::ComponentId componentId, const AZ::Uuid& componentTypeId);

    // returns the AZ::ComponentId of the component associated with this node if it is of type AnimNodeType::Component, InvalidComponentId otherwise
    AZ::ComponentId GetComponentId() const;

    // IAnimNodeOwner
    void MarkAsModified() override;
    // ~IAnimNodeOwner

    // Compares all of the node's track values at the given time with the associated property value and 
    //     sets a key at that time if they are different to match the latter
    // Returns the number of keys set
    int SetKeysForChangedTrackValues(float time) { return m_animNode->SetKeysForChangedTrackValues(time); }

    // returns true if this node is associated with an AnimNodeType::AzEntity node and contains a component with the given id
    bool ContainsComponentWithId(AZ::ComponentId componentId) const;

    //////////////////////////////////////////////////////////////////////////
    // AzToolsFramework::EditorEntityContextNotificationBus implementation
    void OnStartPlayInEditor() override;
    void OnStopPlayInEditor() override;
    //~AzToolsFramework::EditorEntityContextNotificationBus implementation

    //////////////////////////////////////////////////////////////////////////
    // AZ::EntityBus
    void OnEntityActivated(const AZ::EntityId& entityId) override;
    void OnEntityDestruction(const AZ::EntityId& entityId) override;
    //~AZ::EntityBus 

    //////////////////////////////////////////////////////////////////////////
    //! AZ::TransformNotificationBus::Handler
    void OnTransformChanged(const AZ::Transform& local, const AZ::Transform& world) override;
    void OnParentChanged(AZ::EntityId oldParent, AZ::EntityId newParent) override;
    void OnParentTransformWillChange(AZ::Transform oldTransform, AZ::Transform newTransform) override;
    //////////////////////////////////////////////////////////////////////////

    void OnEntityRemoved();

    // Creates a sub-node for the given component. Returns a pointer to the created component sub-node
    CTrackViewAnimNode* AddComponent(const AZ::Component* component, bool disabled);

    // Depth-first search for TrackViewAnimNode associated with the given animNode. Returns the first match found or nullptr if not found
    CTrackViewAnimNode* FindNodeByAnimNode(const IAnimNode* animNode);

protected:
    IAnimNode* GetAnimNode() { return m_animNode.get(); }

private:
    // Copy selected keys to XML representation for clipboard
    virtual void CopyKeysToClipboard(XmlNodeRef& xmlNode, const bool bOnlySelectedKeys, const bool bOnlyFromSelectedTracks) override;

    void CopyNodesToClipboardRec(CTrackViewAnimNode* pCurrentAnimNode, XmlNodeRef& xmlNode, const bool bOnlySelected);

    void PasteTracksFrom(XmlNodeRef& xmlNodeWithTracks);

    bool HasObsoleteTrackRec(CTrackViewNode* pCurrentNode) const;
    CTrackViewTrackBundle GetTracks(const bool bOnlySelected, const CAnimParamType& paramType) const;

    void PasteNodeFromClipboard(AZStd::map<int, IAnimNode*>& copiedIdToNodeMap, XmlNodeRef xmlNode);

    void SetPosRotScaleTracksDefaultValues(bool positionAllowed = true, bool rotationAllowed = true, bool scaleAllowed = true);

    void UpdateTrackGizmo();

    bool CheckTrackAnimated(const CAnimParamType& paramType) const;

    // IAnimNodeOwner
    void OnNodeVisibilityChanged(IAnimNode* pNode, const bool bHidden) override;
    void OnNodeReset(IAnimNode* pNode) override;
    // ~IAnimNodeOwner

    // ITransformDelegate
    void MatrixInvalidated() override;

    Vec3 GetTransformDelegatePos(const Vec3& realPos) const override;
    Quat GetTransformDelegateRotation(const Quat& realRotation) const override;
    Vec3 GetTransformDelegateScale(const Vec3& realScale) const override;

    void SetTransformDelegatePos(const Vec3& position) override;
    void SetTransformDelegateRotation(const Quat& rotation) override;
    void SetTransformDelegateScale(const Vec3& scale) override;

    // If those return true the base object uses its own transform instead
    bool IsPositionDelegated() const override;
    bool IsRotationDelegated() const override;
    bool IsScaleDelegated() const override;
    // ~ITransformDelegate

    // Helper for Is<Position/Rotation/Scale>Delegated to call internally
    bool IsTransformAnimParamTypeDelegated(AnimParamType animParamType) const;

    // EntitySelectionEvents
    void OnSelected() override;
    void OnDeselected() override;

    void OnSelectionChanged(bool selected);

    void UpdateKeyDataAfterParentChanged(const AZ::Transform& oldParentWorldTM, const AZ::Transform& newParentWorldTM);

    // Used to track Editor object listener registration
    void RegisterEditorObjectListeners(AZ::EntityId entityId);
    void UnRegisterEditorObjectListeners();

    // Helper functions
    static void RemoveChildNode(CTrackViewAnimNode* child);
    static AZ::Transform GetEntityWorldTM(AZ::EntityId entityId);
    static void SetParentsInChildren(CTrackViewAnimNode* currentNode);

    IAnimSequence* m_animSequence;
    AZStd::intrusive_ptr<IAnimNode> m_animNode;
    AZ::EntityId m_nodeEntityId;
    AZStd::unique_ptr<IAnimNodeAnimator> m_pNodeAnimator;
    _smart_ptr<CGizmo> m_trackGizmo;

    // used to stash the Editor sequence and node entity Ids when we switch to game mode from the editor
    AZ::EntityId    m_stashedAnimNodeEditorAzEntityId;
    AZ::EntityId    m_stashedAnimSequenceEditorAzEntityId;

    // Used to track Editor object listener registration
    AZ::EntityId m_entityIdListenerRegistered;

    // used to return a const reference to a null Uuid
    static const AZ::Uuid s_nullUuid;
};
