/* -*-  Mode: C++; c-file-style: "gnu"; indent-tabs-mode:nil; -*- */
/*
 * Copyright (c) 2011-2018 Centre Tecnologic de Telecomunicacions de Catalunya (CTTC)
 *
 * 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/point-to-point-module.h"
#include "ns3/internet-module.h"
#include "ns3/applications-module.h"
#include "ns3/mobility-module.h"
#include "ns3/config-store-module.h"
#include "ns3/lte-module.h"
#include "ns3/flow-monitor-module.h"
#include "ns3/core-module.h"
#include "ns3/network-module.h"
#include "ns3/mobility-module.h"
#include "ns3/config-store.h"
#include <ns3/buildings-helper.h>
#include "ns3/log.h"
#include <ns3/buildings-module.h>
#include "ns3/network-module.h"
#include "ns3/ipv4-global-routing-helper.h"
#include "ns3/internet-module.h"
#include "ns3/eps-bearer-tag.h"
#include "ns3/point-to-point-helper.h"
#include "ns3/flow-monitor-module.h"
#include "ns3/applications-module.h"
#include "ns3/flow-monitor-helper.h"
#include "ns3/internet-apps-module.h"
#include "ns3/epc-helper.h"
 #include "ns3/random-variable-stream.h"
//#include "ns3/gtk-config-store.h"

using namespace ns3;
struct info {
  uint16_t rnti; uint16_t cellId; double rsrp; double rsrq; bool servingCell; uint8_t carrier;
};


uint16_t counterN310FirsteNB = 0;
Time t310StartTimeFirstEnb = Seconds (0);
std::vector<info> info_vector;

void
PrintUePosition (uint64_t imsi)
{

  for (NodeList::Iterator it = NodeList::Begin (); it != NodeList::End (); ++it)
    {
      Ptr<Node> node = *it;
      int nDevs = node->GetNDevices ();
      for (int j = 0; j < nDevs; j++)
        {
          Ptr<LteUeNetDevice> uedev = node->GetDevice (j)->GetObject <LteUeNetDevice> ();
          if (uedev)
            {
              if (imsi == uedev->GetImsi ())
                {
                  Vector pos = node->GetObject<MobilityModel> ()->GetPosition ();
                  std::cout << "IMSI : " << uedev->GetImsi () << " at " << pos.x << "," << pos.y << std::endl;
                }
            }
        }
    }
}

/// Map each of UE RRC states to its string representation.
static const std::string g_ueRrcStateName[LteUeRrc::NUM_STATES] =
{
  "IDLE_START",
  "IDLE_CELL_SEARCH",
  "IDLE_WAIT_MIB_SIB1",
  "IDLE_WAIT_MIB",
  "IDLE_WAIT_SIB1",
  "IDLE_CAMPED_NORMALLY",
  "IDLE_WAIT_SIB2",
  "IDLE_RANDOM_ACCESS",
  "IDLE_CONNECTING",
  "CONNECTED_NORMALLY",
  "CONNECTED_HANDOVER",
  "CONNECTED_PHY_PROBLEM",
  "CONNECTED_REESTABLISHING"
};

/**
 * \param s The UE RRC state.
 * \return The string representation of the given state.
 */
static const std::string & ToString (LteUeRrc::State s)
{
  return g_ueRrcStateName[s];
}

void
UeStateTransition (uint64_t imsi, uint16_t cellId, uint16_t rnti, LteUeRrc::State oldState, LteUeRrc::State newState)
{
  std::cout << Simulator::Now ().GetSeconds ()
            << " UE with IMSI " << imsi << " RNTI " << rnti << " connected to cell " << cellId <<
  " transitions from " << ToString (oldState) << " to " << ToString (newState) << std::endl;
}

void
EnbRrcTimeout (uint64_t imsi, uint16_t rnti, uint16_t cellId, std::string cause)
{
  std::cout << Simulator::Now ().GetSeconds ()
            << " IMSI " << imsi << ", RNTI " << rnti << ", Cell id " << cellId
            << ", ENB RRC " << cause << std::endl;
}

void
NotifyConnectionReleaseAtEnodeB (uint64_t imsi, uint16_t cellId, uint16_t rnti)
{
  std::cout << Simulator::Now ()
            << " IMSI " << imsi << ", RNTI " << rnti << ", Cell id " << cellId
            << ", UE context destroyed at eNodeB" << std::endl;
}

void PhySyncDetection (uint16_t n310, uint64_t imsi, uint16_t rnti, uint16_t cellId, std::string type, uint8_t count)
{
  std::cout << Simulator::Now ().GetSeconds ()
            << " IMSI " << imsi << ", RNTI " << rnti
            << ", Cell id " << cellId << ", " << type << ", no of sync indications: " << +count
            << std::endl;

  if (type == "Notify out of sync" && cellId == 1)
    {
      ++counterN310FirsteNB;
      if (counterN310FirsteNB == n310)
        {
          t310StartTimeFirstEnb = Simulator::Now ();
        }
      // NS_LOG_DEBUG ("counterN310FirsteNB = " << counterN310FirsteNB);
    }
}

void RadioLinkFailure (Time t310, uint64_t imsi, uint16_t cellId, uint16_t rnti)
{
  std::cout << Simulator::Now ()
            << " IMSI " << imsi << ", RNTI " << rnti
            << ", Cell id " << cellId << ", radio link failure detected"
            << std::endl << std::endl;

  PrintUePosition (imsi);

  if (cellId == 1)
    {
      NS_ABORT_MSG_IF ((Simulator::Now () - t310StartTimeFirstEnb) != t310, "T310 timer expired at wrong time");
    }
}

void
NotifyRandomAccessErrorUe (uint64_t imsi, uint16_t cellId, uint16_t rnti)
{
  std::cout << Simulator::Now ().GetSeconds ()
            << " IMSI " << imsi << ", RNTI " << rnti << ", Cell id " << cellId
            << ", UE RRC Random access Failed" << std::endl;
}

void
NotifyConnectionTimeoutUe (uint64_t imsi, uint16_t cellId, uint16_t rnti,
                           uint8_t connEstFailCount)
{
  std::cout << Simulator::Now ().GetSeconds ()
            << " IMSI " << imsi << ", RNTI " << rnti
            << ", Cell id " << cellId
            << ", T300 expiration counter " << (uint16_t) connEstFailCount
            << ", UE RRC Connection timeout" << std::endl;
}

void
NotifyRaResponseTimeoutUe (uint64_t imsi, bool contention,
                           uint8_t preambleTxCounter,
                           uint8_t maxPreambleTxLimit)
{
  std::cout << Simulator::Now ().GetSeconds ()
            << " IMSI " << imsi << ", Contention flag " << contention
            << ", preamble Tx Counter " << (uint16_t) preambleTxCounter
            << ", Max Preamble Tx Limit " << (uint16_t) maxPreambleTxLimit
            << ", UE RA response timeout" << std::endl;
  NS_FATAL_ERROR ("NotifyRaResponseTimeoutUe");
}

void 
ReportUeMeasurementsCallbackDetail (Ptr<OutputStreamWrapper> stream,std::string path, uint16_t rnti, uint16_t cellId, double rsrp, double rsrq, bool servingCell, uint8_t carrier){
    std::cout << "Ue Measurements"<< std::endl;
    std::cout << path <<std::endl;
    std::cout << "cellid:" << cellId<< std::endl;
    std::cout << "servingCell:" << servingCell << std::endl;
    std::cout << "carrier:" << carrier << std::endl;
    std::cout << "rnti:" << rnti<< std::endl;
    std::cout << "rsrp:" << rsrp<< std::endl;
    std::cout << "rsrq:" << rsrq<< std::endl; 
    // std::cout << "Time:" << Simulator::Now().GetSeconds()<<std::endl; 
    
    // info actual_info;
    // actual_info.rnti = rnti;

    // info_vector.push_back(actual_info);

    // std::ofstream fout;
    // fout.open("Ue_Measurements.txt");
     *stream->GetStream() <<Simulator::Now()<< cellId << "\t"<< servingCell<< "\t"<< rnti << "\t"<< rsrp << "\t"<< rsrq << std::endl;


  //   for (uint16_t l = 0; l < info_vector.size(); l++){
  //   fout << info_vector.at(l).cellId  << "\t";
  //   fout << info_vector.at(l).servingCell  << "\t";
  //   fout << info_vector.at(l).rnti  << "\t";
  //   fout << info_vector.at(l).rsrp  << "\t";
  //   fout << info_vector.at(l).rsrq  << std::endl;
  // }

  //   fout.close();
   }
  // }

void
NotifyUeReport (Ptr<OutputStreamWrapper> stream, std::string context, uint16_t cellid, uint16_t rnti, double rsrp , double avsinr, uint8_t componentCarrier)
{
     std::cout << " eNB CellId " << cellid << std::endl
                << "rnti:" << rnti<< std::endl
                << " RSRP " << rsrp << std::endl
                << " SINR " << 10*log(avsinr) / log(10) << std::endl
                <<  "Time:" << Simulator::Now().GetSeconds()
                << std::endl;


    int nth=3;  //looking for the second ocurrence of "/"
    int cnt=0; 
    size_t pos=0;

    while( cnt != nth )
    {
        pos = context.find("/", pos);
        if ( pos == std::string::npos )
          std::cout << nth << "th ocurrence not found!"<< std::endl;
        pos+=1;
        cnt++;
    }

    // std::cout << "test" <<context<< std::endl;


    //manipulate the string to get the node ID
    std::string str1;
    str1 = context.substr(10,pos-11);
    int ue= atoi(str1.c_str());



    //Get the UE position from the NodeList
    Ptr<MobilityModel> m1 = ns3::NodeList::GetNode(ue)->GetObject<MobilityModel>();
    

//    Get the eNB position from the eNB
     Ptr<MobilityModel> m2 = ns3::NodeList::GetNode(0)->GetObject<MobilityModel>();
   

    // //print UE and eNB location
    Vector v1 = m1->GetPosition();
 
    Vector v2 = Vector (25, 25 , 7);

    std::cout << Simulator::Now().GetSeconds()<<std::endl; 
    // std::cout <<"\tUE:" << rnti << " - (" << v1.x << ", " << v1.y << "," <<v1.z <<")"<<std::endl;
    // std::cout << " \teNB:" << 1<< " - (" << v2.x << ", " << v2.y <<"," <<v2.z << ")"<<std::endl;

    //Print the distante between UE and eNB
    // double distance = m1->GetDistanceFrom (m2);
    Vector distance = v2-v1;
    // std::cout << " Distance : " << distance << std::endl;
     *stream->GetStream() << Simulator::Now().GetSeconds() <<"\t"<<rnti<<"\t"<<rsrp<< "\t" << 10*log(avsinr) / log(10) << "\t"<<v1<<"\t"<<distance<<std::endl;
}


void NotifyConnectionEstablishedEnb (Ptr<OutputStreamWrapper> stream,std::string context, uint64_t imsi, uint16_t cellid, uint16_t rnti)
{
  std::cout << Simulator::Now ().GetSeconds () << " "
            << " eNB CellId " << cellid
            << ": successful connection of UE with IMSI " << imsi
            << " RNTI " << rnti
            << std::endl;

       *stream->GetStream() << Simulator::Now().GetSeconds() <<"\t"<<cellid<<"\t"<<rnti<< "\t" << imsi << std::endl;
    // std::ofstream fout;
    // fout.open("Ue_imsi.txt");
    // fout<<cellid <<"\t";
    // fout<<imsi<<"\t";
    // fout<<rnti<<"\t" << std::endl;

    // fout.close();


            // << "Message" << msg.measResults.rsrpResult
}



NS_LOG_COMPONENT_DEFINE ("RelayingSimulator");

int
main (int argc, char *argv[])
{
  double simTime = 10;
    uint16_t n310 = 1;
  Time t310 = Seconds (1);
   uint16_t n311 = 1;
  // bool useCa = false;
   
 
  CommandLine cmd;
  cmd.AddValue ("simTime", "Total duration of the simulation", simTime);
  // cmd.AddValue ("useCa", "Whether to use carrier aggregation.", useCa);
  cmd.Parse (argc, argv);
  // Command line arguments

  ConfigStore inputConfig;
  inputConfig.ConfigureDefaults ();

  // parse again so you can override default values from the command line
  cmd.Parse(argc, argv);

  Config::SetDefault ("ns3::LteHelper::Scheduler", StringValue("ns3::PfFfMacScheduler"));
  Config::SetDefault ("ns3::LteHelper::PathlossModel", StringValue( "ns3::Cost231PropagationLossModel"));
  // Config::SetDefault ("ns3::LteHelper::PathlossModel", StringValue( "ns3::BuildingsPropagationLossModel"));
  // Config::SetDefault ("ns3::MmWave3gppPropagationLossModel::Frequency", DoubleValue(6e9));
  //   Config::SetDefault ("ns3::MmWave3gppPropagationLossModel::Shadowing", BooleanValue(true));
  // Config::SetDefault ("ns3::MmWave3gppPropagationLossModel::ChannelCondition", StringValue("a"));
  // Config::SetDefault ("ns3::MmWave3gppPropagationLossModel::Scenario", StringValue("InF-Factory"));
  Config::SetDefault ("ns3::LteEnbNetDevice::UlBandwidth", UintegerValue(100));
  Config::SetDefault ("ns3::LteEnbNetDevice::DlBandwidth", UintegerValue(100));
  Config::SetDefault ("ns3::LteEnbNetDevice::DlEarfcn", UintegerValue( 6250));
  Config::SetDefault ("ns3::LteEnbNetDevice::UlEarfcn", UintegerValue(24250));
  Config::SetDefault ("ns3::LteUePhy::TxPower", DoubleValue(20)); //0.1 W =20
  Config::SetDefault ("ns3::LteUePhy::NoiseFigure", DoubleValue(9));
  Config::SetDefault ("ns3::LteEnbPhy::TxPower", DoubleValue(27)); // 0.5 W =26
  Config::SetDefault ("ns3::LteEnbPhy::NoiseFigure", DoubleValue(5)); 
  Config::SetDefault ("ns3::LteAmc::AmcModel", EnumValue (LteAmc::PiroEW2010));
  Config::SetDefault ("ns3::LteAmc::Ber", DoubleValue (0.01));
  Config::SetDefault ("ns3::PfFfMacScheduler::HarqEnabled", BooleanValue (true));
  // Config::SetDefault ("ns3::UrbanMacroCellPropagationLossModel::LosEnabled", BooleanValue (true));
  // Config::SetDefault ("ns3::Hybrid3gppPropagationLossModel::ShadowingEnabled", BooleanValue (false));
  // Config::SetDefault ("ns3::Hybrid3gppPropagationLossModel::CacheLoss", BooleanValue (false));

    //Radio link failure detection parameters
  Config::SetDefault ("ns3::LteUeRrc::N310", UintegerValue (n310));
  Config::SetDefault ("ns3::LteUeRrc::N311", UintegerValue (n311));
  Config::SetDefault ("ns3::LteUeRrc::T310", TimeValue (t310));
  // Config::SetDefault ("ns3::LteEnbRrc::EpsBearerToRlcMapping", EnumValue (LteEnbRrc::RLC_AM_ALWAYS) );


  // if (useCa)
  //  {
  //    Config::SetDefault ("ns3::LteHelper::UseCa", BooleanValue (useCa));
  //    Config::SetDefault ("ns3::LteHelper::NumberOfComponentCarriers", UintegerValue (2));
  //    Config::SetDefault ("ns3::LteHelper::EnbComponentCarrierManager", StringValue ("ns3::RrComponentCarrierManager"));
  //  }

  Ptr<LteHelper> lteHelper = CreateObject<LteHelper> ();
  Ptr<PointToPointEpcHelper> epcHelper = CreateObject<PointToPointEpcHelper> ();
  lteHelper->SetEpcHelper (epcHelper);
  lteHelper->SetSpectrumChannelType ("ns3::MultiModelSpectrumChannel");

  lteHelper->SetFadingModel("ns3::TraceFadingLossModel");
  std::ifstream ifTraceFile;
  ifTraceFile.open ("../../src/lte/model/fading-traces/fading_trace_ETU_3kmph.fad", std::ifstream::in);
  if (ifTraceFile.good ())
  {
    // script launched by test.py
    lteHelper->SetFadingModelAttribute ("TraceFilename", StringValue ("../../src/lte/model/fading-traces/fading_trace_ETU_3kmph.fad"));
  }
  else
  {
    // script launched as an example
    lteHelper->SetFadingModelAttribute ("TraceFilename", StringValue ("src/lte/model/fading-traces/fading_trace_ETU_3kmph.fad"));
    NS_LOG_UNCOND ("Trace file load fail");
  }

    // lteHelper->SetAttribute ("PathlossModel", StringValue ("ns3::Cost231PropagationLossModel"));
    // Set pathloss model
  // lteHelper->SetAttribute ("PathlossModel", StringValue ("ns3::Hybrid3gppPropagationLossModel"));
  //   lteHelper->SetAttribute ("PathlossModel", StringValue ("ns3::MmWave3gppPropagationLossModel"));
  // lteHelper->SetAttribute ("ChannelModel", StringValue ("ns3::MmWave3gppChannel"));

   //Create nodes
    NodeContainer eNbNodes;
    NodeContainer ueNodes;
    NodeContainer ueNodesMobile;
    NodeContainer ueNodesStatic;
    eNbNodes.Create (1);
    // ueNodes.Create (ueNum);
    uint16_t ueNumMobile = 20;
    uint16_t ueNumStatic = 80;

    ueNodesMobile.Create (ueNumMobile);
    ueNodesStatic.Create (ueNumStatic);
    ueNodes.Add(ueNodesMobile);
    ueNodes.Add(ueNodesStatic);

   double ueX = 1.00, ueY = 0.00;

    //Positioning the nodes 
  Ptr<ListPositionAllocator> eNbpositionAlloc = CreateObject<ListPositionAllocator> ();

      eNbpositionAlloc->Add (Vector (25, 25 , 7.5));
  
  // Ptr<ListPositionAllocator> UEpositionAlloc = CreateObject<ListPositionAllocator> ();
  //   for (uint16_t i = 0; i < ueNum; i++)
  //   {
  //     UEpositionAlloc->Add (Vector (distance * i, 0, 1));
  //   }
  //Setup the mobility and position of the nodes 
  MobilityHelper mobility;
  mobility.SetMobilityModel ("ns3::ConstantPositionMobilityModel");
  mobility.SetPositionAllocator(eNbpositionAlloc);
  mobility.Install (eNbNodes);
  // mobility.SetPositionAllocator(UEpositionAlloc);
  // mobility.Install (ueNodes);
   SeedManager::SetSeed(30);
   SeedManager::SetRun(2);
   Ptr<UniformRandomVariable> ueRandomVarX = CreateObject<UniformRandomVariable>();
 // Ptr<RandomBoxPositionAllocator> ueRandomRectPosAlloc = CreateObject<RandomBoxPositionAllocator> ();
   ueRandomVarX->SetAttribute ("Min", DoubleValue (0));
   ueRandomVarX->SetAttribute ("Max", DoubleValue (50));
   // ueRandomRectPosAlloc->SetX(ueRandomVarX);
   Ptr<UniformRandomVariable> ueRandomVarY = CreateObject<UniformRandomVariable>();
   ueRandomVarY->SetAttribute ("Min", DoubleValue (1));
   ueRandomVarY->SetAttribute ("Max", DoubleValue (50));
   // // ueRandomRectPosAlloc->SetY(ueRandomVarY);
   Ptr<ConstantRandomVariable> ueRandomVarZ = CreateObject<ConstantRandomVariable>();
   ueRandomVarZ->SetAttribute("Constant", DoubleValue(0.3));
   // ueRandomRectPosAlloc->SetZ(ueRandomVarZ);
   Ptr<ListPositionAllocator> posAllocUeMobile = CreateObject<ListPositionAllocator>();

 for (uint8_t j = 0; j < ueNumMobile; j++)
   {
       for (uint8_t i = 0; i < 10; i++)
       {
         posAllocUeMobile->Add( Vector(ueX*i,ueY*j,0.3) );
   }
 }
   Ptr<RandomBoxPositionAllocator> wpAllocUeMobile = CreateObject<RandomBoxPositionAllocator>();
   wpAllocUeMobile->SetX(ueRandomVarX);    wpAllocUeMobile->SetX(ueRandomVarY);    wpAllocUeMobile->SetZ(ueRandomVarZ);
 // Ptr<ListPositionAllocator> posAllocUeMobile = CreateObject<ListPositionAllocator>();

   MobilityHelper mobilityHelperUeMobile;
   mobilityHelperUeMobile.SetMobilityModel( "ns3::RandomWaypointMobilityModel");
    mobilityHelperUeMobile.SetMobilityModel ("ns3::RandomWalk2dMobilityModel",
                            "Mode", StringValue ("Time"),
                            "Time", StringValue ("1s"),
                            "Speed", StringValue ("ns3::ConstantRandomVariable[Constant=3.0]"),
                            "Bounds", StringValue ("0|50|0|50"));
   //
   //     "Speed", StringValue("ns3::ConstantRandomVariable[Constant=16.67]"),    // in 'm/s'
   //     "Pause", StringValue("ns3::ConstantRandomVariable[Constant=0.00]"),
   //     "PositionAllocator", PointerValue(wpAllocUeMobile) );
   mobilityHelperUeMobile.SetPositionAllocator(wpAllocUeMobile);
   mobilityHelperUeMobile.Install( ueNodesMobile );
//    MobilityHelper mobilityHelperUeMobile;
//    mobilityHelperUeMobile.SetMobilityModel ("ns3::ConstantPositionMobilityModel");
//    mobilityHelperUeMobile.SetPositionAllocator (posAllocUeMobile);
//    mobilityHelperUeMobile.Install (ueNodesMobile);
// //
   Ptr<ListPositionAllocator> posAllocUeStatic = CreateObject<ListPositionAllocator> ();

   for (uint8_t j = 0; j < 8; j++)
     {
       for (uint8_t i = 0; i < 10; i++)
         {
           posAllocUeStatic->Add(Vector( i*5, j*6, 1));
         }
     }
   MobilityHelper mobilityHelperUeStatic;
   mobilityHelperUeStatic.SetMobilityModel ( "ns3::RandomWaypointMobilityModel");
    mobilityHelperUeStatic.SetMobilityModel ("ns3::RandomWalk2dMobilityModel",
                            "Mode", StringValue ("Time"),
                            "Time", StringValue ("1s"),
                            "Speed", StringValue ("ns3::ConstantRandomVariable[Constant=0.1]"),
                            "Bounds", StringValue ("0|50|0|50"));

   mobilityHelperUeStatic.SetPositionAllocator (posAllocUeStatic);
   mobilityHelperUeStatic.Install (ueNodesStatic);




  //Get the position of the nodes 
  // for (NodeContainer::Iterator j = ueNodes.Begin ();j != ueNodes.End (); ++j)
  //   {
  //     Ptr<Node> object = *j;
  //     Ptr<MobilityModel> position = object->GetObject<MobilityModel> ();
  //     NS_ASSERT (position != 0);
  //     Vector pos = position->GetPosition ();
  //     std::cout << "x=" << pos.x << ", y=" << pos.y << ", z=" << pos.z << std::endl;
  //   }

    for (NodeContainer::Iterator j = eNbNodes.Begin ();j != eNbNodes.End (); ++j)
    {
      Ptr<Node> object = *j;
      Ptr<MobilityModel> position = object->GetObject<MobilityModel> ();
      NS_ASSERT (position != 0);
      Vector pos = position->GetPosition ();
      std::cout << "x=" << pos.x << ", y=" << pos.y << ", z=" << pos.z << std::endl;
    }

      // Create Devices and install them in the Nodes (eNB and UE)
  NetDeviceContainer enbDevs;
  NetDeviceContainer ueDevs;
  // Default scheduler is PF, uncomment to use RR
  //lteHelper->SetSchedulerType ("ns3::RrFfMacScheduler");

  enbDevs = lteHelper->InstallEnbDevice (eNbNodes);
  ueDevs = lteHelper->InstallUeDevice (ueNodes);

   // Create a single RemoteHost
  Ptr<Node> pgw = epcHelper->GetPgwNode ();
  NodeContainer remoteHostContainer;
  remoteHostContainer.Create (1);
  Ptr<Node> remoteHost = remoteHostContainer.Get (0);
  InternetStackHelper internet;
  internet.Install (remoteHostContainer);

  // Create the Internet
  PointToPointHelper p2ph;
  p2ph.SetDeviceAttribute ("DataRate", DataRateValue (DataRate ("100Gb/s")));
  p2ph.SetDeviceAttribute ("Mtu", UintegerValue (1500));
  p2ph.SetChannelAttribute ("Delay", TimeValue (MilliSeconds (0.000)));
  NetDeviceContainer internetDevices = p2ph.Install (pgw, remoteHost);
  Ipv4AddressHelper ipv4h;
  ipv4h.SetBase ("1.0.0.0", "255.0.0.0");
  Ipv4InterfaceContainer internetIpIfaces = ipv4h.Assign (internetDevices);
  // interface 0 is localhost, 1 is the p2p device
  Ipv4Address remoteHostAddr = internetIpIfaces.GetAddress (1);

  Ipv4StaticRoutingHelper ipv4RoutingHelper;
  Ptr<Ipv4StaticRouting> remoteHostStaticRouting = ipv4RoutingHelper.GetStaticRouting (remoteHost->GetObject<Ipv4> ());
  remoteHostStaticRouting->AddNetworkRouteTo (Ipv4Address ("7.0.0.0"), Ipv4Mask ("255.0.0.0"), 1);
  internet.Install (ueNodes);
  Ipv4InterfaceContainer ueIpIface;
  ueIpIface = epcHelper->AssignUeIpv4Address (NetDeviceContainer (ueDevs));
  
    // Set the default gateway for the UEs
    for (uint32_t j = 0; j < ueNodes.GetN(); ++j)
    {
        Ptr<Ipv4StaticRouting> ueStaticRouting = ipv4RoutingHelper.GetStaticRouting (ueNodes.Get(j)->GetObject<Ipv4> ());
        ueStaticRouting->SetDefaultRoute (epcHelper->GetUeDefaultGatewayAddress (), 1);
      
    } 
      //Printing IMSI, IP address and Position 
     for (uint32_t u = 0; u < ueNodes.GetN(); ++u)
    {
      Ptr<MobilityModel> m1 = ueNodes.Get(u)->GetObject<MobilityModel>();
      std::cout<<"IP Address: "<<ueNodes.Get(u)->GetObject<Ipv4>()->GetAddress(1,0).GetLocal() <<"IMSI: "
      << ueNodes.Get(u)->GetDevice(0)->GetObject <LteUeNetDevice>()->GetImsi() << "POS:"<< m1->GetPosition() <<std::endl;
      
    } 

    std::cout<<remoteHostAddr<<std::endl;

  // Attach a UE to a eNB
  lteHelper->Attach (ueDevs, enbDevs.Get(0));

  Ptr<FlowMonitor> flowMonitor;
  FlowMonitorHelper flowHelper;
  flowMonitor = flowHelper.InstallAll();

  Ptr<ExponentialRandomVariable> emergencyIntervalSeconds = CreateObject<ExponentialRandomVariable> ();
  emergencyIntervalSeconds->SetAttribute("Mean", DoubleValue(simTime));     // To be defined
  emergencyIntervalSeconds->SetAttribute("Bound", DoubleValue(simTime));
  // Install and start applications on UEs and remote host
  uint16_t dlPort = 1234;
  uint16_t ulPort = 2000;
  uint16_t otherPort = 3000;
  double interPacketInterval = 25;
  uint16_t packetSize =250 ; //bytes 
  ApplicationContainer clientApps;
  ApplicationContainer serverApps;
  for (uint32_t u = 0; u < ueNodes.GetN (); ++u)
    {
      ++ulPort;
      ++otherPort;
      PacketSinkHelper dlPacketSinkHelper ("ns3::UdpSocketFactory", InetSocketAddress (Ipv4Address::GetAny (), dlPort));
      PacketSinkHelper ulPacketSinkHelper ("ns3::UdpSocketFactory", InetSocketAddress (Ipv4Address::GetAny (), ulPort));
      PacketSinkHelper packetSinkHelper ("ns3::UdpSocketFactory", InetSocketAddress (Ipv4Address::GetAny (), otherPort));
      serverApps.Add (dlPacketSinkHelper.Install (ueNodes.Get(u)));
      serverApps.Add (ulPacketSinkHelper.Install (remoteHost));
      serverApps.Add (packetSinkHelper.Install (ueNodes.Get(u)));
      // serverApps.Add (packetSinkHelper.Install (ueNodes.Get(u)));

      UdpClientHelper dlClient (ueIpIface.GetAddress (u), dlPort);
      dlClient.SetAttribute ("Interval", TimeValue (MilliSeconds(interPacketInterval)));
      dlClient.SetAttribute("PacketSize", UintegerValue(packetSize));
      dlClient.SetAttribute ("MaxPackets", UintegerValue(1000000));

      UdpClientHelper ulClient (remoteHostAddr, ulPort);
      ulClient.SetAttribute ("Interval", TimeValue (MilliSeconds(interPacketInterval)));
      ulClient.SetAttribute("PacketSize", UintegerValue(packetSize));
      ulClient.SetAttribute ("MaxPackets", UintegerValue(1000000));

      UdpClientHelper client (ueIpIface.GetAddress (u), otherPort);
      client.SetAttribute ("Interval", TimeValue (Seconds(emergencyIntervalSeconds->GetValue())));
      client.SetAttribute ("MaxPackets", UintegerValue(1000000));
      client.SetAttribute("PacketSize", UintegerValue(packetSize));

      clientApps.Add (dlClient.Install (remoteHost));
      clientApps.Add (ulClient.Install (ueNodes.Get(u)));
      clientApps.Add (client.Install (remoteHost));
      // if (u+1 < ueNodes.GetN ())
      //   {
      //     clientApps.Add (client.Install (ueNodes.Get(u+1)));
      //   }
      // else
      //   {
      //     clientApps.Add (client.Install (ueNodes.Get(0)));
      //   }
    }
  serverApps.Start (Seconds (1));
  clientApps.Start (Seconds (1));
  serverApps.Stop(Seconds (simTime));
  clientApps.Stop(Seconds (simTime));
  AsciiTraceHelper asciiTraceHelper;
  Ptr<OutputStreamWrapper> stream = asciiTraceHelper.CreateFileStream ("ueReport.txt");

  Ptr<OutputStreamWrapper> stream1 = asciiTraceHelper.CreateFileStream ("Ueimsi.txt");
  Ptr<OutputStreamWrapper> stream2 = asciiTraceHelper.CreateFileStream ("Ue_Measurement.txt");
  // std::cout<<Simulator::Now().GetSeconds()<<std::endl;
  Config::Connect ("/NodeList/*/DeviceList/*/$ns3::LteUeNetDevice/ComponentCarrierMapUe/*/LteUePhy/ReportCurrentCellRsrpSinr",  MakeBoundCallback (&NotifyUeReport, stream));
  Config::Connect ("/NodeList/*/DeviceList/*/$ns3::LteUeNetDevice/ComponentCarrierMapUe/*/LteUePhy/ReportUeMeasurements", MakeBoundCallback (&ReportUeMeasurementsCallbackDetail, stream2));
  Config::Connect ("/NodeList/*/DeviceList/*/LteEnbRrc/ConnectionEstablished", MakeBoundCallback (&NotifyConnectionEstablishedEnb,stream1));
    Config::ConnectWithoutContext ("/NodeList/*/DeviceList/*/LteUeRrc/StateTransition",
                                 MakeCallback (&UeStateTransition));
      Config::ConnectWithoutContext ("/NodeList/*/DeviceList/*/LteEnbRrc/NotifyConnectionRelease",
                                 MakeCallback (&NotifyConnectionReleaseAtEnodeB));
        Config::ConnectWithoutContext ("/NodeList/*/DeviceList/*/LteEnbRrc/RrcTimeout",
                                 MakeCallback (&EnbRrcTimeout));
         Config::ConnectWithoutContext ("/NodeList/*/DeviceList/*/LteUeRrc/RandomAccessError",
                                 MakeCallback (&NotifyRandomAccessErrorUe));
  Config::ConnectWithoutContext ("/NodeList/*/DeviceList/*/LteUeRrc/ConnectionTimeout",
                                   MakeCallback (&NotifyConnectionTimeoutUe));
  Config::ConnectWithoutContext ("/NodeList/*/DeviceList/*/LteUeMac/RaResponseTimeout",
                                   MakeCallback (&NotifyRaResponseTimeoutUe));
    Config::ConnectWithoutContext ("/NodeList/*/DeviceList/*/LteUeRrc/PhySyncDetection",
                                 MakeBoundCallback (&PhySyncDetection, n310));
  Config::ConnectWithoutContext ("/NodeList/*/DeviceList/*/LteUeRrc/RadioLinkFailure",
                                 MakeBoundCallback (&RadioLinkFailure, t310));

  lteHelper->EnableTraces ();
  // Uncomment to enable PCAP tracing
  p2ph.EnablePcapAll("basic_first");

  Simulator::Stop(Seconds(simTime+0.5));
  Simulator::Run();

  std::string outputDir = "./";
    std::string simTag= "test1";
    flowMonitor->CheckForLostPackets ();
    Ptr<Ipv4FlowClassifier> classifier = DynamicCast<Ipv4FlowClassifier> (flowHelper.GetClassifier ());
    FlowMonitor::FlowStatsContainer stats = flowMonitor->GetFlowStats ();

    double averageFlowThroughput = 0.0;
    double averageFlowDelay = 0.0;
    uint32_t sumLostPackets = 0.0;
    uint32_t sumTxPackets = 0.0;

    std::ofstream outFile;
    std::string filename = outputDir + "/" + simTag;
    outFile.open (filename.c_str (), std::ofstream::out | std::ofstream::app);
    if (!outFile.is_open ())
    {
      std::cout<<"Can't open file " << filename;
      return 1;
    }
    outFile.setf (std::ios_base::fixed);

  for (std::map<FlowId, FlowMonitor::FlowStats>::const_iterator i = stats.begin (); i != stats.end (); ++i)
    {
      
      Ipv4FlowClassifier::FiveTuple t = classifier->FindFlow(i->first);
      std::stringstream protoStream;
      protoStream << (uint16_t) t.protocol;
      if (t.protocol == 6)
        {
          protoStream.str ("TCP");
        }
      if (t.protocol == 17)
        {
          protoStream.str ("UDP");
        }
    //   for (uint32_t reasonCode = 0; reasonCode < i->second.packetsDropped.size (); reasonCode++)
    // {
    //   outFile<<"Print in";
    //   outFile << "<packetsDropped reasonCode=" << reasonCode
    //       << " number=" << i->second.packetsDropped[reasonCode] << "\n";

    // }
        outFile << "Flow " << i->first << " (" << t.sourceAddress << ":" << t.sourcePort << " -> " << t.destinationAddress << ":" << t.destinationPort << ") proto " << protoStream.str () << "\n"; 
        outFile << "  Tx Packets: " << i->second.txPackets << "\n";
        outFile << "  Tx Bytes:   " << i->second.txBytes << "\n";
        outFile << "  TxOffered:  " << i->second.txBytes * 8.0 / (simTime - 0.01) / 1000 / 1000  << " Mbps\n";
        outFile << "  Rx Bytes:   " << i->second.rxBytes << "\n";
        outFile << "  Lost Packets: " << i->second.lostPackets<< "\n";
        // outFile << " Packets Dropped : " << i-> second.packetsDropped<<"\n";
        // outFile << " Byte Dropped: " << i->second.bytesDropped<<"\n";
        sumTxPackets += i->second.txPackets;
        sumLostPackets+= i->second.lostPackets;
      if (i->second.rxPackets > 0)
        {
          // Measure the duration of the flow from receiver's perspective
          double rxDuration = i->second.timeLastRxPacket.GetSeconds () - i->second.timeFirstTxPacket.GetSeconds ();

          averageFlowThroughput += i->second.rxBytes * 8.0 / rxDuration / 1000 / 1000;
          averageFlowDelay += 1000 * i->second.delaySum.GetSeconds () / i->second.rxPackets;

          outFile << "  Throughput: " << i->second.rxBytes * 8.0 / rxDuration / 1000 / 1000  << " Mbps\n";
          outFile << "  Mean delay:  " << 1000 * i->second.delaySum.GetSeconds () / i->second.rxPackets << " ms\n";
          //outFile << "  Mean upt:  " << i->second.uptSum / i->second.rxPackets / 1000/1000 << " Mbps \n";
          outFile << "  Mean jitter:  " << 1000 * i->second.jitterSum.GetSeconds () / i->second.rxPackets  << " ms\n";
//          for (uint32_t reasonCode = 0; reasonCode < i->second.packetsDropped.size (); reasonCode++)
//        {
//          outFile<<"Print in";
//          outFile << "<packetsDropped reasonCode=" << reasonCode
//              << " number=" << i->second.packetsDropped[reasonCode] << "\n";
//
//        }
        // std::cout<<i->second.packetsDropped(0)<<std::endl;
        // for (uint32_t reasonCode = 0; reasonCode < i->second.bytesDropped.size (); reasonCode++)
        // {
        //   std::cout<<"\n"<< "<bytesDropped reasonCode=\"" << reasonCode << "\""
        //       << " bytes=\"" << i->second.bytesDropped[reasonCode] << std::endl;
        // }
        }
      else
        {
          outFile << "  Throughput:  0 Mbps\n";
          outFile << "  Mean delay:  0 ms\n";
          outFile << "  Mean upt:  0  Mbps \n";
          outFile << "  Mean jitter: 0 ms\n";
        }
      outFile << "\n" << "Loss Packet %" <<(((i->second.txPackets-i->second.rxPackets)*1.0)/i->second.txPackets);
      // outFile << "\n" << "Lost Packets:" << sumLostPackets;
      outFile << "  Rx Packets: " << i->second.rxPackets << "\n";

    }


    outFile << "\n" << "Packets Transmitted" << sumTxPackets << "\n";
    outFile << "\n" << "Packets Lost" << sumLostPackets << "\n";
    
    outFile << "\n\n  Mean flow throughput: " << averageFlowThroughput / stats.size() << "\n";
    outFile << "  Mean flow delay: " << averageFlowDelay / stats.size () << "\n";
    outFile <<"----------------------------------------Next Simulation -------------------------------------------------------------------------------------" << "\n";
  outFile.close ();

  flowMonitor->SerializeToXmlFile("test1.xml", true, true);
  /*GtkConfigStore config;
  config.ConfigureAttributes();*/

  Simulator::Destroy();
  return 0;

}