#include "xsysstats.h"
#include "patchlevel.h"

struct base_types {
	char	*cmd_option;	/* how it is called on the command line */
	char	*window_name;	/* what we call it on the window */
	int	def_scale;	/* default scale for that graph type */
};

struct Command_Line_Options {
	char	*cmd_option;	/* how it is called on the command line */
	char	num_args;	/* Number of options for this command */
	short	pass;		/* What pass this should be processed on. */
	void	(*func)();	/* function to call when we match this.
				 * if num_args is true, than that gets passed
				 * to the function, otherwise nothing is passed
				 */
};

/* It is important that the n'th placement of this array corresponds
 * to the n'th value type in xsysstats.h (ie, 9'th value corresponds to errors)
 */
struct base_types types[NUM_TYPES] = {
{"cpu", "cpu", 100}, 
{"packets", "pkts",32}, 
{"pagei", "pagei",16},
{"swapi", "iswap",16},
{"interrupts", "intr",200}, 
{"disk", "disk",40},
{"context", "ctxt",128},
{"load1", "load1",2},
{"load5", "load5", 2},
{"load15", "load15", 2},
{"collisions", "coll",8},
{"errors", "errs",8},
{"packetsi", "pktsi",32},
{"packetso", "pktso",32},
{"apagei", "apagei",16},
{"apageo", "apageo",16},
{"pageo", "pageo",16},
{"page", "page",16},
{"swapo", "swapo",16},
{"swap", "swap",16},
{"ucpu", "ucpu", 100}, 
{"ncpu", "ncpu", 100}, 
{"scpu", "scpu", 100}, 
{"icpu", "icpu", 100}, 
};


/* Various notes:
 * we store the values in the 'points' variable.  They are stored in the
 * form we get from rstat.  To figure out where the point should be plotted,
 * it is rstat_value * vertical_size / scale.
 */

typedef enum  {Nolabel, Minimal, Normal} LabelStyle;

LabelStyle label_style=Normal;

int	screen_num, num_graphs=0, **points,point_pos=-1,sleep_time=2,
	font_height=0, num_info_lines,
	draw_baseline=FALSE,allnames=FALSE,
	num_hosts=0,localhost=-1,
	show_time=FALSE,
	time_ticks=45,		/* How many seconds a test entry to the
				 * ruler should be printed.
				 */
	split_x=1, split_y=1, 
	next_window=-1,		/* Which window to put the next
				 * graph into
				 */
	num_windows_spec=0,	/* How many graphs with unspecified
				 * windows have been processed
				 */
	split_width,split_height,  /* Width and height of the windows.
				* This is unadjusted height, which is the
				* Total drawing area.  The width and height
				* stored in the Xss_Window structure is the
				* line graph area, adjusted for labels
				*/
	*lasty,			/* Contains the last y points for each
				 * graph (once the space is allocated.)
				 * just quickens and simplifies things.
				 */
	scale_sync=FALSE,	/* True if all the graphs should have the
				 * same scale.
				 */
	solid=FALSE,		/* True if drawing solid (not line) graph
				 */
        hide_hosts=FALSE,	/* If true, don't display hostnames. */
	show_max=FALSE,		/* If true, show max value graph ever hit */
	graph_spacing=INSIDE_BORDER_WIDTH;
				/* How many pixels should be between each
				 * graph and the edge of the window.
				 */
		

unsigned long	whitepixel,blackpixel;
Display	*display;
Window	win;
XFontStruct	*font_info;
GC	gc;
XColor	background, foreground,baseline;
Pixmap	new_win;	/* used for scrolling */
char	*display_name="";		/* name to open the display with */
struct Xss_Window **windows=NULL;
struct Host_Info *hosts;

XSizeHints size_hints = { PMinSize | PWinGravity,0,0,DEFAULT_SIZE,
	DEFAULT_SIZE,DEFAULT_SIZE, DEFAULT_SIZE, 0,0,0,0,{0,0},{0,0},0,0,
	NorthWestGravity};

static unsigned long defcolors[NUM_TYPES];
char *window_name, *window_title=VERSION;

char *strdup_local(char *str)
{
	char *s;

	s = malloc(sizeof(char)*(strlen(str)+1));
	strcpy(s,str);
	return s;
}

int minimal_label_lines(int windowno)
{
    int labelwidth=0, lines_needed=0,dummy,i;
    char tmp[80];
    XCharStruct	scale_info;

    /* First is to see how many lines we need for the labels. Use a 3
     *  pixel spacing between each element.
     */
    if (windows[windowno]->title!=NULL) {
	XTextExtents(font_info, windows[windowno]->title, strlen(windows[windowno]->title), 
		     &dummy, &dummy, &dummy, &scale_info);
	labelwidth = scale_info.width+3;
    }
    if (labelwidth > windows[windowno]->width) {
	labelwidth=0;
	lines_needed++;
    }
    for (i=0; i<num_graphs; i++) {
	if (graphs[i].window!=windowno) continue;
	if (show_max)
	    sprintf(tmp, "%d/%d", graphs[i].scale, graphs[i].max_val/graphs[i].scale_mult);
	else
	    sprintf(tmp,"%d", graphs[i].scale);
	XTextExtents(font_info, tmp, strlen(tmp), 
		     &dummy, &dummy, &dummy, &scale_info);
	labelwidth += scale_info.width+3;
	if (labelwidth > windows[windowno]->width) {
	    labelwidth=scale_info.width+3;
	    lines_needed++;
	}
    }
    /* if there is leftover, it will have to go on the next line. */
    if (labelwidth) lines_needed++;
    return lines_needed;
}

/* Draws the window label when in Minimal mode. */
void draw_minimal_label(int windowno)
{
    int labelwidth=0, on_line=0,dummy,i;
    char tmp[80];
    XCharStruct	scale_info;

    /* 
     * First, draw the window title.
     */
    if (windows[windowno]->title!=NULL) {
	XTextExtents(font_info, windows[windowno]->title, strlen(windows[windowno]->title), 
		     &dummy, &dummy, &dummy, &scale_info);
	labelwidth = scale_info.width+3;
	XSetForeground(display, gc, foreground.pixel);
	XDrawImageString(display, win, gc,
		windows[windowno]->x, 
		windows[windowno]->y + windows[windowno]->height + 1 +
		font_height*show_time + font_info->max_bounds.ascent,
		windows[windowno]->title, strlen(windows[windowno]->title));
    }
    if (labelwidth > windows[windowno]->width) {
	labelwidth=0;
	on_line++;
    }
    /*
     * Now go through and draw each graph.
     */
    for (i=0; i<num_graphs; i++) {
	if (graphs[i].window!=windowno) continue;
	if (show_max)
	    sprintf(tmp, "%d/%d", graphs[i].scale, graphs[i].max_val/graphs[i].scale_mult);
	else
	    sprintf(tmp,"%d", graphs[i].scale);
	XTextExtents(font_info, tmp, strlen(tmp), 
		     &dummy, &dummy, &dummy, &scale_info);

	if (labelwidth + scale_info.width+3> windows[windowno]->width) {
	    on_line++;
	    labelwidth=0;
	}
	XSetForeground(display, gc, graphs[i].color_pixel);
	XDrawImageString(display, win, gc, 
		windows[windowno]->x + labelwidth,
	        windows[windowno]->y + windows[windowno]->height + 1 +
	        font_height * (show_time+on_line) + 
		font_info->max_bounds.ascent, tmp, strlen(tmp));
	labelwidth += scale_info.width+3;
    }
}

/* This function takes a window number as an argument, and re-draws that
 * window.
 */
void redraw_graph(int windowno)
{
	int	i,pp,x,max_x,font_line=0,fontx=0,fonts_per_line,deltax,pp1,dummy;
	char	tmp[256];
	XCharStruct	scale_info;

	XClearArea(display, win, windows[windowno]->x, windows[windowno]->y,
		split_width, split_height, False);
	windows[windowno]->redraw_needed=FALSE;

	if (draw_baseline) {
	    XSetForeground(display, gc, baseline.pixel);
	    XDrawLine(display, win, gc, windows[windowno]->x,
		windows[windowno]->y + windows[windowno]->height,
		windows[windowno]->x +  windows[windowno]->xpos,
		windows[windowno]->y+windows[windowno]->height);
	}
	if (show_time) {
	    font_line = 1; /* so that the type/scale information is one
			    * line further down, leaving room for the rule.
			    */
	    XSetForeground(display, gc, foreground.pixel);
	    for (i=0; i*time_ticks < windows[windowno]->width * sleep_time; i++) {

		if ((time_ticks * i )%60 == 0) 
		    sprintf(tmp, "%dm", time_ticks * i / 60);
		else
		    sprintf(tmp, "%d", time_ticks * i);
		XTextExtents(font_info, tmp, strlen(tmp), 
			&dummy, &dummy, &dummy, &scale_info);

		x = windows[windowno]->x + windows[windowno]->width - 1 - 
			i*time_ticks/sleep_time;

		XDrawImageString(display, win, gc, x - scale_info.width / 2,
			windows[windowno]->y + windows[windowno]->height + 2 +
			font_info->max_bounds.ascent,
			tmp, strlen(tmp));
		XDrawLine(display, win, gc, x,
		    windows[windowno]->y + windows[windowno]->height + 1, x,
		    windows[windowno]->y + windows[windowno]->height + 4);
	    }
	}
	if (label_style==Minimal) draw_minimal_label(windowno);
	fonts_per_line = windows[windowno]->width / TEXT_WIDTH;
	deltax = windows[windowno]->width / fonts_per_line;

	for (i=0; i<num_graphs; i++) {
	    if (graphs[i].window!=windowno) continue;

	    XSetForeground(display, gc, graphs[i].color_pixel);
	    if (label_style==Normal) {
		XDrawImageString(display, win, gc,
			windows[windowno]->x + fontx * deltax, 
			windows[windowno]->y + windows[windowno]->height + 1 +
			font_height*font_line + font_info->max_bounds.ascent,
			graphs[i].name, graphs[i].name_len);

		if (show_max)
		    sprintf(tmp, "%d/%d", graphs[i].scale, graphs[i].max_val/graphs[i].scale_mult);
		else
		    sprintf(tmp,"%d", graphs[i].scale);

		XTextExtents(font_info, tmp, strlen(tmp), 
			&x, &x, &x, &scale_info);
		XDrawImageString(display, win, gc, 
			windows[windowno]->x + (fontx + 1) * deltax -
			scale_info.width - 1, 
			windows[windowno]->y + windows[windowno]->height + 1 +
			font_height * font_line + font_info->max_bounds.ascent,
			tmp, strlen(tmp));
		fontx++;
		if (fontx==fonts_per_line) {
			fontx=0;
			font_line++;
		}
	    }

	    if ( windows[windowno]->xpos<windows[windowno]->width) {
		pp=0;
		max_x =  windows[windowno]->xpos-1;
	    }
	    else {
		pp = (point_pos + 1) % split_width;
		max_x = windows[windowno]->width;
	    }
	    for (x=0; x<max_x; x++) {
		pp1 = (pp +1) % split_width;
		if (solid) {
                   XFillRectangle(display,win, gc, x + windows[windowno]->x,
                        1+windows[windowno]->y + windows[windowno]->height -
                        points[i][pp] * windows[windowno]->height/
                            (graphs[i].scale * graphs[i].scale_mult),
                        1,
                        (points[i][pp] * windows[windowno]->height) /
                        (graphs[i].scale * graphs[i].scale_mult));
		} else {
		  XDrawLine(display,win, gc, x + windows[windowno]->x, 
		    windows[windowno]->y + windows[windowno]->height -
		    points[i][pp] * windows[windowno]->height/(graphs[i].scale *
		    graphs[i].scale_mult), x+windows[windowno]->x+1,
		    windows[windowno]->y + windows[windowno]->height -
		    (points[i][pp1] * windows[windowno]->height) /
		    (graphs[i].scale * graphs[i].scale_mult));
		}
		pp = pp1;
		/* point_pos %2 is to draw it every other point. */
		if (graphs[i].scale_lines>0 && !(pp % 2) &&
		    graphs[i].scale_lines<graphs[i].scale) {
			int	sline,slineinc=1;

			while ((graphs[i].scale_lines*slineinc*
			    windows[windowno]->height / graphs[i].scale) <
			    PTSSCALELINE)
				slineinc*=2;
			sline = slineinc;
			/* Yes, it is supposed to be just less than scale,
			 * not less than equal.  No point drawing a line at
			 * the top of the window.
			 */
			while (sline*graphs[i].scale_lines < graphs[i].scale) {
			    XDrawPoint(display, win, gc, x + windows[windowno]->x,
				windows[windowno]->y + windows[windowno]->height
				- (sline * graphs[i].scale_lines * 
				windows[windowno]->height) /
				graphs[i].scale + graph_spacing);
			    sline+=slineinc;
			}
		    }
	    }
	}
}

void redraw_all_graphs()
{
	int	count;

	XClearWindow(display,win);
	for (count=0;count<split_x*split_y; count++)
		redraw_graph(count);
}

void set_graph_windows()
{

	int fonts_per_line,x_pos,ypos;

	split_width = (size_hints.width - graph_spacing*(split_x+1)) /
		split_x;
	split_height = (size_hints.height - graph_spacing*(split_y+1)) /
		split_y;

	fonts_per_line = split_width/TEXT_WIDTH;

	for (x_pos=0; x_pos<split_x; x_pos++)
	    for (ypos=0; ypos<split_y; ypos++) {
		int window_num = x_pos + ypos*split_x;

		windows[window_num]->width = split_width-1;
		windows[window_num]->x = x_pos * split_width +
		    graph_spacing*(x_pos+1);

		windows[window_num]->label_lines =
		    windows[window_num]->num_graphs / fonts_per_line;
		if (windows[window_num]->num_graphs >
		    windows[window_num]->label_lines*fonts_per_line)
			windows[window_num]->label_lines++;

		windows[window_num]->y = ypos * split_height +
		    graph_spacing*(ypos+1);

		windows[window_num]->height = split_height;
		if (label_style == Normal)
		    windows[window_num]->height -= font_height *
			windows[window_num]->label_lines;
		else if (label_style == Minimal) {
		    windows[window_num]->height -= 
			font_height*minimal_label_lines(window_num);
		}
		if (show_time)
		    windows[window_num]->height -= font_height;
	    }
}
		
void resize_window(XEvent event)
{
	int i, tmp_split_width, redraw_needed=FALSE,tmp_split_height;

	/* Width changes are a little tricky.  We need
	 * malloc new structures, copy the data into
	 * it, and the free the old ones.
	 */

	tmp_split_width = (event.xconfigure.width - graph_spacing*(split_x+1)) /
		split_x;
	tmp_split_height = (event.xconfigure.height - graph_spacing*(split_y+1)) /
		split_y;

	if (split_width != tmp_split_width && 
	    event.xconfigure.width>=size_hints.min_width) {
		int	*new_points,q;

		size_hints.width = event.xconfigure.width;
		for (i=0; i<num_graphs; i++) {
			new_points =  (int *) malloc(sizeof(int) *
			    tmp_split_width);

			/* If the window is larger, we need to copy all
			 * the old points.
			 */

			if (tmp_split_width>split_width) {
			    if ( windows[0]->xpos<split_width) {
				for (q=0; q<=point_pos; q++)
					new_points[q] = points[i][q];
			    }
			    else {
				int	z=0;
				for (q=point_pos+1; q<=point_pos+split_width; q++)
				    new_points[z++]=
					points[i][q%split_width];
			    }
			}
			/* if the new window is smaller, we only want to
			 * copy the newest points.
			 */
			else {
			    int	z=0;
			    if (windows[0]->xpos<windows[0]->width) {
				int xstart;

				if (point_pos>tmp_split_width)
				    xstart=point_pos-
					tmp_split_width;
				else xstart=0;
				for (q=xstart; q<=point_pos; q++)
					new_points[z++]=points[i][q];
			    }
			    else {
				for (q=0; q<tmp_split_width; q++)
				    new_points[z++]=
					points[i][(q+split_width-
					    tmp_split_width)%
					    split_width];
			    }
			}
			free(points[i]);
			points[i] = new_points;
		}

		/* If we had more points than the new window can display,
		 * then set the number of points we have to the max the
		 * window can handle. 
		 * Otherwise, if the old window was filled up, set the
		 * next point to be drawn at the end of the old window.
		 * Remember, after the copy, the oldest point will be point
		 * #0.
		 * Otherwise, if the old window wasn't filled up and
		 * the number of points would not fill up the new
		 * window, keep the point_pos value the same.
		 */

		if (point_pos > tmp_split_width)
			point_pos = tmp_split_width-1;
		else if ( windows[0]->xpos>=split_width)
			point_pos=split_width-1;

		if (tmp_split_width< windows[0]->xpos) {
		    int tmp_cnt;

		    for (tmp_cnt=0; tmp_cnt<split_x*split_y; tmp_cnt++)
			windows[tmp_cnt]->xpos=tmp_split_width;
		}
		redraw_needed = TRUE;
	    }


	/* height change is simple.  Just update our
	 * values, and the re-draw function will scale
	 * everything to the new height.
	 */
	if (tmp_split_height !=split_height &&
	    event.xconfigure.height>=size_hints.min_height) {
		size_hints.height = event.xconfigure.height;
		redraw_needed = TRUE;
	    }

	/* This is true only if width or height actually
	 * changed in any way.
	 */
	set_graph_windows();
	if (redraw_needed) {
		redraw_all_graphs();
		XFreePixmap(display, new_win);
		new_win = XCreatePixmap(display, win, split_width+1,
		    split_height,
			DefaultDepth(display, screen_num));
	}
}


/* This function process various keypresses.  If an unknown/unsupported
 * key is pressed, it is just ignored.  As of now, only 1 character
 * codes are used, so only the first character of keypress holds any
 * relevance.
 */
void process_keypress(char *keypress)
{
    switch (keypress[0]) {
	case 'l':
	case 'L':
		label_style++;
		if (label_style > Normal) label_style=Nolabel;
		set_graph_windows();
		redraw_all_graphs();
		break;
	case 'm':
	case 'M':
		show_max = !(show_max);
		redraw_all_graphs();
		break;
	case 'r':
	case 'R':
		show_time = !(show_time);
		set_graph_windows();
		redraw_all_graphs();
		break;
	case 12: /* C-l */
		redraw_all_graphs();
		break;

#if defined(KEYQUIT)
	case 'q':
	case 'Q':
	    XCloseDisplay(display);
	    exit(0);
#endif
    }
}

void check_events()
{
	XEvent	event;
	char	keystring[10];

	while (XEventsQueued(display,QueuedAfterFlush)) {
	    XNextEvent(display, &event);

	    switch (event.type) {
		case Expose:
			if (event.xexpose.count==0)
				redraw_all_graphs();
			break;

		case ConfigureNotify:
			resize_window(event);
			break;

		case KeyPress:
			keystring[0]=0;
			XLookupString((XKeyEvent *) &event, keystring,10, NULL, NULL);
			process_keypress(keystring);
			break;

		/* We don't really care about these, but they might
		 * be generated with the StructureNotifyMask.
		 */
		case CirculateNotify:
		case GravityNotify:
		case MapNotify:
		case ReparentNotify:
		case UnmapNotify:
		case MappingNotify:
			break;

		default:
			printf("unknown event: %d\n", event.type);
	    }
	}
}


/* This function updates the window.  Call the redraw_graph function
 * if the window is marked for redraw.  Otherwise, scroll the
 * window (if necessary).  Then, draw the various lines.
 */

void update_window(int windowno)
{
	int	count;

	if (windows[windowno]->redraw_needed) {
	    redraw_graph(windowno);
	    if ( windows[windowno]->xpos>windows[windowno]->width)
		 windows[windowno]->xpos--;
	}
	/* Need to scroll the window over */
	else if (windows[windowno]->xpos>windows[windowno]->width) {
#if 0
	    XCopyArea(display, win, new_win, gc, windows[windowno]->x+1,
		windows[windowno]->y, windows[windowno]->width, windows[windowno]->height+1,
		0, 0);
	    XClearArea(display, win, windows[windowno]->x +
		windows[windowno]->width -1, windows[windowno]->y,
		2, windows[windowno]->height+1, False);
	    XCopyArea(display, new_win, win, gc, 0, 0, 
		windows[windowno]->width, windows[windowno]->height+1, 
		windows[windowno]->x, windows[windowno]->y);
#else
	    XCopyArea(display, win, win, gc, windows[windowno]->x+1,
		windows[windowno]->y, windows[windowno]->width, windows[windowno]->height+1,
		windows[windowno]->x, windows[windowno]->y);
	    XClearArea(display, win, windows[windowno]->x +
		windows[windowno]->width , windows[windowno]->y,
		1, windows[windowno]->height+1, False);
#endif
	    windows[windowno]->xpos--;
	}
        if (draw_baseline) {
	    XSetForeground(display,gc, baseline.pixel);
	    XDrawPoint(display,win,gc, windows[windowno]->x + windows[windowno]->xpos,
		windows[windowno]->y + windows[windowno]->height);
	}

	for (count=0; count<num_graphs; count++) {
	    if (graphs[count].window != windowno) continue;

	    XSetForeground(display, gc, graphs[count].color_pixel);

	    if (solid) {
              XFillRectangle(display,win, gc,
                  windows[windowno]->x + windows[windowno]->xpos-1,
                  1+windows[windowno]->y + windows[windowno]->height -
                  (points[count][point_pos] * windows[windowno]->height) /
                  (graphs[count].scale * graphs[count].scale_mult),
                  1,
                  (points[count][point_pos] * windows[windowno]->height) /
                  (graphs[count].scale * graphs[count].scale_mult));
	    } else {
		XDrawLine(display,win, gc,
		    windows[windowno]->x + windows[windowno]->xpos-1,
		    windows[windowno]->y + windows[windowno]->height-
		    lasty[count] * windows[windowno]->height/(graphs[count].scale *
		    graphs[count].scale_mult),
		    windows[windowno]->x + windows[windowno]->xpos,
		    windows[windowno]->y +windows[windowno]->height - 
		    (points[count][point_pos] *windows[windowno]->height) /
		    (graphs[count].scale * graphs[count].scale_mult));
	    }
	    lasty[count] = points[count][point_pos];

	    /* point_pos %2 is to draw it every other point. */
	    if (graphs[count].scale_lines>0 && !(point_pos % 2) &&
		graphs[count].scale_lines<graphs[count].scale) {

		int	sline,slineinc=1;

		/* Keep is so that the number of horizontal lines is
		 * at least somewhat reasonable.
		 */
		while ((graphs[count].scale_lines*slineinc*windows[windowno]->height / graphs[count].scale) < PTSSCALELINE)
			slineinc*=2;
		sline = slineinc;

		/* Yes, it is supposed to be just less than scale,
		 * not less than equal.  No point drawing a line at
		 * the top of the window.
		 */
		while (sline*graphs[count].scale_lines < graphs[count].scale) {
		    XDrawPoint(display, win, gc, windows[windowno]->x+ windows[windowno]->xpos -1,
			windows[windowno]->y + windows[windowno]->height - (sline *
			graphs[count].scale_lines * windows[windowno]->height) /
			graphs[count].scale + graph_spacing);
		    sline+=slineinc;
		}
	    }
	}
	windows[windowno]->xpos++;
}

void graph_loop()
{
	int	i;

	set_graph_windows();
	points =(int **)  malloc(sizeof(int * )  * num_graphs);
	lasty = (int *) malloc(sizeof(int) * num_graphs);

	for (i=0; i<num_graphs; i++) {
		points[i] =  (int *) malloc(sizeof(int) *
		    split_width);
		graphs[i].running_avg = (graphs[i].scale+1)*split_width *
			graphs[i].scale_mult;
		if (!hide_hosts && 
		    graphs[i].host_offset != localhost || allnames) {
			strcat(graphs[i].name,"@");
			strcat(graphs[i].name,hosts[graphs[i].host_offset].name);
		}
		graphs[i].name_len = strlen(graphs[i].name);
		graphs[i].max_val=0;
	}

	/* Create a pixmap for scrolling just about the size of the
	 * window. size_hints.height is used instead of the height
	 * variable because the labels can be turned on and off.  So
	 * we create a pixmap that is large enough in case the labels
	 * are turned off.
	 */
	new_win = XCreatePixmap(display, win, split_width-1,size_hints.height+
		graph_spacing, DefaultDepth(display, screen_num));

	set_first_values();
	sleep(sleep_time);

	set_values();		/* this call sets the starting point */
	sleep(sleep_time);
	for (i=0; i<split_x * split_y; i++)
	     windows[i]->xpos=1;

	for (i=0; i<num_graphs; i++)
		lasty[i] = points[i][0];

	while (1) {
	    /* sets the values for all the graphs */
	    set_values();
	    for (i=0; i <split_x * split_y; i++)
		update_window(i);

	    /* Sleep for 1 second at a time, then check for events.
	     * This is done so that there will never be more than
	     * 1 second before events are handled.  The program does
	     * assume that all other processing time is negligable
	     * in respect to the sleep amount
	     */
	    for (i=0; i<sleep_time; i++) {
		sleep(1);
		check_events();
	    }
	}
}

void usage()
{
	printf("%s Usage:\n",VERSION);
	puts("-allnames		Display names of all hosts, even localhost");
	puts("-background color 	background color");
	puts("-baseline color		draw baseline of color 'color'");
	puts("-border number		spacing between graphs and window edges");
	puts("-color color		color of last graph specified by -type");
	puts("-defcolor graphtype color	Sets default color for all types of graphtype");
	puts("-display displayname	X server to contact");
	puts("-foreground color	foreground color");
	puts("-geometry geom		size (in pixels) and position");
	puts("-hidelabels		do not display labels at bottom of graph.");
	puts("-hidehosts		do not add hostnames to labels.");
	puts("-host string		machine to report statistics on");
	puts("-link graph		link scale of last graph to graph specified.");
	puts("			For graphs on remote hosts, use graph@host from.");
	puts("-min number		minimum scale of last graph specified by -type");
	puts("-max number		maximum scale of last graph specified by -type");
	puts("-name string		Set the X application resource name");
	puts("-ruler number		draw a ruler below the baseline, with time markings");
	puts("			every 'number' seconds.");
	puts("-samescale		Synchronize scales of similar graphs");
	puts("-sample number		how many seconds between each update");
	puts("-scale number		draw dotted horizontal lines of the last graph");
	puts("			at value specified.");
	puts("-showmax			show running maximum value");
	puts("-solid			Draw a solid graph.");
	puts("-split WxH		Split the window into W width and H height sections.");
	puts("-title string		Set the window manager title");
	puts("-type string		type of graph to display: Choices of cpu,");
	puts("			packets, pagei, swap, interrupts, disk, context, load,");
	puts("			collisions, errors, ipackets, opackets, apagei, apageo, pageo.");
	puts("-window number		Window number to plot graph in.");
	puts("-wtitle name		Sets the default short subwindow name.");
	
	exit(1);
}

void fatal(char *message)
{
	fprintf(stderr,"%s\n", message);
	exit(1);
}

void set_up_graph(char *graphtype)
{
	int	i;
	char	*host;
	void	set_graph_host(char *);

	host = strchr(graphtype,'@');
	if (host!=NULL) {
		*host='\0';
		host++;
	}
	for (i=0; i < NUM_TYPES; i++) {
	    if (!strcmp(graphtype, types[i].cmd_option)) {
		if (num_graphs==0)
			graphs = (struct graph_info *)
				malloc(sizeof(struct graph_info));
		else
			graphs = (struct graph_info *)
			    realloc(graphs, sizeof(struct graph_info)*
				(num_graphs+1));
		graphs[num_graphs].type = i;
		graphs[num_graphs].scale = types[i].def_scale;
		graphs[num_graphs].true_scale = types[i].def_scale;
		graphs[num_graphs].color_pixel = defcolors[i];
		strcpy(graphs[num_graphs].name,types[i].window_name);
		graphs[num_graphs].host_offset = -1;
		graphs[num_graphs].scale_lines = 0;
		graphs[num_graphs].link = num_graphs;
		if (next_window!=-1)
			graphs[num_graphs].window = next_window;
		else {
			graphs[num_graphs].window = num_windows_spec++;
			if (num_windows_spec > (split_x * split_y-1))
				num_windows_spec--;
		}
		if (i==CPU || i==UCPU || i==NICECPU || i==SCPU || i==ICPU) {
			graphs[num_graphs].max_scale = 100;
			graphs[num_graphs].min_scale = 100;
		}
		else {
			graphs[num_graphs].max_scale = 0x7fffffff;
			graphs[num_graphs].min_scale = 2;
		}
		if (i>=LOAD1 && i<=LOAD15)
			graphs[num_graphs].scale_mult = LOAD_FACTOR;
		else
			graphs[num_graphs].scale_mult = 1;

		break;
	    }
	}
	if (i>=NUM_TYPES) {
		fprintf(stderr,"Unknown graph type: %s\n", graphtype);
		exit(1);
	}
	num_graphs++;
	if (host!=NULL)
		set_graph_host(host);
}

void parse_geometry(char *geostring)
{
	int	geomask;

	/* Would be nice if in the size_hints structure
	 * declaration in the X11 header files that width
	 * and height would be declared the same as what
	 * most calls use..
	 */
	geomask = XParseGeometry(geostring, &size_hints.x,
	    &size_hints.y, (unsigned *)&size_hints.width,
	    (unsigned *)&size_hints.height);

	size_hints.min_width = split_x * (DEFAULT_SIZE + graph_spacing -1);
	size_hints.min_height = split_y  * (DEFAULT_SIZE + graph_spacing -1);
	if (size_hints.min_width > size_hints.width)
	    size_hints.width = size_hints.min_width;
	if (size_hints.min_height > size_hints.height)
	    size_hints.height = size_hints.min_height;

	if (geomask & XNegative)
	    size_hints.x += WidthOfScreen(ScreenOfDisplay(display,screen_num))
		 - size_hints.width;
	if (geomask & YNegative)
		size_hints.y += HeightOfScreen(ScreenOfDisplay(display,screen_num))
		 - size_hints.height;
	if (geomask & (XValue | YValue))
		size_hints.flags |= USPosition;
	if ((geomask & XNegative) && (geomask & YNegative))
		size_hints.win_gravity = SouthEastGravity;
	else if (geomask & XNegative)
		size_hints.win_gravity = NorthEastGravity;
	else if (geomask & YNegative)
		size_hints.win_gravity = SouthWestGravity;
}



void set_display(char *option)
{
	display_name = option;
}

void set_background_color(char *color)
{
    XColor	exact;

    if (!XAllocNamedColor(display, DefaultColormap(display, screen_num),
	color, &background,&exact))
		    fprintf(stderr,"Could not allocated color %s\n",color);
}

void set_foreground_color(char *color)
{
    XColor	exact;

    if (!XAllocNamedColor(display, DefaultColormap(display, screen_num),
	color, &foreground,&exact))
	    fprintf(stderr,"Could not allocated color %s\n",color);
}

void set_baseline(char *color)
{
    XColor	exact;

    if (!XAllocNamedColor(display, DefaultColormap(display, screen_num),
	color, &baseline,&exact))
	    fprintf(stderr,"Could not allocated color %s\n",color);
    else draw_baseline = TRUE;
}

void set_sample_time(char *val)
{
	sleep_time = atoi(val);
	if (sleep_time < 1)
		usage();
}

void set_allnames() {	allnames=TRUE;	}
void set_samescale() {	scale_sync = TRUE; }
void set_hidelabels() {	label_style = Nolabel; }
void set_solid() {	solid=TRUE; }
void set_hidehosts() { hide_hosts = TRUE; }
void set_name(char *val) {     window_name = val; }
void set_title(char *val) {     window_title = val; }
void set_showmax() { show_max = TRUE; }

void set_ruler(char *val)
{
    if (show_time)
	fatal("Only one -ruler arguement may be specified.");
    else {
	time_ticks = atoi(val);
	show_time = TRUE;
	if (time_ticks < 0)
	    fatal("Time value for -ruler must be at least 10.");
    }
}

void set_border(char *val)
{
	graph_spacing = atoi(val);

	if (graph_spacing<1 || graph_spacing>50) 
		fatal("-space value must be between 1 and 50.");
}

void set_split (char *split)
{
    int tmpx, tmpy;

    if (sscanf(split,"%dx%d", &tmpx, &tmpy)!=2)
	fatal("Error in specifying widthxheight split argument");

    if (tmpx<1 || tmpy<1)
	fatal("Split width and height must be at least 1.");

    if (windows!=NULL) {
	int i;
	for (i=0; i<split_x*split_y; i++)
	    free(windows[i]);
	free(windows);
    }

    split_x = tmpx;
    split_y = tmpy;
    windows = malloc(sizeof(struct Xss_Window*) * split_x * split_y);

    for (tmpx = 0; tmpx < split_x*split_y; tmpx++) {
	windows[tmpx] = malloc(sizeof(struct Xss_Window) * split_x * split_y);
	windows[tmpx]->num_graphs = 0;
	windows[tmpx]->redraw_needed = FALSE;
	windows[tmpx]->title = NULL;
    }
}

void set_graph_color(char *color)
{
    XColor	exact, screen_def;

    if (!XAllocNamedColor(display, DefaultColormap(display, screen_num),
	color, &screen_def,&exact))
	    fprintf(stderr,"Could not allocated color %s\n",color);
    else {
	if (!num_graphs) 
		fatal("No graphs have been specified");
	else
		graphs[num_graphs-1].color_pixel =
			screen_def.pixel;
    }
}

void set_graph_min(char *val)
{
	if (!num_graphs)
		fatal("No graphs have been specified");
	else {
		graphs[num_graphs-1].min_scale = atoi(val);
		if (graphs[num_graphs-1].scale< graphs[num_graphs-1].min_scale) 
			graphs[num_graphs-1].true_scale = graphs[num_graphs-1].scale = graphs[num_graphs-1].min_scale;
		if (graphs[num_graphs-1].min_scale < 2)
			graphs[num_graphs-1].min_scale = 2;
	}
}

void set_graph_max(char *val)
{
	if (!num_graphs) 
		fatal("No graphs have been specified");
	else {
		graphs[num_graphs-1].max_scale = atoi(val);
		if (graphs[num_graphs-1].scale> graphs[num_graphs-1].max_scale) 
			graphs[num_graphs-1].true_scale = graphs[num_graphs-1].scale = graphs[num_graphs-1].max_scale;
		if (graphs[num_graphs-1].max_scale < graphs[num_graphs-1].min_scale)
			graphs[num_graphs-1].max_scale =
				graphs[num_graphs-1].min_scale;
	}
}

void set_graph_host(char *host)
{
    if (!num_graphs)
	fatal("No graphs have been specified");
    else {
	if (!num_hosts) {
		hosts = (struct Host_Info*) malloc(sizeof(struct Host_Info));
		hosts[0].name = strdup_local(host);
#ifdef USE_NEW_RSTAT
		hosts[0].client=NULL;
#endif
		graphs[num_graphs-1].host_offset = 0;
		num_hosts++;
	}
	else {
		int s;

		for (s=0; s<num_hosts; ++s)
			if (!strcmp(hosts[s].name,host)) break;
		if (s==num_hosts) {
		    hosts =(struct Host_Info *)realloc(hosts, (num_hosts+1) * sizeof(struct Host_Info));
		    hosts[num_hosts].name = strdup_local(host);
#ifdef USE_NEW_RSTAT
		    hosts[num_hosts].client=NULL;
#endif
		    num_hosts++;
		}
		graphs[num_graphs-1].host_offset = s;
	}
    }
}

void set_graph_scale(char *val)
{
	if (!num_graphs)
		fatal("No graphs have been specified");
	else
		graphs[num_graphs-1].scale_lines = atoi(val);
}

void set_link(char *link)
{

    if (num_graphs<2)
	fatal("Must have selected at least two graphs before you can link\ngraphs together.");
    else {
	char *graph=link,*host;
	int	tmp=0,graph_type=0;


	/* Break down the arguement so the graph is the graph name
	 * and host is the host name.  Set the host name to the
	 * local host name if the host name was not specified.
	 */
	host = strchr(link,'@');
	if (host!=NULL) {
		*host='\0';
		host++;
	}
	for (tmp=0; tmp<NUM_TYPES; tmp++)
	    if (!strcmp(graph, types[tmp].cmd_option)) {
		graph_type = tmp;
		break;
	    }
	if (tmp>=NUM_TYPES) {
		fprintf(stderr,"Could not match type to %s\n",graph);
		exit(1);
	}
	for (tmp=0; tmp<(num_graphs-1); tmp++)
	     if (graphs[tmp].type == graph_type) {
		if ((host==NULL && graphs[tmp].host_offset == -1) ||
		    (graphs[tmp].host_offset!=-1 && !strcmp(host, hosts[graphs[tmp].host_offset].name)))
			break;
	     }

	if (tmp==(num_graphs-1)) {
		fprintf(stderr, "Could not find match for %s\n", link);
		exit(1);
	}
	else {
		while (graphs[tmp].link !=tmp)
		    tmp = graphs[tmp].link;
		graphs[num_graphs-1].link = tmp;
		graphs[num_graphs-1].scale = graphs[tmp].scale;
	}
    }
}


void set_window_title(char *val)
{
    int winnum=next_window;

    if (winnum==-1) {
	winnum=0;
    }
    if (winnum>split_x*split_y) {
	fprintf(stderr,"Internal error - next_window is too high (%d)\n" , next_window);
	return;
    }
    if (windows[winnum]->title)
	free(windows[winnum]->title);
    windows[winnum]->title = strdup_local(val);
    label_style = Minimal;
}
	

void set_window(char *val)
{
    int tmp_window;

    tmp_window=atoi(val);
    if (tmp_window<1 || tmp_window>split_x*split_y)
	fatal("Invalid window number.");

    next_window=--tmp_window;
}

void set_def_graph_color(char *graphtype, char *color)
{
    int i;
    XColor	exact, screen_def;

    for (i=0; i < NUM_TYPES; i++) {
	if (!strcmp(graphtype, types[i].cmd_option)) {

	    if (!XAllocNamedColor(display, DefaultColormap(display, screen_num),
				  color, &screen_def,&exact))
		fprintf(stderr,"Could not allocated color %s\n",color);
	    else {
		defcolors[i]=screen_def.pixel;
	    }
	    break;
	}
    }
    if (i>=NUM_TYPES) {
	fprintf(stderr,"Unknown graph type: %s\n", graphtype);
    }
}


/* This table is set up by pass number the options are processed.
 * As of now, this order is not used, but it makes things a bit clearer
 */
struct Command_Line_Options options[]= {
{"-display", 1, 1, set_display},	/* display needs to be set before */
					/* anything else */

{"-name", TRUE, 2, set_name},
{"-title", TRUE, 2, set_title},
{"-split", 1, 2, set_split},
{"-background", 1, 2, set_background_color},
{"-bg", 1, 2, set_background_color},
{"-foreground", 1, 2, set_foreground_color},
{"-fg", 1, 2, set_foreground_color},
{"-baseline", 1, 2, set_baseline},
{"-sample", 1, 2, set_sample_time},
{"-allnames", 0, 2, set_allnames},
{"-showmax", 0, 2, set_showmax},
{"-samescale", 0, 2, set_samescale},
{"-hidelabels", 0, 2, set_hidelabels},
{"-ruler", 1, 2, set_ruler},
{"-border", 1, 2, set_border},
{"-defcolor",2, 2, set_def_graph_color},
{"-hidehosts", 0, 2, set_hidehosts},

{"-geometry", 1, 3, parse_geometry},		/* Geometry needs to be set */
						/* after split */

/* Stage 4 is really the graphs and their options */
{"-type", 1, 4, set_up_graph},
{"-color", 1, 4, set_graph_color},
{"-min", 1, 4, set_graph_min},
{"-max", 1, 4, set_graph_max},
{"-host", 1, 4, set_graph_host},
{"-scale", 1, 4, set_graph_scale},
{"-link", 1, 4, set_link},
{"-window", 1, 4, set_window},
{"-wtitle", 1, 4, set_window_title},
{"-solid", 0, 4, set_solid}
};

void parse_args(int argc, char *argv[], int pass)
{
    int i, on_arg=1;

    while (on_arg<argc) {
	for (i=0; i<sizeof(options)/sizeof(struct Command_Line_Options); i++) {
	    if (!strcmp(options[i].cmd_option, argv[on_arg])) {
		/* Found a matching option, but should not be processed on
		 * this pass.  Just skip over it
		 */
		if (options[i].pass != pass) {
		    on_arg += options[i].num_args+1;
		    break;
		}
		if (options[i].num_args) {
		    if ((on_arg+options[i].num_args)==argc) {
			fprintf(stderr,"%s requires an argument.\n", options[i].cmd_option);
			exit(1);
		    }
		    else {
			if (options[i].num_args==1)
				options[i].func(argv[on_arg+1]);
			if (options[i].num_args==2)
				options[i].func(argv[on_arg+1],argv[on_arg+2]);
			on_arg +=options[i].num_args+1;
		    }
		}
		else {
			options[i].func();
			on_arg++;
		}
		break;
	    }
	}
	if (i==sizeof(options)/sizeof(struct Command_Line_Options)) {
		fprintf(stderr,"Unknown option: %s\n", argv[on_arg]);
		exit(1);
	}
    }
}

static void set_min_geometry()
{
	size_hints.min_width = split_x * (DEFAULT_SIZE + graph_spacing) +
		graph_spacing;
	size_hints.min_height = split_y  * (DEFAULT_SIZE + graph_spacing) +
		graph_spacing;
	if (size_hints.min_width > size_hints.width)
	    size_hints.width = size_hints.min_width;
	if (size_hints.min_height > size_hints.height)
	    size_hints.height = size_hints.min_height;
}


int main( argc, argv )
	int             argc;
	char           *argv[];

{
	XGCValues	xgcvalues;
	XWMHints	wm_hints;
	XClassHint	class_hints;
	XTextProperty	wm_title;
	XSetWindowAttributes	window_attributes;
	int		i;
	char		*cp;

	/* localhost needs to be static because we set a pointer to it. */
	static char	localhostname[MAXHOSTNAMELEN];

	if (argc==1) usage();

	/* By convention, the name of the executable is used as the default
	 * application name.
	 */
	for ( window_name = argv[0] + strlen(argv[0]); window_name > argv[0];
	     --window_name )
	    if (*window_name == '/') {
		 ++window_name;
		 break;
	    }


	/* Find the display (and screen number) first.  Once we have that
	 * information, we can process the other arguements better as
	 * we read them.
	 */

	parse_args(argc, argv, 1);
	if ((display=XOpenDisplay(display_name))==NULL) {
		fprintf(stderr, "Unable to open display: %s\n",
			XDisplayName(display_name));
		exit(1);
	}
	screen_num = DefaultScreen(display);

	whitepixel = WhitePixel(display, screen_num);
	blackpixel = BlackPixel(display, screen_num);

	foreground.pixel = blackpixel;
	foreground.red = foreground.blue = foreground.green = 0;
	background.pixel = whitepixel;
	background.red = foreground.blue = foreground.green = 65535;
	for (i=0; i<NUM_TYPES; i++)
		defcolors[i]=foreground.pixel;

	windows = malloc(sizeof(struct Xss_Window*));
	windows[0]= malloc(sizeof(struct Xss_Window) * split_x * split_y);
	windows[0]->num_graphs = 0;
	windows[0]->redraw_needed = FALSE;
	windows[0]->title=NULL;

	parse_args(argc, argv,2);
	parse_args(argc, argv,3);
	parse_args(argc, argv,4);

	if (!num_graphs) {
		fprintf(stderr,"At least one graph type needs to be specified\n");
		exit(1);
	}


/* Following sets up the graphs where a hostname was not specified.  First
 * find out if the local host was specified for at least one graph.
 * Then, go through the graph types.  If there are graphs in which a
 * host has not been specified, then set it the localhost, and allocate
 * the space for it, if that is required.
 */

	gethostname(localhostname, MAXHOSTNAMELEN);
	set_min_geometry();

	for (i=0; i<num_hosts; i++) {
		if (!strcmp(hosts[i].name,localhostname)) {
			localhost = i;
			break;
		}
	}
	for (i=0; i<num_graphs; i++) {
		if (scale_sync) {
		    int j;
		    for (j=i+1; j<num_graphs; j++) {
			if (graphs[j].type == graphs[i].type &&
			    graphs[j].link == j) {
				graphs[j].link = i;
				graphs[j].scale = graphs[i].scale;
			    }
		    }
		}
		if (graphs[i].window>=split_x*split_y)
		    fatal("Graph window larger than actual number of windows");
		windows[graphs[i].window]->num_graphs++;

		if (graphs[i].host_offset == -1) {
		    if (localhost==-1) {
			if (num_hosts==0)
			    hosts = (struct Host_Info *)malloc(sizeof(struct Host_Info));
			else 
			    hosts=(struct Host_Info *)realloc(hosts, (1+num_hosts) * sizeof(struct Host_Info));
			hosts[num_hosts].name = localhostname;
#ifdef USE_NEW_RSTAT
			hosts[num_hosts].client = NULL;
#endif
			localhost = num_hosts++;
		    }
		    graphs[i].host_offset = localhost;
		}
	}

	window_attributes.background_pixel = background.pixel;
	window_attributes.backing_store = WhenMapped;
	win = XCreateWindow(display, RootWindow(display, screen_num),
		size_hints.x, size_hints.y, size_hints.width, size_hints.height,
		0, CopyFromParent, InputOutput,  CopyFromParent,
		CWBackPixel | CWBackingStore, &window_attributes);

	XSelectInput(display, win, StructureNotifyMask | ExposureMask |
		KeyPressMask);


	if ((font_info = XLoadQueryFont(display, FONT))==NULL) {
		fprintf(stderr,"Unable to load font %s\n",FONT);
		exit(1);
	}
	font_height=font_info->max_bounds.ascent +
		font_info->max_bounds.descent;

	xgcvalues.foreground = foreground.pixel;
	xgcvalues.background = background.pixel;
/*	xgcvalues.line_width = 1;*/
	xgcvalues.line_width = 0;
	
	xgcvalues.graphics_exposures = False;
	xgcvalues.font = font_info->fid;

	/* create the various graphic contexts we need. gc and gc_color
	differ only in the we only change the colors on the gc_color context,
	and thus, it is only used for brick drawing.  gc_color really has no
	use on a black & white system */

	gc = XCreateGC(display, win, GCFont | GCLineWidth |
		GCForeground | GCBackground | GCGraphicsExposures, &xgcvalues);

	wm_hints.flags = InputHint;
	wm_hints.input = True;

	class_hints.res_name = window_name;
	class_hints.res_class =  "XSysStats";

	XStringListToTextProperty(&window_title, 1, &wm_title);
	XSetWMProperties(display, win, &wm_title, &wm_title,
		argv,argc, &size_hints, &wm_hints, &class_hints);


	XMapWindow(display, win);
	XFlush(display);

	/* initialize random (used to determine next stage) */
	graph_loop();
	exit(0);

}



syntax highlighted by Code2HTML, v. 0.9.1