/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#pragma once

#include "IPostEffectGroup.h"
#include "ISplines.h"
#include <AzCore/std/containers/unordered_map.h>
#include <AzCore/Asset/AssetCommon.h>
#include <AzFramework/Asset/AssetCatalogBus.h>

class PostEffectGroup : public IPostEffectGroup
{
public:
    // PostEffectGroups are assets, but have no handler yet.  For now, we will use this UUID to refer to them.
    AZ_TYPE_INFO(PostEffectGroup, "{BDDCFCE8-6E4E-4816-AE1C-ED98B02DA75D}"); 

    // Priority to assign to any group. Note that a higher value relates to a higher priority.
    enum GroupPriority
    {
        kPriorityDefault = 0, // Used for the default effect settings.
        kPriorityBase    = 1, // Base group edited by user/code. Should always be higher priority than kDefalt.
        // Add any future priorities here.
    };

    PostEffectGroup(class PostEffectGroupManager* manager, const char* name, GroupPriority priority, bool hold, float fadeDistance);

    const char* GetName() const override { return m_name.c_str(); }
    void SetEnable(bool enable) override;
    bool GetEnable() const override { return m_enable; }
    unsigned int GetPriority() const override { return m_priority; }
    bool GetHold() const override { return m_hold; }
    float GetFadeDistance() const override { return m_fadeDistance; }
    void SetParam(const char* name, const PostEffectGroupParam& value) override;
    PostEffectGroupParam* GetParam(const char* name) override { return &m_params[name]; }
    void ClearParams() override;
    void ApplyAtPosition(const Vec3& position) override;
    ISplineInterpolator* GetBlendIn() { return &m_blendIn; }
    ISplineInterpolator* GetBlendOut() { return &m_blendOut; }
    uint32 GetLastUpdateFrame() const { return m_lastUpdateFrame; }
    void BlendWith(AZStd::unordered_map<AZStd::string, PostEffectGroupParam>& paramMap);
    void ResetStrength() { m_strength = 0.f; }
    AZ::Data::AssetId GetAssetId() const { return m_id; }
    //  Functions for changing an effect that has already been loaded. Not in the interface because they are intended to be used internally during hot reloads in the editor.
    void StopEffect();
    void ClearSplines();
    void SetPriority(GroupPriority priority) { m_priority = priority; }
    void SetHold(bool hold) { m_hold = hold; }
    void SetFadeDistance(float fadeDistance) { m_fadeDistance = fadeDistance; }

private:
    class BlendSpline : public spline::CBaseSplineInterpolator < float, spline::BezierSpline<float> >
    {
    public:
        float GetKeyRangeEnd()
        {
            return empty() ? 0.f : GetKeyTime(num_keys() - 1);
        }

        void Clear()
        {
            RemoveKeysInRange(0.0f, GetKeyRangeEnd());
        }

        void SerializeSpline(XmlNodeRef &node, bool bLoading){}
    };

    class PostEffectGroupManager* m_manager;
    AZStd::string m_name;
    bool m_enable;
    GroupPriority m_priority;
    bool m_hold;
    float m_fadeDistance;
    AZStd::unordered_map<AZStd::string, PostEffectGroupParam> m_params;
    BlendSpline m_blendIn;
    BlendSpline m_blendOut;
    uint32 m_lastUpdateFrame;
    float m_enableDuration;
    float m_disableDuration;
    float m_strength;
    AZ::Data::AssetId m_id;
};

class PostEffectGroupManager
    : public IPostEffectGroupManager
    , public ISyncMainWithRenderListener
    , private AzFramework::AssetCatalogEventBus::Handler
{
public:
    PostEffectGroupManager();
    ~PostEffectGroupManager() override;

    IPostEffectGroup* GetGroup(const char* name) override;
    IPostEffectGroup* GetGroup(const unsigned int index) override;
    const unsigned int GetGroupCount() override;
    void SyncMainWithRender() override;
    void BlendWithParameterCache() override;
    void ClearParameterCache();
    void Sort();

    // Returns a list of PostEFfectGroups that had their enabled state changed this frame
    const PostEffectGroupList& GetGroupsToggledThisFrame() override;
    void SetGroupToggledThisFrame( IPostEffectGroup* group );
private:
    //  Sending in a PostEffectGroup* to LoadGroup will cause a load in place.
    //  Sending in a nullptr to LoadGroup will construct a new PostEffectGroup.
    IPostEffectGroup* LoadGroup(const char* name, PostEffectGroup* groupToLoadInto = nullptr);
    // =============== AssetCatalogEventBus Functions ==============
    void OnCatalogAssetChanged(const AZ::Data::AssetId& assetId) override;

    std::vector<std::unique_ptr<PostEffectGroup>> m_groups;
    AZStd::unordered_map<AZStd::string, PostEffectGroupParam> m_paramCache;

    // A list of PostEffectGroups that had their Enabled state changed this frame
    // Double buffered for render thread/main thread.
    PostEffectGroupList m_groupsToggledThisFrame[2];

    // The renderer version of the fill/process thread IDs is not available to us here.
    unsigned int m_fillThreadIndex = 0; 
};