///|
struct Statement {
  mut handle : @ffi.Sqlite3Stmt?
  mut has_row : Bool
  mut column_types : Array[Int]?
  connection : Connection
}

///|
fn Statement::get_handle(
  self : Statement,
) -> @ffi.Sqlite3Stmt raise SqliteError {
  match self.handle {
    Some(handle) => handle
    None => raise sqlite_misuse("sqlite3: statement is finalized")
  }
}

///|
fn Statement::column_info(
  self : Statement,
  index : Int,
) -> (@ffi.Sqlite3Stmt, Int) raise SqliteError {
  match (self.handle, self.has_row) {
    (Some(handle), true) => {
      let column_count = @ffi.sqlite3_column_count(handle)
      if index < 0 || index >= column_count {
        raise sqlite_misuse(
          "sqlite3: column index \{index} is outside 0..<\{column_count}",
        )
      }
      // SQLite only guarantees the initial storage class before a coercing
      // accessor runs. Cache it on the first read of each cell so typed and
      // dynamic reads remain order-independent within the current row.
      let types = match self.column_types {
        Some(types) => types
        None => {
          let types = Array::make(column_count, 0)
          self.column_types = Some(types)
          types
        }
      }
      if types[index] == 0 {
        types[index] = @ffi.sqlite3_column_type(handle, index)
      }
      (handle, types[index])
    }
    (None, _) => raise sqlite_misuse("sqlite3: statement is finalized")
    _ => raise sqlite_misuse("sqlite3: statement has no current row")
  }
}

///|
fn Statement::error(self : Statement, raw_code : Int) -> SqliteError {
  self.connection.error(raw_code)
}

///|
fn sql_tail_is_ignorable(sql : String, start : Int) -> Bool {
  let scanner = @lexbuf.StringScanner::{ data: sql[start:], cursor: 0 }
  for ;; {
    lexscan scanner with longest {
      re"^[ \t\n\f\r;\uFEFF]+" => continue
      re"^--[^\n]*(\n|$)" => continue
      re"^/[*]([^*]|[*]+[^*/])*[*]+/" => continue
      re"^/[*]([^*]|[*]+[^*/])*[*]*$" => return true
      re"^$" => return true
      _ => return false
    }
  }
}

///|
pub fn Connection::prepare(
  self : Connection,
  stmt : String,
) -> Statement raise SqliteError {
  if stmt.contains_code_unit(0x0000) {
    raise sqlite_misuse("sqlite3: SQL contains a NUL code unit")
  }
  let db = self.handle()
  let tail_offset = Ref(-1)
  let rescode = Ref(SQLITE_OK)
  let out = @ffi.sqlite3_prepare16_v2(db, stmt, tail_offset, rescode)
  if rescode.val != SQLITE_OK {
    raise sqlite_error(rescode.val, @ffi.sqlite3_errmsg(db))
  }
  if tail_offset.val < 0 {
    raise sqlite_misuse("sqlite3: SQL does not contain a statement")
  }
  if tail_offset.val > stmt.length() ||
    !sql_tail_is_ignorable(stmt, tail_offset.val) {
    let _ = @ffi.sqlite3_finalize(out)
    raise sqlite_misuse("sqlite3: SQL contains more than one statement")
  }
  { handle: Some(out), has_row: false, column_types: None, connection: self }
}

///|
pub fn[T : Bind] Statement::bind(
  self : Statement,
  index~ : Int,
  val : T,
) -> Unit raise SqliteError {
  T::bind(self, index, val)
}

///|
pub fn[T : Column] Statement::column(
  self : Statement,
  index~ : Int,
) -> T raise SqliteError {
  T::column(self, index)
}

///|
pub fn Statement::step(self : Statement) -> Bool raise SqliteError {
  let handle = self.get_handle()
  self.has_row = false
  self.column_types = None
  let rescode = @ffi.sqlite3_step(handle)
  match rescode {
    SQLITE_DONE => false
    SQLITE_ROW => {
      self.has_row = true
      true
    }
    _ => raise self.error(rescode)
  }
}

///|
pub fn Statement::finalize(self : Statement) -> Unit raise SqliteError {
  match self.handle {
    None => ()
    Some(handle) => {
      // sqlite3_finalize consumes the statement even when it reports an error.
      self.handle = None
      self.has_row = false
      self.column_types = None
      let rescode = @ffi.sqlite3_finalize(handle)
      if rescode != SQLITE_OK {
        raise self.error(rescode)
      }
    }
  }
}