///|
priv suberror SeqError {
  SeqError(String)
}

///|
priv struct Decimal {
  coefficient : BigInt
  scale : Int
}

///|
fn pow10(exponent : Int) -> BigInt {
  let mut value = BigInt::from_int(1)
  for _ in 0.. Decimal {
  let ten = BigInt::from_int(10)
  let mut coefficient = self.coefficient
  let mut scale = self.scale
  while scale > 0 && (coefficient % ten).is_zero() {
    coefficient = coefficient / ten
    scale -= 1
  }
  { coefficient, scale, }
}

///|
fn parse_decimal(text : String) -> Decimal raise SeqError {
  if text == "" || text.contains("_") || text.length() > 4096 {
    raise SeqError("invalid number: '\{text}'")
  }
  let chars : Array[Char] = text.iter().collect()
  let mut start = 0
  let mut negative = false
  if chars[0] == '+' || chars[0] == '-' {
    negative = chars[0] == '-'
    start = 1
  }
  if start == chars.length() {
    raise SeqError("invalid number: '\{text}'")
  }
  let mut exponent_index = -1
  for index in start..= 0 {
        raise SeqError("invalid number: '\{text}'")
      }
      exponent_index = index
    }
  }
  let mantissa_end = if exponent_index < 0 {
    chars.length()
  } else {
    exponent_index
  }
  let exponent = if exponent_index < 0 {
    0
  } else {
    let exponent_text = text[exponent_index + 1:].to_owned()
    @string.parse_int(exponent_text) catch {
      _ => raise SeqError("invalid exponent: '\{text}'")
    }
  }
  if exponent > 4096 || exponent < -4096 {
    raise SeqError("exponent out of supported range: '\{text}'")
  }
  let digits = StringBuilder()
  let mut decimal_seen = false
  let mut fractional_digits = 0
  for index in start.. {
        digits.write_char(digit)
        if decimal_seen {
          fractional_digits += 1
        }
      }
      '.' if !decimal_seen => decimal_seen = true
      _ => raise SeqError("invalid number: '\{text}'")
    }
  }
  if digits.is_empty() {
    raise SeqError("invalid number: '\{text}'")
  }
  let mut coefficient = @string.parse_bigint(digits.to_string()) catch {
    _ => raise SeqError("number out of range: '\{text}'")
  }
  if negative {
    coefficient = -coefficient
  }
  let scale = fractional_digits - exponent
  if scale > 4096 || scale < -4096 {
    raise SeqError("number precision exceeds the supported limit: '\{text}'")
  }
  if scale < 0 {
    coefficient = coefficient * pow10(-scale)
    return ({ coefficient, scale: 0, } : Decimal).normalize()
  }
  ({ coefficient, scale, } : Decimal).normalize()
}

///|
fn Decimal::at_scale(self : Decimal, scale : Int) -> BigInt {
  if scale == self.scale {
    self.coefficient
  } else {
    self.coefficient * pow10(scale - self.scale)
  }
}

///|
fn Decimal::compare(self : Decimal, other : Decimal) -> Int {
  let scale = if self.scale > other.scale { self.scale } else { other.scale }
  self.at_scale(scale).compare(other.at_scale(scale))
}

///|
fn Decimal::add(self : Decimal, other : Decimal) -> Decimal {
  let scale = if self.scale > other.scale { self.scale } else { other.scale }
  (
    { coefficient: self.at_scale(scale) + other.at_scale(scale), scale, } :
    Decimal).normalize()
}

///|
fn Decimal::render(self : Decimal) -> String {
  let negative = self.coefficient.compare_int(0) < 0
  let absolute = if negative { -self.coefficient } else { self.coefficient }
  let digits = absolute.to_string()
  let body = if self.scale == 0 {
    digits
  } else if digits.length() <= self.scale {
    "0." + "0".repeat(self.scale - digits.length()) + digits
  } else {
    let split = digits.length() - self.scale
    digits[:split].to_owned() + "." + digits[split:].to_owned()
  }
  if negative {
    "-" + body
  } else {
    body
  }
}

///|
fn zero_pad(text : String, width : Int) -> String {
  if text.length() >= width {
    return text
  }
  let zeros = "0".repeat(width - text.length())
  if text.has_prefix("-") {
    "-" + zeros + text[1:].to_owned()
  } else {
    zeros + text
  }
}

///|
async fn main {
  let args = @env.args()[1:]
  let operands : Array[String] = []
  let mut separator = "\n"
  let mut equal_width = false
  let mut options = true
  let mut i = 0
  while i < args.length() {
    let arg = args[i]
    if options && arg == "--" {
      options = false
    } else if options && (arg == "-w" || arg == "--equal-width") {
      equal_width = true
    } else if options && (arg == "-s" || arg == "--separator") {
      if i + 1 >= args.length() {
        @stdio.stderr.write("seq: missing separator\n")
        @sys.exit(1)
        return
      }
      i += 1
      separator = args[i]
    } else if options && arg.has_prefix("-s") && arg.length() > 2 {
      separator = arg[2:].to_owned()
    } else if options && arg.has_prefix("--separator=") {
      separator = arg[12:].to_owned()
    } else if options && arg == "--help" {
      @stdio.stdout.write(
        "Usage: seq [-w] [-s STRING] [FIRST [INCREMENT]] LAST\n",
      )
      return
    } else if options &&
      arg.has_prefix("-") &&
      arg.length() > 1 &&
      !(arg[1] is ('0'..='9' | '.')) {
      @stdio.stderr.write("seq: invalid option: '\{arg}'\n")
      @sys.exit(1)
      return
    } else {
      operands.push(arg)
    }
    i += 1
  }
  if operands.length() < 1 || operands.length() > 3 {
    @stdio.stderr.write("seq: expected one to three operands\n")
    @sys.exit(1)
    return
  }
  let parsed : Array[Decimal] = []
  for operand in operands {
    let number = parse_decimal(operand) catch {
      SeqError(message) => {
        @stdio.stderr.write("seq: \{message}\n")
        @sys.exit(1)
        return
      }
    }
    parsed.push(number)
  }
  let (first, step, last) = match parsed {
    [last] => (parse_decimal("1"), parse_decimal("1"), last)
    [first, last] => (first, parse_decimal("1"), last)
    [first, step, last] => (first, step, last)
    _ => abort("unreachable")
  }
  let step_direction = step.coefficient.compare_int(0)
  if step_direction == 0 {
    @stdio.stderr.write("seq: zero increment\n")
    @sys.exit(1)
    return
  }
  let first_text = first.render()
  let last_text = last.render()
  let width = if first_text.length() > last_text.length() {
    first_text.length()
  } else {
    last_text.length()
  }
  let mut current = first
  let mut emitted = 0
  while (step_direction > 0 && current.compare(last) <= 0) ||
        (step_direction < 0 && current.compare(last) >= 0) {
    if emitted > 0 {
      @stdio.stdout.write(separator)
    }
    let rendered = current.render()
    @stdio.stdout.write(
      if equal_width {
        zero_pad(rendered, width)
      } else {
        rendered
      },
    )
    emitted += 1
    if emitted > 10_000_000 {
      @stdio.stderr.write("seq: output limit exceeded\n")
      @sys.exit(1)
      return
    }
    current = current.add(step)
  }
  if emitted > 0 {
    @stdio.stdout.write("\n")
  }
}