/*
 * SPDX-License-Identifier: GPL-2.0-only
 */

// Default network topology includes some number of AP nodes specified by
// the variable nWifis (defaults to two).  Off of each AP node, there are some
// number of STA nodes specified by the variable nStas (defaults to two).
// Each AP talks to its associated STA nodes.  There are bridge net devices
// on each AP node that bridge the whole thing into one network.
//
//      +-----+      +-----+            +-----+      +-----+
//      | STA |      | STA |            | STA |      | STA |
//      +-----+      +-----+            +-----+      +-----+
//    192.168.0.2  192.168.0.3        192.168.0.5  192.168.0.6
//      --------     --------           --------     --------
//      WIFI STA     WIFI STA           WIFI STA     WIFI STA
//      --------     --------           --------     --------
//        ((*))       ((*))       |      ((*))        ((*))
//                                |
//              ((*))             |             ((*))
//             -------                         -------
//             WIFI AP   CSMA ========= CSMA   WIFI AP
//             -------   ----           ----   -------
//             ##############           ##############
//                 BRIDGE                   BRIDGE
//             ##############           ##############
//               192.168.0.1              192.168.0.4
//               +---------+              +---------+
//               | AP Node |              | AP Node |
//               +---------+              +---------+

#include "ns3/bridge-helper.h"
#include "ns3/command-line.h"
#include "ns3/csma-helper.h"
#include "ns3/double.h"
#include "ns3/internet-stack-helper.h"
#include "ns3/ipv4-address-helper.h"
#include "ns3/mobility-helper.h"
#include "ns3/on-off-helper.h"
#include "ns3/packet-socket-address.h"
#include "ns3/rectangle.h"
#include "ns3/ssid.h"
#include "ns3/string.h"
#include "ns3/uinteger.h"
#include "ns3/yans-wifi-channel.h"
#include "ns3/yans-wifi-helper.h"

using namespace ns3;

int
main(int argc, char* argv[])
{
    uint32_t nWifis = 2;
    uint32_t nStas = 2;
    bool sendIp = true;
    bool writeMobility = false;

    CommandLine cmd(__FILE__);
    cmd.AddValue("nWifis", "Number of wifi networks", nWifis);
    cmd.AddValue("nStas", "Number of stations per wifi network", nStas);
    cmd.AddValue("SendIp", "Send Ipv4 or raw packets", sendIp);
    cmd.AddValue("writeMobility", "Write mobility trace", writeMobility);
    cmd.Parse(argc, argv);

    NodeContainer backboneNodes;
    NetDeviceContainer backboneDevices;
    Ipv4InterfaceContainer backboneInterfaces;
    std::vector<NodeContainer> staNodes;
    std::vector<NetDeviceContainer> staDevices;
    std::vector<NetDeviceContainer> apDevices;
    std::vector<Ipv4InterfaceContainer> staInterfaces;
    std::vector<Ipv4InterfaceContainer> apInterfaces;

    InternetStackHelper stack;
    CsmaHelper csma;
    Ipv4AddressHelper ip;
    ip.SetBase("192.168.0.0", "255.255.255.0");

    backboneNodes.Create(nWifis);
    stack.Install(backboneNodes);

    backboneDevices = csma.Install(backboneNodes);

    meter_u wifiX = 0.0;

    YansWifiPhyHelper wifiPhy;
    wifiPhy.SetPcapDataLinkType(WifiPhyHelper::DLT_IEEE802_11_RADIO);

    for (uint32_t i = 0; i < nWifis; ++i)
    {
        // calculate ssid for wifi subnetwork
        std::ostringstream oss;
        oss << "wifi-default-" << i;
        Ssid ssid = Ssid(oss.str());

        NodeContainer sta;
        NetDeviceContainer staDev;
        NetDeviceContainer apDev;
        Ipv4InterfaceContainer staInterface;
        Ipv4InterfaceContainer apInterface;
        MobilityHelper mobility;
        BridgeHelper bridge;
        WifiHelper wifi;
        WifiMacHelper wifiMac;
        YansWifiChannelHelper wifiChannel = YansWifiChannelHelper::Default();
        wifiPhy.SetChannel(wifiChannel.Create());

        sta.Create(nStas);
        mobility.SetPositionAllocator("ns3::GridPositionAllocator",
                                      "MinX",
                                      DoubleValue(wifiX),
                                      "MinY",
                                      DoubleValue(0.0),
                                      "DeltaX",
                                      DoubleValue(5.0),
                                      "DeltaY",
                                      DoubleValue(5.0),
                                      "GridWidth",
                                      UintegerValue(1),
                                      "LayoutType",
                                      StringValue("RowFirst"));

        // setup the AP.
        mobility.SetMobilityModel("ns3::ConstantPositionMobilityModel");
        mobility.Install(backboneNodes.Get(i));
        wifiMac.SetType("ns3::ApWifiMac", "Ssid", SsidValue(ssid));
        apDev = wifi.Install(wifiPhy, wifiMac, backboneNodes.Get(i));

        NetDeviceContainer bridgeDev;
        bridgeDev =
            bridge.Install(backboneNodes.Get(i), NetDeviceContainer(apDev, backboneDevices.Get(i)));

        // assign AP IP address to bridge, not wifi
        apInterface = ip.Assign(bridgeDev);

        // setup the STAs
        stack.Install(sta);
        mobility.SetMobilityModel(
            "ns3::RandomWalk2dMobilityModel",
            "Mode",
            StringValue("Time"),
            "Time",
            StringValue("2s"),
            "Speed",
            StringValue("ns3::ConstantRandomVariable[Constant=1.0]"),
            "Bounds",
            RectangleValue(Rectangle(wifiX, wifiX + 5.0, 0.0, (nStas + 1) * 5.0)));
        mobility.Install(sta);
        wifiMac.SetType("ns3::StaWifiMac", "Ssid", SsidValue(ssid));
        staDev = wifi.Install(wifiPhy, wifiMac, sta);
        staInterface = ip.Assign(staDev);

        // save everything in containers.
        staNodes.push_back(sta);
        apDevices.push_back(apDev);
        apInterfaces.push_back(apInterface);
        staDevices.push_back(staDev);
        staInterfaces.push_back(staInterface);

        wifiX += 20.0;
    }

    Address dest;
    std::string protocol;
    if (sendIp)
    {
        dest = InetSocketAddress(staInterfaces[1].GetAddress(1), 1025);
        protocol = "ns3::UdpSocketFactory";
    }
    else
    {
        PacketSocketAddress tmp;
        tmp.SetSingleDevice(staDevices[0].Get(0)->GetIfIndex());
        tmp.SetPhysicalAddress(staDevices[1].Get(0)->GetAddress());
        tmp.SetProtocol(0x807);
        dest = tmp;
        protocol = "ns3::PacketSocketFactory";
    }

    OnOffHelper onoff = OnOffHelper(protocol, dest);
    onoff.SetConstantRate(DataRate("500kb/s"));
    ApplicationContainer apps = onoff.Install(staNodes[0].Get(0));
    apps.Start(Seconds(0.5));
    apps.Stop(Seconds(3));

    wifiPhy.EnablePcap("wifi-wired-bridging", apDevices[0]);
    wifiPhy.EnablePcap("wifi-wired-bridging", apDevices[1]);

    if (writeMobility)
    {
        AsciiTraceHelper ascii;
        MobilityHelper::EnableAsciiAll(ascii.CreateFileStream("wifi-wired-bridging.mob"));
    }

    Simulator::Stop(Seconds(5));
    Simulator::Run();
    Simulator::Destroy();

    return 0;
}
