#include <X11/X.h>
#include <X11/Xlib.h>
#include <X11/Xutil.h>
#include <X11/Xos.h>
#include <X11/X10.h>
#include <stdio.h>
#include <stdlib.h>
#include <math.h>


/* graphics window stuff */
Display *d;
Window w;
int w_width, w_height;
char buf[100];
GC text_GC;
GC gcs[256];
GC black_gc;
GC white_gc;
unsigned long pixels [256];
struct RGB
{
    unsigned short int r, g, b;
}
rgbs[256];



/*
   prototypes:
*/
void start_X(int width, int height, int colormap_type);
void handle_events(void);
void clear_window(void);
void process_button_press_event(XEvent e);
void process_key_press_event(XKeyEvent *e);
void re_expose_window(void);
void jump(void);
void make_color_map(Window w, int std);
void make_black_and_white(Colormap cmap);
void make_uniform_color_map(Colormap cmap);
void make_sin_color_map(Colormap cmap);
void plot(int x, int y, int r, int g, int b);
void fill_block(int ll_x, int ll_y, int ur_x, int ur_y, int r, int g, int b);
GC best_gc(int r, int g, int b);

/* Sather routine(s) */
void X_draw_it(void);
void set_window_dimensions(int width, int height);
void set_viewpoint_abs(int qx0, int qx1);
void set_viewpoint_rel(int wx0, int wx1);
void give_qt_coords(int wx0, int wx1);
void sample_elem(int wx0, int wx1);




void clear_window(void) 
{
    XClearWindow(d, w);
    handle_events();
}
/* void clear_window(void) */



void plot(int x, int y, int r, int g, int b)
{
    XDrawPoint(d, w, best_gc(r, g, b), x, y);
    handle_events();
}
/* void plot(int, int, int, int, int) */



void fill_block(int x, int y, int width, int height, int r, int g, int b)
{
    XFillRectangle(d, w, best_gc(r, g, b), x, y, width, height);
    handle_events();
}
/* void fill_block(int, int, int, int, int, int, int) */



GC best_gc(int r, int g, int b) 
{
    int i, best_i;
    int error, min_error;
    
    min_error = abs(rgbs[0].r - r) + abs(rgbs[0].g - g) + abs(rgbs[0].b - b);
    best_i = 0;
    
    for (i = 1; i < 256; i++)
    {
	error = abs(rgbs[i].r - r) + abs(rgbs[i].g - g) + abs(rgbs[i].b - b);
	
	if (error < min_error)
	{
	    min_error = error;
	    best_i = i;
	}
    }

    return (gcs[best_i]);
}
/* GC best_gc(int, int, int) */



void handle_events(void)
{
    /* event mask set in start_X(int) */
    XEvent e;

    
#   if X_SYNCHRONIZE_FLG
    XSynchronize(d, True);
#   endif

    while (XPending(d)) 
    {
	XNextEvent(d, &e);
	switch (e.type)
	{
        case KeyPress:
	    process_key_press_event(&(e.xkey));
	    break;
        case ButtonPress:
	    process_button_press_event(e);
	    break;
        case Expose:
	    re_expose_window();
	    break;
	}
    }
} /* void handle_events(void) */



void process_button_press_event(XEvent e)
{
    int root_x, root_y, child_x, child_y;
    Window root_window, child_window;
    unsigned int mask_return;

    XQueryPointer(d, w, &root_window, &child_window, &root_x, &root_y, 
		&child_x, &child_y, &mask_return);
    
    if (e.xbutton.button == Button1)
    {
	X_give_qt_coords(child_x, child_y);
    }
    else if (e.xbutton.button == Button2)
    {
	X_set_viewpoint_rel(child_x, child_y);
    } 
    else if (e.xbutton.button == Button3)
    {
	X_sample_elem(child_x, child_y);
    }
} /* void process_button_press_event(void) */



void process_key_press_event(XKeyEvent *e)
{
    char buf[10];
    KeySym ks;
    XComposeStatus xcs;
    
    int root_x, root_y, child_x, child_y;
    Window root_window, child_window;
    unsigned int mask_return;


    XQueryPointer(d, w, &root_window, &child_window, &root_x, &root_y, 
		&child_x, &child_y, &mask_return);
    XLookupString(e, buf, 10, &ks, &xcs);
    
    switch (ks)
    {
    case 'q':
    case 'Q':
	exit(0);
    case 'c':
    case 'C':
	XQueryPointer(d, w, &root_window, &child_window, &root_x, &root_y, 
		      &child_x, &child_y, &mask_return);
	X_set_viewpoint_rel(child_x, child_y);
	break;
    case 'r':
    case 'R':
	X_set_viewpoint_abs(0, 0);
	break;
    case 'j':
    case 'J':
	jump();
	break;
    }
} /* void process_key_press_event(XKeyEvent) */



void jump(void)
{
    int x, y;
    
    fprintf(stderr, "\aEnter coordinates to jump to: ");
    scanf("%d %d", &x , &y);
    X_set_viewpoint_abs(x, y);
} /* void jump(void) */



void re_expose_window(void)
{
    Window root;
    XWindowAttributes xwa;
    int x, y;
    int bw, depth;

    XGetGeometry(d, w, &root, &x, &y, &w_width, &w_height, &bw, &depth);
    X_set_window_dimensions(w_width, w_height);
    X_draw_it();
    handle_events();
} /* void re_expose_window(void) */
		     


void start_X(int width, int height, int colormap_type)
{   
    XSetWindowAttributes *a;
    XGCValues gcv;
    XEvent e;
    int i;
    char buf[100];


    w_width = width;
    w_height = height;
    X_set_window_dimensions(w_width, w_height);    
    d = XOpenDisplay(NULL);
    w = XCreateSimpleWindow(d, DefaultRootWindow(d), 0,0,
			    w_width, w_height, 1,
			    XWhitePixel(d, 0), XBlackPixel(d, 0));
    make_color_map(w, colormap_type);
    XSelectInput(d, w, ExposureMask);
    
    while (XPending(d) != 0) 
    {
	XNextEvent(d, &e);
    }
  
    XMapRaised(d, w);
    XSync(d, False);
    
    while (! XCheckWindowEvent(d, w, ExposureMask, &e))
    {
    }

    while (XPending(d) != 0) 
    {
	XNextEvent(d, &e);    /* just flush any waiting events. */
    }   

    white_gc = gcs[0];
    black_gc = gcs[1];  

    XSelectInput(d, w, ButtonPressMask | ExposureMask | KeyPressMask);
 } /* void start_X(int, int, int) */




void make_color_map(Window w, int colormap_type) 
{
    int nocolors = 256;
    long  scrap[100];
    
    Colormap cmap = XCreateColormap(d, w,
				    XDefaultVisual(d, XDefaultScreen(d)), 
				    AllocNone);

    if (XAllocColorCells(d, cmap, True, scrap, 0, pixels,
			      nocolors) == 0)
    {
	fprintf(stderr, "xalloccolorcells died couldnt allocate %d colors\n", 
		nocolors);
	exit(-1);
    }

    make_black_and_white(cmap);    

    if (colormap_type)
    {
	make_uniform_color_map(cmap); 
    }
    else 
    {
	make_sin_color_map(cmap);
    }

    XSetWindowColormap(d, w, cmap);
} /* make_color_map */





void make_black_and_white(Colormap cmap)
{
    XGCValues gcv;
    XColor color;

    color.blue  = 65535;
    color.red   = 65535;
    color.green = 65535;
    color.flags = DoRed | DoBlue | DoGreen;
    color.pixel = pixels[0];
    rgbs[0].r = 65535;
    rgbs[0].g = 65535;
    rgbs[0].b = 65535;
    
    XStoreColor(d, cmap, &color);    
        
    gcv.foreground = pixels[0];
    gcv.background = pixels[0];
    gcv.function = GXcopy;
    gcs[0] = XCreateGC(d, w, GCForeground | GCBackground, &gcv);
    text_GC = gcs[0];

    color.blue  = 0;
    color.red   = 0;
    color.green = 0;
    color.flags = DoRed | DoBlue | DoGreen;
    color.pixel = pixels[1]; 
    rgbs[1].r = 0;
    rgbs[1].g = 0;
    rgbs[1].b = 0;
    
    XStoreColor(d, cmap, &color);    
    
    gcv.foreground = pixels[1];
    gcs[1] = XCreateGC(d, w,  GCForeground | GCBackground, &gcv);
} /* make_black_and_white */




void make_uniform_color_map(Colormap cmap)
{
    /*
       assumes gcs[0], gcs[1] are taken up by "black" and "white"
       makes 254 remaining colors as over spectrum as evenly as possible
    */

    int i,j,k;
    int ic, jc, kc;
    int start, inc;
    int gcindex;
    int blackpixel, whitepixel;
    XGCValues gcv;
    int currentpixel;
    int r, g, b;
    XColor color;
    
    const int Dr = 9364;
    const int Dg = 9362;
    const int Db = 9362;
    const int Base_R = Dr;
    const int Base_G = Dg;
    const int Base_B = Db;
    
    
    i = 2;
    
    for (r = Base_R; (r < 65536) && (i < 256); r += Dr) 
    {
	for (g = Base_G; (g < 65536) && (i < 256); g += Dg) 
	{
	    for (b = Base_B; (b < 65536) && (i < 256); b += Db) 
	    {
		color.red = r;
		color.blue = b;
		color.green = g;
		color.flags = DoRed | DoBlue | DoGreen;
		color.pixel = pixels[i];	    
		rgbs[i].r = r;
		rgbs[i].g = g;
		rgbs[i].b = b;

		XStoreColor(d, cmap, &color);
	    	    
		gcv.foreground = color.pixel;
		gcv.background = color.pixel;
		gcv.function   = GXcopy;
		gcs[i] = XCreateGC(d, w, GCForeground | GCBackground, &gcv);
		i++;
	    }
	}
    }
} /* make_uniform_color_map */





#define RPHASE  1.3
#define BPHASE  0.0
#define GPHASE  0.0
#define FMULT   2.0

#define SIN(x,freq,phase) sin(2.0 * M_PI*(freq)*(x)+phase-M_PI_2) 
#define SIN_COLOR(x,freq, phase) (int)(65535. * (SIN(x, freq, phase) +\
              1.0) / 2.0) 


void make_sin_color_map(Colormap cmap)
{
   int i;
    XColor color;    
    XGCValues gcv;
    
    for (i = 2; i < 256; i++)
    {
	double ri = i/256.;

	color.blue  = SIN_COLOR(ri, FMULT*2.2, BPHASE);
	color.red   = SIN_COLOR(ri, FMULT*2.3, RPHASE);
	color.green = SIN_COLOR(ri, FMULT*1.7, GPHASE);
	color.flags = DoRed | DoBlue | DoGreen;
	color.pixel = pixels[i];
	rgbs[i].r = color.red;
	rgbs[i].g = color.green;
	rgbs[i].b = color.blue;

	XStoreColor(d, cmap, &color);

	gcv.foreground = color.pixel;
	gcv.background = color.pixel;
	gcv.fill_style = FillSolid;
	gcv.function = GXcopy;
	gcs[i] = XCreateGC(d, w, GCForeground | GCBackground, &gcv);
   }
} /* make_sin_color_map */
