/* -*-  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"
#include "ns3/network-module.h"
#include "ns3/internet-module.h"
#include "ns3/applications-module.h"
#include "ns3/mobility-module.h"
#include "ns3/pmip6-module.h"
#include "ns3/wifi-module.h"
#include "ns3/csma-module.h"
#include "ns3/bridge-module.h"
#include <ns3/flow-monitor-module.h>
#include "ns3/ipv6-static-routing.h"
#include "ns3/ipv6-static-source-routing.h"
#include "ns3/ipv6-routing-table-entry.h"

#include <iostream>
#include <iomanip>
#include <fstream>
#include <vector>
#include <string>

NS_LOG_COMPONENT_DEFINE ("Pmip6Wifi");

using namespace ns3;

void ThroughputMonitor (FlowMonitorHelper* fmhelper, Ptr<FlowMonitor> flowMon)
	{	
		flowMon->CheckForLostPackets(); 
		std::map<FlowId, FlowMonitor::FlowStats> flowStats = flowMon->GetFlowStats();
		Ptr<Ipv6FlowClassifier> classing = DynamicCast<Ipv6FlowClassifier> (fmhelper->GetClassifier());
		for (std::map<FlowId, FlowMonitor::FlowStats>::const_iterator stats = flowStats.begin (); stats != flowStats.end (); ++stats)
		{	
			Ipv6FlowClassifier::FiveTuple fiveTuple = classing->FindFlow (stats->first);
			std::cout<<"Flow ID			: " << stats->first <<" ; "<< fiveTuple.sourceAddress <<" -----> "<<fiveTuple.destinationAddress<<std::endl;
//			std::cout<<"Tx Packets = " << stats->second.txPackets<<std::endl;
//			std::cout<<"Rx Packets = " << stats->second.rxPackets<<std::endl;
			std::cout<<"Duration		: "<<stats->second.timeLastRxPacket.GetSeconds()-stats->second.timeFirstTxPacket.GetSeconds()<<std::endl;
			std::cout<<"Last Received Packet	: "<< stats->second.timeLastRxPacket.GetSeconds()<<" Seconds"<<std::endl;
			std::cout<<"Throughput: " << stats->second.rxBytes * 8.0 / (stats->second.timeLastRxPacket.GetSeconds()-stats->second.timeFirstTxPacket.GetSeconds())/1024/1024  << " Mbps"<<std::endl;
			std::cout<<"---------------------------------------------------------------------------"<<std::endl;
		}	
			Simulator::Schedule(Seconds(1),&ThroughputMonitor, fmhelper, flowMon);

			
	}

Ipv6InterfaceContainer AssignIpv6Address(Ptr<NetDevice> device, Ipv6Address addr, Ipv6Prefix prefix)
{
  Ipv6InterfaceContainer retval;

  Ptr<Node> node = device->GetNode ();
  NS_ASSERT_MSG (node, "Ipv6AddressHelper::Allocate (): Bad node");

  Ptr<Ipv6> ipv6 = node->GetObject<Ipv6> ();
  NS_ASSERT_MSG (ipv6, "Ipv6AddressHelper::Allocate (): Bad ipv6");
  int32_t ifIndex = 0;

  ifIndex = ipv6->GetInterfaceForDevice (device);
  if (ifIndex == -1)
    {
      ifIndex = ipv6->AddInterface (device);
    }
  NS_ASSERT_MSG (ifIndex >= 0, "Ipv6AddressHelper::Allocate (): "
                 "Interface index not found");

  Ipv6InterfaceAddress ipv6Addr = Ipv6InterfaceAddress (addr, prefix);
  ipv6->SetMetric (ifIndex, 1);
  ipv6->SetUp (ifIndex);
  ipv6->AddAddress (ifIndex, ipv6Addr);

  retval.Add (ipv6, ifIndex);

  return retval;
}

Ipv6InterfaceContainer AssignWithoutAddress(Ptr<NetDevice> device)
{
  Ipv6InterfaceContainer retval;

  Ptr<Node> node = device->GetNode ();
  NS_ASSERT_MSG (node, "Ipv6AddressHelper::Allocate (): Bad node");

  Ptr<Ipv6> ipv6 = node->GetObject<Ipv6> ();
  NS_ASSERT_MSG (ipv6, "Ipv6AddressHelper::Allocate (): Bad ipv6");
  int32_t ifIndex = 0;

  ifIndex = ipv6->GetInterfaceForDevice (device);
  if (ifIndex == -1)
    {
      ifIndex = ipv6->AddInterface (device);
    }
  NS_ASSERT_MSG (ifIndex >= 0, "Ipv6AddressHelper::Allocate (): "
                 "Interface index not found");

  ipv6->SetMetric (ifIndex, 1);
  ipv6->SetUp (ifIndex);

  retval.Add (ipv6, ifIndex);

  return retval;
}

int main (int argc, char *argv[])
{
	GlobalValue::Bind ("ChecksumEnabled", BooleanValue (true));
  NodeContainer sta;
  NodeContainer cn;
  NodeContainer backbone;
  NodeContainer aps;

  //ref nodes
  NodeContainer lma;
  NodeContainer mags;
  NodeContainer outerNet;
  NodeContainer mag1Net;
  NodeContainer mag2Net;
  
  NetDeviceContainer backboneDevs;
  NetDeviceContainer outerDevs;
  NetDeviceContainer mag1Devs;
  NetDeviceContainer mag2Devs;
  NetDeviceContainer mag1ApDev;
  NetDeviceContainer mag2ApDev;
  NetDeviceContainer mag1BrDev;
  NetDeviceContainer mag2BrDev;
  NetDeviceContainer staDevs;
  
  Ipv6InterfaceContainer backboneIfs;
  Ipv6InterfaceContainer outerIfs;
  Ipv6InterfaceContainer mag1Ifs;
  Ipv6InterfaceContainer mag2Ifs;
  Ipv6InterfaceContainer staIfs;
  
  CommandLine cmd;
  //cmd.AddValue ("verbose", "turn on some relevant log components", verbose);
  cmd.Parse (argc, argv);
//set the seed it will duplicate the seed value 6 times
  SeedManager::SetSeed (123456);

  LogLevel logAll = static_cast<LogLevel>(LOG_PREFIX_TIME | LOG_PREFIX_NODE | LOG_LEVEL_ALL);
//  LogLevel logLogic = static_cast<LogLevel>(LOG_PREFIX_TIME | LOG_PREFIX_NODE | LOG_LEVEL_LOGIC);
  LogLevel logInfo = static_cast<LogLevel>(LOG_PREFIX_TIME | LOG_PREFIX_NODE | LOG_LEVEL_INFO);

  LogComponentEnable ("UdpServer", logInfo);
  LogComponentEnable ("Pmipv6Agent", logAll);
  LogComponentEnable ("Pmipv6MagNotifier", logAll);
 
  backbone.Create(3);
  aps.Create(2);
  cn.Create(1);
  sta.Create(1);

  InternetStackHelper internet;
  internet.Install (backbone);
  internet.Install (aps);
  internet.Install (cn);
  internet.Install (sta);

  lma.Add(backbone.Get(0));
  
  mags.Add(backbone.Get(1));
  mags.Add(backbone.Get(2));
  
  outerNet.Add(lma);
  outerNet.Add(cn);
  
  mag1Net.Add(mags.Get(0));
  mag1Net.Add(aps.Get(0));

  mag2Net.Add(mags.Get(1));
  mag2Net.Add(aps.Get(1));

  //The CSMA net devices and channels are typically created
  //and configured using the associated CsmaHelper object.
  //Once you have your nodes, you need to instantiate a CsmaHelper
  CsmaHelper csma, csma1;

  //MAG's MAC Address (for unify default gateway of MN)
  Mac48Address magMacAddr("00:00:AA:BB:CC:DD");

  Ipv6InterfaceContainer iifc;
  
  //set any attributes you may want to change
  //Link between CN and LMA is 50Mbps and 0.1ms delay
  csma1.SetChannelAttribute ("DataRate", DataRateValue (DataRate(50000000)));
  csma1.SetChannelAttribute ("Delay", TimeValue (MicroSeconds(100)));
  csma1.SetDeviceAttribute ("Mtu", UintegerValue (1400));
  
  //Once the attributes are set, all that remains is to create the devices
  //and install them on the required nodes, and to connect the devices together
  //using a CSMA channel.
  //When we create the net devices, we add them to a container to allow you
  //to use them in the future. This all takes just one line of code
  outerDevs = csma1.Install(outerNet);

  iifc = AssignIpv6Address(outerDevs.Get(0), Ipv6Address("3ffe:2::1"), 64);
  outerIfs.Add(iifc);
  iifc = AssignIpv6Address(outerDevs.Get(1), Ipv6Address("3ffe:2::2"), 64);
  outerIfs.Add(iifc);
  //outerIfs.SetRouter(0, true);
  //outerIfs.SetForwarding(0, true);
  //void ns3::Ipv6InterfaceContainer::SetForwarding	(	uint32_t 	i,
  //													bool 	state
  //												)
  //Set the state of the stack (act as a router or as an host) for the specified index.
  //This automatically sets all the node's interfaces to the same forwarding state.
  //
  outerIfs.SetForwarding (0, true);

  //void ns3::Ipv6InterfaceContainer::SetDefaultRouteInAllNodes	(	uint32_t 	router	)
  //Set the default route for all the devices (except the router itself).
  //Parameters
  //			router	the default router index
  outerIfs.SetDefaultRouteInAllNodes (0);

  //All Link is 50Mbps and 0.1ms delay
  csma.SetChannelAttribute ("DataRate", DataRateValue (DataRate(50000000)));
  csma.SetChannelAttribute ("Delay", TimeValue (MicroSeconds(100)));
  csma.SetDeviceAttribute ("Mtu", UintegerValue (1400));

  backboneDevs = csma.Install(backbone);
  iifc = AssignIpv6Address(backboneDevs.Get(0), Ipv6Address("3ffe:1::1"), 64);
  backboneIfs.Add(iifc);
  iifc = AssignIpv6Address(backboneDevs.Get(1), Ipv6Address("3ffe:1::2"), 64);
  backboneIfs.Add(iifc);
  iifc = AssignIpv6Address(backboneDevs.Get(2), Ipv6Address("3ffe:1::3"), 64);
  backboneIfs.Add(iifc);
  //backboneIfs.SetRouter(0, true);
  backboneIfs.SetForwarding (0, true);
  backboneIfs.SetDefaultRouteInAllNodes (0);
  
  BridgeHelper bridge;
  MobilityHelper mobility;
  Ptr<ListPositionAllocator> positionAlloc;
  
  positionAlloc = CreateObject<ListPositionAllocator> ();
  
  positionAlloc->Add (Vector (0.0, -20.0, 0.0));   //LMA
  positionAlloc->Add (Vector (-50.0, 20.0, 0.0)); //MAG1
  positionAlloc->Add (Vector (50.0, 20.0, 0.0));  //MAG2
  
  //void ns3::MobilityHelper::SetPositionAllocator	(	Ptr< PositionAllocator > 	allocator	)
  //Set the position allocator which will be used to allocate the initial position
  //of every node initialized during MobilityModel::Install.
  //Parameters
  //		allocator	allocate initial node positions

  mobility.SetPositionAllocator (positionAlloc);
  //
  //ns3::ConstantPositionMobilityModel::ConstantPositionMobilityModel()
  //Create a position located at coordinates (0,0,0)
  mobility.SetMobilityModel ("ns3::ConstantPositionMobilityModel");
  
  mobility.Install (backbone);
  
  positionAlloc = CreateObject<ListPositionAllocator> ();
  
  positionAlloc->Add (Vector (75.0, -20.0, 0.0));   //CN
  
  mobility.SetPositionAllocator (positionAlloc);
  mobility.SetMobilityModel ("ns3::ConstantPositionMobilityModel");
  
  mobility.Install (cn);
  
  positionAlloc = CreateObject<ListPositionAllocator> ();
  
  positionAlloc->Add (Vector (-50.0, 40.0, 0.0)); //MAG1AP
  positionAlloc->Add (Vector (50.0, 40.0, 0.0));  //MAG2AP
  
  mobility.SetPositionAllocator (positionAlloc);
  mobility.SetMobilityModel ("ns3::ConstantPositionMobilityModel");
  
  mobility.Install (aps);

  //Setting MAG1 and WLAN AP
  mag1Devs = csma.Install(mag1Net);
  mag1Devs.Get(0)->SetAddress(magMacAddr);
  
  mag1Ifs = AssignIpv6Address(mag1Devs.Get(0), Ipv6Address("3ffe:1:1::1"), 64);

  Ssid ssid = Ssid("MAG");
  YansWifiPhyHelper wifiPhy = YansWifiPhyHelper::Default ();
  wifiPhy.SetPcapDataLinkType (YansWifiPhyHelper::DLT_IEEE802_11_RADIO);

  WifiHelper wifi = WifiHelper::Default ();
  /**
  * class NqosWifiMacHelper : public WifiMacHelper
  * This class can create MACs of type ns3::ApWifiMac, ns3::StaWifiMac,
  * and, ns3::AdhocWifiMac, with QosSupported attribute set to False.
  */
  NqosWifiMacHelper wifiMac = NqosWifiMacHelper::Default ();

  /**
   * \brief manage and create wifi channel objects for the yans model.
   *
   * The intent of this class is to make it easy to create a channel object
   * which implements the yans channel model. The yans channel model is described
   * in "Yet Another Network Simulator", http://cutebugs.net/files/wns2-yans.pdf
   */
  YansWifiChannelHelper wifiChannel = YansWifiChannelHelper::Default ();
  wifiPhy.SetChannel (wifiChannel.Create ());
   
  wifiMac.SetType ("ns3::ApWifiMac",
		           "Ssid", SsidValue (ssid),
		           "BeaconGeneration", BooleanValue (true),
		           "BeaconInterval", TimeValue (MicroSeconds (102400)));

  mag1ApDev = wifi.Install (wifiPhy, wifiMac, mag1Net.Get(1));
  
  mag1BrDev = bridge.Install (aps.Get(0), NetDeviceContainer(mag1ApDev, mag1Devs.Get(1)));
  
  iifc = AssignWithoutAddress(mag1Devs.Get(1));
  mag1Ifs.Add(iifc);
  //mag1Ifs.SetRouter(0, true);
  mag1Ifs.SetForwarding (0, true);
  mag1Ifs.SetDefaultRouteInAllNodes (0);
  
  //Setting MAG2
  mag2Devs = csma.Install(mag2Net);
  /**
  * Set the address of this interface
  * \param address address to set
  */
  mag2Devs.Get(0)->SetAddress(magMacAddr);
  
  mag2Ifs = AssignIpv6Address(mag2Devs.Get(0), Ipv6Address("3ffe:1:2::1"), 64);
  
  mag2ApDev = wifi.Install (wifiPhy, wifiMac, mag2Net.Get(1));
  
  mag2BrDev = bridge.Install (aps.Get(1), NetDeviceContainer(mag2ApDev, mag2Devs.Get(1)));
  
  iifc = AssignWithoutAddress(mag2Devs.Get(1));
  mag2Ifs.Add(iifc);
  //mag2Ifs.SetRouter(0, true);
  mag2Ifs.SetForwarding (0, true);
  mag2Ifs.SetDefaultRouteInAllNodes (0);
  
  //setting station
  positionAlloc = CreateObject<ListPositionAllocator> ();
  
  positionAlloc->Add (Vector (-50.0, 60.0, 0.0)); //STA
  
  mobility.SetPositionAllocator (positionAlloc);
  mobility.SetMobilityModel ("ns3::ConstantVelocityMobilityModel");  
  mobility.Install(sta);
  
  Ptr<ConstantVelocityMobilityModel> cvm = sta.Get(0)->GetObject<ConstantVelocityMobilityModel>();
  cvm->SetVelocity(Vector (10.0, 0, 0)); //move to left to right 10.0m/s

  //WLAN interface
  wifiMac.SetType ("ns3::StaWifiMac",
	               "Ssid", SsidValue (ssid),
	               "ActiveProbing", BooleanValue (false));
  staDevs.Add( wifi.Install (wifiPhy, wifiMac, sta));

  iifc = AssignWithoutAddress(staDevs.Get(0));
  staIfs.Add(iifc);
  
  //attach PMIPv6 agents
  Pmip6ProfileHelper *profile = new Pmip6ProfileHelper();

  //adding profile for each station  
  profile->AddProfile(Identifier("pmip1@example.com"), Identifier(Mac48Address::ConvertFrom(staDevs.Get(0)->GetAddress())), backboneIfs.GetAddress(0, 1), std::list<Ipv6Address>());

  Pmip6LmaHelper lmahelper;
  //void Pmip6LmaHelper::SetPrefixPoolBase(Ipv6Address prefixBegin, uint8_t prefixLen)
  lmahelper.SetPrefixPoolBase(Ipv6Address("3ffe:1:4::"), 48);
  //void Pmip6LmaHelper::SetProfileHelper(Pmip6ProfileHelper *pf)
  lmahelper.SetProfileHelper(profile);
  
  lmahelper.Install(lma.Get(0));
  
  Pmip6MagHelper maghelper;

  maghelper.SetProfileHelper(profile);  
  
  maghelper.Install (mags.Get(0), mag1Ifs.GetAddress(0, 0), aps.Get(0));
  maghelper.Install (mags.Get(1), mag2Ifs.GetAddress(0, 0), aps.Get(1));

  
  AsciiTraceHelper ascii;
  csma.EnableAsciiAll (ascii.CreateFileStream ("pmip6-wifi.tr"));
  csma.EnablePcapAll (std::string ("pmip6-wifi"), true);
  
  wifiPhy.EnablePcap ("pmip6-wifi", mag1ApDev.Get(0));
  wifiPhy.EnablePcap ("pmip6-wifi", mag2ApDev.Get(0));
  wifiPhy.EnablePcap ("pmip6-wifi", staDevs.Get(0));
  
  /* Create a Ping6 application to send ICMPv6 echo request from node zero to
   */
  UdpServerHelper udpServer(6000);

  ApplicationContainer apps = udpServer.Install (sta.Get (0));
  apps.Start (Seconds (1.0));
  apps.Stop (Seconds (10.0));

  uint32_t packetSize = 1024;
  uint32_t maxPacketCount = 0xffffffff;
  Time interPacketInterval = MilliSeconds(2);
  UdpClientHelper udpClient(Ipv6Address("3ffe:1:4:1:200:ff:fe00:c"), 6000);
  
  //udpClient.SetIfIndex (outerIfs.GetInterfaceIndex (1));
  udpClient.SetAttribute ("Interval", TimeValue (interPacketInterval));
  udpClient.SetAttribute ("PacketSize", UintegerValue (packetSize));
  udpClient.SetAttribute ("MaxPackets", UintegerValue (maxPacketCount));

  apps = udpClient.Install (cn.Get (0));
  apps.Start (Seconds (1.5));
  apps.Stop (Seconds (10.0));

  FlowMonitorHelper flowmon; 
  Ptr<FlowMonitor> monitor;
  monitor = flowmon.InstallAll();
  monitor->CheckForLostPackets (); 

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

  // Print per flow statistics
  monitor->CheckForLostPackets ();
  Ptr<Ipv6FlowClassifier> classifier = DynamicCast<Ipv6FlowClassifier> (flowmon.GetClassifier ());
  std::map<FlowId, FlowMonitor::FlowStats> stats = monitor->GetFlowStats ();

  for (std::map<FlowId, FlowMonitor::FlowStats>::const_iterator iter = stats.begin (); iter != stats.end (); ++iter)
    {
	  Ipv6FlowClassifier::FiveTuple t = classifier->FindFlow (iter->first);

      if (t.sourceAddress == Ipv6Address("3ffe:2::1") && t.destinationAddress == Ipv6Address("3ffe:1:4:1:200:ff:fe00:c"))
        {
    	  NS_LOG_UNCOND("Flow ID: " << iter->first << " Src Addr " << t.sourceAddress << " Dst Addr " << t.destinationAddress);
    	  NS_LOG_UNCOND("Tx Packets = " << iter->second.txPackets);
    	  NS_LOG_UNCOND("Rx Packets = " << iter->second.rxPackets);
    	  NS_LOG_UNCOND("Throughput: " << iter->second.rxBytes * 8.0 / (iter->second.timeLastRxPacket.GetSeconds()-iter->second.timeFirstTxPacket.GetSeconds()) / 1024  << " Kbps");
        }
    }
  monitor->SerializeToXmlFile("lab-5.flowmon", true, true);
  Simulator::Destroy ();

  return 0;
}

