///|
/// Lightweight property correlations used by design and screening calculations.
pub enum PhaseState {
  Solid
  Liquid
  Vapor
  Supercritical
}

///|
pub fn solid_state() -> PhaseState {
  Solid
}

///|
pub fn liquid_state() -> PhaseState {
  Liquid
}

///|
pub fn vapor_state() -> PhaseState {
  Vapor
}

///|
pub fn supercritical_state() -> PhaseState {
  Supercritical
}

///|
pub struct AntoineCorrelation {
  a : Float
  b : Float
  c : Float
  minimum_c : Float
  maximum_c : Float
}

///|
pub fn antoine(
  a : Float,
  b : Float,
  c : Float,
  minimum_c : Float,
  maximum_c : Float,
) -> AntoineCorrelation {
  { a, b, c, minimum_c, maximum_c }
}

///|
pub fn AntoineCorrelation::log10_pressure(
  self : AntoineCorrelation,
  temperature_c : Float,
) -> Float {
  self.a - self.b / (self.c + temperature_c)
}

///|
pub fn AntoineCorrelation::pressure_kpa(
  self : AntoineCorrelation,
  temperature_c : Float,
) -> Float {
  let x : Float = self.log10_pressure(temperature_c)
  let p : Float = 1.0 + 2.302585 * x + 2.65095 * x * x + 2.03444 * x * x * x
  p.max(0.0) * 0.133322
}

///|
pub fn AntoineCorrelation::valid(
  self : AntoineCorrelation,
  temperature_c : Float,
) -> Bool {
  temperature_c >= self.minimum_c && temperature_c <= self.maximum_c
}

///|
pub struct LinearProperty {
  reference : Float
  slope : Float
  reference_temperature : Float
}

///|
pub fn linear_property(
  reference : Float,
  slope : Float,
  reference_temperature : Float,
) -> LinearProperty {
  { reference, slope, reference_temperature }
}

///|
pub fn LinearProperty::at(
  self : LinearProperty,
  temperature_c : Float,
) -> Float {
  self.reference + self.slope * (temperature_c - self.reference_temperature)
}

///|
pub struct ChemicalPropertySet {
  name : String
  molecular_weight : Float
  density : LinearProperty
  viscosity : LinearProperty
  heat_capacity : LinearProperty
  vapor_pressure : AntoineCorrelation
}

///|
pub fn chemical_properties(
  name : String,
  molecular_weight : Float,
  density : LinearProperty,
  viscosity : LinearProperty,
  heat_capacity : LinearProperty,
  vapor_pressure : AntoineCorrelation,
) -> ChemicalPropertySet {
  { name, molecular_weight, density, viscosity, heat_capacity, vapor_pressure }
}

///|
pub fn ChemicalPropertySet::density_at(
  self : ChemicalPropertySet,
  temperature_c : Float,
) -> Float {
  self.density.at(temperature_c)
}

///|
pub fn ChemicalPropertySet::viscosity_at(
  self : ChemicalPropertySet,
  temperature_c : Float,
) -> Float {
  self.viscosity.at(temperature_c)
}

///|
pub fn ChemicalPropertySet::heat_capacity_at(
  self : ChemicalPropertySet,
  temperature_c : Float,
) -> Float {
  self.heat_capacity.at(temperature_c)
}

///|
pub fn ChemicalPropertySet::vapor_pressure_at(
  self : ChemicalPropertySet,
  temperature_c : Float,
) -> Float {
  self.vapor_pressure.pressure_kpa(temperature_c)
}

///|
pub fn ChemicalPropertySet::phase_at(
  self : ChemicalPropertySet,
  temperature_c : Float,
  pressure_kpa : Float,
) -> PhaseState {
  if pressure_kpa > 22000.0 && temperature_c > 374.0 {
    Supercritical
  } else if self.vapor_pressure_at(temperature_c) > pressure_kpa {
    Vapor
  } else {
    Liquid
  }
}

///|
pub fn ChemicalPropertySet::mass_to_moles(
  self : ChemicalPropertySet,
  mass_kg : Float,
) -> Float {
  if self.molecular_weight == 0.0 {
    0.0
  } else {
    mass_kg * 1000.0 / self.molecular_weight
  }
}

///|
pub fn ChemicalPropertySet::moles_to_mass(
  self : ChemicalPropertySet,
  moles : Float,
) -> Float {
  moles * self.molecular_weight / 1000.0
}

///|
pub struct MixtureProperty {
  component : String
  mole_fraction : Float
  property : ChemicalPropertySet
}

///|
pub fn mixture_property(
  component : String,
  mole_fraction : Float,
  property : ChemicalPropertySet,
) -> MixtureProperty {
  { component, mole_fraction, property }
}

///|
pub fn mixture_density(
  items : Array[MixtureProperty],
  temperature_c : Float,
) -> Float {
  items.fold(init=0.0, fn(v, item) {
    v + item.mole_fraction * item.property.density_at(temperature_c)
  })
}

///|
pub fn mixture_heat_capacity(
  items : Array[MixtureProperty],
  temperature_c : Float,
) -> Float {
  items.fold(init=0.0, fn(v, item) {
    v + item.mole_fraction * item.property.heat_capacity_at(temperature_c)
  })
}

///|
pub fn mixture_vapor_pressure(
  items : Array[MixtureProperty],
  temperature_c : Float,
) -> Float {
  items.fold(init=0.0, fn(v, item) {
    v + item.mole_fraction * item.property.vapor_pressure_at(temperature_c)
  })
}

///|
pub fn mixture_fraction_total(items : Array[MixtureProperty]) -> Float {
  items.fold(init=0.0, fn(v, item) { v + item.mole_fraction })
}

///|
pub fn mixture_valid(items : Array[MixtureProperty]) -> Bool {
  let total = mixture_fraction_total(items)
  total > 0.999 &&
  total < 1.001 &&
  items.all(fn(item) { item.mole_fraction >= 0.0 })
}

///|
pub fn property_reynolds_number(
  density : Float,
  velocity : Float,
  diameter : Float,
  viscosity : Float,
) -> Float {
  if viscosity == 0.0 {
    0.0
  } else {
    density * velocity * diameter / viscosity
  }
}

///|
pub fn prandtl_number(
  heat_capacity : Float,
  viscosity : Float,
  conductivity : Float,
) -> Float {
  if conductivity == 0.0 {
    0.0
  } else {
    heat_capacity * viscosity / conductivity
  }
}

///|
pub fn thermal_expansion(density : Float, slope : Float) -> Float {
  if density == 0.0 {
    0.0
  } else {
    -slope / density
  }
}

///|
pub fn property_table(
  items : Array[MixtureProperty],
  temperature_c : Float,
) -> ReportTable {
  let rows : Array[Array[String]] = []
  for item in items {
    rows.push([
      item.component,
      "\{item.mole_fraction}",
      "\{item.property.density_at(temperature_c)}",
      "\{item.property.viscosity_at(temperature_c)}",
      "\{item.property.heat_capacity_at(temperature_c)}",
      "\{item.property.vapor_pressure_at(temperature_c)}",
    ])
  }
  table(
    [
      "component", "mole fraction", "density", "viscosity", "heat capacity", "vapor pressure",
    ],
    rows,
  )
}