#include <string.h>
#include "types.h"
#include "result.h"
#include "display/parcel.h"
#include "display/buffer_producer.h"


// This implements the version of Android IGraphicBufferProducer used by Switch.
// Hence names/params etc here are based on Android IGraphicBufferProducer.cpp.

// Based on an old version of the enum from the above .cpp.
// https://android.googlesource.com/platform/frameworks/native/+/29a3e90879fd96404c971e7187cd0e05927bbce0/libs/gui/IGraphicBufferProducer.cpp#35

enum {
    /* 0x1 */ REQUEST_BUFFER = BINDER_FIRST_CALL_TRANSACTION,
    /* 0x2 */ SET_BUFFER_COUNT,
    /* 0x3 */ DEQUEUE_BUFFER,
    /* 0x4 */ DETACH_BUFFER,
    /* 0x5 */ DETACH_NEXT_BUFFER,
    /* 0x6 */ ATTACH_BUFFER,
    /* 0x7 */ QUEUE_BUFFER,
    /* 0x8 */ CANCEL_BUFFER,
    /* 0x9 */ QUERY,
    /* 0xA */ CONNECT,
    /* 0xB */ DISCONNECT,
    /* 0xC */ SET_SIDEBAND_STREAM,
    /* 0xD */ ALLOCATE_BUFFERS,
    /* 0xE */ SET_PREALLOCATED_BUFFER, // Custom Switch-specific command
};

static const char g_bq_InterfaceDescriptor[] = "android.gui.IGraphicBufferProducer";

Result bqRequestBuffer(Binder *b, s32 bufferIdx, BqGraphicBuffer *buf)
{
    Result rc;
    Parcel parcel, parcel_reply;

    parcelCreate(&parcel);
    parcelCreate(&parcel_reply);

    parcelWriteInterfaceToken(&parcel, g_bq_InterfaceDescriptor);
    parcelWriteInt32(&parcel, bufferIdx);

    rc = parcelTransact(b, REQUEST_BUFFER, &parcel, &parcel_reply);

    if (R_SUCCEEDED(rc)) {
        int nonNull = parcelReadInt32(&parcel_reply);

        if (nonNull != 0) {
            size_t tmp_size=0;
            void* tmp_ptr;

            tmp_ptr = parcelReadFlattenedObject(&parcel_reply, &tmp_size);
            if (!tmp_ptr)
                return MAKERESULT(Module_Libnx, LibnxError_BadInput);

            if (buf)
                return MAKERESULT(Module_Libnx, LibnxError_BadInput); // not implemented
        }

        rc = binderConvertErrorCode(parcelReadInt32(&parcel_reply));
    }

    return rc;
}

Result bqDequeueBuffer(Binder *b, bool async, u32 width, u32 height, s32 format, u32 usage, s32 *buf, NvMultiFence *fence)
{
    Result rc;
    Parcel parcel, parcel_reply;

    parcelCreate(&parcel);
    parcelCreate(&parcel_reply);

    parcelWriteInterfaceToken(&parcel, g_bq_InterfaceDescriptor);

    parcelWriteInt32(&parcel, async);
    parcelWriteUInt32(&parcel, width);
    parcelWriteUInt32(&parcel, height);
    parcelWriteInt32(&parcel, format);
    parcelWriteUInt32(&parcel, usage);

    rc = parcelTransact(b, DEQUEUE_BUFFER, &parcel, &parcel_reply);

    if (R_SUCCEEDED(rc)) {
        *buf = parcelReadInt32(&parcel_reply);

        if(parcelReadInt32(&parcel_reply)) {
            size_t tmp_size=0;
            void* tmp_ptr;

            tmp_ptr = parcelReadFlattenedObject(&parcel_reply, &tmp_size);
            if (tmp_ptr == NULL || tmp_size != sizeof(NvMultiFence))
                return MAKERESULT(Module_Libnx, LibnxError_BadInput);

            if (fence)
                memcpy(fence, tmp_ptr, sizeof(NvMultiFence));
        }

        rc = binderConvertErrorCode(parcelReadInt32(&parcel_reply));
    }

    return rc;
}

Result bqDetachBuffer(Binder *b, s32 slot)
{
    Result rc;
    Parcel parcel, parcel_reply;

    parcelCreate(&parcel);
    parcelCreate(&parcel_reply);

    parcelWriteInterfaceToken(&parcel, g_bq_InterfaceDescriptor);
    parcelWriteInt32(&parcel, slot);

    rc = parcelTransact(b, DETACH_BUFFER, &parcel, &parcel_reply);

    if (R_SUCCEEDED(rc)) {
        rc = binderConvertErrorCode(parcelReadInt32(&parcel_reply));
    }

    return rc;
}

Result bqQueueBuffer(Binder *b, s32 buf, const BqBufferInput *input, BqBufferOutput *output)
{
    Result rc;
    Parcel parcel, parcel_reply;

    parcelCreate(&parcel);
    parcelCreate(&parcel_reply);

    parcelWriteInterfaceToken(&parcel, g_bq_InterfaceDescriptor);
    parcelWriteInt32(&parcel, buf);
    parcelWriteFlattenedObject(&parcel, input, sizeof(*input));

    rc = parcelTransact(b, QUEUE_BUFFER, &parcel, &parcel_reply);

    if (R_SUCCEEDED(rc)) {
        if (parcelReadData(&parcel_reply, output, sizeof(*output)) == NULL)
            return MAKERESULT(Module_Libnx, LibnxError_BadInput);

        rc = binderConvertErrorCode(parcelReadInt32(&parcel_reply));
    }

    return rc;
}

Result bqCancelBuffer(Binder *b, s32 buf, const NvMultiFence *fence)
{
    Result rc;
    Parcel parcel, parcel_reply;

    parcelCreate(&parcel);
    parcelCreate(&parcel_reply);

    parcelWriteInterfaceToken(&parcel, g_bq_InterfaceDescriptor);
    parcelWriteInt32(&parcel, buf);
    parcelWriteFlattenedObject(&parcel, fence, sizeof(*fence));

    rc = parcelTransact(b, CANCEL_BUFFER, &parcel, &parcel_reply);
    // Reply parcel has no content

    return rc;
}

Result bqQuery(Binder *b, s32 what, s32* value)
{
    Result rc;
    Parcel parcel, parcel_reply;

    parcelCreate(&parcel);
    parcelCreate(&parcel_reply);

    parcelWriteInterfaceToken(&parcel, g_bq_InterfaceDescriptor);
    parcelWriteInt32(&parcel, what);

    rc = parcelTransact(b, QUERY, &parcel, &parcel_reply);

    if (R_SUCCEEDED(rc)) {
        *value = parcelReadInt32(&parcel_reply);

        rc = binderConvertErrorCode(parcelReadInt32(&parcel_reply));
    }

    return rc;
}

Result bqConnect(Binder *b, s32 api, bool producerControlledByApp, BqBufferOutput *output)
{
    Result rc;
    Parcel parcel, parcel_reply;

    parcelCreate(&parcel);
    parcelCreate(&parcel_reply);

    parcelWriteInterfaceToken(&parcel, g_bq_InterfaceDescriptor);

    // Hard-code this as if listener==NULL, since that's not known to be used officially.
    parcelWriteInt32(&parcel, 0);
    parcelWriteInt32(&parcel, api);
    parcelWriteInt32(&parcel, producerControlledByApp);

    rc = parcelTransact(b, CONNECT, &parcel, &parcel_reply);

    if (R_SUCCEEDED(rc)) {
        if (parcelReadData(&parcel_reply, output, sizeof(*output)) == NULL)
            return MAKERESULT(Module_Libnx, LibnxError_BadInput);

        rc = binderConvertErrorCode(parcelReadInt32(&parcel_reply));
    }

    return rc;
}

Result bqDisconnect(Binder *b, s32 api)
{
    Result rc;
    Parcel parcel, parcel_reply;

    parcelCreate(&parcel);
    parcelCreate(&parcel_reply);

    parcelWriteInterfaceToken(&parcel, g_bq_InterfaceDescriptor);
    parcelWriteInt32(&parcel, api);

    rc = parcelTransact(b, DISCONNECT, &parcel, &parcel_reply);

    if (R_SUCCEEDED(rc)) {
        rc = binderConvertErrorCode(parcelReadInt32(&parcel_reply));
    }

    return rc;
}

Result bqSetPreallocatedBuffer(Binder *b, s32 buf, const BqGraphicBuffer *input)
{
    Result rc;
    Parcel parcel, parcel_reply;
    bool hasInput = false;

    if (input) {
        hasInput = true;
        if (!input->native_handle || input->native_handle->num_fds || input->native_handle->num_ints > 0x80)
            return MAKERESULT(Module_Libnx, LibnxError_BadInput);
    }

    parcelCreate(&parcel);
    parcelCreate(&parcel_reply);

    parcelWriteInterfaceToken(&parcel, g_bq_InterfaceDescriptor);
    parcelWriteInt32(&parcel, buf);

    parcelWriteInt32(&parcel, hasInput);
    if (hasInput) {
        struct {
            u32 magic;
            u32 width;
            u32 height;
            u32 stride;
            u32 format;
            u32 usage;

            u32 pid;
            u32 refcount;

            u32 numFds;
            u32 numInts;

            u32 ints[input->native_handle->num_ints];
        } buf;

        // Serialize the buffer
        buf.magic = 0x47424652; // GBFR (Graphic Buffer)
        buf.width = input->width;
        buf.height = input->height;
        buf.stride = input->stride;
        buf.format = input->format;
        buf.usage = input->usage;
        buf.pid = 42; // Official sw sets this to the value of getpid(), which is hardcoded to return 42.
        buf.refcount = 0; // Official sw sets this to the output of android_atomic_inc(). We instead don't care and set it to zero since it is ignored during marshalling.
        buf.numFds = 0;
        buf.numInts = input->native_handle->num_ints;
        memcpy(buf.ints, input->native_handle+1, sizeof(buf.ints));

        parcelWriteFlattenedObject(&parcel, &buf, sizeof(buf));
    }

    rc = parcelTransact(b, SET_PREALLOCATED_BUFFER, &parcel, &parcel_reply);
    // Reply parcel has no content

    return rc;
}