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

/**
 *    @file
 *      DeviceControllerFactory is a singleton utility class that manages the
 *      runtime DeviceControllerSystemState and provides APIs to setup DeviceControllers
 *      and DeviceCommissioners.
 *
 *      Together with the SystemState this class implicitly manages the lifecycle of the underlying
 *      CHIP stack. It lazily initializes the CHIPStack when setting up Controllers if the SystemState
 *      was previously shutdown.
 */

#pragma once

#include <controller/CHIPDeviceController.h>
#include <controller/CHIPDeviceControllerSystemState.h>
#include <credentials/GroupDataProvider.h>
#include <credentials/OperationalCertificateStore.h>
#include <credentials/attestation_verifier/DeviceAttestationVerifier.h>
#include <protocols/secure_channel/SessionResumptionStorage.h>

namespace chip {

namespace Controller {

struct SetupParams
{
    OperationalCredentialsDelegate * operationalCredentialsDelegate = nullptr;

    /* The following keypair must correspond to the public key used for generating
    controllerNOC. It's used by controller to establish CASE sessions with devices */
    Crypto::P256Keypair * operationalKeypair = nullptr;

    /**
     * Controls whether or not the operationalKeypair should be owned by the
     * caller.  By default, this is false, but if the keypair cannot be
     * serialized, then setting this to true will allow the caller to manage
     * this keypair's lifecycle.
     */
    bool hasExternallyOwnedOperationalKeypair = false;

    /* The following certificates must be in x509 DER format */
    ByteSpan controllerNOC;
    ByteSpan controllerICAC;
    ByteSpan controllerRCAC;

    //
    // This must be set to a valid, operational VendorId value associated with
    // the controller/commissioner.
    //
    chip::VendorId controllerVendorId = VendorId::Unspecified;

    // The Device Pairing Delegated used to initialize a Commissioner
    DevicePairingDelegate * pairingDelegate = nullptr;

    /**
     * Controls whether we permit multiple DeviceController instances to exist
     * on the same logical fabric (identified by the tuple of the fabric's
     * root public key + fabric id).
     *
     * Each controller instance will be associated with its own FabricIndex.
     * This pivots the FabricTable to tracking identities instead of fabrics,
     * represented by FabricInfo instances that can have colliding logical fabrics.
     *
     */
    bool permitMultiControllerFabrics = false;

    //
    // Controls enabling server cluster interactions on a controller. This in turn
    // causes the following to get enabled:
    //
    //  - CASEServer to listen for unsolicited Sigma1 messages.
    //  - Advertisement of active controller operational identities.
    //
    bool enableServerInteractions = false;

    /**
     * Controls whether shutdown of the controller removes the corresponding
     * entry from the in-memory fabric table, but NOT from storage.
     *
     * Note that this means that after controller shutdown the storage and
     * in-memory versions of the fabric table will be out of sync.
     * For compatibility reasons this is the default behavior.
     *
     * @see deleteFromFabricTableOnShutdown
     */
    bool removeFromFabricTableOnShutdown = true;

    /**
     * Controls whether shutdown of the controller deletes the corresponding
     * entry from the fabric table (both in-memory and storage).
     *
     * If both `removeFromFabricTableOnShutdown` and this setting are true,
     * this setting will take precedence.
     *
     * @see removeFromFabricTableOnShutdown
     */
    bool deleteFromFabricTableOnShutdown = false;

    /**
     * Specifies whether to utilize the fabric table entry for the given FabricIndex
     * for initialization. If provided and neither the operational key pair nor the NOC
     * chain are provided, then attempt to locate a fabric corresponding to the given FabricIndex.
     */
    chip::Optional<FabricIndex> fabricIndex;

    Credentials::DeviceAttestationVerifier * deviceAttestationVerifier = nullptr;
    CommissioningDelegate * defaultCommissioner                        = nullptr;
};

// TODO everything other than the fabric storage, group data provider, OperationalKeystore,
// OperationalCertificateStore, SessionKeystore, and SessionResumptionStorage here should
// be removed. We're blocked because of the need to support !CHIP_DEVICE_LAYER
struct FactoryInitParams
{
    System::Layer * systemLayer                                        = nullptr;
    PersistentStorageDelegate * fabricIndependentStorage               = nullptr;
    Credentials::CertificateValidityPolicy * certificateValidityPolicy = nullptr;
    Credentials::GroupDataProvider * groupDataProvider                 = nullptr;
    app::reporting::ReportScheduler::TimerDelegate * timerDelegate     = nullptr;
    Crypto::SessionKeystore * sessionKeystore                          = nullptr;
    Inet::EndPointManager<Inet::TCPEndPoint> * tcpEndPointManager      = nullptr;
    Inet::EndPointManager<Inet::UDPEndPoint> * udpEndPointManager      = nullptr;
    FabricTable * fabricTable                                          = nullptr;
    Crypto::OperationalKeystore * operationalKeystore                  = nullptr;
    Credentials::OperationalCertificateStore * opCertStore             = nullptr;
    SessionResumptionStorage * sessionResumptionStorage                = nullptr;
#if CONFIG_NETWORK_LAYER_BLE
    Ble::BleLayer * bleLayer = nullptr;
#endif
#if CHIP_DEVICE_CONFIG_ENABLE_WIFIPAF
    Transport::WiFiPAFLayer * wifipaf_layer = nullptr;
#endif

    //
    // Controls enabling server cluster interactions on a controller. This in turn
    // causes the following to get enabled:
    //
    //  - Advertisement of active controller operational identities.
    //
    bool enableServerInteractions = false;

    /* The port used for operational communication to listen for and send messages over UDP/TCP.
     * The default value of `0` will pick any available port. */
    uint16_t listenPort = 0;
};

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

    CHIP_ERROR Init(FactoryInitParams params);

    // Shuts down matter and frees the system state.
    //
    // Must not be called while any controllers are alive, or while any calls
    // to RetainSystemState or EnsureAndRetainSystemState have not been balanced
    // by a call to ReleaseSystemState.
    void Shutdown();

    CHIP_ERROR SetupController(SetupParams params, DeviceController & controller);
    CHIP_ERROR SetupCommissioner(SetupParams params, DeviceCommissioner & commissioner);

    // ----- IO -----
    /**
     * @brief
     * Start the event loop task within the CHIP stack
     * @return CHIP_ERROR   The return status
     */
    CHIP_ERROR ServiceEvents();

    ~DeviceControllerFactory();
    DeviceControllerFactory(DeviceControllerFactory const &) = delete;
    void operator=(DeviceControllerFactory const &)          = delete;

    //
    // Some clients do not prefer a complete shutdown of the stack being initiated if
    // all device controllers have ceased to exist. To avoid that, this method has been
    // created to permit retention of the underlying system state.
    //
    // Calls to this method must be balanced by calling ReleaseSystemState before Shutdown.
    //
    void RetainSystemState();

    //
    // To initiate shutdown of the stack upon termination of all resident controllers in the
    // system, invoke this method to decrement the refcount on the system state and consequently,
    // shut-down the stack.
    //
    // This should only be invoked if a matching call to RetainSystemState() was called prior.
    //
    // Returns true if stack was shut down in response to this call, or false otherwise.
    //
    bool ReleaseSystemState();

    // Like RetainSystemState(), but will re-initialize the system state first if necessary.
    // Calls to this method must be balanced by calling ReleaseSystemState before Shutdown.
    CHIP_ERROR EnsureAndRetainSystemState();

    //
    // Retrieve a read-only pointer to the system state object that contains pointers to key stack
    // singletons. If the pointer is null, it indicates that the DeviceControllerFactory has yet to
    // be initialized properly, or has already been shut-down.
    //
    // This pointer ceases to be valid after a call to Shutdown has been made, or if all active
    // DeviceController instances have gone to 0. Consequently, care has to be taken to correctly
    // sequence the shutting down of active controllers with any entity that interacts with objects
    // present in the system state object. If de-coupling is desired, RetainSystemState and
    // ReleaseSystemState can be used to avoid this.
    //
    const DeviceControllerSystemState * GetSystemState() const { return mSystemState; }

    class ControllerFabricDelegate final : public chip::FabricTable::Delegate
    {
    public:
        CHIP_ERROR Init(SessionResumptionStorage * sessionResumptionStorage, Credentials::GroupDataProvider * groupDataProvider)
        {
            VerifyOrReturnError(sessionResumptionStorage != nullptr, CHIP_ERROR_INVALID_ARGUMENT);
            VerifyOrReturnError(groupDataProvider != nullptr, CHIP_ERROR_INVALID_ARGUMENT);

            mSessionResumptionStorage = sessionResumptionStorage;
            mGroupDataProvider        = groupDataProvider;
            return CHIP_NO_ERROR;
        };

        void OnFabricRemoved(const chip::FabricTable & fabricTable, FabricIndex fabricIndex) override
        {
            (void) fabricTable;
            if (mGroupDataProvider != nullptr)
            {
                mGroupDataProvider->RemoveFabric(fabricIndex);
            }
            ClearCASEResumptionStateOnFabricChange(fabricIndex);
        };

        void OnFabricUpdated(const chip::FabricTable & fabricTable, chip::FabricIndex fabricIndex) override
        {
            (void) fabricTable;
            ClearCASEResumptionStateOnFabricChange(fabricIndex);
        }

    private:
        void ClearCASEResumptionStateOnFabricChange(chip::FabricIndex fabricIndex)
        {
            VerifyOrReturn(mSessionResumptionStorage != nullptr);
            CHIP_ERROR err = mSessionResumptionStorage->DeleteAll(fabricIndex);
            if (err != CHIP_NO_ERROR)
            {
                ChipLogError(Controller,
                             "Warning, failed to delete session resumption state for fabric index 0x%x: %" CHIP_ERROR_FORMAT,
                             static_cast<unsigned>(fabricIndex), err.Format());
            }
        }

        Credentials::GroupDataProvider * mGroupDataProvider  = nullptr;
        SessionResumptionStorage * mSessionResumptionStorage = nullptr;
    };

private:
    DeviceControllerFactory() {}
    void PopulateInitParams(ControllerInitParams & controllerParams, const SetupParams & params);
    CHIP_ERROR InitSystemState(FactoryInitParams params);
    CHIP_ERROR ReinitSystemStateIfNecessary();
    void ControllerInitialized(const DeviceController & controller);

    uint16_t mListenPort;
    DeviceControllerSystemState * mSystemState                          = nullptr;
    PersistentStorageDelegate * mFabricIndependentStorage               = nullptr;
    Crypto::OperationalKeystore * mOperationalKeystore                  = nullptr;
    Credentials::OperationalCertificateStore * mOpCertStore             = nullptr;
    Credentials::CertificateValidityPolicy * mCertificateValidityPolicy = nullptr;
    SessionResumptionStorage * mSessionResumptionStorage                = nullptr;
    bool mEnableServerInteractions                                      = false;
};

} // namespace Controller
} // namespace chip
