#include "ns3/core-module.h"
#include "ns3/network-module.h"
#include "ns3/mobility-module.h"
#include "ns3/simulator.h"
#include "ns3/log.h"

#include <vector>
#include <algorithm>
#include <cmath>
#include <iostream>
#include <fstream>

using namespace ns3;

NS_LOG_COMPONENT_DEFINE ("SdnLoRaWanRoutingSimulation");

struct GatewayInfo {
    uint32_t gatewayId;
    uint32_t queueLength; 
    Vector position;    
};


class SdnController {
private:
    double alpha; 
    double beta;  
    double gamma; 

public:
    SdnController(double a, double b, double g) : alpha(a), beta(b), gamma(g) {}

   
    double CalculateCost(double residualEnergy, uint32_t queueLength, double distance) {
        double energyFactor = (residualEnergy > 0.0) ? (1.0 / residualEnergy) : 1000.0;
       
        double cost = (alpha * energyFactor) + (beta * static_cast<double>(queueLength)) + (gamma * distance);
        return cost;
    }

   
    uint32_t SelectOptimalGateway(const std::vector<GatewayInfo>& gateways, double deviceEnergy, Vector devicePos) {
        uint32_t bestGatewayId = 0;
        double minCost = 1e9;

        for (const auto& gw : gateways) {
           
            double distance = std::sqrt(std::pow(devicePos.x - gw.position.x, 2) + 
                                        std::pow(devicePos.y - gw.position.y, 2));

            double cost = CalculateCost(deviceEnergy, gw.queueLength, distance);
            if (cost < minCost) {
                minCost = cost;
                bestGatewayId = gw.gatewayId;
            }
        }
        return bestGatewayId;
    }
};

int main(int argc, char *argv[]) {
    Time::SetResolution (Time::NS);
    LogComponentEnable ("SdnLoRaWanRoutingSimulation", LOG_LEVEL_INFO);

    NS_LOG_INFO ("Initializing Advanced SDN-LoRaWAN Simulation Environment...");

   
    uint32_t nEndDevices = 100;
    uint32_t nGateways = 4;
    double areaWidth = 5000.0;   
    double areaHeight = 5000.0;

   
    double alpha = 0.4;
    double beta = 0.3;
    double gamma = 0.3;
    SdnController sdnController(alpha, beta, gamma);

    
    NodeContainer endDevices;
    endDevices.Create (nEndDevices);

    NodeContainer gateways;
    gateways.Create (nGateways);

    MobilityHelper mobility;

   
    MobilityHelper gatewayMobility;
    gatewayMobility.SetPositionAllocator ("ns3::GridPositionAllocator",
                                         "MinX", DoubleValue (1000.0),
                                         "MinY", DoubleValue (1000.0),
                                         "DeltaX", DoubleValue (2000.0),
                                         "DeltaY", DoubleValue (2000.0),
                                         "GridWidth", UintegerValue (2),
                                         "LayoutType", StringValue ("RowFirst"));
    gatewayMobility.SetMobilityModel ("ns3::ConstantPositionMobilityModel");
    gatewayMobility.Install (gateways);

   
    mobility.SetPositionAllocator ("ns3::RandomRectanglePositionAllocator",
                                   "X", StringValue ("ns3::UniformRandomVariable[Min=0.0|Max=" + std::to_string(areaWidth) + "]"),
                                   "Y", StringValue ("ns3::UniformRandomVariable[Min=0.0|Max=" + std::to_string(areaHeight) + "]"));
    mobility.SetMobilityModel ("ns3::ConstantPositionMobilityModel");
    mobility.Install (endDevices);

   
    std::vector<GatewayInfo> gwList;
    for (uint32_t i = 0; i < gateways.GetN(); ++i) {
        Ptr<Node> node = gateways.Get(i);
        Ptr<MobilityModel> pos = node->GetObject<MobilityModel>();
        Vector p = pos->GetPosition();

        GatewayInfo gw;
        gw.gatewayId = i;
        gw.queueLength = 3 + (i * 4); 
        gw.position = p;
        gwList.push_back(gw);
    }

   
    std::ofstream resultsFile("sdn_lorawan_results.csv");
    resultsFile << "EndDeviceID,ResidualEnergy,AssignedGateway,Cost\n";

   
    for (uint32_t i = 0; i < endDevices.GetN(); ++i) {
        Ptr<Node> edNode = endDevices.Get(i);
        Ptr<MobilityModel> edMobility = edNode->GetObject<MobilityModel>();
        Vector edPos = edMobility->GetPosition();

        double mockEnergy = 90.0 - (i % 50); 
        uint32_t chosenGw = sdnController.SelectOptimalGateway(gwList, mockEnergy, edPos);
        double finalCost = sdnController.CalculateCost(mockEnergy, gwList[chosenGw].queueLength, 0.0);

        resultsFile << i << "," << mockEnergy << "," << chosenGw << "," << finalCost << "\n";
    }
    resultsFile.close();
    NS_LOG_INFO ("Advanced simulation data successfully exported to sdn_lorawan_results.csv");

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

    NS_LOG_INFO ("Simulation scenario finished successfully.");
    return 0;
}
