#include <gtk/gtk.h>
#include <stdio.h>
#include <string.h>

#ifdef __FreeBSD__
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/sysctl.h>
#include <net/if.h>
#include <net/if_mib.h>
#endif

#include "bungmeter.h"
#include "callbacks.h"
#include "interface.h"
#include "support.h"

/* peaks bytes during a timeslice */
gint max_in;
gint max_out;

/* average bytes */
gint avg_in[AVG_RATE];
gint avg_out[AVG_RATE];
short int avg_pos = 0;

/* total bytes */
long unsigned ttl_in = 0;
long unsigned ttl_out = 0;

/* bytes this timeslice */
long unsigned bytes_in;
long unsigned bytes_out;

/* transfer history, for the actual graph */
gint graph_in[AREAW] = {0};
gint graph_out[AREAW] = {0};

gint nowgra=0;
gint big=1;
gint timer=0;
char *i;

/* colors for the graph */
GdkColor bgcolor = {0,0x0000,0x0000,0x0000};
GdkColor linecolor = {0,0x5555,0xffff,0x5555};
GdkColor incolor = {0,0xffff,0x3333,0x3333};
GdkColor outcolor = {0,0x3333,0x3333,0xffff};

GdkGC * bgcolor_gc;
GdkGC * linecolor_gc;
GdkGC * incolor_gc;
GdkGC * outcolor_gc;


gboolean thetimer(gpointer data) {
	if (nowgra < AREAW - 1) {
		nowgra++;
	} else {
		nowgra=0;
	}

	updatevalue();
	flash(data);

	return TRUE;
}

void updatevalue() {
	int n;
	unsigned long stack_in_old, stack_in_new, stack_out_old, stack_out_new;

	stack_in_old = bytes_in;
	stack_out_old = bytes_out;
	getvalue(interface, &bytes_in, &bytes_out);
	stack_in_new = bytes_in;
	stack_out_new = bytes_out;
	graph_in[nowgra] = stack_in_new - stack_in_old;
	graph_out[nowgra] = stack_out_new - stack_out_old;

	/* Keep track of max. peaks */ 
	if (max_in < graph_in[nowgra]) max_in = graph_in[nowgra];
	if (max_out < graph_out[nowgra]) max_out = graph_out[nowgra];

	/* Keep track of average speed */
	avg_in[avg_pos] = graph_in[nowgra];
	avg_out[avg_pos] = graph_out[nowgra];
	avg_pos++;
	if(avg_pos >= AVG_RATE) avg_pos=0;

	/* keep track of totals */
	ttl_in += graph_in[nowgra];
	ttl_out += graph_out[nowgra];

	big = 1; 
	for (n=0; n < AREAW; n++) {
		if (graph_in[n] > big) big = graph_in[n];
		if (graph_out[n] > big) big = graph_out[n];
	}
}

#ifdef __linux__
#define PROC_NET_DEV_FILE "/proc/net/dev"

/* open interface, 1 on success, 0 on error */
int interfaceExists(char *interface) {
	static FILE *proc_net_dev;
	char buffer[256];
	char *ptr;
	char *devname;

	/* open the proc interface */
 	if (!proc_net_dev && (proc_net_dev = fopen(PROC_NET_DEV_FILE, "r")) == NULL)
		return 0;

    /* do not parse the first two lines as they only contain static garbage */
	rewind(proc_net_dev);
	fgets(buffer, sizeof(buffer), proc_net_dev);
	fgets(buffer, sizeof(buffer), proc_net_dev);

	while (fgets(buffer, sizeof(buffer), proc_net_dev) != NULL) {
		/* find the device name and substitute ':' with '\0' */
		ptr = buffer;
		while (*ptr == ' ') {
			ptr++;
		}
		devname = ptr;
		while (*ptr != ':') {
			ptr++;
		}
		*ptr = '\0';
		ptr++;
		if (!strcmp(devname, interface)) {
			return 1;
		}
	}
	return 0;
}

void getvalue(char *interface, long unsigned *inb, long unsigned *outb) {
	static FILE *proc_net_dev;
	char buffer[256];
	char *ptr;
	char *devname;
	int dump;

 	if (!proc_net_dev && (proc_net_dev = fopen(PROC_NET_DEV_FILE, "r")) == NULL)
		return;
	
    /* do not parse the first two lines as they only contain static garbage */
	rewind(proc_net_dev);
	fgets(buffer, sizeof(buffer), proc_net_dev);
	fgets(buffer, sizeof(buffer), proc_net_dev);

	while (fgets(buffer, sizeof(buffer), proc_net_dev) != NULL) {
		/* find the device name and substitute ':' with '\0' */
		ptr = buffer;
		while (*ptr == ' ') {
			ptr++;
		}
		devname = ptr;
		while (*ptr != ':') {
			ptr++;
		}
		*ptr = '\0';
		ptr++;
		if (!strcmp(devname, interface)) {
			/* read stats and fill struct */
			sscanf(ptr, "%lu %d %d %d %d %d %d %d %lu %d %d %d %d %d %d %d",
				inb, &dump, &dump, &dump, &dump, &dump, &dump, &dump,
				outb, &dump, &dump, &dump, &dump, &dump, &dump, &dump);
//			g_printf("%s", ptr);
//			g_printf("test %lu %lu \n", inb, outb);
			continue; /* why does this need to be continue? */
		}
	}
}
#elif __FreeBSD__
int getvalue(char *interface, unsigned long *inb, unsigned long *outb) {
	size_t len;
	int name[6], num_iface, i;
    struct ifmibdata ifmd;

	len = sizeof(num_iface);
    sysctlbyname("net.link.generic.system.ifcount", &num_iface, &len, NULL, 0);
	for (i=1; i <= num_iface; i++) {
		name[0] = CTL_NET;
		name[1] = PF_LINK;
		name[2] = NETLINK_GENERIC;
		name[3] = IFMIB_IFDATA;
		name[4] = i;
		name[5] = IFDATA_GENERAL;

		len = sizeof(ifmd);
		sysctl(name, 6, &ifmd, &len, NULL, 0);
		if (strcmp(ifmd.ifmd_name, (char *)interface) == 0) {
			len = sizeof(ifmd);
	        sysctl(name, 6, &ifmd, &len, NULL, 0);
			*inb = ifmd.ifmd_data.ifi_ibytes;
			*outb = ifmd.ifmd_data.ifi_obytes;
			break;
		}
	}
}
#else
void getvalue(char *interface, unsigned long *inb, unsigned long *outb) {
	printf("Invalid operating system detected.\n");
	*inb = 0;
	*outb = 0;
}
#endif

/* updates the graph and text */
gboolean flash(GtkWidget  *drawingarea) {
	int n, p;
	float avg;
	char s[50];
	char slabel[100];  

	GdkDrawable *drawable = drawingarea->window;


    /* draw the bg black */
	gdk_draw_rectangle(drawable, bgcolor_gc, TRUE, 0, 0, AREAW, AREAH);
    

    /* draw the grid lines */ 
	for (n=1; n < 4; n++)
		gdk_draw_line(drawable, linecolor_gc, 0, AREAH*n/4 , AREAW, AREAH*n/4); 
	for (n=1; n < 6; n++) 
		gdk_draw_line(drawable, linecolor_gc, AREAW*n/6, 0, AREAW*n/6, AREAH); 


    /* draw the activity lines */
	for (n=0; n < AREAW; n++) {
		p = nowgra - n;
		if (p < 0) p = p + AREAW;  
		gdk_draw_line(drawable, incolor_gc, n, AREAH, n, AREAH - graph_in[p] * AREAH / big );
		gdk_draw_line(drawable, outcolor_gc, n, AREAH, n,
				AREAH - graph_out[p] * AREAH / big );  
	}

	/* Current average in speed */
	strcpy(slabel,"In: ");
	avg = 0;
	for(n=0; n < AVG_RATE; n++) {
		avg += avg_in[n];
	}
	avg /= AVG_RATE;
	bits2bytes(avg, s);
	strcat(slabel, s);

	/* Total transfer in */
	strcat(slabel, "/s (");
	bits2bytes(ttl_in,s);
	strcat(slabel, s);
	strcat(slabel, " total)\n");

	/* Current average out speed */
	strcat(slabel, "Out: ");
	avg = 0;
	for(n=0; n < AVG_RATE; n++) {
		avg += avg_out[n];
	}
	avg /= AVG_RATE;
	bits2bytes(avg, s);
	strcat(slabel, s);

	/* Total transfer out */
	strcat(slabel, "/s (");
	bits2bytes(ttl_out,s);
	strcat(slabel, s);
	strcat(slabel, " total)");

//	gtk_label_set_text(GTK_LABEL(lookup_widget(GTK_WIDGET (drawingarea), "label2")), slabel);
	gtk_label_set_text(GTK_LABEL(label), slabel);
//	gdk_gc_unref (gc);  
//    g_object_unref(gc);

	return TRUE;
}

void bits2bytes (float f, char * s) {
	char suffix[3];
	
#ifdef DISPLAY_BITS
	f = f * 8;
	if(f < 1e6) {
		f = f / 1e3;
		strcpy(suffix, "Kibit");
	} else if(f < 1e9) {
		f = f / 1e6;
		strcpy(suffix, "Mibit");
	} else if(f < 1e12) {
		f = f / 1e9;
		strcpy(suffix, "Gibit");
	} else {
		f = f / 1e12;
		strcpy(suffix, "Tibit");
	}
#else
	if(f < 1e6) {
		f = f / 1e3;
		strcpy(suffix, "KiB");
	} else if(f < 1e9) {
		f = f / 1e6;
		strcpy(suffix, "MiB");
	} else if(f < 1e12) {
		f = f / 1e9;
		strcpy(suffix, "GiB");
	} else {
		f = f / 1e12;
		strcpy(suffix, "TiB");
	}
#endif
	sprintf(s, "%.2f%s", f, suffix);
	return;
}

gboolean on_drawingarea1_expose_event(GtkWidget       *widget,
                                      GdkEventExpose  *event,
                                      gpointer         user_data) {
	if(!timer) {
		timer_init(widget);
	}
 
	flash(lookup_widget(GTK_WIDGET (widget), "drawingarea1"));
	return FALSE;
}


void on_window1_destroy(GtkObject *object, gpointer user_data) {
	gtk_main_quit();
}

/* start the timer going, run every 1 second */
void timer_init(GtkWidget *widget) {
	char title[20];

	/* set the app title */
	strcpy(title, "bungmeter (");
    strcat(title, interface);
   	strcat(title, ")");
	gtk_window_set_title(GTK_WINDOW (lookup_widget(GTK_WIDGET (widget), "window1")), title);


//	if(!interfaceExists(interface)) {
//		gtk_main_quit();
//	}
	getvalue(interface, &bytes_in, &bytes_out);
	timer = gtk_timeout_add(1000, thetimer, lookup_widget(GTK_WIDGET (widget), "drawingarea1"));

	GdkDrawable *drawable = widget->window;

    /* alloc colors */
    gdk_colormap_alloc_color(gdk_colormap_get_system (), &bgcolor, FALSE, TRUE);
	bgcolor_gc = gdk_gc_new(drawable);
	gdk_gc_set_foreground(bgcolor_gc, &bgcolor);

    gdk_colormap_alloc_color(gdk_colormap_get_system (), &linecolor, FALSE, TRUE);
	linecolor_gc = gdk_gc_new(drawable);
	gdk_gc_set_foreground(linecolor_gc, &linecolor);

    gdk_colormap_alloc_color(gdk_colormap_get_system (), &incolor, FALSE, TRUE);
	incolor_gc = gdk_gc_new(drawable);
	gdk_gc_set_foreground(incolor_gc, &incolor);

    gdk_colormap_alloc_color(gdk_colormap_get_system (), &outcolor, FALSE, TRUE);
	outcolor_gc = gdk_gc_new(drawable);
	gdk_gc_set_foreground(outcolor_gc, &outcolor);
}
 
