/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
/*
 * Copyright (c) 2010 Hemanth Narra
 *
 * 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: Hemanth Narra <hemanth@ittc.ku.com>
 *
 * James P.G. Sterbenz <jpgs@ittc.ku.edu>, director
 * ResiliNets Research Group  http://wiki.ittc.ku.edu/resilinets
 * Information and Telecommunication Technology Center (ITTC)
 * and Department of Electrical Engineering and Computer Science
 * The University of Kansas Lawrence, KS USA.
 *
 * Work supported in part by NSF FIND (Future Internet Design) Program
 * under grant CNS-0626918 (Postmodern Internet Architecture),
 * NSF grant CNS-1050226 (Multilayer Network Resilience Analysis and Experimentation on GENI),
 * US Department of Defense (DoD), and ITTC at The University of Kansas.
 */

#include <iostream>
#include <cmath>
#include "ns3/core-module.h"
#include "ns3/network-module.h"
#include "ns3/applications-module.h"
#include "ns3/mobility-module.h"
#include "ns3/config-store-module.h"
#include "ns3/internet-module.h"
#include "ns3/dsdv-helper.h"
#include "ns3/netanim-module.h"
#include "ns3/flow-monitor-helper.h"
#include "ns3/yans-wifi-helper.h"

using namespace ns3;

uint16_t portW = 9;
uint16_t portWD = 80;
uint16_t portLTE = 1000;

NS_LOG_COMPONENT_DEFINE ("DsdvManetExample");

/**
 * \ingroup dsdv
 * \ingroup dsdv-examples
 * \ingroup examples
 *
 * \brief DSDV Manet example
 */
class DsdvManetExample
{
public:
  DsdvManetExample ();
  /**
   * Run function
   * \param nWifis The total number of nodes
   * \param nSinks The total number of receivers
   * \param totalTime The total simulation time
   * \param rate The network speed
   * \param phyMode The physical mode
   * \param nodeSpeed The node speed
   * \param periodicUpdateInterval The routing update interval
   * \param settlingTime The routing update settling time
   * \param dataStart The data transmission start time
   * \param printRoutes print the routes if true
   * \param CSVfileName The CSV file name
   */
  void CaseRun (uint32_t nWifis,
                uint32_t nSinks,
                double totalTime,
//                std::string rate,
//                std::string phyMode,
                uint32_t nodeSpeed,
                uint32_t periodicUpdateInterval,
                uint32_t settlingTime,
                double dataStart,
                bool printRoutes,
                std::string CSVfileName);

private:
  uint32_t m_nWifis; ///< total number of nodes
  uint32_t m_nSinks; ///< number of receiver nodes
  double m_totalTime; ///< total simulation time (in seconds)
//  std::string m_rate; ///< network bandwidth
//  std::string m_phyMode; ///< remote station manager data mode
  uint32_t m_nodeSpeed; ///< mobility speed
  uint32_t m_periodicUpdateInterval; ///< routing update interval
  uint32_t m_settlingTime; ///< routing setting time
  double m_dataStart; ///< time to start data transmissions (seconds)
  uint32_t bytesTotal; ///< total bytes received by all nodes
  uint32_t packetsReceived; ///< total packets received by all nodes
  bool m_printRoutes; ///< print routing table
  std::string m_CSVfileName; ///< CSV file name
  NetDeviceContainer deviceW, deviceWD, deviceLTE;
  NodeContainer nodes; ///< the collection of nodes
  Ipv4InterfaceContainer interfaceW, interfaceWD, interfaceLTE; ///< the collection of interfaces

private:
  /// Create and initialize all nodes
  void CreateNodes ();
  /**
   * Create and initialize all devices
   * \param tr_name The trace file name
   */
  void CreateDevices (std::string tr_name);
  /**
   * Create network
   * \param tr_name The trace file name
   */
  void InstallInternetStack (std::string tr_name);
  /// Create data sinks and sources
  void InstallApplications ();
  /// Setup mobility model
  void SetupMobility ();
  /**
   * Packet receive function
   * \param socket The communication socket
   */
  void ReceivePacket (Ptr <Socket> socket);
  /**
   * Setup packet receivers
   * \param addr the receiving IPv4 address
   * \param node the receiving node
   * \returns the communication socket
   */
  Ptr <Socket> SetupPacketReceiveW (Ipv4Address addr, Ptr <Node> node );
  Ptr <Socket> SetupPacketReceiveWD (Ipv4Address addr, Ptr <Node> node );
  Ptr <Socket> SetupPacketReceiveLTE (Ipv4Address addr, Ptr <Node> node );
  /// Check network throughput
  void CheckThroughput ();

};

int main (int argc, char **argv)
{
  DsdvManetExample test;
  uint32_t nWifis = 2;
  uint32_t nSinks = 1;
  double totalTime = 10.0;
//  std::string rate ("8Mbps");
//  std::string phyMode ("OfdmRate12Mbps");
  uint32_t nodeSpeed = 5; // in m/s
  std::string appl = "all";
  uint32_t periodicUpdateInterval = 15;
  uint32_t settlingTime = 6;
  double dataStart = 5.0;
  bool printRoutingTable = true;
  std::string CSVfileName = "DsdvManetExample.csv";

  CommandLine cmd;
  cmd.AddValue ("nWifis", "Number of wifi nodes[Default:30]", nWifis);
  cmd.AddValue ("nSinks", "Number of wifi sink nodes[Default:10]", nSinks);
  cmd.AddValue ("totalTime", "Total Simulation time[Default:100]", totalTime);
//  cmd.AddValue ("phyMode", "Wifi Phy mode[Default:DsssRate11Mbps]", phyMode);
//  cmd.AddValue ("rate", "CBR traffic rate[Default:8Mbps]", rate);
  cmd.AddValue ("nodeSpeed", "Node speed in RandomWayPoint model[Default:10]", nodeSpeed);
  cmd.AddValue ("periodicUpdateInterval", "Periodic Interval Time[Default=15]", periodicUpdateInterval);
  cmd.AddValue ("settlingTime", "Settling Time before sending out an update for changed metric[Default=6]", settlingTime);
  cmd.AddValue ("dataStart", "Time at which nodes start to transmit data[Default=50.0]", dataStart);
  cmd.AddValue ("printRoutingTable", "print routing table for nodes[Default:1]", printRoutingTable);
  cmd.AddValue ("CSVfileName", "The name of the CSV output file name[Default:DsdvManetExample.csv]", CSVfileName);
  cmd.Parse (argc, argv);

  std::ofstream out (CSVfileName.c_str ());
  out << "SimulationSecond," <<
  "ReceiveRate," <<
  "PacketsReceived," <<
  "NumberOfSinks," <<
  std::endl;
  out.close ();

  SeedManager::SetSeed (12345);

  Config::SetDefault ("ns3::OnOffApplication::PacketSize", StringValue ("1000"));
//  Config::SetDefault ("ns3::OnOffApplication::DataRate", StringValue (rate));
//  Config::SetDefault ("ns3::WifiRemoteStationManager::NonUnicastMode", StringValue (phyMode));
  Config::SetDefault ("ns3::WifiRemoteStationManager::RtsCtsThreshold", StringValue ("2000"));

  test = DsdvManetExample ();
  test.CaseRun (nWifis, nSinks, totalTime, nodeSpeed, periodicUpdateInterval, settlingTime, dataStart, printRoutingTable, CSVfileName);

  return 0;
}

DsdvManetExample::DsdvManetExample ()
  : bytesTotal (0),
    packetsReceived (0)
{
}

static inline std::string
PrintReceivedPacket (Ptr<Socket> socket, Ptr<Packet> packet, Address senderAddress)
{
  std::ostringstream oss;

  oss << Simulator::Now ().GetSeconds () << " " << socket->GetNode ()->GetId ();
  Ptr<Node> node = socket->GetNode ();
  Ptr<Ipv4> ipv4_ptr = node->GetObject<Ipv4> (); // Get Ipv4 instance of the node
  Ipv4Address addr0 = ipv4_ptr->GetAddress (1, 0).GetLocal (); // Get Ipv4InterfaceAddress of 1st interface.
  Ipv4Address addr1 = ipv4_ptr->GetAddress (2, 0).GetLocal (); // Get Ipv4InterfaceAddress of 1st interface.
  Ipv4Address addr2 = ipv4_ptr->GetAddress (3, 0).GetLocal (); // Get Ipv4InterfaceAddress of 1st interface.
  //std::cout << "Node: " << node->GetId() << " Address: " << addr<<std::endl;
  if (InetSocketAddress::IsMatchingType (senderAddress))
    {
      InetSocketAddress addr = InetSocketAddress::ConvertFrom (senderAddress);

      oss << "Node: " << node->GetId() << " Address0: " << addr0 << " received one packet from " << addr.GetIpv4 () << std::endl;
      oss << "Node: " << node->GetId() << " Address1: " << addr1 << " received one packet from " << addr.GetIpv4 () << std::endl;
      oss << "Node: " << node->GetId() << " Address2: " << addr2 << " received one packet from " << addr.GetIpv4 () << std::endl;

    }
  else
    {
      oss << " received one packet!";
    }
  return oss.str ();
}

void
DsdvManetExample::ReceivePacket (Ptr <Socket> socket)
{
	Ptr<Packet> packet;
	  Address senderAddress;
	  while ((packet = socket->RecvFrom (senderAddress)))
	    {
	      bytesTotal += packet->GetSize ();
	      packetsReceived += 1;
	      NS_LOG_UNCOND (PrintReceivedPacket (socket, packet, senderAddress));
	    }
}



void
DsdvManetExample::CheckThroughput ()
{
  double kbs = (bytesTotal * 8.0) / 1000;
  bytesTotal = 0;

  std::ofstream out (m_CSVfileName.c_str (), std::ios::app);

  out << (Simulator::Now ()).GetSeconds () << "," << kbs << "," << packetsReceived << "," << m_nSinks << std::endl;

  out.close ();
  packetsReceived = 0;
  Simulator::Schedule (Seconds (1.0), &DsdvManetExample::CheckThroughput, this);
}

Ptr <Socket>
DsdvManetExample::SetupPacketReceiveW (Ipv4Address addr, Ptr <Node> node)
{

  TypeId tid = TypeId::LookupByName ("ns3::UdpSocketFactory");
  Ptr <Socket> sink = Socket::CreateSocket (node, tid);

  InetSocketAddress local = InetSocketAddress (addr, portW);

  sink->Bind (local);

  sink->SetRecvCallback (MakeCallback ( &DsdvManetExample::ReceivePacket, this));

  return sink;
}

Ptr <Socket>
DsdvManetExample::SetupPacketReceiveWD (Ipv4Address addr, Ptr <Node> node)
{

  TypeId tid = TypeId::LookupByName ("ns3::UdpSocketFactory");
  Ptr <Socket> sink = Socket::CreateSocket (node, tid);

  InetSocketAddress local = InetSocketAddress (addr, portWD);

  sink->Bind (local);

  sink->SetRecvCallback (MakeCallback ( &DsdvManetExample::ReceivePacket, this));

  return sink;
}

Ptr <Socket>
DsdvManetExample::SetupPacketReceiveLTE (Ipv4Address addr, Ptr <Node> node)
{

  TypeId tid = TypeId::LookupByName ("ns3::UdpSocketFactory");
  Ptr <Socket> sink = Socket::CreateSocket (node, tid);

  InetSocketAddress local = InetSocketAddress (addr, portLTE);

  sink->Bind (local);

  sink->SetRecvCallback (MakeCallback ( &DsdvManetExample::ReceivePacket, this));

  return sink;
}
//std::string rate,std::string phyMode,

void
DsdvManetExample::CaseRun (uint32_t nWifis, uint32_t nSinks, double totalTime,  uint32_t nodeSpeed, uint32_t periodicUpdateInterval, uint32_t settlingTime,
                           double dataStart, bool printRoutes, std::string CSVfileName)
{
  m_nWifis = nWifis;
  m_nSinks = nSinks;
  m_totalTime = totalTime;
//  m_rate = rate;
//  m_phyMode = phyMode;
  m_nodeSpeed = nodeSpeed;
  m_periodicUpdateInterval = periodicUpdateInterval;
  m_settlingTime = settlingTime;
  m_dataStart = dataStart;
  m_printRoutes = printRoutes;
  m_CSVfileName = CSVfileName;

  std::stringstream ss;
  ss << m_nWifis;
  std::string t_nodes = ss.str ();

  std::stringstream ss3;
  ss3 << m_totalTime;
  std::string sTotalTime = ss3.str ();

  std::string tr_name = "Dsdv_Manet_" + t_nodes + "Nodes_" + sTotalTime + "SimTime";
  std::cout << "Trace file generated is " << tr_name << ".tr\n";

  CreateNodes ();
  CreateDevices (tr_name);
  SetupMobility ();
  InstallInternetStack (tr_name);
  InstallApplications ();

  std::cout << "\nStarting simulation for " << m_totalTime << " s ...\n";
  AsciiTraceHelper ascii;
  MobilityHelper::EnableAsciiAll (ascii.CreateFileStream (tr_name + ".mob"));
  CheckThroughput ();
  Ptr<FlowMonitor> flowmon;
  FlowMonitorHelper flowmonHelper;
  flowmon = flowmonHelper.InstallAll ();

  NS_LOG_INFO ("Run Simulation.");
  AnimationInterface anim("anim.xml");
  Simulator::Stop (Seconds (m_totalTime));
  Simulator::Run ();
  flowmon->SerializeToXmlFile ((tr_name + ".flowmon").c_str(), false, false);
  Simulator::Destroy ();
}

void
DsdvManetExample::CreateNodes ()
{
  std::cout << "Creating " << (unsigned) m_nWifis << " nodes.\n";
  nodes.Create (m_nWifis);
  NS_ASSERT_MSG (m_nWifis > m_nSinks, "Sinks must be less or equal to the number of nodes in network");
}

void
DsdvManetExample::SetupMobility ()
{
  MobilityHelper mobility;
  ObjectFactory pos;
  pos.SetTypeId ("ns3::RandomRectanglePositionAllocator");
  pos.Set ("X", StringValue ("ns3::UniformRandomVariable[Min=0.0|Max=100.0]"));
  pos.Set ("Y", StringValue ("ns3::UniformRandomVariable[Min=0.0|Max=100.0]"));

  std::ostringstream speedConstantRandomVariableStream;
  speedConstantRandomVariableStream << "ns3::ConstantRandomVariable[Constant=" << m_nodeSpeed << "]";

  Ptr <PositionAllocator> taPositionAlloc = pos.Create ()->GetObject <PositionAllocator> ();
  mobility.SetMobilityModel ("ns3::RandomWaypointMobilityModel", "Speed", StringValue (speedConstantRandomVariableStream.str ()),
                             "Pause", StringValue ("ns3::ConstantRandomVariable[Constant=2.0]"), "PositionAllocator", PointerValue (taPositionAlloc));
  mobility.SetPositionAllocator (taPositionAlloc);
  mobility.Install (nodes);
}

void
DsdvManetExample::CreateDevices (std::string tr_name)
{
	 WifiMacHelper wifiMac;
	  wifiMac.SetType ("ns3::AdhocWifiMac");
	  YansWifiPhyHelper wifiPhy = YansWifiPhyHelper::Default ();
	  YansWifiChannelHelper wifiChannel;
	  wifiChannel.SetPropagationDelay ("ns3::ConstantSpeedPropagationDelayModel");
	  wifiChannel.AddPropagationLoss ("ns3::FriisPropagationLossModel");
	  wifiPhy.SetChannel (wifiChannel.Create ());
	  WifiHelper wifi, wifiD, wLTE;
	  wifi.SetStandard (WIFI_PHY_STANDARD_80211b);
	  wifiD.SetStandard(WIFI_PHY_STANDARD_80211b);
	  wLTE.SetStandard(WIFI_PHY_STANDARD_80211g);

	  wifi.SetRemoteStationManager ("ns3::ConstantRateWifiManager", "DataMode", StringValue ("DsssRate2Mbps"), "ControlMode", StringValue ("DsssRate2Mbps"));
	  wifiD.SetRemoteStationManager("ns3::ConstantRateWifiManager", "DataMode", StringValue ("DsssRate11Mbps"), "ControlMode", StringValue ("DsssRate11Mbps"));
	  wLTE.SetRemoteStationManager("ns3::ConstantRateWifiManager", "DataMode", StringValue ("ErpOfdmRate54Mbps"), "ControlMode", StringValue ("ErpOfdmRate54Mbps"));

	  deviceW = wifi.Install (wifiPhy, wifiMac, nodes);
	  deviceWD = wifiD.Install (wifiPhy, wifiMac, nodes);
	  deviceLTE = wLTE.Install (wifiPhy, wifiMac, nodes);

	  AsciiTraceHelper ascii;
	  wifiPhy.EnableAsciiAll (ascii.CreateFileStream (tr_name + ".tr"));
	  wifiPhy.EnablePcapAll (tr_name);

}

void
DsdvManetExample::InstallInternetStack (std::string tr_name)
{
//  DsdvHelper dsdv;
//  dsdv.Set ("PeriodicUpdateInterval", TimeValue (Seconds (m_periodicUpdateInterval)));
//  dsdv.Set ("SettlingTime", TimeValue (Seconds (m_settlingTime)));
  InternetStackHelper stack;
  //stack.SetRoutingHelper (dsdv); // has effect on the next Install ()
  stack.Install (nodes);
  Ipv4AddressHelper address;
  address.SetBase ("10.1.1.0", "255.255.255.0");
  interfaceW = address.Assign (deviceW);
  address.SetBase ("10.1.2.0", "255.255.255.0");
  interfaceWD = address.Assign (deviceWD);
  address.SetBase ("10.1.3.0", "255.255.255.0");
  interfaceLTE = address.Assign (deviceLTE);

//  if (m_printRoutes)
//    {
//      Ptr<OutputStreamWrapper> routingStream = Create<OutputStreamWrapper> ((tr_name + ".routes"), std::ios::out);
//      dsdv.PrintRoutingTableAllAt (Seconds (61), routingStream);
//    }
}

void
DsdvManetExample::InstallApplications ()
{
  for (uint32_t i = 0; i <= m_nSinks - 1; i++ )
    {
      Ptr<Node> node = NodeList::GetNode (i);
      Ipv4Address nodeAddress = node->GetObject<Ipv4> ()->GetAddress (1, 0).GetLocal ();
      NS_LOG_DEBUG("Node Address: " << nodeAddress);
      Ipv4Address nodeAddress1 = node->GetObject<Ipv4> ()->GetAddress (2, 0).GetLocal ();
      NS_LOG_DEBUG("Node Address1: " << nodeAddress1);
      Ipv4Address nodeAddress2 = node->GetObject<Ipv4> ()->GetAddress (3, 0).GetLocal ();
      NS_LOG_DEBUG("Node Address2: " << nodeAddress2);
      Ptr<Socket> sink = SetupPacketReceiveW (nodeAddress, node);
      Ptr<Socket> sink1 = SetupPacketReceiveWD(nodeAddress1, node);
      Ptr<Socket> sink2 = SetupPacketReceiveLTE(nodeAddress2, node);
    }

  for (uint32_t clientNode = 0; clientNode <= m_nWifis - 1; clientNode++ )
    {
      for (uint32_t j = 0; j <= m_nSinks - 1; j++ )
        {
          OnOffHelper onoff1 ("ns3::UdpSocketFactory", Address (InetSocketAddress (interfaceW.GetAddress (j), portW)));
          NS_LOG_DEBUG("Interface Address of WiFi: " << interfaceW.GetAddress(j) << ", and Port Number: " << portW);
          onoff1.SetAttribute ("OnTime", StringValue ("ns3::ConstantRandomVariable[Constant=1.0]"));
          onoff1.SetAttribute ("OffTime", StringValue ("ns3::ConstantRandomVariable[Constant=0.0]"));

          if (j != clientNode)
            {
              ApplicationContainer apps1 = onoff1.Install (nodes.Get (clientNode));
              Ptr<UniformRandomVariable> var = CreateObject<UniformRandomVariable> ();
              apps1.Start (Seconds (var->GetValue (m_dataStart, m_dataStart + 1)));
              NS_LOG_DEBUG("WiFi Interface APP Port Number: " << portW);
              apps1.Stop (Seconds (m_totalTime));
            }
        }
    }

  for (uint32_t clientNode = 0; clientNode <= m_nWifis - 1; clientNode++ )
      {
        for (uint32_t j = 0; j <= m_nSinks - 1; j++ )
          {
            OnOffHelper onoff1 ("ns3::UdpSocketFactory", Address (InetSocketAddress (interfaceWD.GetAddress (j), portWD)));
            NS_LOG_DEBUG("Interface Address of Wifi Direct: " << interfaceWD.GetAddress(j) << ", and Port Number: " << portWD);
            onoff1.SetAttribute ("OnTime", StringValue ("ns3::ConstantRandomVariable[Constant=1.0]"));
            onoff1.SetAttribute ("OffTime", StringValue ("ns3::ConstantRandomVariable[Constant=0.0]"));

            if (j != clientNode)
              {
                ApplicationContainer apps1 = onoff1.Install (nodes.Get (clientNode));
                Ptr<UniformRandomVariable> var = CreateObject<UniformRandomVariable> ();
                apps1.Start (Seconds (var->GetValue (m_dataStart, m_dataStart + 1)));
                NS_LOG_DEBUG("WiFi Direct Interface APP Port Number: "<< portWD);
                apps1.Stop (Seconds (m_totalTime));
              }
          }
      }

  for (uint32_t clientNode = 0; clientNode <= m_nWifis - 1; clientNode++ )
      {
        for (uint32_t j = 0; j <= m_nSinks - 1; j++ )
          {
            OnOffHelper onoff1 ("ns3::UdpSocketFactory", Address (InetSocketAddress (interfaceLTE.GetAddress (j), portLTE)));
            NS_LOG_DEBUG("Interface Address of LTE: " << interfaceLTE.GetAddress(j) << ", and Port Number:" << portLTE);
            onoff1.SetAttribute ("OnTime", StringValue ("ns3::ConstantRandomVariable[Constant=1.0]"));
            onoff1.SetAttribute ("OffTime", StringValue ("ns3::ConstantRandomVariable[Constant=0.0]"));

            if (j != clientNode)
              {
                ApplicationContainer apps1 = onoff1.Install (nodes.Get (clientNode));
                Ptr<UniformRandomVariable> var = CreateObject<UniformRandomVariable> ();
                apps1.Start (Seconds (var->GetValue (m_dataStart, m_dataStart + 1)));
                NS_LOG_DEBUG("LTE Interface APP Port Number: " << portLTE);
                apps1.Stop (Seconds (m_totalTime));
              }
          }
      }
}

