///|
/// A reproducible engineering casebook. Values are dimensionless on the same
/// basis as the liquid package and are intentionally kept in source control.
pub(all) struct BenchmarkCase {
  id : String
  carrier_flow : Double
  feed_concentration : Double
  solvent_flow : Double
  distribution : Double
} derive(Debug, Eq, ToJson)

///|
pub fn BenchmarkCase::new(
  id~ : String,
  carrier_flow~ : Double,
  feed_concentration~ : Double,
  solvent_flow~ : Double,
  distribution~ : Double,
) -> BenchmarkCase raise @domain.ExtractionError {
  @domain.require_positive("benchmark carrier flow", carrier_flow)
  @domain.require_non_negative(
    "benchmark feed concentration", feed_concentration,
  )
  @domain.require_positive("benchmark solvent flow", solvent_flow)
  @domain.require_positive("benchmark distribution", distribution)
  { id, carrier_flow, feed_concentration, solvent_flow, distribution }
}

///|
pub(all) struct BenchmarkResult {
  case_id : String
  recovery : Double
  raffinate : Double
  residual : Double
  extraction_factor : Double
} derive(Debug, Eq, ToJson)

///|
pub fn run(
  case : BenchmarkCase,
) -> BenchmarkResult raise @domain.ExtractionError {
  let result = @liquid.single_stage_constant_k(
    feed_flow=case.carrier_flow,
    feed_concentration=case.feed_concentration,
    solvent_flow=case.solvent_flow,
    distribution=case.distribution,
  )
  {
    case_id: case.id,
    recovery: result.recovery,
    raffinate: result.raffinate_concentration,
    residual: result.balance_residual,
    extraction_factor: @liquid.extraction_factor(
      distribution=case.distribution,
      solvent_flow=case.solvent_flow,
      carrier_flow=case.carrier_flow,
    ),
  }
}

///|
pub fn casebook() -> Array[BenchmarkCase] raise @domain.ExtractionError {
  [
    BenchmarkCase::new(
      id="water-acetone-01",
      carrier_flow=100.0,
      feed_concentration=0.010,
      solvent_flow=25.0,
      distribution=1.20,
    ),
    BenchmarkCase::new(
      id="water-acetone-02",
      carrier_flow=100.0,
      feed_concentration=0.025,
      solvent_flow=25.0,
      distribution=1.20,
    ),
    BenchmarkCase::new(
      id="water-acetone-03",
      carrier_flow=100.0,
      feed_concentration=0.050,
      solvent_flow=25.0,
      distribution=1.20,
    ),
    BenchmarkCase::new(
      id="water-acetone-04",
      carrier_flow=100.0,
      feed_concentration=0.100,
      solvent_flow=25.0,
      distribution=1.20,
    ),
    BenchmarkCase::new(
      id="water-acetone-05",
      carrier_flow=100.0,
      feed_concentration=0.200,
      solvent_flow=25.0,
      distribution=1.20,
    ),
    BenchmarkCase::new(
      id="water-acetone-06",
      carrier_flow=100.0,
      feed_concentration=0.010,
      solvent_flow=50.0,
      distribution=1.80,
    ),
    BenchmarkCase::new(
      id="water-acetone-07",
      carrier_flow=100.0,
      feed_concentration=0.025,
      solvent_flow=50.0,
      distribution=1.80,
    ),
    BenchmarkCase::new(
      id="water-acetone-08",
      carrier_flow=100.0,
      feed_concentration=0.050,
      solvent_flow=50.0,
      distribution=1.80,
    ),
    BenchmarkCase::new(
      id="water-acetone-09",
      carrier_flow=100.0,
      feed_concentration=0.100,
      solvent_flow=50.0,
      distribution=1.80,
    ),
    BenchmarkCase::new(
      id="water-acetone-10",
      carrier_flow=100.0,
      feed_concentration=0.200,
      solvent_flow=50.0,
      distribution=1.80,
    ),
    BenchmarkCase::new(
      id="water-acetone-11",
      carrier_flow=250.0,
      feed_concentration=0.010,
      solvent_flow=50.0,
      distribution=2.40,
    ),
    BenchmarkCase::new(
      id="water-acetone-12",
      carrier_flow=250.0,
      feed_concentration=0.025,
      solvent_flow=50.0,
      distribution=2.40,
    ),
    BenchmarkCase::new(
      id="water-acetone-13",
      carrier_flow=250.0,
      feed_concentration=0.050,
      solvent_flow=50.0,
      distribution=2.40,
    ),
    BenchmarkCase::new(
      id="water-acetone-14",
      carrier_flow=250.0,
      feed_concentration=0.100,
      solvent_flow=50.0,
      distribution=2.40,
    ),
    BenchmarkCase::new(
      id="water-acetone-15",
      carrier_flow=250.0,
      feed_concentration=0.200,
      solvent_flow=50.0,
      distribution=2.40,
    ),
    BenchmarkCase::new(
      id="water-acetone-16",
      carrier_flow=250.0,
      feed_concentration=0.010,
      solvent_flow=100.0,
      distribution=2.40,
    ),
    BenchmarkCase::new(
      id="water-acetone-17",
      carrier_flow=250.0,
      feed_concentration=0.025,
      solvent_flow=100.0,
      distribution=2.40,
    ),
    BenchmarkCase::new(
      id="water-acetone-18",
      carrier_flow=250.0,
      feed_concentration=0.050,
      solvent_flow=100.0,
      distribution=2.40,
    ),
    BenchmarkCase::new(
      id="water-acetone-19",
      carrier_flow=250.0,
      feed_concentration=0.100,
      solvent_flow=100.0,
      distribution=2.40,
    ),
    BenchmarkCase::new(
      id="water-acetone-20",
      carrier_flow=250.0,
      feed_concentration=0.200,
      solvent_flow=100.0,
      distribution=2.40,
    ),
  ]
}

///|
pub fn run_casebook(
  cases : ArrayView[BenchmarkCase],
) -> Array[BenchmarkResult] raise @domain.ExtractionError {
  if cases.length() == 0 {
    raise @domain.ExtractionError::InsufficientData(required=1, actual=0)
  }
  [
    for case in cases => run(case)
  ]
}

///|
pub fn maximum_residual(results : ArrayView[BenchmarkResult]) -> Double {
  let mut maximum = 0.0
  for result in results {
    maximum = maximum.max(result.residual.abs())
  }
  maximum
}

///|
pub fn average_recovery(results : ArrayView[BenchmarkResult]) -> Double {
  if results.length() == 0 {
    0.0
  } else {
    let mut sum = 0.0
    for result in results {
      sum += result.recovery
    }
    sum / results.length().to_double()
  }
}