// Network topology
//
//       n0 ----------- n1
//            100 Mbps
//             10 ms
//
//  Router queue size: q
//
#include <string>
#include <fstream>
#include "ns3/core-module.h"
#include "ns3/point-to-point-module.h"
#include "ns3/internet-module.h"
#include "ns3/applications-module.h"
#include "ns3/network-module.h"
#include "ns3/packet-sink.h"

using namespace ns3;
using namespace std;

NS_LOG_COMPONENT_DEFINE ("TCPSingleFlow");




void BandwidthTrace (void);
void ScheduleBw (void);
std::string bandwidth = "2Mbps";


ifstream bwfile ("rate_trace.txt");
void
BandwidthTrace (void)
{
  getline (bwfile, bandwidth);
  Config::Set ("/NodeList/*/DeviceList/*/$ns3::PointToPointNetDevice/DataRate",StringValue (bandwidth));

  cout << "At time " << Simulator::Now().GetSeconds() << ": BandwidthTrace bandwidth " << bandwidth << endl;
  if (!bwfile.eof ())
  {
    ScheduleBw ();
  }
  else  
  {
    std::cout << "end of file!" << std::endl;
  }

}


void ScheduleBw (void)
{
     Simulator::Schedule (MilliSeconds (1000), BandwidthTrace);
}




static void
CwndChange (Ptr<OutputStreamWrapper> stream, uint32_t oldCwnd, uint32_t newCwnd)
{
  *stream->GetStream () << Simulator::Now ().GetSeconds () << "\t" << oldCwnd << "\t" << newCwnd << std::endl;
}

static void
RttChange (Ptr<OutputStreamWrapper> stream, Time oldRtt, Time newRtt)
{
  *stream->GetStream () << Simulator::Now ().GetSeconds () << "\t" << oldRtt.GetSeconds () << "\t" << newRtt.GetSeconds () << std::endl;
}



static void Rx (Ptr<OutputStreamWrapper> stream, Ptr<const Packet> packet, const Address &from)
{
  *stream->GetStream () << Simulator::Now ().GetSeconds () << "\t" << packet->GetSize()<< std::endl;
}


static void
Traces(uint16_t nodeNum, std::string protocol)
{
  AsciiTraceHelper asciiTraceHelper;
  cout<<"Inside \"Traces\""<< endl;

  std::ostringstream pathCW;
  pathCW<<"/NodeList/*/$ns3::TcpL4Protocol/SocketList/0/CongestionWindow";

  std::ostringstream fileCW;
  fileCW<<protocol<<"-"<<nodeNum+1<<"-TCP-CWND.txt";

  std::ostringstream pathRTT;
  pathRTT<<"/NodeList/*/$ns3::TcpL4Protocol/SocketList/0/RTT";

  std::ostringstream fileRTT;
  fileRTT<<protocol<<"-"<<nodeNum+1<<"-TCP-RTT.txt";

  std::ostringstream pathRCWnd;
  pathRCWnd<<"/NodeList/*/$ns3::TcpL4Protocol/SocketList/0/RWND";

  std::ostringstream fileRCWnd;
  fileRCWnd<<protocol<<"-"<<nodeNum+1<<"-TCP-RCWND.txt";

  Ptr<OutputStreamWrapper> stream1 = asciiTraceHelper.CreateFileStream (fileCW.str ().c_str ());
  Config::ConnectWithoutContext (pathCW.str ().c_str (), MakeBoundCallback(&CwndChange, stream1));

  Ptr<OutputStreamWrapper> stream2 = asciiTraceHelper.CreateFileStream (fileRTT.str ().c_str ());
  Config::ConnectWithoutContext (pathRTT.str ().c_str (), MakeBoundCallback(&RttChange, stream2));

  Ptr<OutputStreamWrapper> stream4 = asciiTraceHelper.CreateFileStream (fileRCWnd.str ().c_str ());
  Config::ConnectWithoutContext (pathRCWnd.str ().c_str (), MakeBoundCallback(&CwndChange, stream4));

}


int
main (int argc, char *argv[])
{
  
  double simStopTime = 190;
  bool harqEnabled = true;
  bool rlcAmEnabled = true;
  std::string protocol = "TcpNewReno";
  int bufferSize = 1000 *1000 * 1.5;
  int packetSize = 14000;
  int p2pDelay = 0;
  // This 3GPP channel model example only demonstrate the pathloss model. The fast fading model is still in developing.

  //The available channel scenarios are 'RMa', 'UMa', 'UMi-StreetCanyon', 'InH-OfficeMixed', 'InH-OfficeOpen', 'InH-ShoppingMall'
  //std::string scenario = "RMa";
  //std::string condition = "n";

  CommandLine cmd;
//  cmd.AddValue("numEnb", "Number of eNBs", numEnb);
//  cmd.AddValue("numUe", "Number of UEs per eNB", numUe);
  cmd.AddValue("simTime", "Total duration of the simulation [s])", simStopTime);
//  cmd.AddValue("interPacketInterval", "Inter-packet interval [us])", interPacketInterval);
  cmd.AddValue("harq", "Enable Hybrid ARQ", harqEnabled);
  cmd.AddValue("rlcAm", "Enable RLC-AM", rlcAmEnabled);
  cmd.AddValue("protocol", "TCP protocol", protocol);
  cmd.AddValue("bufferSize", "buffer size", bufferSize);
  cmd.AddValue("packetSize", "packet size", packetSize);
  cmd.AddValue("p2pDelay","delay between server and PGW", p2pDelay);
  cmd.Parse(argc, argv);

  //Config::SetDefault ("ns3::TcpSocket::SegmentSize", UintegerValue (65535));
  Config::SetDefault ("ns3::TcpSocketBase::MinRto", TimeValue (MilliSeconds (200)));
  Config::SetDefault ("ns3::Ipv4L3Protocol::FragmentExpirationTimeout", TimeValue (Seconds (1)));
  Config::SetDefault ("ns3::TcpSocket::SegmentSize", UintegerValue (packetSize));
  Config::SetDefault ("ns3::TcpSocket::DelAckCount", UintegerValue (1));

  Config::SetDefault ("ns3::TcpSocket::SndBufSize", UintegerValue (131072*400));
  Config::SetDefault ("ns3::TcpSocket::RcvBufSize", UintegerValue (131072*400));

  Config::SetDefault ("ns3::QueueBase::MaxPackets", UintegerValue (100*1000));

  Config::SetDefault ("ns3::CoDelQueueDisc::Mode", StringValue ("QUEUE_DISC_MODE_PACKETS"));
  Config::SetDefault ("ns3::CoDelQueueDisc::MaxPackets", UintegerValue (50000));

 if(protocol == "TcpNewReno")
    {
  Config::SetDefault ("ns3::TcpL4Protocol::SocketType", TypeIdValue (TcpNewReno::GetTypeId ()));
    }
    else if (protocol == "TcpVegas")
    {
      Config::SetDefault ("ns3::TcpL4Protocol::SocketType", TypeIdValue (TcpVegas::GetTypeId ()));
    }
    else if (protocol == "TcpLedbat")
    {
      Config::SetDefault ("ns3::TcpL4Protocol::SocketType", TypeIdValue (TcpLedbat::GetTypeId ()));

    }
    else if (protocol == "TcpHighSpeed")
    {
      Config::SetDefault ("ns3::TcpL4Protocol::SocketType", TypeIdValue (TcpHighSpeed::GetTypeId ()));

    }
    else if (protocol == "TcpCubic")
    {
      Config::SetDefault ("ns3::TcpL4Protocol::SocketType", TypeIdValue (TcpCubic::GetTypeId ()));

    }
    else if (protocol == "TcpIllinois")
    {
      Config::SetDefault ("ns3::TcpL4Protocol::SocketType", TypeIdValue (TcpIllinois::GetTypeId ()));

    }
    else if (protocol == "TcpHybla")
    {
      Config::SetDefault ("ns3::TcpL4Protocol::SocketType", TypeIdValue (TcpHybla::GetTypeId ()));

    }
    else if (protocol == "TcpVeno")
    {
      Config::SetDefault ("ns3::TcpL4Protocol::SocketType", TypeIdValue (TcpVeno::GetTypeId ()));

    }
    else if (protocol == "TcpWestwood")
    {
      Config::SetDefault ("ns3::TcpL4Protocol::SocketType", TypeIdValue (TcpWestwood::GetTypeId ()));

    }
    else if (protocol == "TcpYeah")
    {
      Config::SetDefault ("ns3::TcpL4Protocol::SocketType", TypeIdValue (TcpYeah::GetTypeId ()));

    }
    else
    {
    std::cout<<protocol<<" Unkown protocol.\n";
    return 1;
    }
  Config::SetDefault ("ns3::TcpVegas::Alpha", UintegerValue (20));
  Config::SetDefault ("ns3::TcpVegas::Beta", UintegerValue (40));
  Config::SetDefault ("ns3::TcpVegas::Gamma", UintegerValue (2));



  NodeContainer nodes;
  nodes.Create (2);
  PointToPointHelper pointToPoint;
  pointToPoint.SetDeviceAttribute ("DataRate", DataRateValue (DataRate ("1Mb/s")));
  pointToPoint.SetDeviceAttribute ("Mtu", UintegerValue (1500));
  pointToPoint.SetChannelAttribute ("Delay", TimeValue (MilliSeconds (p2pDelay)));

  NetDeviceContainer devices;
  devices = pointToPoint.Install (nodes);
  InternetStackHelper internet;
  internet.Install (nodes);
  Ipv4AddressHelper ipv4;
  ipv4.SetBase ("10.1.1.0", "255.255.255.0");
  Ipv4InterfaceContainer i = ipv4.Assign (devices);



  ApplicationContainer sourceApps;
  ApplicationContainer sinkApps;
  uint16_t sinkPort = 20000;
  PacketSinkHelper sink ("ns3::TcpSocketFactory",
                         InetSocketAddress (Ipv4Address::GetAny (), sinkPort));
  sinkApps = sink.Install (nodes.Get (1));

  //BulkSendHelper ftp ("ns3::TcpSocketFactory",
  //                         InetSocketAddress (i.GetAddress (1), sinkPort));
  //sourceApps.Add (ftp.Install (nodes.Get (0)));
  BulkSendHelper source ("ns3::TcpSocketFactory",
                            InetSocketAddress (i.GetAddress (1), sinkPort));
   // Set the amount of data to send in bytes.  Zero is unlimited.
  uint32_t maxBytes = 0;
   source.SetAttribute ("MaxBytes", UintegerValue (maxBytes));
   sourceApps = source.Install (nodes.Get (0));


  std::ostringstream fileName;
  fileName<<protocol+"-"+std::to_string(bufferSize)+"-"+std::to_string(packetSize)+"-"+std::to_string(p2pDelay)<<"-"<<0+1<<"-TCP-DATA.txt";

  AsciiTraceHelper asciiTraceHelper;

  Ptr<OutputStreamWrapper> stream = asciiTraceHelper.CreateFileStream (fileName.str ().c_str ());
  sinkApps.Get(0)->TraceConnectWithoutContext("Rx",MakeBoundCallback (&Rx, stream));
  Simulator::Schedule (Seconds (0.0101), &Traces, 0, protocol+"-"+std::to_string(bufferSize)+"-"+std::to_string(packetSize)+"-"+std::to_string(p2pDelay));

  sourceApps.Start (Seconds (0.01));
  sourceApps.Stop (Seconds (simStopTime));
 
  sinkApps.Start (Seconds (0.0));
  sinkApps.Stop (Seconds (20.0));

  Simulator::Schedule(Seconds(0.00001),&BandwidthTrace);

  pointToPoint.EnablePcapAll ("tcp-single-flow-mod");

  Config::Set ("/NodeList/*/DeviceList/*/TxQueue/MaxPackets", UintegerValue (1000*1000));
  Config::Set ("/NodeList/*/DeviceList/*/TxQueue/MaxBytes", UintegerValue (1500*1000*1000));


  Simulator::Stop (Seconds (simStopTime));
  Simulator::Run ();
  Simulator::Destroy ();


}
