
/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
/*
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License version 2 as
 * published by the Free Software Foundation;
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */

#include "ns3/core-module.h"

using namespace ns3;

//NS_LOG_COMPONENT_DEFINE ("ScratchSimulator");

#include <ios>
#include <iostream>
#include <fstream>
#include <string>
#include <stdlib.h>
#include "ns3/core-module.h"
#include "ns3/network-module.h"
#include "ns3/internet-module.h"
#include "ns3/point-to-point-module.h"
#include "ns3/applications-module.h"
#include "ns3/error-model.h"
#include "ns3/tcp-header.h"
#include "ns3/udp-header.h"
#include "ns3/enum.h"
#include "ns3/event-id.h"
#include "ns3/flow-monitor-helper.h"
#include "ns3/ipv4-global-routing-helper.h"
#include "ns3/traffic-control-module.h"
#include "ns3/flow-monitor-module.h"
#include "ns3/wifi-module.h"
#include "ns3/mobility-module.h"
#include "ns3/olsr-helper.h"
#include "ns3/olsr-routing-protocol.h"
#include "ns3/netanim-module.h"

using namespace std;
using namespace ns3;

NS_LOG_COMPONENT_DEFINE ("TcpVariantsComparison");


std::vector<uint64_t> lastTotalRx;
std::vector<uint64_t> lastTotalRxWiFi;


void
CalculateThroughput (Ptr<PacketSink> sink, Ptr<OutputStreamWrapper> tpStream,uint32_t nodeId)
{

	Time now = Simulator::Now ();                                         /* Return the simulator's virtual time. */
	uint64_t totalRx= sink->GetTotalRx();
	uint64_t lastTotRx= lastTotalRx.at(nodeId);//sink->GetLastTotalRx();
	// double cur = (totalRx - lastTotRx ) * (double) 8 / 1e5;
	double cur = (totalRx - lastTotRx ) * (double) 8 / 1e5;     /* Convert Application RX Packets to MBits. */
	std::cout << now.GetSeconds () << "s: \t" <<"Wired Node "<< cur << " Mbit/s" << std::endl;
	*tpStream->GetStream () << now.GetSeconds () << " " << cur << std::endl;
	//	*tpStreamWired.at(nodeId)->GetStream () << now.GetSeconds () << " " << cur << std::endl;
	lastTotalRx.at(nodeId)=sink->GetTotalRx();
	Simulator::Schedule (MilliSeconds (100), &CalculateThroughput,sink,tpStream, nodeId);
	//lastTotalRx = sink->GetTotalRx ();
	// Simulator::Schedule (MilliSeconds (100), &CalculateThroughput, sink);
}
void
CalculateThroughputWiFiNodes (Ptr<PacketSink> sink, Ptr<OutputStreamWrapper> tpStream,uint32_t nodeId)
{

	Time now = Simulator::Now ();                                         /* Return the simulator's virtual time. */
	uint64_t totalRx= sink->GetTotalRx();
	uint64_t lastTotRx= lastTotalRxWiFi.at(nodeId);//sink->GetLastTotalRx();
	// double cur = (totalRx - lastTotRx ) * (double) 8 / 1e5;
	double cur = (totalRx - lastTotRx ) * (double) 8 / 1e5;     /* Convert Application RX Packets to MBits. */
	std::cout << now.GetSeconds () << "s: \t" << "WiFi Node "<<cur << " Mbit/s" << std::endl;
	*tpStream->GetStream () << now.GetSeconds () << " " << cur << std::endl;
	//	*tpStreamWiFi.at(nodeId)->GetStream () << now.GetSeconds () << " " << cur << std::endl;
	lastTotalRxWiFi.at(nodeId)=sink->GetTotalRx();
	Simulator::Schedule (MilliSeconds (100), &CalculateThroughputWiFiNodes,sink,tpStream, nodeId);
	//lastTotalRx = sink->GetTotalRx ();
	// Simulator::Schedule (MilliSeconds (100), &CalculateThroughput, sink);
}

static void ScheduleThroughputTrace(ApplicationContainer sinkApplications,ApplicationContainer sinkApplicationsWiFi, std::string prefix_file_name, std::string file_dir,NodeContainer sinkNodes) // in Mbps calculated every 2s
{
	//std::string outDir="/home/anagha/ns-allinone-3/ns-3.27/out/";
	//Wired Nodes
	lastTotalRx.resize(sinkApplications.GetN ());
	//	tpStreamWired.resize(sinkApplications.GetN ());
	for (uint16_t i = 0; i < sinkApplications.GetN (); i++)
	{
		lastTotalRx.at(i)=0;
		Ptr<PacketSink> sink = DynamicCast<PacketSink> (sinkApplications.Get (i));
		uint32_t nodeId=sinkNodes.Get(i)->GetId();
		std::ostringstream oss1;
		oss1 <<file_dir<<"/"<<prefix_file_name<<"-"<< nodeId<< "-throughput.txt";
		AsciiTraceHelper ascii;
		Ptr<OutputStreamWrapper> tpStream = ascii.CreateFileStream (oss1.str());
		//		tpStreamWired.at(i)=ascii.CreateFileStream (oss1.str());
		Simulator::Schedule (MilliSeconds (100), &CalculateThroughput,sink, tpStream, i);
	}
}


static void ScheduleWiFiThroughputTrace(ApplicationContainer sinkApplicationsWiFi, std::string prefix_file_name, std::string file_dir)
{
	//WiFi Nodes
	lastTotalRxWiFi.resize(sinkApplicationsWiFi.GetN ());
	//	tpStreamWiFi.resize(sinkApplicationsWiFi.GetN ());
	for (uint16_t i = 0; i < sinkApplicationsWiFi.GetN (); i++)
	{
		lastTotalRxWiFi.at(i)=0;
		Ptr<PacketSink> sink = DynamicCast<PacketSink> (sinkApplicationsWiFi.Get (i));
		//uint32_t nodeId=sinkApplicationsWiFi.Get(i);
		std::ostringstream oss1;
		oss1 <<file_dir<<"/"<<prefix_file_name<<"-"<< i<< "-WiFi-throughput.txt";
		AsciiTraceHelper ascii;
		Ptr<OutputStreamWrapper> tpStream = ascii.CreateFileStream (oss1.str());
		//	tpStreamWiFi.at(i)=ascii.CreateFileStream (oss1.str());
		Simulator::Schedule (MilliSeconds (100), &CalculateThroughputWiFiNodes,sink, tpStream, i);
	}
}
static void
CwndTracer (Ptr<OutputStreamWrapper> stream,uint32_t oldval, uint32_t newval)
{
	*stream->GetStream () << Simulator::Now ().GetSeconds () << " " << newval << std::endl;
}

//-----------------------------------------------------

static void
TraceCwnd (std::string cwnd_tr_file_name,uint32_t nodeId)
{
	AsciiTraceHelper ascii;
	Ptr<OutputStreamWrapper> cWndStream = ascii.CreateFileStream (cwnd_tr_file_name.c_str ());
	std::ostringstream oss;
	oss << "/NodeList/" << nodeId << "/$ns3::TcpL4Protocol/SocketList/0/CongestionWindow";
	//Config::ConnectWithoutContext ("/NodeList/2/$ns3::TcpL4Protocol/SocketList/0/CongestionWindow", MakeCallback (&CwndTracer));
	Config::ConnectWithoutContext (oss.str (), MakeBoundCallback (&CwndTracer,cWndStream));
}

static void ScheduleAllTraces(std::string prefix_file_name,std::string file_dir, NodeContainer srcNodes, NodeContainer sinkNodes) // in Mbps calculated every 2s
{
	//std::string outDir="/home/anagha/ns-allinone-3/ns-3.27/out/";

	for (uint16_t i = 0; i < srcNodes.GetN (); i++)
	{
		uint32_t nodeId=srcNodes.Get(i)->GetId();
		std::ostringstream oss1;
		oss1 <<file_dir<<"/"<<prefix_file_name<<"-"<< nodeId<< "-cwnd.data";
		Simulator::Schedule (Seconds (0.00001), &TraceCwnd, oss1.str(),nodeId);
	}
}


//-------------------------------Main method -----------------------------------------
int main (int argc, char *argv[])
{
	std::cout<<"Starting Simulator";
	std::string transport_prot = "TcpNewReno";
	double error_p = 0.0;
	std::string bandwidth = "10Mbps";
	std::string delay = "0.1ms";
	std::string access_bandwidth = "10Mbps";
	std::string access_delay = "0.1ms";
	bool tracing = true;
	std::string prefix_file_name = "TcpVariantsComparison";
	std::string file_dir = "/home/anagha/ns-allinone-3/ns-3.27";
	double data_mbytes = 0;
	uint32_t mtu_bytes = 400;
	uint16_t num_flows = 3;			//Number of sources and destinations
	uint16_t wifi = 2;
	float duration = 5;
	uint32_t run = 0;
	bool sack = false;
	std::string queue_disc_type = "ns3::PfifoFastQueueDisc";//"ns3::PfifoFastQueueDisc";
	uint16_t mimo=0; //1 for mimo
	uint16_t dist=10;
	std::string appl="BulkSend";
	bool interf = false;


	//WiFi nodes
	//	uint32_t nWifi = 1;

	CommandLine cmd;
	cmd.AddValue ("transport_prot", "Transport protocol to use: TcpNewReno, "
			"TcpHybla, TcpHighSpeed, TcpHtcp, TcpVegas, TcpScalable, TcpVeno, "
			"TcpBic, TcpYeah, TcpIllinois, TcpWestwood, TcpWestwoodPlus, TcpLedbat ", transport_prot);
	cmd.AddValue ("num_flows", "Number of flows", num_flows);
	cmd.AddValue ("queue_disc_type", "Queue disc type for gateway (e.g. ns3::PfifoFastQueueDisc, ns3::RedQueueDisc )", queue_disc_type);
	cmd.AddValue ("prefix_file_name", "Prefix of output trace file", prefix_file_name);
	cmd.AddValue ("file_dir", "Prefix of output trace file", file_dir);
	cmd.AddValue ("mimo", "Allow MiMo config", mimo);
	cmd.AddValue ("appl", "Types of Traffic Flows: BulkSend, OnOff", appl);
	/*cmd.AddValue ("error_p", "Packet error rate", error_p);
	cmd.AddValue ("bandwidth", "Bottleneck bandwidth", bandwidth);
	cmd.AddValue ("delay", "Bottleneck delay", delay);
	cmd.AddValue ("access_bandwidth", "Access link bandwidth", access_bandwidth);
	cmd.AddValue ("access_delay", "Access link delay", access_delay);
	cmd.AddValue ("tracing", "Flag to enable/disable tracing", tracing);
	cmd.AddValue ("prefix_name", "Prefix of output trace file", prefix_file_name);
	cmd.AddValue ("data", "Number of Megabytes of data to transmit", data_mbytes);
	cmd.AddValue ("mtu", "Size of IP packets to send in bytes", mtu_bytes);
	cmd.AddValue ("num_flows", "Number of flows", num_flows);
	cmd.AddValue ("duration", "Time to allow flows to run in seconds", duration);
	cmd.AddValue ("run", "Run index (for setting repeatable seeds)", run);
	cmd.AddValue ("flow_monitor", "Enable flow monitor", flow_monitor);
	cmd.AddValue ("pcap_tracing", "Enable or disable PCAP tracing", pcap);
	cmd.AddValue ("queue_disc_type", "Queue disc type for gateway (e.g. ns3::PfifoFastQueueDisc, ns3::RedQueueDisc )", queue_disc_type);
	cmd.AddValue ("sack", "Enable or disable SACK option", sack);*/
	//cmd.AddValue ("queue_disc_type", "Type of Queue Disc:ns3::PfifoFastQueueDisc, ns3::RedQueueDisc ", sack);
	cmd.Parse (argc, argv);

	transport_prot = std::string ("ns3::") + transport_prot;

	SeedManager::SetSeed (1);
	SeedManager::SetRun (run);

	std::cout<<"Starting scratch simulator"<<std::endl;
	// Calculate the ADU size
	Header* temp_header = new Ipv4Header ();
	uint32_t ip_header = temp_header->GetSerializedSize ();
	NS_LOG_LOGIC ("IP Header size is: " << ip_header);
	delete temp_header;
	temp_header = new TcpHeader ();
	uint32_t tcp_header = temp_header->GetSerializedSize ();
	NS_LOG_LOGIC ("TCP Header size is: " << tcp_header);
	delete temp_header;
	uint32_t tcp_adu_size = mtu_bytes - 20 - (ip_header + tcp_header);
	NS_LOG_LOGIC ("TCP ADU size is: " << tcp_adu_size);

	// Set the simulation start and stop time
	float start_time = 0.1;
	float stop_time = start_time + duration;

	// 4 MB of TCP buffer
	Config::SetDefault ("ns3::TcpSocket::RcvBufSize", UintegerValue (1 << 21));
	Config::SetDefault ("ns3::TcpSocket::SndBufSize", UintegerValue (1 << 21));
	Config::SetDefault ("ns3::TcpSocketBase::Sack", BooleanValue (sack));

	// Select TCP variant
	if (transport_prot.compare ("ns3::TcpWestwoodPlus") == 0)
	{
		// TcpWestwoodPlus is not an actual TypeId name; we need TcpWestwood here
		Config::SetDefault ("ns3::TcpL4Protocol::SocketType", TypeIdValue (TcpWestwood::GetTypeId ()));
		// the default protocol type in ns3::TcpWestwood is WESTWOOD
		Config::SetDefault ("ns3::TcpWestwood::ProtocolType", EnumValue (TcpWestwood::WESTWOODPLUS));
	}
	else
	{
		TypeId tcpTid;
		NS_ABORT_MSG_UNLESS (TypeId::LookupByNameFailSafe (transport_prot, &tcpTid), "TypeId " << transport_prot << " not found");
		Config::SetDefault ("ns3::TcpL4Protocol::SocketType", TypeIdValue (TypeId::LookupByName (transport_prot)));
	}

	TrafficControlHelper tchPfifo;
	tchPfifo.SetRootQueueDisc ("ns3::PfifoFastQueueDisc");

	TrafficControlHelper tchCoDel;
	tchCoDel.SetRootQueueDisc ("ns3::CoDelQueueDisc");

	Ipv4AddressHelper address;
	address.SetBase ("10.0.5.0", "255.255.255.0");

	// Configure the sources and sinks net devices
	// and the channels between the sources/sinks and the gateways
	PointToPointHelper LocalLink;
	LocalLink.SetDeviceAttribute ("DataRate", StringValue (access_bandwidth));
	LocalLink.SetChannelAttribute ("Delay", StringValue (access_delay));

	PointToPointHelper LocalSinkLink;
	LocalSinkLink.SetDeviceAttribute ("DataRate", StringValue (access_bandwidth));
	LocalSinkLink.SetChannelAttribute ("Delay", StringValue (access_delay));

	DataRate access_b (access_bandwidth);
	DataRate bottle_b (bandwidth);
	Time access_d (access_delay);
	Time bottle_d (delay);

	Config::SetDefault ("ns3::CoDelQueueDisc::Mode", EnumValue (CoDelQueueDisc::QUEUE_DISC_MODE_BYTES));

	uint32_t size = (std::min (access_b, bottle_b).GetBitRate () / 8) *
			((access_d + bottle_d) * 2).GetSeconds ();

	// Configure the error model
	// Here we use RateErrorModel with packet error rate
	Ptr<UniformRandomVariable> uv = CreateObject<UniformRandomVariable> ();
	uv->SetStream (50);
	RateErrorModel error_model;
	error_model.SetRandomVariable (uv);
	error_model.SetUnit (RateErrorModel::ERROR_UNIT_PACKET);
	error_model.SetRate (error_p);

	PointToPointHelper UnReLink;
	UnReLink.SetDeviceAttribute ("DataRate", StringValue (bandwidth));
	UnReLink.SetChannelAttribute ("Delay", StringValue (delay));
	UnReLink.SetDeviceAttribute ("ReceiveErrorModel", PointerValue (&error_model));

	Config::SetDefault ("ns3::PfifoFastQueueDisc::Limit", UintegerValue (size / mtu_bytes));
	Config::SetDefault ("ns3::CoDelQueueDisc::MaxBytes", UintegerValue (size));

	WifiMacHelper wifiMac;
	WifiHelper wifiHelper;
	wifiHelper.SetStandard (WIFI_PHY_STANDARD_80211ac);

	/* Set up Legacy Channel */
	YansWifiChannelHelper wifiChannel = YansWifiChannelHelper::Default ();
	/*wifiChannel.SetPropagationDelay ("ns3::ConstantSpeedPropagationDelayModel");
	wifiChannel.AddPropagationLoss ("ns3::FriisPropagationLossModel", "Frequency", DoubleValue (5e9));*/

	/* Setup Physical Layer */
	//std::string phyRate = "HtMcs7";                    /* Physical layer bitrate. */
	YansWifiPhyHelper wifiPhy = YansWifiPhyHelper::Default ();
	//YansWifiChannelHelper channel;
	/*channel.AddPropagationLoss ("ns3::FriisPropagationLossModel",
			"Frequency", DoubleValue (5.180e9));
	channel.SetPropagationDelay ("ns3::ConstantSpeedPropagationDelayModel");*/
	wifiPhy.SetChannel (wifiChannel.Create());
	wifiChannel.AddPropagationLoss ("ns3::FriisPropagationLossModel",
			"Frequency", DoubleValue (5.210e9));

	/*	wifiPhy.Set ("Frequency", UintegerValue (5180));
	wifiPhy.Set ("ShortGuardEnabled", BooleanValue (false));
	wifiPhy.Set ("ChannelWidth", UintegerValue (20));*/
	//wifiPhy.SetChannel (wifiChannel);

	/*wifiPhy.Set("ChannelNumber",UintegerValue(40));
	wifiPhy.Set ("Frequency", UintegerValue (2417));
	wifiPhy.Set ("ChannelWidth", UintegerValue (22));*/

	/*wifiPhy.Set ("TxPowerStart", DoubleValue (10.0));
	wifiPhy.Set ("TxPowerEnd", DoubleValue (10.0));
	wifiPhy.Set ("TxPowerLevels", UintegerValue (1));
	wifiPhy.Set ("TxGain", DoubleValue (0));
	wifiPhy.Set ("RxGain", DoubleValue (0));
	wifiPhy.Set ("RxNoiseFigure", DoubleValue (10));
	wifiPhy.Set ("CcaMode1Threshold", DoubleValue (-79));
	wifiPhy.Set ("EnergyDetectionThreshold", DoubleValue (-79 + 3));*/
	wifiPhy.Set ("CcaMode1Threshold", DoubleValue (-62));
	wifiPhy.Set ("EnergyDetectionThreshold", DoubleValue (-82));
	wifiPhy.SetErrorRateModel ("ns3::YansErrorRateModel");
	//	wifiHelper.SetRemoteStationManager ("ns3::ConstantRateWifiManager","DataMode", StringValue (phyRate),"ControlMode", StringValue ("HtMcs0"));
	wifiHelper.SetRemoteStationManager ("ns3::ConstantRateWifiManager", "DataMode", StringValue("VhtMcs9"), "ControlMode", StringValue("VhtMcs0"));

	//Wireless Devices
	/*NodeContainer networkNodes, networkNodes1;
	networkNodes.Create (4);*/
	NodeContainer apNodes;
	apNodes.Create(wifi);
	//networkNodes1.Create(2);
	//	Ptr<Node> serverNode = networkNodes.Get (0);
	//Ptr<Node> apWifiNode = apNodes.Get (0);
	//Ptr<Node> staWifiNode = staNodes.Get (0);


	//Wired Devices
	NodeContainer srcNodes, sinkNodes, routerNodes;
	std::cout<<num_flows+wifi<<std::endl;
	srcNodes.Create(num_flows+wifi);
	sinkNodes.Create(num_flows);
	routerNodes.Create(2);

	NetDeviceContainer apDevice, staDevice;
	Ssid ssid;




	/* Mobility model */
	MobilityHelper mobility;
	Ptr<ListPositionAllocator> positionAlloc = CreateObject<ListPositionAllocator> ();
	positionAlloc->Add (Vector (15.0, 15.0, 0.0));
	positionAlloc->Add (Vector (25.0, 15.0, 0.0));
	/*positionAlloc->Add (Vector (15.0, 30.0, 0.0));
	positionAlloc->Add (Vector (15.0, 35.0, 0.0));*/
	mobility.SetPositionAllocator (positionAlloc);
	mobility.SetMobilityModel ("ns3::ConstantPositionMobilityModel");
	mobility.Install (routerNodes.Get(0));
	mobility.Install (routerNodes.Get(1));
	/*mobility.Install (apWifiNode);
	mobility.Install (staWifiNode);*/

	for(int i=0;i<num_flows;i++)
	{
		Ptr<ListPositionAllocator> positionAlloc1 = CreateObject<ListPositionAllocator> ();
		positionAlloc1->Add (Vector (5.0, 5.0*(i+1), 0.0));
		positionAlloc1->Add (Vector (35.0, 5.0*(i+1), 0.0));
		mobility.SetPositionAllocator (positionAlloc1);
		mobility.Install (srcNodes.Get(i));
		mobility.Install (sinkNodes.Get(i));
	}

	for(uint16_t i=0;i<wifi;i++)
	{
		Ptr<ListPositionAllocator> positionAlloc2 = CreateObject<ListPositionAllocator> ();
		positionAlloc2->Add (Vector (5.0, 5.0*(num_flows+i+1), 0.0));
		mobility.SetPositionAllocator (positionAlloc2);
		mobility.Install (srcNodes.Get((num_flows+i)));

		Ptr<ListPositionAllocator> positionAlloc1 = CreateObject<ListPositionAllocator> ();
		positionAlloc1->Add (Vector (15.0+(i*10), 30.0, 0.0));
		mobility.SetPositionAllocator (positionAlloc1);
		mobility.Install (apNodes.Get(i));

	}

	/* Internet stack */
	InternetStackHelper stack;
	stack.Install(srcNodes);
	stack.Install(sinkNodes);
	stack.Install(routerNodes);
	/*Ipv4StaticRoutingHelper staticRoutingHelper;
	stack.SetRoutingHelper (staticRoutingHelper);*/
	//stack.Install (staNodes);
	stack.Install (apNodes);

	Ipv4AddressHelper addr;



	Ipv4InterfaceContainer src_interface;
	//Connectiong sources and sinks
	Ipv4InterfaceContainer sink_interface;
	NetDeviceContainer sinkDevices;

	for (int i = 0; i < num_flows; i++)
	{
		NetDeviceContainer devices,devicesSink;
		Ipv4InterfaceContainer interfaces;
		devices=LocalLink.Install(srcNodes.Get(i),routerNodes.Get(0));
		tchPfifo.Install (devices);
		address.NewNetwork ();
		//interfaces=address.Assign(devices);
		src_interface.Add(address.Assign(devices).Get(0));

		devicesSink=LocalSinkLink.Install(routerNodes.Get(1),sinkNodes.Get(i));
		if (queue_disc_type.compare ("ns3::PfifoFastQueueDisc") == 0)
		{
			tchPfifo.Install (devicesSink);
		}
		else if (queue_disc_type.compare ("ns3::CoDelQueueDisc") == 0)
		{
			tchCoDel.Install (devicesSink);
		}
		else
		{
			NS_FATAL_ERROR ("Queue not recognized. Allowed values are ns3::CoDelQueueDisc or ns3::PfifoFastQueueDisc");
		}
		address.NewNetwork ();
		sink_interface.Add(address.Assign(devicesSink).Get(1));
		sinkDevices.Add(devicesSink);
	}

	//WiFi flow
	/*std::vector<NetDeviceContainer> servDevices1;
	std::vector<Ipv4InterfaceContainer> serverInterfaces1;
	for (int i = 0; i < wifi; i++)
	{
		NetDeviceContainer devices=LocalLink.Install(srcNodes.Get(num_flows+i),routerNodes.Get(0));
		tchPfifo.Install (devices);
		address.NewNetwork ();
		//src_interface.Add(address.Assign(devices).Get(0));
		Ipv4InterfaceContainer serverInterface;
		serverInterface=address.Assign(devices);
		serverInterfaces1.push_back(serverInterface);
		servDevices1.push_back(devices);
		std::cout<<"Source-Router IP"<<serverInterfaces1.at(i).GetAddress(0)<<" "<<serverInterfaces1.at(i).GetAddress(1)<<std::endl;
	}*/

	for (uint16_t i = 0; i < wifi; i++)
	{
		NetDeviceContainer devices=LocalLink.Install(srcNodes.Get(num_flows+i),routerNodes.Get(0));
		tchPfifo.Install (devices);
		address.NewNetwork ();
		src_interface.Add(address.Assign(devices).Get(0));
	}

	//connect
	addr.SetBase ("192.168.0.0", "255.255.255.0");
	NetDeviceContainer servDevice;
	for (uint16_t i = 0; i < wifi; i++)
	{
		servDevice =UnReLink.Install(routerNodes.Get(0),apNodes.Get(i));
		Ipv4InterfaceContainer serverInterfaces;
		serverInterfaces= addr.Assign (servDevice);
		addr.NewNetwork();
	}


	//Configure Routers
	NetDeviceContainer routerDevices;
	routerDevices =UnReLink.Install(routerNodes.Get(0),routerNodes.Get(1));
	tchPfifo.Install (routerDevices);
	addr.NewNetwork();
	Ipv4InterfaceContainer routerInterfaces=addr.Assign (routerDevices);


	/* Populate routing table */

	Ipv4GlobalRoutingHelper::PopulateRoutingTables ();
	std::cout<<"populated Routing"<<std::endl;

	Ipv4StaticRoutingHelper staticRoutingHelper;
	stack.SetRoutingHelper (staticRoutingHelper);


	NodeContainer staNodes;
	staNodes.Create(wifi);
	stack.Install (staNodes);



	for(uint16_t i=0;i<wifi;i++)
	{
		Ptr<ListPositionAllocator> positionAlloc1 = CreateObject<ListPositionAllocator> ();
		positionAlloc1->Add (Vector (15.0+(i*10), 35.0, 0.0));
		mobility.SetPositionAllocator (positionAlloc1);
		mobility.Install (staNodes.Get(i));
	}

	/* Configure AP & STA */
	//Ipv4AddressHelper addr;
	addr.SetBase ("192.168.5.0", "255.255.255.0");
	std::vector<NetDeviceContainer> staDevices;
	std::vector<NetDeviceContainer> apDevices;
	std::vector<NetDeviceContainer> wifiApSta;
	//	std::vector<Ipv4InterfaceContainer> wifiApStaInterface;
	Ipv4InterfaceContainer staInterface;
	Ipv4InterfaceContainer apInterface;
	/*Ipv4InterfaceContainer apInterface;
		Ipv4InterfaceContainer staInterface;
		std::vector<Ipv4InterfaceContainer> staInterfaces;
		std::vector<Ipv4InterfaceContainer> apInterfaces;*/
	// ssid = Ssid ("network1", );
	// NetDeviceContainer wiFiNw1,
	for(uint16_t i = 0; i < wifi; i++)
	{
		std::ostringstream str;
		str<<"network"<<i;
		ssid = Ssid (str.str());
		//MiMo Settings------------
		if(mimo==1)
		{
			std::cout<<"ssid"<<ssid<<std::endl;
			//wifiPhy.SetChannel (wifiChannel);
			wifiPhy.Set ("ShortGuardEnabled", BooleanValue (false));
			wifiPhy.Set ("Antennas", UintegerValue (4));
			wifiPhy.Set ("MaxSupportedTxSpatialStreams", UintegerValue (2));
			wifiPhy.Set ("MaxSupportedRxSpatialStreams", UintegerValue (2));
			wifiMac.SetType ("ns3::ApWifiMac",	"Ssid", SsidValue (ssid));
			apDevice = wifiHelper.Install (wifiPhy, wifiMac,apNodes.Get(i));//apWifiNode

			wifiPhy.Set ("ShortGuardEnabled", BooleanValue (false));
			wifiPhy.Set ("Antennas", UintegerValue (2));
			wifiPhy.Set ("MaxSupportedTxSpatialStreams", UintegerValue (2));
			wifiPhy.Set ("MaxSupportedRxSpatialStreams", UintegerValue (2));

			wifiMac.SetType ("ns3::StaWifiMac",
					"Ssid", SsidValue (ssid));
			staDevice = wifiHelper.Install (wifiPhy, wifiMac, staNodes.Get(i));//staWifiNode
			NetDeviceContainer wiFiNw (apDevice,staDevice);
			Ipv4InterfaceContainer wifiInterface;

			apInterface.Add(addr.Assign(apDevice));
			staInterface.Add(addr.Assign(staDevice));
			//apInterface=addr.Assign(apDevice);
			//staInterface=addr.Assign(staDevice);
			wifiApSta.push_back(wiFiNw);
			/*Ptr<Node> apWifiNode =  apNodes.Get(i);
						Ptr<Node> staWifiNode = staNodes.Get(i);*/

			Ptr<Ipv4StaticRouting> staticRouting;
			staticRouting = Ipv4RoutingHelper::GetRouting <Ipv4StaticRouting> (( staNodes.Get(i))->GetObject<Ipv4> ()->GetRoutingProtocol ());
			staticRouting->SetDefaultRoute (apInterface.GetAddress(i), 1 );
			//	std::cout<<"IP"<<wifiApStaInterface[i].GetAddress(0)<<" "<<wifiApStaInterface[i].GetAddress(1)<<std::endl;
		}
		else
		{std::cout<<"ssid"<<ssid<<std::endl;
		//ssid = Ssid ("network");
		wifiMac.SetType ("ns3::ApWifiMac",	"Ssid", SsidValue (ssid));
		apDevice = wifiHelper.Install (wifiPhy, wifiMac, apNodes.Get(i));
		wifiMac.SetType ("ns3::StaWifiMac",
				"Ssid", SsidValue (ssid));
		staDevice = wifiHelper.Install (wifiPhy, wifiMac, staNodes.Get(i));
		NetDeviceContainer wiFiNw (apDevice,staDevice);
		Ipv4InterfaceContainer wifiInterface;

		apInterface.Add(addr.Assign(apDevice));
		staInterface.Add(addr.Assign(staDevice));
		wifiApSta.push_back(wiFiNw);
		//apInterface=addr.Assign(apDevice);
		//staInterface=addr.Assign(staDevice);
		/*Ptr<Node> apWifiNode =  apNodes.Get(i);
											Ptr<Node> staWifiNode = staNodes.Get(i);*/

		Ptr<Ipv4StaticRouting> staticRouting;
		staticRouting = Ipv4RoutingHelper::GetRouting <Ipv4StaticRouting> (( staNodes.Get(i))->GetObject<Ipv4> ()->GetRoutingProtocol ());
		staticRouting->SetDefaultRoute (apInterface.GetAddress(i), 1 );
		}
		addr.NewNetwork();
	}
	std::cout<<"wifi installed"<<std::endl;
	//WiFi stations here

	//Ipv4GlobalRoutingHelper::PopulateRoutingTables ();
	//++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
	if(interf)
	{
		//Add new WiFi Link
		NodeContainer newNetworkNodes;
		newNetworkNodes.Create (2);
		Ptr<Node> apWifiNode1 =  newNetworkNodes.Get (0);
		Ptr<Node> staWifiNode1 = newNetworkNodes.Get (1);
		NetDeviceContainer apDevice1, staDevices1;

		if(mimo==1)
		{
			Ssid ssid = Ssid ("wifi-default");
			wifiMac.SetType ("ns3::ApWifiMac",
					"Ssid", SsidValue (ssid));
			apDevice1 = wifiHelper.Install (wifiPhy, wifiMac, apWifiNode1);//apWifiNode1
			wifiMac.SetType ("ns3::StaWifiMac",
					"Ssid", SsidValue (ssid));
			staDevices1 = wifiHelper.Install (wifiPhy, wifiMac, staWifiNode1);//staWifiNode1

		}
		else
		{
			ssid = Ssid ("wifi-default");
			//	wifiPhy.SetChannel (wifiChannel);
			wifiMac.SetType ("ns3::ApWifiMac",
					"Ssid", SsidValue (ssid));
			apDevice1 = wifiHelper.Install (wifiPhy, wifiMac, apWifiNode1);
			wifiMac.SetType ("ns3::StaWifiMac",
					"Ssid", SsidValue (ssid));
			staDevices1 = wifiHelper.Install (wifiPhy, wifiMac, staWifiNode1);

		}
		Ptr<ListPositionAllocator> positionAlloc2 = CreateObject<ListPositionAllocator> ();
		positionAlloc2->Add (Vector ((15.0-dist), 30.0, 0.0));
		positionAlloc2->Add (Vector ((15.0-dist), 35.0, 0.0));
		mobility.SetMobilityModel ("ns3::ConstantPositionMobilityModel");
		mobility.SetPositionAllocator (positionAlloc2);
		mobility.Install (apWifiNode1);
		mobility.Install (staWifiNode1);

		stack.Install(newNetworkNodes);
		addr.SetBase ("192.168.20.0", "255.255.255.0");
		//Ipv4InterfaceContainer apInterface1;
		Ipv4InterfaceContainer wifiInterface1;
		//apInterface1 = addr.Assign (apDevice1);
		//Ipv4InterfaceContainer staInterface1;
		//staInterface1 = addr.Assign (staDevices1);
		NetDeviceContainer extraWiFiLink (apDevice1, staDevices1);
		wifiInterface1=addr.Assign(extraWiFiLink);
		std::cout<<"IP"<<wifiInterface1.GetAddress(0)<<" "<<wifiInterface1.GetAddress(1)<<std::endl;

		//Extra AP-STA Link
		//UDP flow
		ApplicationContainer serverApp;
		uint32_t payloadSize = 1472; //bytes
		uint16_t udpPort = 9;
		UdpServerHelper server (udpPort);
		serverApp = server.Install (apWifiNode1);
		serverApp.Start (Seconds (0.0));
		serverApp.Stop (Seconds (stop_time));

		//UdpClientHelper client (apInterface1.GetAddress (0), udpPort);

		UdpClientHelper client (wifiInterface1.GetAddress(0), udpPort);
		client.SetAttribute ("MaxPackets", UintegerValue (4294967295u));
		client.SetAttribute ("Interval", TimeValue (Time ("0.00001"))); //packets/s
		client.SetAttribute ("PacketSize", UintegerValue (payloadSize));
		ApplicationContainer clientApp = client.Install (staWifiNode1);
		clientApp.Start (Seconds (0.0));
		clientApp.Stop (Seconds (stop_time));
	}
	//++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

	std::cout<<"routing Link"<<std::endl;

	ApplicationContainer sourceApplications,sourceApplicationsWiFi, sinkApplications,sinkApplicationsWiFi;
	//+++++++++++++++++++++++++++++++++BulkSendApplication++++++++++++++++++++++++++++++++++++
	if (appl=="BulkSend")
	{
		uint16_t port = 50000;
		Address sinkLocalAddress (InetSocketAddress (Ipv4Address::GetAny (), port));
		PacketSinkHelper sinkHelper ("ns3::TcpSocketFactory", sinkLocalAddress);

		//(srcNodes-1) --> wired last node --> wiFi node
		for (uint16_t i = 0; i < num_flows; i++)
		{
			AddressValue remoteAddress (InetSocketAddress (sink_interface.GetAddress(i,0), port));
			Config::SetDefault ("ns3::TcpSocket::SegmentSize", UintegerValue (tcp_adu_size));
			BulkSendHelper ftp ("ns3::TcpSocketFactory", Address ());
			ftp.SetAttribute ("Remote", remoteAddress);
			ftp.SetAttribute ("SendSize", UintegerValue (tcp_adu_size));
			ftp.SetAttribute ("MaxBytes", UintegerValue (int(data_mbytes * 1000000)));

			ApplicationContainer sourceApp = ftp.Install (srcNodes.Get (i));
			sourceApplications.Add(sourceApp);
			sourceApp.Start (Seconds (0.0));
			sourceApp.Stop (Seconds (stop_time ));

			sinkHelper.SetAttribute ("Protocol", TypeIdValue (TcpSocketFactory::GetTypeId ()));
			ApplicationContainer sinkApp = sinkHelper.Install (sinkNodes.Get(i));
			//sinkApp = sinkHelper.Install (wifiStaNodes.Get (nWifi - 1));
			sinkApplications.Add(sinkApp);
			sinkApp.Start (Seconds (0.0));
			sinkApp.Stop (Seconds (stop_time));
			//Sink helper for Wifi
			//sourceApp.Stop (Seconds (stop_time - 3));
		}

		//AddressValue remoteAddress1 (InetSocketAddress (staInterface.GetAddress (0), port));
		for (uint16_t i = 0; i < wifi; i++)
		{
			std::cout<<staInterface.GetAddress(i,0)<<std::endl;
			AddressValue remoteAddress1 (InetSocketAddress (staInterface.GetAddress(i,0), port));
			Config::SetDefault ("ns3::TcpSocket::SegmentSize", UintegerValue (tcp_adu_size));
			BulkSendHelper ftp1 ("ns3::TcpSocketFactory", Address ());
			ftp1.SetAttribute ("Remote", remoteAddress1);
			ftp1.SetAttribute ("SendSize", UintegerValue (tcp_adu_size));
			ftp1.SetAttribute ("MaxBytes", UintegerValue (int(data_mbytes * 1000000)));

			ApplicationContainer sourceApp1 = ftp1.Install (srcNodes.Get((num_flows+i)));
			sourceApplicationsWiFi.Add(sourceApp1);
			sourceApp1.Start (Seconds (0.0));

			sinkHelper.SetAttribute ("Protocol", TypeIdValue (TcpSocketFactory::GetTypeId ()));
			ApplicationContainer sinkApp1 = sinkHelper.Install (staNodes.Get(i));
			//sink = StaticCast<PacketSink> (sinkApp1.Get (0));
			sinkApplicationsWiFi.Add(sinkApp1);
			sinkApp1.Start (Seconds (0.0));
			sinkApp1.Stop (Seconds (stop_time));
			sourceApp1.Stop (Seconds (stop_time));
		}
	}
	//++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
	//+++++++++++++++++++++++++++++++++++++++OnOffApplication++++++++++++++++++++++++++++++++++++++++++++++++++++++
	//onOff Application
	if (appl=="OnOff")
	{
		uint16_t port = 9;
		Address sinkLocalAddress (InetSocketAddress (Ipv4Address::GetAny (), port));
		PacketSinkHelper sinkHelper ("ns3::TcpSocketFactory", sinkLocalAddress);
		ApplicationContainer sourceApplications,sourceApplicationsWiFi, sinkApplications,sinkApplicationsWiFi;


		//Weibull Random Variables
		double scale = 2.23;
		double shape = 0.37;
		Ptr<WeibullRandomVariable> weibull = CreateObject<WeibullRandomVariable> ();
		weibull->SetAttribute ("Scale", DoubleValue (scale));
		weibull->SetAttribute ("Shape", DoubleValue (shape));

		//Gamma Random Variable
		double a = 0.99;
		double b = 3.43;
		Ptr<GammaRandomVariable> gamma = CreateObject<GammaRandomVariable> ();
		gamma->SetAttribute ("Alpha", DoubleValue (a));
		gamma->SetAttribute ("Beta", DoubleValue (b));

		for (uint16_t i = 0; i < num_flows; i++)
		{
			Config::SetDefault ("ns3::TcpSocket::SegmentSize", UintegerValue (tcp_adu_size));
			OnOffHelper onoff ("ns3::TcpSocketFactory", (InetSocketAddress (sink_interface.GetAddress(i,0), port)));
			onoff.SetAttribute ("OnTime", PointerValue (gamma));
			onoff.SetAttribute ("OffTime", PointerValue (weibull));
			onoff.SetAttribute ("PacketSize", UintegerValue (tcp_adu_size));
			onoff.SetAttribute ("DataRate", DataRateValue (DataRate (bandwidth)));
			ApplicationContainer sourceApp = onoff.Install (srcNodes.Get (i));
			//sourceApp.Add(onoff.Install(srcNodes.Get (i)));
			sourceApplications.Add(sourceApp);
			sourceApp.Start (Seconds (0.0));

			sinkHelper.SetAttribute ("Protocol", TypeIdValue (TcpSocketFactory::GetTypeId ()));
			ApplicationContainer sinkApp = sinkHelper.Install (sinkNodes.Get(i));
			//sinkApp = sinkHelper.Install (wifiStaNodes.Get (nWifi - 1));
			sinkApplications.Add(sinkApp);
			sinkApp.Start (Seconds (0.0));
			sinkApp.Stop (Seconds (stop_time));
			sourceApp.Stop (Seconds (stop_time ));

		}

		for (uint16_t i = 0; i < wifi; i++)
		{
			AddressValue remoteAddress1 (InetSocketAddress (staInterface.GetAddress(i,0), port));
			Config::SetDefault ("ns3::TcpSocket::SegmentSize", UintegerValue (tcp_adu_size));
			OnOffHelper onoff1 ("ns3::TcpSocketFactory", Address ());
			onoff1.SetAttribute ("Remote", remoteAddress1);
			onoff1.SetAttribute ("OnTime", PointerValue (gamma));
			onoff1.SetAttribute ("OffTime", PointerValue (weibull));
			onoff1.SetAttribute ("PacketSize", UintegerValue (tcp_adu_size));
			//	onoff1.SetAttribute ("MaxBytes", UintegerValue (int(data_mbytes * 1000000)));
			onoff1.SetAttribute ("StartTime", TimeValue (Seconds (1.0)));
			onoff1.SetAttribute ("DataRate", DataRateValue (DataRate (bandwidth)));
			ApplicationContainer sourceApp1 = onoff1.Install (srcNodes.Get((num_flows+i)));
			sourceApplicationsWiFi.Add(sourceApp1);
			sourceApp1.Start (Seconds (0.0));

			sinkHelper.SetAttribute ("Protocol", TypeIdValue (TcpSocketFactory::GetTypeId ()));
			ApplicationContainer sinkApp1 = sinkHelper.Install (staNodes.Get(i));
			//sink = StaticCast<PacketSink> (sinkApp1.Get (0));
			sinkApplicationsWiFi.Add(sinkApp1);
			sinkApp1.Start (Seconds (0.0));
			sinkApp1.Stop (Seconds (stop_time));
			sourceApp1.Stop (Seconds (stop_time));
		}
	}
	//+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
	//NetAnim
	AnimationInterface anim ("animation.xml");

	if (tracing)
	{
		std::cout<<"tracing activated"<<std::endl;
		ScheduleAllTraces(prefix_file_name,file_dir,srcNodes,sinkNodes);
		ScheduleThroughputTrace(sinkApplications,sinkApplicationsWiFi, prefix_file_name, file_dir,sinkNodes);
		ScheduleWiFiThroughputTrace(sinkApplicationsWiFi, prefix_file_name, file_dir);
	}

	Simulator::Stop (Seconds (stop_time));
	Simulator::Run ();

	std::cout<<"Transport Protocol: "<<transport_prot;
	Simulator::Destroy ();

	for (uint16_t i = 0; i < sinkApplications.GetN (); i++)
	{
		Ptr<PacketSink> sink = DynamicCast<PacketSink> (sinkApplications.Get (i));
		double averageThroughput = ((sink->GetTotalRx () * 8) / (1e6  * duration));
		std::cout << "\nAverage throughput by "<<i<<": " << averageThroughput << " Mbit/s" << std::endl;
	}

	for (uint16_t i = 0; i < sinkApplicationsWiFi.GetN (); i++)
	{
		Ptr<PacketSink> sink = DynamicCast<PacketSink> (sinkApplicationsWiFi.Get (i));
		double averageThroughput = ((sink->GetTotalRx () * 8) / (1e6  * duration));
		std::cout << "\nAverage throughput by WiFi "<<i<<": " << averageThroughput << " Mbit/s" << std::endl;
	}
	return 0;
}

