#include "ns3/network-module.h"
#include "ns3/core-module.h"
#include "ns3/internet-module.h"
#include "ns3/dce-module.h"
#include "ns3/applications-module.h"
#include "ns3/csma-module.h"
#include <fstream>

//Intended Topology:
//Extendible.
//CSMA nodes (Ethernet-like network but without Collision Detection)
//   DataRate = 10 Mpbs
//   Delay = 0.2-0.3 ms
//
//  192.168.2.0 n1                   n4  192.168.3.0
//              |      192.168.1.0   | 
//variable      |--- n0 ------ n3 ---|   variable
//              |                    |
//             n2                    n5

#define DEBUG 1

using namespace ns3;
using namespace std;

NS_LOG_COMPONENT_DEFINE ("pfctest3");


/*
static std::vector<std::string> NodeIp(Ptr<Node> node)
{
	std::vector<std::string> addresses;
	int nDevices = node->GetNDevices();
  Ptr<Ipv4> ipv4 = node->GetObject<Ipv4> (); // Get Ipv4 instance of the node
	for (int i=0; i<nDevices; i++){
  	Ipv4Address addr = ipv4->GetAddress (i, 0).GetLocal (); // Get Ipv4InterfaceAddress of xth interface.
		addresses.push_back(addr);
	}
}
*/
std::string Ipv4AddressToString (Ipv4Address ad)
{
  std::ostringstream oss;
  ad.Print (oss);
  return oss.str ();
}


int main (int argc, char *argv[])
{
  CommandLine cmd;
  cmd.Parse (argc, argv);

  int totalNodes = 6; //left+right+central

  if (totalNodes > 30){
  	NS_LOG_ERROR ("ERROR: In this topology there can't be more than 30 nodes");
  	exit(-1);
  }

  //Creates all the nodes together.
  NodeContainer nodes;
  nodes.Create (totalNodes);
 
  NodeContainer leftNodes;
	leftNodes.Add(nodes.Get(0)); 
	leftNodes.Add(nodes.Get(1)); 	
	leftNodes.Add(nodes.Get(2)); //So it belongs to both .1 and .2 networks.
	
  NodeContainer centralNodes = NodeContainer (nodes.Get(2), nodes.Get(3));

  NodeContainer rightNodes;
	rightNodes.Add(nodes.Get(3)); //So it belongs to both .1 and .3 networks.
	rightNodes.Add(nodes.Get(4)); //So it belongs to both .1 and .3 networks.
	rightNodes.Add(nodes.Get(5)); //So it belongs to both .1 and .3 networks.

  
  //Installs the linux kernel in all nodes.
  DceManagerHelper dceManager;
  dceManager.SetNetworkStack ("ns3::LinuxSocketFdFactory", "Library", StringValue ("liblinux.so"));
  dceManager.Install (nodes);
	
  LinuxStackHelper stack;
  stack.Install (nodes);
  
  //Create the fast CSMA (100Mbit/s channel) and assign the nodes to it.
  CsmaHelper csmaFast;
  csmaFast.SetChannelAttribute ("DataRate", StringValue("100Mbps"));
  csmaFast.SetChannelAttribute ("Delay", StringValue("2ms"));

  NetDeviceContainer leftDevices = csmaFast.Install(leftNodes);
  NetDeviceContainer rightDevices = csmaFast.Install(rightNodes);

  //Create the slow CSMA (10Mbit/s channel) and assign the nodes.
	CsmaHelper csmaSlow;
  csmaSlow.SetChannelAttribute ("DataRate", StringValue("10Mbps"));
	csmaSlow.SetChannelAttribute ("Delay", StringValue("2ms"));
  //Same Delay value. 

  NetDeviceContainer centralDevices = csmaSlow.Install(centralNodes);


  //Assigns IP addresses to the three different networks.
  Ipv4AddressHelper address;
  
  address.SetBase("192.168.1.0", "255.255.255.0");
  Ipv4InterfaceContainer centralInterfaces =  address.Assign (centralDevices);
  
  address.SetBase("192.168.2.0", "255.255.255.0");
  Ipv4InterfaceContainer leftInterfaces = address.Assign (leftDevices);

  address.SetBase("192.168.3.0", "255.255.255.0");
  Ipv4InterfaceContainer rightInterfaces = address.Assign (rightDevices);

	//Populate routing tables.
  Ipv4GlobalRoutingHelper::PopulateRoutingTables ();
  LinuxStackHelper::PopulateRoutingTables ();


#if DEBUG
	DceApplicationHelper dce;
  ApplicationContainer apps;

  dce.SetStackSize (1 << 20);

	Ptr<Node> node = nodes.Get (5); // Get pointer to the node in container
  Ptr<Ipv4> ipv4 = node->GetObject<Ipv4> (); // Get Ipv4 instance of the node
  Ipv4Address addr = ipv4->GetAddress (0, 0).GetLocal (); // Get Ipv4InterfaceAddress of xth interface.
	string str = Ipv4AddressToString (addr);


  // Launch iperf client on node 0
  dce.SetBinary ("iperf");
  dce.ResetArguments ();
  dce.ResetEnvironment ();
  dce.AddArgument ("-c");
  dce.AddArgument (str);
  dce.AddArgument ("-i");
  dce.AddArgument ("1");
  dce.AddArgument ("--time");
  dce.AddArgument ("10");

  apps = dce.Install (nodes.Get (0));
  apps.Start (Seconds (0.7));
  apps.Stop (Seconds (20));

  // Launch iperf server on node 1
  dce.SetBinary ("iperf");
  dce.ResetArguments ();
  dce.ResetEnvironment ();
  dce.AddArgument ("-s");
  dce.AddArgument ("-P");
  dce.AddArgument ("1");

  apps = dce.Install (nodes.Get (5));

  apps.Start (Seconds (0.6));
#else
	std::cout << "TODO THIS STUFFZ" << "\n";
#endif

	csmaFast.EnablePcapAll ("csmaFast-iperf");
	csmaSlow.EnablePcapAll ("csmaSlow-iperf");

  Simulator::Stop (Seconds (40.0));
  Simulator::Run ();
  Simulator::Destroy ();

  return 0;
}
