/*
 * Copyright (c) 2004,2005,2006 INRIA
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License version 2 as
 * published by the Free Software Foundation;
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 *
 * Author: Mathieu Lacage <mathieu.lacage@sophia.inria.fr>
 */

#include "aarf-wifi-manager.h"

#include "ns3/log.h"
#include "ns3/wifi-tx-vector.h"

#define Min(a, b) ((a < b) ? a : b)
#define Max(a, b) ((a > b) ? a : b)

namespace ns3
{

NS_LOG_COMPONENT_DEFINE("AarfWifiManager");

/**
 * \brief hold per-remote-station state for AARF Wifi manager.
 *
 * This struct extends from WifiRemoteStation struct to hold additional
 * information required by the AARF Wifi manager
 */
struct AarfWifiRemoteStation : public WifiRemoteStation
{
    uint32_t m_timer;            ///< timer
    uint32_t m_success;          ///< success
    uint32_t m_failed;           ///< failed
    bool m_recovery;             ///< recovery
    uint32_t m_timerTimeout;     ///< timer timeout
    uint32_t m_successThreshold; ///< success threshold
    uint8_t m_rate;              ///< rate
};

NS_OBJECT_ENSURE_REGISTERED(AarfWifiManager);

TypeId
AarfWifiManager::GetTypeId()
{
    static TypeId tid =
        TypeId("ns3::AarfWifiManager")
            .SetParent<WifiRemoteStationManager>()
            .SetGroupName("Wifi")
            .AddConstructor<AarfWifiManager>()
            .AddAttribute("SuccessK",
                          "Multiplication factor for the success threshold in the AARF algorithm.",
                          DoubleValue(2.0),
                          MakeDoubleAccessor(&AarfWifiManager::m_successK),
                          MakeDoubleChecker<double>())
            .AddAttribute("TimerK",
                          "Multiplication factor for the timer threshold in the AARF algorithm.",
                          DoubleValue(2.0),
                          MakeDoubleAccessor(&AarfWifiManager::m_timerK),
                          MakeDoubleChecker<double>())
            .AddAttribute("MaxSuccessThreshold",
                          "Maximum value of the success threshold in the AARF algorithm.",
                          UintegerValue(60),
                          MakeUintegerAccessor(&AarfWifiManager::m_maxSuccessThreshold),
                          MakeUintegerChecker<uint32_t>())
            .AddAttribute("MinTimerThreshold",
                          "The minimum value for the 'timer' threshold in the AARF algorithm.",
                          UintegerValue(15),
                          MakeUintegerAccessor(&AarfWifiManager::m_minTimerThreshold),
                          MakeUintegerChecker<uint32_t>())
            .AddAttribute("MinSuccessThreshold",
                          "The minimum value for the success threshold in the AARF algorithm.",
                          UintegerValue(10),
                          MakeUintegerAccessor(&AarfWifiManager::m_minSuccessThreshold),
                          MakeUintegerChecker<uint32_t>())
            .AddTraceSource("Rate",
                            "Traced value for rate changes (b/s)",
                            MakeTraceSourceAccessor(&AarfWifiManager::m_currentRate),
                            "ns3::TracedValueCallback::Uint64");
    return tid;
}

AarfWifiManager::AarfWifiManager()
    : WifiRemoteStationManager(),
      m_currentRate(0)
{
    NS_LOG_FUNCTION(this);
}

AarfWifiManager::~AarfWifiManager()
{
    NS_LOG_FUNCTION(this);
}

void
AarfWifiManager::DoInitialize()
{
    NS_LOG_FUNCTION(this);
    if (GetHtSupported())
    {
        NS_FATAL_ERROR("WifiRemoteStationManager selected does not support HT rates");
    }
    if (GetVhtSupported())
    {
        NS_FATAL_ERROR("WifiRemoteStationManager selected does not support VHT rates");
    }
    if (GetHeSupported())
    {
        NS_FATAL_ERROR("WifiRemoteStationManager selected does not support HE rates");
    }
}

WifiRemoteStation*
AarfWifiManager::DoCreateStation() const
{
    NS_LOG_FUNCTION(this);
    auto station = new AarfWifiRemoteStation();

    station->m_successThreshold = m_minSuccessThreshold;
    station->m_timerTimeout = m_minTimerThreshold;
    station->m_rate = 0;
    station->m_success = 0;
    station->m_failed = 0;
    station->m_recovery = false;
    station->m_timer = 0;

    return station;
}

void
AarfWifiManager::DoReportRtsFailed(WifiRemoteStation* station)
{
    NS_LOG_FUNCTION(this << station);
}

/**
 * It is important to realize that "recovery" mode starts after failure of
 * the first transmission after a rate increase and ends at the first successful
 * transmission. Specifically, recovery mode transcends retransmissions boundaries.
 * Fundamentally, ARF handles each data transmission independently, whether it
 * is the initial transmission of a packet or the retransmission of a packet.
 * The fundamental reason for this is that there is a backoff between each data
 * transmission, be it an initial transmission or a retransmission.
 *
 * \param st the station that we failed to send Data
 */
void
AarfWifiManager::DoReportDataFailed(WifiRemoteStation* st)
{
    NS_LOG_FUNCTION(this << st);
    auto station = static_cast<AarfWifiRemoteStation*>(st);
    station->m_timer++;
    station->m_failed++;
    station->m_success = 0;

    if (station->m_recovery)
    {
        NS_ASSERT(station->m_failed >= 1);
        if (station->m_failed == 1)
        {
            // need recovery fallback
            station->m_successThreshold =
                (int)(Min(station->m_successThreshold * m_successK, m_maxSuccessThreshold));
            station->m_timerTimeout =
                (int)(Max(station->m_timerTimeout * m_timerK, m_minSuccessThreshold));
            if (station->m_rate != 0)
            {
                station->m_rate--;
            }
        }
        station->m_timer = 0;
    }
    else
    {
        NS_ASSERT(station->m_failed >= 1);
        if (((station->m_failed - 1) % 2) == 1)
        {
            // need normal fallback
            station->m_timerTimeout = m_minTimerThreshold;
            station->m_successThreshold = m_minSuccessThreshold;
            if (station->m_rate != 0)
            {
                station->m_rate--;
            }
        }
        if (station->m_failed >= 2)
        {
            station->m_timer = 0;
        }
    }
}

void
AarfWifiManager::DoReportRxOk(WifiRemoteStation* station, double rxSnr, WifiMode txMode)
{
    NS_LOG_FUNCTION(this << station << rxSnr << txMode);
}

void
AarfWifiManager::DoReportRtsOk(WifiRemoteStation* station,
                               double ctsSnr,
                               WifiMode ctsMode,
                               double rtsSnr)
{
    NS_LOG_FUNCTION(this << station << ctsSnr << ctsMode << rtsSnr);
    NS_LOG_DEBUG("station=" << station << " rts ok");
}

void
AarfWifiManager::DoReportDataOk(WifiRemoteStation* st,
                                double ackSnr,
                                WifiMode ackMode,
                                double dataSnr,
                                uint16_t dataChannelWidth,
                                uint8_t dataNss)
{
    NS_LOG_FUNCTION(this << st << ackSnr << ackMode << dataSnr << dataChannelWidth << +dataNss);
    auto station = static_cast<AarfWifiRemoteStation*>(st);
    station->m_timer++;
    station->m_success++;
    station->m_failed = 0;
    station->m_recovery = false;
    NS_LOG_DEBUG("station=" << station << " data ok success=" << station->m_success
                            << ", timer=" << station->m_timer);
    if ((station->m_success == station->m_successThreshold ||
         station->m_timer == station->m_timerTimeout) &&
        (station->m_rate < (GetNSupported(station) - 1)))
    {
        NS_LOG_DEBUG("station=" << station << " inc rate");
        station->m_rate++;
        station->m_timer = 0;
        station->m_success = 0;
        station->m_recovery = true;
    }
}

void
AarfWifiManager::DoReportFinalRtsFailed(WifiRemoteStation* station)
{
    NS_LOG_FUNCTION(this << station);
}

void
AarfWifiManager::DoReportFinalDataFailed(WifiRemoteStation* station)
{
    NS_LOG_FUNCTION(this << station);
}

WifiTxVector
AarfWifiManager::DoGetDataTxVector(WifiRemoteStation* st, uint16_t allowedWidth)
{
    NS_LOG_FUNCTION(this << st << allowedWidth);
    auto station = static_cast<AarfWifiRemoteStation*>(st);
    uint16_t channelWidth = GetChannelWidth(station);
    if (channelWidth > 20 && channelWidth != 22)
    {
        channelWidth = 20;
    }
    WifiMode mode = GetSupported(station, station->m_rate);
    uint64_t rate = mode.GetDataRate(channelWidth);
    if (m_currentRate != rate)
    {
        NS_LOG_DEBUG("New datarate: " << rate);
        m_currentRate = rate;
    }
    return WifiTxVector(
        mode,
        GetDefaultTxPowerLevel(),
        GetPreambleForTransmission(mode.GetModulationClass(), GetShortPreambleEnabled()),
        800,
        1,
        1,
        0,
        channelWidth,
        GetAggregation(station));
}

WifiTxVector
AarfWifiManager::DoGetRtsTxVector(WifiRemoteStation* st)
{
    NS_LOG_FUNCTION(this << st);
    /// \todo we could/should implement the AARF algorithm for
    /// RTS only by picking a single rate within the BasicRateSet.
    auto station = static_cast<AarfWifiRemoteStation*>(st);
    uint16_t channelWidth = GetChannelWidth(station);
    if (channelWidth > 20 && channelWidth != 22)
    {
        channelWidth = 20;
    }
    WifiMode mode;
    if (!GetUseNonErpProtection())
    {
        mode = GetSupported(station, 0);
    }
    else
    {
        mode = GetNonErpSupported(station, 0);
    }
    return WifiTxVector(
        mode,
        GetDefaultTxPowerLevel(),
        GetPreambleForTransmission(mode.GetModulationClass(), GetShortPreambleEnabled()),
        800,
        1,
        1,
        0,
        channelWidth,
        GetAggregation(station));
}

} // namespace ns3
