/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
/*
 * Copyright (c) 2008,2009 IITP RAS
 *
 * 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
 *
 * Author: Kirill Andreev <andreev@iitp.ru>
 *
 *
 * By default this script creates m_xSize * m_ySize square grid topology with
 * IEEE802.11s stack installed at each node with peering management
 * and HWMP protocol.
 * The side of the square cell is defined by m_step parameter.
 * When topology is created, UDP ping is installed to opposite corners
 * by diagonals. packet size of the UDP ping and interval between two
 * successive packets is configurable.
 * 
 *  m_xSize * step
 *  |<--------->|
 *   step
 *  |<--->|
 *  * --- * --- * <---Ping sink  _
 *  | \   |   / |                ^
 *  |   \ | /   |                |
 *  * --- * --- * m_ySize * step |
 *  |   / | \   |                |
 *  | /   |   \ |                |
 *  * --- * --- *                _
 *  ^ Ping source
 *
 *  See also MeshTest::Configure to read more about configurable
 *  parameters.
 */


#include "ns3/core-module.h"
#include "ns3/internet-module.h"
#include "ns3/network-module.h"
#include "ns3/applications-module.h"
#include "ns3/wifi-module.h"
#include "ns3/mesh-module.h"
#include "ns3/mobility-module.h"
#include "ns3/mesh-helper.h"
#include "ns3/mesh-helper.h"
#include "ns3/flow-monitor.h"
#include "ns3/flow-monitor-helper.h"
#include "ns3/ipv4-flow-classifier.h"
#include "ns3/ipv4-global-routing-helper.h"
#include <iostream>
#include <sstream>
#include <fstream>
#include <vector>


using namespace ns3;

NS_LOG_COMPONENT_DEFINE ("TestMeshScript");

class MeshTest
{
public:
  /// Init test
  MeshTest ();
  /// Configure test from command line arguments
  void Configure (int argc, char ** argv);
  /// Run test
  int Run ();
private:
  int       m_xSize;
  int       m_ySize;
  double    m_step;
  double    m_randomStart;
  double    m_totalTime;
  double    m_packetInterval;
  uint16_t  m_packetSize;
  uint32_t  m_nIfaces;
  bool      m_chan;
  bool      m_pcap;
  std::string m_stack;
  std::string m_root;
  /// List of network nodes
  NodeContainer nodes;
  /// List of all mesh point devices
  NetDeviceContainer meshDevices;
  //Addresses of interfaces:
  Ipv4InterfaceContainer interfaces;
  // MeshHelper. Report is not static methods
  MeshHelper mesh;
private:
  /// Create nodes and setup their mobility
  void CreateNodes ();
  /// Install internet m_stack on nodes
  void InstallInternetStack ();
  /// Install applications
  void InstallApplication ();
  /// Print mesh devices diagnostics
  void Report ();
};
MeshTest::MeshTest () :
  m_xSize (3),
  m_ySize (3),
  m_step (50.0),
  m_randomStart (0.1),
  m_totalTime (200.0),
  m_packetInterval (0.1),
  m_packetSize (1024),
  m_nIfaces (2),
  m_chan (true),
  m_pcap (true),
  m_stack ("ns3::Dot11sStack"),
  m_root ("ff:ff:ff:ff:ff:ff")
{
}
void
MeshTest::Configure (int argc, char *argv[])
{
  CommandLine cmd;
  cmd.AddValue ("x-size", "Number of nodes in a row grid. [6]", m_xSize);
  cmd.AddValue ("y-size", "Number of rows in a grid. [6]", m_ySize);
  cmd.AddValue ("step",   "Size of edge in our grid, meters. [100 m]", m_step);
  /*
   * As soon as starting node means that it sends a beacon,
   * simultaneous start is not good.
   */
  cmd.AddValue ("start",  "Maximum random start delay, seconds. [0.1 s]", m_randomStart);
  cmd.AddValue ("time",  "Simulation time, seconds [100 s]", m_totalTime);
  cmd.AddValue ("packet-interval",  "Interval between packets in UDP ping, seconds [0.001 s]", m_packetInterval);
  cmd.AddValue ("packet-size",  "Size of packets in UDP ping", m_packetSize);
  cmd.AddValue ("interfaces", "Number of radio interfaces used by each mesh point. [1]", m_nIfaces);
  cmd.AddValue ("channels",   "Use different frequency channels for different interfaces. [0]", m_chan);
  cmd.AddValue ("pcap",   "Enable PCAP traces on interfaces. [0]", m_pcap);
  cmd.AddValue ("stack",  "Type of protocol stack. ns3::Dot11sStack by default", m_stack);
  cmd.AddValue ("root", "Mac address of root mesh point in HWMP", m_root);

  cmd.Parse (argc, argv);
  NS_LOG_DEBUG ("Grid:" << m_xSize << "*" << m_ySize);
  NS_LOG_DEBUG ("Simulation time: " << m_totalTime << " s");
}
void
MeshTest::CreateNodes ()
{ 
  /*
   * Create m_ySize*m_xSize stations to form a grid topology
   */
  nodes.Create (m_ySize*m_xSize);
  std::cout<<"step: "<<m_step<<std::endl;
  std::cout<<"time: "<<m_totalTime<<std::endl;
  // Configure YansWifiChannel
  YansWifiPhyHelper wifiPhy = YansWifiPhyHelper::Default ();
  YansWifiChannelHelper wifiChannel = YansWifiChannelHelper::Default ();
  wifiPhy.SetChannel (wifiChannel.Create ());
  /*
   * Create mesh helper and set stack installer to it
   * Stack installer creates all needed protocols and install them to
   * mesh point device
   */
  mesh = MeshHelper::Default ();
  if (!Mac48Address (m_root.c_str ()).IsBroadcast ())
    {
      mesh.SetStackInstaller (m_stack, "Root", Mac48AddressValue (Mac48Address (m_root.c_str ())));
    }
  else
    {
      //If root is not set, we do not use "Root" attribute, because it
      //is specified only for 11s
      mesh.SetStackInstaller (m_stack);
    }
  if (m_chan)
    {
      mesh.SetSpreadInterfaceChannels (MeshHelper::SPREAD_CHANNELS);
    }
  else
    {
      mesh.SetSpreadInterfaceChannels (MeshHelper::ZERO_CHANNEL);
    }
  mesh.SetMacType ("RandomStart", TimeValue (Seconds (m_randomStart)));
  // Set number of interfaces - default is single-interface mesh point
  mesh.SetNumberOfInterfaces (m_nIfaces);
  // Install protocols and return container if MeshPointDevices
  meshDevices = mesh.Install (wifiPhy, nodes);
  // Setup mobility - static grid topology
  MobilityHelper mobility;
  mobility.SetPositionAllocator ("ns3::GridPositionAllocator",
                                 "MinX", DoubleValue (0.0),
                                 "MinY", DoubleValue (0.0),
                                 "DeltaX", DoubleValue (m_step),
                                 "DeltaY", DoubleValue (m_step),
                                 "GridWidth", UintegerValue (m_xSize),
                                 "LayoutType", StringValue ("RowFirst"));
  mobility.SetMobilityModel ("ns3::ConstantPositionMobilityModel");
  mobility.Install (nodes);
  if (m_pcap)
    wifiPhy.EnablePcapAll (std::string ("mp-"));
}
void
MeshTest::InstallInternetStack ()
{
  InternetStackHelper internetStack;
  internetStack.Install (nodes);
  Ipv4AddressHelper address;
  address.SetBase ("10.1.1.0", "255.255.255.0");
  interfaces = address.Assign (meshDevices);
}
void
MeshTest::InstallApplication ()
{
  UdpEchoServerHelper echoServer (9);
  ApplicationContainer serverApps = echoServer.Install (nodes.Get (0));
  serverApps.Start (Seconds (0.0));
  serverApps.Stop (Seconds (m_totalTime));
  UdpEchoClientHelper echoClient (interfaces.GetAddress (0), 9);
  echoClient.SetAttribute ("MaxPackets", UintegerValue (2));
  echoClient.SetAttribute ("Interval", TimeValue (Seconds (m_packetInterval)));
  echoClient.SetAttribute ("PacketSize", UintegerValue (m_packetSize));
  ApplicationContainer clientApps = echoClient.Install (nodes.Get (m_xSize*m_ySize-1));
  clientApps.Start (Seconds (0.0));
  clientApps.Stop (Seconds (m_totalTime));
}
int
MeshTest::Run ()
{
  CreateNodes ();
  InstallInternetStack ();
  InstallApplication ();

double m_timeStart,m_timeEnd,m_timeTotals;
  FlowMonitorHelper flowmon;
  Ptr<FlowMonitor> monitor = flowmon.InstallAll();
  m_timeStart=clock();
  Simulator::Schedule (Seconds (m_totalTime), &MeshTest::Report, this);
 //Config::Connect ("/NodeList/*/DeviceList/*/Phy/State/RxOk",MakeCallback  (&PhyRxOkTrace)); 
  Simulator::Stop (Seconds (m_totalTime));
  Simulator::Run ();
   monitor->SerializeToXmlFile("mesh_01_FM.xml", false, false);
  
 std::cout<<"\nX_Size="<<m_xSize<<"\nY_Size="<<m_ySize <<"\nStep="<<m_step<<"\nRandom Start="<<m_randomStart<<"\n";
  std::cout<<"Total Time="<<m_totalTime<<"\nPacket Interval="<<m_packetInterval<<"\nPacket Size="<<m_packetSize<<"\n";
  std::cout<<"Interface="<<m_nIfaces<<"\nRoot="<< m_root<<"\n";

// Define variables to calculate the metrics
  int k=0;
  int totaltxPackets = 0;
  int totalrxPackets = 0;
  int lostPackets=0;
  int txPackets=0;
  double droprate=0,total_droprate=0;
  double totaltxbytes = 0;
  double totalrxbytes = 0;
  double totaldelay = 0;
  double totalrxbitrate = 0;
  double difftx, diffrx;
  double pdf_value, rxbitrate_value, txbitrate_value, delay_value;
  double pdf_total, rxbitrate_total, delay_total;
  int packets_per_s=0,total_packets_per_s=0;
  int no_flows=0;

 
  //Print per flow statistics
   monitor->CheckForLostPackets ();
   Ptr<Ipv4FlowClassifier> classifier = DynamicCast<Ipv4FlowClassifier>(flowmon.GetClassifier ());
   std::map<FlowId, FlowMonitor::FlowStats> stats = monitor->GetFlowStats ();
   for (std::map<FlowId, FlowMonitor::FlowStats>::const_iterator i = stats.begin (); i != stats.end (); ++i)
   {
        Ipv4FlowClassifier::FiveTuple t = classifier->FindFlow (i->first);
        difftx = i->second.timeLastTxPacket.GetSeconds() -
        i->second.timeFirstTxPacket.GetSeconds();
        diffrx = i->second.timeLastRxPacket.GetSeconds() -
        i->second.timeFirstRxPacket.GetSeconds();
        pdf_value = (double) i->second.rxPackets / (double) i->second.txPackets * 100;
        txbitrate_value = (double) i->second.txBytes * 8 / 1024 / difftx;
        packets_per_s=(int)i->second.txPackets/difftx;
        if (i->second.rxPackets != 0){
                rxbitrate_value = (double)i->second.rxPackets * m_packetSize * 8 /1024 / diffrx;
                delay_value = (double) i->second.delaySum.GetSeconds() /(double) i->second.rxPackets;
        }
        else{
                rxbitrate_value = 0;
                delay_value = 0;
        }
       // We are only interested in the metrics of the data flows
        if ((!t.destinationAddress.IsSubnetDirectedBroadcast("255.255.255.0")))
        {
                k++;
        // Plot the statistics for each data flow
               lostPackets=i->second.lostPackets;
               txPackets=i->second.txPackets;
            
                std::cout << "\nFlow " << k << " (" << t.sourceAddress << " -> "<< t.destinationAddress << ")\n";
                std::cout << "Tx Packets: " << i->second.txPackets << "\n";
                std::cout << "Rx Packets: " << i->second.rxPackets << "\n";
                std::cout << "Lost Packets: " << i->second.lostPackets << "\n";
                droprate=((double)lostPackets*100)/(double)txPackets;
                std::cout<< "Packets drop rate: "<<droprate<<"\n"; 
               
                std::cout << "PDF: " << pdf_value << " %\n";
                std::cout << "Average delay: " << delay_value << "s\n";
               
                std::cout << "Rx bitrate (Throughput): " << rxbitrate_value << " kbps\n";
                std::cout << "Tx bitrate: " << txbitrate_value << " kbps\n\n";
                std::cout << "Packets transmitted per sec: " << packets_per_s << "\n\n";
              
                // Acumulate for average statistics
                totaltxPackets += i->second.txPackets;
                totaltxbytes += i->second.txBytes;
                totalrxPackets += i->second.rxPackets;
                totaldelay += i->second.delaySum.GetSeconds();
                totalrxbitrate += rxbitrate_value;
                totalrxbytes += i->second.rxBytes;
                total_packets_per_s+=packets_per_s;
                total_droprate+=(double)droprate/100;
           
        }
        no_flows++;
   }
   // Average all nodes statistics
  if (totaltxPackets != 0){
        pdf_total = (double) totalrxPackets / (double) totaltxPackets * 100;
   }
  else{
        pdf_total = 0;
   }
  if (totalrxPackets != 0){
        rxbitrate_total = totalrxbitrate;
        delay_total = (double) totaldelay / (double) totalrxPackets;
   }
  else{
      rxbitrate_total = 0;
      delay_total = 0;
 }
  //print all nodes statistics
  std::cout << "\nTotal PDF: " << pdf_total << " %\n";
  std::cout << "Total Rx bitrate i.e Throughput: " << rxbitrate_total << " kbps\n";
  std::cout << "Total Delay: " << delay_total << " s\n";
  std::cout << "Average Packets transmitted per second: " << (int)( total_packets_per_s/no_flows) << "packets/s\n";
  std::cout <<"Average Packet drop rate is: "<<(double)((total_droprate/no_flows)*100)<<" % \n\n";
  
 //print all nodes statistics in files
  std::ostringstream os;
  os << "1_HWMP_PDF_r-"<<m_packetInterval<<".txt";
  std::ofstream of (os.str().c_str(), std::ios::out | std::ios::app);
  of << pdf_total << "\n";
  of.close ();
  std::ostringstream os2;
  os2 << "1_HWMP_Delay_r-"<<m_packetInterval<<".txt";
  std::ofstream of2 (os2.str().c_str(), std::ios::out | std::ios::app);
  of2 << delay_total << "\n";
  of2.close ();
  std::ostringstream os3;
  os3 << "1_HWMP_Throu_r-"<<m_packetInterval<<".txt";
  std::ofstream of3 (os3.str().c_str(), std::ios::out | std::ios::app);
  of3 << rxbitrate_total << "\n";
  of3.close ();
 // Flow Monitor Export trial Serializing Flow Monitor output to XML.")
 
 
  Simulator::Destroy ();
  m_timeEnd=clock();
  m_timeTotals=(m_timeEnd - m_timeStart)/(double) CLOCKS_PER_SEC;
  std::cout << "\n*** Simulation time: " << m_timeTotals << "s\n\n";
  return 0;
}
void
MeshTest::Report ()
{
  unsigned n (0);
  for (NetDeviceContainer::Iterator i = meshDevices.Begin (); i != meshDevices.End (); ++i, ++n)
    {
      std::ostringstream os;
      os << "mp-report-" << n << ".xml";
      std::cerr << "Printing mesh point device #" << n << " diagnostics to " << os.str () << "\n";
      std::ofstream of;
      of.open (os.str ().c_str ());
      if (!of.is_open ())
        {
          std::cerr << "Error: Can't open file " << os.str () << "\n";
          return;
        }
      mesh.Report (*i, of);
      of.close ();
    }
}
int
main (int argc, char *argv[])
{
  MeshTest t; 
  t.Configure (argc, argv);
  return t.Run ();
}
