#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(Promise ret, entity result, entity userdata) onResolve,
        Promise(Promise ret, entity err, entity userdata) onReject,
        Promise ret,
        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,
            (onResolve ? onResolve : _Promise_onResolve_default),
            (onReject ? onReject : _Promise_onReject_default),
            ret,
            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
