// libinjection parser state translated to bounds-checked byte indices.

///|
priv struct Scanner {
  data : Bytes
  dialect : Dialect
  quote : Quote
  mut pos : Int
  mut tokens : Int
  mut dash_ambiguous : Int
  mut hash_count : Int
}

///|
fn scanner(data : Bytes, dialect : Dialect, quote : Quote) -> Scanner {
  { data, dialect, quote, pos: 0, tokens: 0, dash_ambiguous: 0, hash_count: 0, }
}

///|
fn Scanner::at(self : Scanner, i : Int) -> Int {
  if i >= 0 && i < self.data.length() {
    self.data[i].to_int()
  } else {
    -1
  }
}

///|
fn Scanner::take(
  self : Scanner,
  kind : String,
  start : Int,
  end : Int,
) -> Token {
  self.pos = end
  token(kind, start, byte_slice(self.data, start, end))
}

///|
fn Scanner::span(self : Scanner, start : Int, accepted : String) -> Int {
  let mut end = start
  while end < self.data.length() && contains_byte(self.at(end), accepted) {
    end += 1
  }
  end
}

///|
fn Scanner::find_pair(
  self : Scanner,
  start : Int,
  end : Int,
  a : Int,
  b : Int,
) -> Int {
  for i = start; i + 1 < end; i = i + 1 {
    if self.at(i) == a && self.at(i + 1) == b {
      return i
    }
  }
  -1
}

///|
fn Scanner::line_comment(self : Scanner) -> Token {
  let start = self.pos
  let mut end = start
  while end < self.data.length() && self.at(end) != 10 {
    end += 1
  }
  let t = self.take("c", start, end)
  if end < self.data.length() {
    self.pos += 1
  }
  t
}

///|
fn Scanner::operator(self : Scanner) -> Token {
  let start = self.pos
  if self.at(start) == 60 &&
    self.at(start + 1) == 61 &&
    self.at(start + 2) == 62 {
    return self.take("o", start, start + 3)
  }
  if start + 1 < self.data.length() {
    let kind = lookup(byte_slice(self.data, start, start + 2))
    if kind != "" {
      return self.take(kind, start, start + 2)
    }
  }
  self.take(if self.at(start) == 58 { ":" } else { "o" }, start, start + 1)
}

///|
fn Scanner::dash(self : Scanner) -> Token {
  let start = self.pos
  if self.at(start + 1) == 45 {
    if start + 2 == self.data.length() ||
      white(self.at(start + 2)) ||
      self.at(start + 2) == 0 {
      return self.line_comment()
    }
    if self.dialect == Ansi {
      self.dash_ambiguous += 1
      return self.line_comment()
    }
  }
  self.take("o", start, start + 1)
}

///|
fn Scanner::hash(self : Scanner) -> Token {
  self.hash_count += 1
  if self.dialect == Mysql {
    self.line_comment()
  } else {
    self.take("o", self.pos, self.pos + 1)
  }
}

///|
fn Scanner::slash(self : Scanner) -> Token {
  let start = self.pos
  if self.at(start + 1) != 42 {
    return self.take("o", start, start + 1)
  }
  let close = self.find_pair(start + 2, self.data.length(), 42, 47)
  let end = if close < 0 { self.data.length() } else { close + 2 }
  let evil = self.at(start + 2) == 33 ||
    (close >= 0 && self.find_pair(start + 2, close + 1, 47, 42) >= 0)
  self.take(if evil { "X" } else { "c" }, start, end)
}