///|
/// Recoverable invalid input or numerical failure. No invalid state is silently clipped.
#derive(Debug)
pub suberror GasError {
  Invalid(String)
  Numerical(String)
}

///|
/// Primitive state for one ideal gas. Units must be consistent across the experiment.
#derive(Debug, Eq, ToJson, FromJson)
pub(all) struct Primitive {
  rho : Double
  velocity : Double
  pressure : Double
}

///|
/// A finite heat capacity ratio greater than one.
#derive(Debug, Eq)
pub struct Gas {
  gamma : Double
}

///|
fn finite(x : Double) -> Bool {
  !x.is_nan() && !x.is_inf()
}

///|
pub fn Gas::new(gamma : Double) -> Gas raise GasError {
  if !finite(gamma) || gamma <= 1.0 {
    raise Invalid("gamma must be finite and greater than one")
  }
  { gamma, }
}

///|
pub fn Gas::gamma(self : Gas) -> Double {
  self.gamma
}

///|
pub fn Primitive::new(
  rho : Double,
  velocity : Double,
  pressure : Double,
) -> Primitive raise GasError {
  let q = { rho, velocity, pressure, }
  q.validate()
  q
}

///|
pub fn Primitive::validate(self : Primitive) -> Unit raise GasError {
  if !finite(self.rho) || self.rho <= 0.0 {
    raise Invalid("density must be finite and positive")
  }
  if !finite(self.pressure) || self.pressure <= 0.0 || !finite(self.velocity) {
    raise Invalid(
      "pressure must be finite and positive; velocity must be finite",
    )
  }
}

///|
pub fn Gas::sound_speed(self : Gas, q : Primitive) -> Double raise GasError {
  q.validate()
  let c = (self.gamma * (q.pressure / q.rho)).sqrt()
  if !finite(c) || c <= 0.0 {
    raise Numerical("sound speed overflow or underflow")
  }
  c
}

///|
pub fn Gas::specific_energy(self : Gas, q : Primitive) -> Double raise GasError {
  q.validate()
  let e = q.pressure / q.rho / (self.gamma - 1.0)
  if !finite(e) || e <= 0.0 {
    raise Numerical("internal energy outside floating point range")
  }
  e
}

///|
pub fn Gas::mach(self : Gas, q : Primitive) -> Double raise GasError {
  let result = q.velocity / self.sound_speed(q)
  if !finite(result) {
    raise Numerical("Mach number overflow")
  }
  result
}

///|
pub extend GasError with @moonbitlang/core/debug.Debug::{to_repr}

///|
pub extend Primitive with @moonbitlang/core/debug.Debug::{to_repr}

///|
pub extend Primitive with Eq::{equal, not_equal}

///|
pub extend Primitive with ToJson::{to_json}

///|
pub extend Primitive with @json.FromJson::{from_json}

///|
pub extend Gas with @moonbitlang/core/debug.Debug::{to_repr}

///|
pub extend Gas with Eq::{equal, not_equal}