/* -*-  Mode: C++; c-file-style: "gnu"; indent-tabs-mode:nil; -*- */
/*
 * NIST-developed software is provided by NIST as a public
 * service. You may use, copy and distribute copies of the software in
 * any medium, provided that you keep intact this entire notice. You
 * may improve, modify and create derivative works of the software or
 * any portion of the software, and you may copy and distribute such
 * modifications or works. Modified works should carry a notice
 * stating that you changed the software and should note the date and
 * nature of any such change. Please explicitly acknowledge the
 * National Institute of Standards and Technology as the source of the
 * software.
 *
 * NIST-developed software is expressly provided "AS IS." NIST MAKES
 * NO WARRANTY OF ANY KIND, EXPRESS, IMPLIED, IN FACT OR ARISING BY
 * OPERATION OF LAW, INCLUDING, WITHOUT LIMITATION, THE IMPLIED
 * WARRANTY OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE,
 * NON-INFRINGEMENT AND DATA ACCURACY. NIST NEITHER REPRESENTS NOR
 * WARRANTS THAT THE OPERATION OF THE SOFTWARE WILL BE UNINTERRUPTED
 * OR ERROR-FREE, OR THAT ANY DEFECTS WILL BE CORRECTED. NIST DOES NOT
 * WARRANT OR MAKE ANY REPRESENTATIONS REGARDING THE USE OF THE
 * SOFTWARE OR THE RESULTS THEREOF, INCLUDING BUT NOT LIMITED TO THE
 * CORRECTNESS, ACCURACY, RELIABILITY, OR USEFULNESS OF THE SOFTWARE.
 *
 * You are solely responsible for determining the appropriateness of
 * using and distributing the software and you assume all risks
 * associated with its use, including but not limited to the risks and
 * costs of program errors, compliance with applicable laws, damage to
 * or loss of data, programs or equipment, and the unavailability or
 * interruption of operation. This software is not intended to be used
 * in any situation where a failure could cause risk of injury or
 * damage to property. The software developed by NIST employees is not
 * subject to copyright protection within the United States.
 */


#include "ns3/lte-module.h"
#include "ns3/core-module.h"
#include "ns3/network-module.h"
#include "ns3/internet-module.h"
#include "ns3/mobility-module.h"
#include "ns3/applications-module.h"
#include "ns3/point-to-point-module.h"
#include "ns3/config-store.h"
#include <cfloat>
#include <sstream>
#include <math.h>
#include "ns3/gnuplot.h"
#include "ns3/udp-echo-client.h"
#include <ns3/buildings-helper.h>
#include <ns3/buildings-module.h>
#include "ns3/flow-monitor-module.h"
#include "ns3/flow-monitor-helper.h"
#include "ns3/netanim-module.h"

using namespace ns3;

NS_LOG_COMPONENT_DEFINE ("Factory_Realying");

uint32_t SERVICE_CODE = 33;
int TX_PACKETS = 0;

int RX_PACKETS = 0; //Used to trace the number of Rx packets



/**
 * Structure to store the number of received messages
//  */
// struct NumberOfRxMessagesTracer
// {
//   void Trace (Ptr<const Packet> p, const Address &srcAddrs, const Address &dstAddrs); ///< Trace sink function
//   Ptr<Node> m_node; ///< Node
//   Ipv6Address m_network; ///< UE-to-Network Relay IPv6 network address
//   Ipv6Prefix m_prefix; ///< UE-to-Network Relay IPv6 prefix
//   uint32_t  m_nRxMsgs {0}; ///< Number of messages received
//   uint32_t  m_nRxMsgsRelay {0}; ///< Number of messages received while connected to Relay UE
// };


 // * Trace sink function to count the number of received packets at the
 // * application layer. Two metrics are tracked: the total number of received
 // * packets, and how many of them were received while connected to the Relay UE
 
// void
// NumberOfRxMessagesTracer::Trace (Ptr<const Packet> p, const Address &srcAddrs, const Address &dstAddrs)
// {
//   m_nRxMsgs++;

//   Ptr<Ipv6> ipv6 = m_node->GetObject<Ipv6> ();
//   //Get the interface used for UE-to-Network Relay (LteSlUeNetDevices)
//   int32_t ipInterfaceIndex = ipv6->GetInterfaceForPrefix (m_network, m_prefix);
//   if (ipInterfaceIndex != -1)
//     {
//       //We found an address within the UE-to-Network Relay network,
//       //i.e., the Remote UE is connected to the Relay UE
//       m_nRxMsgsRelay++;
//     }
// }

/*
 * Trace sink function for logging when a packet is transmitted or received
 * at the application layer
 */
/**
 * Trace sink function for logging when a SD-RSRP measurement is done by a
 * Remote UE
 */
void
ReportUeSdRsrpMeasurementsTrace (Ptr<OutputStreamWrapper> stream, uint16_t rnti, uint64_t relayId, uint32_t serviceCode, double sdRsrp)
{
  std::ostringstream oss;
  stream->GetStream ()->precision (6);

  *stream->GetStream () << Simulator::Now ().GetNanoSeconds () / (double) 1e9 << "\t"
                        << rnti << "\t"
                        << relayId << "\t"
                        << serviceCode << "\t"
                        << sdRsrp << "\t"
                        << std::endl;
}

/**
 * Trace sink function for logging when a Remote UE selects a new Relay UE
 */
void
RelayUeSelectionTrace (Ptr<OutputStreamWrapper> stream, uint64_t imsi, uint32_t serviceCode, uint64_t currentRelayId, uint64_t selectedRelayId)
{
  std::ostringstream oss;
  stream->GetStream ()->precision (6);

  *stream->GetStream () << Simulator::Now ().GetNanoSeconds () / (double) 1e9 << "\t"
                        << imsi << "\t"
                        << serviceCode << "\t"
                        << currentRelayId << "\t"
                        << selectedRelayId << "\t"
                        << std::endl;
}

/**
 * Trace sink function for logging PC5 connection change of status
 */
void
Pc5ConnectionStatusTrace (Ptr<OutputStreamWrapper> stream, uint32_t selfUeId,  uint32_t peerUeId, LteSlUeRrc::RelayRole role,
                          LteSlBasicUeController::Pc5ConnectionStatus status)
{
  std::ostringstream oss;
  stream->GetStream ()->precision (6);

  *stream->GetStream () << Simulator::Now ().GetNanoSeconds () / (double) 1e9 << "\t"
                        << selfUeId << "\t"
                        << peerUeId << "\t"
                        << role << "\t"
                        << status << "\t"
                        << std::endl;
}

void
UePacketTrace (Ptr<OutputStreamWrapper> stream, std::string context, Ptr<const Packet> p, const Address &srcAddrs, const Address &dstAddrs)
{
  std::ostringstream oss;
  stream->GetStream ()->precision (6);

  *stream->GetStream () << Simulator::Now ().GetNanoSeconds () / (double) 1e9 << "\t"
                        << context << "\t"
                        << Inet6SocketAddress::ConvertFrom (srcAddrs).GetIpv6 () << ":"
                        << Inet6SocketAddress::ConvertFrom (srcAddrs).GetPort () << "\t"
                        << Inet6SocketAddress::ConvertFrom (dstAddrs).GetIpv6 () << ":"
                        << Inet6SocketAddress::ConvertFrom (dstAddrs).GetPort () << "\t"
                        << p->GetSize () << "\t"
                        << std::endl;
  RX_PACKETS++;
}

void
UePacketInputTrace (Ptr<OutputStreamWrapper> stream, std::string context, Ptr<const Packet> p, const Address &srcAddrs, const Address &dstAddrs)
{
  std::ostringstream oss;
  stream->GetStream ()->precision (6);

  *stream->GetStream () << Simulator::Now ().GetNanoSeconds () / (double) 1e9 << "\t"
                        << context << "\t"
                        << Inet6SocketAddress::ConvertFrom (srcAddrs).GetIpv6 () << ":"
                        << Inet6SocketAddress::ConvertFrom (srcAddrs).GetPort () << "\t"
                        << Inet6SocketAddress::ConvertFrom (dstAddrs).GetIpv6 () << ":"
                        << Inet6SocketAddress::ConvertFrom (dstAddrs).GetPort () << "\t"
                        << p->GetSize () << "\t"
                        << std::endl;
  TX_PACKETS++;
}

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;
}
/*
 * Trace sink function to monitor primary cell RSRP and activate UE-to-Network
 * Relay service as Remote UE when the RSRP falls below a threshold
 */
void
RsrpMeasurementTrace (Ptr<OutputStreamWrapper> stream, const double threshold, Ptr<NetDevice> ueDevice, std::string context, uint64_t imsi, uint16_t cellId, double rsrp)
{
  std::ostringstream oss;
  stream->GetStream ()->precision (6);
  *stream->GetStream () << Simulator::Now ().GetNanoSeconds () / (double) 1e9 << "\t"
                        << imsi << "\t"
                        << cellId << "\t"
                        << rsrp << "\t"
                        << threshold << "\t"
                        << std::endl;

  Ptr<LteUeRrc> rrc = ueDevice->GetObject<LteUeNetDevice> ()->GetRrc ();
  PointerValue ptrOne;
  rrc->GetAttribute ("SidelinkConfiguration", ptrOne);
  Ptr<LteSlUeRrc> slUeRrc = ptrOne.Get<LteSlUeRrc> ();

  //Avoid that in-coverage Remote UE starts Relay service before cell
  //attachment (happening at the first RSRP report: 200 ms)

  if (Simulator::Now ().GetMilliSeconds () < 400)
    {
      return;
    }
  else
    {
      //If the RSRP of the primary cell is below the threshold and
      //the UE is not in Remote UE role already (i.e., already monitoring Discovery Relay Announcements (we are using discovery Model A))
      //then start UE-to-Network Relay Service as Remote UE
      if (rsrp < threshold && !slUeRrc->IsMonitoringRelayServiceCode (LteSlDiscHeader::DISC_RELAY_ANNOUNCEMENT, SERVICE_CODE))
        {
          rrc->StartRelayService (SERVICE_CODE, LteSlUeRrc::ModelA, LteSlUeRrc::RemoteUE);
        }
    }
}

/*
 * Trace sink function for logging when PC5 signaling messages are received
 */
void
TraceSinkPC5SignalingPacketTrace (Ptr<OutputStreamWrapper> stream, uint32_t srcL2Id, uint32_t dstL2Id, Ptr<Packet> p)
{
  LteSlPc5SignallingMessageType lpc5smt;
  p->PeekHeader (lpc5smt);
  *stream->GetStream () << Simulator::Now ().GetSeconds () << "\t" << srcL2Id << "\t" << dstL2Id << "\t" << lpc5smt.GetMessageName () << std::endl;
}


int main (int argc, char *argv[])
{

  double simTime = 1.0; //s //Simulation time (in seconds) updated automatically based on number of nodes
  // double relayRadius = 40.0; //m
  // double remoteRadius = 5.0; //m
  uint32_t nRelayUes = 80;
  uint32_t nRemoteUes = 20;
  // bool remoteUeMobility = true;
  bool useRelay = true;
  double threshold = -60; //dBm
  // uint16_t n310 = 1;
  // Time t310 = Seconds (1);
  // uint16_t n311 = 1;
   // double startTimeRemote [nRemoteUes];

  CommandLine cmd;

  cmd.AddValue ("useRelay", "The Remote UEs will use Relay Service", useRelay);
  cmd.AddValue ("threshold", "The RSRP value (dBm) for which the Remote UE will start Relay Service", threshold);

  cmd.Parse (argc, argv);

  //Set the start time of the relay service for the Relay UE and
  //the start of the application for the Remote UE
  double startTimeRelay1 = 1.0; //s
  double startTimeRemoteApp = 2.0; //s

  //Calculate simTime to give 10 s of traffic for the Remote UE
  simTime = startTimeRemoteApp + 30.0; //s

  NS_LOG_INFO ("Configuring default parameters...");

  //Configure the UE for UE_SELECTED scenario
  Config::SetDefault ("ns3::LteUeMac::SlGrantMcs", UintegerValue (16));
  Config::SetDefault ("ns3::LteUeMac::SlGrantSize", UintegerValue (6)); //The number of RBs allocated per UE for Sidelink
  Config::SetDefault ("ns3::LteUeMac::Ktrp", UintegerValue (1));
  Config::SetDefault ("ns3::LteUeMac::UseSetTrp", BooleanValue (false));
  Config::SetDefault ("ns3::LteUeMac::SlScheduler", StringValue ("MaxCoverage")); //Values include Fixed, Random, MinPrb, MaxCoverage

  // //Set the frequency
  // Config::SetDefault ("ns3::LteEnbNetDevice::DlEarfcn", UintegerValue (5330));
  // Config::SetDefault ("ns3::LteUeNetDevice::DlEarfcn", UintegerValue (5330));
  // Config::SetDefault ("ns3::LteEnbNetDevice::UlEarfcn", UintegerValue (23330));
  // Config::SetDefault ("ns3::LteEnbNetDevice::DlBandwidth", UintegerValue (50));
  // Config::SetDefault ("ns3::LteEnbNetDevice::UlBandwidth", UintegerValue (50));
  // Config::SetDefault ("ns3::LteHelper::PathlossModel", StringValue( "ns3::Cost231PropagationLossModel"));
  // Config::SetDefault ("ns3::LteHelper::PathlossModel", StringValue( "ns3::ThreeGppIndoorFactoryPropagationLossModel"));
  Config::SetDefault ("ns3::LteHelper::PathlossModel", StringValue( "ns3::IndoorToIndoorPropagationLossModel"));
  // Config::SetDefault ("ns3::ThreeGppPropagationLossModel::ShadowingEnabled",  BooleanValue (true));
  // Config::SetDefault ("ns3::ThreeGppPropagationLossModel::Frequency",  DoubleValue (800e6));
    //Set the frequency
  Config::SetDefault ("ns3::LteEnbNetDevice::DlEarfcn", UintegerValue (6250));
  Config::SetDefault ("ns3::LteUeNetDevice::DlEarfcn", UintegerValue (6250));
  Config::SetDefault ("ns3::LteEnbNetDevice::UlEarfcn", UintegerValue (24250));
  Config::SetDefault ("ns3::LteEnbNetDevice::DlBandwidth", UintegerValue (100));
  Config::SetDefault ("ns3::LteEnbNetDevice::UlBandwidth", UintegerValue (100));
  // Config::SetDefault ("ns3::LteEnbNetDevice::DlEarfcn", UintegerValue( 6250)); // 800 mhz 
  // Config::SetDefault ("ns3::LteEnbNetDevice::UlEarfcn", UintegerValue(24250)); // 800 mhz

  //Reduce frequency of CQI report to allow for sidelink transmissions
  Config::SetDefault ("ns3::LteUePhy::DownlinkCqiPeriodicity", TimeValue (MilliSeconds (79)));

  // //Set the UEs power in dBm
  // Config::SetDefault ("ns3::LteUePhy::TxPower", DoubleValue (23.0));
  // //Set the eNBs power in dBm
  // Config::SetDefault ("ns3::LteEnbPhy::TxPower", DoubleValue (30.0));

  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)); 
  //Create the lte helper
  Ptr<LteHelper> lteHelper = CreateObject<LteHelper> ();

  //Create and set the EPC helper
  Ptr<PointToPointEpcHelper>  epcHelper = CreateObject<PointToPointEpcHelper> ();
  lteHelper->SetEpcHelper (epcHelper);

 // lteHelper->SetAttribute ("FadingModel", StringValue ("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");
 //  }

    //Calculate the start time of the relay service for Relay UEs and Remote UEs
  //Do it sequentially for easy of tractability
  // double startTimeRelay [nRelayUes];
  // double startTimeRemote [nRemoteUes];
  // //The time between Remote UE's start of service
  // //Four discovery periods (4 * 0.32 s) to ensure relay selection (measurement report every 4 periods)
  // //One discovery period (0.32 s) to avoid some of the congestion for the connection messages
  // double timeBetweenRemoteStarts = 4 * 0.32 + 0.32; //s
  // The time between Relay UE's start of service
  // 1.0 s of baseline
  // 0.320 s to ensure sending the 1st discovery message
  // plus the time needed to all Remote UEs to connect to it:
  // (2+2*nRemoteUesPerRelay)*0.04 is the time in the worst case for all connection messages to go through between a Remote UE and a Relay UE:
  // 2 msgs from the Remote UE, each of them in 1 single SL period (0.04 s) and
  // 2 msgs from the Relay UE, which given the SL period RR scheduling, it can take in the worst case up to
  // nRemoteUesPerRelay SL periods to be sent
  // timeBetweenRemoteStarts to ensure sequentiality of activation
  // double timeBetweenRelayStarts = 1.0 + nRemoteUes * ((2 + 2 * nRemoteUes) * 0.04 + timeBetweenRemoteStarts); //s

  // for (uint32_t ryIdx = 0; ryIdx < nRelayUes; ryIdx++)
  //   {
  //     startTimeRelay [ryIdx] = 2.0 + 0.320 + timeBetweenRelayStarts * ryIdx;

  //     // NS_LOG_INFO ("Relay UE Idx " << ryIdx << " start time " << startTimeRelay [ryIdx] << "s");

  //     for (uint32_t rm = 0; rm < nRemoteUes; ++rm)
  //       {
  //         uint32_t rmIdx = nRemoteUes + rm;
  //         startTimeRemote [rmIdx] = startTimeRelay [ryIdx] + timeBetweenRemoteStarts * (rm + 1);
  //         // NS_LOG_INFO ("Remote UE Idx " << rmIdx << " start time " << startTimeRemote [rmIdx] << "s");
  //       }
  //   }

    //Calculate simTime based on relay service starts and give 10 s of traffic for the last one
  // simTime = startTimeRemote [(nRelayUes * nRemoteUes - 1)] + 3.0 + 10.0; //
  //Create Sidelink helper and set lteHelper
  Ptr<LteSidelinkHelper> proseHelper = CreateObject<LteSidelinkHelper> ();
  proseHelper->SetLteHelper (lteHelper);

  //Connect Sidelink controller and Sidelink Helper
  Config::SetDefault ("ns3::LteSlBasicUeController::ProseHelper", PointerValue (proseHelper));

  //Configure Relay UE (re)selection algorithm (RandomNoReselection|MaxSDRsrpNoReselection|MaxSDRsrp)
  Config::SetDefault ("ns3::LteSlBasicUeController::RelayUeSelectionAlgorithm", StringValue ("MaxSDRsrpNoReselection"));

  //Set pathloss model
  // lteHelper->SetAttribute ("PathlossModel", StringValue ("ns3::Hybrid3gppPropagationLossModel"));
  // lteHelper->SetAttribute ("PathlossModel", StringValue ("ns3::HybridBuildingsPropagationLossModel"));
  // lteHelper->SetPathlossModelAttribute ("ShadowSigmaExtWalls", DoubleValue (0));
  // lteHelper->SetPathlossModelAttribute ("ShadowSigmaOutdoor", DoubleValue (1));
  // lteHelper->SetPathlossModelAttribute ("ShadowSigmaIndoor", DoubleValue (1.5));
  // lteHelper->SetSpectrumChannelType ("ns3::MultiModelSpectrumChannel");
  // lteHelper->SetPathlossModelAttribute ("Frequency", DoubleValue (700e6));



  //Enable Sidelink
  lteHelper->SetAttribute ("UseSidelink", BooleanValue (true));

  Ptr<Node> pgw = epcHelper->GetPgwNode ();

  // Create a single RemoteHost
  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 (Seconds (0.010)));
  NetDeviceContainer internetDevices = p2ph.Install (pgw, remoteHost);

  //Create nodes (eNb + UEs)
  NodeContainer enbNode;
  enbNode.Create (1);

  NodeContainer relayUeNodes;
  relayUeNodes.Create (nRelayUes);

  NodeContainer remoteUeNodes;
  remoteUeNodes.Create (nRemoteUes);

  NodeContainer allUeNodes = NodeContainer (relayUeNodes,remoteUeNodes);

  double xMin = 10;

  //position of the building
  Ptr<Building> building1 = CreateObject<Building> ();
  building1->SetBoundaries (Box (xMin, xMin + 200, -100, 100, 0.0, 20.0));
  building1->SetBuildingType (Building::Residential);
  building1->SetExtWallsType (Building::ConcreteWithWindows);

  //Position of the nodes
  //eNodeB
  Ptr<ListPositionAllocator> positionAllocEnb = CreateObject<ListPositionAllocator> ();
  positionAllocEnb->Add (Vector (25,25,7));
  MobilityHelper mobilityeNodeB;
  mobilityeNodeB.SetMobilityModel ("ns3::ConstantPositionMobilityModel");
  mobilityeNodeB.SetPositionAllocator (positionAllocEnb);
  mobilityeNodeB.Install (enbNode);
  BuildingsHelper::Install (enbNode);

  // Static nodes
  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 (relayUeNodes);
   BuildingsHelper::Install (relayUeNodes);

   //Mobile Nodes
  Ptr<RandomBoxPositionAllocator> randomUePositionAlloc = CreateObject<RandomBoxPositionAllocator> ();
  Ptr<UniformRandomVariable> xVal = CreateObject<UniformRandomVariable> ();
  xVal->SetAttribute ("Min", DoubleValue (0));
  xVal->SetAttribute ("Max", DoubleValue (50));
  randomUePositionAlloc->SetAttribute ("X", PointerValue (xVal));
  Ptr<UniformRandomVariable> yVal = CreateObject<UniformRandomVariable> ();
  yVal->SetAttribute ("Min", DoubleValue (0));
  yVal->SetAttribute ("Max", DoubleValue (50));
  randomUePositionAlloc->SetAttribute ("Y", PointerValue (yVal));
  Ptr<ConstantRandomVariable> ueRandomVarZ = CreateObject<ConstantRandomVariable>();
  ueRandomVarZ->SetAttribute("Constant", DoubleValue(0.3));
 
  // zVal->SetAttribute ("Min", DoubleValue (macroUeBox.zMin));
  // zVal->SetAttribute ("Max", DoubleValue (macroUeBox.zMax));
  randomUePositionAlloc->SetZ(ueRandomVarZ);
  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"));
  
  mobilityHelperUeMobile.SetPositionAllocator (randomUePositionAlloc);
  mobilityHelperUeMobile.Install (remoteUeNodes);
  BuildingsHelper::Install (remoteUeNodes);


  AsciiTraceHelper ascii;
  // Ptr<OutputStreamWrapper> MobiStream = ascii.CreateFileStream ("UeMobilityTrace.txt");
  // for(uint32_t i=0; i< remoteUeNodes.GetN(); i++)
  // {
  // Ptr<MobilityModel> m1 = ns3::NodeList::GetNode(i)->GetObject<MobilityModel>();
  // Vector v1 = m1->GetPosition();
  // *MobiStream->GetStream () << Simulator::Now ().GetSeconds () << "\t" << remoteUeNodes.Get (i)->GetDevice (0)->GetObject<LteUeNetDevice> ()->GetImsi () << "\t" << v1  << std::endl;
  // }
   // mobilityHelperUeMobile.EnableAsciiAll (ascii.CreateFileStream ("UeMobilityTrace.txt"));

  //Install LTE devices to the nodes
  NetDeviceContainer enbDevs = lteHelper->InstallEnbDevice (enbNode);
  NetDeviceContainer relayUeDevs = lteHelper->InstallUeDevice (relayUeNodes);
  NetDeviceContainer remoteUeDevs = lteHelper->InstallUeDevice (remoteUeNodes);
  NetDeviceContainer allUeDevs = NetDeviceContainer (relayUeDevs, remoteUeDevs);

  //Configure eNodeB for Sidelink
  Ptr<LteSlEnbRrc> enbSidelinkConfiguration = CreateObject<LteSlEnbRrc> ();
  enbSidelinkConfiguration->SetSlEnabled (true);

  //-Configure Sidelink communication pool
  enbSidelinkConfiguration->SetDefaultPool (proseHelper->GetDefaultSlCommTxResourcesSetupUeSelected ());

  //-Enable Sidelink discovery
  enbSidelinkConfiguration->SetDiscEnabled (true);

  //-Configure Sidelink discovery pool
  enbSidelinkConfiguration->AddDiscPool (proseHelper->GetDefaultSlDiscTxResourcesSetupUeSelected ());

  //-Configure UE-to-Network Relay parameters
  enbSidelinkConfiguration->SetDiscConfigRelay (proseHelper->GetDefaultSib19DiscConfigRelay ());

  //Install eNodeB Sidelink configuration on the eNodeB devices
  lteHelper->InstallSidelinkConfiguration (enbDevs, enbSidelinkConfiguration);

  //Preconfigure UEs for Sidelink
  Ptr<LteSlUeRrc> ueSidelinkConfiguration = CreateObject<LteSlUeRrc> ();
  ueSidelinkConfiguration->SetSlEnabled (true);
  //-Set default Sidelink preconfiguration
  LteRrcSap::SlPreconfiguration preconfiguration;
  ueSidelinkConfiguration->SetSlPreconfiguration (preconfiguration);
  //-Enable Sidelink discovery
  ueSidelinkConfiguration->SetDiscEnabled (true);
  //-Set frequency for Sidelink discovery messages monitoring
  ueSidelinkConfiguration->SetDiscInterFreq (enbDevs.Get (0)->GetObject<LteEnbNetDevice> ()->GetUlEarfcn ());

  //Install UE Sidelink configuration on the UE devices
  lteHelper->InstallSidelinkConfiguration (relayUeDevs, ueSidelinkConfiguration);
  lteHelper->InstallSidelinkConfiguration (remoteUeDevs, ueSidelinkConfiguration);

  //Install the IP stack on the UEs and assign network IP addresses
  internet.Install (relayUeNodes);
  internet.Install (remoteUeNodes);
  Ipv6InterfaceContainer ueIpIfaceRelays;
  Ipv6InterfaceContainer ueIpIfaceRemotes;
  ueIpIfaceRelays = epcHelper->AssignUeIpv6Address (relayUeDevs);
  ueIpIfaceRemotes = epcHelper->AssignUeIpv6Address (remoteUeDevs);

  //Set the default gateway for the UEs
  Ipv6StaticRoutingHelper Ipv6RoutingHelper;
  for (uint32_t u = 0; u < allUeNodes.GetN (); ++u)
    {
      Ptr<Node> ueNode = allUeNodes.Get (u);
      Ptr<Ipv6StaticRouting> ueStaticRouting = Ipv6RoutingHelper.GetStaticRouting (ueNode->GetObject<Ipv6> ());
      ueStaticRouting->SetDefaultRoute (epcHelper->GetUeDefaultGatewayAddress6 (), 1);
    }

  //Configure IP for the nodes in the Internet (PGW and RemoteHost)
  Ipv6AddressHelper ipv6h;
  ipv6h.SetBase (Ipv6Address ("6001:db80::"), Ipv6Prefix (64));
  Ipv6InterfaceContainer internetIpIfaces = ipv6h.Assign (internetDevices);
  internetIpIfaces.SetForwarding (0, true);
  internetIpIfaces.SetDefaultRouteInAllNodes (0);

  //Set route for the Remote Host to join the LTE network nodes
  Ipv6StaticRoutingHelper ipv6RoutingHelper;
  Ptr<Ipv6StaticRouting> remoteHostStaticRouting = ipv6RoutingHelper.GetStaticRouting (remoteHost->GetObject<Ipv6> ());
  remoteHostStaticRouting->AddNetworkRouteTo ("7777:f000::", Ipv6Prefix (60), internetIpIfaces.GetAddress (0, 1), 1, 0);

  //Configure UE-to-Network Relay network
  proseHelper->SetIpv6BaseForRelayCommunication ("7777:f00e::", Ipv6Prefix (48));

  //Configure route between PGW and UE-to-Network Relay network
  proseHelper->ConfigurePgwToUeToNetworkRelayRoute (pgw);

  //Attach UEs to the eNB
  lteHelper->Attach (relayUeDevs);
  lteHelper->Attach (remoteUeDevs);

   Ipv6Address ip;

  Ptr<OutputStreamWrapper> MobiStream = ascii.CreateFileStream ("UeMobilityTrace_static.txt");
  for (uint32_t u = 0; u < allUeNodes.GetN (); ++u)
    {
      ip =  allUeNodes.Get(u)->GetObject<Ipv6>()->GetAddress(1,1).GetAddress();

      Ptr<MobilityModel> m1 = allUeNodes.Get(u)->GetObject<MobilityModel>();
      std::cout<<"IP Address: "<<ip<<"IMSI: "
      << allUeNodes.Get(u)->GetDevice(0)->GetObject <LteUeNetDevice>()->GetImsi() << "POS:"<< m1->GetPosition() <<std::endl;
      // std::cout<<"Power"<< ueNodes.Get(u)-> GetDevice(0)->GetObject<LteUePhy>()-> GetTxPower()<<std::endl;
       *MobiStream->GetStream () << Simulator::Now ().GetSeconds () << "\t" << allUeNodes.Get(u)->GetDevice(0)->GetObject <LteUeNetDevice>()->GetImsi() << "\t"<<m1->GetPosition()<<"\t" <<ip << std::endl;
    } 



  ///*** Configure applications ***///
  //For each Remote UE, we have a pair (UpdEchoClient, UdpEchoServer)
  //Each Remote UE has an assigned port
  //UdpEchoClient installed in the Remote UE, sending to the echoServerAddr
  //in the corresponding Remote UE port
  //UdpEchoServer installed in the echoServerNode, listening to the
  //corresponding Remote UE port
  Ptr<ExponentialRandomVariable> emergencyIntervalSeconds = CreateObject<ExponentialRandomVariable> ();
  emergencyIntervalSeconds->SetAttribute("Mean", DoubleValue(simTime));     // To be defined
  emergencyIntervalSeconds->SetAttribute("Bound", DoubleValue(simTime));
  Ipv6Address echoServerAddr = internetIpIfaces.GetAddress (1, 1);
  uint16_t echoPortBase = 50000;

  ApplicationContainer serverApps;
  ApplicationContainer clientApps;

  uint16_t remUePort = echoPortBase + 1;
  uint16_t otherPort = 1234;


  
  ApplicationContainer singleServerApp;
  ApplicationContainer singleClientApp;

  ApplicationContainer emServerApp;
  ApplicationContainer emClientApp;

  std::string echoServerNode ("RemoteHost");
  std::ostringstream oss;
  Ptr<OutputStreamWrapper> packetOutputStream = ascii.CreateFileStream ("AppPacketTrace.txt");
  *packetOutputStream->GetStream () << "time(sec)\ttx/rx\tC/S\tIMSI\tIP[src]\tIP[dst]\tPktSize(bytes)" << std::endl;

    Ptr<OutputStreamWrapper> packetInputStream = ascii.CreateFileStream ("AppPacketInputTrace.txt");
  *packetInputStream->GetStream () << "time(sec)\ttx/rx\tC/S\tIMSI\tIP[src]\tIP[dst]\tPktSize(bytes)" << std::endl;



  for (uint32_t u=0; u< remoteUeNodes.GetN(); ++u)
  {

  remUePort++;
  otherPort++;
  UdpEchoServerHelper echoServerHelper (remUePort);
  singleServerApp.Add (echoServerHelper.Install (remoteHost));
  serverApps.Add (singleServerApp);
  PacketSinkHelper packetSinkHelper ("ns3::UdpSocketFactory",Inet6SocketAddress (Ipv6Address::GetAny (), otherPort));
  emServerApp.Add(packetSinkHelper.Install (remoteUeNodes.Get(u)));
  serverApps.Add (emServerApp);

   singleServerApp.Start (Seconds (1.0));
  singleServerApp.Stop (Seconds (simTime));

   emServerApp.Start (Seconds (1.0));
  emServerApp.Stop (Seconds (simTime));


  // PacketSinkHelper packetSinkHelper ("ns3::UdpSocketFactory", InetSocketAddress (Ipv6Address::GetAny (), otherPort));
  // serverApps.Add (packetSinkHelper.Install (remoteHost));

  // UdpClientHelper client (echoServerAddr);
  // client.SetAttribute ("Interval", TimeValue (Seconds(emergencyIntervalSeconds->GetValue())));
  // client.SetAttribute ("MaxPackets", UintegerValue(1000000));
  // client.SetAttribute("PacketSize", UintegerValue(40));
  // echoClientHelper.SetAttribute ("RemotePort", UintegerValue (otherPort));
  // clientApps.Add (client.Install (remoteUeNodes.Get(u)));
  //UdpEchoClient in the Remote UE
  UdpEchoClientHelper echoClientHelper (echoServerAddr);
  echoClientHelper.SetAttribute ("MaxPackets", UintegerValue (1000000));
  echoClientHelper.SetAttribute ("Interval", TimeValue (MilliSeconds (15)));
  echoClientHelper.SetAttribute ("PacketSize", UintegerValue (200));
  echoClientHelper.SetAttribute ("RemotePort", UintegerValue (remUePort));

 

  singleClientApp = echoClientHelper.Install (remoteUeNodes.Get (u));
    Ipv6Address address = remoteUeNodes.Get(u)->GetObject<Ipv6>()->GetAddress(1,1).GetAddress();

      UdpClientHelper client (address, otherPort);
      client.SetAttribute ("Interval", TimeValue (Seconds(emergencyIntervalSeconds->GetValue())));
      client.SetAttribute ("MaxPackets", UintegerValue(1000000));
      client.SetAttribute("PacketSize", UintegerValue(40));

      emClientApp = client.Install (remoteHost);
      //       emClientApp.Start (Seconds (1) );
      // //Stop the application after 10.0 s
      // emClientApp.Stop (Seconds (3.0 + startTimeRemote [u] + 10.0));
      clientApps.Add (client.Install (remoteHost));
  
  NS_LOG_INFO ("Remote UE node id = [" << remoteUeNodes.Get (u)->GetId () << "] start application at " << startTimeRemoteApp << " s");
              //Start the application 3.0 s after the remote UE started the relay service
      //normally it should be enough time to connect
      // singleClientApp.Start (Seconds (1) );
      // //Stop the application after 10.0 s
      // singleClientApp.Stop (Seconds (3.0 + startTimeRemote [u] + 10.0));
          //Tracing packets on the UdpEchoServer (S)
      // oss << "rx\tS\t" << remoteHost->GetId ();
      // singleServerApp.Get (0)->TraceConnect ("RxWithAddresses", oss.str (), MakeBoundCallback (&UePacketTrace, packetOutputStream));
      // oss.str ("");
      // oss << "tx\tS\t" << remoteHost->GetId ();
      // singleServerApp.Get (0)->TraceConnect ("TxWithAddresses", oss.str (), MakeBoundCallback (&UePacketTrace, packetInputStream));
      // oss.str ("");

  clientApps.Add (singleClientApp);

    oss << "tx\tC\t"  <<"\t"<<remoteUeNodes.Get (u)->GetDevice (0)->GetObject<LteUeNetDevice> ()->GetImsi ();
  singleClientApp.Get (0)->TraceConnect ("TxWithAddresses", oss.str (), MakeBoundCallback (&UePacketInputTrace, packetInputStream));
  oss.str ("");

  oss << "rx\tC\t" <<"\t"<<remoteUeNodes.Get (u)->GetDevice (0)->GetObject<LteUeNetDevice> ()->GetImsi ();
  singleClientApp.Get (0)->TraceConnect ("RxWithAddresses", oss.str (), MakeBoundCallback (&UePacketTrace, packetOutputStream));
  oss.str ("");

        // if (echoServerNode.compare ("RemoteUE") == 0 && u != 0)
        // {
        //   //Schedule the change of the RemoteAddress to 100 ms before the start of the application
        //   //normally 'Remote UE (0)' should be already connected to its Relay UE so we can
        //   //assign its address as RemoteAddress
        //   Simulator::Schedule (Seconds (1.0 + startTimeRemote [u] - 0.100),
        //                        &ChangeUdpEchoClientRemote, remoteUeNodes.Get (0),
        //                        singleClientApp.Get (0)->GetObject<UdpEchoClient> (),
        //                        remUePort,
        //                        proseHelper->GetIpv6NetworkForRelayCommunication (),
        //                        proseHelper->GetIpv6PrefixForRelayCommunication ());
        // }
  // }
  //   //Set Rx trace for test
  // NumberOfRxMessagesTracer tracer;
  // tracer.m_node = clientApps.Get (0)->GetNode ();
  // tracer.m_network = proseHelper->GetIpv6NetworkForRelayCommunication ();
  // tracer.m_prefix = proseHelper->GetIpv6PrefixForRelayCommunication ();
  // oss << "rx\tC\t" << remoteUeNodes.Get (u)->GetId () <<"\t"<<remoteUeNodes.Get (u)->GetDevice (0)->GetObject<LteUeNetDevice> ()->GetImsi ();
  // clientApps.Get (0)->TraceConnectWithoutContext ("RxWithAddresses",  MakeCallback (&NumberOfRxMessagesTracer::Trace, &tracer));

  }
  singleServerApp.Start (Seconds (1));
  singleServerApp.Stop (Seconds (simTime));
  singleClientApp.Start (Seconds (1));
  singleClientApp.Stop (Seconds (simTime));
   emServerApp.Start (Seconds (1));
  emServerApp.Stop (Seconds (simTime));
  emClientApp.Start (Seconds (1));
  emClientApp.Stop (Seconds (simTime));

  // double power =10.0;
  // // Ptr<ns3::NetDevice> netDevice = remoteUeDevs.Get (0);
  // Simulator::Schedule(Seconds(2),&LteUePhy::SetTxPower,remoteUeDevs.Get (0), power);

  ///*** Configure Relaying ***///
  if (useRelay)
    {
      // proseHelper->SetIpv6BaseForRelayCommunication ("7777:f00e::", Ipv6Prefix (48));
      //Setup dedicated bearer for the Relay UE
      Ptr<EpcTft> tft = Create<EpcTft> ();
      EpcTft::PacketFilter dlpf;
      dlpf.localIpv6Address = proseHelper->GetIpv6NetworkForRelayCommunication ();
      dlpf.localIpv6Prefix = proseHelper->GetIpv6PrefixForRelayCommunication ();
      tft->Add (dlpf);
      EpsBearer bearer (EpsBearer::NGBR_VIDEO_TCP_DEFAULT);
      lteHelper->ActivateDedicatedEpsBearer (relayUeDevs, bearer, tft);
      for (uint32_t u=0; u< relayUeNodes.GetN(); ++u)
      {
      //Schedule the start of the relay service in the Relay UE
      Simulator::Schedule (Seconds ((startTimeRelay1 + u*(0.01)+0.32)), &LteSidelinkHelper::StartRelayService,
                           proseHelper, relayUeDevs.Get (u), SERVICE_CODE,
                           LteSlUeRrc::ModelA, LteSlUeRrc::RelayUE);
      NS_LOG_INFO ("Relay UE node id = [" << relayUeNodes.Get (u)->GetId () << "] provides Service Code " << SERVICE_CODE << " and start service at " << startTimeRelay1 << " s");
      }
      //Connect trace in all UEs for monitoring UE-to-Network Relay signaling messages
      Ptr<OutputStreamWrapper> PC5SignalingPacketTraceStream = ascii.CreateFileStream ("PC5SignalingPacketTrace_factory.txt");
      *PC5SignalingPacketTraceStream->GetStream () << "time(s)\ttxId\tRxId\tmsgType" << std::endl;
      for (uint32_t ueDevIdx = 0; ueDevIdx < allUeDevs.GetN (); ueDevIdx++)
        {
          Ptr<LteUeRrc> rrc = allUeDevs.Get (ueDevIdx)->GetObject<LteUeNetDevice> ()->GetRrc ();
          PointerValue ptrOne;
          rrc->GetAttribute ("SidelinkConfiguration", ptrOne);
          Ptr<LteSlUeRrc> slUeRrc = ptrOne.Get<LteSlUeRrc> ();
          slUeRrc->TraceConnectWithoutContext ("PC5SignalingPacketTrace",
                                               MakeBoundCallback (&TraceSinkPC5SignalingPacketTrace,
                                                                  PC5SignalingPacketTraceStream));
        }
      //Tracing SD-RSRP measurements in the Remote UEs
  Ptr<OutputStreamWrapper> ReportUeSdRsrpMeasurementsStream = ascii.CreateFileStream ("ReportUeSdRsrpMeasurementsTrace_factory.txt");
  *ReportUeSdRsrpMeasurementsStream->GetStream () << "time(s)\trnti\trelayId\tSC\tsdRsrp" << std::endl;
  for (uint32_t ueDevIdx = 0; ueDevIdx < remoteUeDevs.GetN (); ueDevIdx++)
    {
      Ptr<LteUePhy> phy = remoteUeDevs.Get (ueDevIdx)->GetObject<LteUeNetDevice> ()->GetPhy ();
      phy->TraceConnectWithoutContext ("ReportUeSdRsrpMeasurements",
                                       MakeBoundCallback (&ReportUeSdRsrpMeasurementsTrace,
                                                          ReportUeSdRsrpMeasurementsStream));
    }

  //Tracing Relay Selection decisions in the Remote UEs
  Ptr<OutputStreamWrapper> RelayUeSelectionStream = ascii.CreateFileStream ("RelayUeSelectionTrace_factory.txt");
  *RelayUeSelectionStream->GetStream () << "time(s)\timsi\tSC\tcRelayId\tsRelayId" << std::endl;

  for (uint32_t ueDevIdx = 0; ueDevIdx < remoteUeDevs.GetN (); ueDevIdx++)
    {
      Ptr<LteUeRrc> rrc = remoteUeDevs.Get (ueDevIdx)->GetObject<LteUeNetDevice> ()->GetRrc ();
      PointerValue ptrOne;
      rrc->GetAttribute ("SidelinkConfiguration", ptrOne);
      Ptr<LteSlUeRrc> slUeRrc = ptrOne.Get<LteSlUeRrc> ();
      PointerValue ptrTwo;
      slUeRrc->GetAttribute ("SlController", ptrTwo);
      Ptr<LteSlBasicUeController> ctlr = ptrTwo.Get<LteSlBasicUeController>();
      ctlr->TraceConnectWithoutContext ("RelayUeSelection",
                                        MakeBoundCallback (&RelayUeSelectionTrace,
                                                           RelayUeSelectionStream));
    }

  //Tracing one-to-one connection status in all UEs (Remote UEs and Relay UEs)
  Ptr<OutputStreamWrapper> Pc5ConnectionStatusStream = ascii.CreateFileStream ("Pc5ConnectionStatusTrace_factory.txt");
  *Pc5ConnectionStatusStream->GetStream () << "time(s)\tselfUeId\tpeerUeId\trole\tstatus" << std::endl;

  for (uint32_t ueDevIdx = 0; ueDevIdx < allUeDevs.GetN (); ueDevIdx++)
    {
      Ptr<LteUeRrc> rrc = allUeDevs.Get (ueDevIdx)->GetObject<LteUeNetDevice> ()->GetRrc ();
      PointerValue ptrOne;
      rrc->GetAttribute ("SidelinkConfiguration", ptrOne);
      Ptr<LteSlUeRrc> slUeRrc = ptrOne.Get<LteSlUeRrc> ();
      PointerValue ptrTwo;
      slUeRrc->GetAttribute ("SlController", ptrTwo);
      Ptr<LteSlBasicUeController> ctlr = ptrTwo.Get<LteSlBasicUeController>();
      ctlr->TraceConnectWithoutContext ("Pc5ConnectionStatus",
                                        MakeBoundCallback (&Pc5ConnectionStatusTrace,
                                                           Pc5ConnectionStatusStream));
    }

      //Connect trace in the Remote UE to monitor primary cell RSRP and activate
      //Relay service when it falls below 'threshold' (See 'RsrpMeasurementTrace' function)
      Ptr<OutputStreamWrapper> RsrpMeasurementsStream = ascii.CreateFileStream ("RsrpMeasurementTrace_factory.txt");
      *RsrpMeasurementsStream->GetStream () << "time(s)\timsi\tcellId\trsrp\tthreshold" << std::endl;
      for (uint32_t u=0; u< remoteUeNodes.GetN(); ++u)
      {
      std::ostringstream ctx;
      ctx << remoteUeNodes.Get (u)->GetId ();
      Ptr<LteUeRrc> rrc = remoteUeDevs.Get (u)->GetObject<LteUeNetDevice> ()->GetRrc ();
      PointerValue ptrOne;
      rrc->TraceConnect ("RsrpMeasurement",  ctx.str (),
                         MakeBoundCallback (&RsrpMeasurementTrace,
                                            RsrpMeasurementsStream,
                                            threshold,
                                            remoteUeDevs.Get (u)->GetObject<LteUeNetDevice> ()));
    }
    }
    Ptr<OutputStreamWrapper> stream1 = ascii.CreateFileStream ("Ueimsi_factory.txt");
    Config::Connect ("/NodeList/*/DeviceList/*/LteEnbRrc/ConnectionEstablished", MakeBoundCallback (&NotifyConnectionEstablishedEnb,stream1));
  NS_LOG_INFO ("Enabling traces...");

  lteHelper->EnablePdcpTraces ();
  lteHelper->EnableUlMacTraces ();
  lteHelper->EnableDlMacTraces ();

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

  NS_LOG_INFO ("Simulation time " << simTime << " s");
  NS_LOG_INFO ("Starting simulation...");

  Simulator::Stop (Seconds (simTime));
  AnimationInterface anim("factory_relaying.xml");
    for (uint32_t i = 0; i < remoteUeNodes.GetN (); ++i)
    {
      anim.UpdateNodeDescription (remoteUeNodes.Get (i), "Mobile Nodes"); // Optional
      anim.UpdateNodeColor (remoteUeNodes.Get (i), 255, 0, 0); // Optional
    }
  for (uint32_t i = 0; i < relayUeNodes.GetN (); ++i)
    {
      anim.UpdateNodeDescription (relayUeNodes.Get (i), "Static Nodes"); // Optional
      anim.UpdateNodeColor (relayUeNodes.Get (i), 0, 255, 0); // Optional
    }
  for (uint32_t i = 0; i < enbNode.GetN (); ++i)
    {
      anim.UpdateNodeDescription (enbNode.Get (i), "eNB"); // Optional
      anim.UpdateNodeColor (enbNode.Get (i), 0, 0, 255); // Optional 
    }
  anim.EnablePacketMetadata (true);
  Simulator::Run ();

    std::string outputDir = "./";
    std::string simTag= "relaying_factory";
    flowMonitor->CheckForLostPackets ();
    Ptr<Ipv6FlowClassifier> classifier = DynamicCast<Ipv6FlowClassifier> (flowHelper.GetClassifier6 ());
    // FlowMonitor::FlowStatsContainer stats = flowMonitor->GetFlowStats ();
    std::map<FlowId, FlowMonitor::FlowStats> stats = flowMonitor->GetFlowStats ();

    double averageFlowThroughput = 0.0;
    double averageFlowDelay = 0.0;
    uint32_t sumLostPackets = 0.0;
    uint32_t sumTxPackets = 0.0;
    uint32_t delayPacketSum = 0.0; 
    double delayDrop = 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)
    {
      
      Ipv6FlowClassifier::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 << "  Packet Forwards: "<< i->second.timesForwarded<<"\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;
          delayDrop = i->second.lastDelay.GetSeconds(); 
          std::cout << "Delay per packet"<< delayDrop; 
        if (delayDrop > 10)
        {
          delayPacketSum ++; 

        }



          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" << "Packet Lost due to delay"<< delayPacketSum << "\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->CheckForLostPackets ();

// Ptr<Ipv6FlowClassifier> classifier = DynamicCast<Ipv6FlowClassifier> (flowHelper.GetClassifier6 ());
// std::map<FlowId, FlowMonitor::FlowStats> stats = flowMonitor->GetFlowStats ();

// for (std::map<FlowId, FlowMonitor::FlowStats>::const_iterator iter = stats.begin (); iter != stats.end (); ++iter)
//   {
//   Ipv6FlowClassifier::FiveTuple t = classifier->FindFlow (iter->first);
//   if (iter->second.rxPackets>0)
//     {
//       std::cout<<"Flow ID: " << iter->first << " Src Addr " << t.sourceAddress << " Dst Addr " << t.destinationAddress<<"\n";
//       std::cout<<"Tx Packets = " << iter->second.txPackets<<"\n";
//       std::cout<<"Rx Packets = " << iter->second.rxPackets<<"\n";
//       std::cout<<"Mean Delay = " << (iter->second.delaySum/iter->second.rxPackets).GetMilliSeconds()<<" msec \n";
//       std::cout<<"Throughput: " << iter->second.rxBytes * 8.0 / (iter->second.timeLastRxPacket.GetSeconds()-iter->second.timeFirstTxPacket.GetSeconds()) / 1024  << " Kbps\n";
//       std::cout  <<std::endl;
//       }
//   }


  /*GtkConfigStore config;
  config.ConfigureAttributes();*/

  flowMonitor->SerializeToXmlFile("relaying_factory.xml", true, true);
  Simulator::Destroy ();

  std::cout << "Rx packets: " << RX_PACKETS << "/" << TX_PACKETS << std::endl;

  return 0;

}
