class StatementIterator : public node::ObjectWrap {
public:

	// Provides public access to the constructor.
	static v8::MaybeLocal<v8::Object> New(v8::Isolate* isolate, NODE_ARGUMENTS info) {
		v8::Local<v8::Function> c = v8::Local<v8::Function>::New(isolate, constructor);
		caller_info = &info;
		v8::MaybeLocal<v8::Object> maybe_iter = c->NewInstance(OnlyContext, 0, NULL);
		caller_info = NULL;
		return maybe_iter;
	}

	// The ~Statement destructor currently covers any state this object creates.
	// Additionally, we actually DON'T want to set stmt->locked or db_state
	// ->iterators in this destructor, to ensure deterministic database access.
	~StatementIterator() {}

private:

	explicit StatementIterator(Statement* _stmt, bool _bound) : node::ObjectWrap(),
		stmt(_stmt),
		handle(_stmt->handle),
		db_state(_stmt->db->GetState()),
		bound(_bound),
		safe_ints(_stmt->safe_ints),
		mode(_stmt->mode),
		alive(true),
		logged(false) {
		assert(stmt != NULL);
		assert(handle != NULL);
		assert(stmt->bound == bound);
		assert(stmt->alive == true);
		assert(stmt->locked == false);
		assert(db_state->iterators < USHRT_MAX);
		stmt->locked = true;
		db_state->iterators += 1;
	}

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

		NODE_SET_PROTOTYPE_METHOD(t, "next", JS_next);
		NODE_SET_PROTOTYPE_METHOD(t, "return", JS_return);
		NODE_SET_PROTOTYPE_SYMBOL_METHOD(t, v8::Symbol::GetIterator(isolate), JS_symbolIterator);

		constructor.Reset(isolate, t->GetFunction(OnlyContext).ToLocalChecked());
		caller_info = NULL;
	}

	NODE_METHOD(JS_new) {
		if (caller_info == NULL) return ThrowTypeError("Disabled constructor");
		assert(info.IsConstructCall());

		StatementIterator* iter;
		{
			NODE_ARGUMENTS info = *caller_info;
			STATEMENT_START_LOGIC(REQUIRE_STATEMENT_RETURNS_DATA, ADDS_ITERATOR);
			iter = new StatementIterator(stmt, bound);
		}
		UseIsolateAndContext;
		iter->Wrap(info.This());
		SetFrozen(isolate, ctx, info.This(), CS::statement, caller_info->This());

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

	NODE_METHOD(JS_next) {
		StatementIterator* iter = Unwrap<StatementIterator>(info.This());
		REQUIRE_DATABASE_NOT_BUSY(iter->db_state);
		if (iter->alive) iter->Next(info);
		else info.GetReturnValue().Set(DoneRecord(OnlyIsolate));
	}

	NODE_METHOD(JS_return) {
		StatementIterator* iter = Unwrap<StatementIterator>(info.This());
		REQUIRE_DATABASE_NOT_BUSY(iter->db_state);
		if (iter->alive) iter->Return(info);
		else info.GetReturnValue().Set(DoneRecord(OnlyIsolate));
	}

	NODE_METHOD(JS_symbolIterator) {
		info.GetReturnValue().Set(info.This());
	}

	void Next(NODE_ARGUMENTS info) {
		assert(alive == true);
		db_state->busy = true;
		if (!logged) {
			logged = true;
			if (stmt->db->Log(OnlyIsolate, handle)) {
				db_state->busy = false;
				Throw();
				return;
			}
		}
		int status = sqlite3_step(handle);
		db_state->busy = false;
		if (status == SQLITE_ROW) {
			UseIsolateAndContext;
			info.GetReturnValue().Set(NewRecord(isolate, ctx,
				Data::GetRowJS(isolate, ctx, handle, safe_ints, mode)
			));
		} else {
			if (status == SQLITE_DONE) Return(info);
			else Throw();
		}
	}

	void Return(NODE_ARGUMENTS info) {
		Cleanup();
		STATEMENT_RETURN_LOGIC(DoneRecord(OnlyIsolate));
	}

	void Throw() {
		Cleanup();
		Database* db = stmt->db;
		STATEMENT_THROW_LOGIC();
	}

	void Cleanup() {
		assert(alive == true);
		alive = false;
		stmt->locked = false;
		db_state->iterators -= 1;
		sqlite3_reset(handle);
	}

	static v8::Local<v8::Object> NewRecord(v8::Isolate* isolate, v8::Local<v8::Context> ctx, v8::Local<v8::Value> value, bool done = false) {
		v8::Local<v8::Object> record = v8::Object::New(isolate);
		record->Set(ctx, CS::Get(isolate, CS::value), value).FromJust();
		record->Set(ctx, CS::Get(isolate, CS::done), done ? v8::True(isolate) : v8::False(isolate)).FromJust();
		return record;
	}

	static v8::Local<v8::Object> DoneRecord(v8::Isolate* isolate) {
		return NewRecord(isolate, OnlyContext, v8::Undefined(isolate), true);
	}

	static v8::Persistent<v8::Function> constructor;
	static const NODE_ARGUMENTS_TYPE* caller_info;

	Statement* const stmt;
	sqlite3_stmt* const handle;
	Database::State* const db_state;
	const bool bound;
	const bool safe_ints;
	const char mode;
	bool alive;
	bool logged;
};
