/*
 * Copyright (c) 2011 Centre Tecnologic de Telecomunicacions de Catalunya (CTTC)
 *
 * SPDX-License-Identifier: GPL-2.0-only
 *
 * Author: Gaurav Sathe <gaurav.sathe@tcs.com>
 */

#include "ns3/applications-module.h"
#include "ns3/config-store.h"
#include "ns3/core-module.h"
#include "ns3/epc-helper.h"
#include "ns3/internet-module.h"
#include "ns3/ipv4-global-routing-helper.h"
#include "ns3/lte-helper.h"
#include "ns3/lte-module.h"
#include "ns3/mobility-module.h"
#include "ns3/network-module.h"
#include "ns3/point-to-point-helper.h"
// #include "ns3/gtk-config-store.h"

using namespace ns3;

/**
 * Sample simulation script for LTE+EPC. It instantiates one eNodeB,
 * attaches three UE to eNodeB starts a flow for each UE to  and from a remote host.
 * It also instantiates one dedicated bearer per UE
 */
NS_LOG_COMPONENT_DEFINE("BearerDeactivateExample");

int
main(int argc, char* argv[])
{
    uint16_t numberOfNodes = 1;
    uint16_t numberOfUeNodes = 3;
    double simTime = 1.1;
    double distance = 60.0;
    double interPacketInterval = 100;

    // Command line arguments
    CommandLine cmd(__FILE__);
    cmd.AddValue("numberOfNodes", "Number of eNodeBs + UE pairs", numberOfNodes);
    cmd.AddValue("simTime", "Total duration of the simulation [s])", simTime);
    cmd.AddValue("distance", "Distance between eNBs [m]", distance);
    cmd.AddValue("interPacketInterval", "Inter packet interval [ms])", interPacketInterval);
    cmd.Parse(argc, argv);

    Ptr<LteHelper> lteHelper = CreateObject<LteHelper>();
    Ptr<PointToPointEpcHelper> epcHelper = CreateObject<PointToPointEpcHelper>();
    lteHelper->SetEpcHelper(epcHelper);

    ConfigStore inputConfig;
    inputConfig.ConfigureDefaults();

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

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

    // Enable Logging
    auto logLevel = (LogLevel)(LOG_PREFIX_FUNC | LOG_PREFIX_TIME | LOG_LEVEL_ALL);

    LogComponentEnable("BearerDeactivateExample", LOG_LEVEL_ALL);
    LogComponentEnable("LteHelper", logLevel);
    LogComponentEnable("EpcHelper", logLevel);
    LogComponentEnable("EpcEnbApplication", logLevel);
    LogComponentEnable("EpcMmeApplication", logLevel);
    LogComponentEnable("EpcPgwApplication", logLevel);
    LogComponentEnable("EpcSgwApplication", logLevel);
    LogComponentEnable("LteEnbRrc", logLevel);

    // 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);
    Ipv4AddressHelper ipv4h;
    ipv4h.SetBase("1.0.0.0", "255.0.0.0");
    Ipv4InterfaceContainer internetIpIfaces = ipv4h.Assign(internetDevices);
    // interface 0 is localhost, 1 is the p2p device
    Ipv4Address remoteHostAddr = internetIpIfaces.GetAddress(1);

    Ipv4StaticRoutingHelper ipv4RoutingHelper;
    Ptr<Ipv4StaticRouting> remoteHostStaticRouting =
        ipv4RoutingHelper.GetStaticRouting(remoteHost->GetObject<Ipv4>());
    remoteHostStaticRouting->AddNetworkRouteTo(Ipv4Address("7.0.0.0"), Ipv4Mask("255.0.0.0"), 1);

    NodeContainer ueNodes;
    NodeContainer enbNodes;
    enbNodes.Create(numberOfNodes);
    ueNodes.Create(numberOfUeNodes);

    // Install Mobility Model
    Ptr<ListPositionAllocator> positionAlloc = CreateObject<ListPositionAllocator>();
    for (uint16_t i = 0; i < numberOfNodes; i++)
    {
        positionAlloc->Add(Vector(distance * i, 0, 0));
    }
    MobilityHelper mobility;
    mobility.SetMobilityModel("ns3::ConstantPositionMobilityModel");
    mobility.SetPositionAllocator(positionAlloc);
    mobility.Install(enbNodes);
    mobility.Install(ueNodes);

    // Install LTE Devices to the nodes
    NetDeviceContainer enbLteDevs = lteHelper->InstallEnbDevice(enbNodes);
    NetDeviceContainer ueLteDevs = lteHelper->InstallUeDevice(ueNodes);

    // Install the IP stack on the UEs
    internet.Install(ueNodes);
    Ipv4InterfaceContainer ueIpIface;
    ueIpIface = epcHelper->AssignUeIpv4Address(NetDeviceContainer(ueLteDevs));
    // Assign IP address to UEs, and install applications
    for (uint32_t u = 0; u < ueNodes.GetN(); ++u)
    {
        Ptr<Node> ueNode = ueNodes.Get(u);
        // Set the default gateway for the UE
        Ptr<Ipv4StaticRouting> ueStaticRouting =
            ipv4RoutingHelper.GetStaticRouting(ueNode->GetObject<Ipv4>());
        ueStaticRouting->SetDefaultRoute(epcHelper->GetUeDefaultGatewayAddress(), 1);
    }

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

    // Activate an EPS bearer on all UEs

    for (uint32_t u = 0; u < ueNodes.GetN(); ++u)
    {
        Ptr<NetDevice> ueDevice = ueLteDevs.Get(u);
        GbrQosInformation qos;
        qos.gbrDl = 132; // bit/s, considering IP, UDP, RLC, PDCP header size
        qos.gbrUl = 132;
        qos.mbrDl = qos.gbrDl;
        qos.mbrUl = qos.gbrUl;

        EpsBearer::Qci q = EpsBearer::GBR_CONV_VOICE;
        EpsBearer bearer(q, qos);
        bearer.arp.priorityLevel = 15 - (u + 1);
        bearer.arp.preemptionCapability = true;
        bearer.arp.preemptionVulnerability = true;
        lteHelper->ActivateDedicatedEpsBearer(ueDevice, bearer, EpcTft::Default());
    }

    // Install and start applications on UEs and remote host
    uint16_t dlPort = 1234;
    uint16_t ulPort = 2000;
    uint16_t otherPort = 3000;
    ApplicationContainer clientApps;
    ApplicationContainer serverApps;
    for (uint32_t u = 0; u < ueNodes.GetN(); ++u)
    {
        ++ulPort;
        ++otherPort;
        PacketSinkHelper dlPacketSinkHelper("ns3::UdpSocketFactory",
                                            InetSocketAddress(Ipv4Address::GetAny(), dlPort));
        PacketSinkHelper ulPacketSinkHelper("ns3::UdpSocketFactory",
                                            InetSocketAddress(Ipv4Address::GetAny(), ulPort));
        PacketSinkHelper packetSinkHelper("ns3::UdpSocketFactory",
                                          InetSocketAddress(Ipv4Address::GetAny(), otherPort));
        serverApps.Add(dlPacketSinkHelper.Install(ueNodes.Get(u)));
        serverApps.Add(ulPacketSinkHelper.Install(remoteHost));
        serverApps.Add(packetSinkHelper.Install(ueNodes.Get(u)));

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

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

        UdpClientHelper client(ueIpIface.GetAddress(u), otherPort);
        client.SetAttribute("Interval", TimeValue(MilliSeconds(interPacketInterval)));
        client.SetAttribute("MaxPackets", UintegerValue(1000000));

        clientApps.Add(dlClient.Install(remoteHost));
        clientApps.Add(ulClient.Install(ueNodes.Get(u)));
        if (u + 1 < ueNodes.GetN())
        {
            clientApps.Add(client.Install(ueNodes.Get(u + 1)));
        }
        else
        {
            clientApps.Add(client.Install(ueNodes.Get(0)));
        }
    }

    serverApps.Start(Seconds(0.030));
    clientApps.Start(Seconds(0.030));

    double statsStartTime = 0.04; // need to allow for RRC connection establishment + SRS
    double statsDuration = 1.0;

    lteHelper->EnableRlcTraces();
    Ptr<RadioBearerStatsCalculator> rlcStats = lteHelper->GetRlcStats();
    rlcStats->SetAttribute("StartTime", TimeValue(Seconds(statsStartTime)));
    rlcStats->SetAttribute("EpochDuration", TimeValue(Seconds(statsDuration)));

    // get ue device pointer for UE-ID 0 IMSI 1 and enb device pointer
    Ptr<NetDevice> ueDevice = ueLteDevs.Get(0);
    Ptr<NetDevice> enbDevice = enbLteDevs.Get(0);

    /*
     *   Instantiate De-activation using Simulator::Schedule() method which will initiate bearer
     * de-activation after deActivateTime Instantiate De-activation in sequence (Time const &time,
     * MEM mem_ptr, OBJ obj, T1 a1, T2 a2, T3 a3)
     */
    Time deActivateTime(Seconds(1.5));
    Simulator::Schedule(deActivateTime,
                        &LteHelper::DeActivateDedicatedEpsBearer,
                        lteHelper,
                        ueDevice,
                        enbDevice,
                        2);

    // stop simulation after 3 seconds
    Simulator::Stop(Seconds(3));

    Simulator::Run();
    /*GtkConfigStore config;
    config.ConfigureAttributes();*/

    Simulator::Destroy();
    return 0;
}
