// Copyright 2017 plutoo
#define NX_SERVICE_ASSUME_NON_DOMAIN
#include "service_guard.h"
#include "runtime/hosversion.h"
#include "services/pm.h"

#define PM_GENERATE_SERVICE_INIT(name)                  \
static Service g_pm##name##Srv;                         \
                                                        \
NX_GENERATE_SERVICE_GUARD(pm##name);                    \
                                                        \
Result _pm##name##Initialize(void) {                    \
    return smGetService(&g_pm##name##Srv, "pm:"#name);  \
}                                                       \
                                                        \
void _pm##name##Cleanup(void) {                         \
    serviceClose(&g_pm##name##Srv);                      \
}                                                       \
                                                        \
Service* pm##name##GetServiceSession(void) {            \
    return &g_pm##name##Srv;                            \
}

PM_GENERATE_SERVICE_INIT(dmnt);
PM_GENERATE_SERVICE_INIT(shell);
PM_GENERATE_SERVICE_INIT(info);
PM_GENERATE_SERVICE_INIT(bm);

// pmbm

Result pmbmGetBootMode(PmBootMode *out) {
    _Static_assert(sizeof(*out) == sizeof(u32), "PmBootMode");
    return serviceDispatchOut(&g_pmbmSrv, 0, *out);
}

Result pmbmSetMaintenanceBoot(void) {
    return serviceDispatch(&g_pmbmSrv, 1);
}

// pmdmnt

Result pmdmntGetJitDebugProcessIdList(u32* out_count, u64* out_pids, size_t max_pids) {
    const u64 cmd_id = hosversionAtLeast(5,0,0) ? 0 : 1;
    return serviceDispatchOut(&g_pmdmntSrv, cmd_id, *out_count,
        .buffer_attrs = {
            SfBufferAttr_HipcMapAlias | SfBufferAttr_Out,
        },
        .buffers = {
            { out_pids,  max_pids * sizeof(*out_pids) },
        },
    );
}

Result pmdmntStartProcess(u64 pid) {
    const u64 cmd_id = hosversionAtLeast(5,0,0) ? 1 : 2;
    return serviceDispatchIn(&g_pmdmntSrv, cmd_id, pid);
}

Result pmdmntGetProcessId(u64* pid_out, u64 program_id) {
    const u64 cmd_id = hosversionAtLeast(5,0,0) ? 2 : 3;
    return serviceDispatchInOut(&g_pmdmntSrv, cmd_id, program_id, *pid_out);
}

Result pmdmntHookToCreateProcess(Event* out_event, u64 program_id) {
    const u64 cmd_id = hosversionAtLeast(5,0,0) ? 3 : 4;
    Handle event = INVALID_HANDLE;
    Result rc = serviceDispatchIn(&g_pmdmntSrv, cmd_id, program_id,
        .out_handle_attrs = { SfOutHandleAttr_HipcCopy },
        .out_handles = &event,
    );
    if (R_SUCCEEDED(rc))
        eventLoadRemote(out_event, event, true);
    return rc;
}

Result pmdmntGetApplicationProcessId(u64* pid_out) {
    const u64 cmd_id = hosversionAtLeast(5,0,0) ? 4 : 5;
    return serviceDispatchOut(&g_pmdmntSrv, cmd_id, *pid_out);
}

Result pmdmntHookToCreateApplicationProcess(Event* out_event) {
    const u64 cmd_id = hosversionAtLeast(5,0,0) ? 5 : 6;
    Handle event = INVALID_HANDLE;
    Result rc = serviceDispatch(&g_pmdmntSrv, cmd_id,
        .out_handle_attrs = { SfOutHandleAttr_HipcCopy },
        .out_handles = &event,
    );
    if (R_SUCCEEDED(rc))
        eventLoadRemote(out_event, event, true);
    return rc;
}

Result pmdmntClearHook(u32 which) {
    if (hosversionBefore(6,0,0)) return MAKERESULT(Module_Libnx, LibnxError_IncompatSysVer);
    return serviceDispatchIn(&g_pmdmntSrv, 6, which);
}

// pminfo

Result pminfoGetProgramId(u64* program_id_out, u64 pid) {
    return serviceDispatchInOut(&g_pminfoSrv, 0, pid, *program_id_out);
}

// pmshell

Result pmshellLaunchProgram(u32 launch_flags, const NcmProgramLocation *location, u64 *pid) {
    const struct {
        u32 launch_flags;
        u32 pad;
        NcmProgramLocation location;
    } in = { launch_flags, 0, *location };
    return serviceDispatchInOut(&g_pmshellSrv, 0, in, *pid);
}

Result pmshellTerminateProcess(u64 processID) {
    return serviceDispatchIn(&g_pmshellSrv, 1, processID);
}

Result pmshellTerminateProgram(u64 program_id) {
    return serviceDispatchIn(&g_pmshellSrv, 2, program_id);
}

Result pmshellGetProcessEventHandle(Event* out_event) {
    Handle event = INVALID_HANDLE;
    Result rc = serviceDispatch(&g_pmshellSrv, 3,
        .out_handle_attrs = { SfOutHandleAttr_HipcCopy },
        .out_handles = &event,
    );
    if (R_SUCCEEDED(rc))
        eventLoadRemote(out_event, event, true);
    return rc;
}

Result pmshellGetProcessEventInfo(PmProcessEventInfo* out) {
    _Static_assert(sizeof(out->event) == sizeof(u32), "PmProcessEvent");
    return serviceDispatchOut(&g_pmshellSrv, 4, *out);
}

Result pmshellCleanupProcess(u64 pid) {
    if (hosversionAtLeast(5,0,0)) return MAKERESULT(Module_Libnx, LibnxError_IncompatSysVer);
    return serviceDispatchIn(&g_pmshellSrv, 5, pid);
}

Result pmshellClearJitDebugOccured(u64 pid) {
    if (hosversionAtLeast(5,0,0)) return MAKERESULT(Module_Libnx, LibnxError_IncompatSysVer);
    return serviceDispatchIn(&g_pmshellSrv, 6, pid);
}

Result pmshellNotifyBootFinished(void) {
    const u64 cmd_id = hosversionAtLeast(5,0,0) ? 5 : 7;
    return serviceDispatch(&g_pmshellSrv, cmd_id);
}

Result pmshellGetApplicationProcessIdForShell(u64* pid_out) {
    const u64 cmd_id = hosversionAtLeast(5,0,0) ? 6 : 8;
    return serviceDispatchOut(&g_pmshellSrv, cmd_id, *pid_out);
}

Result pmshellBoostSystemMemoryResourceLimit(u64 boost_size) {
    if (hosversionBefore(4,0,0)) return MAKERESULT(Module_Libnx, LibnxError_IncompatSysVer);
    const u64 cmd_id = hosversionAtLeast(5,0,0) ? 7 : 9;
    return serviceDispatchIn(&g_pmshellSrv, cmd_id, boost_size);
}

Result pmshellEnableApplicationExtraThread(void) {
    if (hosversionBefore(7,0,0)) return MAKERESULT(Module_Libnx, LibnxError_IncompatSysVer);
    return serviceDispatch(&g_pmshellSrv, 8);
}