///|
/// Absorption, stripping, and contacting calculations.
pub struct EquilibriumLine {
  slope : Float
  intercept : Float
}

///|
pub fn equilibrium_line(slope : Float, intercept : Float) -> EquilibriumLine {
  { slope, intercept }
}

///|
pub fn EquilibriumLine::gas_fraction(
  self : EquilibriumLine,
  liquid_fraction : Float,
) -> Float {
  self.slope * liquid_fraction + self.intercept
}

///|
pub fn EquilibriumLine::valid(self : EquilibriumLine) -> Bool {
  self.slope >= 0.0 && self.intercept >= 0.0
}

///|
pub struct AbsorberDesign {
  gas_flow_mol_h : Float
  liquid_flow_mol_h : Float
  inlet_gas_fraction : Float
  outlet_gas_fraction : Float
  equilibrium : EquilibriumLine
  overall_kya_h : Float
}

///|
pub fn absorber_design(
  gas_flow_mol_h : Float,
  liquid_flow_mol_h : Float,
  inlet_gas_fraction : Float,
  outlet_gas_fraction : Float,
  equilibrium : EquilibriumLine,
  overall_kya_h : Float,
) -> AbsorberDesign {
  {
    gas_flow_mol_h,
    liquid_flow_mol_h,
    inlet_gas_fraction,
    outlet_gas_fraction,
    equilibrium,
    overall_kya_h,
  }
}

///|
pub fn AbsorberDesign::removal_fraction(self : AbsorberDesign) -> Float {
  if self.inlet_gas_fraction == 0.0 {
    0.0
  } else {
    (self.inlet_gas_fraction - self.outlet_gas_fraction) /
    self.inlet_gas_fraction
  }
}

///|
pub fn AbsorberDesign::solute_load_mol_h(self : AbsorberDesign) -> Float {
  self.gas_flow_mol_h * (self.inlet_gas_fraction - self.outlet_gas_fraction)
}

///|
pub fn AbsorberDesign::liquid_to_gas(self : AbsorberDesign) -> Float {
  if self.gas_flow_mol_h == 0.0 {
    0.0
  } else {
    self.liquid_flow_mol_h / self.gas_flow_mol_h
  }
}

///|
pub fn AbsorberDesign::ntu(self : AbsorberDesign) -> Float {
  if self.outlet_gas_fraction <= 0.0 ||
    self.inlet_gas_fraction <= self.outlet_gas_fraction {
    0.0
  } else {
    (self.inlet_gas_fraction - self.outlet_gas_fraction) /
    self.outlet_gas_fraction
  }
}

///|
pub fn AbsorberDesign::height(self : AbsorberDesign, htu_m : Float) -> Float {
  self.ntu() * htu_m
}

///|
pub struct PackedColumn {
  diameter_m : Float
  packing_height_m : Float
  packing_factor : Float
  gas_velocity_m_s : Float
  flooding_velocity_m_s : Float
}

///|
pub fn packed_column(
  diameter_m : Float,
  packing_height_m : Float,
  packing_factor : Float,
  gas_velocity_m_s : Float,
  flooding_velocity_m_s : Float,
) -> PackedColumn {
  {
    diameter_m,
    packing_height_m,
    packing_factor,
    gas_velocity_m_s,
    flooding_velocity_m_s,
  }
}

///|
pub fn PackedColumn::area_m2(self : PackedColumn) -> Float {
  0.785398 * self.diameter_m * self.diameter_m
}

///|
pub fn PackedColumn::flood_fraction(self : PackedColumn) -> Float {
  if self.flooding_velocity_m_s == 0.0 {
    0.0
  } else {
    self.gas_velocity_m_s / self.flooding_velocity_m_s
  }
}

///|
pub fn PackedColumn::capacity_mol_h(
  self : PackedColumn,
  gas_density_mol_m3 : Float,
) -> Float {
  self.area_m2() * self.gas_velocity_m_s * gas_density_mol_m3 * 3600.0
}

///|
pub fn PackedColumn::acceptable(
  self : PackedColumn,
  maximum_fraction : Float,
) -> Bool {
  self.flood_fraction() <= maximum_fraction && self.packing_height_m > 0.0
}

///|
pub struct TrayStage {
  number : Int
  efficiency : Float
  liquid_flow : Float
  vapor_flow : Float
}

///|
pub fn tray_stage(
  number : Int,
  efficiency : Float,
  liquid_flow : Float,
  vapor_flow : Float,
) -> TrayStage {
  { number, efficiency, liquid_flow, vapor_flow }
}

///|
pub fn TrayStage::effective_stage(self : TrayStage) -> Float {
  self.efficiency.max(0.0).min(1.0)
}

///|
pub fn TrayStage::capacity_ratio(self : TrayStage) -> Float {
  if self.vapor_flow == 0.0 {
    0.0
  } else {
    self.liquid_flow / self.vapor_flow
  }
}

///|
pub fn trays_required(theoretical_stages : Float, efficiency : Float) -> Float {
  if efficiency <= 0.0 {
    0.0
  } else {
    theoretical_stages / efficiency
  }
}

///|
pub fn stage_efficiency_table(stages : Array[TrayStage]) -> ReportTable {
  let rows : Array[Array[String]] = []
  for stage in stages {
    rows.push([
      "\{stage.number}",
      "\{stage.efficiency}",
      "\{stage.effective_stage()}",
      "\{stage.capacity_ratio()}",
    ])
  }
  table(["stage", "efficiency", "effective", "liquid/vapor"], rows)
}

///|
pub fn mass_transfer_coefficient(
  diffusivity_m2_s : Float,
  film_thickness_m : Float,
) -> Float {
  if film_thickness_m == 0.0 {
    0.0
  } else {
    diffusivity_m2_s / film_thickness_m
  }
}

///|
pub fn sherwood_number(
  coefficient_m_s : Float,
  length_m : Float,
  diffusivity_m2_s : Float,
) -> Float {
  if diffusivity_m2_s == 0.0 {
    0.0
  } else {
    coefficient_m_s * length_m / diffusivity_m2_s
  }
}

///|
pub fn transfer_duty(
  flow_mol_h : Float,
  inlet_fraction : Float,
  outlet_fraction : Float,
) -> Float {
  flow_mol_h * (inlet_fraction - outlet_fraction).max(0.0)
}

///|
pub fn transfer_gate(removal : Float, target : Float) -> Bool {
  removal >= target
}