///|
/// Parameters for the optional simulated-annealing driver.
pub struct AnnealingConfig {
  iterations : Int
  initial_temperature : Double
  cooling_rate : Double
  destroy_size : Int
  seed : Int
} derive(Debug, ToJson)

///|
pub fn AnnealingConfig::default() -> AnnealingConfig {
  {
    iterations: 240,
    initial_temperature: 18.0,
    cooling_rate: 0.985,
    destroy_size: 4,
    seed: 31,
  }
}

///|
pub fn AnnealingConfig::tune(
  self : AnnealingConfig,
  iterations? : Int = self.iterations,
  initial_temperature? : Double = self.initial_temperature,
  cooling_rate? : Double = self.cooling_rate,
  destroy_size? : Int = self.destroy_size,
  seed? : Int = self.seed,
) -> AnnealingConfig {
  { iterations, initial_temperature, cooling_rate, destroy_size, seed }
}

///|
pub fn AnnealingConfig::iterations(self : AnnealingConfig) -> Int {
  self.iterations
}

///|
pub fn AnnealingConfig::initial_temperature(self : AnnealingConfig) -> Double {
  self.initial_temperature
}

///|
pub fn AnnealingConfig::cooling_rate(self : AnnealingConfig) -> Double {
  self.cooling_rate
}

///|
pub fn AnnealingConfig::destroy_size(self : AnnealingConfig) -> Int {
  self.destroy_size
}

///|
pub fn AnnealingConfig::seed(self : AnnealingConfig) -> Int {
  self.seed
}

///|
pub struct AnnealingRun {
  result : @core.SolveResult
  iterations : Int
  accepted_moves : Int
  improved_moves : Int
  final_temperature : Double
} derive(Debug)

///|
pub fn AnnealingRun::result(self : AnnealingRun) -> @core.SolveResult {
  self.result
}

///|
pub fn AnnealingRun::iterations(self : AnnealingRun) -> Int {
  self.iterations
}

///|
pub fn AnnealingRun::accepted_moves(self : AnnealingRun) -> Int {
  self.accepted_moves
}

///|
pub fn AnnealingRun::improved_moves(self : AnnealingRun) -> Int {
  self.improved_moves
}

///|
pub fn AnnealingRun::final_temperature(self : AnnealingRun) -> Double {
  self.final_temperature
}

///|
/// Computes the Metropolis acceptance probability for a positive delta.
pub fn acceptance_probability(delta : Double, temperature : Double) -> Double {
  if delta <= 0.0 {
    1.0
  } else {
    @math.exp(-(delta / temperature.max(0.000001)))
  }
}

///|
fn annealing_destroy_operator(iteration : Int) -> @core.DestroyOperator {
  match iteration % 6 {
    0 => @core.DestroyOperator::Random
    1 => @core.DestroyOperator::Shaw
    2 => @core.DestroyOperator::Worst
    3 => @core.DestroyOperator::Route
    4 => @core.DestroyOperator::Cluster
    _ => @core.DestroyOperator::TimeCritical
  }
}

///|
fn annealing_repair_operator(iteration : Int) -> @core.RepairOperator {
  match iteration % 4 {
    0 => @core.RepairOperator::Greedy
    1 => @core.RepairOperator::Regret2
    2 => @core.RepairOperator::DemandFirst
    _ => @core.RepairOperator::TimeWindow
  }
}

///|
/// Runs a deterministic simulated-annealing search from a Clarke-Wright seed.
pub fn anneal(
  instance : @core.VrpInstance,
  solve_config : @core.SolveConfig,
  config : AnnealingConfig,
) -> AnnealingRun {
  let seed_solution = @constructive.clarke_wright_seed(
    instance,
    config=solve_config,
  )
  let mut current = seed_solution
  let mut best = seed_solution
  let rng = @rng.Rng::new(config.seed)
  let mut temperature = config.initial_temperature.max(0.000001)
  let mut accepted_moves = 0
  let mut improved_moves = 0
  let mut best_iteration = 0
  let iterations = config.iterations.max(0)
  for iteration in 0.. String {
  let summary = summarize_search(run.result)
  [
    search_summary_text(summary),
    "Annealing accepted moves: \{run.accepted_moves}",
    "Annealing improved moves: \{run.improved_moves}",
    "Annealing final temperature: \{run.final_temperature}",
  ].join("\n")
}