#include <iostream>
#include <fstream>
#include <string>
#include <vector>
#include <iomanip>
#include "Constant.h"
#include <random>
#include <unordered_map>
#include<algorithm>
using namespace std;
class ChargingStation
{
public:
    int cityId;
    string cityName;
    int distanceToLastCity;
    int numberOfChargers;
    int queueLength;
public:
    ChargingStation(int id, string name, int chargers)
        : cityId(id), cityName(name), numberOfChargers(chargers), queueLength(0) {}
    int distanceToSydney(int cityId)
    {
        int distance = 0;
        for (int i = 0; i < cityId; i++)
        {
            distance += distanceMap[i + 1];
        }
        return distance;
    }
    void displayInfo()
    {
        cout << left << setw(4) << cityId;
        cout << left << setw(25) << cityName;
        cout << left << setw(25) << distanceToSydney(cityId);
        cout << left << setw(20) << numberOfChargers;
        cout << left << setw(15) << queueLength;
        cout << left << setw(40) << fixed << setprecision(2) << (queueLength > 0 ?
(0.5 * static_cast<double>(queueLength) / numberOfChargers) : 0.0) << " hours" <<
endl;
    }
};
class Vehicle
{
public:
    int vehicleId;     // can be any integer
    int currentCityId; // initialised with 0 for Sydney
    int destinationId; // any city other than Sydney
    int capacityRange; // in kilometers
    int remainRange;   // in kilometers
    vector<int> rechargeCities; // Vector to store recharge cities
    int secondRecharge; // Variable to store the second recharge city if needed
public:
    Vehicle(int id, int dest, int capacity, int remain)
        : vehicleId(id), destinationId(dest), capacityRange(capacity),
remainRange(remain), currentCityId(0), secondRecharge(-1) {}
     void calculateRechargeCities()
     {
         int remain = remainRange;
         int distance = 0;
         bool firstRechargeFound = false;
         for (int i = currentCityId; i < destinationId; i++)
         {
             distance = distanceMap[i];
             remain -= distance;
             if (remain < 0)
             {
                 if (!firstRechargeFound)
                 {
                     rechargeCities.push_back(i);
                     firstRechargeFound = true;
                 }
                 else if (secondRecharge == -1)
                 {
                     secondRecharge = i;
                 }
                     remain = capacityRange;
             }
         }
     }
    void displayInfo() const
    {
        string originName = nameMap[currentCityId];
        string destinationName = nameMap[destinationId];
        string firstRechargeName = (rechargeCities.size() >= 1) ?
nameMap[rechargeCities[0]] : "----";
        string secondRechargeName = (secondRecharge != -1) ?
nameMap[secondRecharge] : "----";
         cout   <<   left   <<   setw(4) << vehicleId;
         cout   <<   left   <<   setw(20) << originName;
         cout   <<   left   <<   setw(20) << destinationName;
         cout   <<   left   <<   setw(20) << capacityRange;
         cout   <<   left   <<   setw(15) << remainRange;
         cout   <<   left   <<   setw(15) << firstRechargeName;
         cout   <<   left   <<   setw(20) << secondRechargeName << endl;
     }
};
class DemandGenerator
{
public:
    void generateDemandsToFile(const std::string &fileName, int numDemands)
    {
        std::ofstream file(fileName);
        if (!file.is_open())
        {
             std::cerr << "File creation failed!" << std::endl;
             return;
         }
        std::random_device rd;
        std::mt19937 gen(rd());
        std::uniform_int_distribution<int> destDistribution(1, NUM_CITIES - 1); //
Destination ID range (1 to NUM_CITIES - 1)
        std::uniform_int_distribution<int> capacityDistribution(MIN_CAPACITY,
MAX_CAPACITY);
        std::uniform_int_distribution<int> remainDistribution(0, MAX_CAPACITY);
         int vehicleId = 200;
         for (int i = 0; i < numDemands; ++i)
         {
             vehicleId++;
             int destinationId = destDistribution(gen);
             int capacityRange = capacityDistribution(gen);
             int remainRange = std::min(capacityRange, remainDistribution(gen));
            file << "[" << vehicleId << "," << destinationId << "," <<
capacityRange << "," << remainRange << "]\n";
        }
         file.close();
     }
};
class ChargingAllocation
{
public:
    void allocateChargingStations(const vector<Vehicle> &vehicles,
vector<ChargingStation> &stations)
    {
        // Initialize queue lengths for each charging station to 0
        vector<int> queueLengths(NUM_CITIES, 0);
         // Calculate queue lengths and waiting times for each vehicle
         for (const Vehicle &vehicle : vehicles)
         {
             int remainingRange = vehicle.remainRange;
             int currentCity = vehicle.currentCityId;
             for (int i = 0; i < vehicle.rechargeCities.size(); i++)
             {
                 int rechargeCity = vehicle.rechargeCities[i];
                 int queueLength = queueLengths[rechargeCity];
                 queueLength++;
                 queueLengths[rechargeCity] = queueLength;
             }
         }
         // Display charging station information and waiting times
         cout << left << setw(4) << "Location ID";
         cout << left << setw(20) << "Location Name";
         cout << left << setw(20) << "Distance to Sydney";
         cout << left << setw(20) << "Number of Chargers";
         cout << left << setw(15) << "Queue Length";
         cout << left << setw(15) << "Waiting Hours" << endl;
         double totalWaitingHours = 0.0;
        for (int i = 0; i < NUM_CITIES; i++)
        {
            stations[i].queueLength = queueLengths[i];
            stations[i].displayInfo();
            totalWaitingHours += (0.5 * static_cast<double>(queueLengths[i]) /
stations[i].numberOfChargers);
        }
        cout << "Overall average waiting time per vehicle = " << fixed <<
setprecision(2) << (totalWaitingHours / vehicles.size()) << " hours" << endl;
    }
    void chargingAllocationLogic(const vector<Vehicle> &vehicles,
vector<ChargingStation> &stations)
    {
        // Sort stations based on queue length in ascending order
        sort(stations.begin(), stations.end(), [](const ChargingStation &a, const
ChargingStation &b) {
            return a.queueLength < b.queueLength;
        });
         // Iterate through vehicles and allocate them to stations with the shortest
queues
         for (const Vehicle &vehicle : vehicles)
         {
             int minQueueLength = INT_MAX;
             int selectedStationIndex = -1;
             // Find the station with the shortest queue
             for (int i = 0; i < stations.size(); i++)
             {
                 if (stations[i].queueLength < minQueueLength)
                 {
                     minQueueLength = stations[i].queueLength;
                     selectedStationIndex = i;
                 }
             }
             // Allocate the vehicle to the selected station
             stations[selectedStationIndex].queueLength++;
         }
    }
    void balanceWaitingQueues(const vector<Vehicle> &vehicles,
vector<ChargingStation> &stations)
    {
        const int numSimulations = 5000;
        double bestOverallAvgWaitTime = numeric_limits<double>::max();
         for (int simulation = 1; simulation <= 8; simulation++)
         {
             // Make a copy of the original station queue lengths
             vector<int> originalQueueLengths(NUM_CITIES);
             for (int i = 0; i < NUM_CITIES; i++)
             {
                     originalQueueLengths[i] = stations[i].queueLength;
             }
             // Randomly shuffle the vehicles
             vector<Vehicle> shuffledVehicles = vehicles;
             random_shuffle(shuffledVehicles.begin(), shuffledVehicles.end());
             // Allocate vehicles using the charging allocation logic
             chargingAllocationLogic(shuffledVehicles, stations);
             // Calculate the overall average waiting time
             double totalWaitingHours = 0.0;
            for (int i = 0; i < NUM_CITIES; i++)
            {
                totalWaitingHours += (0.5 *
static_cast<double>(stations[i].queueLength) / stations[i].numberOfChargers);
            }
             double overallAvgWaitTime = totalWaitingHours / vehicles.size();
            // If the current allocation is better, update the best allocation
            if (overallAvgWaitTime < bestOverallAvgWaitTime)
            {
                 bestOverallAvgWaitTime = overallAvgWaitTime;
            }
            else
            {
                 // Revert to the original queue lengths if the current allocation
is not better
                 for (int i = 0; i < NUM_CITIES; i++)
                 {
                     stations[i].queueLength = originalQueueLengths[i];
                 }
            }
            cout << "Improved overall average waiting time = " << fixed <<
setprecision(2) << bestOverallAvgWaitTime << " hours at simulation " << simulation
<< endl;
         }
         cout   <<   "\nCharging allocation after balancing:" << endl;
         cout   <<   left << setw(4) << "Vehicle ID";
         cout   <<   left << setw(20) << "Destination";
         cout   <<   left << setw(20) << "Capacity Range";
         cout   <<   left << setw(20) << "Remaining Range";
         cout   <<   left << setw(20) << "First Recharge";
         cout   <<   left << setw(20) << "Second Recharge" << endl;
         for (const Vehicle &vehicle : vehicles)
         {
             vehicle.displayInfo();
         }
     }
};
int main()
{
    // Load charging station data
    vector<ChargingStation> stations;
    for (int i = 0; i < NUM_CITIES; i++)
    {
        ChargingStation station(i, nameMap[i], chargersMap[i]);
        stations.push_back(station);
    }
    // Generate charging demands and write to a file
    DemandGenerator demandGenerator;
    const std::string fileName = "ChargingDemands.txt";
    const int numDemands = 150 + (std::rand() % 51); // Random number of demands
between 150 and 200
    demandGenerator.generateDemandsToFile(fileName, numDemands);
    // Read vehicle data from the file
    ifstream file(fileName);
    if (!file.is_open())
    {
        cout << "File not found!" << endl;
        return 0;
    }
    vector<Vehicle> vehicles;
    string line;
    while (getline(file, line))
    {
        int id, dest, capacity, remain;
        if (sscanf(line.c_str(), "[%d,%d,%d,%d]", &id, &dest, &capacity, &remain)
== 4)
        {
            Vehicle vehicle(id, dest, capacity, remain);
            vehicle.calculateRechargeCities();
            vehicles.push_back(vehicle);
        }
    }
    file.close();
    // Display vehicle information
    cout << "Vehicle Information:" << endl;
    cout << left << setw(4) << "Vehicle ID";
    cout << left << setw(20) << "Origin";
    cout << left << setw(20) << "Destination";
    cout << left << setw(20) << "Capacity Range";
    cout << left << setw(20) << "Remaining Range" << endl;
    for (const Vehicle &vehicle : vehicles)
    {
        vehicle.displayInfo();
    }
    // Display initial charging allocation
    cout << "\nInitial Charging Allocation:" << endl;
    cout << left << setw(4) << "Vehicle ID";
    cout << left << setw(20) << "Destination";
    cout << left << setw(20) << "Capacity Range";
    cout << left << setw(20) << "Remaining Range";
    cout << left << setw(20) << "First Recharge";
    cout << left << setw(20) << "Second Recharge" << endl;
    for (const Vehicle &vehicle : vehicles)
    {
        vehicle.displayInfo();
    }
    ChargingAllocation chargingAllocation;
    chargingAllocation.allocateChargingStations(vehicles, stations);
    const int numSimulations=8;
    // Balance waiting queues with Monte-Carlo simulations
    cout << "\nBalancing waiting queues with Monte-Carlo simulations." << endl;
    cout << "Number of simulations = " << numSimulations << endl;
    chargingAllocation.balanceWaitingQueues(vehicles, stations);
    return 0;
}