#include "ns3/core-module.h"
#include "ns3/mobility-module.h"
#include "ns3/wifi-module.h"
#include "ns3/internet-module.h"
#include "ns3/network-module.h"
#include "ns3/config-store-module.h"
#include "ns3/olsr-helper.h"
#include "ns3/ipv4-static-routing-helper.h"
#include "ns3/ipv4-list-routing-helper.h"
#include "ns3/error-model.h"
#include "ns3/flow-monitor-helper.h"
#include "ns3/applications-module.h"
 #include "ns3/aodv-module.h"
 #include "ns3/olsr-module.h"
//#include "ns3/ipv4-global-routing-helper.h"
//#include "ns3/traffic-control-module.h"
#include "ns3/flow-monitor-module.h"
//#include "ns3/gnuplot.h"
#include "ns3/netanim-module.h"

#include <iostream>
#include <vector>
#include <fstream>
#include <string>
using namespace ns3;

NS_LOG_COMPONENT_DEFINE ("TcpVariantsComparison");


void ReceivePacket (Ptr<Socket> socket)
{ Ptr<Packet> packet;
  uint32_t bytesTotal;
  uint32_t packetsReceived;
  while (packet = socket->Recv ())
    {  bytesTotal += packet->GetSize ();
       packetsReceived += 1;
      NS_LOG_UNCOND ("Received one packet!");
    }
    
}

static void GenerateTraffic (Ptr<Socket> socket, uint32_t pktSize,
                             uint32_t pktCount, Time pktInterval )
{
  if (pktCount > 0)
    { 
      socket->Send (Create<Packet> (pktSize));
      NS_LOG_UNCOND("socket is sending a Packet with size" << pktSize <<", "<<"socket is sending a Packet number" << pktCount <<", "<< "socket is sending a Packet at time" << pktInterval);

      Simulator::Schedule (pktInterval, &GenerateTraffic,
                           socket, pktSize,pktCount-1, pktInterval);
      NS_LOG_UNCOND("packet is scheduled to be sent in Simulator::schedule");
    }
  else
    {
      socket->Close ();
      std::cout<<"socket is closed since no packet to sent"<<std::endl;
    }
}

 static void 
 CourseChange (std::string foo, Ptr<const MobilityModel> mobility)
 {
   Vector pos = mobility->GetPosition ();
   Vector vel = mobility->GetVelocity ();
   std::cout << Simulator::Now () << ", model=" << mobility << ", POS: x=" << pos.x << ", y=" << pos.y
             << ", z=" << pos.z << "; VEL:" << vel.x << ", y=" << vel.y
             << ", z=" << vel.z << std::endl;
 }

int main (int argc, char *argv[])
{ 

  Packet::EnablePrinting ();
  std::string phyMode ("DsssRate1Mbps");

  
  double rss = -95;  // -dBm
  uint32_t packetSize = 1000; // bytes
  uint32_t numPackets = 10;
  double interval = 0.005; // seconds
  bool verbose = false;
  double distance = 50;

   LogComponentEnable("PhilBuffer", LOG_LEVEL_ALL);
  // LogComponentEnable("PhilHeader", LOG_LEVEL_ALL);
   LogComponentEnable("WifiNetDevice", LOG_LEVEL_ALL);
  // LogComponentEnable("DcaTxop", LOG_LEVEL_ALL);
  //LogComponentEnable("YansWifiChannel", LOG_LEVEL_ALL);
  
  CommandLine cmd;

  cmd.AddValue ("phyMode", "Wifi Phy mode", phyMode);
  cmd.AddValue ("rss", "received signal strength", rss);
  cmd.AddValue ("packetSize", "size of application packet sent", packetSize);
  cmd.AddValue ("numPackets", "number of packets generated", numPackets);
  cmd.AddValue ("interval", "interval (seconds) between packets", interval);
  cmd.AddValue ("verbose", "turn on all AsimpleNetDevice log components", verbose);

  cmd.Parse (argc, argv);

  //LogComponentEnable("PhilBuffer", LOG_LEVEL_ALL);
  // Convert to time object
  Time interPacketInterval = Seconds (interval);

  // // disable fragmentation for frames below 2200 bytes
  // Config::SetDefault ("ns3::WifiRemoteStationManager::FragmentationThreshold", StringValue ("2200"));
  // // turn off RTS/CTS for frames below 2200 bytes
  // Config::SetDefault ("ns3::WifiRemoteStationManager::RtsCtsThreshold", StringValue ("2200"));
  // // Fix non-unicast data rate to be the same as that of unicast
  // Config::SetDefault ("ns3::WifiRemoteStationManager::NonUnicastMode",
  //                     StringValue (phyMode));

  NodeContainer c;
  c.Create (2);

  //----------------------------------------------------------------------------------------------------------------------
  //--setting up wifi phy and channel using helpers
  //----------------------------------------------------------------------------------------------------------------------
  // The below set of helpers will help us to put together the asimple NICs we want
  WifiHelper wifi;

  if (verbose)
    {
      wifi.EnableLogComponents ();  // Turn on all asimple logging
    }
  wifi.SetStandard (WIFI_PHY_STANDARD_80211b);

  YansWifiPhyHelper wifiPhy =  YansWifiPhyHelper::Default ();
  // This is one parameter that matters when using FixedRssLossModel
  // set it to zero; otherwise, gain will be added
  //wifiPhy.Set ("RxGain", DoubleValue (0) );
  // ns-3 supports RadioTap and Prism tracing extensions for 802.11b
  //wifiPhy.SetPcapDataLinkType (YansWifiPhyHelper::DLT_IEEE802_11_RADIO);

  YansWifiChannelHelper wifiChannel = YansWifiChannelHelper::Default();
  wifiChannel.SetPropagationDelay ("ns3::ConstantSpeedPropagationDelayModel");
   // The below FixedRssLossModel will cause the rss to be fixed regardless
   // of the distance between the two stations, and the transmit power
  //wifiChannel.AddPropagationLoss ("ns3::FriisPropagationLossModel", "Frequency",DoubleValue(2.412e9));
  //wifiChannel.AddPropagationLoss ("ns3::FixedRssLossModel","Rss",DoubleValue (rss));
 //wifiChannel.AddPropagationLoss("ns3::RangePropagationLossModel",
  //                               "MaxRange", DoubleValue(50));
  wifiPhy.SetChannel (wifiChannel.Create ());
  
  WifiMacHelper mac;
  mac.SetType ("ns3::AdhocWifiMac");

  //----------------------------------------------------------------------------------------------------------------------
  //-- Add a mac and disable rate control
  //----------------------------------------------------------------------------------------------------------------------
  wifi.SetRemoteStationManager ("ns3::ConstantRateWifiManager",
                                 "DataMode",StringValue (phyMode),
                                 "ControlMode",StringValue (phyMode));

  NetDeviceContainer devices = wifi.Install (wifiPhy, mac, c);


  // Note that with FixedRssLossModel, the positions below are not
  // used for received signal strength.
  // MobilityHelper mobility;
  // Ptr<ListPositionAllocator> positionAlloc = CreateObject<ListPositionAllocator> ();
  // positionAlloc->Add (Vector (0.0, 0.0, 0.0));
  // positionAlloc->Add (Vector (5.0, 0.0, 0.0));
  // mobility.SetPositionAllocator (positionAlloc);
  // mobility.SetMobilityModel ("ns3::ConstantPositionMobilityModel");
  // mobility.Install (c);

  //----------------------------------------------------------------------------------------------------------------------
  //--Setup physical layout
  //----------------------------------------------------------------------------------------------------------------------
  NS_LOG_INFO ("Installing static mobility; distance " << distance << " .");
  MobilityHelper mobility;
  Ptr<ListPositionAllocator> positionAlloc =
    CreateObject<ListPositionAllocator>();
  positionAlloc->Add (Vector (0.0, 0.0, 0.0));
  positionAlloc->Add (Vector (0.0, distance, 0.0));
  mobility.SetPositionAllocator (positionAlloc);
  mobility.Install (c.Get(0));


  mobility.SetMobilityModel ("ns3::RandomWalk2dMobilityModel",
                               "Bounds", RectangleValue (Rectangle (-500, 500, -500, 500)));
   mobility.Install (c.Get(1));
  //----------------------------------------------------------------------------------------------------------------------
  //--Setup physical layout
  //----------------------------------------------------------------------------------------------------------------------
  OlsrHelper olsr;

  Ipv4StaticRoutingHelper staticRouting;

  Ipv4ListRoutingHelper list;
  list.Add (staticRouting, 0);
  list.Add (olsr, 10);

  InternetStackHelper internet;
  internet.SetRoutingHelper (list);
  internet.Install (c);
  //AodvHelper aodv;
  //Ipv4StaticRoutingHelper staticRouting;

  //Ipv4ListRoutingHelper list;
  //list.Add (staticRouting, 0);
  //list.Add (aodv, 100);

  //InternetStackHelper internet;
  //internet.SetRoutingHelper (aodv);
  //internet.Install (c);

  Ipv4AddressHelper ipv4;
  NS_LOG_FUNCTION ("Assign IP Addresses.");
  ipv4.SetBase ("10.1.1.0", "255.255.255.0");
  Ipv4InterfaceContainer i = ipv4.Assign (devices);

  
  TypeId tid = TypeId::LookupByName ("ns3::UdpSocketFactory");
  Ptr<Socket> recvSink = Socket::CreateSocket (c.Get (0), tid);
  InetSocketAddress local = InetSocketAddress (Ipv4Address::GetAny (), 80);
  recvSink->Bind (local);
  recvSink->SetRecvCallback (MakeCallback (&ReceivePacket));

  Ptr<Socket> source = Socket::CreateSocket (c.Get (1), tid);
  //InetSocketAddress remote = InetSocketAddress (Ipv4Address ("255.255.255.255"), 80);
  InetSocketAddress remote = InetSocketAddress (i.GetAddress(0), 80);
  source->SetAllowBroadcast (true);
  source->Connect (remote);
  

  // uint16_t port = 4000;
  // UdpServerHelper server (port);
  // ApplicationContainer apps = server.Install(c.Get(1));
  // apps.Start(Seconds (0.0));
  // apps.Stop(Seconds (10.0));

  // uint32_t MaxPacketSize = 1024;
  // Time interPacketInterval = Seconds(0.005);
  // UdpClientHelper client (Address(i.GetAddress(1)), port);
  // client.SetAttribute("Interval", TimeValue(interPacketInterval));
  // client.SetAttribute("PacketSize", UintegerValue(MaxPacketSize));
  // apps = client.Install(c.Get(0));
  // apps.Start(Seconds (1.0));
  // apps.Stop(Seconds (2.0));

  // Tracing
  wifiPhy.EnablePcap ("wifi-simple-adhoc", devices);
  AnimationInterface anim ("phil.xml");
  // Output what we are doing
  NS_LOG_UNCOND ("Testing " << numPackets  << " packets sent with receiver rss " << rss );

   Simulator::ScheduleWithContext (source->GetNode ()->GetId (),
                                   Seconds (1.0), &GenerateTraffic,
                                   source, packetSize, numPackets, interPacketInterval);

     Config::Connect ("/NodeList/*/$ns3::MobilityModel/CourseChange",
                    MakeCallback (&CourseChange));


  FlowMonitorHelper flowmon;
  Ptr<FlowMonitor> monitor = flowmon.InstallAll ();

  Ipv4GlobalRoutingHelper::PopulateRoutingTables ();

  Simulator::Stop (Seconds (30));

  Simulator::Run ();

  monitor->CheckForLostPackets ();
  Ptr<Ipv4FlowClassifier> classifier = DynamicCast<Ipv4FlowClassifier> (flowmon.GetClassifier ());
  std::map<FlowId, FlowMonitor::FlowStats> stats = monitor->GetFlowStats ();
  for (std::map<FlowId, FlowMonitor::FlowStats>::const_iterator iter = stats.begin (); iter != stats.end (); ++iter)
    {
    Ipv4FlowClassifier::FiveTuple t = classifier->FindFlow (iter->first);
        NS_LOG_UNCOND("-----------------------------------------------------------------------------------------");
        NS_LOG_UNCOND("Flow ID: " << iter->first << " Src Addr " << t.sourceAddress << " Dst Addr " << t.destinationAddress);
        NS_LOG_UNCOND("Tx Packets = " << iter->second.txPackets);
        NS_LOG_UNCOND("Rx Packets = " << iter->second.rxPackets);
        NS_LOG_UNCOND("Rx Bytes = " << iter->second.rxBytes);
        NS_LOG_UNCOND("Time lastreceive = " << iter->second.timeLastRxPacket.GetSeconds());
        NS_LOG_UNCOND("Time firstreceive = " << iter->second.timeFirstTxPacket.GetSeconds());
        NS_LOG_UNCOND("Throughput: " << iter->second.rxBytes * 8.0 / (iter->second.timeLastRxPacket.GetSeconds()-iter->second.timeFirstTxPacket.GetSeconds()) / 1024  << " Kbps");
        NS_LOG_UNCOND("Throughput: " << iter->second.rxBytes * 8.0 / (iter->second.timeLastRxPacket.GetSeconds()-iter->second.timeFirstTxPacket.GetSeconds()) / (1024*1024) << " Mbps");
        NS_LOG_UNCOND("Packet Loss Ratio: " << (iter->second.txPackets - iter->second.rxPackets)*100/(double)iter->second.txPackets << " %");
        NS_LOG_UNCOND("Packet Dropped:: " << iter->second.lostPackets);
        NS_LOG_UNCOND("mean Delay:: " << iter->second.delaySum.GetSeconds()*1000/iter->second.rxPackets << " ms");
        NS_LOG_UNCOND("mean Jitter:: " << iter->second.jitterSum.GetSeconds()*1000/(iter->second.rxPackets - 1) << " ms");
        NS_LOG_UNCOND("-----------------------------------------------------------------------------------------");
    }
  //GetThroughput(monitor, &flowmon);

  Simulator::Destroy ();
  return 0;
}

