class Statement : public node::ObjectWrap {
friend class StatementIterator;
public:

	// Provides public access to the constructor.
	static v8::MaybeLocal<v8::Object> New(v8::Isolate* isolate, v8::Local<v8::Object> database, v8::Local<v8::String> source) {
		v8::Local<v8::Function> c = v8::Local<v8::Function>::New(isolate, constructor);
		v8::Local<v8::Value> args[2] = { database, source };
		constructing_privileges = true;
		v8::MaybeLocal<v8::Object> maybe_statement = c->NewInstance(OnlyContext, 2, args);
		constructing_privileges = false;
		return maybe_statement;
	}

	// Used by the Database::CompareStatement class.
	static inline bool Compare(Statement const * const a, Statement const * const b) {
		return a->extras->id < b->extras->id;
	}

	// Returns the Statement's bind map (creates it upon first execution).
	BindMap* GetBindMap(v8::Isolate* isolate) {
		if (has_bind_map) return &extras->bind_map;
		BindMap* bind_map = &extras->bind_map;
		int param_count = sqlite3_bind_parameter_count(handle);
		for (int i=1; i<=param_count; ++i) {
			const char* name = sqlite3_bind_parameter_name(handle, i);
			if (name != NULL) bind_map->Add(isolate, name + 1, i);
		}
		has_bind_map = true;
		return bind_map;
	}

	// Whenever this is used, db->RemoveStatement must be invoked beforehand.
	void CloseHandles() {
		if (alive) {
			alive = false;
			sqlite3_finalize(handle);
		}
	}

	~Statement() {
		if (alive) db->RemoveStatement(this);
		CloseHandles();
		delete extras;
	}

private:

	// A class for holding values that are less often used.
	class Extras { friend class Statement;
		explicit Extras(sqlite3_uint64 _id) : bind_map(0), id(_id) {}
		BindMap bind_map;
		const sqlite3_uint64 id;
	};

	explicit Statement(Database* _db, sqlite3_stmt* _handle, bool _returns_data) : node::ObjectWrap(),
		db(_db),
		handle(_handle),
		extras(new Extras(next_id++)),
		alive(true),
		locked(false),
		bound(false),
		has_bind_map(false),
		safe_ints(_db->GetState()->safe_ints),
		mode(Data::FLAT),
		returns_data(_returns_data) {
		assert(db != NULL);
		assert(handle != NULL);
		assert(db->GetState()->open);
		assert(!db->GetState()->busy);
		db->AddStatement(this);
	}

	REGISTER(Init) {
		v8::Local<v8::FunctionTemplate> t = v8::FunctionTemplate::New(isolate, JS_new);
		t->InstanceTemplate()->SetInternalFieldCount(1);
		t->SetClassName(StringFromUtf8(isolate, "Statement", -1));

		NODE_SET_PROTOTYPE_METHOD(t, "run", JS_run);
		NODE_SET_PROTOTYPE_METHOD(t, "get", JS_get);
		NODE_SET_PROTOTYPE_METHOD(t, "all", JS_all);
		NODE_SET_PROTOTYPE_METHOD(t, "iterate", JS_iterate);
		NODE_SET_PROTOTYPE_METHOD(t, "bind", JS_bind);
		NODE_SET_PROTOTYPE_METHOD(t, "pluck", JS_pluck);
		NODE_SET_PROTOTYPE_METHOD(t, "expand", JS_expand);
		NODE_SET_PROTOTYPE_METHOD(t, "raw", JS_raw);
		NODE_SET_PROTOTYPE_METHOD(t, "safeIntegers", JS_safeIntegers);
		NODE_SET_PROTOTYPE_METHOD(t, "columns", JS_columns);

		UseContext;
		exports->Set(ctx, StringFromUtf8(isolate, "Statement", -1), t->GetFunction(ctx).ToLocalChecked()).FromJust();
		constructor.Reset(isolate, t->GetFunction(OnlyContext).ToLocalChecked());
		next_id = 0;
		constructing_privileges = false;
	}

	NODE_METHOD(JS_new) {
		if (!constructing_privileges) {
			return ThrowTypeError("Statements can only be constructed by the db.prepare() method");
		}
		assert(info.IsConstructCall());
		REQUIRE_ARGUMENT_OBJECT(first, v8::Local<v8::Object> database);
		REQUIRE_ARGUMENT_STRING(second, v8::Local<v8::String> source);
		Database* db = Unwrap<Database>(database);
		REQUIRE_DATABASE_OPEN(db->GetState());
		REQUIRE_DATABASE_NOT_BUSY(db->GetState());
		if (db->GetState()->pragma_mode) { REQUIRE_DATABASE_NO_ITERATORS(db->GetState()); }

		UseIsolate;
		v8::String::Value sql(EXTRACT_STRING(isolate, source));
		const uint16_t* tail;
		sqlite3_stmt* handle;

		if (sqlite3_prepare16_v3(db->GetHandle(), *sql, sql.length() * sizeof(uint16_t) + 1, SQLITE_PREPARE_PERSISTENT, &handle, reinterpret_cast<const void**>(&tail)) != SQLITE_OK) {
			return db->ThrowDatabaseError();
		}
		if (handle == NULL) {
			return ThrowRangeError("The supplied SQL string contains no statements");
		}
		for (uint16_t const * const end = *sql + sql.length(); tail < end; ++tail) {
			const uint16_t c = *tail;
			if (c != '\n' && c != '\t' && c != ' ' && c != '\r') {
				sqlite3_finalize(handle);
				return ThrowRangeError("The supplied SQL string contains more than one statement");
			}
		}
		bool returns_data = (sqlite3_stmt_readonly(handle) && sqlite3_column_count(handle) >= 1) || db->GetState()->pragma_mode;

		UseContext;
		Statement* stmt = new Statement(db, handle, returns_data);
		stmt->Wrap(info.This());
		SetFrozen(isolate, ctx, info.This(), CS::reader, v8::Boolean::New(isolate, returns_data));
		SetFrozen(isolate, ctx, info.This(), CS::source, source);
		SetFrozen(isolate, ctx, info.This(), CS::database, database);

		info.GetReturnValue().Set(info.This());
	}

	NODE_METHOD(JS_run) {
		STATEMENT_START(REQUIRE_STATEMENT_DOESNT_RETURN_DATA, DOES_MUTATE);
		sqlite3* db_handle = db->GetHandle();
		int total_changes_before = sqlite3_total_changes(db_handle);

		sqlite3_step(handle);
		if (sqlite3_reset(handle) == SQLITE_OK) {
			int changes = sqlite3_total_changes(db_handle) == total_changes_before ? 0 : sqlite3_changes(db_handle);
			sqlite3_int64 id = sqlite3_last_insert_rowid(db_handle);
			UseContext;
			v8::Local<v8::Object> result = v8::Object::New(isolate);
			result->Set(ctx, CS::Get(isolate, CS::changes), v8::Int32::New(isolate, changes)).FromJust();
			result->Set(ctx, CS::Get(isolate, CS::lastInsertRowid), Integer::New(isolate, id, stmt->safe_ints)).FromJust();
			STATEMENT_RETURN(result);
		}
		STATEMENT_THROW();
	}

	NODE_METHOD(JS_get) {
		STATEMENT_START(REQUIRE_STATEMENT_RETURNS_DATA, DOES_NOT_MUTATE);
		int status = sqlite3_step(handle);
		if (status == SQLITE_ROW) {
			v8::Local<v8::Value> result = Data::GetRowJS(isolate, OnlyContext, handle, stmt->safe_ints, stmt->mode);
			sqlite3_reset(handle);
			STATEMENT_RETURN(result);
		} else if (status == SQLITE_DONE) {
			sqlite3_reset(handle);
			STATEMENT_RETURN(v8::Undefined(isolate));
		}
		sqlite3_reset(handle);
		STATEMENT_THROW();
	}

	NODE_METHOD(JS_all) {
		STATEMENT_START(REQUIRE_STATEMENT_RETURNS_DATA, DOES_NOT_MUTATE);
		UseContext;
		v8::Local<v8::Array> result = v8::Array::New(isolate, 0);
		uint32_t row_count = 0;
		const bool safe_ints = stmt->safe_ints;
		const char mode = stmt->mode;
		bool js_error = false;

		while (sqlite3_step(handle) == SQLITE_ROW) {
			if (row_count == 0xffffffff) { ThrowRangeError("Array overflow (too many rows returned)"); js_error = true; break; }
			result->Set(ctx, row_count++, Data::GetRowJS(isolate, ctx, handle, safe_ints, mode)).FromJust();
		}

		if (sqlite3_reset(handle) == SQLITE_OK && !js_error) {
			STATEMENT_RETURN(result);
		}
		if (js_error) db->GetState()->was_js_error = true;
		STATEMENT_THROW();
	}

	NODE_METHOD(JS_iterate) {
		v8::MaybeLocal<v8::Object> maybe_iter = StatementIterator::New(OnlyIsolate, info);
		if (!maybe_iter.IsEmpty()) info.GetReturnValue().Set(maybe_iter.ToLocalChecked());
	}

	NODE_METHOD(JS_bind) {
		Statement* stmt = Unwrap<Statement>(info.This());
		if (stmt->bound) return ThrowTypeError("The bind() method can only be invoked once per statement object");
		REQUIRE_DATABASE_OPEN(stmt->db->GetState());
		REQUIRE_DATABASE_NOT_BUSY(stmt->db->GetState());
		REQUIRE_STATEMENT_NOT_LOCKED(stmt);
		STATEMENT_BIND(stmt->handle);
		stmt->bound = true;
		info.GetReturnValue().Set(info.This());
	}

	NODE_METHOD(JS_pluck) {
		Statement* stmt = Unwrap<Statement>(info.This());
		if (!stmt->returns_data) return ThrowTypeError("The pluck() method is only for statements that return data");
		REQUIRE_DATABASE_NOT_BUSY(stmt->db->GetState());
		REQUIRE_STATEMENT_NOT_LOCKED(stmt);
		bool use = true;
		if (info.Length() != 0) { REQUIRE_ARGUMENT_BOOLEAN(first, use); }
		stmt->mode = use ? Data::PLUCK : stmt->mode == Data::PLUCK ? Data::FLAT : stmt->mode;
		info.GetReturnValue().Set(info.This());
	}

	NODE_METHOD(JS_expand) {
		Statement* stmt = Unwrap<Statement>(info.This());
		if (!stmt->returns_data) return ThrowTypeError("The expand() method is only for statements that return data");
		REQUIRE_DATABASE_NOT_BUSY(stmt->db->GetState());
		REQUIRE_STATEMENT_NOT_LOCKED(stmt);
		bool use = true;
		if (info.Length() != 0) { REQUIRE_ARGUMENT_BOOLEAN(first, use); }
		stmt->mode = use ? Data::EXPAND : stmt->mode == Data::EXPAND ? Data::FLAT : stmt->mode;
		info.GetReturnValue().Set(info.This());
	}

	NODE_METHOD(JS_raw) {
		Statement* stmt = Unwrap<Statement>(info.This());
		if (!stmt->returns_data) return ThrowTypeError("The raw() method is only for statements that return data");
		REQUIRE_DATABASE_NOT_BUSY(stmt->db->GetState());
		REQUIRE_STATEMENT_NOT_LOCKED(stmt);
		bool use = true;
		if (info.Length() != 0) { REQUIRE_ARGUMENT_BOOLEAN(first, use); }
		stmt->mode = use ? Data::RAW : stmt->mode == Data::RAW ? Data::FLAT : stmt->mode;
		info.GetReturnValue().Set(info.This());
	}

	NODE_METHOD(JS_safeIntegers) {
		Statement* stmt = Unwrap<Statement>(info.This());
		REQUIRE_DATABASE_NOT_BUSY(stmt->db->GetState());
		REQUIRE_STATEMENT_NOT_LOCKED(stmt);
		if (info.Length() == 0) stmt->safe_ints = true;
		else { REQUIRE_ARGUMENT_BOOLEAN(first, stmt->safe_ints); }
		info.GetReturnValue().Set(info.This());
	}

	NODE_METHOD(JS_columns) {
		Statement* stmt = Unwrap<Statement>(info.This());
		if (!stmt->returns_data) return ThrowTypeError("The columns() method is only for statements that return data");
		REQUIRE_DATABASE_OPEN(stmt->db->GetState());
		REQUIRE_DATABASE_NOT_BUSY(stmt->db->GetState());
		UseIsolateAndContext;

		int column_count = sqlite3_column_count(stmt->handle);
		v8::Local<v8::Array> columns = v8::Array::New(isolate);

		v8::Local<v8::String> name = CS::Get(isolate, CS::name);
		v8::Local<v8::String> columnName = CS::Get(isolate, CS::column);
		v8::Local<v8::String> tableName = CS::Get(isolate, CS::table);
		v8::Local<v8::String> databaseName = CS::Get(isolate, CS::database);
		v8::Local<v8::String> typeName = CS::Get(isolate, CS::type);

		for (int i=0; i<column_count; ++i) {
			v8::Local<v8::Object> column = v8::Object::New(isolate);

			column->Set(ctx, name,
				InternalizedFromUtf8OrNull(isolate, sqlite3_column_name(stmt->handle, i), -1)
			).FromJust();
			column->Set(ctx, columnName,
				InternalizedFromUtf8OrNull(isolate, sqlite3_column_origin_name(stmt->handle, i), -1)
			).FromJust();
			column->Set(ctx, tableName,
				InternalizedFromUtf8OrNull(isolate, sqlite3_column_table_name(stmt->handle, i), -1)
			).FromJust();
			column->Set(ctx, databaseName,
				InternalizedFromUtf8OrNull(isolate, sqlite3_column_database_name(stmt->handle, i), -1)
			).FromJust();
			column->Set(ctx, typeName,
				InternalizedFromUtf8OrNull(isolate, sqlite3_column_decltype(stmt->handle, i), -1)
			).FromJust();

			columns->Set(ctx, i, column).FromJust();
		}

		info.GetReturnValue().Set(columns);
	}

	static v8::Persistent<v8::Function> constructor;
	static sqlite3_uint64 next_id;
	static bool constructing_privileges;

	Database* const db;
	sqlite3_stmt* const handle;
	Extras* const extras;
	bool alive;
	bool locked;
	bool bound;
	bool has_bind_map;
	bool safe_ints;
	char mode;
	const bool returns_data;
};
