/*
 *  Copyright (c) 2017, 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 data poll (mac data request command) sender.
 */

#ifndef OT_CORE_MAC_DATA_POLL_SENDER_HPP_
#define OT_CORE_MAC_DATA_POLL_SENDER_HPP_

#include "openthread-core-config.h"

#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/neighbor.hpp"

namespace ot {

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

/**
 * Implements the data poll (mac data request command) sender.
 */

class DataPollSender : public InstanceLocator, private NonCopyable
{
public:
    static constexpr uint8_t kDefaultFastPolls  = 8;  ///< Default number of fast poll tx (@sa StartFastPolls).
    static constexpr uint8_t kMaxFastPolls      = 15; ///< Maximum number of fast poll tx allowed.
    static constexpr uint8_t kMaxFastPollsUsers = 63; ///< Maximum number of the users of fast poll tx allowed.

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

    /**
     * Instructs the data poll sender to start sending periodic data polls.
     */
    void StartPolling(void);

    /**
     * Instructs the data poll sender to stop sending periodic data polls.
     */
    void StopPolling(void);

    /**
     * Enqueues a data poll (an IEEE 802.15.4 Data Request) message.
     *
     * @retval kErrorNone          Successfully enqueued a data poll message
     * @retval kErrorAlready       A data poll message is already enqueued.
     * @retval kErrorInvalidState  Device is not in rx-off-when-idle mode.
     * @retval kErrorNoBufs        Insufficient message buffers available.
     */
    Error SendDataPoll(void);

    /**
     * Sets/clears a user-specified/external data poll period.
     *
     * Value of zero for `aPeriod` clears the user-specified poll period.
     *
     * If the user provides a non-zero poll period, the user value specifies the maximum period between data
     * request transmissions. Note that OpenThread may send data request transmissions more frequently when expecting
     * a control-message from a parent or in case of data poll transmission failures or timeouts, or when the specified
     * value is larger than the child timeout.
     *
     * A non-zero `aPeriod` should be larger than or equal to `OPENTHREAD_CONFIG_MAC_MINIMUM_POLL_PERIOD` (10ms) or
     * this method returns `kErrorInvalidArgs`. If a non-zero `aPeriod` is larger than maximum value of
     * `0x3FFFFFF ((1 << 26) - 1)`, it would be clipped to this value.
     *
     * @param[in]  aPeriod  The data poll period in milliseconds.
     *
     * @retval kErrorNone           Successfully set/cleared user-specified poll period.
     * @retval kErrorInvalidArgs    If aPeriod is invalid.
     */
    Error SetExternalPollPeriod(uint32_t aPeriod);

    /**
     * Gets the current user-specified/external data poll period.
     *
     * @returns  The data poll period in milliseconds.
     */
    uint32_t GetExternalPollPeriod(void) const { return mExternalPollPeriod; }

    /**
     * Informs the data poll sender of success/error status of a previously requested poll frame
     * transmission.
     *
     * In case of transmit failure, the data poll sender may choose to send the next data poll more quickly (up to
     * some fixed number of attempts).
     *
     * @param[in] aFrame     The data poll frame.
     * @param[in] aError     Error status of a data poll message transmission.
     */
    void HandlePollSent(Mac::TxFrame &aFrame, Error aError);

    /**
     * Informs the data poll sender that a data poll timeout happened, i.e., when the ack in response to
     * a data request command indicated that a frame was pending, but no frame was received after timeout interval.
     *
     * Data poll sender may choose to transmit another data poll immediately (up to some fixed number of attempts).
     */
    void HandlePollTimeout(void);

    /**
     * Informs the data poll sender to process a received MAC frame.
     *
     * @param[in] aFrame     A reference to the received frame to process.
     */
    void ProcessRxFrame(const Mac::RxFrame &aFrame);

#if OPENTHREAD_CONFIG_THREAD_VERSION >= OT_THREAD_VERSION_1_2
    /**
     * Informs the data poll sender to process a transmitted MAC frame.
     *
     * @param[in]  aFrame      A reference to the frame that was transmitted.
     * @param[in]  aAckFrame   A pointer to the ACK frame, `nullptr` if no ACK was received.
     * @param[in]  aError      kErrorNone when the frame was transmitted successfully,
     *                         kErrorNoAck when the frame was transmitted but no ACK was received,
     *                         kErrorChannelAccessFailure when the tx failed due to activity on the channel,
     *                         kErrorAbort when transmission was aborted for other reasons.
     */
    void ProcessTxDone(const Mac::TxFrame &aFrame, const Mac::RxFrame *aAckFrame, Error aError);
#endif

    /**
     * Asks the data poll sender to recalculate the poll period.
     *
     * This is mainly used to inform the poll sender that a parameter impacting the poll period (e.g., the child's
     * timeout value which is used to determine the default data poll period) is modified.
     */
    void RecalculatePollPeriod(void);

    /**
     * Sets/clears the attach mode on data poll sender.
     *
     * When attach mode is enabled, the data poll sender will send data polls at a faster rate determined by
     * poll period configuration option `OPENTHREAD_CONFIG_MAC_ATTACH_DATA_POLL_PERIOD`.
     *
     * @param[in]  aMode  The mode value.
     */
    void SetAttachMode(bool aMode);

    /**
     * Asks data poll sender to send the next given number of polls at a faster rate (poll period defined
     * by `kFastPollPeriod`). This is used by OpenThread stack when it expects a response from the parent/sender.
     *
     * If @p aNumFastPolls is zero the default value specified by `kDefaultFastPolls` is used instead. The number of
     * fast polls is clipped by maximum value specified by `kMaxFastPolls`.
     *
     * Note that per `SendFastPolls()` would increase the internal reference count until up to the allowed maximum
     * value. If there are retransmission mechanism in the caller component, it should be responsible to call
     * `StopFastPolls()` the same times as `SendFastPolls()` it triggered to decrease the reference count properly,
     * guaranteeing to exit fast poll mode gracefully. Otherwise, fast poll would continue until all data polls are sent
     * out.
     *
     * @param[in] aNumFastPolls  If non-zero, number of fast polls to send, if zero, default value is used instead.
     */
    void SendFastPolls(uint8_t aNumFastPolls = 0);

    /**
     * Asks data poll sender to stop fast polls when the expecting response is received.
     */
    void StopFastPolls(void);

    /**
     * Gets the maximum data polling period in use.
     *
     * The maximum data poll period is determined based as the minimum of the user-specified poll interval and the
     * default poll interval.
     *
     * @returns The maximum data polling period in use.
     */
    uint32_t GetKeepAlivePollPeriod(void) const;

    /**
     * Resets the timer for sending keep-alive messages.
     */
    void ResetKeepAliveTimer(void);

    /**
     * Returns the default maximum poll period.
     *
     * The default poll period is determined based on the child timeout interval, ensuing the child would send data poll
     * within the child's timeout.
     *
     * @returns The maximum default data polling interval (in msec).
     */
    uint32_t GetDefaultPollPeriod(void) const;

    /**
     * Prepares and returns a data request command frame.
     *
     * @param[in] aTxFrames  The set of TxFrames for all radio links.
     *
     * @returns The data poll frame.
     */
    Mac::TxFrame *PrepareDataRequest(Mac::TxFrames &aTxFrames);

private:
    static constexpr uint8_t kQuickPollsAfterTimeout = 5; // Quick data poll tx in case of back-to-back poll timeouts.
    static constexpr uint8_t kMaxPollRetxAttempts    = OPENTHREAD_CONFIG_FAILED_CHILD_TRANSMISSIONS;
    static constexpr uint8_t kMaxCslPollRetxAttempts = OPENTHREAD_CONFIG_MAC_DEFAULT_MAX_FRAME_RETRIES_DIRECT;

    enum PollPeriodSelector : uint8_t
    {
        kUsePreviousPollPeriod,
        kRecalculatePollPeriod,
    };

    // Poll period under different conditions (in milliseconds).
    static constexpr uint32_t kAttachDataPollPeriod = OPENTHREAD_CONFIG_MAC_ATTACH_DATA_POLL_PERIOD;
    static constexpr uint32_t kRetxPollPeriod       = OPENTHREAD_CONFIG_MAC_RETX_POLL_PERIOD;
    static constexpr uint32_t kFastPollPeriod       = 188;
    static constexpr uint32_t kMinPollPeriod        = OPENTHREAD_CONFIG_MAC_MINIMUM_POLL_PERIOD;
    static constexpr uint32_t kMaxExternalPeriod    = ((1 << 26) - 1); // ~18.6 hours.

    void            ScheduleNextPoll(PollPeriodSelector aPollPeriodSelector);
    uint32_t        CalculatePollPeriod(void) const;
    const Neighbor &GetParent(void) const;
    void            HandlePollTimer(void) { IgnoreError(SendDataPoll()); }
#if OPENTHREAD_CONFIG_MULTI_RADIO
    Error GetPollDestinationAddress(Mac::Address &aDest, Mac::RadioType &aRadioType) const;
#else
    Error GetPollDestinationAddress(Mac::Address &aDest) const;
#endif

    using PollTimer = TimerMilliIn<DataPollSender, &DataPollSender::HandlePollTimer>;

    TimeMilli mTimerStartTime;
    uint32_t  mPollPeriod;
    uint32_t  mExternalPollPeriod : 26; // In milliseconds.
    uint8_t   mFastPollsUsers : 6;      // Number of callers which request fast polls.

    PollTimer mTimer;

    bool    mEnabled : 1;              // Indicates whether data polling is enabled/started.
    bool    mAttachMode : 1;           // Indicates whether in attach mode (to use attach poll period).
    bool    mRetxMode : 1;             // Indicates whether last poll tx failed at mac/radio layer (poll retx mode).
    uint8_t mPollTimeoutCounter : 4;   // Poll timeouts counter (0 to `kQuickPollsAfterTimeout`).
    uint8_t mPollTxFailureCounter : 4; // Poll tx failure counter (0 to `kMaxPollRetxAttempts`).
    uint8_t mRemainingFastPolls : 4;   // Number of remaining fast polls when in transient fast polling mode.
};

/**
 * @}
 */

} // namespace ot

#endif // OT_CORE_MAC_DATA_POLL_SENDER_HPP_
