#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include <string.h>
#include <math.h>

#include <sys/time.h>

#include <hexagram/can.h>
#include <hexagram/window.h>
#include <hexagram/cluster.h>

static void handle_xevents(hexagram_window *window,
                           hexagram_cluster *cluster,
                           Display *display,
                           cairo_t *fg) {
    while (XPending(display)) {
        XEvent e;

        XNextEvent(display, &e);

        switch (e.type) {
            case MapNotify:
            case Expose:
                hexagram_window_refresh_bg(window);
                hexagram_cluster_draw_fg(cluster, fg);
                hexagram_window_swap_buffer(window);

                break;

            case ButtonPress:
            case KeyPress:
                break;

            default:
                break;
        }
    }
}

static void cluster_update(hexagram_cluster *cluster,
                           cairo_t *cr,
                           hexagram_window *window,
                           struct can_frame *frame,
                           struct timeval *last) {
    struct timeval now;

    if (!hexagram_cluster_update(cluster, frame)) {
        return;
    }

    gettimeofday(&now, NULL);

    if (now.tv_sec - last->tv_sec || now.tv_usec - last->tv_usec > 16666) {
        hexagram_window_refresh_bg(window);
        hexagram_cluster_draw_fg(cluster, cr);
        hexagram_window_swap_buffer(window);

        memcpy(last, &now, sizeof(now));
    }
}

static int usage(int argc, char **argv, const char *message, ...) {
    if (message) {
        va_list args;

        va_start(args, message);
        vfprintf(stderr, message, args);
         fprintf(stderr, "\n");
        va_end(args);
    }

    fprintf(stderr, "usage: %s canif\n", argv[0]);

    return 1;
}

int main(int argc, char **argv) {
    Display *display;
    fd_set rfds, rready;

    hexagram_can_if *can_if;
    hexagram_window *window;
    hexagram_cluster *cluster;

    int fd, fd2,
        width  = 1024,
        height =  480;

    cairo_t *fg, *bg;

    struct timeval last = {
        .tv_sec  = 0,
        .tv_usec = 0
    };

    if (argc != 2) {
        return usage(argc, argv, "No CAN interface specified");
    }

    if ((can_if = hexagram_can_if_open(argv[1])) == NULL) {
        perror("hexagram_can_if_open()");

        return 1;
    }

    if ((window = hexagram_window_new_x11(NULL, width, height)) == NULL) {
        perror("hexagram_window_new_x11()");

        return 1;
    }

    if ((cluster = hexagram_cluster_new(width, height)) == NULL) {
        perror("hexagram_cluster_new()");

        return 1;
    }

    if (hexagram_cluster_filter_can_if(can_if) < 0) {
        perror("hexagram_cluster_filter_can_if()");

        return 1;
    }

    display = hexagram_window_display(window);

    fd  = hexagram_can_if_fd(can_if);
    fd2 = hexagram_window_display_fd(window);

    /*
     * Set up the rendering surfaces
     */
    fg = hexagram_window_get_fg_context(window);
    bg = hexagram_window_get_bg_context(window);

    /*
     * Draw the background layer
     */
    hexagram_cluster_draw_bg(cluster, bg);

    /*
     * Present the background layer
     */
    hexagram_window_show(window);

    /*
     * Set up file descriptors to monitor
     */
    FD_ZERO(&rfds);

    FD_SET(fd,  &rfds);
    FD_SET(fd2, &rfds);

    while (1) {
        int nfds = fd2 + 1;

        handle_xevents(window, cluster, display, fg);

        memcpy(&rready, &rfds, sizeof(rfds));

        if (select(nfds, &rready, NULL, NULL, NULL) < 0) {
            break;
        }

        if (FD_ISSET(fd, &rready)) {
            struct can_frame frame;

            hexagram_can_if_read(can_if, &frame);

            cluster_update(cluster, fg, window, &frame, &last);
        }
    }

    hexagram_window_destroy(window);

    return 0;
}