/*
 * Copyright (c) 2008 INRIA
 *
 * SPDX-License-Identifier: GPL-2.0-only
 *
 * Author: Mathieu Lacage <mathieu.lacage@sophia.inria.fr>
 */

#ifndef WIFI_MAC_H
#define WIFI_MAC_H

#include "mgt-headers.h"
#include "qos-utils.h"
#include "ssid.h"
#include "wifi-mac-queue-scheduler.h"
#include "wifi-remote-station-manager.h"
#include "wifi-standards.h"

#include "ns3/uniform-random-bit-generator.h"

#include <functional>
#include <list>
#include <map>
#include <memory>
#include <optional>
#include <set>
#include <unordered_map>
#include <vector>

namespace ns3
{

class Txop;
class WifiNetDevice;
class QosTxop;
class WifiPsdu;
class MacRxMiddle;
class MacTxMiddle;
class WifiMacQueue;
class WifiMpdu;
class HtConfiguration;
class VhtConfiguration;
class HeConfiguration;
class EhtConfiguration;
class FrameExchangeManager;
class ChannelAccessManager;
class ExtendedCapabilities;
class OriginatorBlockAckAgreement;
class RecipientBlockAckAgreement;
class UniformRandomVariable;
enum class WifiIcfDrop : uint8_t; // opaque enum declaration

/**
 * @ingroup wifi
 * Enumeration for type of WiFi station
 */
enum TypeOfStation
{
    STA,
    AP,
    ADHOC_STA,
    MESH,
    OCB
};

/**
 * @ingroup wifi
 * @brief The reason why an MPDU was dropped
 */
enum WifiMacDropReason : uint8_t
{
    WIFI_MAC_DROP_FAILED_ENQUEUE = 0,
    WIFI_MAC_DROP_EXPIRED_LIFETIME,
    WIFI_MAC_DROP_REACHED_RETRY_LIMIT,
    WIFI_MAC_DROP_QOS_OLD_PACKET
};

typedef std::unordered_map<uint16_t /* staId */, Ptr<WifiPsdu> /* PSDU */> WifiPsduMap;

/**
 * @brief base class for all MAC-level wifi objects.
 * @ingroup wifi
 *
 * This class encapsulates all the low-level MAC functionality
 * DCA, EDCA, etc) and all the high-level MAC functionality
 * (association/disassociation state machines).
 *
 */
class WifiMac : public Object
{
  public:
    friend class WifiStaticSetupHelper;

    /**
     * @brief Get the type ID.
     * @return the object TypeId
     */
    static TypeId GetTypeId();

    WifiMac();
    ~WifiMac() override;

    // Delete copy constructor and assignment operator to avoid misuse
    WifiMac(const WifiMac&) = delete;
    WifiMac& operator=(const WifiMac&) = delete;

    /// type of the management frames
    using MgtFrameType =
        std::variant<MgtBeaconHeader, MgtProbeResponseHeader, MgtAssocResponseHeader>;

    /**
     * Assign a fixed random variable stream number to the random variables
     * used by this model.  Return the number of streams (possibly zero) that
     * have been assigned.
     *
     * @param stream first stream index to use
     *
     * @return the number of stream indices assigned by this model
     */
    virtual int64_t AssignStreams(int64_t stream);

    /**
     * Sets the device this PHY is associated with.
     *
     * @param device the device this PHY is associated with
     */
    void SetDevice(const Ptr<WifiNetDevice> device);
    /**
     * Return the device this PHY is associated with
     *
     * @return the device this PHY is associated with
     */
    Ptr<WifiNetDevice> GetDevice() const;

    /**
     * Get the Frame Exchange Manager associated with the given link
     *
     * @param linkId the ID of the given link
     * @return the Frame Exchange Manager
     */
    Ptr<FrameExchangeManager> GetFrameExchangeManager(uint8_t linkId = SINGLE_LINK_OP_ID) const;

    /**
     * @param feManagers the frame exchange managers attached to this MAC.
     */
    void SetFrameExchangeManagers(const std::vector<Ptr<FrameExchangeManager>>& feManagers);

    /**
     * Get the Channel Access Manager associated with the given link
     *
     * @param linkId the ID of the given link
     * @return the Channel Access Manager
     */
    Ptr<ChannelAccessManager> GetChannelAccessManager(uint8_t linkId = SINGLE_LINK_OP_ID) const;

    /**
     * @param caManagers the channel access managers attached to this MAC.
     */
    void SetChannelAccessManagers(const std::vector<Ptr<ChannelAccessManager>>& caManagers);

    /**
     * Get the number of links (can be greater than 1 for 11be devices only).
     *
     * @return the number of links used by this MAC
     */
    uint8_t GetNLinks() const;

    /**
     * @return the set of link IDs in use by this device
     */
    const std::set<uint8_t>& GetLinkIds() const;

    /**
     * Get the ID of the link having the given MAC address, if any.
     *
     * @param address the given MAC address
     * @return the ID of the link having the given MAC address, if any
     */
    virtual std::optional<uint8_t> GetLinkIdByAddress(const Mac48Address& address) const;

    /**
     * Get the ID of the link (if any) on which the given PHY is operating.
     *
     * @param phy the given PHY
     * @return the ID of the link (if any) on which the given PHY is operating
     */
    std::optional<uint8_t> GetLinkForPhy(Ptr<const WifiPhy> phy) const;

    /**
     * Get the ID of the link (if any) on which the given PHY is operating.
     *
     * @param phyId the index of the given PHY in the vector of PHYs held by WifiNetDevice
     * @return the ID of the link (if any) on which the given PHY is operating
     */
    std::optional<uint8_t> GetLinkForPhy(std::size_t phyId) const;

    /**
     * Indicate if a given link is on the 6 GHz band.
     *
     * @param linkId the ID of the given link
     * @return whether the given link is on the 6 GHz band
     */
    bool Is6GhzBand(uint8_t linkId) const;

    /**
     * @param remoteAddr the (MLD or link) address of a remote device
     * @return the MLD address of the remote device having the given (MLD or link) address, if
     *         the remote device is an MLD.
     */
    std::optional<Mac48Address> GetMldAddress(const Mac48Address& remoteAddr) const;

    /**
     * Get the local MAC address used to communicate with a remote STA. Specifically:
     * - If the given remote address is the address of a STA affiliated with a remote MLD
     * and operating on a setup link, the address of the local STA operating on such a link
     * is returned.
     * - If the given remote address is the MLD address of a remote MLD (with which some link
     * has been setup), the MLD address of this device is returned.
     * - If this is a single link device, the unique MAC address of this device is returned.
     * - Otherwise, return the MAC address of the affiliated STA (which must exists) that
     * can be used to communicate with the remote device.
     *
     * @param remoteAddr the MAC address of the remote device
     * @return the local MAC address used to communicate with the remote device
     */
    Mac48Address GetLocalAddress(const Mac48Address& remoteAddr) const;

    /**
     * Accessor for the Txop object
     *
     * @return a smart pointer to Txop
     */
    Ptr<Txop> GetTxop() const;
    /**
     * Accessor for a specified EDCA object
     *
     * @param ac the Access Category
     * @return a smart pointer to a QosTxop
     */
    Ptr<QosTxop> GetQosTxop(AcIndex ac) const;
    /**
     * Accessor for a specified EDCA object
     *
     * @param tid the Traffic ID
     * @return a smart pointer to a QosTxop
     */
    Ptr<QosTxop> GetQosTxop(uint8_t tid) const;
    /**
     * Get the wifi MAC queue of the (Qos)Txop associated with the given AC,
     * if such (Qos)Txop is installed, or a null pointer, otherwise.
     *
     * @param ac the given Access Category
     * @return the wifi MAC queue of the (Qos)Txop associated with the given AC,
     *         if such (Qos)Txop is installed, or a null pointer, otherwise
     */
    virtual Ptr<WifiMacQueue> GetTxopQueue(AcIndex ac) const;

    /**
     * Check if the MAC has frames to transmit over the given link
     * @param linkId the ID of the given link.
     * @return whether the MAC has frames to transmit.
     */
    virtual bool HasFramesToTransmit(uint8_t linkId);

    /**
     * Set the wifi MAC queue scheduler
     *
     * @param scheduler the wifi MAC queue scheduler
     */
    virtual void SetMacQueueScheduler(Ptr<WifiMacQueueScheduler> scheduler);
    /**
     * Get the wifi MAC queue scheduler
     *
     * @return the wifi MAC queue scheduler
     */
    Ptr<WifiMacQueueScheduler> GetMacQueueScheduler() const;

    /**
     * This method is invoked by a subclass to specify what type of
     * station it is implementing. This is something that the channel
     * access functions need to know.
     *
     * @param type the type of station.
     */
    void SetTypeOfStation(TypeOfStation type);
    /**
     * Return the type of station.
     *
     * @return the type of station.
     */
    TypeOfStation GetTypeOfStation() const;

    /**
     * @param ssid the current SSID of this MAC layer.
     */
    void SetSsid(Ssid ssid);
    /**
     * @brief Sets the interface in promiscuous mode.
     *
     * Enables promiscuous mode on the interface. Note that any further
     * filtering on the incoming frame path may affect the overall
     * behavior.
     */
    void SetPromisc();
    /**
     * Enable or disable CTS-to-self feature.
     *
     * @param enable true if CTS-to-self is to be supported,
     *               false otherwise
     */
    void SetCtsToSelfSupported(bool enable);

    /**
     * @return the MAC address associated to this MAC layer.
     */
    Mac48Address GetAddress() const;
    /**
     * @return the SSID which this MAC layer is going to try to stay in.
     */
    Ssid GetSsid() const;
    /**
     * @param address the current address of this MAC layer.
     */
    virtual void SetAddress(Mac48Address address);
    /**
     * @return the BSSID of the network the given link belongs to.
     * @param linkId the ID of the given link
     */
    Mac48Address GetBssid(uint8_t linkId) const;
    /**
     * @param bssid the BSSID of the network that the given link belongs to.
     * @param linkId the ID of the given link
     */
    void SetBssid(Mac48Address bssid, uint8_t linkId);

    /**
     * Block the transmission on the given links of all unicast frames addressed to
     * the station with the given address for the given reason. The given MAC address
     * must be the MLD address in case the addressed device is multi-link.
     *
     * @param reason the reason for blocking transmissions
     * @param address the MAC address of the given device
     * @param linkIds the IDs of the links to block
     */
    void BlockUnicastTxOnLinks(WifiQueueBlockedReason reason,
                               Mac48Address address,
                               const std::set<uint8_t>& linkIds);

    /**
     * Unblock the transmission on the given links of all unicast frames addressed to
     * the station with the given address for the given reason. The given MAC address
     * must be the MLD address in case the addressed device is multi-link.
     *
     * @param reason the reason for unblocking transmissions
     * @param address the MAC address of the given device
     * @param linkIds the IDs of the links to unblock
     */
    void UnblockUnicastTxOnLinks(WifiQueueBlockedReason reason,
                                 Mac48Address address,
                                 const std::set<uint8_t>& linkIds);

    /**
     * Check whether the transmission of the packets in the given container queue of the given
     * Access Category are blocked on the given link for the given reason (if any).
     *
     * @param ac the given Access Category
     * @param queueId the given container queue
     * @param linkId the ID of the given link
     * @param reason the reason to block transmissions (REASONS_COUNT indicate no reason)
     * @return whether transmission is blocked
     */
    bool GetTxBlockedOnLink(
        AcIndex ac,
        const WifiContainerQueueId& queueId,
        uint8_t linkId,
        WifiQueueBlockedReason reason = WifiQueueBlockedReason::REASONS_COUNT) const;

    /**
     * Return true if packets can be forwarded to the given destination,
     * false otherwise.
     *
     * @param to the address to which the packet should be sent
     * @return whether packets can be forwarded to the given destination
     */
    virtual bool CanForwardPacketsTo(Mac48Address to) const = 0;

    /**
     * @param packet the packet to send.
     * @param to the address to which the packet should be sent.
     *
     * The packet should be enqueued in a TX queue, and should be
     * dequeued as soon as the DCF/EDCA function determines that
     * access is granted to this MAC.
     */
    void Enqueue(Ptr<Packet> packet, Mac48Address to);

    /**
     * @param packet the packet to send.
     * @param to the address to which the packet should be sent.
     * @param from the address from which the packet should be sent.
     *
     * The packet should be enqueued in a TX queue, and should be
     * dequeued as soon as the DCF/EDCA function determines that
     * access is granted to this MAC. The extra parameter "from" allows
     * this device to operate in a bridged mode, forwarding received
     * frames without altering the source address.
     */
    void Enqueue(Ptr<Packet> packet, Mac48Address to, Mac48Address from);

    /**
     * @param packet the packet to send.
     * @param to the address to which the packet should be sent.
     * @param from the address from which the packet should be sent.
     * @param tid the TID to use to send this packet
     *
     * The packet should be enqueued in a TX queue, and should be
     * dequeued as soon as the DCF/EDCA function determines that
     * access is granted to this MAC. The extra parameter "tid" allows
     * to specify the TID to use in case QoS is supported.
     */
    void Enqueue(Ptr<Packet> packet, Mac48Address to, Mac48Address from, uint8_t tid);

    /**
     * @return if this MAC supports sending from arbitrary address.
     *
     * The interface may or may not support sending from arbitrary address.
     * This function returns true if sending from arbitrary address is supported,
     * false otherwise.
     */
    virtual bool SupportsSendFrom() const;

    /**
     * @param phys the physical layers attached to this MAC.
     */
    virtual void SetWifiPhys(const std::vector<Ptr<WifiPhy>>& phys);
    /**
     * @param linkId the index (starting at 0) of the PHY object to retrieve
     * @return the physical layer attached to this MAC
     */
    Ptr<WifiPhy> GetWifiPhy(uint8_t linkId = SINGLE_LINK_OP_ID) const;
    /**
     * Remove currently attached WifiPhy objects from this MAC.
     */
    void ResetWifiPhys();

    /**
     * @param stationManager the station manager attached to this MAC.
     */
    void SetWifiRemoteStationManager(Ptr<WifiRemoteStationManager> stationManager);
    /**
     * @param stationManagers the station managers attached to this MAC.
     */
    void SetWifiRemoteStationManagers(
        const std::vector<Ptr<WifiRemoteStationManager>>& stationManagers);
    /**
     * @param linkId the ID (starting at 0) of the link of the RemoteStationManager object
     * to retrieve
     * @return the remote station manager operating on the given link
     */
    Ptr<WifiRemoteStationManager> GetWifiRemoteStationManager(uint8_t linkId = 0) const;

    /**
     * This type defines the callback of a higher layer that a
     * WifiMac(-derived) object invokes to pass a packet up the stack.
     *
     * @param packet the packet that has been received.
     * @param from the MAC address of the device that sent the packet.
     * @param to the MAC address of the device that the packet is destined for.
     */
    typedef Callback<void, Ptr<const Packet>, Mac48Address, Mac48Address> ForwardUpCallback;

    /**
     * @param upCallback the callback to invoke when a packet must be
     *        forwarded up the stack.
     */
    void SetForwardUpCallback(ForwardUpCallback upCallback);
    /**
     * @param linkUp the callback to invoke when the link becomes up.
     */
    virtual void SetLinkUpCallback(Callback<void> linkUp);
    /**
     * @param linkDown the callback to invoke when the link becomes down.
     */
    void SetLinkDownCallback(Callback<void> linkDown);
    /* Next functions are not pure virtual so non QoS WifiMacs are not
     * forced to implement them.
     */

    /**
     * Notify that channel on the given link has been switched.
     *
     * @param linkId the ID of the given link
     */
    virtual void NotifyChannelSwitching(uint8_t linkId);

    /**
     * @param packet the packet being enqueued
     *
     * Public method used to fire a MacTx trace. Implemented for encapsulation purposes.
     * Note this trace indicates that the packet was accepted by the device only.
     * The packet may be dropped later (e.g. if the queue is full).
     */
    void NotifyTx(Ptr<const Packet> packet);
    /**
     * @param packet the packet being dropped
     *
     * Public method used to fire a MacTxDrop trace.
     * This trace indicates that the packet was dropped before it was queued for
     * transmission (e.g. when a STA is not associated with an AP).
     */
    void NotifyTxDrop(Ptr<const Packet> packet);
    /**
     * @param packet the packet we received
     *
     * Public method used to fire a MacRx trace. Implemented for encapsulation purposes.
     */
    void NotifyRx(Ptr<const Packet> packet);
    /**
     * @param packet the packet we received promiscuously
     *
     * Public method used to fire a MacPromiscRx trace. Implemented for encapsulation purposes.
     */
    void NotifyPromiscRx(Ptr<const Packet> packet);
    /**
     * @param packet the packet we received but is not destined for us
     *
     * Public method used to fire a MacRxDrop trace. Implemented for encapsulation purposes.
     */
    void NotifyRxDrop(Ptr<const Packet> packet);

    /**
     * @return pointer to HtConfiguration if it exists
     */
    Ptr<HtConfiguration> GetHtConfiguration() const;
    /**
     * @return pointer to VhtConfiguration if it exists
     */
    Ptr<VhtConfiguration> GetVhtConfiguration() const;
    /**
     * @return pointer to HeConfiguration if it exists
     */
    Ptr<HeConfiguration> GetHeConfiguration() const;
    /**
     * @return pointer to EhtConfiguration if it exists
     */
    Ptr<EhtConfiguration> GetEhtConfiguration() const;

    /**
     * Return the extended capabilities of the device.
     *
     * @return the extended capabilities that we support
     */
    ExtendedCapabilities GetExtendedCapabilities() const;
    /**
     * Return the HT capabilities of the device for the given link.
     *
     * @param linkId the ID of the given link
     * @return the HT capabilities that we support
     */
    HtCapabilities GetHtCapabilities(uint8_t linkId) const;
    /**
     * Return the VHT capabilities of the device for the given link.
     *
     * @param linkId the ID of the given link
     * @return the VHT capabilities that we support
     */
    VhtCapabilities GetVhtCapabilities(uint8_t linkId) const;
    /**
     * Return the HE capabilities of the device for the given link.
     *
     * @param linkId the ID of the given link
     * @return the HE capabilities that we support
     */
    HeCapabilities GetHeCapabilities(uint8_t linkId) const;
    /**
     * Return the HE 6GHz band capabilities of the device for the given 6 GHz link.
     *
     * @param linkId the ID of the given 6 GHz link
     * @return the HE 6GHz band capabilities that we support
     */
    He6GhzBandCapabilities GetHe6GhzBandCapabilities(uint8_t linkId) const;
    /**
     * Return the EHT capabilities of the device for the given link.
     *
     * @param linkId the ID of the given link
     * @return the EHT capabilities that we support
     */
    EhtCapabilities GetEhtCapabilities(uint8_t linkId) const;

    /**
     * Return whether the device supports QoS.
     *
     * @return true if QoS is supported, false otherwise
     */
    bool GetQosSupported() const;
    /**
     * Return whether the device supports ERP on the given link.
     *
     * @param linkId the ID of the given link
     * @return true if ERP is supported, false otherwise
     */
    bool GetErpSupported(uint8_t linkId) const;
    /**
     * Return whether the device supports DSSS on the given link.
     *
     * @param linkId the ID of the given link
     * @return true if DSSS is supported, false otherwise
     */
    bool GetDsssSupported(uint8_t linkId) const;
    /**
     * Return whether the device supports HT on the given link.
     *
     * @param linkId the ID of the given link.
     * @return true if HT is supported, false otherwise
     */
    bool GetHtSupported(uint8_t linkId) const;
    /**
     * Return whether the device supports VHT on the given link.
     *
     * @param linkId the ID of the given link.
     * @return true if VHT is supported, false otherwise
     */
    bool GetVhtSupported(uint8_t linkId) const;
    /**
     * Return whether the device supports HE.
     *
     * @return true if HE is supported, false otherwise
     */
    bool GetHeSupported() const;
    /**
     * Return whether the device supports EHT.
     *
     * @return true if EHT is supported, false otherwise
     */
    bool GetEhtSupported() const;

    /**
     * @param address the (link or MLD) address of a remote station
     * @return true if the remote station with the given address supports HT
     */
    bool GetHtSupported(const Mac48Address& address) const;
    /**
     * @param address the (link or MLD) address of a remote station
     * @return true if the remote station with the given address supports VHT
     */
    bool GetVhtSupported(const Mac48Address& address) const;
    /**
     * @param address the (link or MLD) address of a remote station
     * @return true if the remote station with the given address supports HE
     */
    bool GetHeSupported(const Mac48Address& address) const;
    /**
     * @param address the (link or MLD) address of a remote station
     * @return true if the remote station with the given address supports EHT
     */
    bool GetEhtSupported(const Mac48Address& address) const;

    /**
     * Enable or disable Robust AV Streaming support for the device.
     *
     * @param enable whether Robust AV Streaming is supported
     */
    void SetRobustAVStreamingSupported(bool enable);
    /**
     * Return whether the device supports Robust AV Streaming.
     *
     * @return true if Robust AV Streaming is supported, false otherwise
     */
    bool GetRobustAVStreamingSupported() const;

    /**
     * Return the maximum A-MPDU size of the given Access Category.
     *
     * @param ac Access Category index
     * @return the maximum A-MPDU size
     */
    uint32_t GetMaxAmpduSize(AcIndex ac) const;
    /**
     * Return the maximum A-MSDU size of the given Access Category.
     *
     * @param ac Access Category index
     * @return the maximum A-MSDU size
     */
    uint16_t GetMaxAmsduSize(AcIndex ac) const;

    /// optional const reference to OriginatorBlockAckAgreement
    using OriginatorAgreementOptConstRef =
        std::optional<std::reference_wrapper<const OriginatorBlockAckAgreement>>;
    /// optional const reference to RecipientBlockAckAgreement
    using RecipientAgreementOptConstRef =
        std::optional<std::reference_wrapper<const RecipientBlockAckAgreement>>;

    /**
     * @param recipient (link or device) MAC address of the recipient
     * @param tid traffic ID
     * @param gcrGroupAddr the GCR Group Address (only if it is a GCR Block Ack agreement)
     *
     * @return the originator block ack agreement, if one has been established
     *
     * Checks if an originator block ack agreement is established with station addressed by
     * <i>recipient</i> for TID <i>tid</i>.
     */
    OriginatorAgreementOptConstRef GetBaAgreementEstablishedAsOriginator(
        Mac48Address recipient,
        uint8_t tid,
        std::optional<Mac48Address> gcrGroupAddr = std::nullopt) const;
    /**
     * @param originator (link or device) MAC address of the originator
     * @param tid traffic ID
     * @param gcrGroupAddr the GCR Group Address (only if it is a GCR Block Ack agreement)
     *
     * @return the recipient block ack agreement, if one has been established
     *
     * Checks if a recipient block ack agreement is established with station addressed by
     * <i>originator</i> for TID <i>tid</i>.
     */
    RecipientAgreementOptConstRef GetBaAgreementEstablishedAsRecipient(
        Mac48Address originator,
        uint8_t tid,
        std::optional<Mac48Address> gcrGroupAddr = std::nullopt) const;

    /**
     * @param recipient MAC address
     * @param tid traffic ID
     *
     * @return the type of Block Acks sent by the recipient
     *
     * This function returns the type of Block Acks sent by the recipient.
     */
    BlockAckType GetBaTypeAsOriginator(const Mac48Address& recipient, uint8_t tid) const;
    /**
     * @param recipient MAC address of recipient
     * @param tid traffic ID
     *
     * @return the type of Block Ack Requests sent to the recipient
     *
     * This function returns the type of Block Ack Requests sent to the recipient.
     */
    BlockAckReqType GetBarTypeAsOriginator(const Mac48Address& recipient, uint8_t tid) const;
    /**
     * @param originator MAC address of originator
     * @param tid traffic ID
     *
     * @return the type of Block Acks sent to the originator
     *
     * This function returns the type of Block Acks sent to the originator.
     */
    BlockAckType GetBaTypeAsRecipient(Mac48Address originator, uint8_t tid) const;
    /**
     * @param originator MAC address of originator
     * @param tid traffic ID
     *
     * @return the type of Block Ack Requests sent by the originator
     *
     * This function returns the type of Block Ack Requests sent by the originator.
     */
    BlockAckReqType GetBarTypeAsRecipient(Mac48Address originator, uint8_t tid) const;

    /**
     * Get the maximum Block Ack buffer size (in number of MPDUs) supported by the given device,
     * if any, or by this device, otherwise, based on the supported standard.
     *
     * @param address the (MLD or link) address of the given device
     * @return the maximum supported Block Ack buffer size (in number of MPDUs)
     */
    uint16_t GetMaxBaBufferSize(std::optional<Mac48Address> address = std::nullopt) const;

    /**
     * @param size the size (in number of MPDUs) of the buffer used for each BlockAck
     *             agreement in which this node is a recipient
     */
    void SetMpduBufferSize(uint16_t size);

    /**
     * @return the size (in number of MPDUs) of the buffer used for each BlockAck
     *             agreement in which this node is a recipient
     */
    uint16_t GetMpduBufferSize() const;

    /**
     * Set the frame retry limit.
     *
     * @param limit the frame retry limit
     */
    void SetFrameRetryLimit(uint32_t limit);

    /**
     * @return the frame retry limit
     */
    uint32_t GetFrameRetryLimit() const;

    /**
     * Get the TID-to-Link Mapping negotiated with the given MLD (if any) for the given direction.
     * An empty mapping indicates the default mapping.
     *
     * @param mldAddr the MLD address of the given MLD
     * @param dir the given direction (DL or UL)
     * @return the negotiated TID-to-Link Mapping
     */
    std::optional<std::reference_wrapper<const WifiTidLinkMapping>> GetTidToLinkMapping(
        Mac48Address mldAddr,
        WifiDirection dir) const;

    /**
     * Check whether the given TID is mapped on the given link in the given direction for the
     * given MLD.
     *
     * @param mldAddr the MLD address of the given MLD
     * @param dir the given direction (DL or UL)
     * @param tid the given TID
     * @param linkId the ID of the given link
     * @return whether the given TID is mapped on the given link in the given direction for the
     *         given MLD
     */
    bool TidMappedOnLink(Mac48Address mldAddr,
                         WifiDirection dir,
                         uint8_t tid,
                         uint8_t linkId) const;

    /// Information reported by ICF drop trace
    struct IcfDropInfo
    {
        WifiIcfDrop reason{}; ///< the reason why the ICF was dropped by the EMLSR client
        uint8_t linkId{};     ///< the ID of the link on which the ICF was dropped
        Mac48Address sender;  ///< the sender of the ICF
    };

    /**
     * TracedCallback signature for ICF drop events.
     *
     * @param info information reported by ICF drop trace
     */
    typedef void (*IcfDropCallback)(const IcfDropInfo& info);

    /// TracedCallback for ICF drop events typedef
    using IcfDropTracedCallback = TracedCallback<const IcfDropInfo&>;

  protected:
    void DoInitialize() override;
    void DoDispose() override;
    void NotifyConstructionCompleted() override;

    /**
     * @param cwMin the minimum contention window size
     * @param cwMax the maximum contention window size
     *
     * This method is called to set the minimum and the maximum
     * contention window size.
     */
    virtual void ConfigureContentionWindow(uint32_t cwMin, uint32_t cwMax);

    /**
     * Enable or disable short slot time feature.
     *
     * @param enable true if short slot time is to be supported,
     *               false otherwise
     */
    void SetShortSlotTimeSupported(bool enable);
    /**
     * @return whether the device supports short slot time capability.
     */
    bool GetShortSlotTimeSupported() const;

    /**
     * Accessor for the AC_VO channel access function
     *
     * @return a smart pointer to QosTxop
     */
    Ptr<QosTxop> GetVOQueue() const;
    /**
     * Accessor for the AC_VI channel access function
     *
     * @return a smart pointer to QosTxop
     */
    Ptr<QosTxop> GetVIQueue() const;
    /**
     * Accessor for the AC_BE channel access function
     *
     * @return a smart pointer to QosTxop
     */
    Ptr<QosTxop> GetBEQueue() const;
    /**
     * Accessor for the AC_BK channel access function
     *
     * @return a smart pointer to QosTxop
     */
    Ptr<QosTxop> GetBKQueue() const;

    /**
     * This method acts as the MacRxMiddle receive callback and is
     * invoked to notify us that a frame has been received on the given link.
     * The implementation is intended to capture logic that is going to be
     * common to all (or most) derived classes. Specifically, handling
     * of Block Ack management frames is dealt with here.
     *
     * This method will need, however, to be overridden by derived
     * classes so that they can perform their data handling before
     * invoking the base version.
     *
     * The given link may be undefined in some cases (e.g., in case of
     * QoS Data frames received in the context of a Block Ack agreement --
     * because the BlockAckManager does not have to record the link each
     * buffered MPDU has been received on); in such a cases, the value
     * of <i>linkId</i> should be WIFI_LINKID_UNDEFINED.
     *
     * @param mpdu the MPDU that has been received.
     * @param linkId the ID of the given link
     */
    virtual void Receive(Ptr<const WifiMpdu> mpdu, uint8_t linkId);
    /**
     * Forward the packet up to the device.
     *
     * @param packet the packet that we are forwarding up to the device
     * @param from the address of the source
     * @param to the address of the destination
     */
    void ForwardUp(Ptr<const Packet> packet, Mac48Address from, Mac48Address to);

    /**
     * This method can be called to de-aggregate an A-MSDU and forward
     * the constituent packets up the stack.
     *
     * @param mpdu the MPDU containing the A-MSDU.
     */
    virtual void DeaggregateAmsduAndForward(Ptr<const WifiMpdu> mpdu);

    /**
     * Apply the TID-to-Link Mapping negotiated with the given MLD for the given direction
     * by properly configuring the queue scheduler.
     *
     * @param mldAddr the MLD MAC address of the given MLD
     * @param dir the given direction (DL or UL)
     */
    void ApplyTidLinkMapping(const Mac48Address& mldAddr, WifiDirection dir);

    /**
     * Swap the links based on the information included in the given map. This method
     * is normally called by a non-AP MLD upon completing ML setup to have its link IDs
     * match AP MLD's link IDs.
     *
     * @param links a set of pairs (from, to) each mapping a current link ID to the
     *              link ID it has to become (i.e., link 'from' becomes link 'to')
     */
    void SwapLinks(std::map<uint8_t, uint8_t> links);

    /**
     * Structure holding information specific to a single link. Here, the meaning of
     * "link" is that of the 11be amendment which introduced multi-link devices. For
     * previous amendments, only one link can be created. Therefore, "link" has not
     * to be confused with the general concept of link for a NetDevice (used by the
     * m_linkUp and m_linkDown callbacks).
     */
    struct LinkEntity
    {
        /// Destructor (a virtual method is needed to make this struct polymorphic)
        virtual ~LinkEntity();

        Ptr<WifiPhy> phy;                               //!< Wifi PHY object
        Ptr<ChannelAccessManager> channelAccessManager; //!< channel access manager object
        Ptr<FrameExchangeManager> feManager;            //!< Frame Exchange Manager object
        Ptr<WifiRemoteStationManager> stationManager;   //!< Remote station manager (rate control,
                                                        //!< RTS/CTS/fragmentation thresholds etc.)
        bool erpSupported{false};  //!< set to \c true iff this WifiMac is to model 802.11g
        bool dsssSupported{false}; //!< set to \c true iff this WifiMac is to model 802.11b
    };

    /**
     * @return a const reference to the map of link entities
     */
    const std::map<uint8_t, std::unique_ptr<LinkEntity>>& GetLinks() const;

    /**
     * Get a reference to the link associated with the given ID.
     *
     * @param linkId the given link ID
     * @return a reference to the link associated with the given ID
     */
    LinkEntity& GetLink(uint8_t linkId) const;

    /**
     * Update the TID-to-Link Mappings for the given MLD in the given direction based on the
     * given negotiated mappings. An empty mapping indicates the default mapping.
     *
     * @param mldAddr the MLD address of the given MLD
     * @param dir the given direction (DL or UL)
     * @param mapping the negotiated TID-to-Link Mapping
     */
    void UpdateTidToLinkMapping(const Mac48Address& mldAddr,
                                WifiDirection dir,
                                const WifiTidLinkMapping& mapping);

    /**
     * Update capabilities information from the given management frame.
     *
     * @param frame the body of the given management frame
     * @param addr MAC address of the sender
     * @param linkId ID of the link the management frame was received over
     */
    void RecordCapabilities(const MgtFrameType& frame, const Mac48Address& addr, uint8_t linkId);

    UniformRandomBitGenerator m_shuffleLinkIdsGen; //!< random number generator to shuffle link IDs

    Ptr<MacRxMiddle> m_rxMiddle; //!< RX middle (defragmentation etc.)
    Ptr<MacTxMiddle> m_txMiddle; //!< TX middle (aggregation etc.)
    Ptr<Txop> m_txop;            //!< TXOP used for transmission of frames to non-QoS peers.
    Ptr<WifiMacQueueScheduler> m_scheduler; //!< wifi MAC queue scheduler

    Callback<void> m_linkUp;   //!< Callback when a link is up
    Callback<void> m_linkDown; //!< Callback when a link is down

  private:
    /**
     * Complete the configuration of the MAC layer components.
     */
    void CompleteConfig();

    /**
     * Allow subclasses to complete the configuration of the MAC layer components.
     */
    virtual void DoCompleteConfig() = 0;

    /**
     * @param dcf the DCF to be configured
     * @param cwmin the minimum contention window for the DCF
     * @param cwmax the maximum contention window for the DCF
     * @param isDsss vector of flags to indicate whether PHY is DSSS or HR/DSSS for every link
     * @param ac the access category for the DCF
     *
     * Configure the DCF with appropriate values depending on the given access category.
     */
    void ConfigureDcf(Ptr<Txop> dcf,
                      uint32_t cwmin,
                      uint32_t cwmax,
                      std::list<bool> isDsss,
                      AcIndex ac);

    /**
     * Configure PHY dependent parameters such as CWmin and CWmax on the given link.
     *
     * @param linkId the ID of the given link
     */
    void ConfigurePhyDependentParameters(uint8_t linkId);

    /**
     * Enable or disable QoS support for the device. Construct a Txop object or QosTxop objects
     * accordingly. This method is private so that it is only used while constructing this object.
     *
     * @param enable whether QoS is supported
     */
    void SetQosSupported(bool enable);

    /**
     * Set the Txop object.
     * This method is private so that it is only used while constructing this object.
     *
     * @param dcf the Txop object
     */
    void SetTxop(Ptr<Txop> dcf);

    /**
     * Set the AC_VO channel access function
     * This method is private so that it is only used while constructing this object.
     *
     * @param edca the QosTxop object for AC_VO
     */
    void SetVoQueue(Ptr<QosTxop> edca);

    /**
     * Set the AC_VI channel access function
     * This method is private so that it is only used while constructing this object.
     *
     * @param edca the QosTxop object for AC_VI
     */
    void SetViQueue(Ptr<QosTxop> edca);

    /**
     * Set the AC_BE channel access function
     * This method is private so that it is only used while constructing this object.
     *
     * @param edca the QosTxop object for AC_BE
     */
    void SetBeQueue(Ptr<QosTxop> edca);

    /**
     * Set the AC_BK channel access function
     * This method is private so that it is only used while constructing this object.
     *
     * @param edca the QosTxop object for AC_BK
     */
    void SetBkQueue(Ptr<QosTxop> edca);

    /**
     * This method is a private utility invoked to configure the channel
     * access function for devices that do not support QoS.
     */
    void SetupDcfQueue();

    /**
     * This method is a private utility invoked to configure the channel
     * access function for the specified Access Category.
     *
     * @param ac the Access Category of the queue to initialise.
     */
    void SetupEdcaQueue(AcIndex ac);

    /**
     * If no link has been already created, create the given number links; otherwise, do nothing.
     *
     * @param nLinks the given number of links
     * @return whether the given number of links have been created
     */
    bool CreateLinksIfNeeded(std::size_t nLinks);

    /**
     * Create a LinkEntity object.
     *
     * @return a unique pointer to the created LinkEntity object
     */
    virtual std::unique_ptr<LinkEntity> CreateLinkEntity() const;

    /**
     * This method is intended to be called when a link changes ID in order to update the
     * link ID stored by the Frame Exchange Manager and the Channel Access Manager operating
     * on that link.
     *
     * @param id the (new) ID of the link that has changed ID
     */
    void UpdateLinkId(uint8_t id);

    /**
     * This method is called if this device is an MLD to determine the MAC address of
     * the affiliated STA used to communicate with the single link device having the
     * given MAC address. This method is overridden because its implementation depends
     * on the type of station.
     *
     * @param remoteAddr the MAC address of the remote single link device
     * @return the MAC address of the affiliated STA used to communicate with the remote device
     */
    virtual Mac48Address DoGetLocalAddress(const Mac48Address& remoteAddr) const;

    /**
     * Enable or disable ERP support for the given link.
     *
     * @param enable whether ERP is supported
     * @param linkId the ID of the given link
     */
    void SetErpSupported(bool enable, uint8_t linkId);
    /**
     * Enable or disable DSSS support for the given link.
     *
     * @param enable whether DSSS is supported
     * @param linkId the ID of the given link
     */
    void SetDsssSupported(bool enable, uint8_t linkId);

    /**
     * Set the block ack threshold for AC_VO.
     *
     * @param threshold the block ack threshold for AC_VO.
     */
    void SetVoBlockAckThreshold(uint8_t threshold);
    /**
     * Set the block ack threshold for AC_VI.
     *
     * @param threshold the block ack threshold for AC_VI.
     */
    void SetViBlockAckThreshold(uint8_t threshold);
    /**
     * Set the block ack threshold for AC_BE.
     *
     * @param threshold the block ack threshold for AC_BE.
     */
    void SetBeBlockAckThreshold(uint8_t threshold);
    /**
     * Set the block ack threshold for AC_BK.
     *
     * @param threshold the block ack threshold for AC_BK.
     */
    void SetBkBlockAckThreshold(uint8_t threshold);

    /**
     * Set VO block ack inactivity timeout.
     *
     * @param timeout the VO block ack inactivity timeout.
     */
    void SetVoBlockAckInactivityTimeout(uint16_t timeout);
    /**
     * Set VI block ack inactivity timeout.
     *
     * @param timeout the VI block ack inactivity timeout.
     */
    void SetViBlockAckInactivityTimeout(uint16_t timeout);
    /**
     * Set BE block ack inactivity timeout.
     *
     * @param timeout the BE block ack inactivity timeout.
     */
    void SetBeBlockAckInactivityTimeout(uint16_t timeout);
    /**
     * Set BK block ack inactivity timeout.
     *
     * @param timeout the BK block ack inactivity timeout.
     */
    void SetBkBlockAckInactivityTimeout(uint16_t timeout);

    /**
     * @param mpdu the MPDU to send.
     * @param to the address to which the packet should be sent.
     * @param from the address from which the packet should be sent.
     *
     * Subclasses need to implement this method to finalize the MAC header of the MPDU
     * (MAC addresses and ToDS/FromDS flags) and enqueue the MPDU in a TX queue.
     */
    virtual void Enqueue(Ptr<WifiMpdu> mpdu, Mac48Address to, Mac48Address from) = 0;

    /**
     * Allow subclasses to take actions when a packet to enqueue has been dropped.
     *
     * @param packet the dropped packet
     * @param to the address to which the packet should have been sent
     */
    virtual void NotifyDropPacketToEnqueue(Ptr<Packet> packet, Mac48Address to);

    /**
     * Notify the remote station manager if the given expired (hence dropped) MPDU is a management
     * or data frame (with a unicast receiver address) that has been already transmitted and is not
     * in flight (otherwise, it is handled when the ack is received or the TX timeout expires).
     *
     * @param mpdu the expired MPDU
     */
    void NotifyRsmOfExpiredMpdu(Ptr<const WifiMpdu> mpdu);

    /**
     * This Boolean is set \c true iff this WifiMac is to model
     * 802.11e/WMM style Quality of Service. It is exposed through the
     * attribute system.
     *
     * At the moment, this flag is the sole selection between QoS and
     * non-QoS operation for the STA (whether IBSS, AP, or
     * non-AP). Ultimately, we will want a QoS-enabled STA to be able to
     * fall back to non-QoS operation with a non-QoS peer. This'll
     * require further intelligence - i.e., per-association QoS
     * state. Having a big switch seems like a good intermediate stage,
     * however.
     */
    bool m_qosSupported;

    bool m_shortSlotTimeSupported; ///< flag whether short slot time is supported
    bool m_ctsToSelfSupported;     ///< flag indicating whether CTS-To-Self is supported

    TypeOfStation m_typeOfStation; //!< the type of station

    Ptr<WifiNetDevice> m_device;                            //!< Pointer to the device
    std::map<uint8_t, std::unique_ptr<LinkEntity>> m_links; //!< ID-indexed map of Link objects
    std::set<uint8_t> m_linkIds;                            //!< IDs of the links in use

    Mac48Address m_address; //!< MAC address of this station
    Ssid m_ssid;            //!< Service Set ID (SSID)

    /** This type defines a mapping between an Access Category index,
    and a pointer to the corresponding channel access function.
    Access Categories are sorted in decreasing order of priority. */
    typedef std::map<AcIndex, Ptr<QosTxop>, std::greater<>> EdcaQueues;

    /** This is a map from Access Category index to the corresponding
    channel access function */
    EdcaQueues m_edca;

    uint16_t m_voMaxAmsduSize; ///< maximum A-MSDU size for AC_VO (in bytes)
    uint16_t m_viMaxAmsduSize; ///< maximum A-MSDU size for AC_VI (in bytes)
    uint16_t m_beMaxAmsduSize; ///< maximum A-MSDU size for AC_BE (in bytes)
    uint16_t m_bkMaxAmsduSize; ///< maximum A-MSDU size for AC_BK (in bytes)

    uint32_t m_voMaxAmpduSize; ///< maximum A-MPDU size for AC_VO (in bytes)
    uint32_t m_viMaxAmpduSize; ///< maximum A-MPDU size for AC_VI (in bytes)
    uint32_t m_beMaxAmpduSize; ///< maximum A-MPDU size for AC_BE (in bytes)
    uint32_t m_bkMaxAmpduSize; ///< maximum A-MPDU size for AC_BK (in bytes)

    uint16_t m_mpduBufferSize;  //!< BlockAck buffer size (in number of MPDUs)
    uint32_t m_frameRetryLimit; //!< the frame retry limit

    bool m_robustAVStreamingSupported; ///< flag whether robust AV streaming is supported

    /// @brief DL TID-to-Link Mapping negotiated with an MLD (identified by its MLD address)
    std::unordered_map<Mac48Address, WifiTidLinkMapping, WifiAddressHash> m_dlTidLinkMappings;
    /// @brief UL TID-to-Link Mapping negotiated with an MLD (identified by its MLD address)
    std::unordered_map<Mac48Address, WifiTidLinkMapping, WifiAddressHash> m_ulTidLinkMappings;

    ForwardUpCallback m_forwardUp; //!< Callback to forward packet up the stack

    /**
     * The trace source fired when packets come into the "top" of the device
     * at the L3/L2 transition, before being queued for transmission.
     *
     * @see class CallBackTraceSource
     */
    TracedCallback<Ptr<const Packet>> m_macTxTrace;
    /**
     * The trace source fired when packets coming into the "top" of the device
     * are dropped at the MAC layer before being queued for transmission.
     *
     * @see class CallBackTraceSource
     */
    TracedCallback<Ptr<const Packet>> m_macTxDropTrace;
    /**
     * The trace source fired for packets successfully received by the device
     * immediately before being forwarded up to higher layers (at the L2/L3
     * transition).  This is a promiscuous trace.
     *
     * @see class CallBackTraceSource
     */
    TracedCallback<Ptr<const Packet>> m_macPromiscRxTrace;
    /**
     * The trace source fired for packets successfully received by the device
     * immediately before being forwarded up to higher layers (at the L2/L3
     * transition).  This is a non- promiscuous trace.
     *
     * @see class CallBackTraceSource
     */
    TracedCallback<Ptr<const Packet>> m_macRxTrace;
    /**
     * The trace source fired when packets coming into the "top" of the device
     * are dropped at the MAC layer during reception.
     *
     * @see class CallBackTraceSource
     */
    TracedCallback<Ptr<const Packet>> m_macRxDropTrace;

    /**
     * TracedCallback signature for MPDU drop events.
     *
     * @param reason the reason why the MPDU was dropped (\see WifiMacDropReason)
     * @param mpdu the dropped MPDU
     */
    typedef void (*DroppedMpduCallback)(WifiMacDropReason reason, Ptr<const WifiMpdu> mpdu);

    /// TracedCallback for MPDU drop events typedef
    typedef TracedCallback<WifiMacDropReason, Ptr<const WifiMpdu>> DroppedMpduTracedCallback;

    /**
     * This trace indicates that an MPDU was dropped for the given reason.
     */
    DroppedMpduTracedCallback m_droppedMpduCallback;

    /// TracedCallback for acked/nacked MPDUs typedef
    typedef TracedCallback<Ptr<const WifiMpdu>> MpduTracedCallback;

    MpduTracedCallback m_ackedMpduCallback;  ///< ack'ed MPDU callback
    MpduTracedCallback m_nackedMpduCallback; ///< nack'ed MPDU callback

    /**
     * TracedCallback signature for MPDU response timeout events.
     *
     * @param reason the reason why the timer was started
     * @param mpdu the MPDU whose response was not received before the timeout
     * @param txVector the TXVECTOR used to transmit the MPDU
     */
    typedef void (*MpduResponseTimeoutCallback)(uint8_t reason,
                                                Ptr<const WifiMpdu> mpdu,
                                                const WifiTxVector& txVector);

    /// TracedCallback for MPDU response timeout events typedef
    typedef TracedCallback<uint8_t, Ptr<const WifiMpdu>, const WifiTxVector&>
        MpduResponseTimeoutTracedCallback;

    /**
     * MPDU response timeout traced callback.
     * This trace source is fed by a WifiTxTimer object.
     */
    MpduResponseTimeoutTracedCallback m_mpduResponseTimeoutCallback;

    /**
     * TracedCallback signature for PSDU response timeout events.
     *
     * @param reason the reason why the timer was started
     * @param psdu the PSDU whose response was not received before the timeout
     * @param txVector the TXVECTOR used to transmit the PSDU
     */
    typedef void (*PsduResponseTimeoutCallback)(uint8_t reason,
                                                Ptr<const WifiPsdu> psdu,
                                                const WifiTxVector& txVector);

    /// TracedCallback for PSDU response timeout events typedef
    typedef TracedCallback<uint8_t, Ptr<const WifiPsdu>, const WifiTxVector&>
        PsduResponseTimeoutTracedCallback;

    /**
     * PSDU response timeout traced callback.
     * This trace source is fed by a WifiTxTimer object.
     */
    PsduResponseTimeoutTracedCallback m_psduResponseTimeoutCallback;

    /**
     * TracedCallback signature for PSDU map response timeout events.
     *
     * @param reason the reason why the timer was started
     * @param psduMap the PSDU map for which not all responses were received before the timeout
     * @param missingStations the MAC addresses of the stations that did not respond
     * @param nTotalStations the total number of stations that had to respond
     */
    typedef void (*PsduMapResponseTimeoutCallback)(uint8_t reason,
                                                   WifiPsduMap* psduMap,
                                                   const std::set<Mac48Address>* missingStations,
                                                   std::size_t nTotalStations);

    /// TracedCallback for PSDU map response timeout events typedef
    typedef TracedCallback<uint8_t, WifiPsduMap*, const std::set<Mac48Address>*, std::size_t>
        PsduMapResponseTimeoutTracedCallback;

    /**
     * PSDU map response timeout traced callback.
     * This trace source is fed by a WifiTxTimer object.
     */
    PsduMapResponseTimeoutTracedCallback m_psduMapResponseTimeoutCallback;

    IcfDropTracedCallback m_icfDropCallback; //!< traced callback for ICF drop events
};

} // namespace ns3

#endif /* WIFI_MAC_H */
