/*
 *  Copyright (c) 2019, The OpenThread Authors.
 *  All rights reserved.
 *
 *  Redistribution and use in source and binary forms, with or without
 *  modification, are permitted provided that the following conditions are met:
 *  1. Redistributions of source code must retain the above copyright
 *     notice, this list of conditions and the following disclaimer.
 *  2. Redistributions in binary form must reproduce the above copyright
 *     notice, this list of conditions and the following disclaimer in the
 *     documentation and/or other materials provided with the distribution.
 *  3. Neither the name of the copyright holder nor the
 *     names of its contributors may be used to endorse or promote products
 *     derived from this software without specific prior written permission.
 *
 *  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 *  AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 *  IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 *  ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
 *  LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 *  CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 *  SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 *  INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 *  CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 *  ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 *  POSSIBILITY OF SUCH DAMAGE.
 */

/**
 * @file
 *   This file includes definitions for handling of data polls and indirect frame transmission.
 */

#ifndef DATA_POLL_HANDLER_HPP_
#define DATA_POLL_HANDLER_HPP_

#include "openthread-core-config.h"

#if OPENTHREAD_FTD

#include "common/code_utils.hpp"
#include "common/locator.hpp"
#include "common/non_copyable.hpp"
#include "common/timer.hpp"
#include "mac/mac.hpp"
#include "mac/mac_frame.hpp"
#include "thread/indirect_sender_frame_context.hpp"

namespace ot {

/**
 * @addtogroup core-data-poll-handler
 *
 * @brief
 *   This module includes definitions for data poll handler.
 *
 * @{
 */

class Child;

/**
 * Implements the data poll (mac data request command) handler.
 *
 */
class DataPollHandler : public InstanceLocator, private NonCopyable
{
    friend class Mac::Mac;

public:
    static constexpr uint8_t kMaxPollTriggeredTxAttempts = OPENTHREAD_CONFIG_MAC_MAX_TX_ATTEMPTS_INDIRECT_POLLS;

    /**
     * Defines frame change request types used as input to `RequestFrameChange()`.
     *
     */
    enum FrameChange : uint8_t
    {
        kPurgeFrame,   ///< Indicates that previous frame should be purged. Any ongoing indirect tx should be aborted.
        kReplaceFrame, ///< Indicates that previous frame needs to be replaced with a new higher priority one.
    };

    /**
     * Defines all the child info required for handling of data polls and indirect frame transmissions.
     *
     * `Child` class publicly inherits from this class.
     *
     */
    class ChildInfo
    {
        friend class DataPollHandler;
#if OPENTHREAD_FTD && OPENTHREAD_CONFIG_MAC_CSL_TRANSMITTER_ENABLE
        friend class CslTxScheduler;
#endif

    private:
        bool IsDataPollPending(void) const { return mDataPollPending; }
        void SetDataPollPending(bool aPending) { mDataPollPending = aPending; }

        uint32_t GetIndirectFrameCounter(void) const { return mIndirectFrameCounter; }
        void     SetIndirectFrameCounter(uint32_t aFrameCounter) { mIndirectFrameCounter = aFrameCounter; }

        uint8_t GetIndirectKeyId(void) const { return mIndirectKeyId; }
        void    SetIndirectKeyId(uint8_t aKeyId) { mIndirectKeyId = aKeyId; }

        uint8_t GetIndirectTxAttempts(void) const { return mIndirectTxAttempts; }
        void    ResetIndirectTxAttempts(void) { mIndirectTxAttempts = 0; }
        void    IncrementIndirectTxAttempts(void) { mIndirectTxAttempts++; }

        uint8_t GetIndirectDataSequenceNumber(void) const { return mIndirectDsn; }
        void    SetIndirectDataSequenceNumber(uint8_t aDsn) { mIndirectDsn = aDsn; }

        bool IsFramePurgePending(void) const { return mFramePurgePending; }
        void SetFramePurgePending(bool aPurgePending) { mFramePurgePending = aPurgePending; }

        bool IsFrameReplacePending(void) const { return mFrameReplacePending; }
        void SetFrameReplacePending(bool aReplacePending) { mFrameReplacePending = aReplacePending; }

#if OPENTHREAD_CONFIG_MULTI_RADIO
        Mac::RadioType GetLastPollRadioType(void) const { return mLastPollRadioType; }
        void           SetLastPollRadioType(Mac::RadioType aRadioType) { mLastPollRadioType = aRadioType; }
#endif

        uint32_t mIndirectFrameCounter;    // Frame counter for current indirect frame (used for retx).
        uint8_t  mIndirectKeyId;           // Key Id for current indirect frame (used for retx).
        uint8_t  mIndirectDsn;             // MAC level Data Sequence Number (DSN) for retx attempts.
        uint8_t  mIndirectTxAttempts : 5;  // Number of data poll triggered tx attempts.
        bool     mDataPollPending : 1;     // Indicates whether or not a Data Poll was received.
        bool     mFramePurgePending : 1;   // Indicates a pending purge request for the current indirect frame.
        bool     mFrameReplacePending : 1; // Indicates a pending replace request for the current indirect frame.
#if OPENTHREAD_CONFIG_MULTI_RADIO
        Mac::RadioType mLastPollRadioType; // The radio link last data poll frame was received on.
#endif

        static_assert(kMaxPollTriggeredTxAttempts < (1 << 5), "mIndirectTxAttempts cannot fit max!");
    };

    /**
     * Defines the callbacks used by the `DataPollHandler`.
     *
     */
    class Callbacks : public InstanceLocator
    {
        friend class DataPollHandler;

    private:
        /**
         * Defines the frame context associated with a prepared frame.
         *
         * Data poll handler treats `FrameContext` as an opaque data type. Data poll handler provides the buffer/object
         * for the context when a new frame is prepared (from the callback `PrepareFrameForChild()`). It ensures
         * to save the context along with the prepared frame and provide the same context back in the callback
         * `HandleSentFrameToChild()` when the indirect transmission of the frame is finished.
         *
         */
        typedef IndirectSenderBase::FrameContext FrameContext;

        /**
         * Initializes the callbacks object.
         *
         * @param[in]  aInstance   A reference to the OpenThread instance.
         *
         */
        explicit Callbacks(Instance &aInstance);

        /**
         * This callback method requests a frame to be prepared for indirect transmission to a given sleepy child.
         *
         * @param[out] aFrame    A reference to a MAC frame where the new frame would be placed.
         * @param[out] aContext  A reference to a `FrameContext` where the context for the new frame would be placed.
         * @param[in]  aChild    The child for which to prepare the frame.
         *
         * @retval kErrorNone   Frame was prepared successfully.
         * @retval kErrorAbort  Indirect transmission to child should be aborted (no frame for the child).
         *
         */
        Error PrepareFrameForChild(Mac::TxFrame &aFrame, FrameContext &aContext, Child &aChild);

        /**
         * This callback method notifies the end of indirect frame transmission to a child.
         *
         * @param[in]  aFrame     The transmitted frame.
         * @param[in]  aContext   The context associated with the frame when it was prepared.
         * @param[in]  aError     kErrorNone when the frame was transmitted successfully,
         *                        kErrorNoAck when the frame was transmitted but no ACK was received,
         *                        kErrorChannelAccessFailure tx failed due to activity on the channel,
         *                        kErrorAbort when transmission was aborted for other reasons.
         * @param[in]  aChild     The child to which the frame was transmitted.
         *
         */
        void HandleSentFrameToChild(const Mac::TxFrame &aFrame,
                                    const FrameContext &aContext,
                                    Error               aError,
                                    Child              &aChild);

        /**
         * This callback method notifies that a requested frame change from `RequestFrameChange()` is processed.
         *
         * This callback indicates to the next layer that the indirect frame/message for the child can be safely
         * updated.
         *
         * @param[in]  aChild     The child to update.
         *
         */
        void HandleFrameChangeDone(Child &aChild);
    };

    /**
     * Initializes the data poll handler object.
     *
     * @param[in]  aInstance   A reference to the OpenThread instance.
     *
     */
    explicit DataPollHandler(Instance &aInstance);

    /**
     * Clears any state/info saved per child for indirect frame transmission.
     *
     */
    void Clear(void);

    /**
     * Informs data poll handler that there is a new frame for a given child.
     *
     * After this call, the data poll handler can use the `Callbacks::PrepareFrameForChild()` method to request the
     * frame to be prepared. A subsequent call to `Callbacks::PrepareFrameForChild()` should ensure to prepare the same
     * frame (this is used for retransmissions of frame by data poll handler). If/When the frame transmission is
     * finished, the data poll handler will invoke the `Callbacks::HandleSentFrameToChild()` to indicate the status of
     * the frame transmission.
     *
     * @param[in]  aChild     The child which has a new frame.
     *
     */
    void HandleNewFrame(Child &aChild);

    /**
     * Requests a frame change for a given child.
     *
     * Two types of frame change requests are supported:
     *
     * 1) "Purge Frame" which indicates that the previous frame should be purged and any ongoing indirect tx aborted.
     * 2) "Replace Frame" which indicates that the previous frame needs to be replaced with a new higher priority one.
     *
     * If there is no ongoing indirect frame transmission to the child, the request will be handled immediately and the
     * callback `HandleFrameChangeDone()` is called directly from this method itself. This callback notifies the next
     * layer that the indirect frame/message for the child can be safely updated.
     *
     * If there is an ongoing indirect frame transmission to this child, the request can not be handled immediately.
     * The following options can happen based on the request type:
     *
     * 1) In case of "purge" request, the ongoing indirect transmission is aborted and upon completion of the abort the
     *    callback `HandleFrameChangeDone()` is invoked.
     *
     * 2) In case of "replace" request, the ongoing indirect transmission is allowed to finish (current tx attempt).
     *    2.a) If the tx attempt is successful, the `Callbacks::HandleSentFrameToChild()` in invoked which indicates
     *         the "replace" could not happen (in this case the `HandleFrameChangeDone()` is no longer called).
     *    2.b) If the ongoing tx attempt is unsuccessful, then callback `HandleFrameChangeDone()` is invoked to allow
     *         the next layer to update the frame/message for the child.
     *
     * If there is a pending request, a subsequent call to this method is ignored except for the case where pending
     * request is for "replace frame" and new one is for "purge frame" where the "purge" overrides the "replace"
     * request.
     *
     * @param[in]  aChange    The frame change type.
     * @param[in]  aChild     The child to process its frame change.
     *
     */
    void RequestFrameChange(FrameChange aChange, Child &aChild);

private:
    // Callbacks from MAC
    void          HandleDataPoll(Mac::RxFrame &aFrame);
    Mac::TxFrame *HandleFrameRequest(Mac::TxFrames &aTxFrames);
    void          HandleSentFrame(const Mac::TxFrame &aFrame, Error aError);

    void HandleSentFrame(const Mac::TxFrame &aFrame, Error aError, Child &aChild);
    void ProcessPendingPolls(void);
    void ResetTxAttempts(Child &aChild);

    // In the current implementation of `DataPollHandler`, we can have a
    // single indirect tx operation active at MAC layer at each point of
    // time. `mIndirectTxChild` indicates the child being handled (`nullptr`
    // indicates no active indirect tx). `mFrameContext` tracks the
    // context for the prepared frame for the current indirect tx.

    Child                  *mIndirectTxChild;
    Callbacks::FrameContext mFrameContext;
    Callbacks               mCallbacks;
};

/**
 * @}
 *
 */

} // namespace ot

#endif // OPENTHREAD_FTD

#endif // DATA_POLL_HANDLER_HPP_
