/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
/*
 * Copyright (c) 2013 Dalian University of Technology
 * 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: Junling Bu <linlinjavaer@gmail.com>
 */
#include <iomanip>
#include <iostream>
#include <cstring>
#include "ns3/log.h"
#include "ns3/assert.h"
#include "vendor-specific-action.h"

namespace ns3 {

NS_LOG_COMPONENT_DEFINE ("VendorSpecificAction");

/*********** OrganizationIdentifier *******/

ATTRIBUTE_HELPER_CPP (OrganizationIdentifier);

OrganizationIdentifier::OrganizationIdentifier (void)
  : m_type (Unknown)
{
  NS_LOG_FUNCTION (this);
  m_type = Unknown;
  std::memset (m_oi, 0, 5);
}

OrganizationIdentifier::OrganizationIdentifier (const uint8_t *str, uint32_t length)
{
  NS_LOG_FUNCTION (this << str << length);
  if (length == 3)
    {
      m_type = OUI24;
      std::memcpy (m_oi, str, length);
    }
  else if (length == 5)
    {
      m_type = OUI36;
      std::memcpy (m_oi, str, length);
    }
  else
    {
      m_type = Unknown;
      NS_FATAL_ERROR ("cannot support organization identifier with length=" << length);
    }
}

OrganizationIdentifier&
OrganizationIdentifier::operator= (const OrganizationIdentifier& oi)
{
  this->m_type = oi.m_type;
  std::memcpy (this->m_oi, oi.m_oi, 5);
  return (*this);
}

OrganizationIdentifier::~OrganizationIdentifier (void)
{
  NS_LOG_FUNCTION (this);
}

uint8_t
OrganizationIdentifier::GetManagementId (void) const
{
  NS_LOG_FUNCTION (this);
  NS_ASSERT (m_type == OUI36);
  return (m_oi[4] & 0x0f);
}

bool
OrganizationIdentifier::IsNull (void) const
{
  NS_LOG_FUNCTION (this);
  return m_type == Unknown;
}

uint32_t
OrganizationIdentifier::GetSerializedSize (void) const
{
  NS_LOG_FUNCTION (this);
  switch (m_type)
    {
    case OUI24:
      return 3;
    case OUI36:
      return 5;
    case Unknown:
    default:
      NS_FATAL_ERROR_NO_MSG ();
      return 0;
    }
}

void
OrganizationIdentifier::SetType (enum OrganizationIdentifierType type)
{
  NS_LOG_FUNCTION (this);
  m_type = type;
}

enum OrganizationIdentifier::OrganizationIdentifierType
OrganizationIdentifier::GetType (void) const
{
  NS_LOG_FUNCTION (this);
  return m_type;
}

void
OrganizationIdentifier::Serialize (Buffer::Iterator start) const
{
  NS_LOG_FUNCTION (this << &start);
  start.Write (m_oi, GetSerializedSize ());
}

/*  because OrganizationIdentifier field is not standard
 *  and the length of OrganizationIdentifier is variable
 *  so data parse here is troublesome
 */
uint32_t
OrganizationIdentifier::Deserialize (Buffer::Iterator start)
{
  NS_LOG_FUNCTION (this << &start);
  // first try to parse OUI24 with 3 bytes
  start.Read (m_oi,  3);
  for (std::vector<OrganizationIdentifier>::iterator  i = OrganizationIdentifiers.begin (); i != OrganizationIdentifiers.end (); ++i)
    {
      if ((i->m_type == OUI24)
          && (std::memcmp (i->m_oi, m_oi, 3) == 0 ))
        {
          m_type = OUI24;
          return 3;
        }
    }

  // then try to parse OUI36 with 5 bytes
  start.Read (m_oi + 3,  2);
  for (std::vector<OrganizationIdentifier>::iterator  i = OrganizationIdentifiers.begin (); i != OrganizationIdentifiers.end (); ++i)
    {
      if ((i->m_type == OUI36)
          && (std::memcmp (i->m_oi, m_oi, 4) == 0 ))
        {
          // OUI36 first check 4 bytes, then check half of the 5th byte
          if ((i->m_oi[4] & 0xf0) == (m_oi[4] & 0xf0))
            {
              m_type = OUI36;
              return 5;
            }
        }
    }

  // if we cannot deserialize the organization identifier field,
  // we will fail
  NS_FATAL_ERROR ("cannot deserialize the organization identifier field successfully");
  return 0;
}

bool operator == (const OrganizationIdentifier& a, const OrganizationIdentifier& b)
{
  if (a.m_type != b.m_type)
    {
      return false;
    }

  if (a.m_type == OrganizationIdentifier::OUI24)
    {
      return memcmp (a.m_oi, b.m_oi, 3) == 0;
    }

  if (a.m_type == OrganizationIdentifier::OUI36)
    {
      return (memcmp (a.m_oi, b.m_oi, 4) == 0)
             && ((a.m_oi[4] & 0xf0) == (b.m_oi[4] & 0xf0));
    }

  return false;
}

bool operator != (const OrganizationIdentifier& a, const OrganizationIdentifier& b)
{
  return !(a == b);
}

bool operator < (const OrganizationIdentifier& a, const OrganizationIdentifier& b)
{
  return memcmp (a.m_oi, b.m_oi, std::min (a.m_type, b.m_type)) < 0;
}

std::ostream& operator << (std::ostream& os, const OrganizationIdentifier& oi)
{
  for (int i = 0; i < oi.m_type; i++)
    {
      os << "0x" << std::hex << static_cast<int> (oi.m_oi[i]) << " ";
    }
  os << std::endl;
  return os;
}

std::istream& operator >> (std::istream& is, const OrganizationIdentifier& oi)
{
  return is;
}

/*********** VendorSpecificActionHeader *******/
NS_OBJECT_ENSURE_REGISTERED (VendorSpecificActionHeader);

VendorSpecificActionHeader::VendorSpecificActionHeader (void)
  : m_oi (),
    m_category (CATEGORY_OF_VSA)
{
  NS_LOG_FUNCTION (this);
}

VendorSpecificActionHeader::~VendorSpecificActionHeader (void)
{
  NS_LOG_FUNCTION (this);
}

void
VendorSpecificActionHeader::SetOrganizationIdentifier (OrganizationIdentifier oi)
{
  NS_LOG_FUNCTION (this << oi);
  m_oi = oi;
}

OrganizationIdentifier
VendorSpecificActionHeader::GetOrganizationIdentifier (void) const
{
  NS_LOG_FUNCTION (this);
  return m_oi;
}

TypeId
VendorSpecificActionHeader::GetTypeId (void)
{
  static TypeId tid = TypeId ("ns3::VendorSpecificActionHeader")
    .SetParent<Header> ()
    .AddConstructor<VendorSpecificActionHeader> ()
  ;

  return tid;
}

uint8_t
VendorSpecificActionHeader::GetCategory (void) const
{
  NS_LOG_FUNCTION (this);
  return m_category;
}

TypeId
VendorSpecificActionHeader::GetInstanceTypeId (void) const
{
  NS_LOG_FUNCTION (this);
  return GetTypeId ();
}

void
VendorSpecificActionHeader::Print (std::ostream &os) const
{
  NS_LOG_FUNCTION (this << &os);
  os << "VendorSpecificActionHeader[ "
     << "category = 0x" << std::hex << (int)m_category
     << "organization identifier = " << m_oi
     << std::dec;
}

uint32_t
VendorSpecificActionHeader::GetSerializedSize (void) const
{
  NS_LOG_FUNCTION (this);
  return sizeof(m_category) + m_oi.GetSerializedSize ();
}

void
VendorSpecificActionHeader::Serialize (Buffer::Iterator start) const
{
  NS_LOG_FUNCTION (this << &start);
  start.WriteU8 (m_category);
  m_oi.Serialize (start);
}

uint32_t
VendorSpecificActionHeader::Deserialize (Buffer::Iterator start)
{
  NS_LOG_FUNCTION (this << &start);
  m_category = start.ReadU8 ();
  if (m_category != CATEGORY_OF_VSA)
    {
      return 0;
    }
  m_oi.Deserialize (start);

  return GetSerializedSize ();
}

/********* VendorSpecificContentManager ***********/
VendorSpecificContentManager::VendorSpecificContentManager (void)
{
  NS_LOG_FUNCTION (this);
}

VendorSpecificContentManager::~VendorSpecificContentManager (void)
{
  NS_LOG_FUNCTION (this);
}

void
VendorSpecificContentManager::RegisterVscCallback (OrganizationIdentifier oi, VscCallback cb)
{
  NS_LOG_FUNCTION (this << oi << &cb);
  if (IsVscCallbackRegistered (oi))
    {
      NS_LOG_WARN ("there is already a VsaCallback registered for OrganizationIdentifier " << oi);
    }
  m_callbacks.insert (std::make_pair (oi, cb));
}

void
VendorSpecificContentManager::DeregisterVscCallback (OrganizationIdentifier &oi)
{
  NS_LOG_FUNCTION (this << oi);
  m_callbacks.erase (oi);
}

bool
VendorSpecificContentManager::IsVscCallbackRegistered (OrganizationIdentifier &oi)
{
  NS_LOG_FUNCTION (this << oi);
  if (m_callbacks.find (oi) == m_callbacks.end ())
    {
      OrganizationIdentifiers.push_back (oi);
      return false;
    }
  return true;
}

static VscCallback null_callback = MakeNullCallback<bool, Ptr<WifiMac>, const OrganizationIdentifier &,Ptr<const Packet>,const Address &> ();

VscCallback
VendorSpecificContentManager::FindVscCallback (OrganizationIdentifier &oi)
{
  NS_LOG_FUNCTION (this << oi);
  VscCallbacksI i;
  i = m_callbacks.find (oi);
  return (i == m_callbacks.end ()) ? null_callback : i->second;
}

} // namespace ns3
