#include "global.hh"

#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <signal.h>
#include <sys/types.h>
#include <sys/time.h>
#include <sys/wait.h>
#include <X11/Xatom.h>
#include <X11/xpm.h>
#include <X11/Xft/Xft.h>
#include <math.h>

#include "main.hh"
#include "log.hh"
#include "config.hh"
#include "menus.hh"
#include "misc.hh"
#include "commands.hh"
#include "windows.hh"
#include "events.hh"

char *white = (char *) "white";
char *black = (char *) "black";
char *grey = (char *) "grey";
#ifdef FONTS_XFT
char *default_font = (char *) "sans";
#elif defined FONTS_XCORE
char *default_font = (char *) "fixed";
#endif

XSetWindowAttributes at;
unsigned long vm;
XGCValues gcv;
unsigned long gcm;
Junk junk;
PointerPos ppos;
Window fake_win;
XftDraw *xft_draw;

MyColor pointer_fore;
MyColor pointer_back;
MyFont swm_font;
MyPixmap *root_mypixmap;
MyColor root_mycolor;

int absi (int x) { return (x>0 ? x : -x); }
int sgn (int x) { return (x>0 ? 1 : -1); }
int restrict (int &x, int a, int b) { if (x<a) x=a; else if (x>b) x=b; }

int
iifrac (int x, int y)
{
	int r = (int) ((float) x / (float) y);
	if (x < 0) r--;
	return r;
}

int fake_win_init ()
{	
	fake_win = XCreateSimpleWindow (dpy, root_window, -20, -20, 10, 10, 0, 0L, 0L);
}

GC pixGC;

int
my_pix_init ()
{
	gcv.function = GXcopy;
	gcm = GCFunction;
	pixGC = XCreateGC (dpy, root_window, gcm, &gcv);
}

int
pointer_warping ()
{
	// shortcuts for warping the pointer
	int step = 10;
	if (event->x.xkey.state & ControlMask)	step = 1;
	if (event->x.xkey.state & ShiftMask)	step = 100;

	int dx = 0;
	int dy = 0;

	switch (event->keysym)
	{
	case XK_Up: case XK_k: case XK_p:	dy = -step; event->handled = 1; break;
	case XK_Down: case XK_n: case XK_j:	dy = +step; event->handled = 1; break;
	case XK_Left: case XK_b: case XK_h:	dx = -step; event->handled = 1; break;
	case XK_Right: case XK_f: case XK_l:	dx = +step; event->handled = 1; break;
	default: break;
	};

	if (dx || dy) ppos.warp_by (dx, dy);
}

MyPixmap::MyPixmap ()
{
	pm = None;
	clip_mask = None;
	wd = ht = 0;
}

MyPixmap::~MyPixmap ()
{
	if (pm) XFreePixmap (dpy, pm);
}

int
MyPixmap::load (char *file)
{
	// load pixmap from file
	XWindowAttributes at;
	XpmAttributes xat;
	
	depth = root_depth;
	xat.colormap = root_colormap;
	xat.closeness = 40000;
	xat.valuemask = XpmSize | XpmReturnPixels | XpmColormap | XpmCloseness;
	
	if (file && XpmReadFileToPixmap (dpy, root_window, file, &pm, &clip_mask, &xat) == XpmSuccess) {
		wd = xat.width;
		ht = xat.height;
		return 1;
	}
	else {
		return 0;
	};
}

int
MyPixmap::create (char **data)
{
	// create pixmap from inline data
	XWindowAttributes at;
	XpmAttributes xat;
	
	XGetWindowAttributes(dpy, root_window, &at);
	depth = at.depth;
	xat.colormap = at.colormap;
	xat.closeness = 40000;
	xat.valuemask = XpmSize | XpmReturnPixels | XpmColormap | XpmCloseness;
	
	if (XpmCreatePixmapFromData (dpy, root_window, data, &pm, &clip_mask, &xat) == XpmSuccess) {
		wd = xat.width;
		ht = xat.height;
		return 1;
	}
	else {
		return 0;
	}
}

int
MyPixmap::shrink (Window w, int f)
{
	// window w shrinked by factor f
	XWindowAttributes at;
	XGetWindowAttributes(dpy, w, &at);
	depth = at.depth;
	pm = XCreatePixmap (dpy, root_window, wd, ht, depth);

	gcv.function = GXcopy;
	gcm = GCFunction;
	XChangeGC (dpy, pixGC, gcm, &gcv);

	int i, j;
	for (i = 0; i<wd; i++)
		for (j = 0; j<wd; j++)
			XCopyArea (dpy, w, pm, pixGC, i*f, j*f, 1, 1, i, j); 
}

int
MyPixmap::shrink (MyPixmap *t, int f)
{
	// pixmap p shrinked by factor f
	wd = t->wd / f;
	ht = t->ht / f;
	depth = t->depth;
	pm = XCreatePixmap (dpy, root_window, wd, ht, depth);

	gcv.function = GXcopy;
	gcm = GCFunction;
	XChangeGC (dpy, pixGC, gcm, &gcv);

	int i, j;
	for (i = 0; i<wd; i++)
		for (j = 0; j<wd; j++)
			XCopyArea (dpy, t->pm, pm, pixGC, i*f, j*f, 1, 1, i, j); 
}

int
MyPixmap::set (Window w)
{
	if (pm) {
		XSetWindowBackgroundPixmap (dpy, w, pm);
		XClearWindow (dpy, w);
	}
}

int
MyPixmap::gx_or (Window w, int wx, int wy)
{
	gcv.function = GXor;
	gcm = GCFunction;
	XChangeGC (dpy, pixGC, gcm, &gcv);

	// need this, since function is or
	XClearArea (dpy, w, wx, wy, wd, ht, FALSE);
	XCopyArea (dpy, pm, w, pixGC, 0, 0, wd, ht, wx, wy);
}

int
MyPixmap::gx_copy (Window w, int wx, int wy)
{
	gcv.function = GXcopy;
	gcm = GCFunction;
	XChangeGC (dpy, pixGC, gcm, &gcv);

	XCopyArea (dpy, pm, w, pixGC, 0, 0, wd, ht, wx, wy);
}

int
MyPixmap::gx_copy_mask (Window w, int wx, int wy)
{
	XGCValues gcv;

	gcv.function = GXcopy;
	gcv.clip_mask = clip_mask;
	gcv.clip_x_origin = wx;
	gcv.clip_y_origin = wy;
	gcm = GCFunction | GCClipMask | GCClipXOrigin | GCClipYOrigin;
	XChangeGC (dpy, pixGC, gcm, &gcv);
	XSync (dpy, 0);

	XClearArea (dpy, w, wx, wy, wd, ht, FALSE);
	XCopyArea (dpy, pm, w, pixGC, 0, 0, wd, ht, wx, wy);
}

/*
int
MyImage::MyImage ()
{
};

int
MyImage::load (char *)
{
};
*/

Timer *first_timer = NULL;
int timer_access = 1;

void
time_handler (int nonsense)
{
	if (timer_access) {
		Timer *i;
		for (i=first_timer; i; i=i->next)
			i->tick ();
	}
}

int
timer_init ()
{
	// setup timer
	signal (SIGALRM, time_handler);
	
	itimerval tval;
	tval.it_interval.tv_sec = 0;
	tval.it_interval.tv_usec = 10000;
	tval.it_value.tv_sec = 0;
	tval.it_value.tv_usec = 10000;
	
	setitimer (ITIMER_REAL, &tval, NULL);
}

int
timer_done ()
{
	// stop timer
	itimerval tval;
	tval.it_interval.tv_sec = 0;
	tval.it_interval.tv_usec = 0;
	tval.it_value.tv_sec = 0;
	tval.it_value.tv_usec = 0;
		
	setitimer (ITIMER_REAL, &tval, NULL);
		
	signal (SIGALRM, SIG_IGN);
}

Timer *
add_timer (int t)
{
	timer_access = 0;
	Timer *timer = new Timer;
	timer->t = t;
	
	timer->next = first_timer;
	first_timer = timer;

	timer_access = 1;
	return timer;
}

int
rm_timer (Timer *timer)
{
	if (timer == first_timer)
		first_timer = timer->next;
	else {
		Timer *i;
		for (i=first_timer; i && i->next != timer; i=i->next);
		i->next = i->next->next;
	}

	delete timer;
}

int
Timer::tick () {
	if (t>0) t--;
}

//********************************************************************
/*
struct Node
{
	Node *next, *prev;
	Node (Node *);
	~Node ();
};

int
Node::Node (Node *after)
{
	
};

Node::~Node ()
{
	if (prev)
		prev->next = next;
	if (next)
		next->prev = prev;
};
*/

//********************************************************************

int
PointerPos::warp_by (int dx, int dy)
{
	XWarpPointer (dpy, None, None, 0, 0, 0, 0, dx, dy);
}

int
PointerPos::warp_to (int x, int y)
{
	query ();
	XWarpPointer (dpy, None, None, 0, 0, 0, 0, x-rx, y-ry);
}

int
PointerPos::query ()
{
	XQueryPointer (dpy, root_window, &r, &w, &rx, &ry, &wx, &wy, &mask);
}

int
PointerPos::save ()
{
	query ();
	sx = rx;
	sy = ry;
}

int
PointerPos::restore ()
{
	warp_to (sx, sy);

	// flush motion events
	XSync (dpy, 0);
	while (event->check_mask (PointerMotionMask | EnterWindowMask))
		event->handle ();
}

//********************************************************************

int grab_active = 0;
Window grab_win = None;

int
grab_setup (Window w)
{
	int val;

	// grab keyboard
	val = XGrabKeyboard (dpy, w, TRUE, GrabModeAsync, GrabModeAsync, CurrentTime);

	if (val != GrabSuccess) {
	//	XBell (dpy, screen);
		return 0;
	};

	// grab pointer
	// grabbing root generates an EnterNotify event for root
	val = XGrabPointer
		(dpy, w, TRUE,
		 ButtonPressMask | ButtonReleaseMask | PointerMotionMask | EnterWindowMask,
		 GrabModeAsync, GrabModeAsync, None, cursor, CurrentTime);

	if (val != GrabSuccess) {
		XUngrabKeyboard (dpy, CurrentTime);
	//	XBell (dpy, screen);
		return 0;
	};

	grab_win = w;
	grab_active = 1;
	return 1;
}

int
grab_cancel ()
{
	// ungrab the pointer and keyboard
	XUngrabPointer (dpy, CurrentTime);
	XUngrabKeyboard (dpy, CurrentTime);
	grab_active = 0;
}

//********************************************************************

MyColor::MyColor () 		{ name = NULL; }
MyColor::MyColor (char *iname)	{ name = iname; }
int MyColor::operator= (char *tmp)	{ name = tmp; }
int MyColor::parse ()		{ lps->token (name); }

int
MyColor::init ()
{
	// loads a single color
	xcolor.pixel = 0;
	
	if (!XParseColor (dpy, root_colormap, name, &xcolor)) {
		fprintf (stderr, "swm: can't parse color %s\n", name);
		return 0;
	}
	else if (!XAllocColor (dpy, root_colormap, &xcolor)) {
		fprintf (stderr, "swm: can't alloc color %s\n", name);
		return 0;
	};
	
	pixel = xcolor.pixel;
#ifdef FONTS_XFT
	xftcolor.color.red = xcolor.red;
	xftcolor.color.green = xcolor.green;
	xftcolor.color.blue = xcolor.blue;
	xftcolor.color.alpha = 0xffff;
	xftcolor.pixel = xcolor.pixel;
#endif
	return 1;
}

/*
MyColor::~MyColor ()
{
	XFreeColors (...);
}
*/

//********************************************************************

MyFont::MyFont ()
{
	font = NULL;
	name = default_font;
}

int
MyFont::parse ()
{
	lps->stripcpy (name);
}

#ifdef FONTS_XFT
#define LoadFont(name) XftFontOpenName (dpy, screen, name);
#elif defined FONTS_XCORE
#define LoadFont(name) XLoadQueryFont (dpy, name);
#endif

int
MyFont::init ()
{
	font = LoadFont (name);

	if (!font) {
		if (name != default_font && strcmp(name, default_font)!=0) {
			fprintf (stderr, "swm: can't load font \"%s\", trying \"%s\" instead\n", name, default_font);
			font = LoadFont (default_font);
		};

		if (!font) {
			fprintf (stderr, "swm fatal: can't load default font \"%s\"", default_font);
			exit (1);
		};
	};

#ifdef FONTS_XFT
	ht = font->height;
#elif defined FONTS_XCORE
	ht = font->ascent + font->descent;
#endif
	y = font->ascent;
	printf ("%d\t %s\n", ht, name);
}

int MyFont::done ()
{
#ifdef FONTS_XCORE
	XFreeFont (dpy, font);
#endif
}

//********************************************************************

int
match_wildcards (char *pattern, char *string)
{
	// does `string' match `pattern'? '*' in pattern matches any sub-string
	// (including the null string) '?' matches any single char. For use
	// by filenameforall. Note that '*' matches across directory boundaries
	while (*string && *pattern) {
		if (*pattern == '?') {
			// match any character
			pattern++;
			string++;
		}
		else if (*pattern == '*') {
			// see if the rest of the pattern matches any trailing substring
			// of the string.
			pattern++;
			if (*pattern == 0) return 1;	// no trailing substring => match

			while (*string) {
				if (match_wildcards (pattern, string)) return 1;
				string++;
			};
			return 0;
		}
		else {
			// \ is an escape character
			if (*pattern == '\\') {
				pattern++;
				if (*pattern == 0) pattern--;
				// if backslash is the last character, it escapes itself even if it is not doubled
			}
			
			// it's ok, *string is nonzero (see while() condition) so the following if
			if (*pattern++ != *string++) return 0;
		}
	}

	if (*pattern == 0  &&  *string == 0) return 1;

	if (*string == 0  &&  strcmp (pattern, "*") == 0) return 1;

	return 0;
}

int
XftTextWidthUtf8 (XftFont *font, char *text, int len)
{
	XGlyphInfo extents;
	XftTextExtentsUtf8 (dpy, font, (XftChar8*) text, len, &extents);
	return extents.xOff;
}
