#include "promise.qh"

.int _ref_count;
.void(entity this) _ref_finalize;

void ref_init(entity this, int init, void(entity this) finalize)
{
	this._ref_count = init;
	this._ref_finalize = finalize;
}

// todo: rename to `ref`
entity REF(entity this)
{
	++this._ref_count;
	return this;
}

entity unref(Promise this)
{
	--this._ref_count;
	if (!this._ref_count)
	{
		LOG_DEBUGF("Finalize entity %i (from %s)", this, this.sourceLoc);
		this._ref_finalize(this);
		return NULL;
	}
	return this;
}

enum
{
	PROMISE_PENDING,
	PROMISE_RESOLVED,
	PROMISE_REJECTED,
};

classfield(Promise) .int _promise_state;
classfield(Promise) .entity _promise_result;
classfield(Promise) .IntrusiveList _promise_handlers;

entityclass(PromiseHandler);
classfield(PromiseHandler) .Promise _promise_handler_ret;
classfield(PromiseHandler) .entity _promise_handler_data;
classfield(PromiseHandler) .Promise(Promise ret, entity result, entity userdata) _promise_handler_resolve;
classfield(PromiseHandler) .Promise(Promise ret, entity err, entity userdata) _promise_handler_reject;

void _Promise_finalize(Promise this)
{
	delete(this);
}

Promise Promise_new_(Promise this)
{
	ref_init(this, 2, _Promise_finalize);
	this._promise_result = this; // promises default to being their own result to save on entities
	return this;
}

void _Promise_handle(Promise this, PromiseHandler h);

void Promise_resolve(Promise this)
{
	if (!this)
	{
		LOG_SEVERE("Attempted to resolve a null promise");
		return;
	}
	if (this._promise_state != PROMISE_PENDING)
	{
		LOG_SEVEREF("Resolved non-pending promise %i", this);
		return;
	}
	this._promise_state = PROMISE_RESOLVED;
	if (this._promise_handlers)
	{
		IL_EACH(this._promise_handlers, true, _Promise_handle(this, it));
		IL_DELETE(this._promise_handlers);
	}
	unref(this);
	return;
}

void Promise_reject(Promise this)
{
	if (!this)
	{
		LOG_SEVERE("Attempted to reject a null promise");
		return;
	}
	if (this._promise_state != PROMISE_PENDING)
	{
		LOG_SEVEREF("Rejected non-pending promise %i", this);
		return;
	}
	this._promise_state = PROMISE_REJECTED;
	if (this._promise_handlers)
	{
		IL_EACH(this._promise_handlers, true, _Promise_handle(this, it));
		IL_DELETE(this._promise_handlers);
	}
	unref(this);
	return;
}

Promise _Promise_then(Promise this, Promise ret,
	Promise(Promise ret, entity result, entity userdata) onResolve,
	Promise(Promise ret, entity result, entity userdata) onReject,
	entity userdata
);

void _Promise_handle(Promise this, PromiseHandler h)
{
	switch (this._promise_state)
	{
		case PROMISE_PENDING:
			if (!this._promise_handlers)
				this._promise_handlers = IL_NEW();
			IL_PUSH(this._promise_handlers, h);
			break;
		case PROMISE_RESOLVED:
		{
			Promise ret = h._promise_handler_ret;
			Promise p = h._promise_handler_resolve(ret, this._promise_result, h._promise_handler_data);
			if (p != ret)
				_Promise_then(p, ret, func_null, func_null, NULL); // bind p -> ret
			delete(h);
			break;
		}
		case PROMISE_REJECTED:
		{
			Promise ret = h._promise_handler_ret;
			Promise p = h._promise_handler_reject(ret, this._promise_result, h._promise_handler_data);
			if (p != ret)
				_Promise_then(p, ret, func_null, func_null, NULL); // bind p -> ret
			delete(h);
			break;
		}
	}
}

void _Promise_done(Promise this, Promise ret,
	Promise(Promise ret, entity result, entity userdata) onResolve,
	Promise(Promise ret, entity err, entity userdata) onReject,
	entity userdata)
{
	PromiseHandler h = new_pure(PromiseHandler);
	h._promise_handler_ret = ret;
	h._promise_handler_data = userdata;
	h._promise_handler_resolve = onResolve;
	h._promise_handler_reject = onReject;
	_Promise_handle(this, h);
}

Promise _Promise_onResolve_default(Promise ret, entity result, entity userdata)
{
	ret._promise_result = result;
	Promise_resolve(ret);
	return ret;
}

Promise _Promise_onReject_default(Promise ret, entity err, entity userdata)
{
	ret._promise_result = err;
	Promise_reject(ret);
	return ret;
}

Promise _Promise_then(Promise this, Promise ret,
	Promise(Promise ret, entity result, entity userdata) onResolve,
	Promise(Promise ret, entity result, entity userdata) onReject,
	entity userdata)
{
	_Promise_done(this, ret,
		(onResolve ? onResolve : _Promise_onResolve_default),
		(onReject ? onReject : _Promise_onReject_default),
		userdata
	);
	return ret;
}

Promise Promise_then_(Promise this, Promise ret,
	Promise(Promise ret, entity result, entity userdata) onResolve,
	entity userdata)
{
	unref(ret); // ret is a temporary
	return _Promise_then(this, ret, onResolve, func_null, userdata);
}

Promise Promise_catch_(Promise this, Promise ret,
	Promise(Promise ret, entity result, entity userdata) onReject,
	entity userdata)
{
	unref(ret); // ret is a temporary
	return _Promise_then(this, ret, func_null, onReject, userdata);
}

// utils

#ifndef MENUQC

Promise Promise_sleep(float n)
{
	Promise p = unref(Promise_new());
	setthink(p, Promise_resolve);
	p.nextthink = time + n;
	return p;
}

#endif
