#include <iostream>
#include <fstream>
#include <string>
#include <cassert>
 
#include "ns3/core-module.h"
#include "ns3/network-module.h"
#include "ns3/csma-module.h"
#include "ns3/internet-module.h"
#include "ns3/applications-module.h"
#include "ns3/openflow-module.h"
#include "ns3/log.h"
#include "ns3/bridge-helper.h"
#include "ns3/netanim-module.h" 

#include "ns3/bridge-net-device.h"

#include "ns3/ipv4-global-routing-helper.h"
#include "ns3/mobility-module.h"

using namespace ns3;
using namespace std; 

NS_LOG_COMPONENT_DEFINE("OpenFlowCsmaSwitch");
 
bool verbose = false;
bool use_drop = false;

int m = 2;
int n = 2;
int p = 2;

ns3::Time timeout = ns3::Seconds (0);
 
bool
SetVerbose (std::string value)
{
	  verbose = true;
	    return true;
}
 
bool
SetDrop (std::string value)
{
	  use_drop = true;
	    return true;
}

bool SetM(std::string value)
{
  int m = atof (value.c_str ());
  std::cout << m << ": " << m << '\n';
  return true;
}

bool SetN(std::string value)
{
  int n = atof (value.c_str ());
  std::cout << n << ": " << n << '\n';
  return true;
}

bool SetP(std::string value)
{
  int p = atof (value.c_str ());
  std::cout << p << ": " << p << '\n';
  return true;
}

bool
SetTimeout (std::string value)
{
	  try {
		        timeout = ns3::Seconds (atof (value.c_str ()));
				      return true;
					      }
	    catch (...) { return false; }
		  return false;
}
 
int
main(int argc,char *argv[])
{
	Config::SetDefault ("ns3::Ipv4GlobalRouting::RespondToInterfaceEvents", BooleanValue (true));
	CommandLine cmd;
 
	cmd.AddValue ("v", "Verbose (turns on logging).", MakeCallback (&SetVerbose));
	cmd.AddValue ("verbose", "Verbose (turns on logging).", MakeCallback (&SetVerbose));
	cmd.AddValue ("d", "Use Drop Controller (Learning if not specified).", MakeCallback (&SetDrop));
	cmd.AddValue ("drop", "Use Drop Controller (Learning if not specified).", MakeCallback (&SetDrop));
	cmd.AddValue ("t", "Learning Controller Timeout (has no effect if drop controller is specified).", MakeCallback ( &SetTimeout));
	cmd.AddValue ("timeout", "Learning Controller Timeout (has no effect if drop controller is specified).", MakeCallback ( &SetTimeout));

        cmd.AddValue ("m", "client(number).", MakeCallback (&SetM));
        cmd.AddValue ("n", "multi thread(number).", MakeCallback (&SetN));
        cmd.AddValue ("p", "server(number).", MakeCallback (&SetP));
	cmd.Parse (argc, argv);
 
	if (verbose)
	{
		LogComponentEnable ("OpenFlowCsmaSwitch", LOG_LEVEL_INFO);
		LogComponentEnable ("OpenFlowInterface", LOG_LEVEL_INFO);
		LogComponentEnab0le ("OpenFlowSwitchNetDevice", LOG_LEVEL_INFO);
		LogComponentEnable ("UdpEchoClientApplication", LOG_LEVEL_INFO);
		LogComponentEnable ("UdpEchoServerApplication", LOG_LEVEL_INFO);
	}
 
	NS_LOG_INFO ("Create nodes");
	
	NodeContainer terminals;
	terminals.Create(m+1+n);
 
	NodeContainer ofSwitch;
	ofSwitch.Create(p);
 
	NS_LOG_INFO ("Create Topology");
	CsmaHelper csma;
	csma.SetChannelAttribute("DataRate",DataRateValue(5000000));
	csma.SetChannelAttribute("Delay",TimeValue(MilliSeconds(2)));
 
	NetDeviceContainer terminalDevices;
	NetDeviceContainer switchDevice1,switchDevice2;
     

	NetDeviceContainer link;

	//Connect terminal1 to client
	for (int i = 0;i < m;i ++)
	{
		link = csma.Install(NodeContainer(terminals.Get(i),ofSwitch.Get(0)));
		terminalDevices.Add(link.Get(0));
		switchDevice1.Add(link.Get(1));
	}

	//Connect ofSwitch1 to ofSwitch2
	link = csma.Install(NodeContainer(ofSwitch.Get(0),terminals.Get(2)));
	switchDevice1.Add(link.Get(0));
	terminalDevices.Add(link.Get(1));

	link = csma.Install(NodeContainer(terminals.Get(2),ofSwitch.Get(1)));
	//terminalDevices.Add(link.Get(0));
	switchDevice2.Add(link.Get(1));
 
	//Connect terminal server
	for (int i = m+1;i < m+n+1;i ++)
	{
		link = csma.Install(NodeContainer(terminals.Get(i),ofSwitch.Get(1)));
		terminalDevices.Add(link.Get(0));
		switchDevice2.Add(link.Get(1));
	}
 
	//Create the switch netdevice,which will do the packet switching
	Ptr<Node> OFNode1 = ofSwitch.Get(0);
	Ptr<Node> OFNode2 = ofSwitch.Get(1);
	//Ptr<Node> OFNode3 = ofSwitch.Get(2);
 
	OpenFlowSwitchHelper ofswHelper;
	if (use_drop)
	{
		Ptr<ns3::ofi::DropController> controller1 = CreateObject<ns3::ofi::DropController> ();
		ofswHelper.Install (OFNode1, switchDevice1, controller1);
		Ptr<ns3::ofi::DropController> controller2 = CreateObject<ns3::ofi::DropController> ();
		ofswHelper.Install (OFNode2, switchDevice2, controller2);
		//Ptr<ns3::ofi::DropController> controller3 = CreateObject<ns3::ofi::DropController> ();
		//ofswHelper.Install (OFNode3, switchDevice3, controller3);
	}
	else
	{
		Ptr<ns3::ofi::LearningController> controller1 = CreateObject<ns3::ofi::LearningController> ();
		if (!timeout.IsZero ()) controller1->SetAttribute ("ExpirationTime", TimeValue (timeout));
		ofswHelper.Install (OFNode1, switchDevice1, controller1);
		Ptr<ns3::ofi::LearningController> controller2 = CreateObject<ns3::ofi::LearningController> ();
		if (!timeout.IsZero ()) controller2->SetAttribute ("ExpirationTime", TimeValue (timeout));
		ofswHelper.Install (OFNode2, switchDevice2, controller2);
		//Ptr<ns3::ofi::LearningController> controller3 = CreateObject<ns3::ofi::LearningController> ();
		//if (!timeout.IsZero ()) controller3->SetAttribute ("ExpirationTime", TimeValue (timeout));
		//ofswHelper.Install (OFNode3, switchDevice3, controller3);
	}

	Ipv4GlobalRoutingHelper globalRouting;
	Ipv4StaticRoutingHelper staticRouting;

	Ipv4ListRoutingHelper listRouting;
	listRouting.Add (globalRouting, 20);
	listRouting.Add (staticRouting, 10);

	//Add internet stacks to the terminals
	RipHelper rip;
	//listRouting.Add(rip,30);

	InternetStackHelper internet;
	//internet.SetRoutingHelper (rip);
	internet.SetRoutingHelper(listRouting);
	internet.Install (terminals.Get(2));

	//listRouting.Add (staticRouting, 40);
	for(int i=0;i<m;i++){
		internet.Install (terminals.Get(i));
	}

	for(int i=m+1;i<m+1+n;i++){
		internet.Install (terminals.Get(i));
	}
 
	//internet.Install (terminals);
	//internet.Install (ofSwitch);

	//Add IP addresses
	NS_LOG_INFO ("Assign IP Addresses.");
	Ipv4AddressHelper ipv4;
	ipv4.SetBase ("10.1.1.0", "255.255.255.0");
	Ipv4InterfaceContainer interface = ipv4.Assign(terminalDevices);
	//ipv4.Assign (terminalDevices);
	
	//Ipv4GlobalRoutingHelper::PopulateRoutingTables ();
	
 
	// Create an OnOff application to send UDP datagrams from n0 to n1.
	NS_LOG_INFO ("Create Applications.");
	uint16_t port = 9;   // Discard port (RFC 863)

	OnOffHelper onoff ("ns3::UdpSocketFactory",Address (InetSocketAddress (Ipv4Address ("10.1.1.4"), port)));
	onoff.SetConstantRate (DataRate ("500kb/s"));

	ApplicationContainer app = onoff.Install (terminals.Get (1));
	// Start the application
	app.Start (Seconds (2.0));
	app.Stop (Seconds (20.0));
	// Create an optional packet sink to receive these packets
	PacketSinkHelper sink ("ns3::UdpSocketFactory",Address (InetSocketAddress (Ipv4Address::GetAny (), port)));
	app = sink.Install (terminals.Get (4));
	//app.Start (Seconds (0.0));

	//
	// Create a similar flow from n3 to n0, starting at time 1.1 seconds
	//
	onoff.SetAttribute ("Remote",AddressValue (InetSocketAddress (Ipv4Address ("10.1.1.3"), port)));
	app = onoff.Install (terminals.Get (0));
	app.Start (Seconds (2.1));
	app.Stop (Seconds (20.0));
	app = sink.Install (terminals.Get (2));
	//app.Start (Seconds (0.0));

	NS_LOG_INFO ("Configure Tracing.");
	//
	// Configure tracing of all enqueue, dequeue, and NetDevice receive events.
	// Trace output will be sent to the file "openflow-switch.tr"
	//
	AsciiTraceHelper ascii;
	csma.EnableAsciiAll (ascii.CreateFileStream ("test.tr"));
  
	csma.EnablePcapAll ("test", true);
	AnimationInterface anim ("test.xml");
	NS_LOG_INFO ("Run simulation");
	Simulator::Run();
	Simulator::Destroy();
	NS_LOG_INFO ("Done.");
	//#else
	//NS_LOG_INFO ("NS-3 OpenFlow is not enabled. Cannot run simulation.");
	//#endif // NS3_OPENFLOW
 
}
