#define NX_SERVICE_ASSUME_NON_DOMAIN
#include "service_guard.h"
#include "sf/tipc.h"
#include "runtime/hosversion.h"
#include "runtime/diag.h"

static union {
    Service cmif;
    TipcService tipc;
} g_smSrv;

#define MAX_OVERRIDES 32

static struct {
    SmServiceName name;
    Handle handle;
} g_smOverrides[MAX_OVERRIDES];

static size_t g_smOverridesNum = 0;

static bool _smShouldUseTipc(void) {
    return hosversionIsAtmosphere() || hosversionAtLeast(12,0,0);
}

void smAddOverrideHandle(SmServiceName name, Handle handle) {
    if (g_smOverridesNum == MAX_OVERRIDES)
        diagAbortWithResult(MAKERESULT(Module_Libnx, LibnxError_TooManyOverrides));

    size_t i = g_smOverridesNum;

    g_smOverrides[i].name   = name;
    g_smOverrides[i].handle = handle;

    g_smOverridesNum++;
}

Handle smGetServiceOverride(SmServiceName name) {
    for (size_t i = 0; i < g_smOverridesNum; i++)
        if (smServiceNamesAreEqual(g_smOverrides[i].name, name))
            return g_smOverrides[i].handle;

    return INVALID_HANDLE;
}

NX_GENERATE_SERVICE_GUARD(sm);

static Result _smCmifCmdInPid(u32 cmd_id) {
    u64 pid_placeholder = 0;
    return serviceDispatchIn(&g_smSrv.cmif, cmd_id, pid_placeholder, .in_send_pid = true);
}

static Result _smTipcCmdInPid(u32 cmd_id) {
    return tipcDispatch(&g_smSrv.tipc, cmd_id, .in_send_pid = true);
}

Result _smInitialize(void) {
    Handle sm_handle;
    Result rc = svcConnectToNamedPort(&sm_handle, "sm:");
    while (R_VALUE(rc) == KERNELRESULT(NotFound)) {
        svcSleepThread(50000000ul);
        rc = svcConnectToNamedPort(&sm_handle, "sm:");
    }

    // Call RegisterClient. This is unconditionally done through cmif,
    // see comment in smGetServiceOriginal for more details.
    if (R_SUCCEEDED(rc)) {
        serviceCreate(&g_smSrv.cmif, sm_handle);
        rc = _smCmifCmdInPid(0); // RegisterClient
    }

    return rc;
}

void _smCleanup(void) {
    serviceClose(&g_smSrv.cmif);
}

Service *smGetServiceSession(void) {
    return &g_smSrv.cmif;
}

TipcService *smGetServiceSessionTipc(void) {
    return &g_smSrv.tipc;
}

Result smGetServiceWrapper(Service* service_out, SmServiceName name) {
    Handle handle = smGetServiceOverride(name);
    bool own_handle = false;
    Result rc = 0;

    if (handle == INVALID_HANDLE) {
        own_handle = true;
        rc = smGetServiceOriginal(&handle, name);
    }

    if (R_SUCCEEDED(rc)) {
        serviceCreate(service_out, handle);
        service_out->own_handle = own_handle;
    }

    return rc;
}

Result smGetServiceOriginal(Handle* handle_out, SmServiceName name) {
    // Even though GetServiceHandle is also available through tipc, we choose to only
    // call this command through cmif, since that is available on all system versions.
    return serviceDispatchIn(&g_smSrv.cmif, 1, name,
        .out_handle_attrs = { SfOutHandleAttr_HipcMove },
        .out_handles = handle_out,
    );
}

Result smRegisterService(Handle* handle_out, SmServiceName name, bool is_light, s32 max_sessions) {
    if (_smShouldUseTipc())
        return smRegisterServiceTipc(handle_out, name, is_light, max_sessions);
    else
        return smRegisterServiceCmif(handle_out, name, is_light, max_sessions);
}

Result smRegisterServiceCmif(Handle* handle_out, SmServiceName name, bool is_light, s32 max_sessions) {
    const struct {
        SmServiceName service_name;
        u8 is_light;
        s32 max_sessions;
    } in = { name, is_light!=0, max_sessions };

    return serviceDispatchIn(&g_smSrv.cmif, 2, in,
        .out_handle_attrs = { SfOutHandleAttr_HipcMove },
        .out_handles = handle_out,
    );
}

Result smRegisterServiceTipc(Handle* handle_out, SmServiceName name, bool is_light, s32 max_sessions) {
    const struct {
        SmServiceName service_name;
        s32 max_sessions;
        u8 is_light;
    } in = { name, max_sessions, is_light!=0 };

    return tipcDispatchIn(&g_smSrv.tipc, 2, in,
        .out_handle_attrs = { SfOutHandleAttr_HipcMove },
        .out_handles = handle_out,
    );
}

Result smUnregisterService(SmServiceName name) {
    if (_smShouldUseTipc())
        return smUnregisterServiceTipc(name);
    else
        return smUnregisterServiceCmif(name);
}

Result smUnregisterServiceCmif(SmServiceName name) {
    return serviceDispatchIn(&g_smSrv.cmif, 3, name);
}

Result smUnregisterServiceTipc(SmServiceName name) {
    return tipcDispatchIn(&g_smSrv.tipc, 3, name);
}

Result smDetachClient(void) {
    if (hosversionIsAtmosphere())
        return smDetachClientTipc();
    else if (hosversionBetween(11, 12))
        return smDetachClientCmif();
    else
        return MAKERESULT(Module_Libnx, LibnxError_IncompatSysVer);
}

Result smDetachClientCmif(void) {
    return _smCmifCmdInPid(4);
}

Result smDetachClientTipc(void) {
    return _smTipcCmdInPid(4);
}