class Backup : public node::ObjectWrap {
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> attachedName, v8::Local<v8::String> destFile, v8::Local<v8::Boolean> unlink) {
		v8::Local<v8::Function> c = v8::Local<v8::Function>::New(isolate, constructor);
		v8::Local<v8::Value> args[4] = { database, attachedName, destFile, unlink };
		constructing_privileges = true;
		v8::MaybeLocal<v8::Object> maybe_backup = c->NewInstance(OnlyContext, 4, args);
		constructing_privileges = false;
		return maybe_backup;
	}

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

	// Whenever this is used, db->RemoveBackup must be invoked beforehand.
	void CloseHandles() {
		if (alive) {
			alive = false;
			std::string filename(sqlite3_db_filename(dest_handle, "main"));
			sqlite3_backup_finish(backup_handle);
			int status = sqlite3_close(dest_handle);
			assert(status == SQLITE_OK); ((void)status);
			if (unlink) remove(filename.c_str());
		}
	}

	~Backup() {
		if (alive) db->RemoveBackup(this);
		CloseHandles();
	}

private:

	explicit Backup(Database* _db, sqlite3* _dest_handle, sqlite3_backup* _backup_handle, bool _unlink) : node::ObjectWrap(),
		db(_db),
		dest_handle(_dest_handle),
		backup_handle(_backup_handle),
		id(next_id++),
		alive(true),
		unlink(_unlink) {
		assert(db != NULL);
		assert(dest_handle != NULL);
		assert(backup_handle != NULL);
		db->AddBackup(this);
	}

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

		NODE_SET_PROTOTYPE_METHOD(t, "transfer", JS_transfer);
		NODE_SET_PROTOTYPE_METHOD(t, "close", JS_close);

		UseContext;
		exports->Set(ctx, StringFromUtf8(isolate, "Backup", -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("Disabled constructor");
		assert(info.IsConstructCall());
		REQUIRE_ARGUMENT_OBJECT(first, v8::Local<v8::Object> database);
		REQUIRE_ARGUMENT_STRING(second, v8::Local<v8::String> attachedName);
		REQUIRE_ARGUMENT_STRING(third, v8::Local<v8::String> destFile);
		REQUIRE_ARGUMENT_BOOLEAN(fourth, bool unlink);
		Database* db = Unwrap<Database>(database);
		REQUIRE_DATABASE_OPEN(db->GetState());
		REQUIRE_DATABASE_NOT_BUSY(db->GetState());

		UseIsolate;
		sqlite3* dest_handle;
		v8::String::Utf8Value dest_file(EXTRACT_STRING(isolate, destFile));
		v8::String::Utf8Value attached_name(EXTRACT_STRING(isolate, attachedName));
		int mask = (SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE);

		if (sqlite3_open_v2(*dest_file, &dest_handle, mask, NULL) != SQLITE_OK) {
			Database::ThrowSqliteError(dest_handle);
			int status = sqlite3_close(dest_handle);
			assert(status == SQLITE_OK); ((void)status);
			return;
		}

		sqlite3_extended_result_codes(dest_handle, 1);
		sqlite3_limit(dest_handle, SQLITE_LIMIT_LENGTH, INT_MAX);
		sqlite3_backup* backup_handle = sqlite3_backup_init(dest_handle, "main", db->GetHandle(), *attached_name);
		if (backup_handle == NULL) {
			Database::ThrowSqliteError(dest_handle);
			int status = sqlite3_close(dest_handle);
			assert(status == SQLITE_OK); ((void)status);
			return;
		}

		Backup* backup = new Backup(db, dest_handle, backup_handle, unlink);
		backup->Wrap(info.This());
		SetFrozen(isolate, OnlyContext, info.This(), CS::database, database);

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

	NODE_METHOD(JS_transfer) {
		Backup* backup = Unwrap<Backup>(info.This());
		REQUIRE_ARGUMENT_INT32(first, int pages);
		REQUIRE_DATABASE_OPEN(backup->db->GetState());
		assert(backup->db->GetState()->busy == false);
		assert(backup->alive == true);

		sqlite3_backup* backup_handle = backup->backup_handle;
		int status = sqlite3_backup_step(backup_handle, pages) & 0xff;

		if (status == SQLITE_OK || status == SQLITE_DONE || status == SQLITE_BUSY) {
			int total_pages = sqlite3_backup_pagecount(backup_handle);
			int remaining_pages = sqlite3_backup_remaining(backup_handle);
			UseIsolateAndContext;
			v8::Local<v8::Object> result = v8::Object::New(isolate);
			result->Set(ctx, CS::Get(isolate, CS::totalPages), v8::Int32::New(isolate, total_pages)).FromJust();
			result->Set(ctx, CS::Get(isolate, CS::remainingPages), v8::Int32::New(isolate, remaining_pages)).FromJust();
			info.GetReturnValue().Set(result);
			if (status == SQLITE_DONE) backup->unlink = false;
		} else {
			Database::ThrowSqliteError(sqlite3_errstr(status), status);
		}
	}

	NODE_METHOD(JS_close) {
		Backup* backup = Unwrap<Backup>(info.This());
		assert(backup->db->GetState()->busy == false);
		if (backup->alive) backup->db->RemoveBackup(backup);
		backup->CloseHandles();
		info.GetReturnValue().Set(info.This());
	}

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

	Database* const db;
	sqlite3* const dest_handle;
	sqlite3_backup* const backup_handle;
	const sqlite3_uint64 id;
	bool alive;
	bool unlink;
};
