/*! \file simulacion2.cc
  \brief Second Simulation. We got a hybrid LAN/P2P link topology, trying to configure a MiTM in hybrid nodes and solve the congestion generated in the First Simulation. NS3 libraries are needed to simulate the topology, including wifi-module, the 
  NS3 GUI Netanim and the Flow Monitor. We need to represent a Multicast frame topology and calculating congestion using report files generated.
*/

#include <iostream>
#include <unistd.h>
#include <fstream>
#include "ns3/core-module.h"
#include "ns3/network-module.h"
#include "ns3/applications-module.h"
#include "ns3/wifi-module.h"
#include "ns3/mobility-module.h"
#include "ns3/csma-module.h"
#include "ns3/internet-module.h"
#include "ns3/point-to-point-module.h"
#include "ns3/netanim-module.h"
#include "ns3/flow-monitor-module.h"
#include "ns3/node-container.h"
#include "ns3/node.h"
#include "ns3/inet-socket-address.h"

#include <fstream>


/// Default Network Topology
//
///  * -> WiFi access
///                                          AP
///  *         *        *                    * 
///  |    |    |         |    
/// n0----n5   n1----n6   n2----n7  .....   n4
///   p2p       p2p          p2p
//                               



using namespace ns3;
using namespace std;


NS_LOG_COMPONENT_DEFINE ("Simulacion2");

void ReceivePacket (Ptr<Socket> socket)
{
    ofstream myfile ("datos.txt");
    Ptr<Packet> packet = socket->Recv();
    packet->EnablePrinting();
  if (myfile.is_open())
  {
    myfile << "Recibido: ";
    myfile << packet->ToString()<< "\n";
  
    myfile.close();
  }
  else cout << "Unable to open file";
  

  NS_LOG_UNCOND ("Received one packet!");

 myfile.close();
}




static void GenerateTraffic (Ptr<Socket> socket, uint32_t pktSize, 
                             uint32_t pktCount, Time pktInterval )
{
  if (pktCount > 0)
    {
      socket->Send (Create<Packet> (pktSize));
      Simulator::Schedule (pktInterval, &GenerateTraffic, 
                           socket, pktSize,pktCount-1, pktInterval);
    }
  else
    {
      socket->Close ();
    }
}

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



  bool verbose = true;/// Program log show in the output 
  int nWifi = 4; /// Number of STA WiFi nodes in our topology. Total number of nodes is (nWifi x 2)+ node AP.
  bool tracing = true; /// For Wireshark representation
  double rss=-30; /// Received signal in dB from AP

  uint32_t packetSize = 1024;
  uint32_t packetCount = 10000;
  double packetInterval = 0.1;


  CommandLine cmd;
  cmd.AddValue ("nWifi", "Number of wifi STA devices", nWifi);
  cmd.AddValue ("verbose", "Tell echo applications to log if true", verbose);
  cmd.AddValue ("tracing", "Enable pcap tracing", tracing);

  cmd.Parse (argc,argv);


  if (verbose)
    {
      LogComponentEnable ("UdpEchoClientApplication", LOG_LEVEL_INFO);
      LogComponentEnable ("UdpEchoServerApplication", LOG_LEVEL_INFO);
    }

///Setting Wireless nodes 
  NodeContainer wifiStaNodes; /// Statics 
  wifiStaNodes.Create (nWifi); 
  NodeContainer wifiApNode;
  wifiApNode.Create(1);/// To route all packets, an AP node is generated  

 

/// Medium is configured. IEEE 802.11. 
  YansWifiChannelHelper channel = YansWifiChannelHelper::Default (); /// We set the default channel. 
  YansWifiPhyHelper phy = YansWifiPhyHelper::Default ();
  phy.SetChannel (channel.Create ()); /// Channel is created. 

/*! \brief This is one parameter that matters when using FixedRssLossModel
   set it to zero; otherwise, gain will be added */
  phy.Set ("RxGain", DoubleValue (0));

  /// ns-3 supports RadioTap and Prism tracing extensions for 802.11g
  phy.SetPcapDataLinkType (YansWifiPhyHelper::DLT_IEEE802_11_RADIO);

/// ns-3 supports RadioTap and Prism tracing extensions for 802.11g
  channel.SetPropagationDelay ("ns3::ConstantSpeedPropagationDelayModel");

  /*! \brief The below FixedRssLossModel will cause the rss to be fixed regardless
   of the distance between the two stations, and the transmit power*/
  channel.AddPropagationLoss ("ns3::FixedRssLossModel","Rss",
    DoubleValue (rss));
  phy.SetChannel (channel.Create ());


 /// Configuring WiFi
    WifiHelper wifi;

  wifi.SetRemoteStationManager ("ns3::AarfWifiManager");

/// Setting both Level two layer in AP and static nodes
  WifiMacHelper mac;
  Ssid ssid = Ssid ("ns-3-ssid");
  mac.SetType ("ns3::StaWifiMac",
               "Ssid", SsidValue (ssid),
               "ActiveProbing", BooleanValue (false));

/// Installing static devices
  NetDeviceContainer staDevices;
  staDevices = wifi.Install (phy, mac, wifiStaNodes);


 /// AP device 
  mac.SetType ("ns3::ApWifiMac",
               "Ssid", SsidValue (ssid));


/// Installing AP devices
  NetDeviceContainer apDevices;
  apDevices = wifi.Install (phy, mac, wifiApNode);


/// Setting a mobility model in static nodes
  MobilityHelper mobility;

  mobility.SetPositionAllocator ("ns3::GridPositionAllocator",
                                 "MinX", DoubleValue (0.0),
                                 "MinY", DoubleValue (0.0),
                                 "DeltaX", DoubleValue (50.0),
                                 "DeltaY", DoubleValue (10.0),
                                 "GridWidth", UintegerValue (3),
                                 "LayoutType", StringValue ("RowFirst"));
/// Setting a mobility model in static nodes
  mobility.SetMobilityModel ("ns3::RandomWalk2dMobilityModel",
                             "Bounds", RectangleValue (Rectangle (-1000, 1000, -1000, 1000)));
  mobility.Install (wifiStaNodes);

/// AP is set as immovable
  mobility.SetMobilityModel ("ns3::ConstantPositionMobilityModel");
  mobility.Install (wifiApNode);


//To configure hybrid LAN/P2P nodes
   NodeContainer *lan[nWifi];
   NodeContainer newLanNodes[nWifi];
   Ipv4InterfaceContainer p2p_address;
           



/// Internet stack is needed to be installed in both kind of nodes
    InternetStackHelper internet_olsr;
    internet_olsr.Install (wifiStaNodes);
    internet_olsr.Install (wifiApNode);
    InternetStackHelper internet_p2p;



    Ipv4AddressHelper address;
/// Setting Adressing on Wifi nodes
  address.SetBase ("192.168.1.0", "255.255.255.0");
 Ipv4InterfaceContainer sta_address = address.Assign (staDevices);
 Ipv4InterfaceContainer ap_address =  address.Assign (apDevices);

   



  
//Configuring hybrid nodes and assignning IPs
for(int i=0; i<nWifi; i++)
{ 

NetDeviceContainer p2pDevices;
  char buffer[50];
  sprintf(buffer,"192.168.%d.0", i+2);
  address.SetBase (buffer, "255.255.255.0");
   
  newLanNodes[i].Create (1);
  internet_p2p.Install (newLanNodes[i]);

   
  lan[i]= new NodeContainer(wifiStaNodes.Get(i), newLanNodes[i]);
   
/*  If you want to enable CSMA bus, (not recommended)
  CsmaHelper csma;
  csma.SetChannelAttribute ("DataRate", StringValue ("100Mbps"));
  csma.SetChannelAttribute ("Delay", TimeValue (NanoSeconds (6560)));*/
    PointToPointHelper p2p;
    p2p.SetDeviceAttribute ("DataRate", StringValue ("5Mbps"));
    p2p.SetChannelAttribute ("Delay", StringValue ("13us"));

  p2pDevices = p2p.Install(*lan[i]);

    
  
  mobility.SetMobilityModel ("ns3::ConstantPositionMobilityModel");
  mobility.Install (*lan[i]);
   
 
  Ipv4InterfaceContainer singleP2P_address = address.Assign (p2pDevices);


  p2p_address.Add(singleP2P_address.Get(1));
  
}

/*
 // Create the OnOff application to send UDP datagrams of size
  // 512 bytes (default) at a rate of 500 Kb/s (default) from n0
  NS_LOG_INFO ("Create Applications.");
  OnOffHelper onoff ("ns3::UdpSocketFactory", 
                     Address (InetSocketAddress (Ipv4Address ("255.255.255.255"), 9)));
  onoff.SetConstantRate (DataRate ("500kb/s"));

  ApplicationContainer app = onoff.Install (newLanNodes[0].Get (0));
  // Start the application
  app.Start (Seconds (1.0));
  app.Stop (Seconds (10.0));

  // Create an optional packet sink to receive these packets
  PacketSinkHelper sink ("ns3::UdpSocketFactory",
                         Address (InetSocketAddress (Ipv4Address::GetAny (), 9)));
  app = sink.Install (newLanNodes[3].Get (0));
  app.Start (Seconds (1.0));
  app.Stop (Seconds (10.0));

  // Configure ascii tracing of all enqueue, dequeue, and NetDevice receive 
  // events on all devices.  Trace output will be sent to the file 
  // "csma-one-subnet.tr"
  AsciiTraceHelper ascii;
  phy.EnableAsciiAll (ascii.CreateFileStream ("wifi-broadcast.tr"));
*/


  NS_LOG_INFO ("Create sockets.");
  //Receiver socket on n1
  TypeId tid = TypeId::LookupByName ("ns3::UdpSocketFactory");

  //Sender socket on n0
  Ptr<Socket> source = Socket::CreateSocket (newLanNodes[0].Get (0), tid);

//  Receiver sockets
   Ptr<Socket> recvSink[nWifi];
InetSocketAddress local = InetSocketAddress (Ipv4Address::GetAny (), 9);
  for(int i=0; i<nWifi; i++)
  {
    recvSink[i] = Socket::CreateSocket (newLanNodes[i].Get (0), tid);
    recvSink[i]->Bind (local);
    recvSink[i]->SetRecvCallback (MakeCallback (&ReceivePacket));
  }
  

// Transmitter socket connections. Set transmitter for broadcasting
  InetSocketAddress remote = InetSocketAddress("192.168.255.255", 9);
  source->SetAllowBroadcast (true);
  source->Connect (remote);


/*   
//Client-Server Application

   ApplicationContainer ServerApps[nWifi];
  UdpEchoServerHelper * server[nWifi];

/// Server is waiting UDP packets from Client in node 1 to nWifi. Port 9 is assigned. Servers start at 1s and stop at 10s.
  for (int i=1; i<nWifi; i++)
 {

    server[i] = new UdpEchoServerHelper(9);
    ServerApps[i]=server[i]->Install(newLanNodes[i].Get(0));
    ServerApps[i].Start (Seconds (1.0));
    ServerApps[i].Stop (Seconds (10.0));
  }

/// A client is installed at node 0 and sending request to servers. Client start at 2s and stops at 10
  ApplicationContainer ClientApps[nWifi];
  UdpEchoClientHelper * client[nWifi];

  for (int i=1 ; i<nWifi; i++)
  {

    client[i]= new UdpEchoClientHelper(p2p_address.GetAddress(i), 9);
    client[i]-> SetAttribute ("MaxPackets", UintegerValue (10000));
    client[i]->SetAttribute ("Interval", TimeValue (Seconds (0.1)));
    client[i]->SetAttribute ("PacketSize", UintegerValue (1024));

    ClientApps[i]=client[i]->Install(newLanNodes[0]);
    ClientApps[i].Start (Seconds (2.0));
    ClientApps[i].Stop (Seconds (10.0));
    
  }*/

//----------------------------------------------------------------------
/// Generic Routing Tables.

 Ipv4GlobalRoutingHelper::PopulateRoutingTables ();
  

   // Trace routing tables
   Ipv4GlobalRoutingHelper g;
   Ptr<OutputStreamWrapper> routingStream = Create<OutputStreamWrapper>
("RoutingTable", std::ios::out);
   g.PrintRoutingTableAllAt (Seconds (12), routingStream);

/// Flow Monitor processing.
  Ptr<FlowMonitor> flowMonitor;
  FlowMonitorHelper flowHelper;
  flowMonitor = flowHelper.InstallAll();

  
  Time interPacketInterval = Seconds (packetInterval);
  Simulator::ScheduleWithContext (source->GetNode ()->GetId (),
                                  Seconds (2.0), &GenerateTraffic, 
                                  source, packetSize, packetCount, interPacketInterval);
  Simulator::Stop (Seconds (20.0));


/// Wireshark trace file.
  if (tracing == true)
    {
      phy.EnablePcap ("simulacion2_pcap", staDevices);

    }

/// Enable animation with Netanim.
  AnimationInterface anim ("animacion2.xml");


/// Enable metadata, very useful to represent in Netanim.
  anim.EnablePacketMetadata (true);

  Simulator::Run ();
  flowMonitor->SerializeToXmlFile("monitor.flowmonitor2", true, true);
  



  Simulator::Destroy ();
  return 0;
}
