///|
// asciidoctor-pdf's `Measurements`: units of measurement to PDF points.

///|
/// `to_pt num, units`: the number in points; None for an unknown unit
/// (Ruby raises).
pub fn to_pt(num : Double, units : String) -> Double? {
  match units {
    "" | "pt" => Some(num)
    "in" => Some(num * 72.0)
    "mm" => Some(num * (72.0 / 25.4))
    "cm" => Some(num * (720.0 / 25.4))
    // assuming a canvas of 96 dpi
    "px" => Some(num * 0.75)
    "pc" => Some(num * 12.0)
    _ => None
  }
}

///|
/// Whether `s[i:]` starts with a unit of `(in|mm|cm|p[txc])`; its length.
fn unit_at(s : String, i : Int) -> String? {
  if i + 2 > s.length() {
    return None
  }
  let u = s[i:i + 2].to_owned()
  match u {
    "in" | "mm" | "cm" | "pt" | "px" | "pc" => Some(u)
    _ => None
  }
}

///|
/// `str_to_pt`: `MeasurementValueRx = /(\d+|\d*\.\d+)(in|mm|cm|p[txc])?$/`
/// matched against the value's end, converted; else `to_f`.
pub fn str_to_pt(val : String) -> Double {
  // the unit, if the value ends with one
  let (body, units) = if val.length() >= 2 &&
    unit_at(val, val.length() - 2) is Some(u) {
    (val[:val.length() - 2].to_owned(), u)
  } else {
    (val, "")
  }
  // the number just before it: digits, optionally with one decimal point
  let mut i = body.length()
  while i > 0 && is_digit(body[i - 1].to_int().unsafe_to_char()) {
    i -= 1
  }
  let frac_start = i
  let mut number_start = i
  if i > 0 && body[i - 1] == '.' && frac_start < body.length() {
    let mut j = i - 1
    while j > 0 && is_digit(body[j - 1].to_int().unsafe_to_char()) {
      j -= 1
    }
    number_start = j
  }
  if number_start == body.length() {
    // no number at the end
    if units != "" {
      return ruby_string_to_f(val)
    }
    return ruby_string_to_f(val)
  }
  let num = ruby_string_to_f(body[number_start:].to_owned())
  to_pt(num, units).unwrap_or(num)
}

///|
/// `InsetMeasurementValueRx`:
/// `(?<=^| |\()(-?\d+(?:\.\d+)?)(in|mm|cm|p[txc])(?=$| |\))` — every
/// measurement in the string converted to points (Ruby's `Float#to_s`),
/// when some digit is followed by a unit (`MeasurementValueHintRx`).
pub fn resolve_measurement_values(str : String) -> String {
  // MeasurementValueHintRx: /\d(in|mm|cm|p[txc])/
  let mut hint = false
  for i in 0.. {
          let pt = to_pt(ruby_string_to_f(num), units).unwrap_or(0.0)
          sb.write_string(ruby_float_to_s(pt))
          i = end
          continue
        }
        None => ()
      }
    }
    sb.write_char(str[i].to_int().unsafe_to_char())
    i += 1
  }
  sb.to_string()
}

///|
/// `-?\d+(?:\.\d+)?` then a unit then the end, a space or `)`, at `start`:
/// (end, number, unit).
fn match_inset_measurement(s : String, start : Int) -> (Int, String, String)? {
  let n = s.length()
  let mut i = start
  if i < n && s[i] == '-' {
    i += 1
  }
  let digits_start = i
  while i < n && is_digit(s[i].to_int().unsafe_to_char()) {
    i += 1
  }
  if i == digits_start {
    return None
  }
  // the fraction, when digits follow the point (the regex backtracks over
  // a point without digits)
  if i + 1 < n && s[i] == '.' && is_digit(s[i + 1].to_int().unsafe_to_char()) {
    i += 1
    while i < n && is_digit(s[i].to_int().unsafe_to_char()) {
      i += 1
    }
  }
  guard unit_at(s, i) is Some(units) else { return None }
  let end = i + 2
  if end < n && s[end] != ' ' && s[end] != ')' {
    return None
  }
  Some((end, s[start:i].to_owned(), units))
}