///|
/// Mission profile with multiple operating segments.
pub struct MissionSegment {
  start : Double
  duration : Double
  stress_multiplier : Double
}

///|
pub fn mission_segment(
  start~ : Double,
  duration~ : Double,
  stress_multiplier~ : Double,
) -> MissionSegment {
  if start < 0.0 || duration < 0.0 || stress_multiplier <= 0.0 {
    abort("invalid mission segment")
  }
  { start, duration, stress_multiplier }
}

///|
pub struct MissionResult {
  survival : Double
  cumulative_hazard : Double
  segment_hazards : Array[Double]
  expected_failures : Double
}

///|
pub fn mission_result(
  survival~ : Double,
  cumulative_hazard~ : Double,
  segment_hazards~ : Array[Double],
  expected_failures~ : Double,
) -> MissionResult {
  { survival, cumulative_hazard, segment_hazards, expected_failures }
}

///|
pub fn evaluate_mission(
  model : ReliabilityModel,
  segments : Array[MissionSegment],
) -> MissionResult {
  if segments.is_empty() {
    abort("mission needs segments")
  }
  let hazards = segments.map(segment => {
    model_hazard(model, segment.start) *
    segment.duration *
    segment.stress_multiplier
  })
  let cumulative = hazards.fold(init=0.0, (sum, value) => sum + value)
  mission_result(
    survival=@math.exp(-cumulative),
    cumulative_hazard=cumulative,
    segment_hazards=hazards,
    expected_failures=1.0 - @math.exp(-cumulative),
  )
}

///|
pub fn mission_profile_grid(
  start : Double,
  duration : Double,
  segments : Int,
  stress : Double,
) -> Array[MissionSegment] {
  let width = duration / segments.to_double()
  Array::makei(segments, i => {
    mission_segment(
      start=start + i.to_double() * width,
      duration=width,
      stress_multiplier=stress,
    )
  })
}

///|
pub fn mission_margin(
  result : MissionResult,
  target_survival : Double,
) -> Double {
  result.survival - target_survival
}

///|
pub fn mission_reliability_with_derating(
  model : ReliabilityModel,
  segments : Array[MissionSegment],
  derating : Double,
) -> Double {
  evaluate_mission(
    model,
    segments.map(segment => {
      ..segment,
      stress_multiplier: segment.stress_multiplier * derating,
    }),
  ).survival
}

///|
pub fn mission_stress_sensitivity(
  model : ReliabilityModel,
  segments : Array[MissionSegment],
  perturbation : Double,
) -> SensitivityPoint {
  let baseline = evaluate_mission(model, segments).survival
  let perturbed = mission_reliability_with_derating(
    model,
    segments,
    1.0 + perturbation,
  )
  sensitivity_point(
    parameter="stress multiplier",
    baseline~,
    perturbed~,
    absolute_change=perturbed - baseline,
    relative_change=(perturbed - baseline) / baseline.max(1.0e-300),
    elasticity=(perturbed - baseline) / baseline.max(1.0e-300) / perturbation,
  )
}