/* -*-  Mode: C++; c-file-style: "gnu"; indent-tabs-mode:nil; -*- */
/*
 * Copyright (c) 2008,2009 IITP RAS
 *
 * 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
 *
 * Authors: Kirill Andreev <andreev@iitp.ru>
 *          Aleksey Kovalenko <kovalenko@iitp.ru>
 */

#include "ns3/peer-management-protocol.h"
#include "peer-management-protocol-mac.h"
#include "ie-dot11s-configuration.h"
#include "ie-dot11s-id.h"
#include "ns3/mesh-point-device.h"
#include "ns3/simulator.h"
#include "ns3/assert.h"
#include "ns3/log.h"
#include "ns3/random-variable.h"
#include "ns3/mesh-wifi-interface-mac.h"
#include "ns3/mesh-wifi-interface-mac-plugin.h"
#include "ns3/wifi-net-device.h"
#include "ns3/trace-source-accessor.h"

NS_LOG_COMPONENT_DEFINE ("PeerManagementProtocol");
namespace ns3 {
namespace dot11s {
/***************************************************
 * PeerManager
 ***************************************************/
NS_OBJECT_ENSURE_REGISTERED (PeerManagementProtocol);

TypeId
PeerManagementProtocol::GetTypeId (void)
{
  static TypeId tid = TypeId ("ns3::dot11s::PeerManagementProtocol")
  .SetParent<Object> ()
  .AddConstructor<PeerManagementProtocol> ()
  // maximum number of peer links. Now we calculate the total
  // number of peer links on all interfaces
  .AddAttribute ( "MaxNumberOfPeerLinks",
                  "Maximum number of peer links",
                  UintegerValue (32),
                  MakeUintegerAccessor (
                      &PeerManagementProtocol::m_maxNumberOfPeerLinks),
                  MakeUintegerChecker<uint8_t> ()
                )
  .AddAttribute ( "MaxBeaconShiftValue",
                  "Maximum number of TUs for beacon shifting",
                  UintegerValue (15),
                  MakeUintegerAccessor (
                      &PeerManagementProtocol::m_maxBeaconShift),
                  MakeUintegerChecker<uint16_t> ()
                )
  .AddAttribute ( "EnableBeaconCollisionAvoidance",
                  "Enable/Disable Beacon collision avoidance.",
                  BooleanValue (true),
                  MakeBooleanAccessor (
                    &PeerManagementProtocol::SetBeaconCollisionAvoidance, &PeerManagementProtocol::GetBeaconCollisionAvoidance),
                    MakeBooleanChecker ()
                  )
  .AddTraceSource ("LinkOpen",
                      "New peer link opened",
                      MakeTraceSourceAccessor (&PeerManagementProtocol::m_linkOpenTraceSrc)
                  )
  .AddTraceSource ("LinkClose",
                      "New peer link closed",
                      MakeTraceSourceAccessor (&PeerManagementProtocol::m_linkCloseTraceSrc)
                  )
                 
                ;
  return tid;
}
PeerManagementProtocol::PeerManagementProtocol () :
  m_lastAssocId (0), m_lastLocalLinkId (1), m_enableBca (true), m_maxBeaconShift (15)
{
}
PeerManagementProtocol::~PeerManagementProtocol ()
{
  m_meshId = 0;
}
void
PeerManagementProtocol::DoDispose ()
{
  //cancel cleanup event and go through the map of peer links,
  //deleting each
  for (PeerLinksMap::iterator j = m_peerLinks.begin (); j != m_peerLinks.end (); j++)
    {
      for (PeerLinksOnInterface::iterator i = j->second.begin (); i != j->second.end (); i++)
        {
          (*i) = 0;
        }
      j->second.clear ();
    }
  m_peerLinks.clear ();
  m_plugins.clear ();
}

bool
PeerManagementProtocol::Install (Ptr<MeshPointDevice> mp)
{
  std::vector<Ptr<NetDevice> > interfaces = mp->GetInterfaces ();
  for (std::vector<Ptr<NetDevice> >::iterator i = interfaces.begin (); i != interfaces.end (); i++)
    {
      Ptr<WifiNetDevice> wifiNetDev = (*i)->GetObject<WifiNetDevice> ();
      if (wifiNetDev == 0)
        {
          return false;
        }
      Ptr<MeshWifiInterfaceMac> mac = wifiNetDev->GetMac ()->GetObject<MeshWifiInterfaceMac> ();
      if (mac == 0)
        {
          return false;
        }
      Ptr<PeerManagementProtocolMac> plugin = Create<PeerManagementProtocolMac> ((*i)->GetIfIndex (), this);
      mac->InstallPlugin (plugin);
      m_plugins[(*i)->GetIfIndex ()] = plugin;
      PeerLinksOnInterface newmap;
      m_peerLinks[(*i)->GetIfIndex ()] = newmap;
    }
  // Mesh point aggregates all installed protocols
  m_address = Mac48Address::ConvertFrom (mp->GetAddress ());
  mp->AggregateObject (this);
  return true;
}

Ptr<IeBeaconTiming>
PeerManagementProtocol::GetBeaconTimingElement (uint32_t interface)
{
  if (!GetBeaconCollisionAvoidance ())
    {
      return 0;
    }
  Ptr<IeBeaconTiming> retval = Create<IeBeaconTiming> ();
  PeerLinksMap::iterator iface = m_peerLinks.find (interface);
  NS_ASSERT (iface != m_peerLinks.end ());
  for (PeerLinksOnInterface::iterator i = iface->second.begin (); i != iface->second.end (); i++)
    {
      //If we do not know peer Assoc Id, we shall not add any info
      //to a beacon timing element
      if ((*i)->GetBeaconInterval () == Seconds (0))
        {
          //No beacon was received, do not include to the beacon timing element
          continue;
        }
      retval->AddNeighboursTimingElementUnit ((*i)->GetLocalAid (), (*i)->GetLastBeacon (),
          (*i)->GetBeaconInterval ());
    }
  return retval;
}
void
PeerManagementProtocol::ReceiveBeacon (uint32_t interface, Mac48Address peerAddress, Time beaconInterval, Ptr<IeBeaconTiming> timingElement)
{
  //PM STATE Machine
  //Check that a given beacon is not from our interface
  Simulator::Schedule (beaconInterval - TuToTime (m_maxBeaconShift + 1), &PeerManagementProtocol::DoShiftBeacon, this, interface);
  for (PeerManagementProtocolMacMap::const_iterator i = m_plugins.begin (); i != m_plugins.end (); i++)
    {
      if (i->second->GetAddress () == peerAddress)
        {
          return;
        }
    }
  Ptr<PeerLink> peerLink = FindPeerLink (interface, peerAddress);
  if (peerLink == 0)
    {
      if (ShouldSendOpen (interface, peerAddress))
        {
          peerLink = InitiateLink (interface, peerAddress, Mac48Address::GetBroadcast ());
          peerLink->MLMEActivePeerLinkOpen ();
        }
    }
  peerLink->SetBeaconInformation (Simulator::Now (), beaconInterval);
  if (GetBeaconCollisionAvoidance ())
    {
      peerLink->SetBeaconTimingElement (*PeekPointer (timingElement));
    }
}

void
PeerManagementProtocol::ReceivePeerLinkFrame (uint32_t interface, Mac48Address peerAddress,
    Mac48Address peerMeshPointAddress, uint16_t aid, IePeerManagement peerManagementElement,
    IeConfiguration meshConfig)
{
  Ptr<PeerLink> peerLink = FindPeerLink (interface, peerAddress);
  if (peerManagementElement.SubtypeIsOpen ())
    {
      PmpReasonCode reasonCode (REASON11S_RESERVED);
      bool reject = !(ShouldAcceptOpen (interface, peerAddress, reasonCode));
      if (peerLink == 0)
        {
          peerLink = InitiateLink (interface, peerAddress, peerMeshPointAddress);
        }
      if (!reject)
        {
          peerLink->OpenAccept (peerManagementElement.GetLocalLinkId (), meshConfig, peerMeshPointAddress);
        }
      else
        {
          peerLink->OpenReject (peerManagementElement.GetLocalLinkId (), meshConfig, peerMeshPointAddress,
              reasonCode);
        }
    }
  if (peerLink == 0)
    {
      return;
    }
  if (peerManagementElement.SubtypeIsConfirm ())
    {
      peerLink->ConfirmAccept (peerManagementElement.GetLocalLinkId (),
          peerManagementElement.GetPeerLinkId (), aid, meshConfig, peerMeshPointAddress);
    }
  if (peerManagementElement.SubtypeIsClose ())
    {
      peerLink->Close (peerManagementElement.GetLocalLinkId (), peerManagementElement.GetPeerLinkId (),
          peerManagementElement.GetReasonCode ());
    }
}
void
PeerManagementProtocol::ConfigurationMismatch (uint32_t interface, Mac48Address peerAddress)
{
  Ptr<PeerLink> peerLink = FindPeerLink (interface, peerAddress);
  if (peerLink != 0)
    {
      peerLink->MLMECancelPeerLink (REASON11S_MESH_CAPABILITY_POLICY_VIOLATION);
    }
}
void
PeerManagementProtocol::TransmissionFailure (uint32_t interface, Mac48Address peerAddress)
{
  NS_LOG_DEBUG("transmission failed between "<<GetAddress () << " and " << peerAddress << " failed, link will be colsed");
  Ptr<PeerLink> peerLink = FindPeerLink(interface, peerAddress);
  if (peerLink != 0)
    {
      peerLink->TransmissionFailure ();
    }
}
void
PeerManagementProtocol::TransmissionSuccess (uint32_t interface, Mac48Address peerAddress)
{
  NS_LOG_DEBUG("transmission success "<<GetAddress () << " and " << peerAddress << " failed, link will be colsed");
  Ptr<PeerLink> peerLink = FindPeerLink(interface, peerAddress);
  if (peerLink != 0)
    {
      peerLink->TransmissionSuccess ();
    }
}
Ptr<PeerLink>
PeerManagementProtocol::InitiateLink (uint32_t interface, Mac48Address peerAddress,
    Mac48Address peerMeshPointAddress)
{
  Ptr<PeerLink> new_link = CreateObject<PeerLink> ();
  //find a peer link  - it must not exist
  if (FindPeerLink (interface, peerAddress) != 0)
    {
      NS_FATAL_ERROR ("Peer link must not exist.");
    }
  // Plugin must exist
  PeerManagementProtocolMacMap::iterator plugin = m_plugins.find (interface);
  NS_ASSERT (plugin != m_plugins.end ());
  PeerLinksMap::iterator iface = m_peerLinks.find (interface);
  NS_ASSERT (iface != m_peerLinks.end ());
  new_link->SetLocalAid (m_lastAssocId++);
  new_link->SetInterface (interface);
  new_link->SetLocalLinkId (m_lastLocalLinkId++);
  new_link->SetPeerAddress (peerAddress);
  new_link->SetPeerMeshPointAddress (peerMeshPointAddress);
  new_link->SetMacPlugin (plugin->second);
  new_link->MLMESetSignalStatusCallback (MakeCallback (&PeerManagementProtocol::PeerLinkStatus, this));
  iface->second.push_back (new_link);
  return new_link;
}

Ptr<PeerLink>
PeerManagementProtocol::FindPeerLink (uint32_t interface, Mac48Address peerAddress)
{
  PeerLinksMap::iterator iface = m_peerLinks.find (interface);
  NS_ASSERT (iface != m_peerLinks.end ());
  for (PeerLinksOnInterface::iterator i = iface->second.begin (); i != iface->second.end (); i++)
    {
      if ((*i)->GetPeerAddress () == peerAddress)
        {
          if ((*i)->LinkIsIdle ())
            {
              (*i) = 0;
              (iface->second).erase (i);
              return 0;
            }
          else
            {
              return (*i);
            }
        }
    }
  return 0;
}
void
PeerManagementProtocol::SetPeerLinkStatusCallback (
    Callback<void, Mac48Address, Mac48Address, uint32_t, bool> cb)
{
  m_peerStatusCallback = cb;
}

std::vector<Mac48Address>
PeerManagementProtocol::GetPeers (uint32_t interface) const
{
  std::vector<Mac48Address> retval;
  PeerLinksMap::const_iterator iface = m_peerLinks.find (interface);
  NS_ASSERT (iface != m_peerLinks.end ());
  for (PeerLinksOnInterface::const_iterator i = iface->second.begin (); i != iface->second.end (); i++)
    {
      if ((*i)->LinkIsEstab ())
        {
          retval.push_back ((*i)->GetPeerAddress ());
        }
    }
  return retval;
}

std::vector< Ptr<PeerLink> >
PeerManagementProtocol::GetPeerLinks () const
{
  std::vector< Ptr<PeerLink> > links;

  for (PeerLinksMap::const_iterator iface = m_peerLinks.begin (); iface != m_peerLinks.end (); ++iface)
    {
      for (PeerLinksOnInterface::const_iterator i = iface->second.begin ();
      i != iface->second.end (); i++)
        if ((*i)->LinkIsEstab ())
          links.push_back (*i);
    }
  return links;
}
bool
PeerManagementProtocol::IsActiveLink (uint32_t interface, Mac48Address peerAddress)
{
  Ptr<PeerLink> peerLink = FindPeerLink (interface, peerAddress);
  if (peerLink != 0)
    {
      return (peerLink->LinkIsEstab ());
    }
  return false;
}
bool
PeerManagementProtocol::ShouldSendOpen (uint32_t interface, Mac48Address peerAddress)
{
  return (m_stats.linksTotal <= m_maxNumberOfPeerLinks);
}

bool
PeerManagementProtocol::ShouldAcceptOpen (uint32_t interface, Mac48Address peerAddress,
    PmpReasonCode & reasonCode)
{
  if (m_stats.linksTotal > m_maxNumberOfPeerLinks)
    {
      reasonCode = REASON11S_MESH_MAX_PEERS;
      return false;
    }
  return true;
}

void
PeerManagementProtocol::DoShiftBeacon (uint32_t interface)
{
  if (!GetBeaconCollisionAvoidance ())
    {
      return;
    }
  // If beacon interval is equal to the neighbor's one and one o more beacons received
  // by my neighbor coincide with my beacon - apply random uniformly distributed shift from
  // [-m_maxBeaconShift, m_maxBeaconShift] except 0.
  UniformVariable beaconShift (-m_maxBeaconShift, m_maxBeaconShift);
  PeerLinksMap::iterator iface = m_peerLinks.find (interface);
  NS_ASSERT (iface != m_peerLinks.end ());
  PeerManagementProtocolMacMap::const_iterator plugin = m_plugins.find (interface);
  NS_ASSERT (plugin != m_plugins.end ());
  std::map<uint32_t, Time>::const_iterator lastBeacon = m_lastBeacon.find (interface);
  std::map<uint32_t, Time>::const_iterator beaconInterval = m_beaconInterval.find (interface);
  if ((lastBeacon == m_lastBeacon.end ()) || (beaconInterval == m_beaconInterval.end ()))
    {
      return;
    }
  if (TuToTime (m_maxBeaconShift) > m_beaconInterval[interface])
    {
      NS_FATAL_ERROR ("Wrong beacon shift parameters");
      return;
    }
  for (PeerLinksOnInterface::iterator i = iface->second.begin (); i != iface->second.end (); i++)
    {
      IeBeaconTiming::NeighboursTimingUnitsList neighbours;
      neighbours = (*i)->GetBeaconTimingElement ().GetNeighboursTimingElementsList ();
      //Going through all my timing elements and detecting future beacon collisions
      for (IeBeaconTiming::NeighboursTimingUnitsList::const_iterator j = neighbours.begin (); j
          != neighbours.end (); j++)
        {
          if ((*i)->GetPeerAid () == (*j)->GetAid ())
            {
              // I am present at neighbour's list of neighbors
              continue;
            }
          //Beacon interval is stored in TU's
          if (((*j)->GetBeaconInterval ()) != TimeToTu (beaconInterval->second))
            {
              continue;
            }
          //Timing element keeps beacon receiving times in 256us units, TU=1024us
          if ((int) ((int)(*j)->GetLastBeacon () / 4 - (int)TimeToTu (lastBeacon->second)) % TimeToTu (
              beaconInterval->second)
              != 0)
            {
              continue;
            }
              int shift = 0;
              do
                {
                  shift = (int) beaconShift.GetValue ();
                }
              while (shift == 0);
              PeerManagementProtocolMacMap::iterator plugin = m_plugins.find (interface);
              NS_ASSERT (plugin != m_plugins.end ());
              plugin->second->SetBeaconShift (TuToTime (shift));
              return;
        }
    }
}
Time
PeerManagementProtocol::TuToTime (uint32_t x)
{
  return MicroSeconds (x * 1024);
}
uint32_t
PeerManagementProtocol::TimeToTu (Time x)
{
  return (uint32_t) (x.GetMicroSeconds () / 1024);
}

void
PeerManagementProtocol::NotifyLinkOpen (Mac48Address peerMp, Mac48Address peerIface, Mac48Address myIface, uint32_t interface)
{
  NS_LOG_LOGIC ("link_open " << myIface << " " << peerIface);
  m_stats.linksOpened++;
  m_stats.linksTotal++;
  if (!m_peerStatusCallback.IsNull ())
    {
      m_peerStatusCallback (peerMp, peerIface, interface, true);
    }
  m_linkOpenTraceSrc (myIface, peerIface);
}

void
PeerManagementProtocol::NotifyLinkClose (Mac48Address peerMp, Mac48Address peerIface, Mac48Address myIface, uint32_t interface)
{
  NS_LOG_LOGIC ("link_close " << myIface << " " << peerIface);
  m_stats.linksClosed++;
  m_stats.linksTotal--;
  if (!m_peerStatusCallback.IsNull ())
    {
      m_peerStatusCallback (peerMp, peerIface, interface, false);
    }
  m_linkCloseTraceSrc (myIface, peerIface);
}

void
PeerManagementProtocol::PeerLinkStatus (uint32_t interface, Mac48Address peerAddress,
    Mac48Address peerMeshPointAddress, PeerLink::PeerState ostate, PeerLink::PeerState nstate)
{
  PeerManagementProtocolMacMap::iterator plugin = m_plugins.find (interface);
  NS_ASSERT (plugin != m_plugins.end ());
  NS_LOG_DEBUG ("Link between me:" << m_address << " my interface:" << plugin->second->GetAddress ()
      << " and peer mesh point:" << peerMeshPointAddress << " and its interface:" << peerAddress
      << ", at my interface ID:" << interface << ". State movement:" << ostate << " -> " << nstate);
  if ((nstate == PeerLink::ESTAB) && (ostate != PeerLink::ESTAB))
    {
      NotifyLinkOpen (peerMeshPointAddress, peerAddress, plugin->second->GetAddress (), interface);
    }
  if ((ostate == PeerLink::ESTAB) && (nstate != PeerLink::ESTAB))
    {
      NotifyLinkClose (peerMeshPointAddress, peerAddress, plugin->second->GetAddress (), interface);
    }
  if (nstate == PeerLink::IDLE)
    {
      Ptr<PeerLink> link = FindPeerLink (interface, peerAddress);
      NS_ASSERT (link == 0);
    }
}
uint8_t
PeerManagementProtocol::GetNumberOfLinks ()
{
  return m_stats.linksTotal;
}
Ptr<IeMeshId>
PeerManagementProtocol::GetMeshId () const
{
  NS_ASSERT (m_meshId != 0);
  return m_meshId;
}
void
PeerManagementProtocol::SetMeshId (std::string s)
{
  m_meshId = Create<IeMeshId> (s);
}
Mac48Address
PeerManagementProtocol::GetAddress ()
{
  return m_address;
}
void
PeerManagementProtocol::NotifyBeaconSent (uint32_t interface, Time beaconInterval)
{
  m_lastBeacon[interface] = Simulator::Now ();
  m_beaconInterval[interface] = beaconInterval;
}
PeerManagementProtocol::Statistics::Statistics (uint16_t t) :
  linksTotal (t), linksOpened (0), linksClosed (0)
{
}
void
PeerManagementProtocol::Statistics::Print (std::ostream & os) const
{
  os << "<Statistics "
    "linksTotal=\"" << linksTotal << "\" "
    "linksOpened=\"" << linksOpened << "\" "
    "linksClosed=\"" << linksClosed << "\"/>" << std::endl;
}
void
PeerManagementProtocol::Report (std::ostream & os) const
{
  os << "<PeerManagementProtocol>" << std::endl;
  m_stats.Print (os);
  for (PeerManagementProtocolMacMap::const_iterator plugins = m_plugins.begin (); plugins != m_plugins.end (); plugins++)
    {
      //Take statistics from plugin:
      plugins->second->Report (os);
      //Print all active peer links:
      PeerLinksMap::const_iterator iface = m_peerLinks.find (plugins->second->m_ifIndex);
      NS_ASSERT (iface != m_peerLinks.end ());
      for (PeerLinksOnInterface::const_iterator i = iface->second.begin (); i != iface->second.end (); i++)
        {
          (*i)->Report (os);
        }
    }
  os << "</PeerManagementProtocol>" << std::endl;
}
void
PeerManagementProtocol::ResetStats ()
{
  m_stats = Statistics (m_stats.linksTotal); // don't reset number of links
  for (PeerManagementProtocolMacMap::const_iterator plugins = m_plugins.begin (); plugins != m_plugins.end (); plugins++)
    {
      plugins->second->ResetStats ();
    }
}

void
PeerManagementProtocol::SetBeaconCollisionAvoidance (bool enable)
{
  m_enableBca = enable;
}
bool
PeerManagementProtocol::GetBeaconCollisionAvoidance () const
{
  return m_enableBca;
}
} // namespace dot11s
} //namespace ns3

