/*
 *
 *    Copyright (c) 2022 Project CHIP Authors
 *
 *    Licensed under the Apache License, Version 2.0 (the "License");
 *    you may not use this file except in compliance with the License.
 *    You may obtain a copy of the License at
 *
 *        http://www.apache.org/licenses/LICENSE-2.0
 *
 *    Unless required by applicable law or agreed to in writing, software
 *    distributed under the License is distributed on an "AS IS" BASIS,
 *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *    See the License for the specific language governing permissions and
 *    limitations under the License.
 */
#pragma once

#include <app-common/zap-generated/cluster-objects.h>
#include <app/clusters/scenes-server/ExtensionFieldSets.h>
#include <app/storage/TableEntry.h>
#include <lib/support/CHIPMemString.h>
#include <lib/support/IntrusiveList.h>
#include <lib/support/PersistentData.h>
#include <lib/support/Span.h>

namespace chip {
namespace scenes {

// Storage index for scenes in nvm
typedef app::Storage::Data::EntryIndex SceneIndex;

typedef uint32_t TransitionTimeMs;
typedef uint32_t SceneTransitionTime;

inline constexpr GroupId kGlobalGroupSceneId = 0x0000;
inline constexpr SceneId kUndefinedSceneId   = 0xff;

static constexpr size_t kSceneNameMaxLength      = CHIP_CONFIG_SCENES_CLUSTER_MAXIMUM_NAME_LENGTH;
static constexpr size_t kScenesMaxTransitionTime = 60'000'000u;

/// @brief SceneHandlers are meant as interface between various clusters and the Scene table.
/// When a scene command involving extension field sets is received, the Scene Table will go through
/// the list of handlers to either retrieve, populate or apply those extension field sets.
///
/// Generally, for each specific <endpoint, cluster> pair there should be one and only one handler
/// registered with the scene table that claims to handle that pair.
///
/// A SceneHandler can handle a single <endpoint, cluster> pair, or many such pairs.
///
/// @note If more than one handler claims to handle a specific <endpoint, cluster> pair, only one of
/// those handlers will get called when executing actions related to extension field sets on the scene
/// table.  It is not defined which handler will be selected.

class SceneHandler : public IntrusiveListNodeBase<>
{
public:
    SceneHandler(){};
    virtual ~SceneHandler() = default;

    /// @brief Copies the list of supported clusters for an endpoint in a Span and resizes the span to fit the actual number of
    /// supported clusters
    /// @param endpoint target endpoint
    /// @param clusterBuffer Buffer to hold the supported cluster IDs, cannot hold more than
    /// CHIP_CONFIG_SCENES_MAX_CLUSTERS_PER_SCENE. The function shall use the reduce_size() method in the event it is supporting
    /// less than CHIP_CONFIG_SCENES_MAX_CLUSTERS_PER_SCENE clusters.
    virtual void GetSupportedClusters(EndpointId endpoint, Span<ClusterId> & clusterBuffer) = 0;

    /// @brief Returns whether or not a cluster for scenes is supported on an endpoint
    ///
    /// @param endpoint Target Endpoint ID
    /// @param cluster Target Cluster ID
    /// @return true if supported, false if not supported
    virtual bool SupportsCluster(EndpointId endpoint, ClusterId cluster) = 0;

    /// @brief Called when handling AddScene.  Allows the handler to filter through the clusters in the command to serialize only
    /// the supported ones.
    ///
    /// @param endpoint[in] Endpoint ID
    /// @param extensionFieldSet[in] ExtensionFieldSetStruct provided by the AddScene Command, pre initialized
    /// @param serialisedBytes[out] Buffer to fill from the ExtensionFieldSetStruct in command
    /// @return CHIP_NO_ERROR if successful, CHIP_ERROR value otherwise
    /// @note Only gets called after the scene-cluster has previously verified that the endpoint,cluster pair is supported by
    /// the handler. It is therefore the implementation's reponsibility to also implement the SupportsCluster method.
    virtual CHIP_ERROR
    SerializeAdd(EndpointId endpoint,
                 const app::Clusters::ScenesManagement::Structs::ExtensionFieldSetStruct::DecodableType & extensionFieldSet,
                 MutableByteSpan & serialisedBytes) = 0;

    /// @brief Called when handling StoreScene, and only if the handler supports the given endpoint and cluster.
    ///
    /// The implementation must write the actual scene data to store to serializedBytes as described below.
    ///
    /// @param endpoint[in] Target Endpoint
    /// @param cluster[in] Target Cluster
    /// @param serializedBytes[out] Output buffer, data needs to be writen in there and size adjusted to the size of the data
    /// written.
    ///
    /// @return CHIP_NO_ERROR if successful, CHIP_ERROR value otherwise
    virtual CHIP_ERROR SerializeSave(EndpointId endpoint, ClusterId cluster, MutableByteSpan & serializedBytes) = 0;

    /// @brief Deserialize an ExtensionFieldSetStruct into a cluster object (e.g. when handling ViewScene).
    ///
    /// @param endpoint[in] Endpoint ID
    /// @param cluster[in] Cluster ID
    /// @param serializedBytes[in] ExtensionFieldSetStruct stored in NVM
    ///
    /// @param extensionFieldSet[out] ExtensionFieldSetStruct in command format
    /// @return CHIP_NO_ERROR if successful, CHIP_ERROR value otherwise
    /// @note Only gets called for handlers for which SupportsCluster() is true for the given endpoint and cluster.
    virtual CHIP_ERROR Deserialize(EndpointId endpoint, ClusterId cluster, const ByteSpan & serializedBytes,

                                   app::Clusters::ScenesManagement::Structs::ExtensionFieldSetStruct::Type & extensionFieldSet) = 0;

    /// @brief Restore a stored scene for the given cluster instance, over timeMs milliseconds (e.g. when handling RecallScene)
    ///
    /// @param endpoint[in] Endpoint ID
    /// @param cluster[in] Cluster ID
    /// @param serializedBytes[in] ExtensionFieldSetStruct stored in NVM
    ///
    /// @param timeMs[in] Transition time in ms to apply the scene
    /// @return CHIP_NO_ERROR if successful, CHIP_ERROR value otherwise
    /// @note Only gets called for handlers for which SupportsCluster() is true for the given endpoint and cluster.
    virtual CHIP_ERROR ApplyScene(EndpointId endpoint, ClusterId cluster, const ByteSpan & serializedBytes,
                                  TransitionTimeMs timeMs) = 0;
};

template <class EFStype>
class SceneTable
{
public:
    /// @brief struct used to identify a scene in storage by 3 ids, endpoint, group and scene
    struct SceneStorageId
    {
        // Identifies group within the scope of the given fabric
        GroupId mGroupId = kGlobalGroupSceneId;
        SceneId mSceneId = kUndefinedSceneId;

        SceneStorageId() = default;
        SceneStorageId(SceneId id, GroupId groupId = kGlobalGroupSceneId) : mGroupId(groupId), mSceneId(id) {}

        void Clear()
        {
            mGroupId = kGlobalGroupSceneId;
            mSceneId = kUndefinedSceneId;
        }

        bool IsValid() { return (mSceneId != kUndefinedSceneId); }

        bool operator==(const SceneStorageId & other) const { return (mGroupId == other.mGroupId && mSceneId == other.mSceneId); }
    };

    /// @brief struct used to store data held in a scene
    /// Members:
    /// mName: char buffer holding the name of the scene, only serialized when mNameLenght is greater than 0
    /// mNameLength: lentgh of the name if a name was provided at scene creation
    /// mSceneTransitionTimeSeconds: Time in seconds it will take a cluster to change to the scene
    /// mExtensionFieldSets: class holding the different field sets of each cluster values to store with the scene
    /// mTransitionTime100ms: Transition time in tenths of a second, allows for more precise transition when combiened with
    /// mSceneTransitionTimeSeconds in enhanced scene commands
    struct SceneData
    {
        char mName[kSceneNameMaxLength]            = { 0 };
        size_t mNameLength                         = 0;
        SceneTransitionTime mSceneTransitionTimeMs = 0;
        EFStype mExtensionFieldSets;

        SceneData(const CharSpan & sceneName = CharSpan(), SceneTransitionTime time = 0) : mSceneTransitionTimeMs(time)
        {
            SetName(sceneName);
        }
        SceneData(EFStype fields, const CharSpan & sceneName = CharSpan(), SceneTransitionTime time = 0) :
            mSceneTransitionTimeMs(time)
        {
            SetName(sceneName);

            mExtensionFieldSets = fields;
        }
        SceneData(const SceneData & other) : mSceneTransitionTimeMs(other.mSceneTransitionTimeMs)
        {
            SetName(CharSpan(other.mName, other.mNameLength));

            mExtensionFieldSets = other.mExtensionFieldSets;
        }
        ~SceneData(){};

        bool operator==(const SceneData & other) const
        {
            return ((CharSpan(mName, mNameLength).data_equal(CharSpan(other.mName, other.mNameLength))) &&
                    (mSceneTransitionTimeMs == other.mSceneTransitionTimeMs) && (mExtensionFieldSets == other.mExtensionFieldSets));
        }

        void SetName(const CharSpan & sceneName)
        {
            if (nullptr == sceneName.data())
            {
                mName[0]    = 0;
                mNameLength = 0;
            }
            else
            {
                size_t maxChars = std::min(sceneName.size(), kSceneNameMaxLength);
                memcpy(mName, sceneName.data(), maxChars);
                mNameLength = maxChars;
            }
        }

        void Clear()
        {
            SetName(CharSpan());
            mSceneTransitionTimeMs = 0;
            mExtensionFieldSets.Clear();
        }

        void operator=(const SceneData & other)
        {
            SetName(CharSpan(other.mName, other.mNameLength));
            mExtensionFieldSets    = other.mExtensionFieldSets;
            mSceneTransitionTimeMs = other.mSceneTransitionTimeMs;
        }
    };

    using SceneTableEntry = app::Storage::Data::TableEntry<SceneStorageId, SceneData>;

    SceneTable(){};

    virtual ~SceneTable(){};

    // Not copyable
    SceneTable(const SceneTable &) = delete;

    SceneTable & operator=(const SceneTable &) = delete;

    virtual CHIP_ERROR Init(PersistentStorageDelegate & storage) = 0;
    virtual void Finish()                                        = 0;

    // Global scene count
    virtual CHIP_ERROR GetEndpointSceneCount(uint8_t & scene_count)                         = 0;
    virtual CHIP_ERROR GetFabricSceneCount(FabricIndex fabric_index, uint8_t & scene_count) = 0;

    // Data
    virtual CHIP_ERROR GetRemainingCapacity(FabricIndex fabric_index, uint8_t & capacity)                                   = 0;
    virtual CHIP_ERROR SetSceneTableEntry(FabricIndex fabric_index, const SceneTableEntry & entry)                          = 0;
    virtual CHIP_ERROR GetSceneTableEntry(FabricIndex fabric_index, SceneStorageId scene_id, SceneTableEntry & entry)       = 0;
    virtual CHIP_ERROR RemoveSceneTableEntry(FabricIndex fabric_index, SceneStorageId scene_id)                             = 0;
    virtual CHIP_ERROR RemoveSceneTableEntryAtPosition(EndpointId endpoint, FabricIndex fabric_index, SceneIndex scene_idx) = 0;

    // Groups
    virtual CHIP_ERROR GetAllSceneIdsInGroup(FabricIndex fabric_index, GroupId group_id, Span<SceneId> & scene_list) = 0;
    virtual CHIP_ERROR DeleteAllScenesInGroup(FabricIndex fabric_index, GroupId group_id)                            = 0;

    // SceneHandlers
    virtual void RegisterHandler(SceneHandler * handler)   = 0;
    virtual void UnregisterHandler(SceneHandler * handler) = 0;
    virtual void UnregisterAllHandlers()                   = 0;

    // Extension field sets operation
    virtual CHIP_ERROR SceneSaveEFS(SceneTableEntry & scene)        = 0;
    virtual CHIP_ERROR SceneApplyEFS(const SceneTableEntry & scene) = 0;

    // Fabrics

    /**
     * @brief Removes all scenes associated with a fabric index and the stored FabricSceneData that maps them
     * @param fabric_index Fabric index to remove
     * @return CHIP_ERROR, CHIP_NO_ERROR if successful or if the Fabric was not found, specific CHIP_ERROR otherwise
     * @note This function is meant to be used after a fabric is removed from the device, the implementation MUST ensure that it
     * won't interact with the actual fabric table as it will be removed beforehand.
     */
    virtual CHIP_ERROR RemoveFabric(FabricIndex fabric_index) = 0;
    virtual CHIP_ERROR RemoveEndpoint()                       = 0;

    // Handlers
    virtual bool HandlerListEmpty() { return mHandlerList.Empty(); }

    IntrusiveList<SceneHandler> mHandlerList;
};

} // namespace scenes
} // namespace chip
