#include "Solution.h"
#include <vector>
#include <unordered_set>
#include <unordered_map>
#include <algorithm>
#include <numeric>
#include <cmath>
#include <random>

Solution::Solution(const Instance& instance) : instance(instance)
{
    // Initialize totalCosts to zero
    totalCosts = 0.0;

    // Create a tour for each customer
    for (int idx = 1; idx <= instance.numCustomers; ++idx) {
        Tour t;

        // Start each tour with a single node (customer)
        t.nodes.push_back(idx);

        // Update the cost and demand of the tour based on the instance
        t.updateCostAndDemand(instance);

        // Add the tour to the solution
        tours.push_back(t);

        // Increment the totalCosts with the cost of the new tour
        totalCosts += t.costs;
    }

    generateCustomerToTourMap();
}

Solution::Solution(const Instance& instance, const std::vector<std::vector<int>>& tours) : instance(instance)
{
    // Initialize totalCosts to zero
    totalCosts = 0.0;

    // Iterate through the provided list of tours using a reference
    for (const auto& nodes : tours) {
        Tour t;

        // Copy the nodes from the input tours to the new tour
        t.nodes = nodes;

        // Update the cost and demand of the tour based on the instance
        t.updateCostAndDemand(instance);

        // Add the tour to the solution
        this->tours.push_back(t);

        // Increment the totalCosts with the cost of the new tour
        totalCosts += t.costs;
    }

    generateCustomerToTourMap();
}

void Solution::generateCustomerToTourMap() {

    customerToTourMap.resize(instance.numNodes);

    // Update pointers to tours using range-based for loops
    for (size_t tourIndex = 0; tourIndex < tours.size(); ++tourIndex) {
        const auto& t = tours[tourIndex];
        for (const auto& c : t.nodes) {
            customerToTourMap[c] = tourIndex;
        }
    }
}



void Solution::acceptModifiedSolution(ModifiedSolution & modSol) {
        totalCosts = modSol.totalCosts;

    // Remove tours not present in oldTours from tours
    //tours.erase(std::remove_if(tours.begin(), tours.end(), [&](const Tour& t) {
    //    return std::find(modSol.oldTours.begin(), modSol.oldTours.end(), &t) == modSol.oldTours.end();
    //    }), tours.end());

        std::sort(modSol.removedToursId.rbegin(), modSol.removedToursId.rend());

        // Step 4: Remove the elements based on the indices vector
        for (size_t index : modSol.removedToursId) {
            auto& removedTour = tours[index];
            removedTour = std::move(tours.back());
            tours.pop_back();

            for (const int c : removedTour.nodes) {
                customerToTourMap[c] = index; 
            }
        }

        // Capture the starting point of new tours
        auto startNewTours = tours.size();

        // Move newTours to tours
        tours.insert(tours.end(), modSol.newTours.begin(), modSol.newTours.end());

        // Iterate over the new tours
        for (size_t tourIndex = startNewTours; tourIndex < tours.size(); ++tourIndex) {
            const auto& t = tours[tourIndex];
            for (const int c : t.nodes) { // Assuming nodes is a member of the Tour struct or class
                customerToTourMap[c] = tourIndex;
            }
        }

}

std::vector<std::vector<int>> Solution::getTourList() const {
    std::vector<std::vector<int>> nodeList;
    nodeList.reserve(tours.size());  // Reserve space for efficiency

    for (const auto& tour : tours) {
        nodeList.push_back(tour.nodes);
    }

    return nodeList;
}

std::size_t Solution::getHash()
{
    std::size_t hash = 0;
    for (auto& tour : tours) {
        std::size_t seed = tour.nodes.size();
        for (auto& i : tour.nodes) {
            seed ^= i + 0x9e3779b9 + (seed << 6) + (seed >> 2);
        }
        hash += seed;
    }
    return hash;
}

Solution& Solution::operator=(const Solution & other)
{
    totalCosts = other.totalCosts;
    tours = other.tours;
    customerToTourMap = other.customerToTourMap;   
    return *this;
}





ModifiedSolution::ModifiedSolution(Solution& originalSolution) : originalSolution(originalSolution), instance(originalSolution.instance) {

    totalCosts = originalSolution.totalCosts;
}

void ModifiedSolution::removeCustomers(const std::vector<int>& A) {
    std::unordered_map<int, std::vector<int>> tourModifications;

    totalCosts = 0;

    // Populate the map with customers to be removed per tour
    for (int customer : A) {
        int tourIndex = originalSolution.customerToTourMap[customer];
        tourModifications[tourIndex].push_back(customer);
    }

    // Apply removals and update tours
    for (const auto& entry : tourModifications) {
        int tourIndex = entry.first;
        const std::vector<int>& customersToRemove = entry.second;

        Tour& tour = originalSolution.tours[tourIndex];
        Tour newTour;
        // Reserve space in advance to avoid multiple allocations
        newTour.nodes.reserve(tour.nodes.size() - customersToRemove.size());

        // Remove specified customers from the tour
        for (int node_id : tour.nodes) {
            if (std::find(customersToRemove.begin(), customersToRemove.end(), node_id) == customersToRemove.end()) {
                newTour.nodes.push_back(node_id);
            }
        }

        if (!newTour.nodes.empty()) {
            newTour.updateCostAndDemand(instance);
            totalCosts += newTour.costs;
            newTours.push_back(newTour);
        }
        removedToursId.push_back(tourIndex);
    }

    // Include the costs of unaffected tours
    for (size_t i = 0; i < originalSolution.tours.size(); ++i) {
        if (tourModifications.find(i) == tourModifications.end()) {
            totalCosts += originalSolution.tours[i].costs;
        }
    }
}

std::unordered_set<int> ModifiedSolution::destroy(float c_bar, int L_max, float alpha)
{
    std::unordered_set<int> removed_customers;
    std::unordered_set<const Tour*> modified_tours;

    if (originalSolution.tours.empty()) {
        return removed_customers;
    }

    // --- 1. Adaptive Seed Selection ---
    int seed_c;
    if (getRandomFraction() < 0.80) {
        // --- Strategy A (80%): Random Seed (Intensification) ---
        seed_c = getRandomNumber(1, instance.numCustomers);
    } else {
        // --- Strategy B (20%): Worst Customer Seed (Diversification) ---
        std::vector<std::pair<float, int>> cost_savings;
        for (const auto& tour : originalSolution.tours) {
            for (size_t i = 0; i < tour.nodes.size(); ++i) {
                int prev = (i == 0) ? 0 : tour.nodes[i - 1];
                int curr = tour.nodes[i];
                int next = (i == tour.nodes.size() - 1) ? 0 : tour.nodes[i + 1];
                cost_savings.push_back({instance.distanceMatrix[prev][curr] + instance.distanceMatrix[curr][next] - instance.distanceMatrix[prev][next], curr});
            }
        }

        if (cost_savings.empty()) {
            seed_c = getRandomNumber(1, instance.numCustomers); // Fallback to random
        } else {
            std::sort(cost_savings.rbegin(), cost_savings.rend());
            seed_c = cost_savings[0].second; // Use the most expensive customer as the seed
        }
    }

    // --- 2. Proven "Related String Removal" Logic ---
    float avg_tour_cardinality = 0;
    for (const auto& t : originalSolution.tours) avg_tour_cardinality += t.nodes.size();
    avg_tour_cardinality /= originalSolution.tours.size();
    int ls_max = std::min(L_max, static_cast<int>(avg_tour_cardinality));
    if (ls_max <= 0) return removed_customers;
    
    float ks_max = std::max(1.0f, (4.0f * c_bar) / (1.0f + ls_max) - 1.0f);
    int ks = static_cast<int>(getRandomFraction(1, ks_max + 0.9999));
    
    for (int adjacent_customer : instance.adj[seed_c]) {
        if (adjacent_customer == 0 || modified_tours.size() >= ks) break;
        const Tour* tour_ptr = &originalSolution.tours[originalSolution.customerToTourMap[adjacent_customer]];
        if (removed_customers.count(adjacent_customer) == 0 && modified_tours.find(tour_ptr) == modified_tours.end()) {
            int lt_max = std::min((int)tour_ptr->nodes.size(), ls_max);
            if (lt_max == 0) continue;
            int lt = static_cast<int>(getRandomFraction(1, lt_max + 0.9999));
            std::vector<int> removed_from_this_tour;
            Tour new_partial_tour;
            tour_ptr->stringRemoval(removed_from_this_tour, new_partial_tour, lt, adjacent_customer);

            if (!removed_from_this_tour.empty()) {
                removed_customers.insert(removed_from_this_tour.begin(), removed_from_this_tour.end());
                modified_tours.insert(tour_ptr);
                if (!new_partial_tour.nodes.empty()) {
                    new_partial_tour.updateCostAndDemand(instance);
                    newTours.push_back(new_partial_tour);
                }
            }
        }
    }
    
    // --- 3. Final State Update ---
    totalCosts = 0;
    for (size_t i = 0; i < originalSolution.tours.size(); ++i) {
        if (modified_tours.find(&originalSolution.tours[i]) == modified_tours.end()) {
            totalCosts += originalSolution.tours[i].costs;
        } else {
            removedToursId.push_back(i);
        }
    }
    for (const auto& t : newTours) totalCosts += t.costs;

    return removed_customers;
}

void ModifiedSolution::repair(const std::vector<int>& A, float beta, bool insertInNewToursOnly) {
    const int& capacity = instance.vehicleCapacity;

    int best_original_tour_idx;
    int best_new_tour_idx;
    int best_ins_pos;
    int next_node;
    float insertionCosts;
    float bestInsertionCost;
    std::vector<int> oldTourIds;

    if (!insertInNewToursOnly) {
        for (int t_idx = 0; t_idx < originalSolution.tours.size(); ++t_idx) {
            if (std::find(removedToursId.begin(), removedToursId.end(), t_idx) == removedToursId.end()) {
                oldTourIds.push_back(t_idx);
            }
        }
    }


    for (int c : A) {
        bestInsertionCost = std::numeric_limits<float>::infinity();
        const int& c_demand = instance.demand[c];
        best_original_tour_idx = -1;
        best_new_tour_idx = -1;

        if (!insertInNewToursOnly) {
            for (int t_idx : oldTourIds) {
                const auto& tour = originalSolution.tours[t_idx];

                if (tour.demand + c_demand <= capacity) {

                    int prev_node = 0;
                    for (int new_pos = 0; new_pos < tour.nodes.size(); ++new_pos) {
                        next_node = tour.nodes[new_pos];

                        insertionCosts = instance.distanceMatrix[prev_node][c] + instance.distanceMatrix[c][next_node] - instance.distanceMatrix[prev_node][next_node];

                        prev_node = next_node;

                        if (insertionCosts < bestInsertionCost) {
                            if (getRandomFractionFast() < (1 - beta)) {
                                best_original_tour_idx = t_idx;
                                best_ins_pos = new_pos;
                                bestInsertionCost = insertionCosts;
                            }
                        }
                    }

                    insertionCosts = instance.distanceMatrix[prev_node][c] + instance.distanceMatrix[c][0] - instance.distanceMatrix[prev_node][0];

                    if (insertionCosts < bestInsertionCost) {
                        if (getRandomFractionFast() < (1 - beta)) {
                            best_original_tour_idx = t_idx;
                            best_ins_pos = tour.nodes.size();
                            bestInsertionCost = insertionCosts;
                        }
                    }

                }
            }
        }


        for (int t_idx = 0; t_idx < newTours.size(); ++t_idx) {
            const auto& tour = newTours[t_idx];

            if (tour.demand + c_demand <= capacity) {

                int prev_node = 0;
                for (int new_pos = 0; new_pos < tour.nodes.size(); ++new_pos) {
                    next_node = tour.nodes[new_pos];

                    insertionCosts = instance.distanceMatrix[prev_node][c] + instance.distanceMatrix[c][next_node] - instance.distanceMatrix[prev_node][next_node];

                    prev_node = next_node;

                    if (insertionCosts < bestInsertionCost) {
                        if (getRandomFractionFast() < (1 - beta)) {
                            best_new_tour_idx = t_idx;
                            best_ins_pos = new_pos;
                            bestInsertionCost = insertionCosts;
                        }
                    }
                }

                insertionCosts = instance.distanceMatrix[prev_node][c] + instance.distanceMatrix[c][0] - instance.distanceMatrix[prev_node][0];

                if (insertionCosts < bestInsertionCost) {
                    if (getRandomFractionFast() < (1 - beta)) {
                        best_new_tour_idx = t_idx;
                        best_ins_pos = tour.nodes.size();
                        bestInsertionCost = insertionCosts;
                    }
                }

            }
        }

        if (best_new_tour_idx != -1) {
            auto& tour = newTours[best_new_tour_idx];
            tour.nodes.insert(tour.nodes.begin() + best_ins_pos, c);
            tour.demand += c_demand;
            tour.costs += bestInsertionCost;
            totalCosts += bestInsertionCost;

        }
        else if (best_original_tour_idx != -1) {
            Tour newTour = originalSolution.tours[best_original_tour_idx];
            removedToursId.push_back(best_original_tour_idx);
            oldTourIds.erase(std::remove(oldTourIds.begin(), oldTourIds.end(), best_original_tour_idx), oldTourIds.end());

            newTour.nodes.insert(newTour.nodes.begin() + best_ins_pos, c);
            newTour.demand += c_demand;
            newTour.costs += bestInsertionCost;

            newTours.push_back(newTour);
            totalCosts += bestInsertionCost;

        }
        else {
            Tour newTour;
            newTour.nodes = { c };
            newTour.costs = instance.distanceMatrix[0][c] + instance.distanceMatrix[c][0];
            newTour.demand = c_demand;
            totalCosts += newTour.costs;
            newTours.push_back(newTour);
        }
    }
}



ModifiedSolution& ModifiedSolution::operator=(const ModifiedSolution& other) {
    if (this != &other) {
        // Copy values from 'other' to 'this'
        totalCosts = other.totalCosts;
        newTours = other.newTours;
        removedToursId = other.removedToursId;
    }
    return *this;
}