/***************************************************************************
 *            x11.c
 *
 *  Thu Jan 27 23:31:10 2005
 *  Copyright  2005  Peter Baldwin
 *  pgbaldwi@uwaterloo.ca
 ****************************************************************************/

#include "common.h"

//implements
#include "graphics.h"
#include "console.h"
//calls
#include "events.h"

#include <X11/Xlib.h>
#include <X11/Xutil.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <ctype.h>


/*
 * Defines for item types
 */
#define String 0
#define Rectangle 1

const int LW = 2;
XFontStruct *SMALL,*MEDIUM,*LARGE, *active_font;
const char* SMALL_NAME = "*-helvetica-medium-r-*-12-*";
const char* MEDIUM_NAME = "*-helvetica-medium-r-*-18-*";
const char* LARGE_NAME = "*-helvetica-medium-r-*-34-*";
const char *TITLE = "Gname";
int width,height;
double whratio; //width-to-height ratio
Display *display;
GC gc = 0;
Pixmap back_buffer = 0;
Window window;
unsigned long background, foreground;
unsigned long WHITE, BLACK, GRAY, active_color;

void gCreateBackBuffer();

void cQuit() {
	if (gc) {
		XFreeGC( display, gc );
	}
	if (back_buffer) {
		XFreePixmap( display, back_buffer );
	}
	exit(0);
}

/*
 * Function to put out a message on error exits.
 */
void cError( char *str ) {
  printf( "Error: %s\n", str );
  cQuit();
}

/*
 * Function to put out a message on error exits.
 */
void cWarning( char *str ) {
  printf( "Warning: %s\n", str );
}

void cMessage( char *str ) {
  printf( "Message: %s\n", str );
}

void gInit( int argc, char *argv[], double ratio ) {
	XColor gray;
	Colormap colormap;
	XSizeHints hints;
	int screen;
	
	//init ratio
	height = 300;
	width = (int)(ratio * height);
	whratio = ratio;
	
	/*
	* Display opening uses the DISPLAY  environment variable.
	* It can go wrong if DISPLAY isn't set, or you don't have permission.
	*/  
	display = XOpenDisplay( "" );
	if ( !display ) {
		cError( "Can't open display." );
	}
	screen = DefaultScreen( display );
	colormap = DefaultColormap(display, screen);
	
	if (!XParseColor(display, colormap, "gray", &gray)) {
		cWarning( "Failed to Parse Color 'gray'" );
	}
	if (!XAllocColor(display, colormap, &gray)) {
		cWarning( "Failed to Allocate Color 'gray'" );
	}
	
	background = 	WHITE = WhitePixel( display, screen );
	foreground = 	BLACK = BlackPixel( display, screen );
					GRAY = gray.pixel;
	
	/*
	* Set up hints and properties for the window manager, and open a window.
	*/
	hints.x = 100; hints.y = 100; 
	hints.width = width; hints.height = height; 
	hints.base_width = width;
	hints.base_height = height;
	hints.min_width = width;
	hints.min_height = height;
	hints.max_width = width;
	hints.max_height = height;
	hints.flags = PPosition | PMinSize | PMaxSize | PBaseSize | PSize ;
	window = XCreateSimpleWindow( display, DefaultRootWindow( display ), hints.x, hints.y,
								hints.width, hints.height, 5, foreground, background );
	XSetStandardProperties( display, window, TITLE, TITLE, None, argv, argc, &hints );
	/*
	* Get a graphics context and set the drawing colours.
	*/
	gc = XCreateGC (display, window, 0, 0 );
	XSetBackground( display, gc, background ); XSetForeground( display, gc, foreground );
	
	
	XFontStruct *default_font = XLoadQueryFont ( display, "*" );
	if (!default_font) {
		cError("Failed to load default font");
	}
	SMALL = XLoadQueryFont( display, SMALL_NAME );
	MEDIUM = XLoadQueryFont( display, MEDIUM_NAME );
	LARGE =  XLoadQueryFont( display, LARGE_NAME );
	if (!SMALL)  SMALL = default_font;
	if (!MEDIUM) MEDIUM = default_font;
	if (!LARGE)  LARGE = default_font;
		
	gSetFontMedium();
		
	gCreateBackBuffer();
	
	/*
	* Tell the window manager what input you want.
	*/
	XSelectInput( display, window, ButtonPressMask | ButtonReleaseMask | 
		KeyPressMask | ExposureMask | PointerMotionMask | ResizeRedirectMask );
	/*
	* Put the window on the screen.
	*/
	XMapRaised( display, window );
}

mouse_button xMapButton(unsigned int button) {
	switch (button) {
		case Button1:
			return BUTTON1;
		case Button2:
			return BUTTON2;
		case Button3:
			return BUTTON3;
		default:
			return UNKNOWN_BUTTON;
	}
}

int xMapX(double x) { return (int)(x/whratio*width);  }
int xMapY(double y) { return (int)(y*height); }
double xUnMapX(int x) { return whratio*x/width;  }
double xUnMapY(int y) { return (double)y/height;  }
int xMapA(double a) { return (int)(a*64);     }

void gEvents() {
	XEvent event;
	KeySym key;
	int i;
	char text[10];
	
	while(XPending(display)) {
		XNextEvent( display, &event );
		switch( event.type ) {
		/*
		* Repaint the window on expose events.
		*/
		case Expose:
			if ( event.xexpose.count == 0 ) {
			//repaint( head, display, window, gc );
			}
			break;
		case ButtonPress:
			eMousePressed( xUnMapX(event.xbutton.x), xUnMapY(event.xbutton.y), xMapButton(event.xbutton.button));
			break;
		case ButtonRelease:
			eMouseReleased( xUnMapX(event.xbutton.x), xUnMapY(event.xbutton.y), xMapButton(event.xbutton.button));
			break;
		/*
		* Exit when 'q' is typed.
		*/
		case KeyPress:
			i = XLookupString( (XKeyEvent *)&event, text, 10, &key, 0 );
			if ( i == 1 && tolower(text[0]) == 'q' ) {
				cQuit();
			} else {
				eKeyPressed( text[0] );
			}
			break;
		case MotionNotify:
			eMouseMoved(xUnMapX(event.xbutton.x), xUnMapY(event.xbutton.y));
			break;
		case ResizeRequest:
			width = event.xresizerequest.width;
			height = event.xresizerequest.height;
			gCreateBackBuffer(); //recreate
			XResizeWindow(display,window,width,height);
			break;
		}
	}
}


void gSwap() {
	XCopyArea(display, back_buffer, window, gc, 0, 0, width, height, 0, 0);
	XSync( display, FALSE );
}

void gClearBuffer() {
	XSetBackground( display, gc, BLACK ); XSetForeground( display, gc, WHITE );
	XFillRectangle( display, back_buffer, gc, 0, 0, width, height);
	XSetBackground( display, gc, WHITE ); XSetForeground( display, gc, BLACK );	
}


void gDrawString(char *string, double x, double y) {
	int i,start = 0;
	int n = strlen(string);
	int dx = xMapX(x);
	int dy = xMapY(y);
	int h = active_font->ascent + active_font->descent;
	int cy = dy;
	
	for (i=0; i<n; i++) {
		
		if ( string[i] == '\n' || i == n-1 ) {
			int len = (string[i] == '\n') ? i-start : i-start+1;
			XDrawString( display, back_buffer, gc, dx, cy, &string[start], len );
			cy+=h;
			start = i+1;
		}
	}
}

void gGetTextDimensions(char *string, double *w, double *h) {
	*w = xUnMapX(XTextWidth(active_font, string, strlen(string)));
	*h = xUnMapY(active_font->ascent + active_font->descent);
}


void gSetColor(unsigned long color) { //private
	XSetForeground( display, gc, color );
	active_color = color;
}

void gSetColorBlack() { gSetColor(BLACK); }
void gSetColorWhite() { gSetColor(WHITE); }
void gSetColorGray()  { gSetColor(GRAY);  }

void gSetFont(XFontStruct *font) {
	XSetFont( display, gc, font->fid );
	active_font = font;
}

void gSetFontSmall()  { gSetFont(SMALL);  }
void gSetFontMedium() { gSetFont(MEDIUM); }
void gSetFontLarge()  { gSetFont(LARGE);  }


//potential optimization: queue shape operations 
// and use XDrawRectangles, XDrawArcs, etc.

void gDrawRect(double x, double y, double w, double h) {
	XDrawRectangle( display, back_buffer, gc, xMapX(x), xMapY(y), xMapX(w), xMapY(h) );	
}
void gFillRect(double x, double y, double w, double h) {
	XFillRectangle( display, back_buffer, gc, xMapX(x), xMapY(y), xMapX(w), xMapY(h) );	
}


void gDrawArc(double x, double y, double w, double h, double a1, double a2) {
	XDrawArc( display, back_buffer, gc, xMapX(x), xMapY(y), xMapX(w), xMapY(h), xMapA(a1), xMapA(a2));
}

void gFillArc(double x, double y, double w, double h, double a1, double a2) {
	XFillArc( display, back_buffer, gc, xMapX(x), xMapY(y), xMapX(w), xMapY(h), xMapA(a1), xMapA(a2));
}

void gDrawOutlinedRect(double x, double y, double w, double h) {
	const unsigned long fill = active_color;
	gSetColorBlack();
	gFillRect(x,y,w,h);
	gSetColor(fill);
	gFillRect(x+xUnMapX(LW),y+xUnMapY(LW),w-xUnMapX(LW*2),h-xUnMapY(LW*2));
}

void gDrawOutlinedArc(double x, double y, double w, double h, double a1, double a2) {
	const unsigned long fill = active_color;
	gSetColorBlack();
	gFillArc(x,y,w,h,a1,a2);
	gSetColor(fill);
	gFillArc(x+xUnMapX(LW),y+xUnMapY(LW),w-xUnMapX(LW*2),h-xUnMapY(LW*2),a1,a2);
	
}

void gCreateBackBuffer() {
	if (back_buffer) { 
		XFreePixmap( display, back_buffer);
		back_buffer = 0;
	}
	back_buffer = XCreatePixmap(display, window, width, height, DefaultDepth(display, DefaultScreen(display)));
}
