#include "unix/guts.h"
#include "Icon.h"

static int
cursor_map[] = {
	/* crArrow           => */   XC_left_ptr,
	/* crText            => */   XC_xterm,
	/* crWait            => */   XC_watch,
	/* crSize            => */   XC_sizing,
	/* crMove            => */   XC_fleur,
	/* crSizeWest        => */   XC_left_side,
	/* crSizeEast        => */   XC_right_side,
	/* crSizeNE          => */   XC_sb_h_double_arrow,
	/* crSizeNorth       => */   XC_top_side,
	/* crSizeSouth       => */   XC_bottom_side,
	/* crSizeNS          => */   XC_sb_v_double_arrow,
	/* crSizeNW          => */   XC_top_left_corner,
	/* crSizeSE          => */   XC_bottom_right_corner,
	/* crSizeNE          => */   XC_top_right_corner,
	/* crSizeSW          => */   XC_bottom_left_corner,
	/* crInvalid         => */   XC_X_cursor,
};

Cursor
predefined_cursors[] = {
	None,
	None,
	None,
	None,
	None,
	None,
	None,
	None,
	None,
	None,
	None,
	None,
	None,
	None,
	None,
	None
};

static int
get_cursor( Handle self, Pixmap *source, Pixmap *mask, Point *hot_spot, Cursor *cursor)
{
	int id = X(self)-> pointer_id;

	while ( self && ( id = X(self)-> pointer_id) == crDefault)
		self = PWidget(self)-> owner;
	if ( id == crDefault) {
		id = crArrow;
	} else if ( id == crUser) {
		if (source)       *source   = X(self)-> user_p_source;
		if (mask)         *mask     = X(self)-> user_p_mask;
		if (hot_spot)     *hot_spot = X(self)-> pointer_hot_spot;
		if (cursor)       *cursor   = X(self)-> user_pointer;
	}

	return id;
}

static Bool
load_pointer_font( void)
{
	if ( !guts.pointer_font)
		guts.pointer_font = XLoadQueryFont( DISP, "cursor");
	if ( !guts.pointer_font) {
		warn( "Cannot load cursor font");
		return false;
	}
	return true;
}

Point
apc_pointer_get_hot_spot( Handle self)
{
	Point hot_spot;
	int idx;
	int id = get_cursor(self, nil, nil, &hot_spot, nil);
	XFontStruct *fs;
	XCharStruct *cs;
	Point ret = {0,0};

	if ( id < crDefault || id > crUser)  return ret;
	if ( id == crUser)                   return hot_spot;
	if ( !load_pointer_font())           return ret;

	idx = cursor_map[id];
	fs = guts.pointer_font;
	if ( !fs-> per_char)
		cs = &fs-> min_bounds;
	else if ( idx < fs-> min_char_or_byte2 || idx > fs-> max_char_or_byte2) {
		int default_char = fs-> default_char;
		if ( default_char < fs-> min_char_or_byte2 || default_char > fs-> max_char_or_byte2)
		default_char = fs-> min_char_or_byte2;
		cs = fs-> per_char + default_char - fs-> min_char_or_byte2;
	} else
		cs = fs-> per_char + idx - fs-> min_char_or_byte2;
	ret. x = -cs->lbearing;
	ret. y = guts.cursor_height - cs->ascent;
	if ( ret. x < 0) ret. x = 0;
	if ( ret. y < 0) ret. y = 0;
	if ( ret. x >= guts. cursor_width)  ret. x = guts. cursor_width  - 1;
	if ( ret. y >= guts. cursor_height) ret. y = guts. cursor_height - 1;
	return ret;
}

Point
apc_pointer_get_pos( Handle self)
{
	Point p;
	XWindow root, child;
	int x, y;
	unsigned int mask;

	if ( !XQueryPointer( DISP, guts. root,
			&root, &child, &p. x, &p. y,
			&x, &y, &mask)) 
		return guts. displaySize;
	p. y = guts. displaySize. y - p. y - 1;
	return p;
}

int
apc_pointer_get_shape( Handle self)
{
	return X(self)->pointer_id;
}

Point
apc_pointer_get_size( Handle self)
{
	Point p;
	p.x = guts.cursor_width;
	p.y = guts.cursor_height;
	return p;
}

Bool
apc_pointer_get_bitmap( Handle self, Handle icon)
{
	XImage *im;
	int id;
	Pixmap p1 = None, p2 = None;
	Bool free_pixmap = true;
	GC gc;
	XGCValues gcv;
	char c;
	int w = guts.cursor_width, h = guts.cursor_height;

	id = get_cursor( self, &p1, &p2, nil, nil);
	if ( id < crDefault || id > crUser)  return false;
	if ( id == crUser) {
		if ( !p1 || !p2) {
			warn( "User pointer inconsistency");
			return false;
		}
		free_pixmap = false;
	} else {
		XFontStruct *fs;
		XCharStruct *cs;
		int idx = cursor_map[id];

		if ( !load_pointer_font()) return false;
		fs = guts.pointer_font;
		if ( !fs-> per_char)
			cs = &fs-> min_bounds;
		else if ( idx < fs-> min_char_or_byte2 || idx > fs-> max_char_or_byte2) {
			int default_char = fs-> default_char;
			if ( default_char < fs-> min_char_or_byte2 || default_char > fs-> max_char_or_byte2)
				default_char = fs-> min_char_or_byte2;
			cs = fs-> per_char + default_char - fs-> min_char_or_byte2;
		} else
			cs = fs-> per_char + idx - fs-> min_char_or_byte2;
		
		p1 = XCreatePixmap( DISP, guts. root, w, h, 1);
		p2 = XCreatePixmap( DISP, guts. root, w, h, 1);
		gcv. background = 1;
		gcv. foreground = 0;
		gcv. font = guts.pointer_font-> fid;
		gc = XCreateGC( DISP, p1, GCBackground | GCForeground | GCFont, &gcv);
		XFillRectangle( DISP, p1, gc, 0, 0, w, h);
		gcv. background = 0;
		gcv. foreground = 1;
		XChangeGC( DISP, gc, GCBackground | GCForeground, &gcv);
		XFillRectangle( DISP, p2, gc, 0, 0, w, h);
		XDrawString( DISP, p1, gc, -cs-> lbearing, cs-> ascent, (c = (char)(idx+1), &c), 1);
		gcv. background = 1;
		gcv. foreground = 0;
		XChangeGC( DISP, gc, GCBackground | GCForeground, &gcv);
		XDrawString( DISP, p2, gc, -cs-> lbearing, cs-> ascent, (c = (char)(idx+1), &c), 1);
		XDrawString( DISP, p1, gc, -cs-> lbearing, cs-> ascent, (c = (char)idx, &c), 1);
		XFreeGC( DISP, gc);
	}
	CIcon(icon)-> create_empty( icon, w, h, imBW);
	im = XGetImage( DISP, p1, 0, 0, w, h, 1, XYPixmap);
	prima_copy_xybitmap( PIcon(icon)-> data, (Byte*)im-> data,
								PIcon(icon)-> w, PIcon(icon)-> h,
								PIcon(icon)-> lineSize, im-> bytes_per_line);
	XDestroyImage( im);
	im = XGetImage( DISP, p2, 0, 0, w, h, 1, XYPixmap);
	prima_copy_xybitmap( PIcon(icon)-> mask, (Byte*)im-> data,
								PIcon(icon)-> w, PIcon(icon)-> h,
								PIcon(icon)-> maskLine, im-> bytes_per_line);
if ( id == crUser) {
	int i;
	Byte * mask = PIcon(icon)-> mask;
	for ( i = 0; i < PIcon(icon)-> maskSize; i++) 
		mask[i] = ~mask[i];
	}   
	XDestroyImage( im);
	if ( free_pixmap) {
		XFreePixmap( DISP, p1);
		XFreePixmap( DISP, p2);
	}
	return true;
}

Bool
apc_pointer_get_visible( Handle self)
{
	return guts. pointer_invisible_count == 0;
}

Bool
apc_pointer_set_pos( Handle self, int x, int y)
{
	XEvent ev;
	if ( !XWarpPointer( DISP, None, guts. root, 
		0, 0, guts. displaySize.x, guts. displaySize.y, x, guts. displaySize.y - y - 1))
		return false;
	XCHECKPOINT;
	XSync( DISP, false);
	while ( XCheckMaskEvent( DISP, PointerMotionMask|EnterWindowMask|LeaveWindowMask, &ev))
		prima_handle_event( &ev, nil);
	return true;   
}

Bool
apc_pointer_set_shape( Handle self, int id)
{
	DEFXX;
	Cursor uc = None;

	if ( id < crDefault || id > crUser)  return false;
	XX-> pointer_id = id;
	id = get_cursor( self, nil, nil, nil, &uc);
	if ( id == crUser) {
		if ( uc != None || ( uc = XX-> user_pointer) != None) {
			if ( self != application) {
				if ( guts. pointer_invisible_count < 0) {
					if ( !XX-> flags. pointer_obscured) {
						XDefineCursor( DISP, XX-> udrawable, prima_null_pointer());   
						XX-> flags. pointer_obscured = 1;
					}   
				} else {   
					XDefineCursor( DISP, XX-> udrawable, uc);
					XX-> flags. pointer_obscured = 0;
				}
				XCHECKPOINT;
			}
		} else
			id = crArrow;
	}
	if ( id != crUser) {
		if ( predefined_cursors[id] == None) {
			predefined_cursors[id] =
				XCreateFontCursor( DISP, cursor_map[id]);
			XCHECKPOINT;
		}
		XX-> actual_pointer = predefined_cursors[id];
		if ( self != application) {
			if ( guts. pointer_invisible_count < 0) {
				if ( !XX-> flags. pointer_obscured) {
					XDefineCursor( DISP, XX-> udrawable, prima_null_pointer());   
					XX-> flags. pointer_obscured = 1;
				}   
			} else {   
				XDefineCursor( DISP, XX-> udrawable, predefined_cursors[id]);
				XX-> flags. pointer_obscured = 0;
			}
			XCHECKPOINT;
		}
	}
	XFlush( DISP);
	if ( guts. grab_widget)
		apc_widget_set_capture( guts. grab_widget, true, guts. grab_confine);
	return true;
}

Bool
apc_pointer_set_user( Handle self, Handle icon, Point hot_spot)
{
	DEFXX;
	Handle cursor;
	PIcon c;

	if ( XX-> user_pointer != None) {
		XFreeCursor( DISP, XX-> user_pointer);
		XX-> user_pointer = None;
	}
	if ( XX-> user_p_source != None) {
		XFreePixmap( DISP, XX-> user_p_source);
		XX-> user_p_source = None;
	}
	if ( XX-> user_p_mask != None) {
		XFreePixmap( DISP, XX-> user_p_mask);
		XX-> user_p_mask = None;
	}
	if ( icon != nilHandle) {
		Bool noSZ  = PIcon(icon)-> w != guts.cursor_width || PIcon(icon)-> h != guts.cursor_height;
		Bool noBPP = (PIcon(icon)-> type & imBPP) != 1;
		XColor xcb, xcw;
		if ( noSZ || noBPP) {
			cursor = CIcon(icon)->dup(icon);
			c = PIcon(cursor);
			if ( cursor == nilHandle) {
				warn( "Error duping user cursor");
				return false;
			}
			if ( noSZ) {
				CIcon(cursor)-> stretch( cursor, guts.cursor_width, guts.cursor_height);
				if ( c-> w != guts.cursor_width || c-> h != guts.cursor_height) {
					warn( "Error stretching user cursor");
					Object_destroy( cursor);
					return false;
				}
			}   
			if ( noBPP) {
				CIcon(cursor)-> set_type( cursor, imMono);
				if ((c-> type & imBPP) != 1) {
					warn( "Error black-n-whiting user cursor");
					Object_destroy( cursor);
					return false;
				}
			}
		} else
			cursor = icon;
		if ( !prima_create_icon_pixmaps( cursor, &XX-> user_p_source, &XX-> user_p_mask)) {
			warn( "Error creating user cursor pixmaps");
			if ( noSZ || noBPP)
				Object_destroy( cursor);
			return false;
		}
		if ( noSZ || noBPP)
			Object_destroy( cursor);
		if ( hot_spot. x < 0) hot_spot. x = 0;
		if ( hot_spot. y < 0) hot_spot. y = 0;
		if ( hot_spot. x >= guts. cursor_width)  hot_spot. x = guts. cursor_width  - 1;
		if ( hot_spot. y >= guts. cursor_height) hot_spot. y = guts. cursor_height - 1;
		XX-> pointer_hot_spot = hot_spot;
		xcb. red = xcb. green = xcb. blue = 0; 
		xcw. red = xcw. green = xcw. blue = 0xFFFF; 
		xcb. pixel = guts. monochromeMap[0];
		xcw. pixel = guts. monochromeMap[1];
		xcb. flags = xcw. flags = DoRed | DoGreen | DoBlue;
		XX-> user_pointer = XCreatePixmapCursor( DISP, XX-> user_p_source,
			XX-> user_p_mask, &xcw, &xcb, 
			hot_spot. x, guts.cursor_height - hot_spot. y);
		if ( XX-> user_pointer == None) {
			warn( "error creating cursor from pixmaps");
			return false;
		}
		if ( XX-> pointer_id == crUser && self != application) {
			if ( guts. pointer_invisible_count < 0) {
				if ( !XX-> flags. pointer_obscured) {
					XDefineCursor( DISP, XX-> udrawable, prima_null_pointer());
					XX-> flags. pointer_obscured = 1;
				}   
			} else {   
				XDefineCursor( DISP, XX-> udrawable, XX-> user_pointer);
				XX-> flags. pointer_obscured = 0;
			}
			XCHECKPOINT;
		}      
	}
	XFlush( DISP);
	if ( guts. grab_widget)
		apc_widget_set_capture( guts. grab_widget, true, guts. grab_confine);
	return true;
}



Bool
apc_pointer_set_visible( Handle self, Bool visible)
{
	/* maintaining hide/show count */
	if ( visible) {
		if ( guts. pointer_invisible_count == 0) 
			return true;
		if ( ++guts. pointer_invisible_count < 0)
			return true;
	} else {
		if ( guts. pointer_invisible_count-- < 0)
			return true;
	}

	/* setting pointer for widget under cursor */
	{
		Point p    = apc_pointer_get_pos( application);
		Handle wij = apc_application_get_widget_from_point( application, p);
		if ( wij) {
			X(wij)-> flags. pointer_obscured = (visible ? 0 : 1);
			XDefineCursor( DISP, X(wij)-> udrawable, 
				visible ? (( X(wij)-> pointer_id == crUser) ? 
								X(wij)-> user_pointer : X(wij)-> actual_pointer) 
						: prima_null_pointer());  
		}   
	}   
	XFlush( DISP);
	if ( guts. grab_widget)
		apc_widget_set_capture( guts. grab_widget, true, guts. grab_confine);
	return true;
}

Cursor
prima_null_pointer( void)
{
	if ( guts. null_pointer == nilHandle) {
		Handle nullc = ( Handle) create_object( "Prima::Icon", "", nil);
		PIcon  n = ( PIcon) nullc;
		Pixmap xor, and;
		XColor xc;      
		if ( nullc == nilHandle) {
			warn("Error creating icon object");
			return nilHandle;
		}   
		n-> self-> create_empty( nullc, 16, 16, imBW);
		memset( n-> mask, 0xFF, n-> maskSize);
		if ( !prima_create_icon_pixmaps( nullc, &xor, &and)) {
			warn( "Error creating null cursor pixmaps"); 
			Object_destroy( nullc);
			return nilHandle;
		}  
		Object_destroy( nullc);
		xc. red = xc. green = xc. blue = 0;
		xc. pixel = guts. monochromeMap[0];
		xc. flags = DoRed | DoGreen | DoBlue;
		guts. null_pointer = XCreatePixmapCursor( DISP, xor, and, &xc, &xc, 0, 0);                                      
		XCHECKPOINT;
		XFreePixmap( DISP, xor);
		XFreePixmap( DISP, and);
		if ( !guts. null_pointer) {
			warn( "Error creating null cursor from pixmaps");
			return nilHandle;
		}   
	}
	return guts. null_pointer;
}
