class StatementIterator : public node::ObjectWrap {
public:

	INIT(Init) {
		v8::Local<v8::FunctionTemplate> t = NewConstructorTemplate(isolate, data, JS_new, "StatementIterator");
		SetPrototypeMethod(isolate, data, t, "next", JS_next);
		SetPrototypeMethod(isolate, data, t, "return", JS_return);
		SetPrototypeSymbolMethod(isolate, data, t, v8::Symbol::GetIterator(isolate), JS_symbolIterator);
		return t->GetFunction(OnlyContext).ToLocalChecked();
	}

	// 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(!db_state->has_logger) {
		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;
	}

	NODE_METHOD(JS_new) {
		UseAddon;
		if (!addon->privileged_info) return ThrowTypeError("Disabled constructor");
		assert(info.IsConstructCall());

		StatementIterator* iter;
		{
			NODE_ARGUMENTS info = *addon->privileged_info;
			STATEMENT_START_LOGIC(REQUIRE_STATEMENT_RETURNS_DATA, DOES_ADD_ITERATOR);
			iter = new StatementIterator(stmt, bound);
		}
		UseIsolate;
		UseContext;
		iter->Wrap(info.This());
		SetFrozen(isolate, ctx, info.This(), addon->cs.statement, addon->privileged_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, iter->db_state->addon));
	}

	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, iter->db_state->addon));
	}

	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) {
			UseIsolate;
			UseContext;
			info.GetReturnValue().Set(
				NewRecord(isolate, ctx, Data::GetRowJS(isolate, ctx, handle, safe_ints, mode), db_state->addon, false)
			);
		} else {
			if (status == SQLITE_DONE) Return(info);
			else Throw();
		}
	}

	void Return(NODE_ARGUMENTS info) {
		Cleanup();
		STATEMENT_RETURN_LOGIC(DoneRecord(OnlyIsolate, db_state->addon));
	}

	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 inline v8::Local<v8::Object> NewRecord(v8::Isolate* isolate, v8::Local<v8::Context> ctx, v8::Local<v8::Value> value, Addon* addon, bool done) {
		v8::Local<v8::Object> record = v8::Object::New(isolate);
		record->Set(ctx, CS::Get(isolate, addon->cs.value), value).FromJust();
		record->Set(ctx, CS::Get(isolate, addon->cs.done), v8::Boolean::New(isolate, done)).FromJust();
		return record;
	}

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

	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;
};
