#include <gtk/gtk.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <unistd.h>
#include <fcntl.h>
#include <time.h>
#include <sys/wait.h>
#include <errno.h>
#include <sys/ioctl.h>
#include <linux/videodev.h>
#include <math.h>


static gboolean delete_event( GtkWidget *widget,
                              GdkEvent  *event,
                              gpointer   data )
{
    /* If you return FALSE in the "delete_event" signal handler,
     * GTK will emit the "destroy" signal. Returning TRUE means
     * you don't want the window to be destroyed.
     * This is useful for popping up 'are you sure you want to quit?'
     * type dialogs. */

    //g_print ("delete event occurred\n");

    /* Change TRUE to FALSE and the main window will be destroyed with
     * a "delete_event". */

    return FALSE;
}

/* Another callback */
static void destroy( GtkWidget *widget,
                     gpointer   data )
{
    gtk_main_quit ();
}

static GdkPixmap *pixmap;
static GtkImage *image;
static GdkGC *gc;

int fd;
int width, height, npixels;
int framesz;
int palette;
unsigned char *buf, *y, *Cb, *Cr, *c, *c2, *rgb;

void normalize(unsigned char *bgr)
{
    int totalr = 0, totalg = 0, totalb = 0;
    int min = 256, max = 0;
    for (int d = 0; d < npixels; d++)
    {
        totalr += bgr[d*3+2];
        totalg += bgr[d*3+1];
        totalb += bgr[d*3+0];

        if (bgr[d*3] > max)
            max = bgr[d*3];
        if (bgr[d*3+1] > max)
            max = bgr[d*3+1];
        if (bgr[d*3+2] > max)
            max = bgr[d*3+2];
        if (bgr[d*3] < min)
            min = bgr[d*3];
        if (bgr[d*3+1] < min)
            min = bgr[d*3+1];
        if (bgr[d*3+2] < min)
            min = bgr[d*3+2];
    }

    if ((max - min) == 0)
        return;

    for (int d = 0; d < npixels; d++)
    {
        int b = (bgr[d*3] - min) * 256 / (max - min);
        int g = (bgr[d*3+1] - min) * 256 / (max - min);
        int r = (bgr[d*3+2] - min) * 256 / (max - min);

        bgr[d*3] = b;
        bgr[d*3+1] = g;
        bgr[d*3+2] = r;
    }
}

#undef CLAMP
#define CLAMP(n) if (n < 0) n = 0; if (n > 255) n = 255

void RGBtoYUV(unsigned char *bgr)
{
    for (int d = 0; d < npixels; d++)
    {
        int R = bgr[d*3+2];
        int G = bgr[d*3+1];
        int B = bgr[d*3+0];

        int Y = (unsigned char)((0.257 * R) + (0.504 * G) + (0.098 * B) + 16);
        CLAMP(Y);
        int V = (unsigned char)((0.439 * R) - (0.368 * G) - (0.071 * B) + 128);
        CLAMP(V);
        int U = (unsigned char)(-(0.148 * R) - (0.291 * G) + (0.439 * B) + 128);
        CLAMP(U);

        y[d] = Y;
        Cr[d] = V;
        Cb[d] = U;
    }
}

void YtoRGB(unsigned char *bgr)
{
    int min = 1000, max = 0;
    for (int d = 0; d < npixels; d++)
    {
        if (y[d] > max)
            max = y[d];
        if (y[d] < min)
            min = y[d];
    }

    for (int d = 0; d < npixels; d++)
    {
        unsigned char v = (y[d] - min) * 256 / (max - min);
        bgr[d*3] = v;
        bgr[d*3+1] = v;
        bgr[d*3+2] = v;
    }
}

void YUVtoRGB(unsigned char *rgb)
{
    for (int d = 0; d < npixels; d++)
    {
        int Y = y[d];
        int U = Cb[d];
        int V = Cr[d];

        int B = (int)(1.164 * (Y - 16) + 2.018 * (U - 128));
        CLAMP(B);
        int G = (int)(1.164 * (Y - 16) - 0.813 * (V - 128) - 0.391 * (U - 128));
        CLAMP(G);
        int R = (int)(1.164 * (Y - 16) + 1.596 * (V - 128));
        CLAMP(R);

        rgb[d*3+2] = R;
        rgb[d*3+1] = G;
        rgb[d*3] = B;
    }
}

void BGRmaskedwithYtoRGB(unsigned char *bgr)
{
    memset(c2, 0, npixels*3);

    for (int d = 0; d < npixels; d++)
    {
        c2[d*3+2] = bgr[d*3];
        c2[d*3+1] = bgr[d*3+1];
        c2[d*3] = bgr[d*3+2];

        c2[d*3+2] += y[d];
    }

    memcpy(bgr, c2, npixels*3);
}

void conv_1()
{
    for (int d = width + 1; d < npixels - width - 1; d++)
    {
        int Gx  = -y[d-width-1] + 0 + y[d-width+1];
            Gx += -y[d-1]*2     + 0 + y[d+1]*2;
            Gx += -y[d+width-1] + 0 + y[d+width+1];
        int Gy  = y[d-width-1]  + y[d-width]*2 + y[d-width+1];
            Gy += -y[d+width-1] + -y[d+width]*2 + -y[d+width+1];
        c[d] = sqrt(Gx*Gx + Gy*Gy);
    }

    memcpy(y, c, npixels);
}

// dunno if this actually is "dithering"
void dither()
{
    for (int d = 0; d < npixels; d++)
    {
        c[d] = y[d];
        if (d < width + 1 || d >= npixels - width - 1)
            continue;
        int avg = (y[d-width-1] + y[d-width] + y[d-width+1] +
                 y[d-1] + y[d+1] +
                 y[d+width-1] + y[d+width] + y[d+width+1]) / 8;
        if (avg > 20)
            c[d] += 10;
    }

    memcpy(y, c, npixels);
}

void mirror(unsigned char *bgr)
{
    for (int j = 0; j < height; j++)
        for (int i = 0; i < width; i++)
        {
            c2[(j*width+i)*3] = bgr[(j*width + (width - i - 1)) * 3];
            c2[(j*width+i)*3+1] = bgr[(j*width + (width - i - 1)) * 3+1];
            c2[(j*width+i)*3+2] = bgr[(j*width + (width - i - 1)) * 3+2];
        }
    memcpy(bgr, c2, npixels*3);
}

static gboolean
time_handler(GtkWidget *widget)
{
    int n = read(fd, buf, framesz);
    if (n != framesz)
    {
        fprintf(stderr, "error reading frame %d != %d\n", n, framesz);
        close(fd);
        exit(0);
    }
    printf("frame ok\n");
    
//    mirror(buf);
//    normalize(buf);
//    RGBtoYUV(buf);
//    conv_1();
//    YtoRGB(buf);

    if (palette == 15)
    {
        int i;
        for (i = 0; i < npixels; i++)
        {
            y[i] = buf[i];
        }     
        int j;
        for (j = 0; j < height / 2; j++)
            for (i = 0; i < width / 2; i++)
            {
                int v = buf[npixels + i + j * width / 2];
                Cr[j*2*width+i*2] = v;
                Cr[j*2*width+i*2+1] = v;
                Cr[(j*2+1)*width+i*2] = v;
                Cr[(j*2+1)*width+i*2+1] = v;
                v = buf[npixels + npixels / 4 + i + j * width / 2];
                Cb[j*2*width+i*2] = v;
                Cb[j*2*width+i*2+1] = v;
                Cb[(j*2+1)*width+i*2] = v;
                Cb[(j*2+1)*width+i*2+1] = v;
            }
        YUVtoRGB(rgb);
    }
    else
        memcpy(rgb, buf, npixels * 3);
    
    gdk_draw_rgb_image(pixmap, widget->style->fg_gc[GTK_STATE_NORMAL], 0, 0, width, height, GDK_RGB_DITHER_NONE, rgb, width*3);

    gtk_widget_draw(GTK_WIDGET(image), NULL);

    return TRUE;
}

int main( int   argc,
          char *argv[] )
{
    fd = open("/dev/video0", O_RDWR);
    if (fd == -1)
    {
        fprintf(stderr, "cannot open device\n");
        return 1;
    }

    struct video_capability vc;
    memset(&vc, 0, sizeof(vc));
    if (ioctl(fd, VIDIOCGCAP, &vc) == -1)
    {
        fprintf(stderr, "error in ioctl VIDIOCGCAP %d\n", errno);
        close(fd);
        return 0;
    }

    printf("name: %s\n", vc.name);
    printf("type: %08x\n", vc.type);
    printf("channels: %d\n", vc.channels);
    printf("audios: %d\n", vc.audios);
    printf("max: %d, %d\n", vc.maxwidth, vc.maxheight);
    printf("min: %d, %d\n", vc.minwidth, vc.minheight);

    struct video_window vw;
    if (ioctl(fd, VIDIOCGWIN, &vw) == -1)
    {
        fprintf(stderr, "error in ioctl VIDIOCGWIN %d\n", errno);
        close(fd);
        return 0;
    }

    printf("window: %d %d\n", vw.width, vw.height);

    vw.width = vc.maxwidth;
    vw.height = vc.maxheight;

    if (ioctl(fd, VIDIOCSWIN, &vw) == -1)
    {
        fprintf(stderr, "error in ioctl VIDIOCSWIN %d\n", errno);
        close(fd);
        return 0;
    }

    struct video_picture vp;
    if (ioctl(fd, VIDIOCGPICT, &vp) == -1)
    {
        fprintf(stderr, "error in ioctl VIDIOCGPICT %d\n", errno);
        close(fd);
        return 0;
    }

    printf("brightness: %d\n", vp.brightness);
    printf("hue: %d\n", vp.hue);
    printf("colour: %d\n", vp.colour);
    printf("contrast: %d\n", vp.contrast);
    printf("depth: %d\n", vp.depth);
    printf("palette: %d\n", vp.palette);

    if (vp.depth != 24)
    {
        fprintf(stderr, "expected a depth of 24\n");
        close(fd);
        return 0;
    }

    palette = vp.palette;

    width = vw.width;
    height = vw.height;
    npixels = width * height;
    framesz = npixels * 3;
    if (palette == 15)
        framesz = npixels * 1.5;
    buf = malloc(framesz);
    y = malloc(npixels);
    Cr = malloc(npixels);
    Cb = malloc(npixels);
    c = malloc(npixels);
    c2 = malloc(npixels*3);
    rgb = malloc(npixels*3);

    /* GtkWidget is the storage type for widgets */
    GtkWidget *window;
    
    /* This is called in all GTK applications. Arguments are parsed
     * from the command line and are returned to the application. */
    gtk_init (&argc, &argv);
    
    /* create a new window */
    window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
    
    /* When the window is given the "delete_event" signal (this is given
     * by the window manager, usually by the "close" option, or on the
     * titlebar), we ask it to call the delete_event () function
     * as defined above. The data passed to the callback
     * function is NULL and is ignored in the callback function. */
    g_signal_connect (G_OBJECT (window), "delete_event",
		      G_CALLBACK (delete_event), NULL);
    
    /* Here we connect the "destroy" event to a signal handler.  
     * This event occurs when we call gtk_widget_destroy() on the window,
     * or if we return FALSE in the "delete_event" callback. */
    g_signal_connect (G_OBJECT (window), "destroy",
		      G_CALLBACK (destroy), NULL);

    g_timeout_add(1000 / 15, (GSourceFunc) time_handler, (gpointer) window);
    
    /* Sets the border width of the window. */
    gtk_container_set_border_width (GTK_CONTAINER (window), 10);

    pixmap = gdk_pixmap_new(NULL, 352, 288, 24);
    gc = gdk_gc_new(pixmap);
    gdk_draw_rectangle(pixmap, gc, TRUE, 0, 0, 352, 288);
    
    /* Creates a new button with the label "Hello World". */
    image = (GtkImage*) gtk_image_new_from_pixmap (pixmap, NULL);
    
    /* This packs the button into the window (a gtk container). */
    gtk_container_add (GTK_CONTAINER (window), GTK_WIDGET (image));
    
    /* The final step is to display this newly created widget. */
    gtk_widget_show (GTK_WIDGET (image));
    
    /* and the window */
    gtk_widget_show (window);
    
    /* All GTK applications must have a gtk_main(). Control ends here
     * and waits for an event to occur (like a key press or
     * mouse event). */
    gtk_main ();
    
    return 0;
}

