#include "ns3/core-module.h"
#include "ns3/simulator-module.h"
#include "ns3/node-module.h"
#include "ns3/helper-module.h"
#include "ns3/global-routing-module.h"
#include "ns3/wifi-module.h"
#include "ns3/mobility-module.h"

// Simplified Network Topology
//
//   Wifi 10.1.3.0
//                 AP   
//  *    *    *    *
//  |    |    |    |    10.1.1.0
// n5   n6 [RECV]  n0 --------------[SENDER]
//                   point-to-point
//                                 
//                                 

using namespace ns3;
using namespace std;

NS_LOG_COMPONENT_DEFINE("ThirdScriptExample");

int
main(int argc, char *argv[]) {
    Ipv4Address mcast_addr("239.192.36.3");
    LogComponentEnable("UdpEchoClientApplication", LOG_LEVEL_INFO);
    LogComponentEnable("UdpEchoServerApplication", LOG_LEVEL_INFO);

    uint32_t nCsma = 3;
    uint32_t nWifi = 3;
    CommandLine cmd;
    cmd.AddValue("nCsma", "Number of \"extra\" CSMA nodes/devices", nCsma);
    cmd.AddValue("nWifi", "Number of wifi STA devices", nWifi);
    cmd.Parse(argc, argv);

    NodeContainer p2pNodes;
    p2pNodes.Create(2);

    PointToPointHelper pointToPoint;
    pointToPoint.SetDeviceAttribute("DataRate", StringValue("5Mbps"));
    pointToPoint.SetChannelAttribute("Delay", StringValue("2ms"));

    NetDeviceContainer p2pDevices;
    p2pDevices = pointToPoint.Install(p2pNodes);

    NodeContainer wifiStaNodes;
    wifiStaNodes.Create(nWifi);
    NodeContainer wifiApNode = p2pNodes.Get(0);

    // setup the wifi network.
    WifiHelper wifi = WifiHelper::Default();
    YansWifiChannelHelper wifiChannel = YansWifiChannelHelper::Default();
    YansWifiPhyHelper wifiPhy = YansWifiPhyHelper::Default();
    wifiPhy.SetChannel(wifiChannel.Create());

    wifi.SetRemoteStationManager("ns3::ArfWifiManager");

    Ssid ssid = Ssid("ns-3-ssid");
    wifi.SetMac("ns3::NqstaWifiMac",
            "Ssid", SsidValue(ssid),
            "ActiveProbing", BooleanValue(false));

    NetDeviceContainer staDevices;
    staDevices = wifi.Install(wifiPhy, wifiStaNodes);

    wifi.SetMac("ns3::NqapWifiMac",
            "Ssid", SsidValue(ssid),
            "BeaconGeneration", BooleanValue(true),
            "BeaconInterval", TimeValue(Seconds(2.5)));

    NetDeviceContainer apDevices;
    apDevices = wifi.Install(wifiPhy, wifiApNode);

    MobilityHelper mobility;

    mobility.SetPositionAllocator("ns3::GridPositionAllocator",
            "MinX", DoubleValue(0.0),
            "MinY", DoubleValue(0.0),
            "DeltaX", DoubleValue(5.0),
            "DeltaY", DoubleValue(10.0),
            "GridWidth", UintegerValue(3),
            "LayoutType", StringValue("RowFirst"));

    mobility.SetMobilityModel("ns3::RandomWalk2dMobilityModel",
            "Bounds", RectangleValue(Rectangle(-50, 50, -50, 50)));
    mobility.Install(wifiStaNodes);

    mobility.SetMobilityModel("ns3::StaticMobilityModel");
    mobility.Install(wifiApNode);

    InternetStackHelper stack;
    stack.Install(wifiApNode);
    stack.Install(wifiStaNodes);
    stack.Install(p2pNodes.Get(1));

    Ipv4AddressHelper address;

    address.SetBase("10.1.1.0", "255.255.255.0");
    Ipv4InterfaceContainer p2pInterfaces;
    p2pInterfaces = address.Assign(p2pDevices);

    address.SetBase("10.1.2.0", "255.255.255.0");
    Ipv4InterfaceContainer staInterfaces=address.Assign(staDevices);
    address.Assign(apDevices);

    UdpEchoServerHelper echoServer(9);

    ApplicationContainer serverApps = echoServer.Install(wifiStaNodes.Get(nWifi - 1));
    serverApps.Start(Seconds(1.0));
    serverApps.Stop(Seconds(10.0));

    //UdpEchoClientHelper echoClient(staInterfaces.GetAddress(nWifi-1), 9);
    UdpEchoClientHelper echoClient(mcast_addr, 9);
    echoClient.SetAttribute("MaxPackets", UintegerValue(1));
    echoClient.SetAttribute("Interval", TimeValue(Seconds(1.)));
    echoClient.SetAttribute("PacketSize", UintegerValue(1024));

    ApplicationContainer clientApps =
            echoClient.Install(p2pNodes.Get(1));
    clientApps.Start(Seconds(2.0));
    clientApps.Stop(Seconds(10.0));
    
    //sender of the multicast datagram
    Ptr<Ipv4> ip=p2pNodes.Get(1)->GetObject<Ipv4>();
    ip->SetDefaultMulticastRoute(1); //0 loopback, 1 p2p
    
    //intermediate node
    ip=wifiApNode.Get(1)->GetObject<Ipv4>();
    ip->SetDefaultMulticastRoute(2); //0 loopback, 1 p2p, 2 wifi
    
    //destination node
    ip=wifiStaNodes.Get(nWifi-1)->GetObject<Ipv4>();
    ip->JoinMulticastGroup(Ipv4Address::GetAny(),mcast_addr);
    
    //no global routing
    //GlobalRouteManager::PopulateRoutingTables();

    Simulator::Stop(Seconds(10.0));

    YansWifiPhyHelper::EnablePcap("sta",wifiStaNodes);
    YansWifiPhyHelper::EnablePcap("ap",wifiApNode);
    PointToPointHelper::EnablePcap("p2p",p2pNodes);

    Simulator::Run();
    Simulator::Destroy();
    return 0;
}
