#include <gint/display.h>
#include <gint/keyboard.h>
#include <gint/usb.h>
#include <gint/usb-ff-bulk.h>
#include <gint/gint.h>

/* Debug mode (uses gint perf counters not committed to repository) */
// #define DEBUG

#ifdef DEBUG
#include <libprof.h>
#include <fxlibc/printf.h>
#endif

//---
// Receiving and displaying video frames
//---

static void process_usb_message(struct usb_fxlink_header const *header)
{
    static int count = 0;
    count++;

    if(header->size > DWIDTH * DHEIGHT * 2) {
        dclear(C_BLACK);
        dprint(1, 1, C_WHITE, "[%d] %.16s %.16s (%d B)", count,
            header->application, header->type, header->size);
        dupdate();
        return;
    }

#ifdef DEBUG
    extern prof_t prof_round, prof_core;
    prof_round = prof_make();
    prof_core = prof_make();

    prof_t prof_read = prof_make();
    prof_enter(prof_read);
#endif

    /* Read the message payload directly into VRAM. We set a timeout so the
       calculator doesn't freeze if the communication stops midway because the
       VNC client on the PC was interrupted. */
    timeout_t tm = timeout_make_ms(250);
    usb_read_sync_timeout(usb_ff_bulk_input(), gint_vram, header->size, false,
        &tm);

#ifdef DEBUG
    prof_leave(prof_read);
    dprint_opt(1, 1, C_WHITE, C_BLACK, DTEXT_LEFT, DTEXT_TOP,
        "%d ms (read %.1D ms, wait %.1D ms)\n",
        prof_time(prof_read) / 1000,
        prof_time(prof_round) / 100,
        prof_time(prof_core) / 100);
#endif

    dupdate();
}

//---
// Sending keyboard events
//---

#define MAX_EVENTS_PER_MESSAGE 16
struct key_event { uint8_t key; bool down; };

/* Keyboard data to be sent to the PC whenever a key is pressed */
static struct key_event keyinfo[MAX_EVENTS_PER_MESSAGE];
/* Whether there is such a transfer in progress */
static bool keyinfo_frame = false;

static void send_keyboard_events_callback(void)
{
    keyinfo_frame = false;
}

static void send_keyboard_events(void)
{
    int i = 0;

    key_event_t ev;
    while((ev = pollevent()).type != KEYEV_NONE) {
        /* Filter out repeats and any events past the maximum number */
        if(ev.type != KEYEV_DOWN && ev.type != KEYEV_UP)
            continue;
        if(i >= MAX_EVENTS_PER_MESSAGE)
            continue;

        keyinfo[i].key = ev.key;
        keyinfo[i].down = (ev.type == KEYEV_DOWN);
        i++;
    }

    /* Send keyboard updates via fxlink */
    if(i > 0 && usb_is_open() && !keyinfo_frame) {
        usb_fxlink_header_t header;
        usb_fxlink_fill_header(&header, "cgvm", "pressed-keys", 2*i);

        /* We don't need to use async writes because all of this clearly fits
           in a single buffer (2 kB) and only the commit communicates */
        int pipe = usb_ff_bulk_output();
        usb_write_sync(pipe, &header, sizeof header, false);
        usb_write_sync(pipe, keyinfo, i * sizeof *keyinfo, false);
        usb_commit_async(pipe, GINT_CALL(send_keyboard_events_callback));

        /* Remember that a transfer is in progress */
        keyinfo_frame = true;
    }
}

//--
// Main loop
//---

int main(void)
{
#ifdef DEBUG
    prof_init();
    __printf_enable_fixed();
#endif

    dclear(C_WHITE);
    dprint(1, 1, C_BLACK, "Opening USB connection...");
    dupdate();

    usb_interface_t const *intf[] = { &usb_ff_bulk, NULL };
    usb_open(intf, GINT_CALL_NULL);
    usb_open_wait();

    dclear(C_WHITE);
    dprint(1, 1, C_BLACK, "USB connected!");
    dupdate();

    struct usb_fxlink_header header;
    while(1) {
        send_keyboard_events();
        if(keydown(KEY_MENU))
            gint_osmenu();
        if(keydown(KEY_EXIT))
            break;

        while(usb_fxlink_handle_messages(&header)) {
            process_usb_message(&header);
        }
    }

#ifdef DEBUG
    prof_quit();
#endif
    return 0;
}
