class CustomAggregate : public CustomFunction {
public:

	explicit CustomAggregate(v8::Isolate* _isolate, Database* _db, v8::Local<v8::Value> _start, v8::Local<v8::Function> _step, v8::Local<v8::Value> _inverse, v8::Local<v8::Value> _result, const char* _name, bool _safe_ints)
		: CustomFunction(_isolate, _db, _step, _name, _safe_ints), invoke_result(_result->IsFunction()), invoke_start(_start->IsFunction()), inverse(_isolate, _inverse->IsFunction() ? v8::Local<v8::Function>::Cast(_inverse) : v8::Local<v8::Function>()), result(_isolate, _result->IsFunction() ? v8::Local<v8::Function>::Cast(_result) : v8::Local<v8::Function>()), start(_isolate, _start) {}

	static void xStep(sqlite3_context* invocation, int argc, sqlite3_value** argv) {
		xStepBase(invocation, argc, argv, &CustomAggregate::fn);
	}

	static void xInverse(sqlite3_context* invocation, int argc, sqlite3_value** argv) {
		xStepBase(invocation, argc, argv, &CustomAggregate::inverse);
	}

	static void xValue(sqlite3_context* invocation) {
		xValueBase(invocation, false);
	}

	static void xFinal(sqlite3_context* invocation) {
		xValueBase(invocation, true);
	}

private:
	static inline void xStepBase(sqlite3_context* invocation, int argc, sqlite3_value** argv, const CopyablePersistent<v8::Function> CustomAggregate::*ptrtm) {
		AGGREGATE_START();

		v8::Local<v8::Value> args_fast[5];
		v8::Local<v8::Value>* args = argc <= 4 ? args_fast : ALLOC_ARRAY<v8::Local<v8::Value>>(argc + 1);
		args[0] = v8::Local<v8::Value>::New(isolate, acc->value);
		if (argc != 0) Data::GetArgumentsJS(isolate, args + 1, argv, argc, self->safe_ints);

		v8::MaybeLocal<v8::Value> maybe_return_value = v8::Local<v8::Function>::New(isolate, self->*ptrtm)->Call(OnlyContext, v8::Undefined(isolate), argc + 1, args);
		if (args != args_fast) delete[] args;

		if (maybe_return_value.IsEmpty()) {
			self->PropagateJSError(invocation);
		} else {
			v8::Local<v8::Value> return_value = maybe_return_value.ToLocalChecked();
			if (!return_value->IsUndefined()) acc->value.Reset(isolate, return_value);
		}
	}

	static inline void xValueBase(sqlite3_context* invocation, bool is_final) {
		AGGREGATE_START();

		if (!is_final) {
			acc->is_window = true;
		} else if (acc->is_window) {
			DestroyAccumulator(invocation);
			return;
		}

		v8::Local<v8::Value> result = v8::Local<v8::Value>::New(isolate, acc->value);
		if (self->invoke_result) {
			v8::MaybeLocal<v8::Value> maybe_result = v8::Local<v8::Function>::New(isolate, self->result)->Call(OnlyContext, v8::Undefined(isolate), 1, &result);
			if (maybe_result.IsEmpty()) {
				self->PropagateJSError(invocation);
				return;
			}
			result = maybe_result.ToLocalChecked();
		}

		Data::ResultValueFromJS(isolate, invocation, result, self);
		if (is_final) DestroyAccumulator(invocation);
	}

	struct Accumulator { public:
		CopyablePersistent<v8::Value> value;
		bool initialized;
		bool is_window;
	}

	Accumulator* GetAccumulator(sqlite3_context* invocation) {
		Accumulator* acc = static_cast<Accumulator*>(sqlite3_aggregate_context(invocation, sizeof(Accumulator)));
		if (!acc->initialized) {
			assert(acc->value.IsEmpty());
			acc->initialized = true;
			if (invoke_start) {
				v8::MaybeLocal<v8::Value> maybe_seed = v8::Local<v8::Function>::Cast(v8::Local<v8::Value>::New(isolate, start))->Call(OnlyContext, v8::Undefined(isolate), 0, NULL);
				if (maybe_seed.IsEmpty()) PropagateJSError(invocation);
				else acc->value.Reset(isolate, maybe_seed.ToLocalChecked());
			} else {
				assert(!start.IsEmpty());
				acc->value.Reset(isolate, start);
			}
		}
		return acc;
	}

	static void DestroyAccumulator(sqlite3_context* invocation) {
		Accumulator* acc = static_cast<Accumulator*>(sqlite3_aggregate_context(invocation, sizeof(Accumulator)));
		assert(acc->initialized);
		acc->value.Reset();
	}

	void PropagateJSError(sqlite3_context* invocation) {
		DestroyAccumulator(invocation);
		CustomFunction::PropagateJSError(invocation);
	}

	const bool invoke_result;
	const bool invoke_start;
	const CopyablePersistent<v8::Function> inverse;
	const CopyablePersistent<v8::Function> result;
	const CopyablePersistent<v8::Value> start;
};
