///|
/// Contractual reliability requirement over a set of mission checkpoints.
pub struct ReliabilityRequirement {
  name : String
  mission_time : Double
  minimum_survival : Double
  confidence_level : Double
  penalty : Double
}

///|
pub fn reliability_requirement(
  name~ : String,
  mission_time~ : Double,
  minimum_survival~ : Double,
  confidence_level~ : Double,
  penalty~ : Double,
) -> ReliabilityRequirement {
  if mission_time < 0.0 ||
    minimum_survival < 0.0 ||
    minimum_survival > 1.0 ||
    confidence_level <= 0.0 ||
    confidence_level >= 1.0 ||
    penalty < 0.0 {
    abort("invalid reliability requirement")
  }
  { name, mission_time, minimum_survival, confidence_level, penalty }
}

///|
pub struct RequirementResult {
  requirement : String
  estimate : Double
  lower_bound : Double
  margin : Double
  passed : Bool
  penalty : Double
  explanation : String
}

///|
pub fn requirement_result(
  requirement~ : String,
  estimate~ : Double,
  lower_bound~ : Double,
  margin~ : Double,
  passed~ : Bool,
  penalty~ : Double,
  explanation~ : String,
) -> RequirementResult {
  { requirement, estimate, lower_bound, margin, passed, penalty, explanation }
}

///|
pub fn evaluate_requirement(
  model : ReliabilityModel,
  requirement : ReliabilityRequirement,
) -> RequirementResult {
  let metric = model_metric(
    model,
    requirement.mission_time,
    requirement.confidence_level,
  )
  let margin = metric.lower - requirement.minimum_survival
  let passed = margin >= 0.0
  requirement_result(
    requirement=requirement.name,
    estimate=metric.estimate,
    lower_bound=metric.lower,
    margin~,
    passed~,
    penalty=if passed { 0.0 } else { requirement.penalty },
    explanation=if passed {
      "requirement satisfied at requested confidence"
    } else {
      "lower confidence bound is below requirement"
    },
  )
}

///|
pub fn evaluate_requirements(
  model : ReliabilityModel,
  requirements : Array[ReliabilityRequirement],
) -> Array[RequirementResult] {
  requirements.map(requirement => evaluate_requirement(model, requirement))
}

///|
pub fn requirement_pass_rate(results : Array[RequirementResult]) -> Double {
  if results.is_empty() {
    return 1.0
  }
  results.filter(result => result.passed).length().to_double() /
  results.length().to_double()
}

///|
pub fn total_contract_penalty(results : Array[RequirementResult]) -> Double {
  results.fold(init=0.0, (sum, result) => sum + result.penalty)
}

///|
pub fn contract_summary(results : Array[RequirementResult]) -> String {
  let builder = StringBuilder::new()
  builder.write_string(
    "requirement,estimate,lower_bound,margin,passed,penalty\n",
  )
  for result in results {
    builder.write_string(
      "\{result.requirement},\{result.estimate},\{result.lower_bound},\{result.margin},\{result.passed},\{result.penalty}\n",
    )
  }
  builder.to_string()
}

///|
pub fn mission_profile_requirement(
  name : String,
  model : ReliabilityModel,
  checkpoints : Array[Double],
  target : Double,
  confidence : Double,
  penalty : Double,
) -> Array[ReliabilityRequirement] {
  ignore(model)
  checkpoints.map(time => {
    reliability_requirement(
      name~,
      mission_time=time,
      minimum_survival=target,
      confidence_level=confidence,
      penalty~,
    )
  })
}

///|
pub fn requirement_breach_time(
  model : ReliabilityModel,
  target : Double,
  confidence : Double,
  start : Double,
  stop : Double,
  steps : Int,
) -> Double? {
  let grid = linspace(start, stop, steps)
  for time in grid {
    let metric = model_metric(model, time, confidence)
    if metric.lower < target {
      return Some(time)
    }
  }
  None
}

///|
pub fn required_scale_for_mission(
  shape : Double,
  target : Double,
  mission_time : Double,
) -> Double {
  if shape <= 0.0 || target <= 0.0 || target >= 1.0 || mission_time <= 0.0 {
    abort("invalid scale requirement")
  }
  mission_time / @math.pow(-@math.ln(target), 1.0 / shape)
}

///|
pub fn required_sample_size(
  expected_proportion : Double,
  half_width : Double,
  confidence : Double,
) -> Int {
  if expected_proportion <= 0.0 ||
    expected_proportion >= 1.0 ||
    half_width <= 0.0 {
    abort("invalid sample-size inputs")
  }
  let z = standard_normal_inv(0.5 + confidence / 2.0)
  (z *
  z *
  expected_proportion *
  (1.0 - expected_proportion) /
  (half_width * half_width))
  .ceil()
  .to_int()
}

///|
pub fn acceptance_number(
  sample_size : Int,
  allowable_failure_rate : Double,
) -> Int {
  if sample_size <= 0 ||
    allowable_failure_rate < 0.0 ||
    allowable_failure_rate > 1.0 {
    abort("invalid acceptance plan")
  }
  (sample_size.to_double() * allowable_failure_rate).floor().to_int()
}

///|
pub fn demonstrate_reliability(
  model : ReliabilityModel,
  times : Array[Double],
) -> Array[MetricEstimate] {
  times.map(time => model_metric(model, time, 0.95))
}