///|
priv struct FloatFormat {
  prefix : String
  suffix : String
  conversion : Char
  width : Int32?
  precision : Int32?
  left : Bool
  plus : Bool
  space : Bool
  zero : Bool
  alternate : Bool
}

///|
let sequence_profile : @numfmt.Profile = @numfmt.Extended80

///|
fn parse_float_format(text : String) -> FloatFormat raise SeqError {
  let chars : Array[Char] = text.iter().collect()
  let prefix = StringBuilder()
  let suffix = StringBuilder()
  let mut index = 0
  while index < chars.length() {
    if chars[index] != '%' {
      prefix.write_char(chars[index])
      index += 1
    } else if index + 1 < chars.length() && chars[index + 1] == '%' {
      prefix.write_char('%')
      index += 2
    } else {
      break
    }
  }
  if index == chars.length() {
    raise SeqError("format '\{text}' has no % directive")
  }
  index += 1
  let mut left = false
  let mut plus = false
  let mut space = false
  let mut zero = false
  let mut alternate = false
  while index < chars.length() {
    match chars[index] {
      '-' => left = true
      '+' => plus = true
      ' ' => space = true
      '0' => zero = true
      '#' => alternate = true
      '\'' => ()
      _ => break
    }
    index += 1
  }
  let mut width : Int32? = None
  let digits = StringBuilder()
  while index < chars.length() && chars[index] is ('0'..<='9') {
    digits.write_char(chars[index])
    index += 1
  }
  if !digits.is_empty() {
    width = Some(
      @string.parse_int(digits.to_string()) catch {
        _ => raise SeqError("format field width is out of range")
      },
    )
  }
  let mut precision : Int32? = None
  if index < chars.length() && chars[index] == '.' {
    index += 1
    let digits = StringBuilder()
    while index < chars.length() && chars[index] is ('0'..<='9') {
      digits.write_char(chars[index])
      index += 1
    }
    precision = Some(
      if digits.is_empty() {
        0
      } else {
        @string.parse_int(digits.to_string()) catch {
          _ => raise SeqError("format precision is out of range")
        }
      },
    )
  }
  if index < chars.length() && chars[index] == 'L' {
    index += 1
  }
  if index == chars.length() {
    raise SeqError("format '\{text}' ends in %")
  }
  let conversion = chars[index]
  if !(conversion is ('a' | 'A' | 'e' | 'E' | 'f' | 'F' | 'g' | 'G')) {
    raise SeqError("format '\{text}' has unknown %\{conversion} directive")
  }
  index += 1
  while index < chars.length() {
    if chars[index] != '%' {
      suffix.write_char(chars[index])
      index += 1
    } else if index + 1 < chars.length() && chars[index + 1] == '%' {
      suffix.write_char('%')
      index += 2
    } else {
      raise SeqError("format '\{text}' has too many % directives")
    }
  }
  {
    prefix: prefix.to_string(),
    suffix: suffix.to_string(),
    conversion,
    width,
    precision,
    left,
    plus,
    space,
    zero,
    alternate,
  }
}

///|
fn FloatFormat::field(self : FloatFormat, value : @numfmt.Value) -> String {
  let body = @numfmt.format_magnitude(
    value,
    self.conversion,
    self.precision,
    self.alternate,
    sequence_profile,
    normalized_hex=false,
  )
  let nonfinite = match value {
    @numfmt.Finite(_, _, _) => false
    _ => true
  }
  let sign = if value is @numfmt.NaN(_) {
    ""
  } else if value.is_negative() {
    "-"
  } else if self.plus {
    "+"
  } else if self.space {
    " "
  } else {
    ""
  }
  let (prefix, body) = if self.conversion is ('a' | 'A') && !nonfinite {
    (sign + body[:2].to_owned(), body[2:].to_owned())
  } else {
    (sign, body)
  }
  let length = prefix.length() + body.length()
  let padding = Int32::max(self.width.unwrap_or(0) - length, 0)
  if self.left {
    prefix + body + " ".repeat(padding)
  } else if self.zero && !nonfinite {
    prefix + "0".repeat(padding) + body
  } else {
    " ".repeat(padding) + prefix + body
  }
}

///|
fn parse_float_operand(text : String) -> @numfmt.Value raise SeqError {
  let (value, consumed, range_error) = @numfmt.parse_prefix(
    text, sequence_profile,
  )
  if consumed == 0 ||
    consumed != text.iter().count() ||
    (range_error && value is @numfmt.Infinity(_)) {
    raise SeqError(
      "invalid floating point argument: '\{text}'\nTry 'seq --help' for more information.",
    )
  }
  if value is @numfmt.NaN(_) {
    raise SeqError(
      "invalid 'not-a-number' argument: '\{text}'\nTry 'seq --help' for more information.",
    )
  }
  value
}

///|
async fn print_formatted(
  operands : Array[String],
  format : FloatFormat,
  separator : String,
) -> Unit {
  let parsed = operands.map(parse_float_operand)
  let one = @numfmt.parse_prefix("1", sequence_profile).0
  let (first, step, last) = match parsed {
    [last] => (one, one, last)
    [first, last] => (first, one, last)
    [first, step, last] => (first, step, last)
    _ => abort("unreachable")
  }
  let direction = @numfmt.compare(step, @numfmt.zero())
  if direction == 0 {
    let text = if operands.length() == 3 { operands[1] } else { "1" }
    raise SeqError(
      "invalid Zero increment value: '\{text}'\nTry 'seq --help' for more information.",
    )
  }
  let before_end = fn(value) {
    let order = @numfmt.compare(value, last)
    if direction < 0 {
      order >= 0
    } else {
      order <= 0
    }
  }
  if !before_end(first) {
    return
  }
  let output = @io.BufferedWriter::new(@stdio.stdout, size=65536)
  let mut current = first
  let mut index = BigInt::from_int(1)
  let mut emitted = false
  for ;; {
    if emitted {
      output.write(separator)
    }
    output.write(format.prefix)
    let field = format.field(current)
    output.write(field)
    output.write(format.suffix)
    emitted = true
    let next = @numfmt.sequence_value(first, step, index, sequence_profile)
    index = index + BigInt::from_int(1)
    if !before_end(next) {
      let next_field = format.field(next)
      let (recovered, consumed, range_error) = @numfmt.parse_prefix(
        next_field, sequence_profile,
      )
      if consumed == next_field.iter().count() &&
        consumed > 0 &&
        !range_error &&
        @numfmt.compare(recovered, last) == 0 &&
        next_field != field {
        output.write(separator)
        output.write(format.prefix)
        output.write(next_field)
        output.write(format.suffix)
      }
      break
    }
    current = next
  }
  output.write("\n")
  output.flush()
}