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

#ifndef LINK_METRICS_MANAGER_HPP_
#define LINK_METRICS_MANAGER_HPP_

#include "openthread-core-config.h"

#if OPENTHREAD_CONFIG_LINK_METRICS_MANAGER_ENABLE

#if OPENTHREAD_CONFIG_MLE_LINK_METRICS_INITIATOR_ENABLE == 0
#error \
    "OPENTHREAD_CONFIG_LINK_METRICS_MANAGER_ENABLE can only be used when OPENTHREAD_CONFIG_MLE_LINK_METRICS_INITIATOR_ENABLE is true"
#endif

#include <openthread/link_metrics.h>

#include "common/clearable.hpp"
#include "common/linked_list.hpp"
#include "common/locator.hpp"
#include "common/non_copyable.hpp"
#include "common/notifier.hpp"
#include "common/pool.hpp"
#include "common/time.hpp"
#include "common/timer.hpp"
#include "mac/mac_types.hpp"
#include "thread/link_metrics_types.hpp"

namespace ot {
class UnitTester;
namespace Utils {

/**
 * @addtogroup utils-link-metrics-manager
 *
 * @brief
 *   This module includes definitions for Link Metrics Manager.
 *
 * @{
 */

/**
 *
 * Link Metrics Manager feature utilizes the Enhanced-ACK Based
 * Probing (abbreviated as "EAP" below) to get the Link Metrics
 * data of neighboring devices. It is a user of the Link Metrics
 * feature.
 *
 * The feature works as follow:
 * - Start/Stop
 *   The switch `OPENTHREAD_CONFIG_LINK_METRICS_MANAGER_ON_BY_DEFAULT`
 *   controls enabling/disabling this feature by default. The feature
 *   will only start to work after it joins a Thread network.
 *
 *   A CLI interface is provided to enable/disable this feature.
 *
 *   Once enabled, it will regularly check current neighbors (all
 *   devices in the neighbor table, including 'Child' and 'Router')
 *   and configure the probing with them if haven't done that.
 *   If disabled, it will clear the configuration with its subjects
 *   and the local data.
 *
 * - Maintenance
 *   The manager will regularly check the status of each subject. If
 *   it finds that the link metrics data for one subject hasn't been
 *   updated for `kStateUpdateIntervalMilliSec`, it will configure
 *   EAP with the subject again.
 *   The manager may find that some subject (neighbor) no longer
 *   exist when trying to configure EAP. It will remove the stale
 *   subject then.
 *
 * - Show data
 *   An OT API is provided to get the link metrics data of any
 *   subject (neighbor) by its extended address. In production, this
 *   data may be fetched by some other means like RPC.
 *
 */

class LinkMetricsManager : public InstanceLocator, private NonCopyable
{
    friend class ot::Notifier;
    friend class ot::UnitTester;

    struct LinkMetricsData
    {
        uint8_t mLqi;        ///< Link Quality Indicator. Value range: [0, 255].
        int8_t  mRssi;       ///< Receive Signal Strength Indicator. Value range: [-128, 0].
        uint8_t mLinkMargin; ///< Link Margin. The relative signal strength is recorded as
                             ///< db above the local noise floor. Value range: [0, 130].
    };

    enum SubjectState : uint8_t
    {
        kNotConfigured = 0,
        kConfiguring,
        kActive,
        kRenewing,
        kNotSupported,
    };

    struct Subject : LinkedListEntry<Subject>, Clearable<Subject>
    {
        Mac::ExtAddress mExtAddress;     ///< Use the extended address to identify the neighbor.
        SubjectState    mState;          ///< Current State of the Subject
        uint8_t         mAttempts;       ///< The count of attempt that has been made to
                                         ///< configure EAP
        TimeMilli       mLastUpdateTime; ///< The time `mData` was updated last time
        LinkMetricsData mData;

        Subject *mNext;

        bool Matches(const Mac::ExtAddress &aExtAddress) const { return mExtAddress == aExtAddress; }
        bool Matches(const LinkMetricsManager &aLinkMetricsMgr);

        Error ConfigureEap(Instance &aInstance);
        Error UnregisterEap(Instance &aInstance);
        Error UpdateState(Instance &aInstance);
    };

public:
    /**
     * Initializes a `LinkMetricsManager` object.
     *
     * @param[in]   aInstance  A reference to the OpenThread instance.
     *
     */
    explicit LinkMetricsManager(Instance &aInstance);

    /**
     * Is the LinkMetricsManager feature enabled.
     *
     * @retval TRUE   Link Metrics Manager is enabled.
     * @retval FALSE  Link Metrics Manager is not enabled.
     *
     */
    bool IsEnabled(void) { return mEnabled; }

    /**
     * Enable/Disable the LinkMetricsManager feature.
     *
     * @param[in]   aEnable  A boolean to indicate enable or disable.
     *
     */
    void SetEnabled(bool aEnable);

    /**
     * Get Link Metrics data of subject by the extended address.
     *
     * @param[in]  aExtAddress     A reference to the extended address of the subject.
     * @param[out] aMetricsValues  A reference to the MetricsValues object to place the result.
     *
     * @retval kErrorNone             Successfully got the metrics value.
     * @retval kErrorInvalidArgs      The arguments are invalid.
     * @retval kNotFound              No neighbor with the given extended address is found.
     *
     */
    Error GetLinkMetricsValueByExtAddr(const Mac::ExtAddress &aExtAddress, LinkMetrics::MetricsValues &aMetricsValues);

private:
    static constexpr uint16_t kTimeBeforeStartMilliSec         = 5000;
    static constexpr uint32_t kStateUpdateIntervalMilliSec     = 150000;
    static constexpr uint8_t  kConfigureLinkMetricsMaxAttempts = 3;
#if OPENTHREAD_FTD
    static constexpr uint8_t kMaximumSubjectToTrack = 128;
#elif OPENTHREAD_MTD
    static constexpr uint8_t kMaximumSubjectToTrack = 1;
#endif

    void Start(void);
    void Stop(void);
    void Update(void);
    void UpdateSubjects(void);
    void UpdateLinkMetricsStates(void);
    void UnregisterAllSubjects(void);
    void ReleaseAllSubjects(void);

    void        HandleNotifierEvents(Events aEvents);
    void        HandleTimer(void);
    static void HandleMgmtResponse(const otIp6Address *aAddress, otLinkMetricsStatus aStatus, void *aContext);
    void        HandleMgmtResponse(const otIp6Address *aAddress, otLinkMetricsStatus aStatus);
    static void HandleEnhAckIe(otShortAddress             aShortAddress,
                               const otExtAddress        *aExtAddress,
                               const otLinkMetricsValues *aMetricsValues,
                               void                      *aContext);
    void        HandleEnhAckIe(otShortAddress             aShortAddress,
                               const otExtAddress        *aExtAddress,
                               const otLinkMetricsValues *aMetricsValues);

    using LinkMetricsMgrTimer = TimerMilliIn<LinkMetricsManager, &LinkMetricsManager::HandleTimer>;

    Pool<Subject, kMaximumSubjectToTrack> mPool;
    LinkedList<Subject>                   mSubjectList;
    LinkMetricsMgrTimer                   mTimer;
    bool                                  mEnabled;
};

/**
 * @}
 *
 */

} // namespace Utils
} // namespace ot

#endif // OPENTHREAD_CONFIG_LINK_METRICS_MANAGER_ENABLE

#endif // LINK_METRICS_MANAGER_HPP_
