/*
 * Copyright (c) 2008 INRIA
 * Copyright (c) 2009 MIRKO BANCHI
 *
 * SPDX-License-Identifier: GPL-2.0-only
 *
 * Authors: Mathieu Lacage <mathieu.lacage@sophia.inria.fr>
 *          Mirko Banchi <mk.banchi@gmail.com>
 */

#ifndef WIFI_HELPER_H
#define WIFI_HELPER_H

#include "wifi-mac-helper.h"

#include "ns3/qos-utils.h"
#include "ns3/trace-helper.h"
#include "ns3/wifi-net-device.h"
#include "ns3/wifi-phy.h"

#include <functional>
#include <map>
#include <optional>
#include <vector>

namespace ns3
{

class Node;
class RadiotapHeader;
class QueueItem;

/**
 * @brief create PHY objects
 *
 * This base class must be implemented by new PHY implementation which wish to integrate
 * with the \ref ns3::WifiHelper class.
 */
class WifiPhyHelper : public PcapHelperForDevice, public AsciiTraceHelperForDevice
{
  public:
    /**
     * Constructor
     *
     * @param nLinks the number of links to configure (>1 only for 11be devices)
     */
    WifiPhyHelper(uint8_t nLinks = 1);
    ~WifiPhyHelper() override;

    /**
     * @param node the node on which the PHY object(s) will reside
     * @param device the device within which the PHY object(s) will reside
     *
     * @returns new PHY objects.
     *
     * Subclasses must implement this method to allow the ns3::WifiHelper class
     * to create PHY objects from ns3::WifiHelper::Install.
     */
    virtual std::vector<Ptr<WifiPhy>> Create(Ptr<Node> node, Ptr<WifiNetDevice> device) const = 0;

    /**
     * @param name the name of the attribute to set
     * @param v the value of the attribute
     *
     * Set an attribute of all the underlying PHY object.
     */
    void Set(std::string name, const AttributeValue& v);

    /**
     * @param name the name of the attribute to set
     * @param v the value of the attribute
     * @param linkId ID of the link to configure (>0 only for 11be devices)
     *
     * Set an attribute of the given underlying PHY object.
     */
    void Set(uint8_t linkId, std::string name, const AttributeValue& v);

    /**
     * Helper function used to set the interference helper.
     *
     * @tparam Args \deduced Template type parameter pack for the sequence of name-value pairs.
     * @param type the type of interference helper
     * @param args A sequence of name-value pairs of the attributes to set.
     */
    template <typename... Args>
    void SetInterferenceHelper(std::string type, Args&&... args);

    /**
     * Helper function used to set the error rate model.
     *
     * @tparam Args \deduced Template type parameter pack for the sequence of name-value pairs.
     * @param type the type of error rate model
     * @param args A sequence of name-value pairs of the attributes to set.
     */
    template <typename... Args>
    void SetErrorRateModel(std::string type, Args&&... args);

    /**
     * @tparam Args \deduced Template type parameter pack for the sequence of name-value pairs.
     * @param linkId ID of the link to configure (>0 only for 11be devices)
     * @param type the type of the error rate model to set.
     * @param args A sequence of name-value pairs of the attributes to set.
     *
     * Set the error rate model and its attributes to use for the given link when Install is called.
     */
    template <typename... Args>
    void SetErrorRateModel(uint8_t linkId, std::string type, Args&&... args);

    /**
     * Helper function used to set the frame capture model.
     *
     * @tparam Args \deduced Template type parameter pack for the sequence of name-value pairs.
     * @param type the type of frame capture model
     * @param args A sequence of name-value pairs of the attributes to set.
     */
    template <typename... Args>
    void SetFrameCaptureModel(std::string type, Args&&... args);

    /**
     * @tparam Args \deduced Template type parameter pack for the sequence of name-value pairs.
     * @param linkId ID of the link to configure (>0 only for 11be devices)
     * @param type the type of the frame capture model to set.
     * @param args A sequence of name-value pairs of the attributes to set.
     *
     * Set the frame capture model and its attributes to use for the given link when Install is
     * called.
     */
    template <typename... Args>
    void SetFrameCaptureModel(uint8_t linkId, std::string type, Args&&... args);

    /**
     * Helper function used to set the preamble detection model.
     *
     * @tparam Args \deduced Template type parameter pack for the sequence of name-value pairs.
     * @param type the type of preamble detection model
     * @param args A sequence of name-value pairs of the attributes to set.
     */
    template <typename... Args>
    void SetPreambleDetectionModel(std::string type, Args&&... args);

    /**
     * @tparam Args \deduced Template type parameter pack for the sequence of name-value pairs.
     * @param linkId ID of the link to configure (>0 only for 11be devices)
     * @param type the type of the preamble detection model to set.
     * @param args A sequence of name-value pairs of the attributes to set.
     *
     * Set the preamble detection model and its attributes to use for the given link when Install is
     * called.
     */
    template <typename... Args>
    void SetPreambleDetectionModel(uint8_t linkId, std::string type, Args&&... args);

    /**
     * Disable the preamble detection model on all links.
     */
    void DisablePreambleDetectionModel();

    /**
     * An enumeration of the pcap data link types (DLTs) which this helper
     * supports.  See http://wiki.wireshark.org/Development/LibpcapFileFormat
     * for more information on these formats.
     */
    enum SupportedPcapDataLinkTypes
    {
        DLT_IEEE802_11 =
            PcapHelper::DLT_IEEE802_11, /**< IEEE 802.11 Wireless LAN headers on packets */
        DLT_PRISM_HEADER =
            PcapHelper::DLT_PRISM_HEADER, /**< Include Prism monitor mode information */
        DLT_IEEE802_11_RADIO =
            PcapHelper::DLT_IEEE802_11_RADIO /**< Include Radiotap link layer information */
    };

    /**
     * An enumeration of the PCAP capture types.
     * The PCAP capture type only matters for multi-link devices.
     */
    enum class PcapCaptureType
    {
        PCAP_PER_DEVICE, /**< Single capture file per device */
        PCAP_PER_PHY,    /**< Single capture file per PHY */
        PCAP_PER_LINK,   /**< Single capture file per link */
    };

    /**
     * structure holding the information about PCAP files generated for a given device
     */
    struct PcapFilesInfo
    {
        /**
         * Constructor
         *
         * @param filename the common file name of the PCAP files
         * @param dlt the selected data link type of the pcap file
         * @param type the selected PCAP capture type
         * @param dev the WifiNetDevice for which the PCAP files are generated
         */
        PcapFilesInfo(const std::string& filename,
                      PcapHelper::DataLinkType dlt,
                      WifiPhyHelper::PcapCaptureType type,
                      Ptr<WifiNetDevice> dev)
            : commonFilename{filename},
              pcapDlt{dlt},
              pcapType{type},
              device{dev},
              files{}
        {
        }

        std::string commonFilename;              ///< common file name of the PCAP files
        PcapHelper::DataLinkType pcapDlt;        ///< the selected data link type of the pcap file
        WifiPhyHelper::PcapCaptureType pcapType; ///< the selected PCAP capture type
        Ptr<WifiNetDevice> device; ///< the WifiNetDevice for which the PCAP files are generated
        std::map<uint8_t, Ptr<PcapFileWrapper>> files; ///< PCAP files indexed by PHY ID
    };

    /**
     * Set the data link type of PCAP traces to be used. This function has to be
     * called before EnablePcap(), so that the header of the pcap file can be
     * written correctly.
     *
     * @see SupportedPcapDataLinkTypes
     *
     * @param dlt The data link type of the pcap file (and packets) to be used
     */
    void SetPcapDataLinkType(SupportedPcapDataLinkTypes dlt);

    /**
     * Get the data link type of PCAP traces to be used.
     *
     * @see SupportedPcapDataLinkTypes
     *
     * @returns The data link type of the pcap file (and packets) to be used
     */
    PcapHelper::DataLinkType GetPcapDataLinkType() const;

    /**
     * Set the PCAP capture type to be used. This function has to be called before EnablePcap().
     *
     * @see PcapCaptureType
     *
     * @param type The PCAP capture type
     */
    void SetPcapCaptureType(PcapCaptureType type);

    /**
     * Get the PCAP capture type to be used.
     *
     * @see PcapCaptureType
     *
     * @return The PCAP capture type to be used
     */
    PcapCaptureType GetPcapCaptureType() const;

  protected:
    /**
     * @param info the information needed to write to the correct PCAP file
     * @param phyId the ID of the PHY that raised the event
     * @param packet the packet
     * @param channelFreqMhz the channel frequency
     * @param txVector the TXVECTOR
     * @param aMpdu the A-MPDU information
     * @param staId the STA-ID (only used for MU)
     *
     * Handle TX pcap.
     */
    static void PcapSniffTxEvent(const std::shared_ptr<PcapFilesInfo>& info,
                                 uint8_t phyId,
                                 Ptr<const Packet> packet,
                                 uint16_t channelFreqMhz,
                                 WifiTxVector txVector,
                                 MpduInfo aMpdu,
                                 uint16_t staId = SU_STA_ID);
    /**
     * @param info the information needed to write to the correct PCAP file
     * @param phyId the ID of the PHY that raised the event
     * @param packet the packet
     * @param channelFreqMhz the channel frequency
     * @param txVector the TXVECTOR
     * @param aMpdu the A-MPDU information
     * @param signalNoise the RX signal and noise information
     * @param staId the STA-ID (only used for MU)
     *
     * Handle RX pcap.
     */
    static void PcapSniffRxEvent(const std::shared_ptr<PcapFilesInfo>& info,
                                 uint8_t phyId,
                                 Ptr<const Packet> packet,
                                 uint16_t channelFreqMhz,
                                 WifiTxVector txVector,
                                 MpduInfo aMpdu,
                                 SignalNoiseDbm signalNoise,
                                 uint16_t staId = SU_STA_ID);

    std::vector<ObjectFactory> m_phys;                   ///< PHY objects
    ObjectFactory m_interferenceHelper;                  ///< interference helper
    std::vector<ObjectFactory> m_errorRateModel;         ///< error rate model
    std::vector<ObjectFactory> m_frameCaptureModel;      ///< frame capture model
    std::vector<ObjectFactory> m_preambleDetectionModel; ///< preamble detection model

  private:
    /**
     * Get the PCAP file to write to from a list of PCAP files indexed by PHY ID.
     * If PCAP files are generated per link, it might create a new file if a link
     * has swapped to a new ID.
     *
     * @param info the information needed to write to the correct PCAP file
     * @param phyId the ID of the PHY that raised the event
     * @return the PCAP file to write to
     */
    static Ptr<PcapFileWrapper> GetOrCreatePcapFile(const std::shared_ptr<PcapFilesInfo>& info,
                                                    uint8_t phyId);

    /**
     * Get the Radiotap header for a received packet.
     *
     * @param packet the packet
     * @param channelFreqMhz the channel frequency
     * @param p20Index the index of the primary20 channel
     * @param txVector the TXVECTOR
     * @param aMpdu the A-MPDU information
     * @param staId the STA-ID
     * @param signalNoise the rx signal and noise information (for receiver only)
     * @return the radiotap header
     */
    static RadiotapHeader GetRadiotapHeader(
        Ptr<Packet> packet,
        uint16_t channelFreqMhz,
        uint8_t p20Index,
        const WifiTxVector& txVector,
        MpduInfo aMpdu,
        uint16_t staId,
        std::optional<SignalNoiseDbm> signalNoise = std::nullopt);

    /**
     * @brief Enable pcap output the indicated net device.
     *
     * NetDevice-specific implementation mechanism for hooking the trace and
     * writing to the trace file.
     *
     * @param prefix Filename prefix to use for pcap files.
     * @param nd Net device for which you want to enable tracing.
     * @param promiscuous If true capture all possible packets available at the device.
     * @param explicitFilename Treat the prefix as an explicit filename if true
     */
    void EnablePcapInternal(std::string prefix,
                            Ptr<NetDevice> nd,
                            bool promiscuous,
                            bool explicitFilename) override;

    /**
     * @brief Enable ASCII trace output on the indicated net device.
     *
     * NetDevice-specific implementation mechanism for hooking the trace and
     * writing to the trace file.
     *
     * @param stream The output stream object to use when logging ASCII traces.
     * @param prefix Filename prefix to use for ASCII trace files.
     * @param nd Net device for which you want to enable tracing.
     * @param explicitFilename Treat the prefix as an explicit filename if true
     */
    void EnableAsciiInternal(Ptr<OutputStreamWrapper> stream,
                             std::string prefix,
                             Ptr<NetDevice> nd,
                             bool explicitFilename) override;

    PcapHelper::DataLinkType m_pcapDlt; ///< PCAP data link type
    PcapCaptureType m_pcapType;         ///< PCAP capture type
};

/**
 * @brief helps to create WifiNetDevice objects
 *
 * This class can help to create a large set of similar
 * WifiNetDevice objects and to configure a large set of
 * their attributes during creation.
 */
class WifiHelper
{
  public:
    virtual ~WifiHelper();

    /**
     * Create a Wifi helper in an empty state: all its parameters
     * must be set before calling ns3::WifiHelper::Install
     *
     * The default state is defined as being an Adhoc MAC layer with an ARF rate control algorithm
     * and both objects using their default attribute values.
     * By default, configure MAC and PHY for 802.11ax.
     */
    WifiHelper();

    /**
     * Helper function used to set the station manager
     *
     * @tparam Args \deduced Template type parameter pack for the sequence of name-value pairs.
     * @param type the type of station manager
     * @param args A sequence of name-value pairs of the attributes to set.
     */
    template <typename... Args>
    void SetRemoteStationManager(std::string type, Args&&... args);

    /**
     * @tparam Args \deduced Template type parameter pack for the sequence of name-value pairs.
     * @param linkId ID of the link to configure (>0 only for 11be devices)
     * @param type the type of the preamble detection model to set.
     * @param args A sequence of name-value pairs of the attributes to set.
     *
     * Set the remote station manager model and its attributes to use for the given link.
     * If the helper stored a remote station manager model for the first N links only
     * (corresponding to link IDs from 0 to N-1) and the given linkId is M >= N, then a
     * remote station manager model using the given attributes is configured for all links
     * with ID from N to M.
     */
    template <typename... Args>
    void SetRemoteStationManager(uint8_t linkId, std::string type, Args&&... args);

    /**
     * Helper function used to set the OBSS-PD algorithm
     *
     * @tparam Args \deduced Template type parameter pack for the sequence of name-value pairs.
     * @param type the type of OBSS-PD algorithm
     * @param args A sequence of name-value pairs of the attributes to set.
     */
    template <typename... Args>
    void SetObssPdAlgorithm(std::string type, Args&&... args);

    /// Callback invoked to determine the MAC queue selected for a given packet
    typedef std::function<std::size_t(Ptr<QueueItem>)> SelectQueueCallback;

    /**
     * @param f the select queue callback
     *
     * Set the select queue callback to set on the NetDevice queue interface aggregated
     * to the WifiNetDevice, in case WifiMac with QoS enabled is used
     */
    void SetSelectQueueCallback(SelectQueueCallback f);

    /**
     * Disable flow control only if you know what you are doing. By disabling
     * flow control, this NetDevice will be sent packets even if there is no
     * room for them (such packets will be likely dropped by this NetDevice).
     * Also, any queue disc installed on this NetDevice will have no effect,
     * as every packet enqueued to the traffic control layer queue disc will
     * be immediately dequeued.
     */
    void DisableFlowControl();

    /**
     * @param phy the PHY helper to create PHY objects
     * @param mac the MAC helper to create MAC objects
     * @param first lower bound on the set of nodes on which a wifi device must be created
     * @param last upper bound on the set of nodes on which a wifi device must be created
     * @returns a device container which contains all the devices created by this method.
     */
    NetDeviceContainer virtual Install(const WifiPhyHelper& phy,
                                       const WifiMacHelper& mac,
                                       NodeContainer::Iterator first,
                                       NodeContainer::Iterator last) const;
    /**
     * @param phy the PHY helper to create PHY objects
     * @param mac the MAC helper to create MAC objects
     * @param c the set of nodes on which a wifi device must be created
     * @returns a device container which contains all the devices created by this method.
     */
    virtual NetDeviceContainer Install(const WifiPhyHelper& phy,
                                       const WifiMacHelper& mac,
                                       NodeContainer c) const;
    /**
     * @param phy the PHY helper to create PHY objects
     * @param mac the MAC helper to create MAC objects
     * @param node the node on which a wifi device must be created
     * @returns a device container which contains all the devices created by this method.
     */
    virtual NetDeviceContainer Install(const WifiPhyHelper& phy,
                                       const WifiMacHelper& mac,
                                       Ptr<Node> node) const;
    /**
     * @param phy the PHY helper to create PHY objects
     * @param mac the MAC helper to create MAC objects
     * @param nodeName the name of node on which a wifi device must be created
     * @returns a device container which contains all the devices created by this method.
     */
    virtual NetDeviceContainer Install(const WifiPhyHelper& phy,
                                       const WifiMacHelper& mac,
                                       std::string nodeName) const;
    /**
     * @param standard the standard to configure during installation
     *
     * This method sets standards-compliant defaults for WifiMac
     * parameters such as SIFS time, slot time, timeout values, etc.,
     * based on the standard selected.  It results in
     * WifiMac::ConfigureStandard(standard) being called on each
     * installed MAC object.
     *
     * The default standard of 802.11ax will be applied if SetStandard()
     * is not called.
     *
     * Note that WifiMac::ConfigureStandard () will overwrite certain
     * defaults in the attribute system, so if a user wants to manipulate
     * any default values affected by ConfigureStandard() while using this
     * helper, the user should use a post-install configuration such as
     * Config::Set() on any objects that this helper creates, such as:
     * @code
     * Config::Set ("/NodeList/0/DeviceList/0/$ns3::WifiNetDevice/Mac/Slot", TimeValue (MicroSeconds
     * (slot))); \endcode
     *
     * \sa WifiMac::ConfigureStandard
     * \sa Config::Set
     */
    virtual void SetStandard(WifiStandard standard);

    /**
     * @param standard String representation of the Wi-Fi standard
     *
     * This method overloads WifiHelper::SetStandard(WifiStandard standard) by allowing
     * selected string names. For example, the strings "802.11ax", "11ax", and "HE"
     * are equivalent and map to WIFI_STANDARD_80211ax. See the documentation of the specified
     * function to see how it interacts with attribute configuration.
     * \sa WifiHelper::SetStandard(WifiStandard standard)
     */
    void SetStandard(const std::string& standard);

    /**
     * Helper function used to configure the HT options listed as attributes of
     * the HtConfiguration class.
     *
     * @tparam Args \deduced Template type parameter pack for the sequence of name-value pairs.
     * @param args A sequence of name-value pairs of the attributes to set.
     */
    template <typename... Args>
    void ConfigHtOptions(Args&&... args);

    /**
     * Helper function used to configure the VHT options listed as attributes of
     * the VhtConfiguration class.
     *
     * @tparam Args \deduced Template type parameter pack for the sequence of name-value pairs.
     * @param args A sequence of name-value pairs of the attributes to set.
     */
    template <typename... Args>
    void ConfigVhtOptions(Args&&... args);

    /**
     * Helper function used to configure the HE options listed as attributes of
     * the HeConfiguration class.
     *
     * @tparam Args \deduced Template type parameter pack for the sequence of name-value pairs.
     * @param args A sequence of name-value pairs of the attributes to set.
     */
    template <typename... Args>
    void ConfigHeOptions(Args&&... args);

    /**
     * Helper function used to configure the EHT options listed as attributes of
     * the EhtConfiguration class.
     *
     * @tparam Args \deduced Template type parameter pack for the sequence of name-value pairs.
     * @param args A sequence of name-value pairs of the attributes to set.
     */
    template <typename... Args>
    void ConfigEhtOptions(Args&&... args);

    /**
     * Helper to enable all WifiNetDevice log components with one statement
     * @param logLevel (optional) log level setting
     */
    static void EnableLogComponents(LogLevel logLevel = LOG_LEVEL_ALL);

    /**
     * Assign a fixed random variable stream number to the random variables
     * used by the PHY and MAC aspects of the Wifi models.  Each device in
     * container c has fixed stream numbers assigned to its random variables.
     * The Wifi channel (e.g. propagation loss model) is excluded.
     * Return the number of streams (possibly zero) that
     * have been assigned. The Install() method should have previously been
     * called by the user.
     *
     * @param c NetDeviceContainer of the set of net devices for which the
     *          WifiNetDevice should be modified to use fixed streams
     * @param stream first stream index to use
     * @return the number of stream indices assigned by this helper
     */
    static int64_t AssignStreams(NetDeviceContainer c, int64_t stream);

  protected:
    mutable std::vector<ObjectFactory> m_stationManager; ///< station manager
    WifiStandard m_standard;                             ///< wifi standard
    ObjectFactory m_htConfig;                            ///< HT configuration
    ObjectFactory m_vhtConfig;                           ///< VHT configuration
    ObjectFactory m_heConfig;                            ///< HE configuration
    ObjectFactory m_ehtConfig;                           ///< EHT configuration
    SelectQueueCallback m_selectQueueCallback;           ///< select queue callback
    ObjectFactory m_obssPdAlgorithm;                     ///< OBSS_PD algorithm
    bool m_enableFlowControl;                            //!< whether to enable flow control
};

} // namespace ns3

/***************************************************************
 *  Implementation of the templates declared above.
 ***************************************************************/

namespace ns3
{

template <typename... Args>
void
WifiPhyHelper::SetInterferenceHelper(std::string type, Args&&... args)
{
    m_interferenceHelper.SetTypeId(type);
    m_interferenceHelper.Set(args...);
}

template <typename... Args>
void
WifiPhyHelper::SetErrorRateModel(std::string type, Args&&... args)
{
    for (std::size_t linkId = 0; linkId < m_phys.size(); linkId++)
    {
        SetErrorRateModel(linkId, type, std::forward<Args>(args)...);
    }
}

template <typename... Args>
void
WifiPhyHelper::SetErrorRateModel(uint8_t linkId, std::string type, Args&&... args)
{
    m_errorRateModel.at(linkId).SetTypeId(type);
    m_errorRateModel.at(linkId).Set(args...);
}

template <typename... Args>
void
WifiPhyHelper::SetFrameCaptureModel(std::string type, Args&&... args)
{
    for (std::size_t linkId = 0; linkId < m_phys.size(); linkId++)
    {
        SetFrameCaptureModel(linkId, type, std::forward<Args>(args)...);
    }
}

template <typename... Args>
void
WifiPhyHelper::SetFrameCaptureModel(uint8_t linkId, std::string type, Args&&... args)
{
    m_frameCaptureModel.at(linkId).SetTypeId(type);
    m_frameCaptureModel.at(linkId).Set(args...);
}

template <typename... Args>
void
WifiPhyHelper::SetPreambleDetectionModel(std::string type, Args&&... args)
{
    for (std::size_t linkId = 0; linkId < m_phys.size(); linkId++)
    {
        SetPreambleDetectionModel(linkId, type, std::forward<Args>(args)...);
    }
}

template <typename... Args>
void
WifiPhyHelper::SetPreambleDetectionModel(uint8_t linkId, std::string type, Args&&... args)
{
    m_preambleDetectionModel.at(linkId).SetTypeId(type);
    m_preambleDetectionModel.at(linkId).Set(args...);
}

template <typename... Args>
void
WifiHelper::SetRemoteStationManager(std::string type, Args&&... args)
{
    SetRemoteStationManager(0, type, std::forward<Args>(args)...);
}

template <typename... Args>
void
WifiHelper::SetRemoteStationManager(uint8_t linkId, std::string type, Args&&... args)
{
    if (m_stationManager.size() > linkId)
    {
        m_stationManager[linkId] = ObjectFactory(type, std::forward<Args>(args)...);
    }
    else
    {
        m_stationManager.resize(linkId + 1, ObjectFactory(type, std::forward<Args>(args)...));
    }
}

template <typename... Args>
void
WifiHelper::SetObssPdAlgorithm(std::string type, Args&&... args)
{
    m_obssPdAlgorithm.SetTypeId(type);
    m_obssPdAlgorithm.Set(args...);
}

template <typename... Args>
void
WifiHelper::ConfigHtOptions(Args&&... args)
{
    m_htConfig.Set(args...);
}

template <typename... Args>
void
WifiHelper::ConfigVhtOptions(Args&&... args)
{
    m_vhtConfig.Set(args...);
}

template <typename... Args>
void
WifiHelper::ConfigHeOptions(Args&&... args)
{
    m_heConfig.Set(args...);
}

template <typename... Args>
void
WifiHelper::ConfigEhtOptions(Args&&... args)
{
    m_ehtConfig.Set(args...);
}

} // namespace ns3

#endif /* WIFI_HELPER_H */
