///|
/// Controls deterministic neighborhood improvement after construction or ALNS.
pub struct LocalSearchConfig {
  max_rounds : Int
  use_two_opt : Bool
  use_relocate : Bool
  use_swap : Bool
  use_reverse : Bool
} derive(Debug, ToJson)

///|
pub fn LocalSearchConfig::default() -> LocalSearchConfig {
  {
    max_rounds: 1,
    use_two_opt: true,
    use_relocate: true,
    use_swap: true,
    use_reverse: false,
  }
}

///|
pub fn LocalSearchConfig::tune(
  self : LocalSearchConfig,
  max_rounds? : Int = self.max_rounds,
  use_two_opt? : Bool = self.use_two_opt,
  use_relocate? : Bool = self.use_relocate,
  use_swap? : Bool = self.use_swap,
  use_reverse? : Bool = self.use_reverse,
) -> LocalSearchConfig {
  { max_rounds, use_two_opt, use_relocate, use_swap, use_reverse }
}

///|
pub fn LocalSearchConfig::max_rounds(self : LocalSearchConfig) -> Int {
  self.max_rounds
}

///|
pub fn LocalSearchConfig::uses_two_opt(self : LocalSearchConfig) -> Bool {
  self.use_two_opt
}

///|
pub fn LocalSearchConfig::uses_relocate(self : LocalSearchConfig) -> Bool {
  self.use_relocate
}

///|
pub fn LocalSearchConfig::uses_swap(self : LocalSearchConfig) -> Bool {
  self.use_swap
}

///|
pub fn LocalSearchConfig::uses_reverse(self : LocalSearchConfig) -> Bool {
  self.use_reverse
}

///|
fn local_search_pass(
  instance : @core.VrpInstance,
  solution : @core.Solution,
  solve_config : @core.SolveConfig,
  options : LocalSearchConfig,
) -> @core.Solution {
  let mut current = solution
  if options.use_two_opt {
    current = @core.improve_with_two_opt(instance, current, solve_config)
  }
  if options.use_relocate {
    current = @operators.best_relocate(instance, current, solve_config)
  }
  if options.use_swap {
    current = @operators.best_swap(instance, current, solve_config)
  }
  if options.use_reverse {
    current = @operators.best_reverse(instance, current, solve_config)
  }
  current
}

///|
/// Performs bounded deterministic improvement and never accepts a worse solution.
pub fn local_search(
  instance : @core.VrpInstance,
  solution : @core.Solution,
  solve_config : @core.SolveConfig,
  options : LocalSearchConfig,
) -> @core.Solution {
  let mut current = solution
  let rounds = options.max_rounds.max(0)
  for _ in 0.. @operators.ImprovementReport {
  let candidate = local_search(instance, solution, solve_config, options)
  let start = solution.objective_value()
  let finish = candidate.objective_value()
  @operators.ImprovementReport::new(
    candidate,
    options.max_rounds.max(0),
    if finish < start {
      1
    } else {
      0
    },
    start,
    finish,
  )
}