// Adapted from NS3 examples/dce-mptcp-lte-wifi.cc

#include "ns3/network-module.h"
#include "ns3/core-module.h"
#include "ns3/internet-module.h"
#include "ns3/dce-module.h"
#include "ns3/point-to-point-module.h"
#include "ns3/mobility-module.h"
#include "ns3/applications-module.h"
#include "ns3/netanim-module.h"
#include "ns3/constant-position-mobility-model.h"
#include "ns3/config-store-module.h"

using namespace ns3;

void setPos (Ptr<Node> n, int x, int y, int z)
{
    Ptr<ConstantPositionMobilityModel> loc = CreateObject<ConstantPositionMobilityModel> ();
    n->AggregateObject (loc);
    Vector locVec2 (x, y, z);
    loc->SetPosition (locVec2);
}

int main (int argc, char *argv[])
{
    double stopTime = 20.0;
    std::string p2pdelay = "50ms";
    int delay = 0;

    CommandLine cmd;
    cmd.AddValue ("stopTime", "StopTime of simulation.", stopTime);
    cmd.AddValue ("p2pDelay", "Delay of p2p links. default is 50ms.", p2pdelay);
    cmd.Parse (argc, argv);

    PointToPointHelper pointToPoint;

    NodeContainer nodes;
    LinuxStackHelper stack;
    DceManagerHelper dceManager;

    GlobalValue::Bind ("ChecksumEnabled", BooleanValue (false));
    nodes.Create (3);

    setPos (nodes.Get (0), 50, 15, 0);
    setPos (nodes.Get (1), 100, 15, 0);
    setPos (nodes.Get (2), 150, 15, 0);

    dceManager.SetTaskManagerAttribute ("FiberManagerType", StringValue ("UcontextFiberManager"));
    dceManager.SetNetworkStack ("ns3::LinuxSocketFdFactory", "Library", StringValue ("liblinux.so"));

    stack.Install (nodes);
    dceManager.Install (nodes);

    pointToPoint.SetChannelAttribute ("Delay", StringValue (p2pdelay));
    pointToPoint.SetChannelAttribute ("Delay", StringValue ("0ms"));
    pointToPoint.Install(nodes.Get(1), nodes.Get(2));
    pointToPoint.Install(nodes.Get(0), nodes.Get(1));
    
    /*Setup Server*/
    LinuxStackHelper::RunIp (nodes.Get (2), Seconds (0.1), "link set up dev sim0");
    LinuxStackHelper::RunIp (nodes.Get (2), Seconds (0.1), "addr add 172.16.1.1/24 dev sim0");

    /*Setup Gateway->Server*/
    LinuxStackHelper::RunIp (nodes.Get (1), Seconds (0.1), "link set up dev sim0");
    LinuxStackHelper::RunIp (nodes.Get (1), Seconds (0.1), "addr add 172.16.1.10/24 dev sim0");

    /*Setup Client -> Gateway*/
    LinuxStackHelper::RunIp (nodes.Get (0), Seconds (0.1), "link set up dev sim0");
    LinuxStackHelper::RunIp (nodes.Get (0), Seconds (0.1), "addr add 192.168.0.10/24 dev sim0");
    LinuxStackHelper::RunIp (nodes.Get (0), Seconds (0.1), "route add default via 192.168.0.1 dev sim0");

    LinuxStackHelper::RunIp (nodes.Get (1), Seconds (0.1), "link set up dev sim1");
    LinuxStackHelper::RunIp (nodes.Get (1), Seconds (0.1), "addr add 192.168.0.1/24 dev sim1");

    LinuxStackHelper::RunIp (nodes.Get (0), Seconds (1), "addr show");
    LinuxStackHelper::RunIp (nodes.Get (0), Seconds (1), "rule show");
    LinuxStackHelper::RunIp (nodes.Get (0), Seconds (1), "route show");

    LinuxStackHelper::RunIp (nodes.Get (1), Seconds (1), "addr show");
    LinuxStackHelper::RunIp (nodes.Get (1), Seconds (1), "rule show");
    LinuxStackHelper::RunIp (nodes.Get (1), Seconds (1), "route show");

    LinuxStackHelper::RunIp (nodes.Get (2), Seconds (1), "addr show");
    LinuxStackHelper::RunIp (nodes.Get (2), Seconds (1), "rule show");
    LinuxStackHelper::RunIp (nodes.Get (2), Seconds (1), "route show");

    DceApplicationHelper dce;
    ApplicationContainer apps;

    dce.SetStackSize (1 << 20);

    dce.SetBinary ("xtables-multi");
    dce.ResetArguments ();
    dce.ResetEnvironment ();
    dce.AddArgument ("iptables");
    dce.AddArgument ("-t");
    dce.AddArgument ("nat");
    dce.AddArgument ("-A");
    dce.AddArgument ("POSTROUTING");
    dce.AddArgument ("-o");
    dce.AddArgument ("sim0");
    dce.AddArgument ("-j");
    dce.AddArgument ("MASQUERADE");

    apps = dce.Install(nodes.Get(1));
    apps.Start(Seconds (2.0));

    dce.SetBinary ("ping");
    dce.ResetArguments ();
    dce.ResetEnvironment ();
    dce.AddArgument ("172.16.1.1");

    apps = dce.Install(nodes.Get(0));
    apps.Start(Seconds (5.0));

    pointToPoint.EnablePcapAll("dce-nat");

    // Output config store to txt format
    Config::SetDefault ("ns3::ConfigStore::Filename", StringValue ("output-attributes.txt"));
    Config::SetDefault ("ns3::ConfigStore::FileFormat", StringValue ("RawText"));
    Config::SetDefault ("ns3::ConfigStore::Mode", StringValue ("Save"));
    ConfigStore outputConfig2;
    outputConfig2.ConfigureDefaults ();
    outputConfig2.ConfigureAttributes ();

    Simulator::Stop (Seconds (stopTime));
    Simulator::Run ();
    Simulator::Destroy ();

    return 0;
}
