/*
 *  Copyright (c) 2020, 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 Thread Link Metrics query and management.
 */

#ifndef LINK_METRICS_HPP_
#define LINK_METRICS_HPP_

#include "openthread-core-config.h"

#if OPENTHREAD_CONFIG_MLE_LINK_METRICS_INITIATOR_ENABLE || OPENTHREAD_CONFIG_MLE_LINK_METRICS_SUBJECT_ENABLE

#if (OPENTHREAD_CONFIG_THREAD_VERSION < OT_THREAD_VERSION_1_2)
#error "Thread 1.2 or higher version is required for OPENTHREAD_CONFIG_MLE_LINK_METRICS_INITIATOR_ENABLE" \
       "and OPENTHREAD_CONFIG_MLE_LINK_METRICS_SUBJECT_ENABLE."
#endif

#include <openthread/link.h>

#include "common/as_core_type.hpp"
#include "common/clearable.hpp"
#include "common/locator.hpp"
#include "common/message.hpp"
#include "common/non_copyable.hpp"
#include "common/pool.hpp"
#include "net/ip6_address.hpp"
#include "thread/link_metrics_tlvs.hpp"
#include "thread/link_quality.hpp"

namespace ot {
class Neighbor;

namespace LinkMetrics {

/**
 * @addtogroup core-link-metrics
 *
 * @brief
 *   This module includes definitions for Thread Link Metrics query and management.
 *
 * @{
 */

/**
 * This type represents the results (values) for a set of metrics.
 *
 * @sa otLinkMetricsValues.
 *
 */
class MetricsValues : public otLinkMetricsValues, public Clearable<MetricsValues>
{
public:
    /**
     * This method gets the metrics flags.
     *
     * @returns The metrics flags.
     *
     */
    Metrics &GetMetrics(void) { return static_cast<Metrics &>(mMetrics); }

    /**
     * This method gets the metrics flags.
     *
     * @returns The metrics flags.
     *
     */
    const Metrics &GetMetrics(void) const { return static_cast<const Metrics &>(mMetrics); }

    /**
     * This method set the metrics flags.
     *
     * @param[in] aMetrics  The metrics flags to set from.
     *
     */
    void SetMetrics(const Metrics &aMetrics) { mMetrics = aMetrics; }
};

/**
 * This class represents one Series that is being tracked by the Subject.
 *
 * When an Initiator successfully configured a Forward Tracking Series, the Subject would use an instance of this class
 * to track the information of the Series. The Subject has a `Pool` of `SeriesInfo`. It would allocate one when a new
 * Series comes, and free it when a Series finishes.
 *
 * This class inherits `LinkedListEntry` and each `Neighbor` has a list of `SeriesInfo` so that the Subject could track
 * per Series initiated by neighbors as long as it has available resources.
 *
 */
class SeriesInfo : public LinkedListEntry<SeriesInfo>
{
    friend class LinkedList<SeriesInfo>;
    friend class LinkedListEntry<SeriesInfo>;

public:
    /**
     * This constant represents Link Probe when filtering frames to be accounted using Series Flag. There's
     * already `kFcfFrameData`, `kFcfFrameAck` and `kFcfFrameMacCmd`. This item is added so that we can
     * filter a Link Probe for series in the same way as other frames.
     *
     */
    static constexpr uint8_t kSeriesTypeLinkProbe = 0;

    /**
     * This method initializes the SeriesInfo object.
     *
     * @param[in]  aSeriesId      The Series ID.
     * @param[in]  aSeriesFlags   The Series Flags which specify what types of frames are to be accounted.
     * @param[in]  aMetrics       Metrics to query.
     *
     */
    void Init(uint8_t aSeriesId, const SeriesFlags &aSeriesFlags, const Metrics &aMetrics);

    /**
     * This method gets the Series ID.
     *
     * @returns  The Series ID.
     *
     */
    uint8_t GetSeriesId(void) const { return mSeriesId; }

    /**
     * This method gets the PDU count.
     *
     * @returns  The PDU count.
     *
     */
    uint32_t GetPduCount(void) const { return mPduCount; }

    /**
     * This method gets the average LQI.
     *
     * @returns  The average LQI.
     *
     */
    uint8_t GetAverageLqi(void) const { return mLqiAverager.GetAverage(); }

    /**
     * This method gets the average RSS.
     *
     * @returns  The average RSS.
     *
     */
    int8_t GetAverageRss(void) const { return mRssAverager.GetAverage(); }

    /**
     * This method aggregates the Link Metrics data of a frame into this series.
     *
     * @param[in]  aFrameType    The type of the frame.
     * @param[in]  aLqi          The LQI value.
     * @param[in]  aRss          The RSS value.
     *
     */
    void AggregateLinkMetrics(uint8_t aFrameType, uint8_t aLqi, int8_t aRss);

    /**
     * This methods gets the metrics.
     *
     * @returns  The metrics associated with `SeriesInfo`.
     *
     */
    const Metrics &GetLinkMetrics(void) const { return mMetrics; }

private:
    bool Matches(const uint8_t &aSeriesId) const { return mSeriesId == aSeriesId; }
    bool IsFrameTypeMatch(uint8_t aFrameType) const;

    SeriesInfo *mNext;
    uint8_t     mSeriesId;
    SeriesFlags mSeriesFlags;
    Metrics     mMetrics;
    RssAverager mRssAverager;
    LqiAverager mLqiAverager;
    uint32_t    mPduCount;
};

/**
 * This enumeration type represent Link Metrics Status.
 *
 */
enum Status : uint8_t
{
    kStatusSuccess                   = OT_LINK_METRICS_STATUS_SUCCESS,
    kStatusCannotSupportNewSeries    = OT_LINK_METRICS_STATUS_CANNOT_SUPPORT_NEW_SERIES,
    kStatusSeriesIdAlreadyRegistered = OT_LINK_METRICS_STATUS_SERIESID_ALREADY_REGISTERED,
    kStatusSeriesIdNotRecognized     = OT_LINK_METRICS_STATUS_SERIESID_NOT_RECOGNIZED,
    kStatusNoMatchingFramesReceived  = OT_LINK_METRICS_STATUS_NO_MATCHING_FRAMES_RECEIVED,
    kStatusOtherError                = OT_LINK_METRICS_STATUS_OTHER_ERROR,
};

/**
 * This class implements Thread Link Metrics query and management.
 *
 */
class LinkMetrics : public InstanceLocator, private NonCopyable
{
    friend class ot::Neighbor;

public:
    typedef otLinkMetricsReportCallback                ReportCallback;
    typedef otLinkMetricsMgmtResponseCallback          MgmtResponseCallback;
    typedef otLinkMetricsEnhAckProbingIeReportCallback EnhAckProbingIeReportCallback;

    /**
     * This constructor initializes an instance of the LinkMetrics class.
     *
     * @param[in]  aInstance  A reference to the OpenThread interface.
     *
     */
    explicit LinkMetrics(Instance &aInstance);

    /**
     * This method sends an MLE Data Request containing Link Metrics Query TLV to query Link Metrics data.
     *
     * It could be either a Single Probe or a Forward Tracking Series.
     *
     * @param[in]  aDestination       A reference to the IPv6 address of the destination.
     * @param[in]  aSeriesId          The Series ID to query, 0 for single probe.
     * @param[in]  aMetrics           A pointer to metrics to query.
     *
     * @retval kErrorNone             Successfully sent a Link Metrics query message.
     * @retval kErrorNoBufs           Insufficient buffers to generate the MLE Data Request message.
     * @retval kErrorInvalidArgs      TypeIdFlags are not valid or exceed the count limit.
     * @retval kErrorUnknownNeighbor  @p aDestination is not link-local or the neighbor is not found.
     *
     */
    Error Query(const Ip6::Address &aDestination, uint8_t aSeriesId, const Metrics *aMetrics);

    /**
     * This method sends an MLE Link Metrics Management Request to configure/clear a Forward Tracking Series.
     *
     * @param[in] aDestination       A reference to the IPv6 address of the destination.
     * @param[in] aSeriesId          The Series ID.
     * @param[in] aSeriesFlags       The Series Flags info which specify what types of frames are to be accounted.
     * @param[in] aMetrics           A pointer to flags specifying what metrics to query.
     *
     * @retval kErrorNone             Successfully sent a Link Metrics Management Request message.
     * @retval kErrorNoBufs           Insufficient buffers to generate the MLE Link Metrics Management Request message.
     * @retval kErrorInvalidArgs      @p aSeriesId is not within the valid range.
     * @retval kErrorUnknownNeighbor  @p aDestination is not link-local or the neighbor is not found.
     *
     */
    Error SendMgmtRequestForwardTrackingSeries(const Ip6::Address &     aDestination,
                                               uint8_t                  aSeriesId,
                                               const SeriesFlags::Info &aSeriesFlags,
                                               const Metrics *          aMetrics);

#if OPENTHREAD_CONFIG_MLE_LINK_METRICS_INITIATOR_ENABLE
    /**
     * This method sends an MLE Link Metrics Management Request to configure/clear a Enhanced-ACK Based Probing.
     *
     * @param[in] aDestination       A reference to the IPv6 address of the destination.
     * @param[in] aEnhAckFlags       Enh-ACK Flags to indicate whether to register or clear the probing. `0` to clear
     *                               and `1` to register. Other values are reserved.
     * @param[in] aMetrics           A pointer to flags specifying what metrics to query. Should be `nullptr` when
     *                               `aEnhAckFlags` is `0`.
     *
     * @retval kErrorNone             Successfully sent a Link Metrics Management Request message.
     * @retval kErrorNoBufs           Insufficient buffers to generate the MLE Link Metrics Management Request message.
     * @retval kErrorInvalidArgs      @p aEnhAckFlags is not a valid value or @p aMetrics isn't correct.
     * @retval kErrorUnknownNeighbor  @p aDestination is not link-local or the neighbor is not found.
     *
     */
    Error SendMgmtRequestEnhAckProbing(const Ip6::Address &aDestination,
                                       EnhAckFlags         aEnhAckFlags,
                                       const Metrics *     aMetrics);

    /**
     * This method sends an MLE Link Probe message.
     *
     * @param[in] aDestination    A reference to the IPv6 address of the destination.
     * @param[in] aSeriesId       The Series ID which the Probe message targets at.
     * @param[in] aLength         The length of the data payload in Link Probe TLV, [0, 64].
     *
     * @retval kErrorNone             Successfully sent a Link Probe message.
     * @retval kErrorNoBufs           Insufficient buffers to generate the MLE Link Probe message.
     * @retval kErrorInvalidArgs      @p aSeriesId or @p aLength is not within the valid range.
     * @retval kErrorUnknownNeighbor  @p aDestination is not link-local or the neighbor is not found.
     *
     */
    Error SendLinkProbe(const Ip6::Address &aDestination, uint8_t aSeriesId, uint8_t aLength);
#endif

#if OPENTHREAD_CONFIG_MLE_LINK_METRICS_SUBJECT_ENABLE
    /**
     * This method appends a Link Metrics Report to a message according to the Link Metrics query.
     *
     * @param[out]  aMessage           A reference to the message to append report.
     * @param[in]   aRequestMessage    A reference to the message of the Data Request.
     * @param[in]   aNeighbor          A reference to the neighbor who queries the report.
     *
     * @retval kErrorNone         Successfully appended the Thread Discovery TLV.
     * @retval kErrorParse        Cannot parse query sub TLV successfully.
     * @retval kErrorInvalidArgs  QueryId is invalid or any Type ID is invalid.
     *
     */
    Error AppendReport(Message &aMessage, const Message &aRequestMessage, Neighbor &aNeighbor);
#endif
    /**
     * This method handles the received Link Metrics Management Request contained in @p aMessage and return a status.
     *
     * @param[in]   aMessage     A reference to the message that contains the Link Metrics Management Request.
     * @param[in]   aNeighbor    A reference to the neighbor who sends the request.
     * @param[out]  aStatus      A reference to the status which indicates the handling result.
     *
     * @retval kErrorNone     Successfully handled the Link Metrics Management Request.
     * @retval kErrorParse    Cannot parse sub-TLVs from @p aMessage successfully.
     *
     */
    Error HandleManagementRequest(const Message &aMessage, Neighbor &aNeighbor, Status &aStatus);

    /**
     * This method handles the received Link Metrics Management Response contained in @p aMessage.
     *
     * @param[in]  aMessage    A reference to the message that contains the Link Metrics Management Response.
     * @param[in]  aAddress    A reference to the source address of the message.
     *
     * @retval kErrorNone     Successfully handled the Link Metrics Management Response.
     * @retval kErrorParse    Cannot parse sub-TLVs from @p aMessage successfully.
     *
     */
    Error HandleManagementResponse(const Message &aMessage, const Ip6::Address &aAddress);

    /**
     * This method handles the received Link Metrics report contained in @p aMessage.
     *
     * @param[in]  aMessage      A reference to the message.
     * @param[in]  aOffset       The offset in bytes where the metrics report sub-TLVs start.
     * @param[in]  aLength       The length of the metrics report sub-TLVs in bytes.
     * @param[in]  aAddress      A reference to the source address of the message.
     *
     */
    void HandleReport(const Message &aMessage, uint16_t aOffset, uint16_t aLength, const Ip6::Address &aAddress);

    /**
     * This method handles the Link Probe contained in @p aMessage.
     *
     * @param[in]   aMessage     A reference to the message that contains the Link Probe Message.
     * @param[out]  aSeriesId    A reference to Series ID that parsed from the message.
     *
     * @retval kErrorNone     Successfully handled the Link Metrics Management Response.
     * @retval kErrorParse    Cannot parse sub-TLVs from @p aMessage successfully.
     *
     */
    Error HandleLinkProbe(const Message &aMessage, uint8_t &aSeriesId);

    /**
     * This method registers a callback to handle Link Metrics report received.
     *
     * @param[in]  aCallback  A pointer to a function that is called when a Link Metrics report is received.
     * @param[in]  aContext   A pointer to application-specific context.
     *
     */
    void SetReportCallback(ReportCallback aCallback, void *aContext);

    /**
     * This method registers a callback to handle Link Metrics Management Response received.
     *
     * @param[in]  aCallback A pointer to a function that is called when a Link Metrics Management Response is received.
     * @param[in]  aContext  A pointer to application-specific context.
     *
     */
    void SetMgmtResponseCallback(MgmtResponseCallback aCallback, void *aContext);

    /**
     * This method registers a callback to handle Link Metrics when Enh-ACK Probing IE is received.
     *
     * @param[in]  aCallback A pointer to a function that is called when Enh-ACK Probing IE is received is received.
     * @param[in]  aContext  A pointer to application-specific context.
     *
     */
    void SetEnhAckProbingCallback(EnhAckProbingIeReportCallback aCallback, void *aContext);

    /**
     * This method processes received Enh-ACK Probing IE data.
     *
     * @param[in] aData      A pointer to buffer containing the Enh-ACK Probing IE data.
     * @param[in] aLength    The length of @p aData.
     * @param[in] aNeighbor  The neighbor from which the Enh-ACK Probing IE was received.
     *
     */
    void ProcessEnhAckIeData(const uint8_t *aData, uint8_t aLength, const Neighbor &aNeighbor);

private:
    static constexpr uint8_t kMaxTypeIdFlags = 4;

    // Max number of SeriesInfo that could be allocated by the pool.
    static constexpr uint16_t kMaxSeriesSupported = OPENTHREAD_CONFIG_MLE_LINK_METRICS_MAX_SERIES_SUPPORTED;

    static constexpr uint8_t kQueryIdSingleProbe = 0;   // This query ID represents Single Probe.
    static constexpr uint8_t kSeriesIdAllSeries  = 255; // This series ID represents all series.
    static constexpr uint8_t kLinkProbeMaxLen    = 64;  // Max length of data payload in Link Probe TLV.

    Error SendLinkMetricsQuery(const Ip6::Address &aDestination,
                               uint8_t             aSeriesId,
                               const TypeIdFlags * aTypeIdFlags,
                               uint8_t             aTypeIdFlagsCount);

    Status ConfigureForwardTrackingSeries(uint8_t            aSeriesId,
                                          const SeriesFlags &aSeriesFlags,
                                          const Metrics &    aMetrics,
                                          Neighbor &         aNeighbor);

    Status ConfigureEnhAckProbing(EnhAckFlags aEnhAckFlags, const Metrics &aMetrics, Neighbor &aNeighbor);

    Neighbor *GetNeighborFromLinkLocalAddr(const Ip6::Address &aDestination);

    static Error ReadTypeIdFlagsFromMessage(const Message &aMessage,
                                            uint8_t        aStartPos,
                                            uint8_t        aEndPos,
                                            Metrics &      aMetrics);
    static Error AppendReportSubTlvToMessage(Message &aMessage, uint8_t &aLength, const MetricsValues &aValues);
    static Error AppendStatusSubTlvToMessage(Message &aMessage, uint8_t &aLength, Status aStatus);

    ReportCallback                mReportCallback;
    void *                        mReportCallbackContext;
    MgmtResponseCallback          mMgmtResponseCallback;
    void *                        mMgmtResponseCallbackContext;
    EnhAckProbingIeReportCallback mEnhAckProbingIeReportCallback;
    void *                        mEnhAckProbingIeReportCallbackContext;

    Pool<SeriesInfo, kMaxSeriesSupported> mSeriesInfoPool;
};

/**
 * @}
 */

} // namespace LinkMetrics

DefineCoreType(otLinkMetrics, LinkMetrics::Metrics);
DefineCoreType(otLinkMetricsValues, LinkMetrics::MetricsValues);
DefineMapEnum(otLinkMetricsEnhAckFlags, LinkMetrics::EnhAckFlags);

} // namespace ot

#endif // OPENTHREAD_CONFIG_MLE_LINK_METRICS_INITIATOR_ENABLE || OPENTHREAD_CONFIG_MLE_LINK_METRICS_SUBJECT_ENABLE

#endif // LINK_METRICS_HPP
