#include <string.h>
#include "libapplet_internal.h"
#include "arm/counter.h"
#include "runtime/hosversion.h"

static bool g_libappletJumpFlag;

void libappletArgsCreate(LibAppletArgs* a, u32 version) {
    memset(a, 0, sizeof(LibAppletArgs));

    a->CommonArgs_version = 1;
    a->CommonArgs_size = sizeof(LibAppletArgs);

    a->LaVersion = version;
    a->ExpectedThemeColor = appletGetThemeColorType();
}

void libappletArgsSetPlayStartupSound(LibAppletArgs* a, bool flag) {
    a->PlayStartupSound = flag!=0;
}

Result libappletCreateWriteStorage(AppletStorage* s, const void* buffer, size_t size) {
    Result rc=0;

    rc = appletCreateStorage(s, size);
    if (R_FAILED(rc)) return rc;

    rc = appletStorageWrite(s, 0, buffer, size);
    if (R_FAILED(rc)) {
        appletStorageClose(s);
        return rc;
    }

    return rc;
}

Result libappletReadStorage(AppletStorage* s, void* buffer, size_t size, size_t *transfer_size) {
    Result rc=0;
    s64 tmpsize=0;

    rc = appletStorageGetSize(s, &tmpsize);

    if (R_SUCCEEDED(rc)) {
        if (tmpsize < size) size = tmpsize;
        rc = appletStorageRead(s, 0, buffer, size);
    }

    if (R_SUCCEEDED(rc) && transfer_size) *transfer_size = size;

    return rc;
}

static Result _libappletPushInData(AppletHolder *h, const void* buffer, size_t size) {
    Result rc=0;
    AppletStorage storage;

    rc = libappletCreateWriteStorage(&storage, buffer, size);
    if (R_FAILED(rc)) return rc;

    return appletHolderPushInData(h, &storage);
}

Result libappletArgsPush(LibAppletArgs* a, AppletHolder *h) {
    //Official sw stores the header in LibAppletArgs seperately (first 8-bytes), but here we're including it with the LibAppletCommonArguments struct.
    //Official sw uses appletStorageWrite twice, for writing the header then the rest of the struct.

    a->tick = armGetSystemTick();

    return _libappletPushInData(h, a, sizeof(LibAppletArgs));
}

Result libappletArgsPop(LibAppletArgs* a) {
    //Official sw stores the header in LibAppletArgs seperately (first 8-bytes), but here we're including it with the LibAppletCommonArguments struct.
    //Official sw uses appletStorageRead twice, for reading the header then the rest of the struct.

    AppletStorage storage;
    s64 tmpsize=0;
    size_t tmpsize2=0;
    Result rc=0;
    memset(a, 0, sizeof(LibAppletArgs));
    rc = appletPopInData(&storage);

    if (R_SUCCEEDED(rc)) rc = appletStorageGetSize(&storage, &tmpsize);
    if (R_SUCCEEDED(rc) && tmpsize != sizeof(LibAppletArgs)) rc = MAKERESULT(Module_Libnx, LibnxError_BadInput);

    if (R_SUCCEEDED(rc)) rc = libappletReadStorage(&storage, a, sizeof(LibAppletArgs), &tmpsize2);
    if (R_SUCCEEDED(rc) && tmpsize2 != sizeof(LibAppletArgs)) rc = MAKERESULT(Module_Libnx, LibnxError_BadInput);
    if (R_SUCCEEDED(rc)) {
        if (a->CommonArgs_version != 1 || a->CommonArgs_size != sizeof(LibAppletArgs)) rc = MAKERESULT(Module_Libnx, LibnxError_BadInput);
    }
    return rc;
}

static Result _libappletQlaunchRequest(u8* buf, size_t size) {
    Result rc=0;
    AppletStorage storage;

    rc = libappletCreateWriteStorage(&storage, buf, size);
    if (R_FAILED(rc)) return rc;

    return appletPushToGeneralChannel(&storage);
}

Result libappletPushInData(AppletHolder *h, const void* buffer, size_t size) {
    return _libappletPushInData(h, buffer, size);
}

Result libappletPopOutData(AppletHolder *h, void* buffer, size_t size, size_t *transfer_size) {
    Result rc=0;
    AppletStorage storage;

    rc = appletHolderPopOutData(h, &storage);
    if (R_FAILED(rc)) return rc;

    rc = libappletReadStorage(&storage, buffer, size, transfer_size);
    appletStorageClose(&storage);
    return rc;
}

void libappletSetJumpFlag(bool flag) {
    g_libappletJumpFlag = flag;
}

Result libappletStart(AppletHolder *h) {
    Result rc=0;

    if (g_libappletJumpFlag)
        return appletHolderJump(h);

    rc = appletHolderStart(h);

    if (R_SUCCEEDED(rc)) {
        appletHolderJoin(h);

        LibAppletExitReason reason = appletHolderGetExitReason(h);

        if (reason == LibAppletExitReason_Canceled || reason == LibAppletExitReason_Abnormal || reason == LibAppletExitReason_Unexpected) {
            rc = MAKERESULT(Module_Libnx, LibnxError_LibAppletBadExit);
        }
    }

    return rc;
}

Result libappletLaunch(AppletId id, LibAppletArgs *commonargs, const void* arg, size_t arg_size, void* reply, size_t reply_size, size_t *out_reply_size) {
    Result rc=0;
    AppletHolder holder;

    rc = appletCreateLibraryApplet(&holder, id, LibAppletMode_AllForeground);
    if (R_FAILED(rc)) return rc;

    rc = libappletArgsPush(commonargs, &holder);

    if (R_SUCCEEDED(rc) && arg && arg_size) rc = libappletPushInData(&holder, arg, arg_size);

    if (R_SUCCEEDED(rc)) rc = libappletStart(&holder);

    if (R_SUCCEEDED(rc) && reply && reply_size) rc = libappletPopOutData(&holder, reply, reply_size, out_reply_size);

    appletHolderClose(&holder);

    return rc;
}

Result libappletRequestHomeMenu(void) {
    u8 storagedata[0x10] = {0x53, 0x41, 0x4d, 0x53, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00};//RequestHomeMenu
    return _libappletQlaunchRequest(storagedata, sizeof(storagedata));
}

Result libappletRequestJumpToSystemUpdate(void) {
    u8 storagedata[0x10] = {0x53, 0x41, 0x4d, 0x53, 0x01, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00};//RequestJumpToSystemUpdate
    return _libappletQlaunchRequest(storagedata, sizeof(storagedata));
}

Result libappletRequestToLaunchApplication(u64 application_id, AccountUid uid, const void* buffer, size_t size, u32 sender) {
    if (hosversionBefore(11,0,0))
        return MAKERESULT(Module_Libnx, LibnxError_IncompatSysVer);

    Result rc=0;
    u32 tmp=size;
    AppletStorage s={0};
    u8 storagedata[0x10] = {0x53, 0x41, 0x4d, 0x53, 0x01, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00};

    rc = appletCreateStorage(&s, 0x800);
    if (R_FAILED(rc)) return rc;

    rc = appletStorageWrite(&s, 0, &storagedata, sizeof(storagedata));
    if (R_SUCCEEDED(rc)) rc = appletStorageWrite(&s, sizeof(storagedata), &sender, sizeof(sender));
    if (R_SUCCEEDED(rc)) rc = appletStorageWrite(&s, sizeof(storagedata)+sizeof(u32), &application_id, sizeof(application_id));
    if (R_SUCCEEDED(rc)) rc = appletStorageWrite(&s, sizeof(storagedata)+sizeof(u32)+sizeof(u64), &uid, sizeof(AccountUid));
    if (R_SUCCEEDED(rc)) rc = appletStorageWrite(&s, sizeof(storagedata)+sizeof(u32)+sizeof(u64)+sizeof(AccountUid), &tmp, sizeof(tmp));
    if (R_SUCCEEDED(rc)) rc = appletStorageWrite(&s, sizeof(storagedata)+sizeof(u32)+sizeof(u64)+sizeof(AccountUid)+sizeof(tmp), buffer, tmp);
    if (R_FAILED(rc)) {
        appletStorageClose(&s);
        return rc;
    }
    return appletPushToGeneralChannel(&s);
}

Result libappletRequestJumpToStory(AccountUid uid, u64 application_id) {
    if (hosversionBefore(11,0,0))
        return MAKERESULT(Module_Libnx, LibnxError_IncompatSysVer);

    u8 storagedata[0x28] = {0x53, 0x41, 0x4d, 0x53, 0x01, 0x00, 0x00, 0x00, 0x21, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00};
    memcpy(&storagedata[0x10], &uid, sizeof(uid));
    memcpy(&storagedata[0x20], &application_id, sizeof(application_id));
    return _libappletQlaunchRequest(storagedata, sizeof(storagedata));
}