#include "sf/service.h"
#include "runtime/hosversion.h"
#include "services/async.h"

static Result _asyncCmdNoIO(Service* srv, u32 cmd_id) {
    return serviceDispatch(srv, cmd_id);
}

static Result _asyncCmdNoInOutU64(Service* srv, u64 *out, u32 cmd_id) {
    return serviceDispatchOut(srv, cmd_id, *out);
}

static Result _asyncCmdNoInOutBuf(Service* srv, void* buffer, size_t size, u32 cmd_id) {
    return serviceDispatch(srv, cmd_id,
        .buffer_attrs = { SfBufferAttr_HipcMapAlias | SfBufferAttr_Out },
        .buffers = { { buffer, size } },
    );
}

// IAsyncValue

void asyncValueClose(AsyncValue *a) {
    if (serviceIsActive(&a->s)) {
        asyncValueCancel(a); // Official sw ignores rc from this prior to waiting on the event.
        asyncValueWait(a, UINT64_MAX);
    }

    serviceClose(&a->s);
    eventClose(&a->event);
}

Result asyncValueWait(AsyncValue *a, u64 timeout) {
    if (!serviceIsActive(&a->s))
        return MAKERESULT(Module_Libnx, LibnxError_NotInitialized);

    return eventWait(&a->event, timeout);
}

Result asyncValueGetSize(AsyncValue *a, u64 *size) {
    if (!serviceIsActive(&a->s))
        return MAKERESULT(Module_Libnx, LibnxError_NotInitialized);

    return _asyncCmdNoInOutU64(&a->s, size, 0);
}

Result asyncValueGet(AsyncValue *a, void* buffer, size_t size) {
    if (!serviceIsActive(&a->s))
        return MAKERESULT(Module_Libnx, LibnxError_NotInitialized);

    Result rc = asyncValueWait(a, UINT64_MAX);
    if (R_SUCCEEDED(rc)) rc = _asyncCmdNoInOutBuf(&a->s, buffer, size, 1);
    return rc;
}

Result asyncValueCancel(AsyncValue *a) {
    if (!serviceIsActive(&a->s))
        return MAKERESULT(Module_Libnx, LibnxError_NotInitialized);

    return _asyncCmdNoIO(&a->s, 2);
}

Result asyncValueGetErrorContext(AsyncValue *a, ErrorContext *context) {
    if (!serviceIsActive(&a->s))
        return MAKERESULT(Module_Libnx, LibnxError_NotInitialized);
    if (hosversionBefore(4,0,0))
        return MAKERESULT(Module_Libnx, LibnxError_IncompatSysVer);

    return _asyncCmdNoInOutBuf(&a->s, context, sizeof(*context), 3);
}

// AsyncResult

void asyncResultClose(AsyncResult *a) {
    if (serviceIsActive(&a->s)) {
        asyncResultCancel(a); // Official sw ignores rc from this prior to waiting on the event.
        asyncResultWait(a, UINT64_MAX);
    }

    serviceClose(&a->s);
    eventClose(&a->event);
}

Result asyncResultWait(AsyncResult *a, u64 timeout) {
    if (!serviceIsActive(&a->s))
        return MAKERESULT(Module_Libnx, LibnxError_NotInitialized);

    return eventWait(&a->event, timeout);
}

Result asyncResultGet(AsyncResult *a) {
    if (!serviceIsActive(&a->s))
        return MAKERESULT(Module_Libnx, LibnxError_NotInitialized);

    Result rc = asyncResultWait(a, UINT64_MAX);
    if (R_SUCCEEDED(rc)) rc = _asyncCmdNoIO(&a->s, 0);
    return rc;
}

Result asyncResultCancel(AsyncResult *a) {
    if (!serviceIsActive(&a->s))
        return MAKERESULT(Module_Libnx, LibnxError_NotInitialized);

    return _asyncCmdNoIO(&a->s, 1);
}

Result asyncResultGetErrorContext(AsyncResult *a, ErrorContext *context) {
    if (!serviceIsActive(&a->s))
        return MAKERESULT(Module_Libnx, LibnxError_NotInitialized);
    if (hosversionBefore(4,0,0))
        return MAKERESULT(Module_Libnx, LibnxError_IncompatSysVer);

    return _asyncCmdNoInOutBuf(&a->s, context, sizeof(*context), 2);
}