/* -*-  Mode: C++; c-file-style: "gnu"; indent-tabs-mode:nil; -*- */
/*
 * Copyright (c) 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 "wifi-phy-state-helper.h"
#include "ns3/log.h"
#include "ns3/simulator.h"
#include "ns3/trace-source-accessor.h"

NS_LOG_COMPONENT_DEFINE ("WifiPhyStateHelper");

namespace ns3 {

NS_OBJECT_ENSURE_REGISTERED (WifiPhyStateHelper);

TypeId 
WifiPhyStateHelper::GetTypeId (void)
{
  static TypeId tid = TypeId ("ns3::WifiPhyStateHelper")
    .SetParent<Object> ()
    .AddConstructor<WifiPhyStateHelper> ()
    .AddTraceSource ("State",
                     "The state of the PHY layer",
                     MakeTraceSourceAccessor (&WifiPhyStateHelper::m_stateLogger))
    .AddTraceSource ("RxOk",
                     "A packet has been received successfully.",
                     MakeTraceSourceAccessor (&WifiPhyStateHelper::m_rxOkTrace))
    .AddTraceSource ("RxError",
                     "A packet has been received unsuccessfully.",
                     MakeTraceSourceAccessor (&WifiPhyStateHelper::m_rxErrorTrace))
    .AddTraceSource ("Tx", "Packet transmission is starting.",
                     MakeTraceSourceAccessor (&WifiPhyStateHelper::m_txTrace))
    ;
  return tid;
}

WifiPhyStateHelper::WifiPhyStateHelper ()
  : m_syncing (false),
    m_endTx (Seconds (0)),
    m_endSync (Seconds (0)),
    m_endCcaBusy (Seconds (0)),
    m_startTx (Seconds (0)),
    m_startSync (Seconds (0)),
    m_startCcaBusy (Seconds (0)),
    m_previousStateChangeTime (Seconds (0))
{
  NS_LOG_FUNCTION (this);
}

void 
WifiPhyStateHelper::SetReceiveOkCallback (WifiPhy::SyncOkCallback callback)
{
  m_syncOkCallback = callback;
}
void 
WifiPhyStateHelper::SetReceiveErrorCallback (WifiPhy::SyncErrorCallback callback)
{
  m_syncErrorCallback = callback;
}
void 
WifiPhyStateHelper::RegisterListener (WifiPhyListener *listener)
{
  m_listeners.push_back (listener);
}

bool 
WifiPhyStateHelper::IsStateCcaBusy (void)
{
  return GetState () == WifiPhy::CCA_BUSY;
}
 
bool 
WifiPhyStateHelper::IsStateIdle (void)
{
  return (GetState () == WifiPhy::IDLE)?true:false;
}
bool 
WifiPhyStateHelper::IsStateBusy (void)
{
  return (GetState () != WifiPhy::IDLE)?true:false;
}
bool 
WifiPhyStateHelper::IsStateSync (void)
{
  return (GetState () == WifiPhy::SYNC)?true:false;
}
bool 
WifiPhyStateHelper::IsStateTx (void)
{
  return (GetState () == WifiPhy::TX)?true:false;
}



Time
WifiPhyStateHelper::GetStateDuration (void)
{
  return Simulator::Now () - m_previousStateChangeTime;
}

Time
WifiPhyStateHelper::GetDelayUntilIdle (void)
{
  Time retval;

  switch (GetState ()) {
  case WifiPhy::SYNC:
    retval = m_endSync - Simulator::Now ();
    break;
  case WifiPhy::TX:
    retval = m_endTx - Simulator::Now ();
    break;
  case WifiPhy::CCA_BUSY:
    retval = m_endCcaBusy - Simulator::Now ();
    break;
  case WifiPhy::IDLE:
    retval = Seconds (0);
    break;
  default:
    NS_ASSERT (false);
    // NOTREACHED
    retval = Seconds (0);
    break;
  }
  retval = Max (retval, Seconds (0));
  return retval;
}

Time 
WifiPhyStateHelper::GetLastRxStartTime (void) const
{
  return m_startSync;
}

char const *
WifiPhyStateHelper::StateToString (enum WifiPhy::State state)
{
  switch (state) {
  case WifiPhy::TX:
    return "TX";
    break;
  case WifiPhy::CCA_BUSY:
    return "CCA_BUSY";
    break;
  case WifiPhy::IDLE:
    return "IDLE";
    break;
  case WifiPhy::SYNC:
    return "SYNC";
    break;
  default:
    NS_ASSERT (false);
    // quiet compiler
    return "INVALID";
    break;
  }
}

enum WifiPhy::State 
WifiPhyStateHelper::GetState (void)
{
  if (m_endTx > Simulator::Now ()) 
    {
      return WifiPhy::TX;
    } 
  else if (m_syncing) 
    {
      return WifiPhy::SYNC;
    } 
  else if (m_endCcaBusy > Simulator::Now ()) 
    {
      return WifiPhy::CCA_BUSY;
    } 
  else 
    {
      return WifiPhy::IDLE;
    }
}


void 
WifiPhyStateHelper::NotifyTxStart (Time duration)
{
  for (Listeners::const_iterator i = m_listeners.begin (); i != m_listeners.end (); i++) {
    (*i)->NotifyTxStart (duration);
  }
}
void 
WifiPhyStateHelper::NotifySyncStart (Time duration)
{
  for (Listeners::const_iterator i = m_listeners.begin (); i != m_listeners.end (); i++) {
    (*i)->NotifyRxStart (duration);
  }
}
void 
WifiPhyStateHelper::NotifySyncEndOk (void)
{
  for (Listeners::const_iterator i = m_listeners.begin (); i != m_listeners.end (); i++) {
    (*i)->NotifyRxEndOk ();
  }
}
void 
WifiPhyStateHelper::NotifySyncEndError (void)
{
  for (Listeners::const_iterator i = m_listeners.begin (); i != m_listeners.end (); i++) {
    (*i)->NotifyRxEndError ();
  }
}
void 
WifiPhyStateHelper::NotifyMaybeCcaBusyStart (Time duration)
{
  for (Listeners::const_iterator i = m_listeners.begin (); i != m_listeners.end (); i++) {
    (*i)->NotifyMaybeCcaBusyStart (duration);
  }
}

void
WifiPhyStateHelper::LogPreviousIdleAndCcaBusyStates (void)
{
  Time now = Simulator::Now ();
  Time idleStart = Max (m_endCcaBusy, m_endSync);
  idleStart = Max (idleStart, m_endTx);
  NS_ASSERT (idleStart <= now);
  if (m_endCcaBusy > m_endSync && 
      m_endCcaBusy > m_endTx) {
    Time ccaBusyStart = Max (m_endTx, m_endSync);
    ccaBusyStart = Max (ccaBusyStart, m_startCcaBusy);
    m_stateLogger (ccaBusyStart, idleStart - ccaBusyStart, WifiPhy::CCA_BUSY);
  }
  m_stateLogger (idleStart, now - idleStart, WifiPhy::IDLE);
}

void
WifiPhyStateHelper::SwitchToTx (Time txDuration, Ptr<const Packet> packet, WifiMode txMode, 
			  WifiPreamble preamble, uint8_t txPower)
{
  m_txTrace (packet, txMode, preamble, txPower);
  NotifyTxStart (txDuration);
  Time now = Simulator::Now ();
  switch (GetState ()) {
  case WifiPhy::SYNC:
    /* The packet which is being received as well
     * as its endSync event are cancelled by the caller.
     */
    m_syncing = false;
    m_stateLogger (m_startSync, now - m_startSync, WifiPhy::SYNC);
    m_endSync = now;
    break;
  case WifiPhy::CCA_BUSY: {
    Time ccaStart = Max (m_endSync, m_endTx);
    ccaStart = Max (ccaStart, m_startCcaBusy);
    m_stateLogger (ccaStart, now - ccaStart, WifiPhy::CCA_BUSY);
  } break;
  case WifiPhy::IDLE:
    LogPreviousIdleAndCcaBusyStates ();
    break;
  default:
    NS_ASSERT (false);
    break;
  }
  m_stateLogger (now, txDuration, WifiPhy::TX);
  m_previousStateChangeTime = now;
  m_endTx = now + txDuration;
  m_startTx = now;
}
void
WifiPhyStateHelper::SwitchToSync (Time rxDuration)
{
  NS_ASSERT (IsStateIdle () || IsStateCcaBusy ());
  NS_ASSERT (!m_syncing);
  NotifySyncStart (rxDuration);
  Time now = Simulator::Now ();
  switch (GetState ()) {
  case WifiPhy::IDLE:
    LogPreviousIdleAndCcaBusyStates ();
    break;
  case WifiPhy::CCA_BUSY: {
    Time ccaStart = Max (m_endSync, m_endTx);
    ccaStart = Max (ccaStart, m_startCcaBusy);
    m_stateLogger (ccaStart, now - ccaStart, WifiPhy::CCA_BUSY);
  } break;
  case WifiPhy::SYNC:
  case WifiPhy::TX:
    NS_ASSERT (false);
    break;
  }
  m_previousStateChangeTime = now;
  m_syncing = true;
  m_startSync = now;
  m_endSync = now + rxDuration;
  NS_ASSERT (IsStateSync ());
}
void 
WifiPhyStateHelper::SwitchFromSyncEndOk (Ptr<Packet> packet, double snr, WifiMode mode, enum WifiPreamble preamble)
{
  m_rxOkTrace (packet, snr, mode, preamble);
  NotifySyncEndOk ();
  DoSwitchFromSync ();
  if (!m_syncOkCallback.IsNull ())
    {
      m_syncOkCallback (packet, snr, mode, preamble);
    }

}
void 
WifiPhyStateHelper::SwitchFromSyncEndError (Ptr<const Packet> packet, double snr)
{
  m_rxErrorTrace (packet, snr);
  NotifySyncEndError ();
  DoSwitchFromSync ();
  if (!m_syncErrorCallback.IsNull ())
    {
      m_syncErrorCallback (packet, snr);
    }
}

void
WifiPhyStateHelper::DoSwitchFromSync (void)
{
  NS_ASSERT (IsStateSync ());
  NS_ASSERT (m_syncing);

  Time now = Simulator::Now ();
  m_stateLogger (m_startSync, now - m_startSync, WifiPhy::SYNC);
  m_previousStateChangeTime = now;
  m_syncing = false;

  NS_ASSERT (IsStateIdle () || IsStateCcaBusy ());
}
void
WifiPhyStateHelper::SwitchMaybeToCcaBusy (Time duration)
{
  NotifyMaybeCcaBusyStart (duration);
  Time now = Simulator::Now ();
  switch (GetState ()) {
  case WifiPhy::IDLE:
    LogPreviousIdleAndCcaBusyStates ();
  break;
  case WifiPhy::CCA_BUSY:
    break;
  case WifiPhy::SYNC:
    break;
  case WifiPhy::TX:
    break;
  }
  m_startCcaBusy = now;
  m_endCcaBusy = std::max (m_endCcaBusy, now + duration);
}

} // namespace ns3
