///|
pub enum UnitDimension {
  Mass
  Amount
  Volume
  Time
  Temperature
  Energy
  Power
  Pressure
  Dimensionless
} derive(Debug, Eq)

///|
pub struct UnitSpec {
  symbol : String
  dimension : UnitDimension
  to_base : String
  offset : String
} derive(Debug, Eq)

///|
pub fn unit_spec(
  symbol : String,
  dimension : UnitDimension,
  to_base : String,
  offset? : String = "0",
) -> UnitSpec {
  { symbol, dimension, to_base, offset }
}

///|
pub fn unit_dimension(symbol : String) -> UnitDimension? {
  match symbol.trim() {
    "kg" | "g" | "mg" | "lb" => Some(Mass)
    "mol" | "kmol" | "mmol" => Some(Amount)
    "m3" | "L" | "l" | "mL" | "gal" => Some(Volume)
    "s" | "min" | "h" | "day" => Some(Time)
    "K" | "C" | "degC" | "F" | "degF" => Some(Temperature)
    "J" | "kJ" | "MJ" | "Wh" | "kWh" => Some(Energy)
    "W" | "kW" | "MW" | "hp" => Some(Power)
    "Pa" | "kPa" | "MPa" | "bar" | "atm" | "psi" => Some(Pressure)
    "1" | "mol/mol" | "%" => Some(Dimensionless)
    _ => None
  }
}

///|
pub fn known_units(dimension : UnitDimension) -> Array[UnitSpec] {
  match dimension {
    Mass =>
      [
        unit_spec("kg", Mass, "1"),
        unit_spec("g", Mass, "0.001"),
        unit_spec("mg", Mass, "0.000001"),
        unit_spec("lb", Mass, "0.45359237"),
      ]
    Amount =>
      [
        unit_spec("mol", Amount, "1"),
        unit_spec("kmol", Amount, "1000"),
        unit_spec("mmol", Amount, "0.001"),
      ]
    Volume =>
      [
        unit_spec("m3", Volume, "1"),
        unit_spec("L", Volume, "0.001"),
        unit_spec("l", Volume, "0.001"),
        unit_spec("mL", Volume, "0.000001"),
        unit_spec("gal", Volume, "0.003785411784"),
      ]
    Time =>
      [
        unit_spec("s", Time, "1"),
        unit_spec("min", Time, "60"),
        unit_spec("h", Time, "3600"),
        unit_spec("day", Time, "86400"),
      ]
    Temperature =>
      [
        unit_spec("K", Temperature, "1"),
        unit_spec("C", Temperature, "1", offset="273.15"),
        unit_spec("degC", Temperature, "1", offset="273.15"),
        unit_spec("F", Temperature, "0.5555555556", offset="255.3722222222"),
        unit_spec("degF", Temperature, "0.5555555556", offset="255.3722222222"),
      ]
    Energy =>
      [
        unit_spec("J", Energy, "1"),
        unit_spec("kJ", Energy, "1000"),
        unit_spec("MJ", Energy, "1000000"),
        unit_spec("Wh", Energy, "3600"),
        unit_spec("kWh", Energy, "3600000"),
      ]
    Power =>
      [
        unit_spec("W", Power, "1"),
        unit_spec("kW", Power, "1000"),
        unit_spec("MW", Power, "1000000"),
        unit_spec("hp", Power, "745.699871"),
      ]
    Pressure =>
      [
        unit_spec("Pa", Pressure, "1"),
        unit_spec("kPa", Pressure, "1000"),
        unit_spec("MPa", Pressure, "1000000"),
        unit_spec("bar", Pressure, "100000"),
        unit_spec("atm", Pressure, "101325"),
        unit_spec("psi", Pressure, "6894.757293"),
      ]
    Dimensionless =>
      [
        unit_spec("1", Dimensionless, "1"),
        unit_spec("mol/mol", Dimensionless, "1"),
        unit_spec("%", Dimensionless, "0.01"),
      ]
  }
}

///|
fn unit_lookup(symbol : String) -> UnitSpec? {
  match unit_dimension(symbol) {
    Some(dimension) =>
      for item in known_units(dimension) {
        if item.symbol == symbol.trim().to_owned() {
          break Some(item)
        }
      } nobreak {
        None
      }
    None => None
  }
}

///|
pub fn compatible_units(left : String, right : String) -> Bool {
  match (unit_dimension(left), unit_dimension(right)) {
    (Some(a), Some(b)) => a == b
    _ => false
  }
}

///|
pub fn unit_catalog() -> Array[UnitSpec] {
  let all : Array[UnitSpec] = []
  for
    dimension in [
      Mass,
      Amount,
      Volume,
      Time,
      Temperature,
      Energy,
      Power,
      Pressure,
      Dimensionless,
    ] {
    for item in known_units(dimension) {
      all.push(item)
    }
  }
  all
}