/* Purpose: Draw a polygon using all points in a file */
/* Compile with:
   gcc -g -L /usr/X11R6/lib -lX11 -lm -o figdrawwin figdrawwin.c
*/
#include <X11/Xlib.h>
#include <X11/Xutil.h>
#include <X11/Xos.h>
#include <X11/Xatom.h>

#include <stdio.h>

#include "bitmaps/icon_bitmap"

#define TOO_SMALL 0
#define BIG_ENOUGH 1
#define TRUE 1
#define FALSE 0

/* Global structure declaration */
struct point
  {
    float x;
    float y;
	 struct point *nx;
  };

/* function prototypes */
void freedata(struct point *p);
void getGC(Window win, GC *gc, XFontStruct *font_info);
void load_font(XFontStruct ** font_info);
void draw_line( Window win, GC gc, 
	unsigned int window_width, unsigned int window_height,
	struct point *p1, struct point *p2);
void draw_axes( Window win, GC gc, 
	unsigned int window_width, unsigned int window_height);
void TooSmall( Window win, GC gc, XFontStruct *font_info);

/* These are used as arguments to nearly every Xlib routine, so it
 * saves routine arguments to declare them global; if there were
 * additional source files, they would be declared extern there. */
Display *display;
int screen_num;

/* progname is the string by which this program was invoked; this
 * is global because it is needed in most application functions. */
static char *progname;

void main(int argc, char *argv[])
{
	Window win;
	unsigned int width, height;	/* Window size */
	int x, y;	/* Window position */
	unsigned int border_width = 4;	/* Four pixels */
	unsigned int display_width, display_height;
	unsigned int icon_width, icon_height;
	char *window_name = "Plane Figure Draw X Window Program";
	char *icon_name = "figdrawwin";
	Pixmap icon_pixmap;
	XSizeHints *size_hints;
	XIconSize *size_list;
	XWMHints *wm_hints;
	XClassHint *class_hints;
	XTextProperty windowName, iconName;
	int count;
	XEvent report;
	GC gc;
	XFontStruct *font_info;
	char *display_name = NULL;
	int window_size = 0;	/* BIG_ENOUGH or TOO_SMALL to
									* display contents */
	char keybuf[10]; /* buffer for XLookupString() */
	struct point *figure = NULL;
	struct point *tptr, *newptr;
	float tempx, tempy;
	int howmany;
	int num_points = 0;
	FILE *fpin;
	char infile[80]; /* filename */

	/* read in data for coordinates */
	if (argc == 1)
	{
		printf("No data file provided.\n");
		printf("Usage: figdrawwin datafilename\n");
		exit(1);
	}
	strcpy(infile, argv[1]);
	fpin = fopen(infile, "r");
	/* if fpin is NULL, it was unable to open file for reading */
	if ( !fpin)
	{
		printf("%s does not exist.\n", infile);
		exit(1);
	}
	do	/* keep reading & saving until no more coordinate pairs found */
	{
		howmany = fscanf(fpin, "%f%f", &tempx, &tempy);
		/* if BOTH coordinates gotten */
		if (howmany == 2)
		{
			/* get RAM for new struct; newptr gets address */
			newptr = (struct point *)malloc(sizeof(struct point));
			/* bulletproofing for running out of RAM */
			if( !newptr )
			{
				printf("Ran out of memory after %d points.\n", num_points);
				/* free up already allocated memory */
				freedata(figure);
				exit(1);
			}
			/* store coordinates */
			newptr->x = tempx;
			newptr->y = tempy;
			/* pointer to NEXT struct is NULL (no next exists yet) */
			newptr->nx = NULL;
			/* if no points yet, attach to base pointer */
			if(figure == NULL)
				figure = newptr;
			else
			{
				/* find last struct */
				tptr = figure;
				while(tptr->nx)
					tptr = tptr->nx;
				/* attach to last struct */
				tptr->nx = newptr;
			}
			num_points++;
		}
	} while (howmany == 2);
	fclose(fpin);

	/* if pointer is NULL, no coordinates found -- so exit */
	if (!figure)
	{
		printf("There are no points in %s.\n", infile);
		exit(1);
	}

	/* print the set of coordinates */
	printf("The %d points are:\n", num_points);

	/* set temp ptr to start of list */
	tptr = figure;
	do {
		printf( "(%.2f, %.2f)\n", tptr->x, tptr->y);
		tptr = tptr->nx;
	} while(tptr);

	printf("Take a look at the figure in the window!\n");
	printf("Click in the window to terminate the program!\n");

	progname = argv[0];

	if (!(size_hints = XAllocSizeHints())) {
		fprintf(stderr, "%s: failure allocating memory\n", progname);
		exit(0);
	}

	if (!(wm_hints = XAllocWMHints())) {
		fprintf(stderr, "%s: failure allocating memory\n", progname);
		exit(0);
	}

	if (!(class_hints = (XClassHint *)XAllocClassHint())) {
		fprintf(stderr, "%s: failure allocating memory\n", progname);
		exit(0);
	}

	/* Connect to X server */
	if ( (display = XOpenDisplay(display_name)) == NULL)
	{
		(void)fprintf( stderr, "%s: cannot connect to X server %s\n",
			progname, XDisplayName(display_name) );
		exit(-1);
	}

	/* Get screen size from display structure macro */
	screen_num = DefaultScreen(display);
	display_width = DisplayWidth(display, screen_num);
	display_height = DisplayHeight(display, screen_num);

	/* Note that in a real application, x and y would default
	 * to 0 but would be settable from the command line or
	 * resource database */
	x = y = 0;

	/* Size window with enough room for text */
	width = display_width/3;
	height = display_height/4;

	/* Create opaque window */
	win = XCreateSimpleWindow(display, RootWindow(display, screen_num),
			x, y, width, height, border_width, 
			BlackPixel(display, screen_num), WhitePixel(display, screen_num));

	/* Get available icon sizes from window manager */
	if (XGetIconSizes(display, RootWindow(display, screen_num),
				&size_list, &count) == 0)
		; /* (void)fprintf( stderr, 
			"%s: Window manager didn't set icon sizes - using default.\n", 
			progname);*/
	else {
		;
		/* A real application would search through size_list
		 * here to find an acceptable icon size and then
		 * create a pixmap of that size; this requires that
		 * the application have data for several sizes of icons */
	}

	/* Create pixmap of depth 1 (bitmap) for the icon */
	icon_pixmap = XCreateBitmapFromData( display, win,
				icon_bitmap_bits, icon_bitmap_width, icon_bitmap_height);

	/* Set size hints for window manager; the window manager
	 * may override these settings */

	/* Note that in a real application, if size or position
	 * were set by the user, the flags would be USPosition
	 * USSize and these would override the window manager's
	 * preferences for this window */

	/* x, y, width, and height hints are now taken from
	 * the actual settings of the window when mapped; note
	 * that PPosition and PSize must be specified anyway. */

	size_hints->flags = PPosition | PSize | PMinSize;
	size_hints->min_width = 300;
	size_hints->min_height = 200;

	/* These calls store window_name and icon_name into
	 * XTextProperty structures and set their other fields properly */
	if (XStringListToTextProperty(&window_name, 1, &windowName) == 0) {
		(void)fprintf( stderr, 
			"%s: structure allocation for windowName failed.\n", progname);
		exit(-1);
	}

	if (XStringListToTextProperty(&icon_name, 1, &iconName) == 0) {
		(void)fprintf( stderr, 
			"%s: structure allocation for iconName failed.\n", progname);
		exit(-1);
	}

	wm_hints->initial_state = NormalState;
	wm_hints->input = True;
	wm_hints->icon_pixmap = icon_pixmap;
	wm_hints->flags = StateHint | IconPixmapHint | InputHint;

	class_hints->res_name = progname;
	class_hints->res_class = "Figdrawwin";

	XSetWMProperties(display, win, &windowName, &iconName,
		argv, argc, size_hints, wm_hints, class_hints);

	/* Select event types wanted */
	XSelectInput(display, win, ExposureMask | KeyPressMask |
		ButtonPressMask | StructureNotifyMask);

	load_font(&font_info);

	/* Create GC for text and drawing */
	getGC(win, &gc, font_info);

	/* Display window */
	XMapWindow(display, win);

	/* Get events, use first to display text and graphics */
	while (1) {
		XNextEvent(display, &report);
		switch (report.type) {
		case Expose:
			/* Unless this is the last contiguous expose,
			 * don't draw the window. */
			if (report.xexpose.count != 0)
				break;
			/* If window too small to use */
			if ( window_size == TOO_SMALL)
				TooSmall(win, gc, font_info);
			else {
				XClearWindow(display, win);
				draw_axes( win, gc, width, height);
				XFlush(display);
				tptr = figure;
				do {
					draw_line( win, gc, width, height, tptr, tptr->nx);
					XFlush(display);
					tptr = tptr->nx;
				} while(tptr->nx);
				draw_line( win, gc, width, height, tptr, figure);
				XFlush(display);
			}
			break;
		case ConfigureNotify:
			/* Window has been resized; change width
			 * and height to send to place_text and
			 * place_graphics in next Expose */
			width = report.xconfigure.width;
			height = report.xconfigure.height;
			if ((width < size_hints->min_width) ||
					(height < size_hints->min_height))
				window_size = TOO_SMALL;
			else
				window_size = BIG_ENOUGH;
			break;
		case ButtonPress:
			/* Trickle down into KeyPress (no break) */
		case KeyPress:
			/* free RAM for all structures */
			len = XLookupString((XKeyEvent *)(&report), keybuf, 10, NULL, NULL);
			printf("%c\n", keybuf[0]);
			freedata(figure);
			XUnloadFont(display, font_info->fid);
			XFreeGC(display, gc);
			XCloseDisplay(display);
			exit(1);
		default:
			/* All events selected by StructureNotifyMask
			 * except ConfigureNotify are thrown away here,
			 * since nothing is done with them */
			break;
		} /* End switch */
	} /* End while(1) */
}

void getGC(Window win, GC *gc, XFontStruct *font_info)
{
	unsigned long valuemask = 0; /* Ignore XGCvalues and use defaults */
	XGCValues values;
	unsigned int line_width = 1;
	int line_style = 0;
	int cap_style = 0;
	int join_style = 0;
	int dash_offset = 0;
	int list_length = 0;

	/* Create default Graphics Context */
	*gc = XCreateGC(display, win, valuemask, &values);

	/* Specify font */
	XSetFont(display, *gc, font_info->fid);

	/* Specify black foreground since default widow background
	 * is white and default foreground is undefined */
	XSetForeground(display, *gc, BlackPixel(display, screen_num));

	/* Set line attributes */
	XSetLineAttributes(display, *gc, line_width, line_style,
		cap_style, join_style);

	/* Set dashes */
/*	XSetDashes(display, *gc, dash_offset, dash_list, list_length);*/
}

void load_font(XFontStruct ** font_info)
{
	char *fontname = "9x15";

	/* Load font and get font information structure */
	if ((*font_info = XLoadQueryFont(display, fontname)) == NULL)
	{
		(void)fprintf( stderr, "%s: Cannot open 9x15 font.\n", progname);
		exit(-1);
	}
}

void draw_line( Window win, GC gc, 
	unsigned int window_width, unsigned int window_height,
	struct point *p1, struct point *p2)
{
	int x_origin, y_origin;
	int width, height;

	x_origin = window_width/2;	/* Origin */
	y_origin = window_height/2;
	XDrawLine(display, win, gc, 
		x_origin + p1->x * 10, y_origin - p1->y * 10, 
		x_origin + p2->x * 10, y_origin - p2->y * 10);
}

void draw_axes( Window win, GC gc, 
	unsigned int window_width, unsigned int window_height)
{
	int x_origin, y_origin;
	int width, height;

	x_origin = window_width/2;	/* Origin */
	y_origin = window_height/2;
	XDrawLine(display, win, gc, 
		x_origin, 0, 
		x_origin, window_height - 1);
	XDrawLine(display, win, gc, 
		0, y_origin,
		window_width - 1, y_origin);
}

void TooSmall( Window win, GC gc, XFontStruct *font_info)
{
	char *string1 = "Too Small";
	int y_offset, x_offset;

	y_offset = font_info->ascent + 2;
	x_offset = 2;

	/* Output text, centered on each line */
	XDrawString(display, win, gc, x_offset, 
		y_offset,
		string1, strlen(string1));
}

void freedata(struct point *p)
{
	struct point *temp;
	/* bulletproofing for no items in list */
	if(p == NULL) return;
	do {
		/* save ptr for next struct */
		temp = p->nx;
		/* free memory for current struct */
		free(p);
		/* assign ptr-for-next to ptr-for-current */
		p = temp;
	} while(p);
}
