/*
 * Copyright (c) 2011 Centre Tecnologic de Telecomunicacions de Catalunya (CTTC)
 *
 * SPDX-License-Identifier: GPL-2.0-only
 *
 * Author: Nicola Baldo <nbaldo@cttc.es> (re-wrote from scratch this helper)
 *         Giuseppe Piro <g.piro@poliba.it> (parts of the PHY & channel  creation & configuration
 * copied from the GSoC 2011 code) Modified by: Danilo Abrignani <danilo.abrignani@unibo.it>
 * (Carrier Aggregation - GSoC 2015) Biljana Bojovic <biljana.bojovic@cttc.es> (Carrier Aggregation)
 */

#include "lte-helper.h"

#include "cc-helper.h"
#include "epc-helper.h"
#include "mac-stats-calculator.h"
#include "phy-rx-stats-calculator.h"
#include "phy-stats-calculator.h"
#include "phy-tx-stats-calculator.h"

#include "ns3/abort.h"
#include "ns3/buildings-propagation-loss-model.h"
#include "ns3/epc-enb-application.h"
#include "ns3/epc-enb-s1-sap.h"
#include "ns3/epc-ue-nas.h"
#include "ns3/epc-x2.h"
#include "ns3/ff-mac-scheduler.h"
#include "ns3/friis-spectrum-propagation-loss.h"
#include "ns3/isotropic-antenna-model.h"
#include "ns3/log.h"
#include "ns3/lte-anr.h"
#include "ns3/lte-chunk-processor.h"
#include "ns3/lte-common.h"
#include "ns3/lte-enb-component-carrier-manager.h"
#include "ns3/lte-enb-mac.h"
#include "ns3/lte-enb-net-device.h"
#include "ns3/lte-enb-phy.h"
#include "ns3/lte-enb-rrc.h"
#include "ns3/lte-ffr-algorithm.h"
#include "ns3/lte-handover-algorithm.h"
#include "ns3/lte-rlc-am.h"
#include "ns3/lte-rlc-um.h"
#include "ns3/lte-rlc.h"
#include "ns3/lte-rrc-protocol-ideal.h"
#include "ns3/lte-rrc-protocol-real.h"
#include "ns3/lte-spectrum-phy.h"
#include "ns3/lte-spectrum-value-helper.h"
#include "ns3/lte-ue-component-carrier-manager.h"
#include "ns3/lte-ue-mac.h"
#include "ns3/lte-ue-net-device.h"
#include "ns3/lte-ue-phy.h"
#include "ns3/lte-ue-rrc.h"
#include "ns3/multi-model-spectrum-channel.h"
#include "ns3/object-factory.h"
#include "ns3/object-map.h"
#include "ns3/pointer.h"
#include "ns3/string.h"
#include "ns3/trace-fading-loss-model.h"

#include <iostream>

namespace ns3
{

NS_LOG_COMPONENT_DEFINE("LteHelper");

NS_OBJECT_ENSURE_REGISTERED(LteHelper);

LteHelper::LteHelper()
    : m_fadingStreamsAssigned(false),
      m_imsiCounter(0),
      m_cellIdCounter{1}
{
    NS_LOG_FUNCTION(this);
    m_enbNetDeviceFactory.SetTypeId(LteEnbNetDevice::GetTypeId());
    m_enbAntennaModelFactory.SetTypeId(IsotropicAntennaModel::GetTypeId());
    m_ueNetDeviceFactory.SetTypeId(LteUeNetDevice::GetTypeId());
    m_ueAntennaModelFactory.SetTypeId(IsotropicAntennaModel::GetTypeId());
    m_channelFactory.SetTypeId(MultiModelSpectrumChannel::GetTypeId());
}

void
LteHelper::DoInitialize()
{
    NS_LOG_FUNCTION(this);
    ChannelModelInitialization();
    m_phyStats = CreateObject<PhyStatsCalculator>();
    m_phyTxStats = CreateObject<PhyTxStatsCalculator>();
    m_phyRxStats = CreateObject<PhyRxStatsCalculator>();
    m_macStats = CreateObject<MacStatsCalculator>();
    Object::DoInitialize();
}

LteHelper::~LteHelper()
{
    NS_LOG_FUNCTION(this);
}

TypeId
LteHelper::GetTypeId()
{
    static TypeId tid =
        TypeId("ns3::LteHelper")
            .SetParent<Object>()
            .AddConstructor<LteHelper>()
            .AddAttribute(
                "Scheduler",
                "The type of scheduler to be used for eNBs. "
                "The allowed values for this attributes are the type names "
                "of any class inheriting from ns3::FfMacScheduler.",
                StringValue("ns3::PfFfMacScheduler"),
                MakeStringAccessor(&LteHelper::SetSchedulerType, &LteHelper::GetSchedulerType),
                MakeStringChecker())
            .AddAttribute("FfrAlgorithm",
                          "The type of FFR algorithm to be used for eNBs. "
                          "The allowed values for this attributes are the type names "
                          "of any class inheriting from ns3::LteFfrAlgorithm.",
                          StringValue("ns3::LteFrNoOpAlgorithm"),
                          MakeStringAccessor(&LteHelper::SetFfrAlgorithmType,
                                             &LteHelper::GetFfrAlgorithmType),
                          MakeStringChecker())
            .AddAttribute("HandoverAlgorithm",
                          "The type of handover algorithm to be used for eNBs. "
                          "The allowed values for this attributes are the type names "
                          "of any class inheriting from ns3::LteHandoverAlgorithm.",
                          StringValue("ns3::NoOpHandoverAlgorithm"),
                          MakeStringAccessor(&LteHelper::SetHandoverAlgorithmType,
                                             &LteHelper::GetHandoverAlgorithmType),
                          MakeStringChecker())
            .AddAttribute("PathlossModel",
                          "The type of pathloss model to be used. "
                          "The allowed values for this attributes are the type names "
                          "of any class inheriting from ns3::PropagationLossModel "
                          "or ns3::SpectrumPropagationLossModel.",
                          TypeIdValue(FriisPropagationLossModel::GetTypeId()),
                          MakeTypeIdAccessor(&LteHelper::SetPathlossModelType),
                          MakeTypeIdChecker())
            .AddAttribute("FadingModel",
                          "The type of fading model to be used."
                          "The allowed values for this attributes are the type names "
                          "of any class inheriting from ns3::SpectrumPropagationLossModel."
                          "If the type is set to an empty string, no fading model is used.",
                          StringValue(""),
                          MakeStringAccessor(&LteHelper::SetFadingModel),
                          MakeStringChecker())
            .AddAttribute("UseIdealRrc",
                          "If true, LteRrcProtocolIdeal will be used for RRC signaling. "
                          "If false, LteRrcProtocolReal will be used.",
                          BooleanValue(true),
                          MakeBooleanAccessor(&LteHelper::m_useIdealRrc),
                          MakeBooleanChecker())
            .AddAttribute("AnrEnabled",
                          "Activate or deactivate Automatic Neighbour Relation function",
                          BooleanValue(true),
                          MakeBooleanAccessor(&LteHelper::m_isAnrEnabled),
                          MakeBooleanChecker())
            .AddAttribute("UsePdschForCqiGeneration",
                          "If true, DL-CQI will be calculated from PDCCH as signal and PDSCH as "
                          "interference. "
                          "If false, DL-CQI will be calculated from PDCCH as signal and PDCCH as "
                          "interference.",
                          BooleanValue(true),
                          MakeBooleanAccessor(&LteHelper::m_usePdschForCqiGeneration),
                          MakeBooleanChecker())
            .AddAttribute("EnbComponentCarrierManager",
                          "The type of Component Carrier Manager to be used for eNBs. "
                          "The allowed values for this attributes are the type names "
                          "of any class inheriting ns3::LteEnbComponentCarrierManager.",
                          StringValue("ns3::NoOpComponentCarrierManager"),
                          MakeStringAccessor(&LteHelper::SetEnbComponentCarrierManagerType,
                                             &LteHelper::GetEnbComponentCarrierManagerType),
                          MakeStringChecker())
            .AddAttribute("UeComponentCarrierManager",
                          "The type of Component Carrier Manager to be used for UEs. "
                          "The allowed values for this attributes are the type names "
                          "of any class inheriting ns3::LteUeComponentCarrierManager.",
                          StringValue("ns3::SimpleUeComponentCarrierManager"),
                          MakeStringAccessor(&LteHelper::SetUeComponentCarrierManagerType,
                                             &LteHelper::GetUeComponentCarrierManagerType),
                          MakeStringChecker())
            .AddAttribute("UseCa",
                          "If true, Carrier Aggregation feature is enabled and a valid Component "
                          "Carrier Map is expected. "
                          "If false, single carrier simulation.",
                          BooleanValue(false),
                          MakeBooleanAccessor(&LteHelper::m_useCa),
                          MakeBooleanChecker())
            .AddAttribute("NumberOfComponentCarriers",
                          "Set the number of Component carrier to use. "
                          "If it is more than one and m_useCa is false, it will raise an error.",
                          UintegerValue(1),
                          MakeUintegerAccessor(&LteHelper::m_noOfCcs),
                          MakeUintegerChecker<uint16_t>(MIN_NO_CC, MAX_NO_CC));
    return tid;
}

void
LteHelper::DoDispose()
{
    NS_LOG_FUNCTION(this);
    m_downlinkChannel = nullptr;
    m_uplinkChannel = nullptr;
    m_componentCarrierPhyParams.clear();
    Object::DoDispose();
}

Ptr<SpectrumChannel>
LteHelper::GetUplinkSpectrumChannel() const
{
    return m_uplinkChannel;
}

Ptr<SpectrumChannel>
LteHelper::GetDownlinkSpectrumChannel() const
{
    return m_downlinkChannel;
}

Ptr<EpcHelper>
LteHelper::GetEpcHelper() const
{
    return m_epcHelper;
}

void
LteHelper::ChannelModelInitialization()
{
    // Channel Object (i.e. Ptr<SpectrumChannel>) are within a vector
    // PathLossModel Objects are vectors --> in InstallSingleEnb we will set the frequency
    NS_LOG_FUNCTION(this << m_noOfCcs);

    m_downlinkChannel = m_channelFactory.Create<SpectrumChannel>();
    m_uplinkChannel = m_channelFactory.Create<SpectrumChannel>();

    m_downlinkPathlossModel = m_pathlossModelFactory.Create();
    Ptr<SpectrumPropagationLossModel> dlSplm =
        m_downlinkPathlossModel->GetObject<SpectrumPropagationLossModel>();
    if (dlSplm)
    {
        NS_LOG_LOGIC(this << " using a SpectrumPropagationLossModel in DL");
        m_downlinkChannel->AddSpectrumPropagationLossModel(dlSplm);
    }
    else
    {
        NS_LOG_LOGIC(this << " using a PropagationLossModel in DL");
        Ptr<PropagationLossModel> dlPlm =
            m_downlinkPathlossModel->GetObject<PropagationLossModel>();
        NS_ASSERT_MSG(dlPlm,
                      " " << m_downlinkPathlossModel
                          << " is neither PropagationLossModel nor SpectrumPropagationLossModel");
        m_downlinkChannel->AddPropagationLossModel(dlPlm);
    }

    m_uplinkPathlossModel = m_pathlossModelFactory.Create();
    Ptr<SpectrumPropagationLossModel> ulSplm =
        m_uplinkPathlossModel->GetObject<SpectrumPropagationLossModel>();
    if (ulSplm)
    {
        NS_LOG_LOGIC(this << " using a SpectrumPropagationLossModel in UL");
        m_uplinkChannel->AddSpectrumPropagationLossModel(ulSplm);
    }
    else
    {
        NS_LOG_LOGIC(this << " using a PropagationLossModel in UL");
        Ptr<PropagationLossModel> ulPlm = m_uplinkPathlossModel->GetObject<PropagationLossModel>();
        NS_ASSERT_MSG(ulPlm,
                      " " << m_uplinkPathlossModel
                          << " is neither PropagationLossModel nor SpectrumPropagationLossModel");
        m_uplinkChannel->AddPropagationLossModel(ulPlm);
    }
    if (!m_fadingModelType.empty())
    {
        m_fadingModel = m_fadingModelFactory.Create<SpectrumPropagationLossModel>();
        m_fadingModel->Initialize();
        m_downlinkChannel->AddSpectrumPropagationLossModel(m_fadingModel);
        m_uplinkChannel->AddSpectrumPropagationLossModel(m_fadingModel);
    }
}

void
LteHelper::SetEpcHelper(Ptr<EpcHelper> h)
{
    NS_LOG_FUNCTION(this << h);
    m_epcHelper = h;
}

void
LteHelper::SetSchedulerType(std::string type)
{
    NS_LOG_FUNCTION(this << type);
    m_schedulerFactory = ObjectFactory();
    m_schedulerFactory.SetTypeId(type);
}

std::string
LteHelper::GetSchedulerType() const
{
    return m_schedulerFactory.GetTypeId().GetName();
}

void
LteHelper::SetSchedulerAttribute(std::string n, const AttributeValue& v)
{
    NS_LOG_FUNCTION(this << n);
    m_schedulerFactory.Set(n, v);
}

std::string
LteHelper::GetFfrAlgorithmType() const
{
    return m_ffrAlgorithmFactory.GetTypeId().GetName();
}

void
LteHelper::SetFfrAlgorithmType(std::string type)
{
    NS_LOG_FUNCTION(this << type);
    m_ffrAlgorithmFactory = ObjectFactory();
    m_ffrAlgorithmFactory.SetTypeId(type);
}

void
LteHelper::SetFfrAlgorithmAttribute(std::string n, const AttributeValue& v)
{
    NS_LOG_FUNCTION(this << n);
    m_ffrAlgorithmFactory.Set(n, v);
}

std::string
LteHelper::GetHandoverAlgorithmType() const
{
    return m_handoverAlgorithmFactory.GetTypeId().GetName();
}

void
LteHelper::SetHandoverAlgorithmType(std::string type)
{
    NS_LOG_FUNCTION(this << type);
    m_handoverAlgorithmFactory = ObjectFactory();
    m_handoverAlgorithmFactory.SetTypeId(type);
}

void
LteHelper::SetHandoverAlgorithmAttribute(std::string n, const AttributeValue& v)
{
    NS_LOG_FUNCTION(this << n);
    m_handoverAlgorithmFactory.Set(n, v);
}

std::string
LteHelper::GetEnbComponentCarrierManagerType() const
{
    return m_enbComponentCarrierManagerFactory.GetTypeId().GetName();
}

void
LteHelper::SetEnbComponentCarrierManagerType(std::string type)
{
    NS_LOG_FUNCTION(this << type);
    m_enbComponentCarrierManagerFactory = ObjectFactory();
    m_enbComponentCarrierManagerFactory.SetTypeId(type);
}

void
LteHelper::SetEnbComponentCarrierManagerAttribute(std::string n, const AttributeValue& v)
{
    NS_LOG_FUNCTION(this << n);
    m_enbComponentCarrierManagerFactory.Set(n, v);
}

std::string
LteHelper::GetUeComponentCarrierManagerType() const
{
    return m_ueComponentCarrierManagerFactory.GetTypeId().GetName();
}

void
LteHelper::SetUeComponentCarrierManagerType(std::string type)
{
    NS_LOG_FUNCTION(this << type);
    m_ueComponentCarrierManagerFactory = ObjectFactory();
    m_ueComponentCarrierManagerFactory.SetTypeId(type);
}

void
LteHelper::SetUeComponentCarrierManagerAttribute(std::string n, const AttributeValue& v)
{
    NS_LOG_FUNCTION(this << n);
    m_ueComponentCarrierManagerFactory.Set(n, v);
}

void
LteHelper::SetPathlossModelType(TypeId type)
{
    NS_LOG_FUNCTION(this << type);
    m_pathlossModelFactory = ObjectFactory();
    m_pathlossModelFactory.SetTypeId(type);
}

void
LteHelper::SetPathlossModelAttribute(std::string n, const AttributeValue& v)
{
    NS_LOG_FUNCTION(this << n);
    m_pathlossModelFactory.Set(n, v);
}

void
LteHelper::SetEnbDeviceAttribute(std::string n, const AttributeValue& v)
{
    NS_LOG_FUNCTION(this);
    m_enbNetDeviceFactory.Set(n, v);
}

void
LteHelper::SetEnbAntennaModelType(std::string type)
{
    NS_LOG_FUNCTION(this);
    m_enbAntennaModelFactory.SetTypeId(type);
}

void
LteHelper::SetEnbAntennaModelAttribute(std::string n, const AttributeValue& v)
{
    NS_LOG_FUNCTION(this);
    m_enbAntennaModelFactory.Set(n, v);
}

void
LteHelper::SetUeDeviceAttribute(std::string n, const AttributeValue& v)
{
    NS_LOG_FUNCTION(this);
    m_ueNetDeviceFactory.Set(n, v);
}

void
LteHelper::SetUeAntennaModelType(std::string type)
{
    NS_LOG_FUNCTION(this);
    m_ueAntennaModelFactory.SetTypeId(type);
}

void
LteHelper::SetUeAntennaModelAttribute(std::string n, const AttributeValue& v)
{
    NS_LOG_FUNCTION(this);
    m_ueAntennaModelFactory.Set(n, v);
}

void
LteHelper::SetFadingModel(std::string type)
{
    NS_LOG_FUNCTION(this << type);
    m_fadingModelType = type;
    if (!type.empty())
    {
        m_fadingModelFactory = ObjectFactory();
        m_fadingModelFactory.SetTypeId(type);
    }
}

void
LteHelper::SetFadingModelAttribute(std::string n, const AttributeValue& v)
{
    m_fadingModelFactory.Set(n, v);
}

void
LteHelper::SetSpectrumChannelType(std::string type)
{
    NS_LOG_FUNCTION(this << type);
    m_channelFactory.SetTypeId(type);
}

void
LteHelper::SetSpectrumChannelAttribute(std::string n, const AttributeValue& v)
{
    m_channelFactory.Set(n, v);
}

NetDeviceContainer
LteHelper::InstallEnbDevice(NodeContainer c)
{
    NS_LOG_FUNCTION(this);
    Initialize(); // will run DoInitialize () if necessary
    NetDeviceContainer devices;
    for (auto i = c.Begin(); i != c.End(); ++i)
    {
        Ptr<Node> node = *i;
        Ptr<NetDevice> device = InstallSingleEnbDevice(node);
        devices.Add(device);
    }
    return devices;
}

NetDeviceContainer
LteHelper::InstallUeDevice(NodeContainer c)
{
    NS_LOG_FUNCTION(this);
    NetDeviceContainer devices;
    for (auto i = c.Begin(); i != c.End(); ++i)
    {
        Ptr<Node> node = *i;
        Ptr<NetDevice> device = InstallSingleUeDevice(node);
        devices.Add(device);
    }
    return devices;
}

Ptr<NetDevice>
LteHelper::InstallSingleEnbDevice(Ptr<Node> n)
{
    NS_LOG_FUNCTION(this << n);
    uint16_t cellId = m_cellIdCounter; // \todo Remove, eNB has no cell ID

    Ptr<LteEnbNetDevice> dev = m_enbNetDeviceFactory.Create<LteEnbNetDevice>();
    Ptr<LteHandoverAlgorithm> handoverAlgorithm =
        m_handoverAlgorithmFactory.Create<LteHandoverAlgorithm>();

    NS_ABORT_MSG_IF(!m_componentCarrierPhyParams.empty(), "CC map is not clean");
    DoComponentCarrierConfigure(dev->GetUlEarfcn(),
                                dev->GetDlEarfcn(),
                                dev->GetUlBandwidth(),
                                dev->GetDlBandwidth());
    NS_ABORT_MSG_IF(m_componentCarrierPhyParams.size() != m_noOfCcs,
                    "CC map size (" << m_componentCarrierPhyParams.size()
                                    << ") must be equal to number of carriers (" << m_noOfCcs
                                    << ")");
    // create component carrier map for this eNb device
    std::map<uint8_t, Ptr<ComponentCarrierBaseStation>> ccMap;
    for (auto it = m_componentCarrierPhyParams.begin(); it != m_componentCarrierPhyParams.end();
         ++it)
    {
        Ptr<ComponentCarrierEnb> cc = CreateObject<ComponentCarrierEnb>();
        cc->SetUlBandwidth(it->second.GetUlBandwidth());
        cc->SetDlBandwidth(it->second.GetDlBandwidth());
        cc->SetDlEarfcn(it->second.GetDlEarfcn());
        cc->SetUlEarfcn(it->second.GetUlEarfcn());
        cc->SetAsPrimary(it->second.IsPrimary());
        NS_ABORT_MSG_IF(m_cellIdCounter == 65535, "max num cells exceeded");
        cc->SetCellId(m_cellIdCounter++);
        ccMap[it->first] = cc;
    }
    // CC map is not needed anymore
    m_componentCarrierPhyParams.clear();

    NS_ABORT_MSG_IF(m_useCa && ccMap.size() < 2,
                    "You have to either specify carriers or disable carrier aggregation");
    NS_ASSERT(ccMap.size() == m_noOfCcs);

    for (auto it = ccMap.begin(); it != ccMap.end(); ++it)
    {
        NS_LOG_DEBUG(this << "component carrier map size " << (uint16_t)ccMap.size());
        Ptr<LteSpectrumPhy> dlPhy = CreateObject<LteSpectrumPhy>();
        Ptr<LteSpectrumPhy> ulPhy = CreateObject<LteSpectrumPhy>();
        Ptr<LteEnbPhy> phy = CreateObject<LteEnbPhy>(dlPhy, ulPhy);

        Ptr<LteHarqPhy> harq = Create<LteHarqPhy>();
        dlPhy->SetHarqPhyModule(harq);
        ulPhy->SetHarqPhyModule(harq);
        phy->SetHarqPhyModule(harq);

        Ptr<LteChunkProcessor> pCtrl = Create<LteChunkProcessor>();
        pCtrl->AddCallback(MakeCallback(&LteEnbPhy::GenerateCtrlCqiReport, phy));
        ulPhy->AddCtrlSinrChunkProcessor(pCtrl); // for evaluating SRS UL-CQI

        Ptr<LteChunkProcessor> pData = Create<LteChunkProcessor>();
        pData->AddCallback(MakeCallback(&LteEnbPhy::GenerateDataCqiReport, phy));
        pData->AddCallback(MakeCallback(&LteSpectrumPhy::UpdateSinrPerceived, ulPhy));
        ulPhy->AddDataSinrChunkProcessor(pData); // for evaluating PUSCH UL-CQI

        Ptr<LteChunkProcessor> pInterf = Create<LteChunkProcessor>();
        pInterf->AddCallback(MakeCallback(&LteEnbPhy::ReportInterference, phy));
        ulPhy->AddInterferenceDataChunkProcessor(pInterf); // for interference power tracing

        dlPhy->SetChannel(m_downlinkChannel);
        ulPhy->SetChannel(m_uplinkChannel);

        Ptr<MobilityModel> mm = n->GetObject<MobilityModel>();
        NS_ASSERT_MSG(
            mm,
            "MobilityModel needs to be set on node before calling LteHelper::InstallEnbDevice ()");
        dlPhy->SetMobility(mm);
        ulPhy->SetMobility(mm);

        Ptr<AntennaModel> antenna = (m_enbAntennaModelFactory.Create())->GetObject<AntennaModel>();
        NS_ASSERT_MSG(antenna, "error in creating the AntennaModel object");
        dlPhy->SetAntenna(antenna);
        ulPhy->SetAntenna(antenna);

        Ptr<LteEnbMac> mac = CreateObject<LteEnbMac>();
        Ptr<FfMacScheduler> sched = m_schedulerFactory.Create<FfMacScheduler>();
        Ptr<LteFfrAlgorithm> ffrAlgorithm = m_ffrAlgorithmFactory.Create<LteFfrAlgorithm>();
        DynamicCast<ComponentCarrierEnb>(it->second)->SetMac(mac);
        DynamicCast<ComponentCarrierEnb>(it->second)->SetFfMacScheduler(sched);
        DynamicCast<ComponentCarrierEnb>(it->second)->SetFfrAlgorithm(ffrAlgorithm);
        DynamicCast<ComponentCarrierEnb>(it->second)->SetPhy(phy);
    }

    Ptr<LteEnbRrc> rrc = CreateObject<LteEnbRrc>();
    Ptr<LteEnbComponentCarrierManager> ccmEnbManager =
        m_enbComponentCarrierManagerFactory.Create<LteEnbComponentCarrierManager>();

    // ComponentCarrierManager SAP
    rrc->SetLteCcmRrcSapProvider(ccmEnbManager->GetLteCcmRrcSapProvider());
    ccmEnbManager->SetLteCcmRrcSapUser(rrc->GetLteCcmRrcSapUser());
    // Set number of component carriers. Note: eNB CCM would also set the
    // number of component carriers in eNB RRC
    ccmEnbManager->SetNumberOfComponentCarriers(m_noOfCcs);

    rrc->ConfigureCarriers(ccMap);

    if (m_useIdealRrc)
    {
        Ptr<LteEnbRrcProtocolIdeal> rrcProtocol = CreateObject<LteEnbRrcProtocolIdeal>();
        rrcProtocol->SetLteEnbRrcSapProvider(rrc->GetLteEnbRrcSapProvider());
        rrc->SetLteEnbRrcSapUser(rrcProtocol->GetLteEnbRrcSapUser());
        rrc->AggregateObject(rrcProtocol);
        rrcProtocol->SetCellId(cellId);
    }
    else
    {
        Ptr<LteEnbRrcProtocolReal> rrcProtocol = CreateObject<LteEnbRrcProtocolReal>();
        rrcProtocol->SetLteEnbRrcSapProvider(rrc->GetLteEnbRrcSapProvider());
        rrc->SetLteEnbRrcSapUser(rrcProtocol->GetLteEnbRrcSapUser());
        rrc->AggregateObject(rrcProtocol);
        rrcProtocol->SetCellId(cellId);
    }

    if (m_epcHelper)
    {
        EnumValue<LteEnbRrc::LteEpsBearerToRlcMapping_t> epsBearerToRlcMapping;
        rrc->GetAttribute("EpsBearerToRlcMapping", epsBearerToRlcMapping);
        // it does not make sense to use RLC/SM when also using the EPC
        if (epsBearerToRlcMapping.Get() == LteEnbRrc::RLC_SM_ALWAYS)
        {
            rrc->SetAttribute("EpsBearerToRlcMapping", EnumValue(LteEnbRrc::RLC_UM_ALWAYS));
        }
    }

    rrc->SetLteHandoverManagementSapProvider(
        handoverAlgorithm->GetLteHandoverManagementSapProvider());
    handoverAlgorithm->SetLteHandoverManagementSapUser(rrc->GetLteHandoverManagementSapUser());

    // This RRC attribute is used to connect each new RLC instance with the MAC layer
    // (for function such as TransmitPdu, ReportBufferStatusReport).
    // Since in this new architecture, the component carrier manager acts a proxy, it
    // will have its own LteMacSapProvider interface, RLC will see it as through original MAC
    // interface LteMacSapProvider, but the function call will go now through
    // LteEnbComponentCarrierManager instance that needs to implement functions of this interface,
    // and its task will be to forward these calls to the specific MAC of some of the instances of
    // component carriers. This decision will depend on the specific implementation of the component
    // carrier manager.
    rrc->SetLteMacSapProvider(ccmEnbManager->GetLteMacSapProvider());

    bool ccmTest;
    for (auto it = ccMap.begin(); it != ccMap.end(); ++it)
    {
        DynamicCast<ComponentCarrierEnb>(it->second)
            ->GetPhy()
            ->SetLteEnbCphySapUser(rrc->GetLteEnbCphySapUser(it->first));
        rrc->SetLteEnbCphySapProvider(
            DynamicCast<ComponentCarrierEnb>(it->second)->GetPhy()->GetLteEnbCphySapProvider(),
            it->first);

        rrc->SetLteEnbCmacSapProvider(
            DynamicCast<ComponentCarrierEnb>(it->second)->GetMac()->GetLteEnbCmacSapProvider(),
            it->first);
        DynamicCast<ComponentCarrierEnb>(it->second)
            ->GetMac()
            ->SetLteEnbCmacSapUser(rrc->GetLteEnbCmacSapUser(it->first));

        DynamicCast<ComponentCarrierEnb>(it->second)->GetPhy()->SetComponentCarrierId(it->first);
        DynamicCast<ComponentCarrierEnb>(it->second)->GetMac()->SetComponentCarrierId(it->first);
        // FFR SAP
        DynamicCast<ComponentCarrierEnb>(it->second)
            ->GetFfMacScheduler()
            ->SetLteFfrSapProvider(DynamicCast<ComponentCarrierEnb>(it->second)
                                       ->GetFfrAlgorithm()
                                       ->GetLteFfrSapProvider());
        DynamicCast<ComponentCarrierEnb>(it->second)
            ->GetFfrAlgorithm()
            ->SetLteFfrSapUser(DynamicCast<ComponentCarrierEnb>(it->second)
                                   ->GetFfMacScheduler()
                                   ->GetLteFfrSapUser());
        rrc->SetLteFfrRrcSapProvider(DynamicCast<ComponentCarrierEnb>(it->second)
                                         ->GetFfrAlgorithm()
                                         ->GetLteFfrRrcSapProvider(),
                                     it->first);
        DynamicCast<ComponentCarrierEnb>(it->second)
            ->GetFfrAlgorithm()
            ->SetLteFfrRrcSapUser(rrc->GetLteFfrRrcSapUser(it->first));
        // FFR SAP END

        // PHY <--> MAC SAP
        DynamicCast<ComponentCarrierEnb>(it->second)
            ->GetPhy()
            ->SetLteEnbPhySapUser(
                DynamicCast<ComponentCarrierEnb>(it->second)->GetMac()->GetLteEnbPhySapUser());
        DynamicCast<ComponentCarrierEnb>(it->second)
            ->GetMac()
            ->SetLteEnbPhySapProvider(
                DynamicCast<ComponentCarrierEnb>(it->second)->GetPhy()->GetLteEnbPhySapProvider());
        // PHY <--> MAC SAP END

        // Scheduler SAP
        DynamicCast<ComponentCarrierEnb>(it->second)
            ->GetMac()
            ->SetFfMacSchedSapProvider(DynamicCast<ComponentCarrierEnb>(it->second)
                                           ->GetFfMacScheduler()
                                           ->GetFfMacSchedSapProvider());
        DynamicCast<ComponentCarrierEnb>(it->second)
            ->GetMac()
            ->SetFfMacCschedSapProvider(DynamicCast<ComponentCarrierEnb>(it->second)
                                            ->GetFfMacScheduler()
                                            ->GetFfMacCschedSapProvider());

        DynamicCast<ComponentCarrierEnb>(it->second)
            ->GetFfMacScheduler()
            ->SetFfMacSchedSapUser(
                DynamicCast<ComponentCarrierEnb>(it->second)->GetMac()->GetFfMacSchedSapUser());
        DynamicCast<ComponentCarrierEnb>(it->second)
            ->GetFfMacScheduler()
            ->SetFfMacCschedSapUser(
                DynamicCast<ComponentCarrierEnb>(it->second)->GetMac()->GetFfMacCschedSapUser());
        // Scheduler SAP END

        DynamicCast<ComponentCarrierEnb>(it->second)
            ->GetMac()
            ->SetLteCcmMacSapUser(ccmEnbManager->GetLteCcmMacSapUser());
        ccmEnbManager->SetCcmMacSapProviders(
            it->first,
            DynamicCast<ComponentCarrierEnb>(it->second)->GetMac()->GetLteCcmMacSapProvider());

        // insert the pointer to the LteMacSapProvider interface of the MAC layer of the specific
        // component carrier
        ccmTest = ccmEnbManager->SetMacSapProvider(
            it->first,
            DynamicCast<ComponentCarrierEnb>(it->second)->GetMac()->GetLteMacSapProvider());

        if (!ccmTest)
        {
            NS_FATAL_ERROR("Error in SetComponentCarrierMacSapProviders");
        }
    }

    dev->SetNode(n);
    dev->SetAttribute("CellId", UintegerValue(cellId));
    dev->SetAttribute("LteEnbComponentCarrierManager", PointerValue(ccmEnbManager));
    dev->SetCcMap(ccMap);
    auto it = ccMap.begin();
    dev->SetAttribute("LteEnbRrc", PointerValue(rrc));
    dev->SetAttribute("LteHandoverAlgorithm", PointerValue(handoverAlgorithm));
    dev->SetAttribute(
        "LteFfrAlgorithm",
        PointerValue(DynamicCast<ComponentCarrierEnb>(it->second)->GetFfrAlgorithm()));

    if (m_isAnrEnabled)
    {
        Ptr<LteAnr> anr = CreateObject<LteAnr>(cellId);
        rrc->SetLteAnrSapProvider(anr->GetLteAnrSapProvider());
        anr->SetLteAnrSapUser(rrc->GetLteAnrSapUser());
        dev->SetAttribute("LteAnr", PointerValue(anr));
    }

    for (it = ccMap.begin(); it != ccMap.end(); ++it)
    {
        Ptr<LteEnbPhy> ccPhy = DynamicCast<ComponentCarrierEnb>(it->second)->GetPhy();
        ccPhy->SetDevice(dev);
        ccPhy->GetUlSpectrumPhy()->SetDevice(dev);
        ccPhy->GetDlSpectrumPhy()->SetDevice(dev);
        ccPhy->GetUlSpectrumPhy()->SetLtePhyRxDataEndOkCallback(
            MakeCallback(&LteEnbPhy::PhyPduReceived, ccPhy));
        ccPhy->GetUlSpectrumPhy()->SetLtePhyRxCtrlEndOkCallback(
            MakeCallback(&LteEnbPhy::ReceiveLteControlMessageList, ccPhy));
        ccPhy->GetUlSpectrumPhy()->SetLtePhyUlHarqFeedbackCallback(
            MakeCallback(&LteEnbPhy::ReportUlHarqFeedback, ccPhy));
        NS_LOG_LOGIC("set the propagation model frequencies");
        double dlFreq = LteSpectrumValueHelper::GetCarrierFrequency(it->second->GetDlEarfcn());
        NS_LOG_LOGIC("DL freq: " << dlFreq);
        bool dlFreqOk =
            m_downlinkPathlossModel->SetAttributeFailSafe("Frequency", DoubleValue(dlFreq));
        if (!dlFreqOk)
        {
            NS_LOG_WARN("DL propagation model does not have a Frequency attribute");
        }

        double ulFreq = LteSpectrumValueHelper::GetCarrierFrequency(it->second->GetUlEarfcn());

        NS_LOG_LOGIC("UL freq: " << ulFreq);
        bool ulFreqOk =
            m_uplinkPathlossModel->SetAttributeFailSafe("Frequency", DoubleValue(ulFreq));
        if (!ulFreqOk)
        {
            NS_LOG_WARN("UL propagation model does not have a Frequency attribute");
        }
    }
    rrc->SetForwardUpCallback(MakeCallback(&LteEnbNetDevice::Receive, dev));
    dev->Initialize();
    n->AddDevice(dev);

    for (it = ccMap.begin(); it != ccMap.end(); ++it)
    {
        m_uplinkChannel->AddRx(
            DynamicCast<ComponentCarrierEnb>(it->second)->GetPhy()->GetUlSpectrumPhy());
    }

    if (m_epcHelper)
    {
        NS_LOG_INFO("adding this eNB to the EPC");
        m_epcHelper->AddEnb(n, dev, dev->GetCellIds());
        Ptr<EpcEnbApplication> enbApp = n->GetApplication(0)->GetObject<EpcEnbApplication>();
        NS_ASSERT_MSG(enbApp, "cannot retrieve EpcEnbApplication");

        // S1 SAPs
        rrc->SetS1SapProvider(enbApp->GetS1SapProvider());
        enbApp->SetS1SapUser(rrc->GetS1SapUser());

        // X2 SAPs
        Ptr<EpcX2> x2 = n->GetObject<EpcX2>();
        x2->SetEpcX2SapUser(rrc->GetEpcX2SapUser());
        rrc->SetEpcX2SapProvider(x2->GetEpcX2SapProvider());
    }

    return dev;
}

Ptr<NetDevice>
LteHelper::InstallSingleUeDevice(Ptr<Node> n)
{
    NS_LOG_FUNCTION(this);

    Ptr<LteUeNetDevice> dev = m_ueNetDeviceFactory.Create<LteUeNetDevice>();

    // Initialize the component carriers with default values in order to initialize MACs and PHYs
    // of each component carrier. These values must be updated once the UE is attached to the
    // eNB and receives RRC Connection Reconfiguration message. In case of primary carrier or
    // a single carrier, these values will be updated once the UE will receive SIB2 and MIB.
    NS_ABORT_MSG_IF(!m_componentCarrierPhyParams.empty(), "CC map is not clean");
    DoComponentCarrierConfigure(dev->GetDlEarfcn() + 18000, dev->GetDlEarfcn(), 25, 25);
    NS_ABORT_MSG_IF(m_componentCarrierPhyParams.size() != m_noOfCcs,
                    "CC map size (" << m_componentCarrierPhyParams.size()
                                    << ") must be equal to number of carriers (" << m_noOfCcs
                                    << ")");
    std::map<uint8_t, Ptr<ComponentCarrierUe>> ueCcMap;

    for (auto it = m_componentCarrierPhyParams.begin(); it != m_componentCarrierPhyParams.end();
         ++it)
    {
        Ptr<ComponentCarrierUe> cc = CreateObject<ComponentCarrierUe>();
        cc->SetUlBandwidth(it->second.GetUlBandwidth());
        cc->SetDlBandwidth(it->second.GetDlBandwidth());
        cc->SetDlEarfcn(it->second.GetDlEarfcn());
        cc->SetUlEarfcn(it->second.GetUlEarfcn());
        cc->SetAsPrimary(it->second.IsPrimary());
        Ptr<LteUeMac> mac = CreateObject<LteUeMac>();
        cc->SetMac(mac);
        // cc->GetPhy ()->Initialize (); // it is initialized within the
        // LteUeNetDevice::DoInitialize ()
        ueCcMap.insert(std::pair<uint8_t, Ptr<ComponentCarrierUe>>(it->first, cc));
    }
    // CC map is not needed anymore
    m_componentCarrierPhyParams.clear();

    for (auto it = ueCcMap.begin(); it != ueCcMap.end(); ++it)
    {
        Ptr<LteSpectrumPhy> dlPhy = CreateObject<LteSpectrumPhy>();
        Ptr<LteSpectrumPhy> ulPhy = CreateObject<LteSpectrumPhy>();

        Ptr<LteUePhy> phy = CreateObject<LteUePhy>(dlPhy, ulPhy);

        Ptr<LteHarqPhy> harq = Create<LteHarqPhy>();
        dlPhy->SetHarqPhyModule(harq);
        ulPhy->SetHarqPhyModule(harq);
        phy->SetHarqPhyModule(harq);

        Ptr<LteChunkProcessor> pRs = Create<LteChunkProcessor>();
        pRs->AddCallback(MakeCallback(&LteUePhy::ReportRsReceivedPower, phy));
        dlPhy->AddRsPowerChunkProcessor(pRs);

        Ptr<LteChunkProcessor> pInterf = Create<LteChunkProcessor>();
        pInterf->AddCallback(MakeCallback(&LteUePhy::ReportInterference, phy));
        dlPhy->AddInterferenceCtrlChunkProcessor(pInterf); // for RSRQ evaluation of UE Measurements

        Ptr<LteChunkProcessor> pCtrl = Create<LteChunkProcessor>();
        pCtrl->AddCallback(MakeCallback(&LteSpectrumPhy::UpdateSinrPerceived, dlPhy));
        dlPhy->AddCtrlSinrChunkProcessor(pCtrl);

        Ptr<LteChunkProcessor> pData = Create<LteChunkProcessor>();
        pData->AddCallback(MakeCallback(&LteSpectrumPhy::UpdateSinrPerceived, dlPhy));
        dlPhy->AddDataSinrChunkProcessor(pData);

        if (m_usePdschForCqiGeneration)
        {
            // CQI calculation based on PDCCH for signal and PDSCH for interference
            // NOTE: Change in pCtrl chunk processor could impact the RLF detection
            // since it is based on CTRL SINR.
            pCtrl->AddCallback(MakeCallback(&LteUePhy::GenerateMixedCqiReport, phy));
            Ptr<LteChunkProcessor> pDataInterf = Create<LteChunkProcessor>();
            pDataInterf->AddCallback(MakeCallback(&LteUePhy::ReportDataInterference, phy));
            dlPhy->AddInterferenceDataChunkProcessor(pDataInterf);
        }
        else
        {
            // CQI calculation based on PDCCH for both signal and interference
            pCtrl->AddCallback(MakeCallback(&LteUePhy::GenerateCtrlCqiReport, phy));
        }

        dlPhy->SetChannel(m_downlinkChannel);
        ulPhy->SetChannel(m_uplinkChannel);

        Ptr<MobilityModel> mm = n->GetObject<MobilityModel>();
        NS_ASSERT_MSG(
            mm,
            "MobilityModel needs to be set on node before calling LteHelper::InstallUeDevice ()");
        dlPhy->SetMobility(mm);
        ulPhy->SetMobility(mm);

        Ptr<AntennaModel> antenna = (m_ueAntennaModelFactory.Create())->GetObject<AntennaModel>();
        NS_ASSERT_MSG(antenna, "error in creating the AntennaModel object");
        dlPhy->SetAntenna(antenna);
        ulPhy->SetAntenna(antenna);

        it->second->SetPhy(phy);
    }
    Ptr<LteUeComponentCarrierManager> ccmUe =
        m_ueComponentCarrierManagerFactory.Create<LteUeComponentCarrierManager>();

    Ptr<LteUeRrc> rrc = CreateObject<LteUeRrc>();
    rrc->SetLteMacSapProvider(ccmUe->GetLteMacSapProvider());
    // setting ComponentCarrierManager SAP
    rrc->SetLteCcmRrcSapProvider(ccmUe->GetLteCcmRrcSapProvider());
    ccmUe->SetLteCcmRrcSapUser(rrc->GetLteCcmRrcSapUser());
    // Set number of component carriers. Note: UE CCM would also set the
    // number of component carriers in UE RRC
    ccmUe->SetNumberOfComponentCarriers(m_noOfCcs);

    // run initializeSap to create the proper number of MAC and PHY control sap provider/users
    rrc->InitializeSap();

    if (m_useIdealRrc)
    {
        Ptr<LteUeRrcProtocolIdeal> rrcProtocol = CreateObject<LteUeRrcProtocolIdeal>();
        rrcProtocol->SetUeRrc(rrc);
        rrc->AggregateObject(rrcProtocol);
        rrcProtocol->SetLteUeRrcSapProvider(rrc->GetLteUeRrcSapProvider());
        rrc->SetLteUeRrcSapUser(rrcProtocol->GetLteUeRrcSapUser());
    }
    else
    {
        Ptr<LteUeRrcProtocolReal> rrcProtocol = CreateObject<LteUeRrcProtocolReal>();
        rrcProtocol->SetUeRrc(rrc);
        rrc->AggregateObject(rrcProtocol);
        rrcProtocol->SetLteUeRrcSapProvider(rrc->GetLteUeRrcSapProvider());
        rrc->SetLteUeRrcSapUser(rrcProtocol->GetLteUeRrcSapUser());
    }

    if (m_epcHelper)
    {
        rrc->SetUseRlcSm(false);
    }
    Ptr<EpcUeNas> nas = CreateObject<EpcUeNas>();

    nas->SetAsSapProvider(rrc->GetAsSapProvider());
    rrc->SetAsSapUser(nas->GetAsSapUser());

    for (auto it = ueCcMap.begin(); it != ueCcMap.end(); ++it)
    {
        rrc->SetLteUeCmacSapProvider(it->second->GetMac()->GetLteUeCmacSapProvider(), it->first);
        it->second->GetMac()->SetLteUeCmacSapUser(rrc->GetLteUeCmacSapUser(it->first));
        it->second->GetMac()->SetComponentCarrierId(it->first);

        it->second->GetPhy()->SetLteUeCphySapUser(rrc->GetLteUeCphySapUser(it->first));
        rrc->SetLteUeCphySapProvider(it->second->GetPhy()->GetLteUeCphySapProvider(), it->first);
        it->second->GetPhy()->SetComponentCarrierId(it->first);
        it->second->GetPhy()->SetLteUePhySapUser(it->second->GetMac()->GetLteUePhySapUser());
        it->second->GetMac()->SetLteUePhySapProvider(
            it->second->GetPhy()->GetLteUePhySapProvider());

        bool ccmTest =
            ccmUe->SetComponentCarrierMacSapProviders(it->first,
                                                      it->second->GetMac()->GetLteMacSapProvider());

        if (!ccmTest)
        {
            NS_FATAL_ERROR("Error in SetComponentCarrierMacSapProviders");
        }
    }

    NS_ABORT_MSG_IF(m_imsiCounter >= 0xFFFFFFFF, "max num UEs exceeded");
    uint64_t imsi = ++m_imsiCounter;

    dev->SetNode(n);
    dev->SetAttribute("Imsi", UintegerValue(imsi));
    dev->SetCcMap(ueCcMap);
    dev->SetAttribute("LteUeRrc", PointerValue(rrc));
    dev->SetAttribute("EpcUeNas", PointerValue(nas));
    dev->SetAttribute("LteUeComponentCarrierManager", PointerValue(ccmUe));
    // \todo The UE identifier should be dynamically set by the EPC
    // when the default PDP context is created. This is a simplification.
    dev->SetAddress(Mac64Address::Allocate());

    for (auto it = ueCcMap.begin(); it != ueCcMap.end(); ++it)
    {
        Ptr<LteUePhy> ccPhy = it->second->GetPhy();
        ccPhy->SetDevice(dev);
        ccPhy->GetUlSpectrumPhy()->SetDevice(dev);
        ccPhy->GetDlSpectrumPhy()->SetDevice(dev);
        ccPhy->GetDlSpectrumPhy()->SetLtePhyRxDataEndOkCallback(
            MakeCallback(&LteUePhy::PhyPduReceived, ccPhy));
        ccPhy->GetDlSpectrumPhy()->SetLtePhyRxCtrlEndOkCallback(
            MakeCallback(&LteUePhy::ReceiveLteControlMessageList, ccPhy));
        ccPhy->GetDlSpectrumPhy()->SetLtePhyRxPssCallback(
            MakeCallback(&LteUePhy::ReceivePss, ccPhy));
        ccPhy->GetDlSpectrumPhy()->SetLtePhyDlHarqFeedbackCallback(
            MakeCallback(&LteUePhy::EnqueueDlHarqFeedback, ccPhy));
    }

    nas->SetDevice(dev);

    n->AddDevice(dev);

    nas->SetForwardUpCallback(MakeCallback(&LteUeNetDevice::Receive, dev));

    if (m_epcHelper)
    {
        m_epcHelper->AddUe(dev, dev->GetImsi());
    }

    dev->Initialize();

    return dev;
}

void
LteHelper::Attach(NetDeviceContainer ueDevices)
{
    NS_LOG_FUNCTION(this);
    for (auto i = ueDevices.Begin(); i != ueDevices.End(); ++i)
    {
        Attach(*i);
    }
}

void
LteHelper::Attach(Ptr<NetDevice> ueDevice)
{
    NS_LOG_FUNCTION(this);

    if (!m_epcHelper)
    {
        NS_FATAL_ERROR("This function is not valid without properly configured EPC");
    }

    Ptr<LteUeNetDevice> ueLteDevice = ueDevice->GetObject<LteUeNetDevice>();
    if (!ueLteDevice)
    {
        NS_FATAL_ERROR("The passed NetDevice must be an LteUeNetDevice");
    }

    // initiate cell selection
    Ptr<EpcUeNas> ueNas = ueLteDevice->GetNas();
    NS_ASSERT(ueNas);
    uint32_t dlEarfcn = ueLteDevice->GetDlEarfcn();
    ueNas->StartCellSelection(dlEarfcn);

    // instruct UE to immediately enter CONNECTED mode after camping
    ueNas->Connect();

    // activate default EPS bearer
    m_epcHelper->ActivateEpsBearer(ueDevice,
                                   ueLteDevice->GetImsi(),
                                   EpcTft::Default(),
                                   EpsBearer(EpsBearer::NGBR_VIDEO_TCP_DEFAULT));
}

void
LteHelper::Attach(NetDeviceContainer ueDevices, Ptr<NetDevice> enbDevice)
{
    NS_LOG_FUNCTION(this);
    for (auto i = ueDevices.Begin(); i != ueDevices.End(); ++i)
    {
        Attach(*i, enbDevice);
    }
}

void
LteHelper::Attach(Ptr<NetDevice> ueDevice, Ptr<NetDevice> enbDevice, uint8_t componentCarrierId)
{
    NS_LOG_FUNCTION(this);
    // enbRrc->SetCellId (enbDevice->GetObject<LteEnbNetDevice> ()->GetCellId ());

    Ptr<LteUeNetDevice> ueLteDevice = ueDevice->GetObject<LteUeNetDevice>();
    Ptr<LteEnbNetDevice> enbLteDevice = enbDevice->GetObject<LteEnbNetDevice>();

    Ptr<EpcUeNas> ueNas = ueLteDevice->GetNas();
    Ptr<ComponentCarrierEnb> componentCarrier =
        DynamicCast<ComponentCarrierEnb>(enbLteDevice->GetCcMap().at(componentCarrierId));
    ueNas->Connect(componentCarrier->GetCellId(), componentCarrier->GetDlEarfcn());

    if (m_epcHelper)
    {
        // activate default EPS bearer
        m_epcHelper->ActivateEpsBearer(ueDevice,
                                       ueLteDevice->GetImsi(),
                                       EpcTft::Default(),
                                       EpsBearer(EpsBearer::NGBR_VIDEO_TCP_DEFAULT));
    }

    // tricks needed for the simplified LTE-only simulations
    if (!m_epcHelper)
    {
        ueDevice->GetObject<LteUeNetDevice>()->SetTargetEnb(
            enbDevice->GetObject<LteEnbNetDevice>());
    }
}

void
LteHelper::AttachToClosestEnb(NetDeviceContainer ueDevices, NetDeviceContainer enbDevices)
{
    NS_LOG_FUNCTION(this);
    for (auto i = ueDevices.Begin(); i != ueDevices.End(); ++i)
    {
        AttachToClosestEnb(*i, enbDevices);
    }
}

void
LteHelper::AttachToClosestEnb(Ptr<NetDevice> ueDevice, NetDeviceContainer enbDevices)
{
    NS_LOG_FUNCTION(this);
    NS_ASSERT_MSG(enbDevices.GetN() > 0, "empty enb device container");
    Vector uepos = ueDevice->GetNode()->GetObject<MobilityModel>()->GetPosition();
    double minDistance = std::numeric_limits<double>::infinity();
    Ptr<NetDevice> closestEnbDevice;
    for (auto i = enbDevices.Begin(); i != enbDevices.End(); ++i)
    {
        Vector enbpos = (*i)->GetNode()->GetObject<MobilityModel>()->GetPosition();
        double distance = CalculateDistance(uepos, enbpos);
        if (distance < minDistance)
        {
            minDistance = distance;
            closestEnbDevice = *i;
        }
    }
    NS_ASSERT(closestEnbDevice);
    Attach(ueDevice, closestEnbDevice);
}

uint8_t
LteHelper::ActivateDedicatedEpsBearer(NetDeviceContainer ueDevices,
                                      EpsBearer bearer,
                                      Ptr<EpcTft> tft)
{
    NS_LOG_FUNCTION(this);
    for (auto i = ueDevices.Begin(); i != ueDevices.End(); ++i)
    {
        uint8_t bearerId = ActivateDedicatedEpsBearer(*i, bearer, tft);
        return bearerId;
    }
    return 0;
}

uint8_t
LteHelper::ActivateDedicatedEpsBearer(Ptr<NetDevice> ueDevice, EpsBearer bearer, Ptr<EpcTft> tft)
{
    NS_LOG_FUNCTION(this);

    NS_ASSERT_MSG(m_epcHelper, "dedicated EPS bearers cannot be set up when the EPC is not used");

    uint64_t imsi = ueDevice->GetObject<LteUeNetDevice>()->GetImsi();
    uint8_t bearerId = m_epcHelper->ActivateEpsBearer(ueDevice, imsi, tft, bearer);
    return bearerId;
}

/**
 * @ingroup lte
 *
 * DrbActivatior allows user to activate bearers for UEs
 * when EPC is not used. Activation function is hooked to
 * the Enb RRC Connection Established trace source. When
 * UE change its RRC state to CONNECTED_NORMALLY, activation
 * function is called and bearer is activated.
 */
class DrbActivator
{
  public:
    /**
     * DrbActivator Constructor
     *
     * @param ueDevice the UeNetDevice for which bearer will be activated
     * @param bearer the bearer configuration
     */
    DrbActivator(Ptr<NetDevice> ueDevice, EpsBearer bearer);

    /**
     * Function hooked to the Enb RRC Connection Established trace source
     * Fired upon successful RRC connection establishment.
     *
     * @param a DrbActivator object
     * @param context
     * @param imsi
     * @param cellId
     * @param rnti
     */
    static void ActivateCallback(std::shared_ptr<DrbActivator> a,
                                 std::string context,
                                 uint64_t imsi,
                                 uint16_t cellId,
                                 uint16_t rnti);

    /**
     * Procedure firstly checks if bearer was not activated, if IMSI
     * from trace source equals configured one and if UE is really
     * in RRC connected state. If all requirements are met, it performs
     * bearer activation.
     *
     * @param imsi
     * @param cellId
     * @param rnti
     */
    void ActivateDrb(uint64_t imsi, uint16_t cellId, uint16_t rnti);

  private:
    /**
     * Bearer can be activated only once. This value stores state of
     * bearer. Initially is set to false and changed to true during
     * bearer activation.
     */
    bool m_active;
    /**
     * UeNetDevice for which bearer will be activated
     */
    Ptr<NetDevice> m_ueDevice;
    /**
     * Configuration of bearer which will be activated
     */
    EpsBearer m_bearer;
    /**
     * imsi the unique UE identifier
     */
    uint64_t m_imsi;
};

DrbActivator::DrbActivator(Ptr<NetDevice> ueDevice, EpsBearer bearer)
    : m_active(false),
      m_ueDevice(ueDevice),
      m_bearer(bearer),
      m_imsi(m_ueDevice->GetObject<LteUeNetDevice>()->GetImsi())
{
}

void
DrbActivator::ActivateCallback(std::shared_ptr<DrbActivator> a,
                               std::string context,
                               uint64_t imsi,
                               uint16_t cellId,
                               uint16_t rnti)
{
    NS_LOG_FUNCTION(a << context << imsi << cellId << rnti);
    a->ActivateDrb(imsi, cellId, rnti);
}

void
DrbActivator::ActivateDrb(uint64_t imsi, uint16_t cellId, uint16_t rnti)
{
    NS_LOG_FUNCTION(this << imsi << cellId << rnti << m_active);
    if ((!m_active) && (imsi == m_imsi))
    {
        Ptr<LteUeRrc> ueRrc = m_ueDevice->GetObject<LteUeNetDevice>()->GetRrc();
        NS_ASSERT(ueRrc->GetState() == LteUeRrc::CONNECTED_NORMALLY);
        uint16_t rnti = ueRrc->GetRnti();
        Ptr<LteEnbNetDevice> enbLteDevice = m_ueDevice->GetObject<LteUeNetDevice>()->GetTargetEnb();
        Ptr<LteEnbRrc> enbRrc = enbLteDevice->GetObject<LteEnbNetDevice>()->GetRrc();
        NS_ASSERT(ueRrc->GetCellId() == enbLteDevice->GetCellId());
        Ptr<UeManager> ueManager = enbRrc->GetUeManager(rnti);
        NS_ASSERT(ueManager->GetState() == UeManager::CONNECTED_NORMALLY ||
                  ueManager->GetState() == UeManager::CONNECTION_RECONFIGURATION);
        EpcEnbS1SapUser::DataRadioBearerSetupRequestParameters params;
        params.rnti = rnti;
        params.bearer = m_bearer;
        params.bearerId = 0;
        params.gtpTeid = 0; // don't care
        enbRrc->GetS1SapUser()->DataRadioBearerSetupRequest(params);
        m_active = true;
    }
}

void
LteHelper::ActivateDataRadioBearer(Ptr<NetDevice> ueDevice, EpsBearer bearer)
{
    NS_LOG_FUNCTION(this << ueDevice);
    NS_ASSERT_MSG(!m_epcHelper, "this method must not be used when the EPC is being used");

    // Normally it is the EPC that takes care of activating DRBs
    // when the UE gets connected. When the EPC is not used, we achieve
    // the same behavior by hooking a dedicated DRB activation function
    // to the Enb RRC Connection Established trace source

    Ptr<LteEnbNetDevice> enbLteDevice = ueDevice->GetObject<LteUeNetDevice>()->GetTargetEnb();

    std::ostringstream path;
    path << "/NodeList/" << enbLteDevice->GetNode()->GetId() << "/DeviceList/"
         << enbLteDevice->GetIfIndex() << "/LteEnbRrc/ConnectionEstablished";
    auto arg = std::make_shared<DrbActivator>(ueDevice, bearer);
    Config::Connect(path.str(), MakeBoundCallback(&DrbActivator::ActivateCallback, arg));
}

void
LteHelper::AddX2Interface(NodeContainer enbNodes)
{
    NS_LOG_FUNCTION(this);

    NS_ASSERT_MSG(m_epcHelper, "X2 interfaces cannot be set up when the EPC is not used");

    for (auto i = enbNodes.Begin(); i != enbNodes.End(); ++i)
    {
        for (auto j = i + 1; j != enbNodes.End(); ++j)
        {
            AddX2Interface(*i, *j);
        }
    }
}

void
LteHelper::AddX2Interface(Ptr<Node> enbNode1, Ptr<Node> enbNode2)
{
    NS_LOG_FUNCTION(this);
    NS_LOG_INFO("setting up the X2 interface");

    m_epcHelper->AddX2Interface(enbNode1, enbNode2);
}

void
LteHelper::HandoverRequest(Time hoTime,
                           Ptr<NetDevice> ueDev,
                           Ptr<NetDevice> sourceEnbDev,
                           Ptr<NetDevice> targetEnbDev)
{
    NS_LOG_FUNCTION(this << ueDev << sourceEnbDev << targetEnbDev);
    NS_ASSERT_MSG(m_epcHelper,
                  "Handover requires the use of the EPC - did you forget to call "
                  "LteHelper::SetEpcHelper () ?");
    uint16_t targetCellId = targetEnbDev->GetObject<LteEnbNetDevice>()->GetCellId();
    Simulator::Schedule(hoTime,
                        &LteHelper::DoHandoverRequest,
                        this,
                        ueDev,
                        sourceEnbDev,
                        targetCellId);
}

void
LteHelper::HandoverRequest(Time hoTime,
                           Ptr<NetDevice> ueDev,
                           Ptr<NetDevice> sourceEnbDev,
                           uint16_t targetCellId)
{
    NS_LOG_FUNCTION(this << ueDev << sourceEnbDev << targetCellId);
    NS_ASSERT_MSG(m_epcHelper,
                  "Handover requires the use of the EPC - did you forget to call "
                  "LteHelper::SetEpcHelper () ?");
    Simulator::Schedule(hoTime,
                        &LteHelper::DoHandoverRequest,
                        this,
                        ueDev,
                        sourceEnbDev,
                        targetCellId);
}

void
LteHelper::DoHandoverRequest(Ptr<NetDevice> ueDev,
                             Ptr<NetDevice> sourceEnbDev,
                             uint16_t targetCellId)
{
    NS_LOG_FUNCTION(this << ueDev << sourceEnbDev << targetCellId);

    Ptr<LteEnbRrc> sourceRrc = sourceEnbDev->GetObject<LteEnbNetDevice>()->GetRrc();
    uint16_t rnti = ueDev->GetObject<LteUeNetDevice>()->GetRrc()->GetRnti();
    sourceRrc->SendHandoverRequest(rnti, targetCellId);
}

void
LteHelper::DeActivateDedicatedEpsBearer(Ptr<NetDevice> ueDevice,
                                        Ptr<NetDevice> enbDevice,
                                        uint8_t bearerId)
{
    NS_LOG_FUNCTION(this << ueDevice << bearerId);
    NS_ASSERT_MSG(m_epcHelper,
                  "Dedicated EPS bearers cannot be de-activated when the EPC is not used");
    NS_ASSERT_MSG(bearerId != 1,
                  "Default bearer cannot be de-activated until and unless and UE is released");

    DoDeActivateDedicatedEpsBearer(ueDevice, enbDevice, bearerId);
}

void
LteHelper::DoDeActivateDedicatedEpsBearer(Ptr<NetDevice> ueDevice,
                                          Ptr<NetDevice> enbDevice,
                                          uint8_t bearerId)
{
    NS_LOG_FUNCTION(this << ueDevice << bearerId);

    // Extract IMSI and rnti
    uint64_t imsi = ueDevice->GetObject<LteUeNetDevice>()->GetImsi();
    uint16_t rnti = ueDevice->GetObject<LteUeNetDevice>()->GetRrc()->GetRnti();

    Ptr<LteEnbRrc> enbRrc = enbDevice->GetObject<LteEnbNetDevice>()->GetRrc();

    enbRrc->DoSendReleaseDataRadioBearer(imsi, rnti, bearerId);
}

void
LteHelper::DoComponentCarrierConfigure(uint32_t ulEarfcn,
                                       uint32_t dlEarfcn,
                                       uint16_t ulbw,
                                       uint16_t dlbw)
{
    NS_LOG_FUNCTION(this << ulEarfcn << dlEarfcn << ulbw << dlbw);

    NS_ABORT_MSG_IF(!m_componentCarrierPhyParams.empty(), "CC map is not clean");
    Ptr<CcHelper> ccHelper = CreateObject<CcHelper>();
    ccHelper->SetNumberOfComponentCarriers(m_noOfCcs);
    ccHelper->SetUlEarfcn(ulEarfcn);
    ccHelper->SetDlEarfcn(dlEarfcn);
    ccHelper->SetDlBandwidth(dlbw);
    ccHelper->SetUlBandwidth(ulbw);
    m_componentCarrierPhyParams = ccHelper->EquallySpacedCcs();
    m_componentCarrierPhyParams.at(0).SetAsPrimary(true);
}

void
LteHelper::ActivateDataRadioBearer(NetDeviceContainer ueDevices, EpsBearer bearer)
{
    NS_LOG_FUNCTION(this);
    for (auto i = ueDevices.Begin(); i != ueDevices.End(); ++i)
    {
        ActivateDataRadioBearer(*i, bearer);
    }
}

void
LteHelper::EnableLogComponents()
{
    LogComponentEnableAll(LOG_PREFIX_TIME);
    LogComponentEnableAll(LOG_PREFIX_FUNC);
    LogComponentEnableAll(LOG_PREFIX_NODE);
    // Model directory
    LogComponentEnable("A2A4RsrqHandoverAlgorithm", LOG_LEVEL_ALL);
    LogComponentEnable("A3RsrpHandoverAlgorithm", LOG_LEVEL_ALL);
    LogComponentEnable("Asn1Header", LOG_LEVEL_ALL);
    LogComponentEnable("ComponentCarrier", LOG_LEVEL_ALL);
    LogComponentEnable("ComponentCarrierEnb", LOG_LEVEL_ALL);
    LogComponentEnable("ComponentCarrierUe", LOG_LEVEL_ALL);
    LogComponentEnable("CqaFfMacScheduler", LOG_LEVEL_ALL);
    LogComponentEnable("EpcEnbApplication", LOG_LEVEL_ALL);
    LogComponentEnable("EpcMmeApplication", LOG_LEVEL_ALL);
    LogComponentEnable("EpcPgwApplication", LOG_LEVEL_ALL);
    LogComponentEnable("EpcSgwApplication", LOG_LEVEL_ALL);
    LogComponentEnable("EpcTft", LOG_LEVEL_ALL);
    LogComponentEnable("EpcTftClassifier", LOG_LEVEL_ALL);
    LogComponentEnable("EpcUeNas", LOG_LEVEL_ALL);
    LogComponentEnable("EpcX2", LOG_LEVEL_ALL);
    LogComponentEnable("EpcX2Header", LOG_LEVEL_ALL);
    LogComponentEnable("FdBetFfMacScheduler", LOG_LEVEL_ALL);
    LogComponentEnable("FdMtFfMacScheduler", LOG_LEVEL_ALL);
    LogComponentEnable("FdTbfqFfMacScheduler", LOG_LEVEL_ALL);
    LogComponentEnable("FfMacScheduler", LOG_LEVEL_ALL);
    LogComponentEnable("GtpuHeader", LOG_LEVEL_ALL);
    LogComponentEnable("LteAmc", LOG_LEVEL_ALL);
    LogComponentEnable("LteAnr", LOG_LEVEL_ALL);
    LogComponentEnable("LteChunkProcessor", LOG_LEVEL_ALL);
    LogComponentEnable("LteCommon", LOG_LEVEL_ALL);
    LogComponentEnable("LteControlMessage", LOG_LEVEL_ALL);
    LogComponentEnable("LteEnbComponentCarrierManager", LOG_LEVEL_ALL);
    LogComponentEnable("LteEnbMac", LOG_LEVEL_ALL);
    LogComponentEnable("LteEnbNetDevice", LOG_LEVEL_ALL);
    LogComponentEnable("LteEnbPhy", LOG_LEVEL_ALL);
    LogComponentEnable("LteEnbRrc", LOG_LEVEL_ALL);
    LogComponentEnable("LteFfrAlgorithm", LOG_LEVEL_ALL);
    LogComponentEnable("LteFfrDistributedAlgorithm", LOG_LEVEL_ALL);
    LogComponentEnable("LteFfrEnhancedAlgorithm", LOG_LEVEL_ALL);
    LogComponentEnable("LteFfrSoftAlgorithm", LOG_LEVEL_ALL);
    LogComponentEnable("LteFrHardAlgorithm", LOG_LEVEL_ALL);
    LogComponentEnable("LteFrNoOpAlgorithm", LOG_LEVEL_ALL);
    LogComponentEnable("LteFrSoftAlgorithm", LOG_LEVEL_ALL);
    LogComponentEnable("LteFrStrictAlgorithm", LOG_LEVEL_ALL);
    LogComponentEnable("LteHandoverAlgorithm", LOG_LEVEL_ALL);
    LogComponentEnable("LteHarqPhy", LOG_LEVEL_ALL);
    LogComponentEnable("LteInterference", LOG_LEVEL_ALL);
    LogComponentEnable("LteMiErrorModel", LOG_LEVEL_ALL);
    LogComponentEnable("LteNetDevice", LOG_LEVEL_ALL);
    LogComponentEnable("LtePdcp", LOG_LEVEL_ALL);
    LogComponentEnable("LtePdcpHeader", LOG_LEVEL_ALL);
    LogComponentEnable("LtePhy", LOG_LEVEL_ALL);
    LogComponentEnable("LteRlc", LOG_LEVEL_ALL);
    LogComponentEnable("LteRlcAm", LOG_LEVEL_ALL);
    LogComponentEnable("LteRlcAmHeader", LOG_LEVEL_ALL);
    LogComponentEnable("LteRlcHeader", LOG_LEVEL_ALL);
    LogComponentEnable("LteRlcTm", LOG_LEVEL_ALL);
    LogComponentEnable("LteRlcUm", LOG_LEVEL_ALL);
    LogComponentEnable("LteRrcProtocolIdeal", LOG_LEVEL_ALL);
    LogComponentEnable("LteRrcProtocolReal", LOG_LEVEL_ALL);
    LogComponentEnable("LteSpectrumPhy", LOG_LEVEL_ALL);
    LogComponentEnable("LteSpectrumSignalParameters", LOG_LEVEL_ALL);
    LogComponentEnable("LteSpectrumValueHelper", LOG_LEVEL_ALL);
    LogComponentEnable("LteUeComponentCarrierManager", LOG_LEVEL_ALL);
    LogComponentEnable("LteUeMac", LOG_LEVEL_ALL);
    LogComponentEnable("LteUeNetDevice", LOG_LEVEL_ALL);
    LogComponentEnable("LteUePhy", LOG_LEVEL_ALL);
    LogComponentEnable("LteUePowerControl", LOG_LEVEL_ALL);
    LogComponentEnable("LteUeRrc", LOG_LEVEL_ALL);
    LogComponentEnable("LteVendorSpecificParameters", LOG_LEVEL_ALL);
    LogComponentEnable("NoOpComponentCarrierManager", LOG_LEVEL_ALL);
    LogComponentEnable("NoOpHandoverAlgorithm", LOG_LEVEL_ALL);
    LogComponentEnable("PfFfMacScheduler", LOG_LEVEL_ALL);
    LogComponentEnable("PssFfMacScheduler", LOG_LEVEL_ALL);
    LogComponentEnable("RemSpectrumPhy", LOG_LEVEL_ALL);
    LogComponentEnable("RrcHeader", LOG_LEVEL_ALL);
    LogComponentEnable("RrFfMacScheduler", LOG_LEVEL_ALL);
    LogComponentEnable("SimpleUeComponentCarrierManager", LOG_LEVEL_ALL);
    LogComponentEnable("TdBetFfMacScheduler", LOG_LEVEL_ALL);
    LogComponentEnable("TdMtFfMacScheduler", LOG_LEVEL_ALL);
    LogComponentEnable("TdTbfqFfMacScheduler", LOG_LEVEL_ALL);
    LogComponentEnable("TraceFadingLossModel", LOG_LEVEL_ALL);
    LogComponentEnable("TtaFfMacScheduler", LOG_LEVEL_ALL);
    // Helper directory
    LogComponentEnable("CcHelper", LOG_LEVEL_ALL);
    LogComponentEnable("EmuEpcHelper", LOG_LEVEL_ALL);
    LogComponentEnable("EpcHelper", LOG_LEVEL_ALL);
    LogComponentEnable("LteGlobalPathlossDatabase", LOG_LEVEL_ALL);
    LogComponentEnable("LteHelper", LOG_LEVEL_ALL);
    LogComponentEnable("LteHexGridEnbTopologyHelper", LOG_LEVEL_ALL);
    LogComponentEnable("LteStatsCalculator", LOG_LEVEL_ALL);
    LogComponentEnable("MacStatsCalculator", LOG_LEVEL_ALL);
    LogComponentEnable("PhyRxStatsCalculator", LOG_LEVEL_ALL);
    LogComponentEnable("PhyStatsCalculator", LOG_LEVEL_ALL);
    LogComponentEnable("PhyTxStatsCalculator", LOG_LEVEL_ALL);
    LogComponentEnable("PointToPointEpcHelper", LOG_LEVEL_ALL);
    LogComponentEnable("RadioBearerStatsCalculator", LOG_LEVEL_ALL);
    LogComponentEnable("RadioBearerStatsConnector", LOG_LEVEL_ALL);
    LogComponentEnable("RadioEnvironmentMapHelper", LOG_LEVEL_ALL);
}

void
LteHelper::EnableTraces()
{
    EnablePhyTraces();
    EnableMacTraces();
    EnableRlcTraces();
    EnablePdcpTraces();
}

void
LteHelper::EnableRlcTraces()
{
    NS_ASSERT_MSG(!m_rlcStats,
                  "please make sure that LteHelper::EnableRlcTraces is called at most once");
    m_rlcStats = CreateObject<RadioBearerStatsCalculator>("RLC");
    m_radioBearerStatsConnector.EnableRlcStats(m_rlcStats);
}

int64_t
LteHelper::AssignStreams(NetDeviceContainer c, int64_t stream, bool assignEpcStreams)
{
    NS_LOG_FUNCTION(this << stream << assignEpcStreams);
    int64_t currentStream = stream;
    if (m_fadingModel && !m_fadingStreamsAssigned)
    {
        Ptr<TraceFadingLossModel> tflm = m_fadingModel->GetObject<TraceFadingLossModel>();
        if (tflm)
        {
            currentStream += tflm->AssignStreams(currentStream);
            m_fadingStreamsAssigned = true;
        }
    }
    Ptr<NetDevice> netDevice;
    for (auto i = c.Begin(); i != c.End(); ++i)
    {
        netDevice = (*i);
        Ptr<LteEnbNetDevice> lteEnb = DynamicCast<LteEnbNetDevice>(netDevice);
        if (lteEnb)
        {
            std::map<uint8_t, Ptr<ComponentCarrierBaseStation>> tmpMap = lteEnb->GetCcMap();
            auto it = tmpMap.begin();
            Ptr<LteSpectrumPhy> dlPhy =
                DynamicCast<ComponentCarrierEnb>(it->second)->GetPhy()->GetDownlinkSpectrumPhy();
            Ptr<LteSpectrumPhy> ulPhy =
                DynamicCast<ComponentCarrierEnb>(it->second)->GetPhy()->GetUplinkSpectrumPhy();
            currentStream += dlPhy->AssignStreams(currentStream);
            currentStream += ulPhy->AssignStreams(currentStream);
        }
        Ptr<LteUeNetDevice> lteUe = DynamicCast<LteUeNetDevice>(netDevice);
        if (lteUe)
        {
            std::map<uint8_t, Ptr<ComponentCarrierUe>> tmpMap = lteUe->GetCcMap();
            auto it = tmpMap.begin();
            Ptr<LteSpectrumPhy> dlPhy = it->second->GetPhy()->GetDownlinkSpectrumPhy();
            Ptr<LteSpectrumPhy> ulPhy = it->second->GetPhy()->GetUplinkSpectrumPhy();
            Ptr<LteUeMac> ueMac = lteUe->GetMac();
            currentStream += dlPhy->AssignStreams(currentStream);
            currentStream += ulPhy->AssignStreams(currentStream);
            currentStream += ueMac->AssignStreams(currentStream);
        }
    }
    if (m_epcHelper && assignEpcStreams)
    {
        currentStream += m_epcHelper->AssignStreams(currentStream);
    }
    return (currentStream - stream);
}

void
LteHelper::EnablePhyTraces()
{
    EnableDlPhyTraces();
    EnableUlPhyTraces();
    EnableDlTxPhyTraces();
    EnableUlTxPhyTraces();
    EnableDlRxPhyTraces();
    EnableUlRxPhyTraces();
}

void
LteHelper::EnableDlTxPhyTraces()
{
    Config::Connect(
        "/NodeList/*/DeviceList/*/ComponentCarrierMap/*/LteEnbPhy/DlPhyTransmission",
        MakeBoundCallback(&PhyTxStatsCalculator::DlPhyTransmissionCallback, m_phyTxStats));
}

void
LteHelper::EnableUlTxPhyTraces()
{
    Config::Connect(
        "/NodeList/*/DeviceList/*/ComponentCarrierMapUe/*/LteUePhy/UlPhyTransmission",
        MakeBoundCallback(&PhyTxStatsCalculator::UlPhyTransmissionCallback, m_phyTxStats));
}

void
LteHelper::EnableDlRxPhyTraces()
{
    Config::Connect(
        "/NodeList/*/DeviceList/*/ComponentCarrierMapUe/*/LteUePhy/DlSpectrumPhy/DlPhyReception",
        MakeBoundCallback(&PhyRxStatsCalculator::DlPhyReceptionCallback, m_phyRxStats));
}

void
LteHelper::EnableUlRxPhyTraces()
{
    Config::Connect(
        "/NodeList/*/DeviceList/*/ComponentCarrierMap/*/LteEnbPhy/UlSpectrumPhy/UlPhyReception",
        MakeBoundCallback(&PhyRxStatsCalculator::UlPhyReceptionCallback, m_phyRxStats));
}

void
LteHelper::EnableMacTraces()
{
    EnableDlMacTraces();
    EnableUlMacTraces();
}

void
LteHelper::EnableDlMacTraces()
{
    NS_LOG_FUNCTION_NOARGS();
    Config::Connect("/NodeList/*/DeviceList/*/ComponentCarrierMap/*/LteEnbMac/DlScheduling",
                    MakeBoundCallback(&MacStatsCalculator::DlSchedulingCallback, m_macStats));
}

void
LteHelper::EnableUlMacTraces()
{
    NS_LOG_FUNCTION_NOARGS();
    Config::Connect("/NodeList/*/DeviceList/*/ComponentCarrierMap/*/LteEnbMac/UlScheduling",
                    MakeBoundCallback(&MacStatsCalculator::UlSchedulingCallback, m_macStats));
}

void
LteHelper::EnableDlPhyTraces()
{
    NS_LOG_FUNCTION_NOARGS();
    Config::Connect(
        "/NodeList/*/DeviceList/*/ComponentCarrierMapUe/*/LteUePhy/ReportCurrentCellRsrpSinr",
        MakeBoundCallback(&PhyStatsCalculator::ReportCurrentCellRsrpSinrCallback, m_phyStats));
}

void
LteHelper::EnableUlPhyTraces()
{
    NS_LOG_FUNCTION_NOARGS();
    Config::Connect("/NodeList/*/DeviceList/*/ComponentCarrierMap/*/LteEnbPhy/ReportUeSinr",
                    MakeBoundCallback(&PhyStatsCalculator::ReportUeSinr, m_phyStats));
    Config::Connect("/NodeList/*/DeviceList/*/ComponentCarrierMap/*/LteEnbPhy/ReportInterference",
                    MakeBoundCallback(&PhyStatsCalculator::ReportInterference, m_phyStats));
}

Ptr<RadioBearerStatsCalculator>
LteHelper::GetRlcStats()
{
    return m_rlcStats;
}

void
LteHelper::EnablePdcpTraces()
{
    NS_ASSERT_MSG(!m_pdcpStats,
                  "please make sure that LteHelper::EnablePdcpTraces is called at most once");
    m_pdcpStats = CreateObject<RadioBearerStatsCalculator>("PDCP");
    m_radioBearerStatsConnector.EnablePdcpStats(m_pdcpStats);
}

Ptr<RadioBearerStatsCalculator>
LteHelper::GetPdcpStats()
{
    return m_pdcpStats;
}

} // namespace ns3
