#include	<stdio.h>
#include	<stdlib.h>
#include	<string.h>
#include	<sys/param.h>
#include	<sys/types.h>
#include	<sys/socket.h>
#include	<sys/sysctl.h>
#include	<netinet/in.h>
#include	<arpa/inet.h>
#include	<net/if.h>
#include	<net/if_var.h>
#include	<net/if_dl.h>
#include	<net/route.h>
#include	<gkrellm2/gkrellm.h>

#define		PLUGIN_NAME			"GkInterfaces"
#define		PLUGIN_VERSION		"1.0.1"
#define		PLUGIN_URL			"http://www.mitz.jp/"
#define		PLUGIN_PLACEMENT	(MON_UPTIME | MON_INSERT_AFTER)
#define		CONFIG_NAME			"Interfaces"
#define		STYLE_NAME			CONFIG_NAME


typedef		struct iface_t{
	GkrellmPanel	*panel;
	GkrellmDecal	*decal;
	GtkTooltips		*tips;
	gchar			*ifname,
					*addr;
#define		ACTIVE				(1 << 0)
#define		SHOW				(1 << 1)
#define		LABEL				(1 << 2)
#define		SCROLL				(1 << 3)
	gint			flags,
					pos;
	struct	iface_t	*next;
}	iface_t;

static	GkrellmMonitor	*monitor;
static	GtkWidget		*PanelBox;
static	gint			style_id;
static	iface_t			*start = NULL;

static	iface_t *new_iface(gchar *ifname, gchar *addr, int flags)
{

	iface_t **p = &start,
			*q;

	while(*p)
		p  = &((*p)->next);

	q = malloc(sizeof(iface_t));
	memset(q, '\0', sizeof(iface_t));
	q->ifname = g_strdup(ifname);
	q->addr = g_strdup(addr ? addr : "0.0.0.0");
	q->flags = flags;
	*p = q;

	return(q);

}

static	void	update_tooltips(iface_t *iface)
{

	gchar	*text;
	GString	*tips;

	if(iface && iface->panel && iface->tips && strlen(iface->addr)){
		text = g_strdup_printf("%s: %s", iface->ifname, iface->addr);

		if((tips = g_string_sized_new(strlen(text)))){
			g_string_append(tips, text);

			gtk_tooltips_set_tip(iface->tips, iface->panel->drawing_area, 
				tips->str, NULL);
			gtk_tooltips_set_delay(iface->tips, 750);
			gtk_tooltips_enable(iface->tips);

			g_string_free(tips, TRUE);

		}
	}

	return;

}

static	int	found_iface(gchar *interface, gchar *addr)
{

	iface_t	*p = start;

	while(p){
		if(!strcmp(p->ifname, interface)){
			if((p->flags & ACTIVE) && (p->flags & SHOW)){
				if(!strcmp(p->addr, addr))
					return(0);
				else{
					g_free(p->addr);
					p->addr = g_strdup(addr);
				}
			}
			p->flags |= ACTIVE;
			return(p->flags & SHOW);
		}
		p = p->next;
	}

	p = new_iface(interface, addr, ACTIVE);
	p->flags |= ACTIVE;
	gkrellm_config_modified();

	return(p->flags & SHOW);

}

/*
 * If you would like to support other operating system.
 * You have to make find_iface(void) function depend operating system.
 * It return small integer number like 0 or 1. If it find interface(s),
 * return not 0, otherwise return 0.
 *
 * find_iface() have to call to found_iface("Interface name", "IP addr")
 * if you want to add new interface.
 */ 
#ifdef	__FreeBSD__
/*
 * xaddrs() and find_iface() code are from FreeBSD's ifconfig.c 
 *  Copyright (c) 1983, 1993
 *  The Regents of the University of California.  All rights reserved.
 */
static	void	xaddrs(caddr_t cp, caddr_t cplim, struct rt_addrinfo *rtinfo)
{

	struct	sockaddr	*sa;
	int		i;

	memset(rtinfo->rti_info, 0, sizeof(rtinfo->rti_info));
	for(i = 0; (i < RTAX_MAX) && (cp < cplim); i++){
		if(rtinfo->rti_addrs & (1 << i)){
			rtinfo->rti_info[i] = sa = (struct sockaddr *)cp;
			cp += SA_SIZE(sa);
		}
	}

	return;

}

static	int	find_iface(void)
{

	int					mib[6],
						count,
						ret = 0;
	size_t				need;
	char				*buf,
						*lim,
						*next,
						name[128];
	struct	sockaddr_in	*sin = NULL;
	struct	if_msghdr	*ifm,
						*nextifm;
	struct	ifa_msghdr	*ifam;
	struct	sockaddr_dl	*sdl;
	struct	rt_addrinfo	info;

	mib[0] = CTL_NET;
	mib[1] = PF_ROUTE;
	mib[2] = 0;
	mib[3] = AF_INET;
	mib[4] = NET_RT_IFLIST;
	mib[5] = 0;

	if(sysctl(mib, 6, NULL, &need, NULL, 0) < 0)
		return(0);

	if(!(buf = malloc(need)))
		return(0);

	if(sysctl(mib, 6, buf, &need, NULL, 0) < 0)
		return(0);

	lim = buf + need;

	next = buf;
	while(next < lim){
		ifm = (struct if_msghdr *)next;

		if(ifm->ifm_type == RTM_IFINFO){
			if(!ifm->ifm_data.ifi_datalen)
				ifm->ifm_data.ifi_datalen = sizeof(struct if_data);
			sdl = (struct sockaddr_dl *)
				((char *)ifm + sizeof(struct if_msghdr) -
			     sizeof(struct if_data) + ifm->ifm_data.ifi_datalen);
		}
		else
			return(0);

		next += ifm->ifm_msglen;
		ifam = NULL;
		count = 0;

		while(next < lim){
			nextifm = (struct if_msghdr *)next;

			if(nextifm->ifm_type != RTM_NEWADDR)
				break;

			if(ifam == NULL)
				ifam = (struct ifa_msghdr *)nextifm;

			count++;
			next += nextifm->ifm_msglen;
		}
		memcpy(name, sdl->sdl_data, sdl->sdl_nlen);
		name[sdl->sdl_nlen] = '\0';

		if(count){
			info.rti_addrs = ifam->ifam_addrs;
			xaddrs((char *)(ifam + 1), ifam->ifam_msglen + (char *)ifam, &info);
			sin = (struct sockaddr_in *)info.rti_info[RTAX_IFA];
		}

		ret += found_iface(name, count ? inet_ntoa(sin->sin_addr) : " n/a ");

	}

	free(buf);

	return(ret);

}
#endif

static	gint	find_interface(void)
{

	return(find_iface());

}

static	gint	panel_expose_event(GtkWidget *widget, GdkEventExpose *event,
	GkrellmPanel *panel)
{

	gdk_draw_pixmap(widget->window,
		widget->style->fg_gc[GTK_WIDGET_STATE(widget)],
		panel->pixmap,
		event->area.x, event->area.y,
		event->area.x, event->area.y,
		event->area.width, event->area.height);

	return(FALSE);

}

static	void	panel_button_event(GtkWidget *widget, GdkEventButton *event, 
	iface_t *iface)
{

	iface->pos = 0;

    if(event->button == 1)
		iface->flags ^= SCROLL;

    if(event->button == 2)
		iface->flags ^= LABEL;

    if(event->button == 3)
		gkrellm_open_config_window(monitor);

}

static	GkrellmPanel	*del_panel(GkrellmPanel *panel)
{

	if(panel){
		gkrellm_destroy_decal_list(panel);
		gkrellm_panel_destroy(panel);
		gkrellm_pack_side_frames();
	}

	return(NULL);

}

static	void	create_panel(iface_t *p, int create)
{

	GkrellmStyle	*style;
	GkrellmPanel	*panel = p->panel;

	if(panel)
		gkrellm_destroy_decal_list(panel);
	else
		panel = gkrellm_panel_new0();

	style = gkrellm_meter_style(style_id);
	panel->textstyle = gkrellm_meter_textstyle(style_id);
	p->decal = gkrellm_create_decal_text(panel,"0.0.0.0",
		panel->textstyle, style, -1, -1, -1);
	gkrellm_panel_configure(panel, NULL, style);
	gkrellm_panel_create(PanelBox, monitor, panel); 

	if(!p->panel || create){
		g_signal_connect(GTK_OBJECT(panel->drawing_area),
			"expose_event", G_CALLBACK(panel_expose_event), panel);
		g_signal_connect(GTK_OBJECT(panel->drawing_area),
			"button_release_event", (GtkSignalFunc)panel_button_event, p);
		p->tips = gtk_tooltips_new();
	}

	gkrellm_draw_decal_text(panel, p->decal, NULL, -10);
	gkrellm_draw_panel_layers(panel);
	gkrellm_pack_side_frames();

	p->panel = panel;

	update_tooltips(p);

	return;

}

static	void	reset_panel(int create)
{

	iface_t	*p = start;

	while(p){
		if((p->flags & ACTIVE) && (p->flags & SHOW))
			create_panel(p, create);
		else
			p->panel = del_panel(p->panel);
		p = p->next;
		
	}

	return;

}

static	iface_t	*update_panel(iface_t *iface)
{

	static	gint	width;
			gint	namelen;
			char	*text,
					*tmp;

	if(iface->panel){
		if(!width)
			width = gkrellm_chart_width();
	
		if(iface->flags & LABEL)
			tmp = g_strdup_printf("%s: ", iface->ifname);
		else
			tmp = g_strdup("");
		namelen = gkrellm_gdk_string_width(gkrellm_default_font(1), tmp);

		if(!(iface->flags & SCROLL)){
			if(!iface->pos){
				while(width <= gkrellm_gdk_string_width(gkrellm_default_font(1),
					iface->addr + iface->pos) + namelen)
					iface->pos++;
				iface->pos++;
			}
		}
		else if(iface->pos >= strlen(iface->addr) ||
			width > (namelen + 
				gkrellm_gdk_string_width(gkrellm_default_font(1), iface->addr)))
			iface->pos = 0;

		text = g_strdup_printf("%s%s", tmp, iface->addr + iface->pos);
		gkrellm_draw_decal_text(iface->panel, iface->decal, text, -10);
		gkrellm_draw_panel_layers(iface->panel);

		if(iface->flags & SCROLL)
			iface->pos++;

		g_free(text);
		g_free(tmp);
	}

	return(iface->next);

}

static	void	update_plugin(void)
{

	iface_t *p = start;

	if(GK.second_tick)
		while(p){
			if((p->flags & ACTIVE) && (p->flags & SHOW))
				p = update_panel(p);
			else
				p = p->next;
		}

	if(GK.two_second_tick)
		if(find_interface())
			reset_panel(0);

	return;

}

static	void	create_plugin(GtkWidget *vbox, gint first_create)
{

	if(first_create)
		PanelBox = vbox;

	find_interface();
	reset_panel(!first_create);

	return;

}

static	void	save_config(FILE *fp)
{

	iface_t	*p = start;
  
  	while(p){
		fprintf(fp, "%s %s %s %d\n", CONFIG_NAME, p->ifname, p->addr, p->flags);
		p = p->next;
	}

	return;

}

static	void	load_config(gchar *arg)
{

	gchar	ifname[10],
			addr[128];
	int		flags;

	if(sscanf(arg, "%s %s %d\n", ifname, addr, &flags) == 3)
		new_iface(ifname, addr, flags);

	return;

}

static	void	show_button(GtkWidget *widget, iface_t *iface)
{

	iface->flags ^= SHOW;

}

static	void	label_button(GtkWidget *widget, iface_t *iface)
{

	iface->flags ^= LABEL;

}

static	void	scroll_button(GtkWidget *widget, iface_t *iface)
{

	iface->flags ^= SCROLL;

}

static	void	create_toggle_button(char *text, iface_t *iface, int flags, 
	GtkWidget *box, GtkSignalFunc func)
{

	GtkWidget	*button;

	button = gtk_check_button_new_with_label(text);

	gtk_box_pack_start(GTK_BOX(box), button, FALSE, TRUE, 3);
	gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(button),
		iface->flags & flags);
	g_signal_connect(G_OBJECT(button), "toggled", G_CALLBACK(func), iface);

}

static	iface_t	*create_device_tab(GtkWidget *notebook, iface_t *iface)
{

	GtkWidget	*label,
				*frame,
				*vbox,
				*separator;

	frame = gtk_frame_new(NULL);
	label = gtk_label_new(iface->ifname);

	gtk_notebook_append_page(GTK_NOTEBOOK(notebook), frame, label);

	vbox = gtk_vbox_new(FALSE, 0);
	gtk_container_add(GTK_CONTAINER(frame), vbox);

	create_toggle_button("Show this Interface", iface, SHOW, vbox,
		(GtkSignalFunc)show_button);
	separator = gtk_hseparator_new();
	gtk_box_pack_start(GTK_BOX(vbox), separator, FALSE, TRUE, 2);
	create_toggle_button("Show Interface name", iface, LABEL, vbox,
		(GtkSignalFunc)label_button);
	create_toggle_button("Scroll IPaddr", iface, SCROLL, vbox,
		(GtkSignalFunc)scroll_button);

	return(iface->next);
  
}

static	void	create_devices(GtkWidget *notebook)
{

	iface_t *p = start;
  
  	while(p)
		p = create_device_tab(notebook, p); 

	return;

}

static	void	set_info(GtkWidget *text)
{

	gchar	*info_text[] = {
		"\n",
		"<b>This plugin allows you to display ipaddr of each interface.\n",
		"<b>\n",
		"User Intarface:\n",
		"\t Left mouse button will (un)scroll ip address\n",
		"\t Middle mouse button will (un)display interface name\n",
		"\t Right mouse button will display config box\n",
		"\n",
		"Configuration:\n",
		"\tEvery detected interface will have ", 
		"one config tab with the following options:\n",
		"\t* Show this interface: Show information about the interface\n",
		"\t* Show Interface name: Show interface name\n",
		"\t* Scroll IPaddr: Displya ip address by scrolling\n",
	};

	gkrellm_gtk_text_view_append_strings(text, info_text,
		sizeof(info_text) / sizeof(gchar *));

	return;

}

static	void	create_info(GtkWidget *notebook)
{

	GtkWidget	*frame,
				*scrolled,
				*text,
				*page;

	frame = gtk_frame_new(NULL);
	gtk_container_border_width(GTK_CONTAINER(frame), 3);

	scrolled = gtk_scrolled_window_new(NULL, NULL);
	gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolled),
		GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
	gtk_container_add(GTK_CONTAINER(frame), scrolled);

	page = gkrellm_gtk_notebook_page(notebook, "Info");
	text = gkrellm_gtk_scrolled_text_view(page, NULL,
		GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC);
	set_info(text);

	return;

}

static	void	create_about(GtkWidget *notebook)
{

	gchar		*about_text;
	GtkWidget	*text,
				*label;

	about_text = g_strdup_printf(
		PLUGIN_NAME " " PLUGIN_VERSION "\n"
		"GkrellM " PLUGIN_NAME " Plugin" "\n\n"
		"Copyright (c) 2006 by Mitzyuki IMAIZUMI\n"
		"mitz@mitz.jp\n"
		PLUGIN_URL "\n\n"
		"Released under the GNU Public Licence.");

	text = gtk_label_new(about_text); 
	label = gtk_label_new("About");
	gtk_notebook_append_page(GTK_NOTEBOOK(notebook), text, label);
	g_free(about_text);

	return;

}


static	void	create_config(GtkWidget *tab) 
{

	GtkWidget *notebook;

	notebook = gtk_notebook_new();
	gtk_notebook_set_tab_pos(GTK_NOTEBOOK(notebook), GTK_POS_TOP);
	gtk_box_pack_start(GTK_BOX(tab), notebook, TRUE, TRUE, 0);

	create_devices(notebook);
	create_info(notebook);
	create_about(notebook);

	return;

}

static	void	apply_config(void)
{

	reset_panel(0);

}

static	GkrellmMonitor	interfaces = {
	CONFIG_NAME,		/* Name, for config tab. */
	0,					/* Id, 0 if a plugin */
	create_plugin,		/* The create function */
	update_plugin,		/* The update function */
	create_config,		/* The config tab create function */
	apply_config,		/* Apply the config function */
	save_config,		/* Save user config */
	load_config,		/* Load user config */
	CONFIG_NAME,		/* config keyword */
	NULL,				/* Undefined 2 */
	NULL,				/* Undefined 1 */
	NULL,				/* Undefined 0 */
	PLUGIN_PLACEMENT,	/* Insert plugin before this monitor. Choose */
	NULL,				/* Handle if a plugin, filled in by GKrellM */
	NULL				/* path if a plugin, filled in by GKrellM */
};


GkrellmMonitor	*gkrellm_init_plugin(void)
{

	style_id = gkrellm_add_meter_style(&interfaces, STYLE_NAME);
	monitor = &interfaces;

	return(monitor);

}
