/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
/*
 * Copyright (c) 2009 University of Washington
 *
 * 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: Leonard Tracy <lentracy@gmail.com>
 */

#include "uan-mac-rc.h"
#include "uan-header-rc.h"
#include "uan-tx-mode.h"
#include "uan-phy.h"
#include "uan-header-common.h"
#include "uan-phy-dual.h"

#include "ns3/log.h"
#include "ns3/nstime.h"
#include "ns3/simulator.h"
#include "ns3/random-variable.h"
#include "ns3/assert.h"
#include "ns3/double.h"
#include "ns3/uinteger.h"

#include <list>
#include <utility>



NS_LOG_COMPONENT_DEFINE ("UanMacRc");
namespace ns3 {

NS_OBJECT_ENSURE_REGISTERED (UanMacRc);

Reservation::Reservation ()
  : m_length (0),
    m_frameNo (0),
    m_retryNo (0),
    m_transmitted (false)
{

}

Reservation::Reservation (std::list<std::pair <Ptr<Packet>, UanAddress > > &list, uint8_t frameNo, uint32_t maxPkts)
  : m_frameNo (frameNo),
    m_retryNo (0),
    m_transmitted (false)
{
  uint32_t numPkts = (maxPkts) ? maxPkts : list.size ();
  uint32_t length = 0;
  UanHeaderRcData dh;
  UanHeaderCommon ch;

  for (uint32_t i = 0; i < numPkts; i++)
    {
      length += list.front ().first->GetSize () +
        ch.GetSerializedSize () +
        dh.GetSerializedSize ();
      m_pktList.push_back (list.front ());
      list.pop_front ();
    }
  m_length = length;
}

Reservation::~Reservation ()
{
  std::list<std::pair <Ptr<Packet>, UanAddress > >::iterator it;
  for (it = m_pktList.begin (); it != m_pktList.end (); it++)
    {
      it->first = Ptr<Packet> ((Packet *) 0);
    }
  m_pktList.clear ();
  m_timestamp.clear ();
}
uint32_t
Reservation::GetNoFrames () const
{
  return m_pktList.size ();
}

uint32_t
Reservation::GetLength () const
{
  return m_length;
}

const std::list<std::pair <Ptr<Packet>, UanAddress > > &
Reservation::GetPktList (void) const
{
  return m_pktList;
}

uint8_t
Reservation::GetFrameNo () const
{
  return m_frameNo;
}

uint8_t
Reservation::GetRetryNo () const
{
  return m_retryNo;
}

Time
Reservation::GetTimestamp (uint8_t n) const
{
  return m_timestamp[n];
}

bool
Reservation::IsTransmitted () const
{
  return m_transmitted;
}

void
Reservation::SetFrameNo (uint8_t fn)
{
  m_frameNo = fn;
}

void
Reservation::AddTimestamp (Time t)
{
  m_timestamp.push_back (t);
}

void
Reservation::IncrementRetry ()
{
  m_retryNo++;
}

void
Reservation::SetTransmitted (bool t)
{
  m_transmitted = true;
}

uint32_t UanMacRc::m_cntrlSends = 0;

UanMacRc::UanMacRc ()
  : UanMac (),
    m_state (UNASSOCIATED),
    m_rtsBlocked (false),
    m_currentRate (10),
    m_frameNo (0),
    m_cleared (false)
{
  UanHeaderCommon ch;
  UanHeaderRcCts ctsh;
  UanHeaderRcCtsGlobal ctsg;

  m_ctsSizeN = ctsh.GetSerializedSize ();
  m_ctsSizeG = ch.GetSerializedSize () + ctsg.GetSerializedSize ();
}

UanMacRc::~UanMacRc ()
{
}

void
UanMacRc::Clear ()
{
  if (m_cleared)
    {
      return;
    }
  m_cleared = true;
  if (m_phy)
    {
      m_phy->Clear ();
      m_phy = 0;
    }
  std::list<std::pair <Ptr<Packet>, UanAddress > >::iterator it;
  for (it = m_pktQueue.begin (); it != m_pktQueue.end (); it++)
    {
      it->first = 0;
    }
  m_pktQueue.clear ();
  m_resList.clear ();
  m_startAgain.Cancel ();
  m_rtsEvent.Cancel ();
}

void
UanMacRc::DoDispose ()
{
  Clear ();
  UanMac::DoDispose ();
}

TypeId
UanMacRc::GetTypeId (void)
{
  static TypeId tid = TypeId ("ns3::UanMacRc")
    .SetParent<UanMac> ()
    .AddConstructor<UanMacRc> ()
    .AddAttribute ("RetryRate",
                   "Number of retry attempts per second (of RTS/GWPING)",
                   DoubleValue (1 / 5.0),
                   MakeDoubleAccessor (&UanMacRc::m_retryRate),
                   MakeDoubleChecker<double> ())
    .AddAttribute ("MaxFrames",
                   "Maximum number of frames to include in a single RTS",
                   UintegerValue (1),
                   MakeUintegerAccessor (&UanMacRc::m_maxFrames),
                   MakeUintegerChecker<uint32_t> ())
    .AddAttribute ("QueueLimit",
                   "Maximum packets to queue at MAC",
                   UintegerValue (10),
                   MakeUintegerAccessor (&UanMacRc::m_queueLimit),
                   MakeUintegerChecker<uint32_t> ())
    .AddAttribute ("SIFS",
                   "Spacing to give between frames (this should match gateway)",
                   TimeValue (Seconds (0.2)),
                   MakeTimeAccessor (&UanMacRc::m_sifs),
                   MakeTimeChecker ())
    .AddAttribute ("NumberOfRates",
                   "Number of rate divisions supported by each PHY",
                   UintegerValue (0),
                   MakeUintegerAccessor (&UanMacRc::m_numRates),
                   MakeUintegerChecker<uint32_t> ())
    .AddAttribute ("MinRetryRate",
                   "Smallest allowed RTS retry rate",
                   DoubleValue (0.01),
                   MakeDoubleAccessor (&UanMacRc::m_minRetryRate),
                   MakeDoubleChecker<double> ())
    .AddAttribute ("RetryStep",
                   "Retry rate increment",
                   DoubleValue (0.01),
                   MakeDoubleAccessor (&UanMacRc::m_retryStep),
                   MakeDoubleChecker<double> ())
    .AddAttribute ("NumberOfRetryRates",
                   "Number of retry rates",
                   UintegerValue (100),
                   MakeUintegerAccessor (&UanMacRc::m_numRetryRates),
                   MakeUintegerChecker<uint16_t> ())
    .AddAttribute ("MaxPropDelay",
                   "Maximum possible propagation delay to gateway",
                   TimeValue (Seconds (2)),
                   MakeTimeAccessor (&UanMacRc::m_learnedProp),
                   MakeTimeChecker ())
    .AddTraceSource ("Enqueue",
                     "A  (data) packet arrived at MAC for transmission",
                     MakeTraceSourceAccessor (&UanMacRc::m_enqueueLogger))
    .AddTraceSource ("Dequeue",
                     "A  (data) packet was passed down to PHY from MAC",
                     MakeTraceSourceAccessor (&UanMacRc::m_dequeueLogger))
    .AddTraceSource ("RX",
                     "A packet was destined for and received at this MAC layer",
                     MakeTraceSourceAccessor (&UanMacRc::m_rxLogger))
  ;
  return tid;
}


Address
UanMacRc::GetAddress (void)
{
  return m_address;
}

void
UanMacRc::SetAddress (UanAddress addr)
{
  m_address = addr;
}

bool
UanMacRc::Enqueue (Ptr<Packet> packet, const Address &dest, uint16_t protocolNumber)
{
  if (protocolNumber > 0)
    {
      NS_LOG_WARN ("Warning: UanMacRc does not support multiple protocols.  protocolNumber argument to Enqueue is being ignored");
    }


  if (m_pktQueue.size () >= m_queueLimit)
    {
      return false;
    }

  m_pktQueue.push_back (std::make_pair (packet, UanAddress::ConvertFrom (dest)));

  switch (m_state)
    {
    case UNASSOCIATED:
      Associate ();
      return true;
    case IDLE:
      if (!m_rtsEvent.IsRunning ())
        {
          SendRts ();
        }
      return true;
    case GWPSENT:
    case RTSSENT:
    case DATATX:
      return true;
    }

  return true;
}

void
UanMacRc::SetForwardUpCb (Callback<void, Ptr<Packet>, const UanAddress&> cb)
{
  m_forwardUpCb = cb;
}

void
UanMacRc::AttachPhy (Ptr<UanPhy> phy)
{
  m_phy = phy;
  m_phy->SetReceiveOkCallback (MakeCallback (&UanMacRc::ReceiveOkFromPhy, this));
}

Address
UanMacRc::GetBroadcast (void) const
{
  return UanAddress::GetBroadcast ();
}

void
UanMacRc::ReceiveOkFromPhy (Ptr<Packet> pkt, double sinr, UanTxMode mode)
{

  UanHeaderCommon ch;
  pkt->RemoveHeader (ch);
  if (ch.GetDest () == m_address || ch.GetDest () == UanAddress::GetBroadcast ())
    {
      m_rxLogger (pkt, mode);
    }

  switch (ch.GetType ())
    {
    case TYPE_DATA:

      if (ch.GetDest () == m_address)
        {
          NS_LOG_DEBUG (Simulator::Now ().GetSeconds () << " Node " << m_address << " UanMacRc Receiving DATA packet from PHY");
          UanHeaderRcData dh;
          pkt->RemoveHeader (dh);
          m_forwardUpCb (pkt, ch.GetSrc ());
        }
      break;
    case TYPE_RTS:
      // Currently don't respond to RTS packets at non-gateway nodes
      // (Code assumes single network neighberhood)
      break;
    case TYPE_CTS:
      {
        uint32_t ctsBytes = ch.GetSerializedSize () + pkt->GetSize ();
        m_assocAddr = ch.GetSrc ();
        UanHeaderRcCtsGlobal ctsg;
        pkt->RemoveHeader (ctsg);
        m_currentRate = ctsg.GetRateNum ();
        m_retryRate = m_minRetryRate + m_retryStep*ctsg.GetRetryRate ();

        UanHeaderRcRts rhtmp;

        Time winDelay = ctsg.GetWindowTime ();

        if (winDelay.GetSeconds () > 0)
          {
            m_rtsBlocked = false;
            Simulator::Schedule (winDelay, &UanMacRc::BlockRtsing, this);
          }
        else
          {
            NS_FATAL_ERROR (Simulator::Now ().GetSeconds () << " Node " << m_address << " Received window period < 0");
          }

        UanHeaderRcCts ctsh;
        ctsh.SetAddress (UanAddress::GetBroadcast ());
        while (pkt->GetSize () > 0)
          {
            pkt->RemoveHeader (ctsh);
            if (ctsh.GetAddress () == m_address)
              {
                if (m_state == GWPSENT)
                  {
                    m_assocAddr = ch.GetSrc ();
                    ScheduleData (ctsh, ctsg, ctsBytes);
                  }
                else if (m_state == RTSSENT)
                  {
                    ScheduleData (ctsh, ctsg, ctsBytes);
                  }
                else
                  {
                    NS_LOG_DEBUG (Simulator::Now ().GetSeconds () << " Node " << m_address << " received CTS while state != RTSSENT or GWPING");
                  }
              }
          }
      }
      break;
    case TYPE_GWPING:
      // Do not respond to GWPINGS at non-gateway nodes
      break;
    case TYPE_ACK:
      m_rtsBlocked = true;
      if (ch.GetDest () != m_address)
        {
          return;
        }
      ProcessAck (pkt);
      break;
    default:
      NS_FATAL_ERROR ("Unknown packet type " << ch.GetType () << " received at node " << GetAddress ());
    }

}

void
UanMacRc::ScheduleData (const UanHeaderRcCts &ctsh, const UanHeaderRcCtsGlobal &ctsg, uint32_t ctsBytes)
{
  NS_ASSERT (m_state == RTSSENT || m_state == GWPSENT);



  std::list<Reservation>::iterator it = m_resList.begin ();
  for (; it != m_resList.end (); it++)
    {
      if (it->GetFrameNo () == ctsh.GetFrameNo ())
        {
          break;
        }
    }
  if (it == m_resList.end ())
    {
      NS_LOG_DEBUG (Simulator::Now ().GetSeconds () << " Node " << m_address << " received CTS packet with no corresponding reservation!");
      return;
    }
  NS_LOG_DEBUG (Simulator::Now ().GetSeconds () << " Node " << m_address << " received CTS packet.  Scheduling data");
  it->SetTransmitted ();

  double currentBps = m_phy->GetMode (m_currentRate).GetDataRateBps ();

  m_learnedProp = Simulator::Now () - ctsg.GetTxTimeStamp () - Seconds (ctsBytes * 8.0 / currentBps);


  Time arrTime = ctsg.GetTxTimeStamp () + ctsh.GetDelayToTx ();
  Time txTime = arrTime - m_learnedProp;

  Time startDelay = txTime - Simulator::Now ();

  Time frameDelay = Seconds (0);

  const std::list<std::pair <Ptr<Packet>, UanAddress > > l = it->GetPktList ();
  std::list<std::pair <Ptr<Packet>, UanAddress > >::const_iterator pit;
  pit = l.begin ();



  for (uint32_t i = 0; i < it->GetNoFrames (); i++, pit++)
    {
      Ptr<Packet> pkt = (*pit).first->Copy ();

      UanHeaderRcData dh;
      dh.SetFrameNo (i);
      dh.SetPropDelay (m_learnedProp);
      pkt->AddHeader (dh);

      UanHeaderCommon ch;
      ch.SetType (TYPE_DATA);
      ch.SetDest (m_assocAddr);
      ch.SetSrc (m_address);

      pkt->AddHeader (ch);
      Time eventTime = startDelay + frameDelay;
      if (eventTime.GetSeconds () < 0)
        {
          if (eventTime.GetSeconds () > -0.001)
            {
              eventTime = Seconds (0);
            }
          else
            {
              NS_FATAL_ERROR ("Scheduling error resulted in very negative data transmission time! eventTime = " << eventTime.GetSeconds ());
            }
        }
      NS_LOG_DEBUG (Simulator::Now ().GetSeconds () << " Node " << m_address << " scheduling with delay " << eventTime.GetSeconds () << " propDelay " << m_learnedProp.GetSeconds () << " start delay " << startDelay.GetSeconds () << " arrival time " << arrTime.GetSeconds ());
      Simulator::Schedule (eventTime, &UanMacRc::SendPacket, this, pkt, m_currentRate);
      frameDelay = frameDelay + m_sifs + Seconds (pkt->GetSize () / currentBps);
    }

  m_state = IDLE;
  if (!m_pktQueue.empty ())
    {

      if (m_rtsEvent.IsRunning ())
        {
          m_rtsEvent.Cancel ();
        }
      ExponentialVariable ev (1 / m_retryRate);
      double timeout = ev.GetValue ();
      m_rtsEvent = Simulator::Schedule (Seconds (timeout), &UanMacRc::SendRts, this);
    }

}

void
UanMacRc::SendPacket (Ptr<Packet> pkt, uint32_t rate)
{
  UanHeaderCommon ch;
  pkt->PeekHeader (ch);
  std::string type;
  switch (ch.GetType ())
    {
    case TYPE_DATA:
      type = "DATA";
      break;
    case TYPE_RTS:
      type = "RTS";
      break;
    case TYPE_CTS:
      type = "CTS";
      break;
    case TYPE_ACK:
      type = "ACK";
      break;
    case TYPE_GWPING:
      type = "GWPING";
      break;
    default:
      type = "UNKNOWN";
      break;
    }
  NS_LOG_DEBUG (Simulator::Now ().GetSeconds () << " Node " << m_address << " transmitting " << pkt->GetSize () << " byte packet of type " << type << " with rate " << rate << "(" << m_phy->GetMode (rate).GetDataRateBps () << ") to " << ch.GetDest ());
  m_dequeueLogger (pkt, rate);
  m_phy->SendPacket (pkt, rate);
}

void
UanMacRc::ProcessAck (Ptr<Packet> ack)
{
  UanHeaderRcAck ah;
  ack->RemoveHeader (ah);

  std::list<Reservation>::iterator it = m_resList.begin ();
  for (; it != m_resList.end (); it++)
    {
      if (it->GetFrameNo () == ah.GetFrameNo ())
        {
          break;
        }
    }
  if (it == m_resList.end ())
    {
      NS_LOG_DEBUG ("In " << __func__ << " could not find reservation corresponding to received ACK");
      return;
    }
  if (!it->IsTransmitted ())
    {
      return;
    }
  if (ah.GetNoNacks () > 0)
    {
      const std::list<std::pair <Ptr<Packet>, UanAddress > > l = it->GetPktList ();
      std::list<std::pair <Ptr<Packet>, UanAddress > >::const_iterator pit;
      pit = l.begin ();

      const std::set<uint8_t> &nacks = ah.GetNackedFrames ();
      std::set<uint8_t>::iterator nit = nacks.begin ();
      uint8_t pnum = 0;
      for (; nit != nacks.end (); nit++)
        {
          NS_LOG_DEBUG (Simulator::Now ().GetSeconds () << " Node " << m_address << " Received NACK for " << (uint32_t) *nit);
          while (pnum < *nit)
            {
              pit++;
              pnum++;
            }
          UanHeaderRcData dh;
          UanHeaderCommon ch;
          m_pktQueue.push_front (*pit);
        }
    }
  else
    {
      NS_LOG_DEBUG (Simulator::Now ().GetSeconds () << " Node " << m_address << " received ACK for all frames");
    }
  m_resList.erase (it);
}

UanHeaderRcRts
UanMacRc::CreateRtsHeader (const Reservation &res)
{
  UanHeaderRcRts rh = UanHeaderRcRts ();

  rh.SetLength (res.GetLength ());
  rh.SetNoFrames (res.GetNoFrames ());
  rh.SetTimeStamp (res.GetTimestamp (res.GetRetryNo ()));
  rh.SetFrameNo (res.GetFrameNo ());
  rh.SetRetryNo (res.GetRetryNo ());
  return rh;
}

void
UanMacRc::Associate (void)
{
  m_cntrlSends++;

  Reservation res (m_pktQueue, m_frameNo, m_maxFrames);
  res.AddTimestamp (Simulator::Now ());
  m_frameNo++;
  m_resList.push_back (res);
  Ptr<UanPhyDual> phyDual = m_phy->GetObject<UanPhyDual> ();
  bool phy1ok = IsPhy1Ok ();
  if (phy1ok && !phyDual->IsPhy2Tx () & !m_rtsBlocked)
    {
      Ptr<Packet> pkt = Create<Packet> (0);
      pkt->AddHeader (CreateRtsHeader (res));
      pkt->AddHeader (UanHeaderCommon (m_address, UanAddress::GetBroadcast (), (uint8_t) TYPE_GWPING));
      NS_LOG_DEBUG (Simulator::Now ().GetSeconds () << " Sending first GWPING " << *pkt);
      SendPacket (pkt,m_currentRate + m_numRates);
    }
  m_state = GWPSENT;
  NS_ASSERT (!m_rtsEvent.IsRunning ());
  ExponentialVariable ev (1.0 / m_retryRate);
  double timeout = ev.GetValue ();
  m_rtsEvent = Simulator::Schedule (Seconds (timeout), &UanMacRc::AssociateTimeout, this);
}

void
UanMacRc::AssociateTimeout ()
{
  m_cntrlSends++;
  if (m_state != GWPSENT)
    {
      return;
    }
  Ptr<UanPhyDual> phyDual = m_phy->GetObject<UanPhyDual> ();
  bool phy1ok = IsPhy1Ok ();
  if (phy1ok && !phyDual->IsPhy2Tx () && !m_rtsBlocked)
    {
      Ptr<Packet> pkt = Create<Packet> ();

      Reservation res = m_resList.back ();
      m_resList.pop_back ();
      res.AddTimestamp (Simulator::Now ());
      res.IncrementRetry ();

      pkt->AddHeader (CreateRtsHeader (res));
      pkt->AddHeader (UanHeaderCommon (m_address, UanAddress::GetBroadcast (), (uint8_t) TYPE_GWPING));

      SendPacket (pkt,m_currentRate + m_numRates);
      m_resList.push_back (res);
    }
  NS_ASSERT (!m_rtsEvent.IsRunning ());
  ExponentialVariable ev (1.0 / m_retryRate);
  double timeout = ev.GetValue ();
  m_rtsEvent = Simulator::Schedule (Seconds (timeout), &UanMacRc::AssociateTimeout, this);
}


void
UanMacRc::SendRts (void)
{
  m_cntrlSends++;
  if (m_state == RTSSENT)
    {
      return;
    }

  NS_ASSERT (!m_pktQueue.empty ());

  Reservation res (m_pktQueue, m_frameNo, m_maxFrames);
  res.AddTimestamp (Simulator::Now ());
  m_frameNo++;
  m_resList.push_back (res);
  Ptr<UanPhyDual> phyDual = m_phy->GetObject<UanPhyDual> ();
  bool phy1ok = IsPhy1Ok ();
  if (phy1ok && !phyDual->IsPhy2Tx () && !m_rtsBlocked )
    {
      Ptr<Packet> pkt = Create<Packet> (0);
      pkt->AddHeader (CreateRtsHeader (res));
      pkt->AddHeader (UanHeaderCommon (m_address, UanAddress::GetBroadcast (), (uint8_t) TYPE_RTS));
      SendPacket (pkt,m_currentRate + m_numRates);
    }
  m_state = RTSSENT;
  NS_ASSERT (!m_rtsEvent.IsRunning ());
  ExponentialVariable ev (1.0 / m_retryRate);
  double timeout = ev.GetValue ();
  m_rtsEvent = Simulator::Schedule (Seconds (timeout), &UanMacRc::RtsTimeout, this);

}

// We assume here that packet types are known at detection.
bool
UanMacRc::IsPhy1Ok ()
{
  Ptr<UanPhyDual> phyDual = m_phy->GetObject<UanPhyDual> ();

  bool phy1ok = true;
  if (phyDual->IsPhy1Rx ())
    {
      Ptr<Packet> pkt = phyDual->GetPhy1PacketRx ();
      UanHeaderCommon ch;
      pkt->PeekHeader (ch);
      if (ch.GetType () == TYPE_CTS || ch.GetType () == TYPE_ACK)
        {
          phy1ok = false;
        }
      else if (ch.GetDest () == m_address)
        {
          phy1ok = false;
        }
    }
  return phy1ok;
}

void
UanMacRc::RtsTimeout (void)
{
  m_cntrlSends++;

  if (m_state != RTSSENT)
    {
      return;
    }
  Ptr<UanPhyDual> phyDual = m_phy->GetObject<UanPhyDual> ();

  bool phy1ok = IsPhy1Ok ();
  if (phy1ok && !phyDual->IsPhy2Tx () && !m_rtsBlocked)
    {

      if (m_resList.empty ())
        {
          NS_FATAL_ERROR (Simulator::Now ().GetSeconds () << " Node " << m_address << " tried to retry RTS with empty reservation list");
        }
      Ptr<Packet> pkt = Create<Packet> (0);

      Reservation res = m_resList.back ();
      NS_ASSERT (!res.IsTransmitted ());
      m_resList.pop_back ();
      res.AddTimestamp (Simulator::Now ());
      res.IncrementRetry ();
      m_resList.push_back (res);
      pkt->AddHeader (CreateRtsHeader (res));
      pkt->AddHeader (UanHeaderCommon (m_address, UanAddress::GetBroadcast (), (uint8_t) TYPE_RTS));
      SendPacket (pkt,m_currentRate + m_numRates);

    }
  m_state = RTSSENT;
  NS_ASSERT (!m_rtsEvent.IsRunning ());
  ExponentialVariable ev (1.0 / m_retryRate);
  double timeout = ev.GetValue ();
  m_rtsEvent = Simulator::Schedule (Seconds (timeout), &UanMacRc::RtsTimeout, this);
}

void 
UanMacRc::BlockRtsing (void)
{
  m_rtsBlocked = true;
}

} // namespace ns3
