/*
 *
 *    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 <app-common/zap-generated/cluster-objects.h>
#include <app/AttributeAccessInterface.h>
#include <app/CommandHandlerInterface.h>
#include <app/ConcreteAttributePath.h>
#include <app/InteractionModelEngine.h>
#include <app/MessageDef/StatusIB.h>
#include <app/reporting/reporting.h>
#include <app/util/attribute-storage.h>
#include <lib/core/CHIPError.h>
#include <protocols/interaction_model/StatusCode.h>

namespace chip {
namespace app {
namespace Clusters {
namespace DeviceEnergyManagement {

class Delegate
{
public:
    virtual ~Delegate() = default;

    void SetEndpointId(EndpointId aEndpoint) { mEndpointId = aEndpoint; }

    /**
     * @brief Delegate should implement a handler to begin to adjust client power
     *        consumption/generation to the level requested.
     *
     *        Note callers must call GetPowerAdjustmentCapability and ensure the return value is not null
     *        before calling PowerAdjustRequest.
     *
     * @param power Milli-Watts the ESA SHALL use during the adjustment period.
     * @param duration The duration that the ESA SHALL maintain the requested power for.
     * @return  Success if the adjustment is accepted; otherwise the command SHALL be rejected with appropriate error.
     */
    virtual Protocols::InteractionModel::Status PowerAdjustRequest(const int64_t power, const uint32_t duration,
                                                                   AdjustmentCauseEnum cause) = 0;

    /**
     * @brief Delegate SHALL make the ESA end the active power adjustment session & return to normal (or idle) power levels.
     *        The ESA SHALL also generate an PowerAdjustEnd Event and the ESAState SHALL be restored to Online.
     *
     * @return It should report SUCCESS if successful and FAILURE otherwise.
     */
    virtual Protocols::InteractionModel::Status CancelPowerAdjustRequest() = 0;

    /**
     * @brief Delegate for the ESA SHALL update its Forecast attribute with the RequestedStartTime including a new ForecastID.
     *
     *   If the ESA supports ForecastAdjustment, and the ESAState is not UserOptOut and the RequestedStartTime is after
     *   the EarliestStartTime and the resulting EndTime is before the LatestEndTime, then ESA SHALL accept the request
     *   to modify the Start Time.
     *   A client can estimate the entire Forecast sequence duration by computing the EndTime - StartTime fields from the
     *   Forecast attribute, and therefore avoid scheduling the start time too late.
     *
     * @param requestedStartTime The requested start time in UTC that the client would like the appliance to shift its power
     * forecast to.
     * @param cause    Who (Grid/local) is triggering this change.
     *
     * @return Success if the StartTime in the Forecast is updated, otherwise the command SHALL be rejected with appropriate
     * IM_Status.
     */
    virtual Protocols::InteractionModel::Status StartTimeAdjustRequest(const uint32_t requestedStartTime,
                                                                       AdjustmentCauseEnum cause) = 0;

    /**
     * @brief Delegate handler for PauseRequest command
     *
     *   If the ESA supports FA and the SlotIsPauseable field is true in the ActiveSlotNumber
     *   index in the Slots list, and the ESAState is not UserOptOut then the ESA SHALL allow its current
     *   operation to be Paused.
     *
     *   During this state the ESA SHALL not consume or produce significant power (other than required to keep its
     *   basic control system operational).
     *
     * @param duration Duration that the ESA SHALL be paused for.
     * @return  Success if the ESA is paused, otherwise returns other IM_Status.
     */
    virtual Protocols::InteractionModel::Status PauseRequest(const uint32_t duration, AdjustmentCauseEnum cause) = 0;

    /**
     * @brief Delegate handler for ResumeRequest command
     *
     *   If the ESA supports FA and it is currently Paused then the ESA SHALL resume its operation.
     *   The ESA SHALL also generate a Resumed Event and the ESAState SHALL be updated accordingly to
     *   reflect its current state.
     *
     * @return  Success if the ESA is resumed, otherwise returns other IM_Status.
     */
    virtual Protocols::InteractionModel::Status ResumeRequest() = 0;

    /**
     * @brief Delegate handler for ModifyForecastRequest
     *
     *   If the ESA supports FA, and the ESAState is not UserOptOut it SHALL attempt to adjust its power forecast.
     *   This allows a one or more modifications in a single command by sending a list of modifications (one for each 'slot').
     *   Attempts to modify slots which have already past, SHALL result in the entire command being rejected.
     *   If the ESA accepts the requested Forecast then it SHALL update its Forecast attribute (incrementing its ForecastID)
     *   and run the revised Forecast as its new intended operation.
     *
     * @param forecastID Indicates the ESA ForecastID that is to be modified.
     * @param slotAdjustments List of adjustments to be applied to the ESA, corresponding to the expected ESA forecastID.
     * @return  Success if the entire list of SlotAdjustmentStruct are accepted, otherwise the command
     *          SHALL be rejected returning other IM_Status.
     */
    virtual Protocols::InteractionModel::Status
    ModifyForecastRequest(const uint32_t forecastID,
                          const DataModel::DecodableList<Structs::SlotAdjustmentStruct::Type> & slotAdjustments,
                          AdjustmentCauseEnum cause) = 0;

    /**
     * @brief Delegate handler for RequestConstraintBasedForecast
     *
     *   The ESA SHALL inspect the requested power limits to ensure that there are no overlapping elements. The ESA
     *   manufacturer may also reject the request if it could cause the user’s preferences to be breached (e.g. may
     *   cause the home to be too hot or too cold, or a battery to be insufficiently charged).
     *   If the ESA can meet the requested power limits, it SHALL regenerate a new Power Forecast with a new ForecastID.
     *
     * @param constraints  Sequence of turn up/down power requests that the ESA is being asked to constrain its operation within.
     * @return  Success if successful, otherwise the command SHALL be rejected returning other IM_Status.
     */
    virtual Protocols::InteractionModel::Status
    RequestConstraintBasedForecast(const DataModel::DecodableList<Structs::ConstraintsStruct::Type> & constraints,
                                   AdjustmentCauseEnum cause) = 0;

    /**
     * @brief Delegate handler for CancelRequest
     *
     *   The ESA SHALL attempt to cancel the effects of any previous adjustment request commands, and re-evaluate its
     *   forecast for intended operation ignoring those previous requests.
     *
     *   If the ESA ForecastStruct ForecastUpdateReason was already `Internal Optimization`, then the command SHALL
     *   be rejected with FAILURE.
     *
     *   If the command is accepted, the ESA SHALL update its ESAState if required, and the command status returned
     *   SHALL be SUCCESS.
     *
     *   The ESA SHALL update its Forecast attribute to match its new intended operation, and update the
     *   ForecastStruct.ForecastUpdateReason to `Internal Optimization`
     *
     * @return  Success if successful, otherwise the command SHALL be rejected returning other IM_Status.
     */
    virtual Protocols::InteractionModel::Status CancelRequest() = 0;

    // ------------------------------------------------------------------
    // Get attribute methods
    virtual ESATypeEnum GetESAType()         = 0;
    virtual bool GetESACanGenerate()         = 0;
    virtual ESAStateEnum GetESAState()       = 0;
    virtual int64_t GetAbsMinPower()         = 0;
    virtual int64_t GetAbsMaxPower()         = 0;
    virtual OptOutStateEnum GetOptOutState() = 0;

    /**
     * @brief Returns the current PowerAdjustCapability object
     *
     * The reference returned from GetPowerAdjustmentCapability() is only valid until the next Matter event
     * is processed.  Callers must not hold on to that reference for any asynchronous processing.
     *
     * Once another Matter event has had a chance to run, the memory associated with the
     * PowerAdjustCapabilityStruct is likely to change or be re-allocated, so would become invalid.
     *
     * @return  The current PowerAdjustCapability object
     */
    virtual const DataModel::Nullable<Structs::PowerAdjustCapabilityStruct::Type> & GetPowerAdjustmentCapability() = 0;

    /**
     * @brief Returns the current Forecast object
     *
     * The reference returned from GetForecast() is only valid until the next Matter event
     * is processed.  Callers must not hold on to that reference for any asynchronous processing.
     *
     * Once another Matter event has had a chance to run, the memory associated with the
     * ForecastStruct is likely to change or be re-allocated, so would become invalid.
     *
     * @return  The current Forecast object
     */
    virtual const DataModel::Nullable<Structs::ForecastStruct::Type> & GetForecast() = 0;

    // ------------------------------------------------------------------
    // Set attribute methods
    virtual CHIP_ERROR SetESAState(ESAStateEnum) = 0;

protected:
    EndpointId mEndpointId = 0;
};

class Instance : public AttributeAccessInterface, public CommandHandlerInterface
{
public:
    Instance(EndpointId aEndpointId, Delegate & aDelegate, Feature aFeature) :
        AttributeAccessInterface(MakeOptional(aEndpointId), Id), CommandHandlerInterface(MakeOptional(aEndpointId), Id),
        mDelegate(aDelegate), mFeature(aFeature)
    {
        /* set the base class delegates endpointId */
        mDelegate.SetEndpointId(aEndpointId);
    }

    ~Instance() { Shutdown(); }

    CHIP_ERROR Init();
    void Shutdown();

    bool HasFeature(Feature aFeature) const;

private:
    Protocols::InteractionModel::Status GetMatterEpochTimeFromUnixTime(uint32_t & currentUtcTime) const;

private:
    Delegate & mDelegate;
    BitMask<Feature> mFeature;

    // AttributeAccessInterface
    CHIP_ERROR Read(const ConcreteReadAttributePath & aPath, AttributeValueEncoder & aEncoder) override;
    // NOTE there are no writable attributes

    // CommandHandlerInterface
    void InvokeCommand(HandlerContext & handlerContext) override;
    CHIP_ERROR RetrieveAcceptedCommands(const ConcreteClusterPath & cluster,
                                        ReadOnlyBufferBuilder<DataModel::AcceptedCommandEntry> & builder) override;

    Protocols::InteractionModel::Status CheckOptOutAllowsRequest(AdjustmentCauseEnum adjustmentCause);
    void HandlePowerAdjustRequest(HandlerContext & ctx, const Commands::PowerAdjustRequest::DecodableType & commandData);
    void HandleCancelPowerAdjustRequest(HandlerContext & ctx,
                                        const Commands::CancelPowerAdjustRequest::DecodableType & commandData);
    void HandleStartTimeAdjustRequest(HandlerContext & ctx, const Commands::StartTimeAdjustRequest::DecodableType & commandData);
    void HandlePauseRequest(HandlerContext & ctx, const Commands::PauseRequest::DecodableType & commandData);
    void HandleResumeRequest(HandlerContext & ctx, const Commands::ResumeRequest::DecodableType & commandData);
    void HandleModifyForecastRequest(HandlerContext & ctx, const Commands::ModifyForecastRequest::DecodableType & commandData);
    void HandleRequestConstraintBasedForecast(HandlerContext & ctx,
                                              const Commands::RequestConstraintBasedForecast::DecodableType & commandData);
    void HandleCancelRequest(HandlerContext & ctx, const Commands::CancelRequest::DecodableType & commandData);
};

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