#include "global.hh"

#include <stdio.h>
#include <signal.h>
#include <string.h>

#include "main.hh"
#include "menus.hh"
#include "misc.hh"
#include "pager.hh"
#include "events.hh"
#include "config.hh"
#include "boxes.hh"
#include "log.hh"

Desktop dsk;
Pager pager;
SWindow *spager;

Desktop::Desktop ()
{
	// default config
	cols = 4; rows = 3;
	x_start = 1; y_start = 0;
	lock = 1;
}

int
Desktop::init ()
{
	wd = root.wd * cols;
	xmax = wd - root.wd;
	ht = root.ht * rows;
	ymax = ht - root.ht;
	Atom actual_type; int actual_format; unsigned long nitems; unsigned long bytes_after;
	SWM_Pager_Prop *xs_pager_prop;
	XGetWindowProperty (dpy, root_window, _XA_SWM_PAGER, 0L, sizeof (SWM_Pager_Prop), 0, AnyPropertyType,
		  &actual_type, &actual_format, &nitems, &bytes_after, (unsigned char **) &xs_pager_prop);

	if (actual_type != None) {
		log_window.pr("starting on X saved desktop position: %+d%+d", xs_pager_prop->x, xs_pager_prop->y);

		x = xs_pager_prop->x;
		y = xs_pager_prop->y;
		restrict (x, 0, xmax);
		restrict (y, 0, ymax);
		pager_prop.x = x;
		pager_prop.y = y;
	}
	else {
		x = x_start * root.wd;
		y = y_start * root.ht;
		save_prop ();
	}
}

int
Desktop::save_prop ()
{
	pager_prop.x = dsk.x;
	pager_prop.y = dsk.y;
	XChangeProperty (dpy, root_window, _XA_SWM_PAGER, _XA_SWM_PAGER,
		8, PropModeReplace, ((unsigned char *) &pager_prop), sizeof(SWM_Pager_Prop));
}

int
Desktop::viewport_to (int nx, int ny)
{
	if (lock) {
		// range-check
		if (nx > xmax) nx = xmax; else if (nx < 0) nx = 0;
		if (ny > ymax) ny = ymax; else if (ny < 0) ny = 0;
	}
	
	int dx, dy;
	dx = x - nx; // if desktop go left, windows go right etc.
	dy = y - ny;

	x = nx;
	y = ny;

	if (!lock) {
		if (x > xmax) x = xmax; else if (x < 0) x = 0;
		if (y > ymax) y = ymax; else if (y < 0) y = 0;
	}

	SWindow *i;
	if (dx || dy) {
		for (i = win_list.first; i; i = i->next)
			i->move (dx, dy);
	}

	pager.update ();
}

int
Desktop::scroll (int dx, int dy, int type)
{
	SWindow *i;

	dx *= root.wd; dy *= root.ht;	// conversion of dx, dy from "screensize units" to pixels

	switch (type)
	{
	case ST_ORDINARY:
		// "viewport ('cursor') movement" (exception: sticked windows)
		// no když to přirovnávám k editoru, tak "how bout regexps?" ale nějak
		// mě nenapadá analogie pro případ oken - blá blá - no názvy jsou
		// stringy a jsou vedle sebe hm ale "to gró" je stejně v 2D pohybu, "f tom
		// se to zas liší od text. editoru" který je spíš string (1D) oriented
		
		viewport_to (x + dx, y + dy);
		break;

	case ST_SWAP:
		// swaps current desktop (defined as windows overlaping it) with the neighboring one
		if (lock) {
			if (x + dx > xmax || x + dx < 0) dx = 0;
			if (y + dy > ymax || y + dy < 0) dy = 0;
		};

		if (dx || dy) {
			x += dx;
			y += dy;
			
			if (!lock) {
				if (x > xmax) x = xmax; else if (x < 0) x = 0;
				if (y > ymax) y = ymax; else if (y < 0) y = 0;
			}

			for (i = win_list.first; i; i = i->next)
				if (i->dwin->wr.overlap (0, 0, root.wd, root.ht)) {
					// keep the window on current desktop (as if sticked during scroll)
					i->move (0, 0);
				}
				else if (i->dwin->wr.overlap (dx, dy, root.wd, root.ht)) {
					i->move (-2*dx, -2*dy);
				}
				else
					i->move (-dx, -dy);

			pager.update ();
		};
		break;

	case ST_WRAP:
		// "wrapped scroll"
		// as if all the windows were sticked + throws the one
		// shifted beyond (virtual) desktop boundaries to the opposing (empty) part of it
		if (!lock) break;

		if (x + dx > xmax || x + dx < 0) dx = 0;
		if (y + dy > ymax || y + dy < 0) dy = 0;
	
		if (dx || dy) {
			int desk_wd = cols * root.wd;
			int desk_ht = rows * root.ht;

			// triggers (jsou případně šouplé mimo desktop na ty strany kde si nezahrajou)
			int left = -x - dx;
			int right = -x + desk_wd - dx;
			int ylo = -y - dy;
			int yhi = -y + desk_ht - dy;
				
			// pozorování: nezávisle na tom kde jsem přehazuju (pro stejná dx, dy) stejnou část desktopu
			// (z pohledu pohledu na pager)

			// imagine moving of pager decoration (-dx,-dy) with windows fixed
			int orig_x=x;
			int orig_y=y;
			x += dx;
			y += dy;

			for (i = win_list.first; i; i = i->next) 
				if (i->dwin->wr.overlap (-orig_x, -orig_y, desk_wd, desk_ht))
				{
				// don't want to mess with windows outside virt. desktop (keep
				// locking possible/making sense - though in ST_WRAP it doesn't
				// change anything)
					Rect drect = i->dwin->wr;
					int xwrap=0;
					int ywrap=0;

					if (drect.x >= right) xwrap = -desk_wd;
					else if (drect.x <left) xwrap = +desk_wd;

					if (drect.y >= yhi) ywrap = -desk_ht;
					else if (drect.y <ylo) ywrap = +desk_ht;

					i->move(xwrap, ywrap);
				}

			pager.update ();
		};
		break;
	};

	if (swm_mode != MODE_SCROLL) {

	if (GrabSuccess != XGrabKeyboard (dpy, root_window, true, GrabModeAsync, GrabModeAsync, CurrentTime))
		// (we have) at least moved once if we don't get the grab (rather theoretical case)
		return 0;
	else {
		swm_mode = MODE_SCROLL;
		#ifdef DEBUG
		//log_window.clear();
		//log_window.pr("scroll-begin %s", (wl.focus != NULL ? wl.focus->name : "<root>"));
		#endif
		if (spager) spager->raise ();

		while (swm_mode == MODE_SCROLL) {
			event->next ();//mask (MENU_EVENT_MASK);

			if (event->x.type==KeyRelease) {
				switch (event->keysym) {
					case XK_Shift_R: case XK_Shift_L:
					case XK_Meta_L: case XK_Meta_R:
					case XK_Alt_L: case XK_Alt_R:		
					case XK_Super_L: case XK_Super_R:	// Super = winlogo key
						swm_mode = MODE_RUN;
						event->handled = 1;
						break;
				}
			}
			if (!event->handled) event->handle ();
		}
		
		if (spager) spager->lower ();
		XUngrabKeyboard(dpy, CurrentTime);
		//wl.focus_find ();
		#ifdef DEBUG
		//log_window.pr("scroll-end");
		#endif
	}

	}
}

int
ConfigParser::parse_pager_style () {
	skip (".");

	if (match ("scale")) {
		num (pager.scale);
		pager.wd = dsk.cols * root.wd / pager.scale;
		pager.ht = dsk.rows * root.ht / pager.scale;
	}
	else if (match ("pos"))
		pos (pager.x, pager.y, pager.wd, pager.ht);

	// else if (match ("geometry")) geom (pager.x, pager.y, pager.wd, pager.ht);
	else if (match ("font")) pager.font.parse ();
	else if (match ("viewport")) pager.view_fore.parse ();
	else if (match ("winborder")) pager.win_border.parse ();
	else
		error ("bad PagerStyle command: ");
}

Pager::Pager ()
{
	// default config
	x = y = 0;
	wd = ht = 0;
	scale = 8;
	font.name = (char *) "fixed";
	view_fore.name = grey;
	win_border.name = grey;
}

int
Pager::init ()
{
	if (!wd || !ht) {
		wd = dsk.cols * root.wd / scale;
		ht = dsk.rows * root.ht / scale;
	}

	win_bkg = new MyPixmap;
	bkg = new MyPixmap;

	// get root pixmap
	//  XWindowAttributes rat;
	//  XGetWindowAttributes (dpy, root_window, &rat);
	// oops! XGetWindowAttributes doesn't return background_pixmap

	// main window
	XSetWindowAttributes at;
	at.cursor = cursor;
//	at.do_not_propagate_mask = ButtonPressMask | ButtonReleaseMask;
	at.event_mask = KeyPressMask | KeyReleaseMask
		| ButtonPressMask | ButtonReleaseMask | PointerMotionMask | ButtonMotionMask
		| EnterWindowMask | LeaveWindowMask | ExposureMask;
	vm = CWCursor | CWEventMask;//| CWDontPropagate 

	if (root_mypixmap->pm) {
		// shrink
		bkg->shrink (root_mypixmap, scale);
		at.background_pixmap = bkg->pm;
		vm |= CWBackPixmap;
	}
	else if (root_mycolor.name) {
		at.background_pixel = root_mycolor.pixel;
		vm |= CWBackPixel;
	}
	else {
		at.background_pixel = BlackPixel (dpy, screen);
		vm |= CWBackPixel;
	};

	w = XCreateWindow (dpy, root_window, x, y, wd, ht, 0,
		 CopyFromParent, InputOutput, CopyFromParent, vm, &at);

	XLowerWindow (dpy, w);
	XMapWindow (dpy, w);

	XSizeHints sizehints = {
		PMinSize | PResizeInc | PBaseSize | PWinGravity | USPosition,
		0, 0, 100, 100, // x, y, width and height
		10, 10, // Min width and height
		0, 0, // Max width and height
		1, 1, // Width and height increments
		{0, 0},
		{0, 0}, // Aspect ratio - not used
		1, 1, // base size
		(NorthWestGravity) // gravity
	};
	XSetWMNormalHints (dpy, w, &sizehints);
	
	XTextProperty name;
	char *pager_name = (char *) "SWM Pager";
	XStringListToTextProperty (&pager_name, 1, &name);
	XSetWMName (dpy, w, &name);
	XSetWMIconName (dpy, w, &name);
	XFree ((char *) name.value);

	if (dwin_style.bkg->pm)
		win_bkg->shrink (dwin_style.bkg, scale);

	// init colors
	font.init ();
	win_border.init ();

	#ifdef FONTS_XCORE
	gcv.foreground = dwin_style.title.pixel;
	gcv.function = GXcopy;
	gcv.font = font.font->fid;
	gcm = GCForeground | GCFunction | GCFont;
	textGC = XCreateGC (dpy, root, gcm, &gcv);
	#endif

	// create GC
	gcv.function = GXor;
	view_fore.init ();
	gcv.foreground = view_fore.pixel;
	gcm = GCFunction | GCForeground;
	vpGC = XCreateGC (dpy, root_window, gcm, &gcv);
	XSync (dpy, 0);

	update ();
}

int
Pager::done () {
	XDestroyWindow (dpy, w);
	delete win_bkg;
	delete bkg;
}

int
Pager::update () {
	Rect oldv = v;
	// update the viewport indicator
	v.x = dsk.x * wd / dsk.wd;
	v.y = dsk.y * ht / dsk.ht;
	v.wd = root.wd * wd / dsk.wd;
	v.ht = root.ht * ht / dsk.ht;

	if (v != oldv)
		XClearArea (dpy, w, oldv.x, oldv.y, oldv.wd, oldv.ht, FALSE);

	XFillRectangle (dpy, w, vpGC, v.x, v.y, v.wd, v.ht);
	if (dsk.pager_prop.x != dsk.x || dsk.pager_prop.y != dsk.y) dsk.save_prop();
}

int
Pager::p2ir (int x, int y, int &rx, int &ry) {
	// integer multiplies of root.wd, root.ht
	rx = root.wd * ifrac (x, wd, dsk.cols);
	ry = root.ht * ifrac (y, ht, dsk.rows);
}

int
Pager::p2r (int x, int y, int &rx, int &ry) {
	rx = ifrac (x, wd, dsk.wd);
	ry = ifrac (y, ht, dsk.ht);
}

int
pager_view_do_move (SWindow *sw)
{
	int px, py;

	switch (event->x.xbutton.button) {
	case 1:
		ppos.save ();
		px = ppos.rx;
		py = ppos.ry;
		break;
	case 3:
		sw->switch_to ();
		px = dsk.x + sw->dwin->wr.x + sw->dwin->wr.wd/2;
		py = dsk.y + sw->dwin->wr.y + sw->dwin->wr.ht/2;
		px = ifrac (px, dsk.wd, pager.wd);
		py = ifrac (py, dsk.ht, pager.ht);
		px += spager->a->x + spager->dwin->wr.x;
		py += spager->a->y + spager->dwin->wr.y;
	default:
		return 0;
	};

	int done = 0;
	int moved = 0;
		
	if (!grab_setup (pager.w)) return 0;

	int orig_spager_flags = spager->flags;
	spager->flags |= KEEPONTOP;

	int orig_border_stick = border_stick;
	int orig_origin_stick = origin_stick;
	border_stick = 0;

	sw->raise ();
	Rect wr0 = sw->dwin->wr;
	int dskx0 = dsk.x;
	int dsky0 = dsk.y;

	while (!done) {
		event->mask (ButtonPressMask | ButtonReleaseMask | PointerMotionMask
		| KeyPressMask | KeyReleaseMask | EnterWindowMask | ExposureMask);

		switch (event->x.type)
		{
		case Expose:
			event->handle ();
			break;
		case ButtonRelease:
			done = 1;
			break;
		case KeyPress:
			switch (event->keysym) {
			case XK_Escape:
				sw->dwin->wr = wr0;
				sw->sync_to_xserver ();
				done = 1;
				break;
			case XK_Control_L:
			case XK_Control_R:
				origin_stick = 0;
				break;
			};
			event->handled = 1;
			break;
		case KeyRelease:
			switch (event->keysym) {
			case XK_Control_L:
			case XK_Control_R:
				origin_stick = orig_origin_stick;
				break;
			};
			event->handled = 1;
			break;
		case EnterNotify:
		case MotionNotify:
			int nx, ny; // diff of new x, y from old x, y
			pager.p2r (event->x.xbutton.x_root - px, event->x.xbutton.y_root - py, nx, ny);
			if (event->x.xmotion.state & Button1Mask) {
				nx += wr0.x;
				ny += wr0.y;

				sw->stick (nx, ny, wr0.x, wr0.y, pager.scale);
				sw->move_to (nx, ny);
			}
			else if (event->x.xmotion.state & Button3Mask) {
				//	nx = root.wd * ifrac (nx + sgn(nx)*root.wd/2, root.wd, 1);
				//	ny = root.ht * ifrac (ny + sgn(ny)*root.ht/2, root.ht, 1);
				nx = root.wd * iifrac (nx + root.wd/2, root.wd);
				ny = root.ht * iifrac (ny + root.ht/2, root.ht);

				int f0 = sw->flags;
				sw->flags |= STICKY;
				dsk.viewport_to (dskx0 + nx, dsky0 + ny);
				sw->flags = f0;
			}

			break;
		}
	}

	spager->flags = orig_spager_flags;
	border_stick = orig_border_stick;
	origin_stick = orig_origin_stick;
	grab_cancel ();

	if (sw->dwin->wr.x == wr0.x && sw->dwin->wr.y == wr0.y) {
		// just a click, so switch to that window

		// panel->lower ();
		spager->lower ();
		log_swindow->lower ();
		sw->switch_to ();
	}
}

int
pager_view_handle_ButtonPress ()
{
	SWindow *sw;
	if (win_list.find_pager_view (&sw, event->x.xbutton.window)) {
	// mouse operations on pager
		switch (event->x.xbutton.button) {
		case 1:
			// possible improvement: shift+left = close window, as in opera
			//      XQueryDeviceState (dpy, 
			pager_view_do_move (sw);
			break;
		case 2:
			//      sw->destroy ();
			sw->close ();
			break;
		case 3:
			if (pager_winops_menu) pager_winops_menu->exec ();
			break;
		case 4:
			sw->pager_view_draw (-1); // scroll the name up
			break;
		case 5:
			// scroll the name down
			sw->pager_view_draw (+1); // scroll the name down
			break;
		}
	}
}

int
Pager::handle_event ()
{
	// viewport movement with mouse click and drag on empty portion of pager

	int nx, ny;
	
	switch (event->x.type) {
	case ButtonPress:
		if (event->x.xbutton.button == 1) {
			spager->raise();
		// left button - move to integer multiple of the basic screen
			p2ir (event->x.xbutton.x, event->x.xbutton.y, nx, ny);
			dsk.viewport_to (nx, ny);
		}
		else if (event->x.xbutton.button == 3) {
			log_swindow->raise();
		}
		/*
		// right button - move float multiple of the basic screen
		else if (event->x.xbutton.button == 3) {
			p2r (event->x.xbutton.x, event->x.xbutton.y, nx, ny);
			nx -= root.wd/2; ny -= root.ht/2;
			dsk.viewport_to (nx, ny);
		}*/
		event->handled=1;
		break;
	case ButtonRelease:
		if (event->x.xbutton.button = 1) {
			spager->lower();
			event->handled=1;
		}
		break;
	case MotionNotify:
		// see p. 188 of xlib.ps, down on the page: "...the X server
		// automatically *starts an active grab* (after the ButtonPress,
		// until ButtonRelease) for the client receiving the MotionNotify
		// event->..."

		if (event->x.xmotion.state & Button1Mask) {
			p2ir (event->x.xbutton.x, event->x.xbutton.y, nx, ny);
			dsk.viewport_to (nx, ny);
		};
		/*else if (event->x.xmotion.state & Button3Mask) {
			p2r (event->x.xbutton.x, event->x.xbutton.y, nx, ny);
			nx -= root.wd/2; ny -= root.ht/2;
			dsk.viewport_to (nx, ny);
		}*/
		event->handled=1;
		break;
	case LeaveNotify:
		// see page 194 of xlib.ps
		//    /*
		if (event->x.xcrossing.detail != NotifyInferior) {
			// we are really leaving the pager's window, and not entering
			// one of the subwindows representing the windows on the desktop
		}
		//    */
		break;
		/*
	case EnterNotify:
		// the following doesn't work
		if (event->x.xcrossing.detail == NotifyInferior) {
			// we are entering one of the subwindows representing the
			// windows on the desktop
			SWindow *sw;
			if (wl.find_pager_view (sw, event->x.xcrossing.subwindow)) {
				log_window.dbg ("ent-here!");
				sw->focus ();
			};
		};
		break;
		*/
	case Expose:
		update ();
		event->handled = 1;
		break;
	}
}
