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

using namespace ns3;

/**
 * Sample simulation script for LTE+EPC. It instantiates several eNodeB,
 * attaches one UE per eNodeB starts a flow for each UE to  and from a remote host.
 * It also  starts yet another flow between each UE pair.
 * https://de.wikipedia.org/wiki/Evolved_Packet_System
 */

NS_LOG_COMPONENT_DEFINE("LTEExample");

void createPointToPointTopology(NodeContainer* p2pNodes, PointToPointHelper* pointToPointHelper,
        NetDeviceContainer* p2pDevices, std::string datarate, std::string delay) {

    //create P2P Helper & set attributes to P2P Helper
    pointToPointHelper->SetDeviceAttribute("DataRate", StringValue(datarate));
    pointToPointHelper->SetDeviceAttribute("Mtu", UintegerValue(1492));
    pointToPointHelper->SetChannelAttribute("Delay", StringValue(delay));

    // create Net Devices for a P2P Connection between the 2 Nodes
    *p2pDevices = pointToPointHelper->Install(*p2pNodes);

    return;
}

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

    LogComponentEnable("UdpEchoClientApplication", LOG_LEVEL_INFO);
    LogComponentEnable("UdpEchoServerApplication", LOG_LEVEL_INFO);
    LogComponentEnable("LTEExample", LOG_LEVEL_ALL);

    CommandLine cmd;
    cmd.Parse(argc, argv);

    uint32_t numberOfNodes = 2;
    double distance = 60.0;
    std::string confFile = "scratch/testFile.conf";

    /**
     * BRITE
     */
    // Invoke the BriteTopologyHelper and pass in a BRITE
    // configuration file and a seed file. This will use
    // BRITE to build a graph from which we can build the ns-3 topology
    BriteTopologyHelper bth(confFile);
    bth.AssignStreams(3);

    Ipv4StaticRoutingHelper staticRouting;
    Ipv4ListRoutingHelper listRouting;
    Ipv4GlobalRoutingHelper globalRouting;

    InternetStackHelper stack;

    listRouting.Add(staticRouting, 0);
    listRouting.Add(globalRouting, 10);
    stack.SetRoutingHelper(listRouting);

    Ipv4AddressHelper address;
    address.SetBase("80.0.0.0", "255.255.255.252");

    bth.BuildBriteTopology(stack);
    bth.AssignIpv4Addresses(address);
    NS_LOG_INFO("Number of AS created " << bth.GetNAs ());

    /**
     * Remote Hosts Node and Connection to BRITE Topology
     */
    NodeContainer remoteHostContainer;
    remoteHostContainer.Create(1);
    remoteHostContainer.Add(bth.GetLeafNodeForAs(0, 0));

    PointToPointHelper remoteHostP2pHelper;
    NetDeviceContainer remoteHostDevices;
    Ipv4InterfaceContainer remoteHostInterfaces;
    createPointToPointTopology(&remoteHostContainer, &remoteHostP2pHelper, &remoteHostDevices, "1Gbps", "1ms");
    stack.Install(remoteHostContainer.Get(0));
    address.SetBase("176.0.1.0", "255.255.255.252");
    remoteHostInterfaces = address.Assign(remoteHostDevices);

    std::cout << "Polulating Routing Tables..." << std::endl;
    Ipv4GlobalRoutingHelper::PopulateRoutingTables();

    /**
     * LTE Topology
     *
     * Create LTE Helpter and Evolved Packet Core (EPC) Helpter.
     * EPC is the architecture of the LTE core network.
     *
     *                      HSS
     *       eNode ---- MME -'
     *  UE <   ANDSF  \             Internet
     *      |          SGW -- PGW -/
     *      ePDG -----/
     */
    Ptr<LteHelper> lteHelper = CreateObject<LteHelper>();
    Ptr<PointToPointEpcHelper> epcHelper = CreateObject<PointToPointEpcHelper>();
    lteHelper->SetEpcHelper(epcHelper);

    /**
     * Remote Hosts Node and Connection to BRITE Topology
     */
    NodeContainer pgwNodeContainer;
    pgwNodeContainer.Add(epcHelper->GetPgwNode());
    pgwNodeContainer.Add(bth.GetLeafNodeForAs(bth.GetNAs() - 1, bth.GetNLeafNodesForAs(bth.GetNAs() - 1) - 1));

    PointToPointHelper pgwP2pHelper;
    NetDeviceContainer pgwDevices;
    Ipv4InterfaceContainer pgwInterfaces;
    createPointToPointTopology(&pgwNodeContainer, &pgwP2pHelper, &pgwDevices, "1Gbps", "2ms");
//	stack.Install(pgwNodeContainer.Get(0));
    address.SetBase("176.0.2.0", "255.255.255.252");
    pgwInterfaces = address.Assign(pgwDevices);


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

    Ptr<ListPositionAllocator> positionAllocator = CreateObject<ListPositionAllocator>();
    for (uint32_t i = 0; i < numberOfNodes; i++) {
        positionAllocator->Add(Vector(distance * i, 0, 0));
    }

    MobilityHelper mobility;
    mobility.SetMobilityModel("ns3::ConstantPositionMobilityModel");
    mobility.SetPositionAllocator(positionAllocator);
    mobility.Install(enbNodes);
    mobility.Install(ueNodes);

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

    stack.Install(ueNodes);
//	stack.Install(enbNodes);


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

//    Ipv4StaticRoutingHelper ipv4RoutingHelper2;
    Ptr<Ipv4StaticRouting> remoteHostStaticRouting2 = staticRouting.GetStaticRouting(
            pgwNodeContainer.Get(0)->GetObject<Ipv4>());
    remoteHostStaticRouting2->AddNetworkRouteTo(Ipv4Address("176.0.1.0"), Ipv4Mask("255.255.255.252"), 1);

    Ipv4InterfaceContainer ueInterfaces;
    ueInterfaces = epcHelper->AssignUeIpv4Address(ueDevices);
    // 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 = staticRouting.GetStaticRouting(ueNode->GetObject<Ipv4>());
        ueStaticRouting->SetDefaultRoute(epcHelper->GetUeDefaultGatewayAddress(), 1);
    }

    // Attach one UE per eNodeB
    for (uint32_t i = 0; i < numberOfNodes; i++) {
        lteHelper->Attach(ueDevices.Get(i), enbDevices.Get(i));
        // side effect: the default EPS bearer will be activated
    }

    /**
     * Application-Level
     */
    UdpEchoServerHelper echoServer(80);
    ApplicationContainer serverApps = echoServer.Install(remoteHostContainer.Get(0));
    serverApps.Start(Seconds(1.0));
    serverApps.Stop(Seconds(12.0));

    UdpEchoClientHelper echoClient(remoteHostInterfaces.GetAddress(0), 80);
    echoClient.SetAttribute("MaxPackets", UintegerValue(5));
    echoClient.SetAttribute("Interval", TimeValue(Seconds(2.0)));
    echoClient.SetAttribute("PacketSize", UintegerValue(1472));

    ApplicationContainer clientApps = echoClient.Install(ueNodes.Get(0));
    clientApps.Start(Seconds(2.0));
    clientApps.Stop(Seconds(12.0));

    // this will not work...
//	std::cout << "Polulating Routing Tables 2nd..." << std::endl;
//	Ipv4GlobalRoutingHelper::PopulateRoutingTables();

    stack.EnablePcapIpv4All("pcap/myLTE");


    // Run the simulator
    Simulator::Stop(Seconds(14.0));
    Simulator::Run();
    Simulator::Destroy();

    return 0;
}

