 
 import Foundation
 
-/// An `sqlite3_stmt *` object
+/// An `sqlite3_stmt *` object.
 ///
 /// - seealso: [SQLite Prepared Statement Object](https://sqlite.org/c3ref/stmt.html)
 public typealias SQLitePreparedStatement = OpaquePointer
 
-/// A class representing an SQL statement with support for binding SQL parameters and retrieving results.
+/// A compiled SQL statement with support for SQL parameter binding and result row processing.
+///
+/// **Creation**
+///
+/// A statement is not created directly but is obtained from a `Database`.
+///
+/// ```swift
+/// let statement = try db.prepare(sql: "select count(*) from t1;")
+/// ```
+///
+/// **Parameter Binding**
+///
+/// A statement supports binding values to SQL parameters by index or by name.
+///
+/// - note: Parameter indexes are 1-based.  The leftmost parameter in a statement has index 1.
+///
+/// ```swift
+/// let statement = try db.prepare(sql: "insert into t1(a, b, c, d, e, f) values (?, ?, ?, :d, :e, :f);")
+/// try statement.bind(value: 30, toParameter: 3)
+/// try statement.bind(value: 40, toParameter: ":d")
+/// try statement.bind(parameters: 10, 20)
+/// try statement.bind(parameters: [":f": 60, ":e": 50])
+/// ```
+///
+/// **Result Rows**
+///
+/// When executed a statement provides zero or more result rows.
+///
+/// ```swift
+/// try statement.execute() { row in
+///     // Do something with `row`
+/// }
+/// ```
+///
+/// ```swift
+/// for row in statement {
+///     // Do something with `row`
+/// }
+/// ```
+///
+/// It is generally preferred to use the block-based method because any errors may be explicitly handled instead of
+/// silently discarded.
 final public class Statement {
-	/// The owning `Database`
+	/// The owning database
 	public let database: Database
 
 	/// The underlying `sqlite3_stmt *` object
 	var stmt: SQLitePreparedStatement
 
-	/// Compile an SQL statement
+	/// Creates a compiled SQL statement.
 	///
 	/// - parameter database: The owning database
 	/// - parameter sql: The SQL statement to compile
-	/// - throws: `DatabaseError`
+	///
+	/// - throws: An error if `sql` could not be compiled
 	init(database: Database, sql: String) throws {
 		self.database = database
 
 		var stmt: SQLitePreparedStatement? = nil
 		guard sqlite3_prepare_v2(database.db, sql, -1, &stmt, nil) == SQLITE_OK else {
-			#if DEBUG
-				print("Error preparing SQL \"\(sql)\"")
-				print("Error message: \(String(cString: sqlite3_errmsg(database.db)))")
-			#endif
-			throw DatabaseError.sqliteError(String(cString: sqlite3_errmsg(database.db)))
+			throw DatabaseError.sqliteError("Error preparing SQL \"\(sql)\": \(String(cString: sqlite3_errmsg(database.db)))")
 		}
 
 		self.stmt = stmt!

	/// The mapping of column names to indexes
	var columnNamesAndIndexes: [String: Int] {
		let columnCount = sqlite3_column_count(stmt)
		var map = [String: Int](minimumCapacity: Int(sqlite3_column_count(stmt)))
		for i in 0..<columnCount {
			let name = String(cString: sqlite3_column_name(stmt, i))
			map[name] = Int(i)

		return map
	}

	/// Returns the name of the column at `index`.
	///
	/// - note: Column indexes are 0-based.  The leftmost column in a result row has index 0.
	/// - requires: `index >= 0`
	/// - requires: `index < self.columnCount`
	///
	/// - parameter index: The index of the desired column
	///
	/// - returns: The name of the column for the specified index
	/// - throws: An error if `index` is out of bounds
	public func name(ofColumn index: Int) throws -> String {
		guard index >= 0, index < self.columnCount else {
			throw DatabaseError.sqliteError("Column index \(index) out of bounds")
		}
		return String(cString: sqlite3_column_name(stmt, Int32(index)))
	}

	/// Executes the statement and applies `block` to each result row.
	///
	/// - parameter block: A closure applied to each result row
	/// - parameter row: A result row of returned data
	///
	/// - throws: `DatabaseError`
	public func execute(_ block: ((_ row: Row) throws -> ())? = nil) throws {
		var result = sqlite3_step(stmt)
		while result == SQLITE_ROW {
			try block?(Row(statement: self))
			result = sqlite3_step(stmt)
		}

		if result != SQLITE_DONE {
			throw DatabaseError.sqliteError("Error executing statement: \(String(cString: sqlite3_errmsg(sqlite3_db_handle(stmt))))")
		}
	}

	/// Resets the statement to its initial state, ready to be re-executed.
	///
	/// - note: This function does not change the value of  any bound SQL parameters.
	///
	/// - throws: An error if the statement could not be reset
	public func reset() throws {
		if sqlite3_reset(stmt) != SQLITE_OK {
			throw DatabaseError.sqliteError("Error resetting statement: \(String(cString: sqlite3_errmsg(sqlite3_db_handle(stmt))))")
		}
	}

	/// Clears all statement bindings by setting SQL parameters to null.
	///
	/// - throws: An error if the bindings could not be cleared
	public func clearBindings() throws {
		if sqlite3_clear_bindings(stmt) != SQLITE_OK {
			throw DatabaseError.sqliteError("Error clearing bindings: \(String(cString: sqlite3_errmsg(sqlite3_db_handle(stmt))))")
		}
	}

	/// Performs a low-level SQLite statement operation.
	///
	/// **Use of this function should be avoided whenever possible**
	///
	/// - parameter block: A closure performing the operation
	/// - parameter stmt: The raw `sqlite3_stmt *` statement object
	///
	/// - throws: Any error thrown in `block`
	///
	/// - returns: The value returned by `block`
	public func withUnsafeRawSQLiteStatement<T>(block: (_ stmt: SQLitePreparedStatement) throws -> (T)) rethrows -> T {
		return try block(stmt)
	}
}

extension Statement: Sequence {
	/// Returns an iterator for accessing the result rows.
	///
	/// Because the iterator discards errors, the preferred way of accessing result rows
	/// is via the block-based `execute(_:)` function
	///
	/// - returns: An iterator over the result rows
	public func makeIterator() -> AnyIterator<Row> {
		return AnyIterator {
			let stmt = self.stmt
			switch sqlite3_step(stmt) {
			case SQLITE_ROW:
				return Row(statement: self)
			case SQLITE_DONE:
				return nil
			default:
				#if DEBUG
					print("Error executing statement: \(String(cString: sqlite3_errmsg(sqlite3_db_handle(stmt))))")
				#endif
				return nil
			}
		}
	}
