#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>

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;

struct hdr
{
    int type;
    int size;
};
struct hdr pkt;
static unsigned char odata[320*240*3];
static unsigned char data[320*240*3];
int a;
int gotpkt = 0;
int total = 0;

void reconnect();

void diff()
{
    return;
    int i;
    int nsame = 0;
    int lrun = 0;
    int run = 0;
    for (i = 0; i < sizeof(data); i++)
        if (data[i] == odata[i])
        {
            nsame++;
            run++;
            if (run > lrun)
                lrun = run;
        }
        else
            run = 0;
    printf("nsame = %d, lrun = %d\n\n", nsame, lrun);
}

void save_frame(struct timeval *now)
{
    gdk_draw_rectangle(pixmap, GTK_WIDGET(image)->style->black_gc,
                TRUE, 135, 223, 320 - 135, 20);

    struct tm *lt = localtime(&now->tv_sec);
    char text[1024];
    sprintf(text, "%02d/%02d/%04d %02d:%02d:%02d.%02d",
        lt->tm_mday, lt->tm_mon+1, lt->tm_year + 1900, 
        lt->tm_hour, lt->tm_min, lt->tm_sec,
        now->tv_usec / 10000);
        
    PangoLayout *layout = gtk_widget_create_pango_layout (GTK_WIDGET(image), text);
    PangoFontDescription *fontdesc = pango_font_description_from_string ("Arial 12");
    pango_layout_set_font_description (layout, fontdesc); 
    gdk_draw_layout (pixmap, GTK_WIDGET(image)->style->white_gc,
                140, 221, layout);
    pango_font_description_free (fontdesc);
    g_object_unref (layout);

    static time_t last = 0;
    if (now->tv_sec != last)
    {
        last = now->tv_sec;
#if 0
        GdkPixbuf *buf = gdk_pixbuf_get_from_drawable(NULL, pixmap, NULL, 0, 0, 0, 0, 320, 240);
        if (buf)
        {
            char text[1024];
            memset(text, 0, sizeof(text));
            sprintf(text, "%04d_%02d_%02d_%02d_%02d_%02d.png",
                lt->tm_year + 1900, lt->tm_mon+1, lt->tm_mday, 
                lt->tm_hour, lt->tm_min, lt->tm_sec);
            if (fork() == 0)
            {
                gdk_pixbuf_save(buf, text, "png", NULL, NULL);
                exit(1);
            }
            g_object_unref(buf);
        }
#endif
    }

    int status;
    waitpid(-1, &status, WNOHANG);
}

static gboolean
time_handler(GtkWidget *widget)
{
    if (gotpkt == 0)
    {
        int r = recv(a, &pkt, sizeof(pkt), 0);
        if (r < 0)
        {
            if (errno == EAGAIN)
                return TRUE;

            gotpkt = 0;
            total = 0;
            reconnect();
            return TRUE;
        }
        gotpkt = 1;
    }

    int r = recv(a, data + total, pkt.size - total, 0);
    if (r < 0)
    {
        if (errno == EAGAIN)
            return TRUE;

        gotpkt = 0;
        total = 0;
        reconnect();
        return TRUE;
    }
    total += r;
    if (total < sizeof(data))
        return TRUE;

    switch(pkt.type)
    {
        case 0xfeedface:
            break;
        case 0xfeedf00d:
            {
                int i;
                for (i = 0; i < 320*240*3; i++)
                    data[i] += odata[i];
            }
            break;
        default:
            printf("unknown packet type %08x\n", pkt.type);
            exit(1);
    }

    static int started = 0;
    static struct timeval start;
    static int frame = 0;
    if (!started)
    {
        gettimeofday(&start);
        started = 1;
    }
    struct timeval now;
    gettimeofday(&now);
    int count = (now.tv_sec*1000 + now.tv_usec/1000) - 
                (start.tv_sec*1000 + start.tv_usec/1000);
    if (count == 0)
        count = 1;
    
#if 1
    // fix bad pixel
    int i;
    for (i = 2; i < sizeof(data) - 4; i++)
        if (data[i] == 0xa8 && data[i+1] == 0xfd && data[i+2] == 0xff && 
            data[i+3] == 0xff)
        {
            //printf("bad pixel: %i\n", i);
            memcpy(data+i-2, odata+i-2, 6);
            break;
        }
    for (i = 2; i < sizeof(data) - 4; i++)
        if (data[i] == 0 && data[i+1] == 0 && data[i+2] == 0 && 
            data[i+3] == 0)
        {
            //printf("bad pixel: %i\n", i);
            memcpy(data+i-2, odata+i-2, 6);
            break;
        }
    diff();
#endif
    memcpy(odata, data, sizeof(odata));

    frame++;
    int fps = frame * 1000 / count;
    printf("frame %d  %d fps   \r", frame, fps);
    fflush(stdout);

    gdk_draw_rgb_image(pixmap, widget->style->fg_gc[GTK_STATE_NORMAL], 0, 0, 320, 240, GDK_RGB_DITHER_NONE, data, 320*3);

    gtk_widget_draw(GTK_WIDGET(image), NULL);

    gotpkt = 0;
    total = 0;

    save_frame(&now);
    return TRUE;
}

int s;

void reconnect()
{
    struct sockaddr_in sin;
    memset(&sin, 0, sizeof(sin));
    int sin_len = sizeof(sin);
    printf("waiting for connection..\n");
    a = accept(s, (struct sockaddr*)&sin, &sin_len);
    if (a < 0)
    {
        fprintf(stderr, "did not get connection\n");
        exit(1);
    }

    fcntl(a, F_SETFL, O_NONBLOCK);
}

int main( int   argc,
          char *argv[] )
{
    s = socket(AF_INET, SOCK_STREAM, 0);
    struct sockaddr_in sin;
    memset(&sin, 0, sizeof(sin));
    sin.sin_family = AF_INET;
    sin.sin_addr.s_addr = INADDR_ANY;
    sin.sin_port = htons(7775);

    int opt = 1;
    setsockopt(s, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
    
    if (bind(s, (struct sockaddr*)&sin, sizeof(sin)) != 0)
    {
        fprintf(stderr, "can't bind to point\n");
        return 1;
    }

    listen(s, 5);

    reconnect();

    /* 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, 320, 240, 24);
    gc = gdk_gc_new(pixmap);
    gdk_draw_rectangle(pixmap, gc, TRUE, 0, 0, 320, 240);
    
    /* 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;
}

