/*
 *
 *    Copyright (c) 2023 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.
 */

#pragma once

#include "operational-state-cluster-objects.h"
#include <app-common/zap-generated/cluster-objects.h>
#include <app/AttributeAccessInterface.h>
#include <app/CommandHandlerInterface.h>
#include <app/cluster-building-blocks/QuieterReporting.h>
#include <app/data-model/Nullable.h>

namespace chip {
namespace app {
namespace Clusters {
namespace OperationalState {

const uint8_t DerivedClusterNumberSpaceStart = 0x40;
const uint8_t VendorNumberSpaceStart         = 0x80;

class Uncopyable
{
protected:
    Uncopyable() {}
    ~Uncopyable() = default;

private:
    Uncopyable(const Uncopyable &)             = delete;
    Uncopyable & operator=(const Uncopyable &) = delete;
};

class Delegate;

/**
 * Instance is a class that represents an instance of a derivation of the operational state cluster.
 * It implements CommandHandlerInterface so it can generically handle commands for any derivation cluster id.
 */
class Instance : public CommandHandlerInterface, public AttributeAccessInterface, public Uncopyable
{
public:
    /**
     * Creates an operational state cluster instance.
     * The Init() function needs to be called for this instance to be registered and called by the
     * interaction model at the appropriate times.
     * It is possible to set the CurrentPhase and OperationalState via the Set... methods before calling Init().
     * @param aDelegate A pointer to the delegate to be used by this server.
     * Note: the caller must ensure that the delegate lives throughout the instance's lifetime.
     * @param aEndpointId The endpoint on which this cluster exists. This must match the zap configuration.
     */
    Instance(Delegate * aDelegate, EndpointId aEndpointId);

    ~Instance() override;

    /**
     * Phase name's max length
     */
    static constexpr uint8_t kMaxPhaseNameLength = 64;

    /**
     * Initialise the operational state server instance.
     * This function must be called after defining an Instance class object.
     * @return Returns an error if the given endpoint and cluster ID have not been enabled in zap or if the
     * CommandHandler or AttributeHandler registration fails, else returns CHIP_NO_ERROR.
     */
    CHIP_ERROR Init();

    // Attribute setters
    /**
     * Set operational phase.
     * @param aPhase The operational phase that should now be the current one.
     * @return CHIP_ERROR_INVALID_ARGUMENT if aPhase is an invalid value. CHIP_NO_ERROR if set was successful.
     */
    CHIP_ERROR SetCurrentPhase(const app::DataModel::Nullable<uint8_t> & aPhase);

    /**
     * Set current operational state to aOpState and the operational error to kNoError.
     * NOTE: This method cannot be used to set the error state. The error state must be set via the
     * OnOperationalErrorDetected method.
     * @param aOpState The operational state that should now be the current one.
     * @return CHIP_ERROR_INVALID_ARGUMENT if aOpState is an invalid value. CHIP_NO_ERROR if set was successful.
     */
    CHIP_ERROR SetOperationalState(uint8_t aOpState);

    // Attribute getters
    /**
     * Get current phase.
     * @return The current phase.
     */
    app::DataModel::Nullable<uint8_t> GetCurrentPhase() const;

    /**
     * Get the current operational state.
     * @return The current operational state value.
     */
    uint8_t GetCurrentOperationalState() const;

    /**
     * Get current operational error.
     * @param error The GenericOperationalError to fill with the current operational error value
     */
    void GetCurrentOperationalError(GenericOperationalError & error) const;

    /**
     * @brief Whenever application delegate wants to possibly report a new updated time,
     *        call this method. The `GetCountdownTime()` method will be called on the delegate.
     */
    void UpdateCountdownTimeFromDelegate() { UpdateCountdownTime(/* fromDelegate = */ true); }

    // Event triggers
    /**
     * @brief Called when the Node detects a OperationalError has been raised.
     * Note: This function also sets the OperationalState attribute to Error.
     * @param aError OperationalError which detects
     */
    void OnOperationalErrorDetected(const Structs::ErrorStateStruct::Type & aError);

    /**
     * @brief Called when the Node detects a OperationCompletion has been raised.
     * @param aCompletionErrorCode CompletionErrorCode
     * @param aTotalOperationalTime TotalOperationalTime
     * @param aPausedTime PausedTime
     */
    void OnOperationCompletionDetected(uint8_t aCompletionErrorCode,
                                       const Optional<DataModel::Nullable<uint32_t>> & aTotalOperationalTime = NullOptional,
                                       const Optional<DataModel::Nullable<uint32_t>> & aPausedTime           = NullOptional);

    // List change reporting
    /**
     * Reports that the contents of the operational state list has changed.
     * The device SHALL call this method whenever it changes the operational state list.
     */
    void ReportOperationalStateListChange();

    /**
     * Reports that the contents of the phase list has changed.
     * The device SHALL call this method whenever it changes the phase list.
     */
    void ReportPhaseListChange();

    /**
     * This function returns true if the phase value given exists in the PhaseList attribute, otherwise it returns false.
     */
    bool IsSupportedPhase(uint8_t aPhase);

    /**
     * This function returns true if the operational state value given exists in the OperationalStateList attribute,
     * otherwise it returns false.
     */
    bool IsSupportedOperationalState(uint8_t aState);

protected:
    /**
     * Creates an operational state cluster instance for a given cluster ID.
     * The Init() function needs to be called for this instance to be registered and called by the
     * interaction model at the appropriate times.
     * It is possible to set the CurrentPhase and OperationalState via the Set... methods before calling Init().
     * @param aDelegate A pointer to the delegate to be used by this server.
     * Note: the caller must ensure that the delegate lives throughout the instance's lifetime.
     * @param aEndpointId The endpoint on which this cluster exists. This must match the zap configuration.
     * @param aClusterId The ID of the operational state derived cluster to be instantiated.
     */
    Instance(Delegate * aDelegate, EndpointId aEndpointId, ClusterId aClusterId);

    /**
     * Given a state in the derived cluster number-space (from 0x40 to 0x7f), this method checks if the state is pause-compatible.
     * Note: if a state outside the derived cluster number-space is given, this method returns false.
     * @param aState The state to check.
     * @return true if aState is pause-compatible, false otherwise.
     */
    virtual bool IsDerivedClusterStatePauseCompatible(uint8_t aState) { return false; };

    /**
     * Given a state in the derived cluster number-space (from 0x40 to 0x7f), this method checks if the state is resume-compatible.
     * Note: if a state outside the derived cluster number-space is given, this method returns false.
     * @param aState The state to check.
     * @return true if aState is pause-compatible, false otherwise.
     */
    virtual bool IsDerivedClusterStateResumeCompatible(uint8_t aState) { return false; };

    /**
     * Handles the invocation of derived cluster commands.
     * If a derived cluster defines its own commands, this method SHALL be implemented by the derived cluster's class
     * to handle the derived cluster's specific commands.
     * @param handlerContext The command handler context containing information about the received command.
     */
    virtual void InvokeDerivedClusterCommand(HandlerContext & handlerContext) { return; };

    /**
     * Causes reporting/udpating of CountdownTime attribute from driver if sufficient changes have
     * occurred (based on Q quality definition for operational state). Calls the Delegate::GetCountdownTime() method.
     *
     * @param fromDelegate true if the change notice was triggered by the delegate, false if internal to cluster logic.
     */
    void UpdateCountdownTime(bool fromDelegate);

    /**
     * @brief Whenever the cluster logic thinks time should be updated, call this.
     */
    void UpdateCountdownTimeFromClusterLogic() { UpdateCountdownTime(/* fromDelegate=*/false); }

private:
    Delegate * mDelegate;

    const EndpointId mEndpointId;
    const ClusterId mClusterId;

    // Attribute Data Store
    app::DataModel::Nullable<uint8_t> mCurrentPhase;
    uint8_t mOperationalState                 = 0; // assume 0 for now.
    GenericOperationalError mOperationalError = to_underlying(ErrorStateEnum::kNoError);
    app::QuieterReportingAttribute<uint32_t> mCountdownTime{ DataModel::NullNullable };

    /**
     * This method is inherited from CommandHandlerInterface.
     * This reimplementation does not check that the cluster ID in the HandlerContext (the cluster the command relates to)
     * matches the cluster ID of the RequestT type.
     * These cluster IDs may be different in the case where a command defined in the base cluster is intended for a
     * derived cluster.
     */
    template <typename RequestT, typename FuncT>
    void HandleCommand(HandlerContext & handlerContext, FuncT func);

    // Inherited from CommandHandlerInterface
    void InvokeCommand(HandlerContext & ctx) override;

    /**
     * IM-level implementation of read
     * @return appropriately mapped CHIP_ERROR if applicable (may return CHIP_IM_GLOBAL_STATUS errors)
     */
    CHIP_ERROR Read(const ConcreteReadAttributePath & aPath, AttributeValueEncoder & aEncoder) override;

    /**
     * Handle Command: Pause.
     * If the current state is not pause-compatible, this method responds with an ErrorStateId of CommandInvalidInState.
     * If the current state is paused, this method responds with an ErrorStateId of NoError but takes no action.
     * Otherwise, this method calls the delegate's HandlePauseStateCallback.
     */
    void HandlePauseState(HandlerContext & ctx, const Commands::Pause::DecodableType & req);

    /**
     * Handle Command: Stop.
     */
    void HandleStopState(HandlerContext & ctx, const Commands::Stop::DecodableType & req);

    /**
     * Handle Command: Start.
     */
    void HandleStartState(HandlerContext & ctx, const Commands::Start::DecodableType & req);

    /**
     * Handle Command: Resume.
     * If the current state is not resume-compatible, this method responds with an ErrorStateId of CommandInvalidInState.
     * Otherwise, this method calls the delegate's HandleResumeStateCallback.
     */
    void HandleResumeState(HandlerContext & ctx, const Commands::Resume::DecodableType & req);
};

/**
 * A delegate to handle application logic of the Operational State aliased Cluster.
 * The delegate API assumes there will be separate delegate objects for each cluster instance.
 * (i.e. each separate operational state cluster derivation, on each separate endpoint),
 * since the delegate methods are not handed the cluster id or endpoint.
 */
class Delegate
{
public:
    Delegate() = default;

    virtual ~Delegate() = default;

    /**
     * Get the countdown time. This will get called on many edges such as
     * commands to change operational state, or when the delegate deals with
     * changes. Make sure it becomes null whenever it is appropriate.
     *
     * @return The current countdown time.
     */
    virtual app::DataModel::Nullable<uint32_t> GetCountdownTime() = 0;

    /**
     * Fills in the provided GenericOperationalState with the state at index `index` if there is one,
     * or returns CHIP_ERROR_NOT_FOUND if the index is out of range for the list of states.
     * Note: This is used by the SDK to populate the operational state list attribute. If the contents of this list changes,
     * the device SHALL call the Instance's ReportOperationalStateListChange method to report that this attribute has changed.
     * @param index The index of the state, with 0 representing the first state.
     * @param operationalState  The GenericOperationalState is filled.
     */
    virtual CHIP_ERROR GetOperationalStateAtIndex(size_t index, GenericOperationalState & operationalState) = 0;

    /**
     * Fills in the provided MutableCharSpan with the phase at index `index` if there is one,
     * or returns CHIP_ERROR_NOT_FOUND if the index is out of range for the list of phases.
     *
     * If CHIP_ERROR_NOT_FOUND is returned for index 0, that indicates that the PhaseList attribute is null
     * (there are no phases defined at all).
     *
     * Note: This is used by the SDK to populate the phase list attribute. If the contents of this list changes, the
     * device SHALL call the Instance's ReportPhaseListChange method to report that this attribute has changed.
     * @param index The index of the phase, with 0 representing the first phase.
     * @param operationalPhase  The MutableCharSpan is filled.
     */
    virtual CHIP_ERROR GetOperationalPhaseAtIndex(size_t index, MutableCharSpan & operationalPhase) = 0;

    // command callback
    /**
     * Handle Command Callback in application: Pause
     * @param[out] err operational error after callback.
     */
    virtual void HandlePauseStateCallback(GenericOperationalError & err) = 0;

    /**
     * Handle Command Callback in application: Resume
     * @param[out] err operational error after callback.
     */
    virtual void HandleResumeStateCallback(GenericOperationalError & err) = 0;

    /**
     * Handle Command Callback in application: Start
     * @param[out] err operational error after callback.
     */
    virtual void HandleStartStateCallback(GenericOperationalError & err) = 0;

    /**
     * Handle Command Callback in application: Stop
     * @param[out] err operational error after callback.
     */
    virtual void HandleStopStateCallback(GenericOperationalError & err) = 0;

    /**
     * This method is used by the SDK to set the instance pointer. This is done during the instantiation of a Instance object.
     * @param aInstance A pointer to the Instance object related to this delegate object.
     * @note This method is for internal SDK use and should only be called by the `Instance` constructor and destructor.
     */
    void SetInstance(Instance * aInstance)
    {
        VerifyOrDie(mInstance == nullptr || aInstance == nullptr || mInstance == aInstance);
        mInstance = aInstance;
    }

private:
    Instance * mInstance = nullptr;

protected:
    /**
     * @brief Provides access to the const Instance pointer.
     *
     * @return A const pointer to the Instance object associated with this delegate.
     */
    const Instance * GetInstance() const { return mInstance; }

    /**
     * @brief Provides access to the Instance pointer.
     *
     * @return A pointer to the Instance object associated with this delegate.
     */
    Instance * GetInstance() { return mInstance; }
};

} // namespace OperationalState

namespace RvcOperationalState {

class Delegate : public OperationalState::Delegate
{
public:
    /**
     * Handle Command Callback in application: GoHome
     * @param[out] err operational error after callback.
     */
    virtual void HandleGoHomeCommandCallback(OperationalState::GenericOperationalError & err)
    {
        err.Set(to_underlying(OperationalState::ErrorStateEnum::kUnknownEnumValue));
    };

    /**
     * The start command is not supported by the RvcOperationalState cluster hence this method should never be called.
     * This is a dummy implementation of the handler method so the consumer of this class does not need to define it.
     */
    void HandleStartStateCallback(OperationalState::GenericOperationalError & err) override
    {
        err.Set(to_underlying(OperationalState::ErrorStateEnum::kUnknownEnumValue));
    };

    /**
     * The stop command is not supported by the RvcOperationalState cluster hence this method should never be called.
     * This is a dummy implementation of the handler method so the consumer of this class does not need to define it.
     */
    void HandleStopStateCallback(OperationalState::GenericOperationalError & err) override
    {
        err.Set(to_underlying(OperationalState::ErrorStateEnum::kUnknownEnumValue));
    };
};

class Instance : public OperationalState::Instance
{
public:
    /**
     * Creates an RVC operational state cluster instance.
     * The Init() function needs to be called for this instance to be registered and called by the
     * interaction model at the appropriate times.
     * It is possible to set the CurrentPhase and OperationalState via the Set... methods before calling Init().
     * @param aDelegate A pointer to the delegate to be used by this server.
     * Note: the caller must ensure that the delegate lives throughout the instance's lifetime.
     * @param aEndpointId The endpoint on which this cluster exists. This must match the zap configuration.
     */
    Instance(Delegate * aDelegate, EndpointId aEndpointId) :
        OperationalState::Instance(aDelegate, aEndpointId, Id), mDelegate(aDelegate)
    {}

protected:
    /**
     * Given a state in the derived cluster number-space (from 0x40 to 0x7f), this method checks if the state is pause-compatible.
     * Note: if a state outside the derived cluster number-space is given, this method returns false.
     * @param aState The state to check.
     * @return true if aState is pause-compatible, false otherwise.
     */
    bool IsDerivedClusterStatePauseCompatible(uint8_t aState) override;

    /**
     * Given a state in the derived cluster number-space (from 0x40 to 0x7f), this method checks if the state is resume-compatible.
     * Note: if a state outside the derived cluster number-space is given, this method returns false.
     * @param aState The state to check.
     * @return true if aState is pause-compatible, false otherwise.
     */
    bool IsDerivedClusterStateResumeCompatible(uint8_t aState) override;

    /**
     * Handles the invocation of RvcOperationalState specific commands
     * @param handlerContext The command handler context containing information about the received command.
     */
    void InvokeDerivedClusterCommand(HandlerContext & handlerContext) override;

private:
    Delegate * mDelegate;

    /**
     * Handle Command: GoHome
     */
    void HandleGoHomeCommand(HandlerContext & ctx, const Commands::GoHome::DecodableType & req);
};

} // namespace RvcOperationalState

namespace OvenCavityOperationalState {

class Instance : public OperationalState::Instance
{
public:
    /**
     * Creates an oven cavity operational state cluster instance.
     * The Init() function needs to be called for this instance to be registered and called by the
     * interaction model at the appropriate times.
     * It is possible to set the CurrentPhase and OperationalState via the Set... methods before calling Init().
     * @param aDelegate A pointer to the delegate to be used by this server.
     * Note: the caller must ensure that the delegate lives throughout the instance's lifetime.
     * @param aEndpointId The endpoint on which this cluster exists. This must match the zap configuration.
     */
    Instance(OperationalState::Delegate * aDelegate, EndpointId aEndpointId) :
        OperationalState::Instance(aDelegate, aEndpointId, Id)
    {}
};

} // namespace OvenCavityOperationalState

} // namespace Clusters
} // namespace app
} // namespace chip
