/*
 *
 *    Copyright (c) 2021 Project CHIP Authors
 *    All rights reserved.
 *
 *    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.
 */

/**
 * @brief classes relating to Content App platform of the Video Player.
 */

#pragma once

#include <app/OperationalSessionSetup.h>
#include <app/app-platform/ContentApp.h>
#include <app/util/attribute-storage.h>
#include <controller/CHIPCluster.h>
#include <platform/CHIPDeviceLayer.h>
#include <protocols/interaction_model/StatusCode.h>
#include <protocols/user_directed_commissioning/UserDirectedCommissioning.h>

using chip::app::Clusters::ApplicationBasic::CatalogVendorApp;
using chip::Controller::CommandResponseFailureCallback;
using chip::Controller::CommandResponseSuccessCallback;

using BindingListType = chip::app::Clusters::Binding::Attributes::Binding::TypeInfo::Type;

namespace chip {
namespace AppPlatform {

// The AppPlatform overrides emberAfExternalAttributeReadCallback to handle external attribute reads for ContentApps.
// This callback can be used to handle external attribute reads for attributes belonging to static endpoints.
Protocols::InteractionModel::Status AppPlatformExternalAttributeReadCallback(EndpointId endpoint, ClusterId clusterId,
                                                                             const EmberAfAttributeMetadata * attributeMetadata,
                                                                             uint8_t * buffer, uint16_t maxReadLength);

// The AppPlatform overrides emberAfExternalAttributeWriteCallback to handle external attribute writes for ContentApps.
// This callback can be used to handle external attribute writes for attributes belonging to static endpoints.
Protocols::InteractionModel::Status AppPlatformExternalAttributeWriteCallback(EndpointId endpoint, ClusterId clusterId,
                                                                              const EmberAfAttributeMetadata * attributeMetadata,
                                                                              uint8_t * buffer);

inline constexpr EndpointId kTargetBindingClusterEndpointId = 0;
inline constexpr EndpointId kLocalVideoPlayerEndpointId     = 1;
inline constexpr EndpointId kLocalSpeakerEndpointId         = 2;

class DLL_EXPORT ContentAppFactory
{
public:
    virtual ~ContentAppFactory() = default;

    // Lookup CatalogVendor App for this client (vendor id/product id client)
    // and then write it to destinationApp
    // return error if not found
    virtual CHIP_ERROR LookupCatalogVendorApp(uint16_t vendorId, uint16_t productId, CatalogVendorApp * destinationApp) = 0;

    // Lookup ContentApp for this catalog id / app id and load it
    virtual ContentApp * LoadContentApp(const CatalogVendorApp & vendorApp) = 0;

    // Gets the catalog vendor ID used by this platform
    virtual uint16_t GetPlatformCatalogVendorId() = 0;

    // Converts application (any catalog) into the platform's catalog Vendor
    // and then writes it to destinationApp
    virtual CHIP_ERROR ConvertToPlatformCatalogVendorApp(const CatalogVendorApp & sourceApp, CatalogVendorApp * destinationApp) = 0;

    // Get the privilege this vendorId should have on endpoints 1, 2, and content app endpoints
    // In the case of casting video clients, this should usually be Access::Privilege::kOperate
    // and for voice agents, this may be Access::Privilege::kAdminister
    // When a vendor has admin privileges, it will get access to all clusters on ep1
    virtual Access::Privilege GetVendorPrivilege(uint16_t vendorId) = 0;

    // Get the cluster list this vendorId/productId should have on static endpoints such as ep1 for casting video clients.
    // When a vendor has admin privileges, it will get access to all clusters on ep1
    virtual std::list<ClusterId> GetAllowedClusterListForStaticEndpoint(EndpointId endpointId, uint16_t vendorId,
                                                                        uint16_t productId) = 0;
};

class DLL_EXPORT ContentAppPlatform
{
public:
    static ContentAppPlatform & GetInstance()
    {
        static ContentAppPlatform instance;
        return instance;
    }

    void SetupAppPlatform();

    inline void SetContentAppFactory(ContentAppFactory * factory) { mContentAppFactory = factory; };

    // add apps to the platform.
    // This will assign the app to an endpoint (if it is not already added) and make it accessible via Matter
    // returns the global endpoint for this app, or kNoCurrentEndpointId if an error occurred.
    // dataVersionStorage.size() needs to be at least as big as the number of
    // server clusters in the EmberAfEndpointType passed in.
    EndpointId AddContentApp(ContentApp * app, EmberAfEndpointType * ep, const Span<DataVersion> & dataVersionStorage,
                             const Span<const EmberAfDeviceType> & deviceTypeList);

    // add apps to the platform.
    // This will assign the app to the desiredEndpointId (if it is not already used)
    // and make it accessible via Matter, return the global endpoint for this app(if app is already added)
    // , or kNoCurrentEndpointId if an error occurred. desiredEndpointId cannot be less that Fixed endpoint count
    // dataVersionStorage.size() needs to be at least as big as the number of
    // server clusters in the EmberAfEndpointType passed in.
    EndpointId AddContentApp(ContentApp * app, EmberAfEndpointType * ep, const Span<DataVersion> & dataVersionStorage,
                             const Span<const EmberAfDeviceType> & deviceTypeList, EndpointId desiredEndpointId);

    // remove app from the platform.
    // returns the endpoint id where the app was, or 0 if app was not loaded
    EndpointId RemoveContentApp(ContentApp * app);

    // load and unload by vendor id
    // void UnloadContentAppByVendorId(uint16_t vendorId, uint16_t productId);

    // Lookup ContentApp for this client (vendor id/product id client) and load it
    ContentApp * LoadContentAppByClient(uint16_t vendorId, uint16_t productId);

    // Lookup ContentApp described by this application and load it
    ContentApp * LoadContentApp(const CatalogVendorApp & application);

    // helpful method to get a Content App by endpoint in order to perform attribute or command ops
    ContentApp * GetContentApp(EndpointId id);

    // helpful method to get a Content App by application, does not load if not found
    ContentApp * GetContentApp(const CatalogVendorApp & application);

    // sets the current app for this platform
    void SetCurrentApp(ContentApp * app);

    // returns true if there is a current app for this platform
    inline bool HasCurrentApp() { return mCurrentAppEndpointId != kNoCurrentEndpointId; }

    // returns true if the vendor/app arguments are the current app
    bool IsCurrentApp(ContentApp * app);

    // returns the current app endpoint
    inline EndpointId GetCurrentAppEndpointId() { return mCurrentAppEndpointId; };

    // unset this as current app, if it is current app
    void UnsetIfCurrentApp(ContentApp * app);

    // loads content app identified by vid/pid of client and calls HandleGetSetupPasscode.
    // Returns 0 if passcode cannot be obtained.
    uint32_t GetPasscodeFromContentApp(uint16_t vendorId, uint16_t productId, CharSpan rotatingId);

    // locates app identified by target info and confirms that it grants access to given vid/pid of client,
    // loads given app and calls HandleGetSetupPasscode. Sets passcode to 0 if it cannot be obtained.
    // return true if a matching app was found (and it granted this client access), even if a passcode was not obtained
    bool HasTargetContentApp(uint16_t vendorId, uint16_t productId, CharSpan rotatingId,
                             Protocols::UserDirectedCommissioning::TargetAppInfo & info, uint32_t & passcode);

    // returns set of connected nodes for a given content app
    std::set<NodeId> GetNodeIdsForContentApp(uint16_t vendorId, uint16_t productId);

    // returns set of connected nodes for a given allowed vendor id
    std::set<NodeId> GetNodeIdsForAllowedVendorId(uint16_t vendorId);

    // store node id for content app after commissioning
    // node id can be used later on to update ACL
    // in case app is not installed
    // Note: This is in memory storing, the values are deleted after reboot
    void StoreNodeIdForContentApp(uint16_t vendorId, uint16_t productId, NodeId nodeId);

    /**
     * @brief
     *   Add ACLs on this device for the given client,
     *   and create bindings on the given client so that it knows what it has access to.
     *
     * The default implementation follows the device library Video Player Architecture spec
     * for a typical video player given assumptions like video player endpoint id is 1 and
     * speaker endpoint id is 2. Some devices may need to override this implementation when
     * these assumptions are not correct.
     *
     * @param[in] exchangeMgr     Exchange manager to be used to get an exchange context.
     * @param[in] sessionHandle   Reference to an established session.
     * @param[in] targetVendorId  Vendor ID for the target device.
     * @param[in] targetProductId Product ID for the target device.
     * @param[in] localNodeId     The NodeId for the local device.
     * @param[in] rotatingId      The rotating account ID to handle account login.
     * @param[in] passcode        The passcode to handle account login.
     * @param[in] bindings        Any additional bindings to include. This may include current bindings.
     * @param[in] successCb       The function to be called on success of adding the binding.
     * @param[in] failureCb       The function to be called on failure of adding the binding.
     *
     * @return CHIP_ERROR         CHIP_NO_ERROR on success, or corresponding error
     */
    CHIP_ERROR ManageClientAccess(Messaging::ExchangeManager & exchangeMgr, SessionHandle & sessionHandle, uint16_t targetVendorId,
                                  uint16_t targetProductId, NodeId localNodeId, chip::CharSpan rotatingId, uint32_t passcode,
                                  std::vector<app::Clusters::Binding::Structs::TargetStruct::Type> bindings,
                                  Controller::WriteResponseSuccessCallback successCb,
                                  Controller::WriteResponseFailureCallback failureCb);

protected:
    // requires vendorApp to be in the catalog of the platform
    ContentApp * LoadContentAppInternal(const CatalogVendorApp & vendorApp);
    ContentApp * GetContentAppInternal(const CatalogVendorApp & vendorApp);
    CHIP_ERROR GetACLEntryIndex(size_t * foundIndex, FabricIndex fabricIndex, NodeId subjectNodeId);

    static const int kNoCurrentEndpointId = 0;
    EndpointId mCurrentAppEndpointId      = kNoCurrentEndpointId;

    ContentAppFactory * mContentAppFactory = nullptr;
    EndpointId mCurrentEndpointId;
    EndpointId mFirstDynamicEndpointId;
    ContentApp * mContentApps[CHIP_DEVICE_CONFIG_DYNAMIC_ENDPOINT_COUNT];
    // key is string -> vendorId:producTid
    std::map<std::string, std::set<NodeId>> mConnectedContentAppNodeIds;

private:
    void IncrementCurrentEndpointID();
};

} // namespace AppPlatform
} // namespace chip
