///|
pub(all) enum FlowArrangement {
  CounterCurrent
  Parallel
} derive(Debug, Eq)

///|
fn safe_lmtd(delta_a : Double, delta_b : Double) -> Double {
  if delta_a <= 0.0 || delta_b <= 0.0 {
    0.0
  } else if (delta_a - delta_b).abs() < 1.0e-9 {
    (delta_a + delta_b) / 2.0
  } else {
    (delta_a - delta_b) / @math.ln(delta_a / delta_b)
  }
}

///|
pub fn lmtd(
  hot_in_temperature : Double,
  hot_out_temperature : Double,
  cold_in_temperature : Double,
  cold_out_temperature : Double,
  arrangement? : FlowArrangement = CounterCurrent,
) -> Double {
  match arrangement {
    CounterCurrent =>
      safe_lmtd(
        hot_in_temperature - cold_out_temperature,
        hot_out_temperature - cold_in_temperature,
      )
    Parallel =>
      safe_lmtd(
        hot_in_temperature - cold_in_temperature,
        hot_out_temperature - cold_out_temperature,
      )
  }
}

///|
pub fn exchanger_ua(duty : Double, lmtd_value : Double) -> Double {
  if lmtd_value == 0.0 {
    0.0
  } else {
    duty.abs() / lmtd_value
  }
}

///|
pub fn parallel_lmtd(
  hot_in_temperature : Double,
  hot_out_temperature : Double,
  cold_in_temperature : Double,
  cold_out_temperature : Double,
) -> Double {
  lmtd(
    hot_in_temperature,
    hot_out_temperature,
    cold_in_temperature,
    cold_out_temperature,
    arrangement=Parallel,
  )
}

///|
pub fn effectiveness_ntu(
  counter_current~ : Bool,
  c_min : Double,
  c_max : Double,
  ua : Double,
) -> Double {
  if c_min <= 0.0 || c_max <= 0.0 {
    0.0
  } else {
    let cr = c_min / c_max
    let ntu = ua / c_min
    if counter_current {
      if (1.0 - cr).abs() < 1.0e-9 {
        ntu / (1.0 + ntu)
      } else {
        let e = @math.exp(-(1.0 - cr) * ntu)
        (1.0 - e) / (1.0 - cr * e)
      }
    } else {
      (1.0 - @math.exp(-(1.0 + cr) * ntu)) / (1.0 + cr)
    }
  }
}

///|
pub fn heat_exchanger(
  hot_in : Stream,
  hot_out_temperature : Double,
  cold_in : Stream,
  cold_out_temperature : Double,
  arrangement? : FlowArrangement = CounterCurrent,
) -> HeatExchangerResult {
  let hot_out = hot_in
    .with_temperature(hot_out_temperature)
    .with_id("\{hot_in.id}-out")
  let cold_out = cold_in
    .with_temperature(cold_out_temperature)
    .with_id("\{cold_in.id}-out")
  let hot_duty = heat_duty(hot_in, hot_out)
  let cold_duty = heat_duty(cold_in, cold_out)
  let exchanged = (cold_duty.abs() + hot_duty.abs()) / 2.0
  let approach = lmtd(
    hot_in.temperature,
    hot_out.temperature,
    cold_in.temperature,
    cold_out.temperature,
    arrangement~,
  )
  let c_hot = hot_in.heat_capacity_flow()
  let c_cold = cold_in.heat_capacity_flow()
  let max_recoverable = c_hot.min(c_cold) *
    (hot_in.temperature - cold_in.temperature).abs()
  {
    hot_out,
    cold_out,
    duty: exchanged,
    lmtd: approach,
    ua: exchanger_ua(exchanged, approach),
    heat_recovery_fraction: if max_recoverable == 0.0 {
      0.0
    } else {
      exchanged / max_recoverable
    },
  }
}