Statement::Statement(
	Database* db,
	sqlite3_stmt* handle,
	sqlite3_uint64 id,
	bool returns_data
) :
	node::ObjectWrap(),
	db(db),
	handle(handle),
	extras(new Extras(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);
}

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

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

// Returns the Statement's bind map (creates it upon first execution).
BindMap* Statement::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;
}

Statement::Extras::Extras(sqlite3_uint64 id)
	: bind_map(0), id(id) {}

INIT(Statement::Init) {
	v8::Local<v8::FunctionTemplate> t = NewConstructorTemplate(isolate, data, JS_new, "Statement");
	SetPrototypeMethod(isolate, data, t, "run", JS_run);
	SetPrototypeMethod(isolate, data, t, "get", JS_get);
	SetPrototypeMethod(isolate, data, t, "all", JS_all);
	SetPrototypeMethod(isolate, data, t, "iterate", JS_iterate);
	SetPrototypeMethod(isolate, data, t, "bind", JS_bind);
	SetPrototypeMethod(isolate, data, t, "pluck", JS_pluck);
	SetPrototypeMethod(isolate, data, t, "expand", JS_expand);
	SetPrototypeMethod(isolate, data, t, "raw", JS_raw);
	SetPrototypeMethod(isolate, data, t, "safeIntegers", JS_safeIntegers);
	SetPrototypeMethod(isolate, data, t, "columns", JS_columns);
	SetPrototypeGetter(isolate, data, t, "busy", JS_busy);
	return t->GetFunction(OnlyContext).ToLocalChecked();
}

NODE_METHOD(Statement::JS_new) {
	UseAddon;
	if (!addon->privileged_info) {
		return ThrowTypeError("Statements can only be constructed by the db.prepare() method");
	}
	assert(info.IsConstructCall());
	Database* db = Unwrap<Database>(addon->privileged_info->This());
	REQUIRE_DATABASE_OPEN(db->GetState());
	REQUIRE_DATABASE_NOT_BUSY(db->GetState());

	v8::Local<v8::String> source = (*addon->privileged_info)[0].As<v8::String>();
	v8::Local<v8::Object> database = (*addon->privileged_info)[1].As<v8::Object>();
	bool pragmaMode = (*addon->privileged_info)[2].As<v8::Boolean>()->Value();
	int flags = SQLITE_PREPARE_PERSISTENT;

	if (pragmaMode) {
		REQUIRE_DATABASE_NO_ITERATORS_UNLESS_UNSAFE(db->GetState());
		flags = 0;
	}

	UseIsolate;
	v8::String::Utf8Value utf8(isolate, source);
	sqlite3_stmt* handle;
	const char* tail;

	if (sqlite3_prepare_v3(db->GetHandle(), *utf8, utf8.length() + 1, flags, &handle, &tail) != SQLITE_OK) {
		return db->ThrowDatabaseError();
	}
	if (handle == NULL) {
		return ThrowRangeError("The supplied SQL string contains no statements");
	}
	// https://github.com/WiseLibs/better-sqlite3/issues/975#issuecomment-1520934678
	for (char c; (c = *tail); ) {
		if (IS_SKIPPED(c)) {
			++tail;
			continue;
		}
		if (c == '/' && tail[1] == '*') {
			tail += 2;
			for (char c; (c = *tail); ++tail) {
				if (c == '*' && tail[1] == '/') {
					tail += 2;
					break;
				}
			}
		} else if (c == '-' && tail[1] == '-') {
			tail += 2;
			for (char c; (c = *tail); ++tail) {
				if (c == '\n') {
					++tail;
					break;
				}
			}
		} else {
			sqlite3_finalize(handle);
			return ThrowRangeError("The supplied SQL string contains more than one statement");
		}
	}

	UseContext;
	bool returns_data = sqlite3_column_count(handle) >= 1 || pragmaMode;
	Statement* stmt = new Statement(db, handle, addon->NextId(), returns_data);
	stmt->Wrap(info.This());
	SetFrozen(isolate, ctx, info.This(), addon->cs.reader, v8::Boolean::New(isolate, returns_data));
	SetFrozen(isolate, ctx, info.This(), addon->cs.readonly, v8::Boolean::New(isolate, sqlite3_stmt_readonly(handle) != 0));
	SetFrozen(isolate, ctx, info.This(), addon->cs.source, source);
	SetFrozen(isolate, ctx, info.This(), addon->cs.database, database);

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

NODE_METHOD(Statement::JS_run) {
	STATEMENT_START(ALLOW_ANY_STATEMENT, 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);
		Addon* addon = db->GetAddon();
		UseContext;
		v8::Local<v8::Object> result = v8::Object::New(isolate);
		result->Set(ctx, addon->cs.changes.Get(isolate), v8::Int32::New(isolate, changes)).FromJust();
		result->Set(ctx, addon->cs.lastInsertRowid.Get(isolate),
			stmt->safe_ints
				? v8::BigInt::New(isolate, id).As<v8::Value>()
				: v8::Number::New(isolate, (double)id).As<v8::Value>()
		).FromJust();
		STATEMENT_RETURN(result);
	}
	STATEMENT_THROW();
}

NODE_METHOD(Statement::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(Statement::JS_all) {
	STATEMENT_START(REQUIRE_STATEMENT_RETURNS_DATA, DOES_NOT_MUTATE);
	UseContext;
	const bool safe_ints = stmt->safe_ints;
	const char mode = stmt->mode;

#if !defined(NODE_MODULE_VERSION) || NODE_MODULE_VERSION < 127
	bool js_error = false;
	uint32_t row_count = 0;
	v8::Local<v8::Array> result = v8::Array::New(isolate, 0);

	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();
#else
	v8::LocalVector<v8::Value> rows(isolate);
	rows.reserve(8);

	if (mode == Data::FLAT) {
		RowBuilder rowBuilder(isolate, handle, safe_ints);
		while (sqlite3_step(handle) == SQLITE_ROW) {
			rows.emplace_back(rowBuilder.GetRowJS());
		}
	} else {
		while (sqlite3_step(handle) == SQLITE_ROW) {
			rows.emplace_back(Data::GetRowJS(isolate, ctx, handle, safe_ints, mode));
		}
	}

	if (sqlite3_reset(handle) == SQLITE_OK) {
		if (rows.size() > 0xffffffff) {
			ThrowRangeError("Array overflow (too many rows returned)");
			db->GetState()->was_js_error = true;
		} else {
			STATEMENT_RETURN(v8::Array::New(isolate, rows.data(), rows.size()));
		}
	}
	STATEMENT_THROW();
#endif
}

NODE_METHOD(Statement::JS_iterate) {
	UseAddon;
	UseIsolate;
	v8::Local<v8::Function> c = addon->StatementIterator.Get(isolate);
	addon->privileged_info = &info;
	v8::MaybeLocal<v8::Object> maybeIterator = c->NewInstance(OnlyContext, 0, NULL);
	addon->privileged_info = NULL;
	if (!maybeIterator.IsEmpty()) info.GetReturnValue().Set(maybeIterator.ToLocalChecked());
}

NODE_METHOD(Statement::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(Statement::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(Statement::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(Statement::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(Statement::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(Statement::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());
	Addon* addon = stmt->db->GetAddon();
	UseIsolate;

#if !defined(NODE_MODULE_VERSION) || NODE_MODULE_VERSION < 127
	UseContext;
	int column_count = sqlite3_column_count(stmt->handle);
	v8::Local<v8::Array> columns = v8::Array::New(isolate);

	v8::Local<v8::String> name = addon->cs.name.Get(isolate);
	v8::Local<v8::String> columnName = addon->cs.column.Get(isolate);
	v8::Local<v8::String> tableName = addon->cs.table.Get(isolate);
	v8::Local<v8::String> databaseName = addon->cs.database.Get(isolate);
	v8::Local<v8::String> typeName = addon->cs.type.Get(isolate);

	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);
#else
	v8::LocalVector<v8::Name> keys(isolate);
	keys.reserve(5);
	keys.emplace_back(addon->cs.name.Get(isolate).As<v8::Name>());
	keys.emplace_back(addon->cs.column.Get(isolate).As<v8::Name>());
	keys.emplace_back(addon->cs.table.Get(isolate).As<v8::Name>());
	keys.emplace_back(addon->cs.database.Get(isolate).As<v8::Name>());
	keys.emplace_back(addon->cs.type.Get(isolate).As<v8::Name>());

	int column_count = sqlite3_column_count(stmt->handle);
	v8::LocalVector<v8::Value> columns(isolate);
	columns.reserve(column_count);

	for (int i = 0; i < column_count; ++i) {
		v8::LocalVector<v8::Value> values(isolate);
		keys.reserve(5);
		values.emplace_back(
			InternalizedFromUtf8OrNull(isolate, sqlite3_column_name(stmt->handle, i), -1)
		);
		values.emplace_back(
			InternalizedFromUtf8OrNull(isolate, sqlite3_column_origin_name(stmt->handle, i), -1)
		);
		values.emplace_back(
			InternalizedFromUtf8OrNull(isolate, sqlite3_column_table_name(stmt->handle, i), -1)
		);
		values.emplace_back(
			InternalizedFromUtf8OrNull(isolate, sqlite3_column_database_name(stmt->handle, i), -1)
		);
		values.emplace_back(
			InternalizedFromUtf8OrNull(isolate, sqlite3_column_decltype(stmt->handle, i), -1)
		);
		columns.emplace_back(
			v8::Object::New(isolate,
				GET_PROTOTYPE(v8::Object::New(isolate)),
				keys.data(),
				values.data(),
				keys.size()
			)
		);
	}

	info.GetReturnValue().Set(
		v8::Array::New(isolate, columns.data(), columns.size())
	);
#endif
}

NODE_GETTER(Statement::JS_busy) {
	Statement* stmt = Unwrap<Statement>(PROPERTY_HOLDER(info));
	info.GetReturnValue().Set(stmt->alive && stmt->locked);
}
