#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>

#include <libusb-1.0/libusb.h>

#include "mprodrv.h"

#define MPRO_MEMORY_NAME     "Global\\MPRO_FRAME_SHARE"
#define MPRO_EVENT_NAME      "Global\\MPRO_FRAME_EVENT"
#define MPRO_MAX_BUFFER      0x800000

void mpro_close_frame(struct mpro_frame *mf)
{
    if (mf == NULL)
        return;
    if (mf->share)
        CloseHandle(mf->share);
    if (mf->event)
        CloseHandle(mf->event);
    free(mf);
}

struct mpro_frame *mpro_open_frame()
{
    struct mpro_frame *mf = (struct mpro_frame *)malloc(sizeof(struct mpro_frame));
    if (mf == NULL)
        return NULL;
    memset(mf, 0, sizeof(struct mpro_frame));

    SECURITY_ATTRIBUTES sa;
    SECURITY_DESCRIPTOR sd;
    InitializeSecurityDescriptor(&sd, SECURITY_DESCRIPTOR_REVISION);
    SetSecurityDescriptorDacl(&sd, TRUE, NULL, FALSE);
    sa.nLength = sizeof(SECURITY_ATTRIBUTES);
    sa.lpSecurityDescriptor = &sd;
    sa.bInheritHandle = FALSE;

    // create or open named shared memory.
    mf->share = OpenFileMappingA(FILE_MAP_ALL_ACCESS, FALSE, MPRO_MEMORY_NAME);
    if (mf->share == NULL) {
        mf->share = CreateFileMappingA(INVALID_HANDLE_VALUE, &sa,
                                       PAGE_READWRITE, 0, MPRO_MAX_BUFFER, MPRO_MEMORY_NAME);
    }
    if (mf->share == NULL)
        goto mpro_open_frame_fail;

    // create or open event for new frame.
    mf->event = CreateEventA(&sa, FALSE, FALSE, MPRO_EVENT_NAME);
    if (mf->event == NULL)
        goto mpro_open_frame_fail;

    // map shared memory to local pointer.
    mf->frame = (struct mpro_frame_data *)MapViewOfFile(mf->share,
                                                         FILE_MAP_WRITE|FILE_MAP_READ, 0, 0, MPRO_MAX_BUFFER);
    if (mf->frame == NULL)
        goto mpro_open_frame_fail;

    return mf;

mpro_open_frame_fail:
    mpro_close_frame(mf);
    return NULL;
}

uint8_t *mpro_get_frame(struct mpro_frame *mf)
{
    if (mf == NULL)
        return NULL;

    if (WaitForSingleObject(mf->event, INFINITE))
        return NULL;

    return mf->frame->data;
}

void mpro_set_frame(struct mpro_frame *mf, uint8_t *d)
{
    if (mf == NULL)
        return;

    memcpy(mf->frame->data, d, mf->frame->stride * mf->frame->height);
    SetEvent(mf->event);
}
