/*
    This file is part of pload - a program to monitor ppp activity for X
    Copyright (C) 1999-2000  Matt Smith <mdsmith@engr.utk.edu>

    This program is free software; you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation; either version 2 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program; if not, write to the Free Software
    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.

*/

#include <X11/Intrinsic.h>
#include <X11/Shell.h>
#include <X11/StringDefs.h>
#include <X11/Xaw/Cardinals.h>
#include <X11/Xaw/Label.h>
#include <X11/Xaw/Form.h>
#include <X11/Xaw/StripChart.h>
#include <stdlib.h>
#include <stdio.h>
#include <math.h>
#include <assert.h>
#include "pload.h"
#include "pload.xbm"

/************* Function Prototypes ********/
static void GetIChartPoint(Widget,XtPointer,XtPointer);
static void GetOChartPoint(Widget,XtPointer,XtPointer);
static void Usage(void);
static void HandleExit(void);
static void CheckForWMExit(Widget, XtPointer, XEvent*, Boolean*);
static void CheckForIconState(Widget, XtPointer, XEvent*, Boolean*);
static void update(XtPointer, XtIntervalId*);
static void make_label(char*,unsigned long,double,double);
static void do_rate(char*,double);
static void do_total(char*, double);

/************* Global Variables **********/
static char		progver[]= VERSION;
static char		buff[128];
static if_data 		ifd;
static XtAppContext 	context;
static Widget		toplevel, topform, iform, oform;
static Widget		ilabel, ichart, ochart, olabel;
static Arg		arglist[8];
static int		i;
static Atom 		wm_delete_window;
static char		*Progname;
static int		iconstate;
static XrmOptionDescRec options[] =
{
{"-minscale",	"*minScale",		XrmoptionSepArg,	NULL},
{"-indiv",	"*inDiv",		XrmoptionSepArg,	(XtPointer)1024},
{"-outdiv",	"*outDiv",		XrmoptionSepArg,	(XtPointer)1024},
{"-hl",		"*highlight",		XrmoptionSepArg,	NULL},
{"-jumpscroll",	"*jumpScroll",		XrmoptionSepArg,	NULL},
{"-update",	"*update",		XrmoptionSepArg,	NULL},
{"-in",		"*showOut",		XrmoptionNoArg,		"False"},
{"-out",	"*showIn",		XrmoptionNoArg,		"False"},
{"-nolabel",	"*showLabel",		XrmoptionNoArg,		"False"},
{"-device",	"*device",		XrmoptionSepArg,	NULL},
{"-incolor",	"*ichart.foreground",	XrmoptionSepArg,	NULL},
{"-outcolor",	"*ochart.foreground",	XrmoptionSepArg,	NULL},
{"-average",	"*averagePoints",	XrmoptionSepArg,	(XtPointer)10},
{"-horiz",	"*horiz",		XrmoptionNoArg,		"True"},
{"-nochart",	"*showChart",		XrmoptionNoArg,		"False"},
{"-logscale",	"*logScale",		XrmoptionNoArg,		"True"},
#ifdef LINUXPROC
{"-noproc",	"*useProc",		XrmoptionNoArg,		"False"},
#endif
{"-ncmsg",	"*noConnectMsg",	XrmoptionSepArg,	NULL},
{"-iformat",	"*inFormat",		XrmoptionSepArg,	NULL},
{"-oformat",	"*outFormat",		XrmoptionSepArg,	NULL},
};
static String default_resources[] =
{
#if 0
	"*minscale		:	3",
	"*ilabel.background	:	red",
	"*olabel.background	:	lightblue",
	"*ochart.background	:	pink",
	"*ichart.background	:	yellow",
#endif
	"*update		:	1",
	"*ichart.foreground	:	red",
	"*ichart.jumpScroll	:	1",
	"*ochart.foreground	:	darkgreen",
	"*ochart.jumpScroll	:	1",
	"*topform*borderWidth	:	0",
/*	"*topform*font		:	fixed",   */
/*	"*Pload.geometry	:	180x200", */	 /* vertical   */
/*	"*Pload.geometry	:	300x120", */	/* horizontal */
/*	"*Pload.geometry	:	180x35",  */	/* labels only */
	"*showIn		:	True",
	"*showOut		:	True",
	"*device		:	ppp0",
	"*showLabel		:	True",
	"*averagePoints		:	10",
	"*inDiv			:	1024",
	"*outDiv		:	1024",
	"*horiz			:	False",
	"*showChart		:	True",
	"*logScale		:	False",
#ifdef LINUXPROC
	"*useProc		:	True",
#endif
	"*noConnectMsg		:	No Connection",
	"*inFormat		:	%t %r in",
	"*outFormat		:	%t %r out",
NULL};

typedef struct
{
	String device;
	String ncMsg;
	String ifmt;
	String ofmt;
	Boolean showIn;
	Boolean showOut;
	Boolean showLabel;
	Boolean horiz;
	Boolean showChart;
#ifdef LINUXPROC
	Boolean useProc;
#endif
	Boolean logScale;
	int avgpts;
	int indiv;
	int outdiv;
	int update;
} PloadResources;

/* another global */
static PloadResources resources;

static XtResource pload_resources[] = {
{
	"averagePoints",		/* Resource name */
	"AveragePoints",		/* Resource class */
	XtRInt,				/* Represenation type */
	sizeof(int),			/* size of representation type */
     	XtOffsetOf(PloadResources, avgpts), /* Offset from base */
	XtRImmediate,			/* Representation of specified default */
	(XtPointer)10			/* Address of default resource */
},
{
	"logScale",		
	XtCBoolean,			
	XtRBoolean,			
	sizeof(Boolean),	
     	XtOffsetOf(PloadResources, logScale), 
	XtRImmediate,		
	(XtPointer)False
},
{
	"showChart",		
	XtCBoolean,			
	XtRBoolean,			
	sizeof(Boolean),	
     	XtOffsetOf(PloadResources, showChart), 
	XtRImmediate,		
	(XtPointer)True
},
{
	"update",
	"Update",
	XtRInt,
	sizeof(int),
     	XtOffsetOf(PloadResources, update),
	XtRImmediate,
	(XtPointer)1
},
#ifdef LINUXPROC
{
	"useProc",
	XtCBoolean,
	XtRBoolean,
	sizeof(Boolean),
     	XtOffsetOf(PloadResources, useProc),
	XtRImmediate,	
	(XtPointer)True
},
#endif
{
	"horiz",
	XtCBoolean,
	XtRBoolean,
	sizeof(Boolean),
     	XtOffsetOf(PloadResources, horiz),
	XtRImmediate,	
	(XtPointer)False
},
{
	"inDiv",
	"InDiv",
	XtRInt,
	sizeof(int),
     	XtOffsetOf(PloadResources, indiv),
	XtRImmediate,
	(XtPointer)1024
},
{
	"outDiv",
	"OutDiv",
	XtRInt,
	sizeof(int),
     	XtOffsetOf(PloadResources, outdiv),
	XtRImmediate,
	(XtPointer)1024
},
{
	"device",			
	XtCString,		
	XtRString,		
	sizeof(String),		
     	XtOffsetOf(PloadResources, device),
	XtRString,		
	"ppp0"				
},
{
	"noConnectMsg",			
	XtCString,		
	XtRString,		
	sizeof(String),		
     	XtOffsetOf(PloadResources, ncMsg),
	XtRString,		
	"No Connection"				
},
{
	"inFormat",			
	XtCString,		
	XtRString,		
	sizeof(String),		
     	XtOffsetOf(PloadResources, ifmt),
	XtRString,		
	"%t %r in"				
},
{
	"outFormat",			
	XtCString,		
	XtRString,		
	sizeof(String),		
     	XtOffsetOf(PloadResources, ofmt),
	XtRString,		
	"%t %r out"				
},
{
	"showLabel",			
	XtCBoolean,		
	XtRBoolean,			
	sizeof(Boolean),		
     	XtOffsetOf(PloadResources, showLabel), 
	XtRImmediate,			
	(XtPointer)True		
},
{
	"showIn",		
	XtCBoolean,			
	XtRBoolean,			
	sizeof(Boolean),	
     	XtOffsetOf(PloadResources, showIn), 
	XtRImmediate,		
	(XtPointer)True
},
{
	"showOut",
	XtCBoolean,
	XtRBoolean,
	sizeof(Boolean),
     	XtOffsetOf(PloadResources, showOut),
	XtRImmediate,
	(XtPointer)True
}
};

/************* Main () ******************/
int main (int argc, char *argv[])
{
	Progname = argv[0];
	
	toplevel = XtAppInitialize (
		&context,			/* context */
		"Pload",			/* app name, class */
		options,			/* options */
		XtNumber(options),		/* num_options */
		&argc, argv,			/* command line */
		default_resources,		/* fallback resources */
		NULL, ZERO);
	
	/* all command line options are handled as X resources,
	   if an unknown option, like "-help" is found, show usage
	   and exit */	
	if (argc != 1) Usage();
	
	XtGetApplicationResources(
		toplevel,			/* widget */
		(XtPointer) &resources, 	/* where to put */
		pload_resources,		/* XtResourceList */
		XtNumber(pload_resources),	/* Cardinal num_resources */
		NULL, ZERO);
	
	/* set the title to reflect the device monitored */
	i=0;
	sprintf(buff, "Pload: %s", resources.device);
	XtSetArg(arglist[i], XtNtitle, buff); i++;
	XtSetValues(toplevel, arglist, i);

#ifdef LINUXPROC
	ifd_initialize(&ifd, resources.device,
		resources.avgpts,resources.useProc);
#else
	ifd_initialize(&ifd, resources.device,
		resources.avgpts,0);
#endif
		
	/* set the default icon */
	i=0;
	XtSetArg(
		arglist[i],				/* Arg */
		XtNiconPixmap,				/* String name */
		XCreateBitmapFromData(			/* XtArgVal */
			XtDisplay(toplevel),		/* Display* */
			XtScreen(toplevel)->root,	/* Drawable d */
			(char *)pload_bits,		/* char *data */
			pload_width,			/* unsigned */
			pload_height)); i++;		/* unsigned */
	
	XtSetValues(toplevel, arglist, i);
	
	/* get notified of a change in icon state */
	XtAddEventHandler(
			toplevel,		/* Widget w */
			StructureNotifyMask,	/* EventMask mask */
			False,			/* Boolean nonmaskable */
			CheckForIconState,	/* XtEventHandler proc */
			NULL);			/* XtPointer client_data */
			
	/******* create the topform widget *************/
	i=0;
	XtSetArg(arglist[i], XtNdefaultDistance, 0); i++;
	topform = XtCreateManagedWidget (
		"topform", formWidgetClass,
		toplevel,
		arglist, i);
	
	/******* create two more forms ***************/
	if (resources.showIn)
	{
		i=0;
		XtSetArg(arglist[i], XtNdefaultDistance, 0); i++;
		XtSetArg(arglist[i], XtNbottom, XawRubber); i++;
		XtSetArg(arglist[i], XtNresizable, True); i++;
		
		iform = XtCreateManagedWidget (
			"iform", formWidgetClass,
			topform,
			arglist, i);
	}
	if (resources.showOut)
	{
		i=0;
		XtSetArg(arglist[i], XtNdefaultDistance, 0); i++;
		XtSetArg(arglist[i], XtNtop, XawRubber); i++;
		XtSetArg(arglist[i], XtNresizable, True); i++;
		if (resources.showIn)
		{
			if (resources.horiz)
			{
				XtSetArg(arglist[i], XtNfromHoriz, iform);
				i++;
			}
			else
			{
				XtSetArg(arglist[i], XtNfromVert, iform);
				i++;
			}
		}
		oform = XtCreateManagedWidget (
			"oform", formWidgetClass,
			topform,
			arglist, i);
	}
	/* Gee, were those if statements tacked on later ? */
	if (resources.showIn)
	{
		if (resources.showLabel)
		{
	/* create a label widget */
	i=0;
	/* XtSetArg(arglist[i], XtNlabel, "starting..."); i++; */
	XtSetArg(arglist[i], XtNjustify, XtJustifyLeft); i++;
	XtSetArg(arglist[i], XtNbottom, XawChainTop); i++;
	XtSetArg(arglist[i], XtNtop, XawChainTop); i++;
	XtSetArg(arglist[i], XtNresizable, True); i++; 
	ilabel = XtCreateManagedWidget (
		"ilabel", labelWidgetClass,	/* name, widgetclass */
		iform,				/* parent */
		arglist, i );			/* arglist, num_args */
		}
		if (resources.showChart)
		{
	/* create a stripchart widget */
	i=0;
	if (resources.showLabel)
	{
		XtSetArg(arglist[i], XtNfromVert, ilabel); i++;
	}
	XtSetArg(arglist[i], XtNtop, XawChainTop); i++;
	ichart = XtCreateManagedWidget (
		"ichart", stripChartWidgetClass,/* name, widgetclass */
		iform,				/* parent */
		arglist, i );			/* arglist, num_args */
	
	/* add a callback for the stripchart */
	XtAddCallback(
		ichart,				/* widget */
		XtNgetValue,			/* callback name */
		GetIChartPoint,			/* callback proc */
		NULL );				/* client data */
		}
	}
		
	if (resources.showOut)
	{
		if (resources.showLabel)
		{
	/***********************************************************/
	/* create another label widget */
	i=0;
	/* XtSetArg(arglist[i], XtNlabel, "starting..."); i++; */
	XtSetArg(arglist[i], XtNjustify, XtJustifyLeft); i++;
	XtSetArg(arglist[i], XtNbottom, XawChainTop); i++;
	XtSetArg(arglist[i], XtNtop, XawChainTop); i++;
	XtSetArg(arglist[i], XtNresizable, True); i++;
	olabel = XtCreateManagedWidget (
		"olabel", labelWidgetClass,	/* name, widgetclass */
		oform,				/* parent */
		arglist, i );			/* arglist, num_args */
		}
	/**********************************************************/
	/* add another stripchart */
		if (resources.showChart)
		{
	i=0;
	if (resources.showLabel)
	{
		XtSetArg(arglist[i], XtNfromVert, olabel); i++;
	}
	XtSetArg(arglist[i], XtNtop, XawChainTop); i++;
	ochart = XtCreateManagedWidget (
		"ochart", stripChartWidgetClass,/* name, widgetclass */
		oform,				/* parent */
		arglist, i );			/* arglist, num_args */
	/* add a callback for the stripchart */
	XtAddCallback(
		ochart,				/* widget */
		XtNgetValue,			/* callback name */
		GetOChartPoint,			/* callback proc */
		NULL );				/* client data */
		}
	}
	
	/* manage and realize initial widget tree */
	XtRealizeWidget(toplevel);
	
	/* register interest in WM_DELETE_WINDOW */
	HandleExit();
	
	/* manually update */
	update(0,0);
	
	/* submit to the great X */
	XtAppMainLoop(context);
	return 0;
}

void update(XtPointer data, XtIntervalId *id)
{
	
	get_stat(&ifd);
	
	if ( 	(resources.showLabel && resources.showIn) ||
		(iconstate == IconicState) )
	{
		if (ifd.in_bytes == 0UL)
			sprintf(buff,"%s",resources.ncMsg);
		else
			make_label(resources.ifmt, ifd.in_bytes,
				ifd.in_rate, ifd.in_max);
	}
	
	if ( resources.showLabel && resources.showIn )	
		XtVaSetValues(ilabel, XtNlabel, buff, NULL);
	
	if (iconstate == IconicState)
		XtVaSetValues(toplevel, XtNiconName, buff, NULL);
		
	if (resources.showLabel && resources.showOut)
	{
		if (ifd.out_bytes == 0UL)
			sprintf(buff,"%s",resources.ncMsg);
		else
			make_label(resources.ofmt, ifd.out_bytes,
				ifd.out_rate, ifd.out_max);
		
		XtVaSetValues(olabel, XtNlabel, buff, NULL);
	}
	
	XtAppAddTimeOut(context, 1000*resources.update, update, NULL);
	return;
}

void GetIChartPoint( Widget w,			/* not used */
		    XtPointer client_data,	/* not used */
		    XtPointer call_data)	/* *double point to return */
{
	/* the update function handles refreshing the interface data */
	double d = ifd.in_rate / (double)resources.indiv;
	
	if (resources.logScale) d = (d < 1.0) ? 0.0 : log10(d);
	
	*(double *)call_data = d;
 	return;
}

void GetOChartPoint( Widget w,			/* not used */
		    XtPointer client_data,	/* not used */
		    XtPointer call_data)	/* *double point to return */
{
	double d = ifd.out_rate / (double)resources.outdiv;
	
	if (resources.logScale) d = (d < 1.0) ? 0.0 : log10(d);
	
	*(double *)call_data = d;
	return;
}


void HandleExit()
{
	wm_delete_window = XInternAtom(
		XtDisplay(toplevel),		/* Display *display */
		"WM_DELETE_WINDOW",		/* char *atom_name */
		False);				/* Bool only_if_exits */
	(void)XSetWMProtocols(		
		XtDisplay(toplevel),		/* Display *display */
		XtWindow(toplevel),		/* Window w */
		&wm_delete_window,		/* Atom *protocols */
		1);				/* int count */
	XtAddEventHandler(			
		toplevel,			/* Widget w */
		NoEventMask,			/* EventMask mask */
		True,				/* Bool nonmaskable */
		CheckForWMExit,			/* XtEventHandler */
		NULL);				/* XtPointer client_data */
}

void CheckForIconState(	Widget w, 
			XtPointer client_data, 
			XEvent *event, 
			Boolean *dispatch)
{
	switch(event->type)
	{
		case UnmapNotify:	iconstate = IconicState; break;
		case MapNotify:		iconstate = NormalState;
		default:		break;
	}
}

void CheckForWMExit(	Widget w, 
			XtPointer client_data, 
			XEvent *event, 
			Boolean *dispatch)
{
	if ((event->type == ClientMessage) &&
	(event->xclient.data.l[0] == wm_delete_window))
	{
		/* unload */
		ifd_stop(&ifd);
		XtDestroyApplicationContext(XtWidgetToApplicationContext(w));
		exit(EXIT_SUCCESS);
	}
	return;
}

void do_total(char *b, double total)
{
	if (total < 1024.0)
		sprintf(b, "%s%0.0f B", b, total);
	else if (total < (1024.0*1024.0))
		sprintf(b, "%s%0.2f kB", b, total/1024.0);
	else if (total < (1024.0*1024.0*1024.0))
		sprintf(b, "%s%0.2f MB", b, total/1024.0/1024.0);
	else
		sprintf(b, "%s%0.2f GB", b, total/1024.0/1024.0/1024.0);
	
	return;
}

void do_rate(char *b, double rate)
{
	if (rate < 1024.0)
		sprintf(b, "%s%0.0f B/s",b, rate);
	else if (rate < (1024.0*1024.0))
		sprintf(b, "%s%0.2f kB/s",b, rate/1024.0);
	else if (rate < (1024.0*1024.0*1024.0))
		sprintf(b, "%s%0.2f MB/s",b, rate/1024.0/1024.0);
	else
		sprintf(b, "%s%0.2f GB/s", b, rate/1024.0/1024.0/1024.0);
	return;
}

/* stores label in global 'buff' */
void make_label(char *fmt, unsigned long total, double rate, double max)
{
	char *p;
	int i;
	
	*buff = '\0';
	
	for(p=fmt;*p;p++)
	{
		if (*p == '%')
		{
			p++;
			switch(*p)
			{
				case 't':
					do_total(buff, (double)total);
					break;
				case 'r':
					do_rate(buff, rate);
					break;
				case 'M':
					do_rate(buff, max);
					break;
				case 'd':
					sprintf(buff, "%s", resources.device);
					break;
				case '%':	/* literal % */
					i = strlen(buff);
					*(buff + i) = *p;
					*(buff + i+1) = '\0';
					break;
				default:
					fprintf(stderr,
					"Unknown format specifier: %%%c\n",*p);
					break;
			}
		}
		else
		{
			/* sprintf(buf, "%s%c", buf, *p); */
			i = strlen(buff);
			*(buff + i) = *p;
			*(buff + i+1) = '\0'; 
		}
		assert(strlen(buff) < sizeof(buff));
	}
	return;
}

void Usage()
{
	fprintf(stderr,
"\n%s : show data for a network interface (ppp)\n", progver);
	fprintf(stderr,
"Usage: %s [options]\n", Progname);
	fprintf(stderr,
" where options includes, in addition to standard toolkit options,\n"
" option            default     description\n"
" -indiv <n>       : 1024      : show dividing lines every n bytes/s for in data\n"
" -outdiv <n>      : 1024      : show dividing lines every n bytes/s for out data\n"
" -logscale        : False     : use base 10 logarithmic scale for rate charts\n"
" -minscale <n>    : 1         : minimum chart scale n\n"
" -jumpscroll <n>  : 1         : jump n pixels on new data, 1 for smooth\n"
" -update <n>      : 1         : update chart every n seconds\n"
" -in              : False     : only show receive chart\n"
" -out             : False     : only show send chart\n"
" -horiz           : False     : arrange horizontally\n"
" -nolabel         : False     : don't show text labels\n"
" -nochart         : False     : don't show charts (labels only)\n"
" -average <n>     : 10        : average data over n points, 1 for no average\n"
" -device <X>      : ppp0      : monitor device X\n"
" -hl <color>      : default   : chart highlight color\n"
" -incolor <color> : red       : receive chart foreground color\n"
" -outcolor <color>: darkgreen : send chart foreground color\n"
" -fg <color>      : default   : foreground color\n"
" -bg <color>      : default   : background color\n"
" -fn <fontspec>   : default   : label font\n"
#ifdef LINUXPROC
" -noproc          : False     : don't use proc interface, use ioctl\n"
#endif
" -iformat <fmt>   : %%t %%r in  : format for inbound label\n"
" -oformat <fmt>   : %%t %%r out : format for outbound label\n"
" -ncmsg <message> : No Connection : label to show when offline\n"
"\nsee the manual page for more information\n\n");
	
	exit(EXIT_FAILURE);
}


syntax highlighted by Code2HTML, v. 0.9.1