///|
priv struct TextCursor {
  text : String
  mut index : Int
  line : Int
}

///|
fn TextCursor::new(text : String, line : Int) -> TextCursor {
  { text, index: 0, line }
}

///|
fn TextCursor::at_end(self : TextCursor) -> Bool {
  self.index >= self.text.length()
}

///|
fn TextCursor::remaining(self : TextCursor) -> String {
  self.text[self.index:].to_owned()
}

///|
fn TextCursor::peek(self : TextCursor) -> Int {
  if self.at_end() {
    -1
  } else {
    self.text[self.index].to_int()
  }
}

///|
fn TextCursor::skip_spaces(self : TextCursor) -> Unit {
  while !self.at_end() {
    let code = self.peek()
    if code == 32 || code == 9 || code == 13 {
      self.index = self.index + 1
    } else {
      break
    }
  }
}

///|
fn TextCursor::consume_char(self : TextCursor, expected : Int) -> Bool {
  if self.peek() == expected {
    self.index = self.index + 1
    true
  } else {
    false
  }
}

///|
fn TextCursor::consume_text(self : TextCursor, expected : String) -> Bool {
  if self.index + expected.length() > self.text.length() {
    return false
  }
  let actual = self.text[self.index:self.index + expected.length()].to_owned()
  if actual == expected {
    self.index = self.index + expected.length()
    true
  } else {
    false
  }
}

///|
fn cursor_error(
  cursor : TextCursor,
  code : DbcErrorCode,
  message : String,
) -> DbcError {
  DbcError::new(
    code,
    message,
    cursor.line,
    cursor.index + 1,
    context=cursor.remaining(),
  )
}

///|
fn is_identifier_start(code : Int) -> Bool {
  (code >= 65 && code <= 90) || (code >= 97 && code <= 122) || code == 95
}

///|
fn is_identifier_continue(code : Int) -> Bool {
  is_identifier_start(code) || (code >= 48 && code <= 57)
}

///|
fn TextCursor::identifier(self : TextCursor) -> Result[String, DbcError] {
  self.skip_spaces()
  let start = self.index
  if !is_identifier_start(self.peek()) {
    return Err(
      cursor_error(self, InvalidIdentifier, "expected a DBC identifier"),
    )
  }
  self.index = self.index + 1
  while is_identifier_continue(self.peek()) {
    self.index = self.index + 1
  }
  Ok(self.text[start:self.index].to_owned())
}

///|
fn TextCursor::integer(self : TextCursor) -> Result[Int, DbcError] {
  self.skip_spaces()
  let start = self.index
  let mut negative = false
  if self.consume_char(45) {
    negative = true
  } else {
    ignore(self.consume_char(43))
  }
  let digit_start = self.index
  let mut value = 0
  let mut digits = 0
  while self.peek() >= 48 && self.peek() <= 57 {
    if digits >= 10 {
      return Err(
        cursor_error(
          self,
          InvalidInteger,
          "integer exceeds the supported digit bound",
        ),
      )
    }
    value = value * 10 + self.peek() - 48
    self.index = self.index + 1
    digits = digits + 1
  }
  if self.index == digit_start {
    self.index = start
    return Err(cursor_error(self, InvalidInteger, "expected a decimal integer"))
  }
  Ok(if negative { -value } else { value })
}

///|
fn power_of_ten(exponent : Int) -> Double {
  let mut value = 1.0
  if exponent >= 0 {
    for _ in 0.. Result[Double, DbcError] {
  self.skip_spaces()
  let start = self.index
  let mut negative = false
  if self.consume_char(45) {
    negative = true
  } else {
    ignore(self.consume_char(43))
  }
  let mut value = 0.0
  let mut digits = 0
  while self.peek() >= 48 && self.peek() <= 57 {
    value = value * 10.0 + (self.peek() - 48).to_double()
    self.index = self.index + 1
    digits = digits + 1
  }
  if self.consume_char(46) {
    let mut scale = 1.0
    while self.peek() >= 48 && self.peek() <= 57 {
      scale = scale * 10.0
      value = value + (self.peek() - 48).to_double() / scale
      self.index = self.index + 1
      digits = digits + 1
    }
  }
  if digits == 0 {
    self.index = start
    return Err(cursor_error(self, InvalidNumber, "expected a decimal number"))
  }
  if self.peek() == 69 || self.peek() == 101 {
    self.index = self.index + 1
    let exponent = match self.integer() {
      Ok(value) => value
      Err(_) =>
        return Err(
          cursor_error(self, InvalidNumber, "invalid numeric exponent"),
        )
    }
    if exponent < -308 || exponent > 308 {
      return Err(
        cursor_error(self, InvalidNumber, "numeric exponent is out of range"),
      )
    }
    value = value * power_of_ten(exponent)
  }
  Ok(if negative { -value } else { value })
}

///|
fn TextCursor::quoted(self : TextCursor) -> Result[String, DbcError] {
  self.skip_spaces()
  if !self.consume_char(34) {
    return Err(cursor_error(self, InvalidSyntax, "expected a quoted string"))
  }
  let mut output = ""
  while !self.at_end() {
    let code = self.peek()
    self.index = self.index + 1
    if code == 34 {
      return Ok(output)
    }
    if code == 92 {
      if self.at_end() {
        return Err(
          cursor_error(self, InvalidSyntax, "quoted string ends with an escape"),
        )
      }
      let escaped = self.peek()
      self.index = self.index + 1
      if escaped == 34 || escaped == 92 {
        output = output + escaped.to_char().unwrap().to_string()
      } else if escaped == 110 {
        output = output + "\n"
      } else if escaped == 114 {
        output = output + "\r"
      } else if escaped == 116 {
        output = output + "\t"
      } else {
        return Err(
          cursor_error(self, InvalidSyntax, "unsupported quoted-string escape"),
        )
      }
    } else if code < 32 {
      return Err(
        cursor_error(
          self,
          InvalidSyntax,
          "quoted string contains a control character",
        ),
      )
    } else {
      output = output + code.to_char().unwrap().to_string()
    }
  }
  Err(cursor_error(self, InvalidSyntax, "unterminated quoted string"))
}

///|
fn TextCursor::expect_char(
  self : TextCursor,
  expected : Int,
  label : String,
) -> Result[Unit, DbcError] {
  self.skip_spaces()
  if self.consume_char(expected) {
    Ok(())
  } else {
    Err(cursor_error(self, InvalidSyntax, "expected " + label))
  }
}

///|
fn TextCursor::finish(
  self : TextCursor,
  semicolon? : Bool = false,
) -> Result[Unit, DbcError] {
  self.skip_spaces()
  if semicolon {
    ignore(self.consume_char(59))
    self.skip_spaces()
  }
  if self.at_end() {
    Ok(())
  } else {
    Err(cursor_error(self, InvalidSyntax, "unexpected trailing content"))
  }
}