#include "ns3/olsr-module.h"
#include "ns3/core-module.h"
#include "ns3/network-module.h"
#include "ns3/internet-module.h"
#include "ns3/mobility-module.h"
#include "ns3/point-to-point-module.h"
#include "ns3/wifi-module.h"
#include "ns3/atn-helper.h"
#include "ns3/v4ping-helper.h"
#include <iostream>
#include <cmath>
#include "ns3/on-off-helper.h"
#include "ns3/packet-sink-helper.h"

using namespace ns3;

class AtnSimulate
{
public:
  AtnSimulate ();
  /// Configure script parameters, \return true on successful configuration
  bool Configure (int argc, char **argv);
  /// Run simulation
  void Run ();
  /// Report results
  void Report (std::ostream & os);

private:
  ///\name parameters
  //\{
  /// Number of nodes
  uint32_t size;
  /// Distance between nodes, meters
  double step;
  /// Simulation time, seconds
  double totalTime;
  /// Write per-device PCAP traces if true
  bool pcap;
  /// Print routes if true
  bool printRoutes;
  //\}

  ///\name network
  //\{
  NodeContainer nodes;
  NetDeviceContainer devices;
  Ipv4InterfaceContainer interfaces;
  //\}

private:
  void CreateNodes ();
  void CreateDevices ();
  void InstallInternetStack ();
  void InstallApplications ();
};

int main (int argc, char **argv)
{
  AtnSimulate test;
  if (!test.Configure (argc, argv))
    NS_FATAL_ERROR ("Configuration failed. Aborted.");

  test.Run ();
  test.Report (std::cout);
  return 0;
}

//-----------------------------------------------------------------------------
AtnSimulate::AtnSimulate () :
  size (3),
  step (100),
  totalTime (12),
  pcap (true),
  printRoutes (true)
{
}

bool
AtnSimulate::Configure (int argc, char **argv)
{
  CommandLine cmd;

  cmd.AddValue ("pcap", "Write PCAP traces.", pcap);
  cmd.AddValue ("printRoutes", "Print routing table dumps.", printRoutes);
  cmd.AddValue ("size", "Number of nodes.", size);
  cmd.AddValue ("time", "Simulation time, s.", totalTime);
  cmd.AddValue ("step", "Grid step, m", step);

  cmd.Parse (argc, argv);
  return true;
}

void
AtnSimulate::Run ()
{
//  Config::SetDefault ("ns3::WifiRemoteStationManager::RtsCtsThreshold", UintegerValue (1)); // enable rts cts all the time.
  CreateNodes ();
  CreateDevices ();
  InstallInternetStack ();
  InstallApplications ();

  std::cout << "Starting simulation for " << totalTime << " s ...\n";

  Simulator::Stop (Seconds (totalTime));
  Simulator::Run ();
  Simulator::Destroy ();
}

void
AtnSimulate::CreateNodes ()
{
  std::cout << "Creating " << (unsigned)size << " nodes " << step << " m apart.\n";
  nodes.Create (size);
  // Name nodes
  for (uint32_t i = 0; i < size; ++i)
    {
      std::ostringstream os;
      os << "node-" << i;
      Names::Add (os.str (), nodes.Get (i));
    }

  // Model of test. One node range out from neighbour
  Ptr<MobilityModel> constPos = CreateObject<ConstantPositionMobilityModel> ();
  nodes.Get (0)->AggregateObject (constPos);

  Ptr<MobilityModel> transitionPos = CreateObject<ConstantPositionMobilityModel> ();
  transitionPos->SetPosition(Vector(300, 50, 0));
  nodes.Get (2)->AggregateObject (transitionPos);

  MobilityHelper mobility;
  mobility.SetMobilityModel("ns3::ConstantVelocityMobilityModel");
  mobility.Install(nodes.Get (1));
  nodes.Get (1)->GetObject<ConstantVelocityMobilityModel> ()->SetVelocity (Vector(80, 0, 0));
  mobility.SetPositionAllocator("ns3::RandomBoxPositionAllocator",
                                "X", StringValue ("ns3::UniformRandomVariable[Min=50|Max=50]"),
                                "Y", StringValue ("ns3::UniformRandomVariable[Min=0|Max=0]"),
                                "Z", StringValue ("ns3::UniformRandomVariable[Min=0|Max=0]"));
}

void
AtnSimulate::CreateDevices ()
{
  NqosWifiMacHelper wifiMac = NqosWifiMacHelper::Default ();
  wifiMac.SetType ("ns3::AdhocWifiMac");
  YansWifiPhyHelper wifiPhy = YansWifiPhyHelper::Default ();
  YansWifiChannelHelper wifiChannel;//= YansWifiChannelHelper::Default ();
  wifiChannel.SetPropagationDelay ("ns3::ConstantSpeedPropagationDelayModel");
  wifiChannel.AddPropagationLoss ("ns3::LogDistancePropagationLossModel", "ReferenceDistance", DoubleValue(30.0), "Exponent", DoubleValue(5.0));
  wifiPhy.SetChannel (wifiChannel.Create ());
  wifiPhy.EnablePcapAll ("pcap");
  WifiHelper wifi = WifiHelper();

  std::string phyMode ("DsssRate1Mbps");
  wifi.SetRemoteStationManager ("ns3::ConstantRateWifiManager",
                                "DataMode",StringValue (phyMode),
                                "ControlMode",StringValue (phyMode));

  devices = wifi.Install (wifiPhy, wifiMac, nodes);

  if (pcap)
    {
      wifiPhy.EnablePcapAll (std::string ("atn-simulator"));
    }
}

void
AtnSimulate::InstallInternetStack ()
{
  OlsrHelper olsr;
  InternetStackHelper stack;
  stack.SetRoutingHelper (olsr); // has effect on the next Install ()
  stack.Install (nodes);

  Ipv4AddressHelper address;
  address.SetBase ("10.0.0.0", "255.0.0.0");
  interfaces = address.Assign (devices);
}

void
AtnSimulate::InstallApplications ()
{
  Ptr<Node> appSource = NodeList::GetNode (0);
  Ptr<Node> appSink = NodeList::GetNode (1);
  // Let's fetch the IP address of the last node, which is on Ipv4Interface 1
  Ipv4Address remoteAddr = appSink->GetObject<Ipv4> ()->GetAddress (1, 0).GetLocal ();

  OnOffHelper onoff ("ns3::UdpSocketFactory",
                    Address (InetSocketAddress (remoteAddr, 4567)));
  onoff.SetAttribute ("OnTime", StringValue ("ns3::ConstantRandomVariable[Constant=1.0]"));
  onoff.SetAttribute ("OffTime", StringValue ("ns3::ConstantRandomVariable[Constant=0.0]"));
  ApplicationContainer app2 = onoff.Install (appSource);
  app2.Start (Seconds (1));
  app2.Stop (Seconds (15));

  // Create a packet sink to receive these packets
  PacketSinkHelper sink ("ns3::UdpSocketFactory",
                        InetSocketAddress (Ipv4Address::GetAny (), 4567));
  ApplicationContainer app3 = sink.Install (appSink);
  app3.Start (Seconds (3));
  app3.Stop (Seconds (15));
}


