///|
fn unit_definition(unit : String) -> (String, Double, Double)? {
  match canonical(unit) {
    "M" | "METRE" | "METER" => Some(("length", 1.0, 0.0))
    "FT" | "FEET" => Some(("length", 0.3048, 0.0))
    "CM" => Some(("length", 0.01, 0.0))
    "MM" => Some(("length", 0.001, 0.0))
    "IN" => Some(("length", 0.0254, 0.0))
    "V" => Some(("voltage", 1.0, 0.0))
    "MV" => Some(("voltage", 0.001, 0.0))
    "G/CC" | "G/CM3" => Some(("density", 1000.0, 0.0))
    "KG/M3" => Some(("density", 1.0, 0.0))
    "DEGC" => Some(("temperature", 1.0, 273.15))
    "DEGF" => Some(("temperature", 5.0 / 9.0, 255.3722222222222))
    "K" => Some(("temperature", 1.0, 0.0))
    "US/FT" => Some(("slowness", 1.0 / 0.3048, 0.0))
    "US/M" => Some(("slowness", 1.0, 0.0))
    _ => None
  }
}

///|
/// Unknown units may pass through identically, but are never guessed or relabelled.
pub fn convert_value(
  value : Double,
  from : String,
  to : String,
) -> Double raise WellError {
  if !finite(value) {
    fail("unit conversion requires finite input")
  }
  if canonical(from) == canonical(to) {
    return value
  }
  let result = match (unit_definition(from), unit_definition(to)) {
    (Some(a), Some(b)) => {
      if a.0 != b.0 {
        fail("incompatible unit dimensions: " + from + " -> " + to)
      }
      // Equivalent aliases must not lose range or precision via the base unit.
      if a.1 == b.1 && a.2 == b.2 {
        return value
      }
      (value * a.1 + a.2 - b.2) / b.1
    }
    _ => {
      fail("unknown unit conversion: " + from + " -> " + to)
      0.0
    }
  }
  if !finite(result) {
    fail("unit conversion overflow")
  }
  result
}

///|
pub fn convert_curve(
  log : Log,
  name : String,
  unit : String,
) -> Log raise WellError {
  validate(log)
  let column = curve_index(log, name)
  let old = log.curves[column]
  ignore(convert_value(0.0, old.unit, unit))
  let curves = log.curves.copy()
  curves[column] = {
    name: old.name,
    unit,
    description: old.description,
    api_code: old.api_code,
  }
  let rows : Array[Array[Double?]] = []
  for original in log.rows {
    let row = original.copy()
    if row[column] is Some(value) {
      row[column] = Some(convert_value(value, old.unit, unit))
    }
    rows.push(row)
  }
  let headers : Array[Header] = []
  for h in log.headers {
    let key = canonical(h.name)
    if column == 0 && (key == "STRT" || key == "STOP" || key == "STEP") {
      headers.push({
        name: h.name,
        unit,
        value: convert_value(parse_number(h.value), old.unit, unit).to_string(),
        description: h.description,
      })
    } else {
      headers.push(h)
    }
  }
  make_log(
    curves,
    rows,
    headers~,
    parameters=log.parameters,
    other=log.other,
    null_value=log.null_value,
    version_headers=log.version_headers,
    extra_sections=log.extra_sections,
  )
}