#pragma once

#include "input/pair.h"
#include "input/pdptw_data.h"
#include "lns/constraints/constraint.h"
#include "route.h"

#include <iostream>
#include <memory>
#include <spdlog/spdlog.h>
#include <vector>

class AtomicModification;
class AtomicRecreation;
class AtomicDestruction;
class TimeWindowConstraint;
class CapacityConstraint;

/*
 * Represents a solution of the PDPTW problem.
 */
class Solution
{
public:
    using PairBank = std::vector<int>;

private:
    std::reference_wrapper<const PDPTWData> data;// Reference to PDPTW data
    PairBank pairBank;                           // Unassigned pairs (Pickup & Delivery)
    std::vector<Route> routes;                   // Routes in the solution
    double rawCost;
    double totalCost;
    std::vector<std::unique_ptr<Constraint>> constraints;

public:
    /*
     * Creates an empty solution with all pairs in the pairBank.
     */
    static Solution emptySolution(PDPTWData const &data);

    /*
     * In-depth copy of the solution.
     */
    Solution(Solution const &);
    /*
     * Copy assignment.
     */
    Solution &operator=(Solution const &);
    /*
     * Move constructor.
     */
    Solution(Solution &&) noexcept;
    /*
     * Move assignment.
     */
    Solution &operator=(Solution &&) noexcept;
    /*
     * Destructor.
     */
    ~Solution() noexcept;

    /*
     * Constructs a solution with given data.
     */
    explicit Solution(PDPTWData const &data);
    /*
     * Constructs a solution with specified parameters.
     */
    Solution(PDPTWData const &data, PairBank pairbank, std::vector<Route> routes, double routeCost, double totalCost);

    /*
     * Returns the pair bank.
     */
    PairBank const &getBank() const;
    /*
     * Returns the pair bank.
     */
    PairBank const &getPairBank() const;
    /*
     * Returns a mutable reference to the pair bank.
     */
    PairBank &getPairBank();
    /*
     * Returns the list of routes.
     */
    std::vector<Route> const &getRoutes() const;
    /*
     * Returns a mutable reference to a specific route.
     */
    Route &getRoute(int routeIndex);
    /*
     * Returns a constant reference to a specific route.
     */
    Route const &getRoute(int routeIndex) const;
    /*
     * Returns the PDPTW data reference.
     */
    PDPTWData const &getData() const;
    /*
     * Returns the raw cost of the solution.
     */
    double getRawCost() const;
    /*
     * Returns the total cost of the solution.
     */
    double getCost() const;
    /*
     * Returns the number of routes.
     */
    int getNumberOfRoutes() const;
    /*
     * Returns the count of missing pairs.
     */
    unsigned int missingPairCount() const;
    /*
     * Returns the constraints applied to the solution.
     */
    std::vector<std::unique_ptr<Constraint>> const &getConstraints() const;

    /*
     * Returns the route index associated with a given location ID, or -1 if not in a route.
     */
    int getRouteIDOf(int locationID) const;
    /*
     * Returns the number of fulfilled requests.
     */
    int requestsFulfilledCount() const;
    /*
     * Checks if the modification is valid according to all constraints.
     */
    bool checkModification(AtomicRecreation const &modification) const;
    /*
     * Verifies the correctness of the solution, mainly for debugging.
     */
    void check() const;

    /*
     * Pre-modification check. The modification must be valid.
     */
    void beforeApplyModification(AtomicModification &modification) const;
    /*
     * Updates constraints after modification is applied.
     */
    void afterApplyModification(AtomicModification &modification);

    /*
     * Applies recreation modification to the solution without validity checks.
     */
    void applyRecreateSolution(AtomicRecreation &modification);
    /*
     * Applies destruction modification to the solution without validity checks.
     */
    void applyDestructSolution(AtomicDestruction &modification);

    /*
     * Computes and returns the penalty value.
     */
    double computePenalisation() const;
    /*
     * Returns a mutable reference to the routes.
     */
    std::vector<Route> &getRoutes();
    /*
     * Prints the solution details.
     */
    void print() const;
    /*
     * Computes and stores the total solution cost.
     */
    void computeAndStoreSolutionCost();

private:
    /*
     * Initializes the object, called by the constructor.
     */
    void init();
    /*
     * Initializes or resets the problem constraints.
     */
    void initConstraints();
    /*
     * Initializes the routes.
     */
    void initRoutes();
    /*
     * Initializes the pair bank.
     */
    void initPairBank();
    /*
     * Computes the total cost of the solution.
     */
    double computeSolutionCost() const;
};