/*
 *	gmotionlive
 *
 *	Copyright Jeroen Vreeken (pe1rxq@amsat.org) 2004, 2005
 *
 *	This program is published under the terms of the GNU General
 *	Public License version 2.0 or any later version.
 */

#include <gtk/gtk.h>
#include <ctype.h>
#include <libgnomevfs/gnome-vfs.h>
#include <string.h>
#include <malloc.h>
#include <stdio.h>
#include <time.h>

GtkWidget *window;
GtkWidget *gtkimage=NULL;
GtkWidget *eventbox;
GdkPixbufLoader *pixbufloader=NULL;

enum {
	NETCAM_STATE_HEADER,
	NETCAM_STATE_START,
	NETCAM_STATE_DATA,
};

int netcam_state = NETCAM_STATE_HEADER;
int netcam_linesize = 0;
#define NETCAM_LINELENGTH 80
char netcam_line[NETCAM_LINELENGTH];
int netcam_datasize = 0;
int netcam_data = 0;
int netcam_boundarylen;
int netcam_offset;
unsigned char databuf[8192];
unsigned char boundary[4096];
unsigned char *boundarystart;
unsigned char *buf = databuf + 4096;
int netcam_count = 0;
int netcam_time = 0;
int netcam_timecount = 0;
char *url_desc;
char *url;
time_t start_time;
long int bytecnt = 0;
long int imgcnt = 0;
char about[] =
	"Gmotionlive 1.0\n"
	"\n"
	"Copyright Jeroen Vreeken (pe1rxq@amsat.org), 2004, 2005\n"
	"\n"
	"Gmotionlive is a simple multipart/x-mixed-replace viewer\n"
	"It was written for viewing the streams produced by motion.\n"
	"(http://motion.sourceforge.net)\n"
	"\n"
	"Gmotionlive is published under the GNU General Public License\n"
	"version 2.0 or any later version.\n"
	"\n";

void failure(char *text)
{
	GtkWidget *dialog, *label;

	dialog = gtk_dialog_new_with_buttons(NULL, NULL,
	    GTK_DIALOG_MODAL | GTK_DIALOG_NO_SEPARATOR, GTK_STOCK_OK,
	    GTK_RESPONSE_ACCEPT, NULL);
	label = gtk_label_new(text);
	g_signal_connect_swapped(GTK_OBJECT(dialog), "response",
	    G_CALLBACK(gtk_main_quit), GTK_OBJECT(dialog));
	gtk_container_add(GTK_CONTAINER(GTK_DIALOG(dialog)->vbox), label);
	gtk_widget_show(label);
	gtk_widget_show_all(dialog);
}

void image_closed(GdkPixbufLoader *loader, gpointer user_data)
{
	GdkPixbuf *newpixbuf;

	if (!pixbufloader) {
		g_print("image_closed: Didn't get a pixbufloader\n");
		return;
	}
	newpixbuf = gdk_pixbuf_loader_get_pixbuf(pixbufloader);
	if (!newpixbuf)
		return;
	gtk_image_set_from_pixbuf(GTK_IMAGE(gtkimage), newpixbuf);
	g_object_unref(newpixbuf);
	gtk_widget_show(gtkimage);
	gtk_window_resize(GTK_WINDOW(window), 100, 100);
	gtk_widget_show_all(window);
	imgcnt++;
}

size_t netcam_write(void *ptr, size_t size, size_t nmemb, void *stream)
{
	int i, j;
	unsigned char *data = ptr;

	/* Write the entire stream to stdout for debugging (or recording): */
	/*write(2, data, size * nmemb);*/
	
	bytecnt += size * nmemb;
	for (i = 0; i < size * nmemb; i++) {
		if (netcam_state == NETCAM_STATE_HEADER) {
			for (; i < size * nmemb; i++) {
				if (data[i] == '\n' ||
				    netcam_linesize >= NETCAM_LINELENGTH - 2) {
					if (netcam_line[0] == '-' && netcam_line[1] == '-') {
						netcam_boundarylen = strlen(netcam_line);
						boundarystart = buf - netcam_boundarylen;
						strcpy(boundary, netcam_line);
						netcam_state = NETCAM_STATE_START;
						i++;
					}
					netcam_line[0] = 0;
					netcam_linesize = 0;
					if (netcam_state != NETCAM_STATE_HEADER)
						break;
				} else {
					netcam_line[netcam_linesize++] = data[i];
					netcam_line[netcam_linesize] = 0;
				}
			}
		}
		if (netcam_state == NETCAM_STATE_START) {
			for (; i < size * nmemb; i++) {
				if (data[i] == '\n') {
					if (netcam_linesize <= 1) {
						i++;
						netcam_linesize = 0;
						netcam_state = NETCAM_STATE_DATA;
						pixbufloader = gdk_pixbuf_loader_new();
						if (!pixbufloader) {
							g_print("ohoh6\n");
							netcam_state = NETCAM_STATE_START;
							break;
						}
						g_signal_connect(G_OBJECT(pixbufloader),
						    "closed",
						    G_CALLBACK(image_closed),
						    NULL);
						break;
					}
					netcam_linesize = 0;
				} else {
					netcam_linesize++;
				}
			}	
		}
		if  (netcam_state == NETCAM_STATE_DATA) {
			unsigned char *start;
			int checklen;
			int boundoff = 0;
			int back = i + 1;
			if (netcam_linesize == 0) {
				netcam_linesize = 1;
				start = data + i;
				checklen = size * nmemb - i - netcam_boundarylen;
				netcam_offset = 0;
				if (checklen < 0) {
					netcam_offset = netcam_boundarylen * 2 + checklen;
					checklen = 0;
				}
				i += netcam_boundarylen;
			} else {
				i += netcam_offset;
				start = boundarystart+netcam_offset;
				checklen = size * nmemb - netcam_offset;
				netcam_offset = 0;
				if (checklen < 0) {
					i = size * nmemb;
					netcam_offset = -checklen;
					boundoff = netcam_boundarylen - netcam_offset;
					checklen = 0;
				}
			}
			for (j = 0; j < checklen; j++) {
				if (start[j] == '-' &&
				    !strncmp(boundarystart+j, boundary, netcam_boundarylen)) {
					j--;
					netcam_state = NETCAM_STATE_START;
					break;
				}
			}
			if (j) {
				if (FALSE == gdk_pixbuf_loader_write(pixbufloader, start, j, NULL)) {
					netcam_linesize = 1;
					i = back;
					netcam_state = NETCAM_STATE_START;
				} 
				else
					i += j;
			}
			if (netcam_state == NETCAM_STATE_START) {
					netcam_linesize = 1;
					netcam_line[0] = 0;
					gdk_pixbuf_loader_close(pixbufloader, NULL);
			}
			memcpy(boundarystart + boundoff, start + checklen, netcam_boundarylen);
		}
	}
	return size * nmemb;
}

void read_callback (GnomeVFSAsyncHandle *handle, GnomeVFSResult result, gpointer buffer, GnomeVFSFileSize bytes_requested, GnomeVFSFileSize bytes_read, gpointer callback_data)
{
	if (result != GNOME_VFS_OK) {
		failure("gmotionlive can not read from the stream");
	} else {
		if (bytes_read)
			netcam_write(buffer, 1, bytes_read, NULL);
		gnome_vfs_async_read(handle, buffer, 4095, read_callback, "read_callback");
	}
}

void open_callback (GnomeVFSAsyncHandle *handle, GnomeVFSResult result, gpointer callback_data)
{
	if (result != GNOME_VFS_OK) {
		failure("gmotionlive was unable to open the specified stream");
	} else {
		gnome_vfs_async_read(handle, buf, 4095, read_callback, "read_callback");
	}
}

int transfer(char *transfer_url)
{
	GnomeVFSAsyncHandle *handle;

	gnome_vfs_async_open(&handle, transfer_url, GNOME_VFS_OPEN_READ,
	    GNOME_VFS_PRIORITY_MIN, open_callback, "open_callback");

	return 0;
}


gint close_application(GtkWidget *widget, GdkEvent *event, gpointer data)
{

	gtk_main_quit();
	return FALSE;
}

static gboolean image_click (GtkWidget *eventbox, GdkEventButton *event, gpointer data)
{
	GtkWidget *dialog, *label;
	char *message;

	message=malloc(strlen(about)+strlen(url_desc)+strlen(url)+80);
	if (!message)
		return FALSE;
	sprintf(message, "%sNow viewing: %s\nURL: %s\nFrames viewed: %ld (%ld KBytes)\nFPS: %.1f (%.1f KBps)\n",
	    about, url_desc, url, imgcnt, bytecnt / 1024,
	    imgcnt/((float)(time(NULL) - start_time) + 0.0001),
	    bytecnt/((float)(time(NULL) - start_time) * 1024 + 0.0001));

	dialog = gtk_dialog_new_with_buttons(NULL, NULL,
	    GTK_DIALOG_MODAL | GTK_DIALOG_NO_SEPARATOR, GTK_STOCK_OK,
	    GTK_RESPONSE_ACCEPT, NULL);
	label = gtk_label_new(message);
	g_signal_connect_swapped(GTK_OBJECT(dialog), "response",
	    G_CALLBACK(gtk_widget_destroy), GTK_OBJECT(dialog));
	gtk_container_add(GTK_CONTAINER(GTK_DIALOG(dialog)->vbox), label);
	gtk_widget_show(label);
	gtk_widget_show_all(dialog);
	free(message);
	
	return FALSE;
}

int main(int argc, char **argv)
{
	gtk_init (&argc, &argv);

	if (argc < 2 || argv[1] == NULL) {
		failure("No URL to an image stream given!");
		gtk_main();
		return 1;
	}

	gnome_vfs_init();

	window = gtk_window_new(GTK_WINDOW_TOPLEVEL);

	gtkimage = gtk_image_new();

	eventbox = gtk_event_box_new();
	gtk_container_add(GTK_CONTAINER(eventbox), gtkimage);
	g_signal_connect(G_OBJECT(eventbox), "button_press_event",
	    G_CALLBACK(image_click), gtkimage);

	gtk_container_add(GTK_CONTAINER(window), eventbox);
	g_signal_connect(G_OBJECT(window), "delete_event",
	    G_CALLBACK(close_application), NULL);
	gtk_widget_show_now(window);

	start_time = time(NULL);
	transfer(argv[1]);
	url = argv[1];
	if (argc >= 2 && argv[2] != NULL)
		url_desc = argv[2];
	else
		url_desc = argv[1];
	gtk_window_set_title(GTK_WINDOW(window), url_desc);

	gtk_main();

	return 0;
}
