# Copyright (C) 2024 Manuel Bustillo

module VNS
  class Engine
    def target_function(&function)
      @target_function = function
    end

    def add_perturbation(klass)
      @perturbations ||= Set.new
      @perturbations << klass
    end

    attr_writer :initial_solution

    def run
      raise 'No target function defined' unless @target_function
      raise 'No perturbations defined' unless @perturbations
      raise 'No initial solution defined' unless @initial_solution

      @best_solution = @initial_solution
      @best_score = @target_function.call(@best_solution)

      puts "Initial score: #{@best_score.to_f}"

      @perturbations.each do |perturbation|
        puts "Running perturbation: #{perturbation.name}"
        optimize(perturbation.new(@best_solution))
      end

      @best_solution 
    end

    private

    def optimize(perturbation)
      perturbation.each do |alternative_solution|
        score = @target_function.call(alternative_solution)
        next if score >= @best_score

        @best_solution = alternative_solution.deep_dup
        @best_score = score

        puts "New lowest score: #{@best_score.to_f}"

        return optimize(perturbation.class.new(@best_solution))
      end
    end
  end
end