#include "global.hh"

#include <stdio.h>
#include <unistd.h>
#include <signal.h>
#include <sys/types.h>
#include <X11/Xatom.h>
#include <X11/keysym.h>

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

Event *event; // current event

int
Event::next ()
{
	XSync (dpy, 0);
	int is_event=0;
	// execute command queue first
	while (cq.first) cq.exec ();
	
	XNextEvent (dpy, &x);
	prepare ();
}

int
Event::check_mask (long mask)
{
	//  cq.exec ();
	if (XCheckMaskEvent (dpy, mask, &x)) {
		prepare ();
		return 1;
	}
	else
		return 0;
}

int
Event::mask (long mask)
{
	//  cq.exec ();
	XMaskEvent (dpy, mask, &x);
	prepare ();
}

int
Event::window (Window w, long mask)
{
	//  cq.exec ();
	XWindowEvent (dpy, w, mask, &x);
	prepare ();
}

int
Event::prepare () {
	handled = 0;

	win_list.find_dwin (&sw, x.xany.window);

	switch (x.type)
	{  
	case KeyPress:
	case KeyRelease:
		// pointer_warping() + menus + bindings uses keysym
		keysym = XLookupKeysym (&x.xkey, 0);
//		log_window.dbg ("'%s' %s", XKeysymToString(keysym), x.type==KeyPress?"press":"release");
		break;
	case Expose:
		r.set (x.xexpose.x, x.xexpose.y, x.xexpose.width, x.xexpose.height);
		break;
	}
}

int
handle_map_window (Window w)
{
	SWindow *sw;

	/*
	if (w == log_window.w || w == info.w || w == pager.w || al.find (w) || w == root)
	{
		return 0;
	}
	else */
	if (win_list.find_dwin (&sw, w)) {
		//    log_window.dbg ("map_frame");
		return 0;
	}
	else if (win_list.findw (&sw, w)) {
		// currently hidden, just ::show (=deiconify)
		sw->show ();
		// log_window.dbg ("unhide %s", sw->name);

		//    if (sw->flags & DoesWmTakeFocus)
		//      send_message (_XA_WM_TAKE_FOCUS, event->x.xmaprequest.time);
	}
	else {
#ifndef DEBUG
		XGrabServer (dpy);
#endif
		// look if the window still exists; we don't want to leave an
		// orphan window; since we have now the server grabbed, we will
		// get a DestroyNotify for it

		//    if (XGetGeometry (dpy, x.xmaprequest.window,
		//    		      &junk.root, &junk.x, &junk.y,
		//   		      &junk.wd, &junk.ht, &junk.bw, &junk.depth))

		XWindowAttributes at;
		if (XGetWindowAttributes (dpy, w, &at)  &&  !at.override_redirect) {
			win_list.add (swindow = new SWindow (w, 1));
			log_window.pr ("map %s", swindow->name);
			if (!swindow->starts_hidden ()) swindow->focus ();
		};

#ifndef DEBUG
		XUngrabServer (dpy);
#endif
		// in fact, the server may get already ungrabbed in
		// SWindow::SWindow (for example when placing window in non-opaque
		// mode -- see Outline::update -- but it is OK, since it takes
		// place after we set up traps for DestroyNotify (with
		// XSelectInput))
	}
}

int
Event::handle ()
{
	context = C_NO_CONTEXT;
	//log_window.dbg ("ev %d", x.type);

	if (sw) sw->dwin->handle_event ();
	if (x.xany.window == pager.w) pager.handle_event ();

	if (!handled) {
		/*
		wl.findw (&sw, x.xany.window);
		*/

	#ifdef DEBUG
	/*
		char *name;
		XFetchName (dpy, x.xany.window, &name);
		printf ("%s %d\n", name, x.type);
		XFree (name);
	*/
	#endif

		switch (x.type)
		{
		case KeyPress:			handle_KeyPress(); break;
		case ButtonPress:		handle_ButtonPress(); break;

		case Expose:			handle_Expose (); break;
//		case MapNotify:			handle_map_window (x.xany.window); break;
//		case FocusIn:			wl.handle_FocusIn (); break;
		case MapRequest:		handle_map_window (x.xmaprequest.window); break;
		case DestroyNotify:		handle_DestroyNotify (); break;
		case UnmapNotify:		handle_UnmapNotify(); break;
		case EnterNotify:		handle_EnterNotify(); break;
		case ConfigureRequest:		handle_ConfigureRequest(); break;
		case ConfigureNotify:		handle_ConfigureNotify(); break;
		case ClientMessage:		handle_ClientMessage(); break;
		case ReparentNotify:
			win_list.findw (&sw, x.xany.window);
			if (sw) log_window.pr("Reparent %s", sw);
			break;
		case PropertyNotify:
			if (win_list.findw (&sw, x.xany.window)) sw->handle_PropertyNotify();
			/*
			else {
				log_window.pr ("prop notify %s\n",
				XGetAtomName (dpy, event->x.xproperty.atom));
			};
			*/
			break;
		default:	break;
		}
	}
}

int
Event::handle_KeyPress ()
{
	if (x.xbutton.window==root_window || x.xany.window==pager.w || x.xany.window==log_window.w
		|| active_list.find (x.xany.window))
		// active windows = ROOT context
		context = C_ROOT;
	else if (win_list.findw (&sw, x.xany.window))
		context = C_WINDOW;

	// here's a real hack - some systems have two keys with the same keysym and different keycodes.
	// This converts all the cases to one keycode.

	//  printf ("%s\n", XKeysymToString(XKeycodeToKeysym (dpy, x.xkey.keycode, 0)));
	//  printf ("%d\n", x.xkey.keycode);
	//  x.xkey.keycode = XKeysymToKeycode (dpy, XKeycodeToKeysym (dpy, x.xkey.keycode, 0));
	//  log_window.pr("%x", x.xkey.state);

	bind_list.handle_event ();
	//  if (!handled)
	// no command bound to the key, send it to the client
	//    XSendEvent (dpy, x.xkey.window, FALSE, 0, &x);
}

int
Event::handle_ButtonPress ()
{
	if (x.xbutton.window == root_window) {
		if (x.xbutton.subwindow == None) {
			// discard propagated ButtonPresses
			context = C_ROOT;
			bind_list.handle_event ();
		}
	}
	else {
		active_list.handle_event();
		if (!handled) pager_view_handle_ButtonPress();
	}
}

int
Event::handle_Expose()
{
	//if (x.xexpose.count > 0)
	//    return 0;

	// wait for other exposes
	//  XSync (dpy, 0);
	//  Window w = x.xany.window;
	//  while (XCheckTypedWindowEvent (dpy, w, Expose, &x));    

	if (x.xany.window == log_window.w)
		log_window.draw ();
	else
		win_list.handle_Expose (x.xany.window);
}

int
Event::handle_ClientMessage ()
{
	if (win_list.findw (&sw, x.xclient.window)) {
		// debug begin
		//    log_window.pr ("cmsg %s %s\n", sw->name, XGetAtomName (dpy, x.xclient.message_type));

		// debug end
		if (x.xclient.message_type == _XA_WM_CHANGE_STATE)
			if (x.xclient.data.l[0] == IconicState)
				sw->hide ();
	}
}

int
Event::handle_DestroyNotify ()
{
	if (win_list.findw (&sw, x.xdestroywindow.window)) {
/*
		SWindow *swindow;
		if (sw == win_list.focus) {
			// try to pass the focus to transient parent
			if (sw->flags & TRANSIENT && win_list.findw (&swindow, sw->transient_for))
			swindow->focus ();
		};
*/
		win_list.rm (sw);
/*
		if (sw == win_list.focus) {
			// the focus hasn't been passed to transient parent, it is now on
			// parent ("RevertToParent"), so pass it to the window the pointer
			// is in (or to root)
			win_list.focus_find ();
		}
*/
	}
}

int
Event::handle_UnmapNotify ()
{
	// in fact handling of WithdrawnState
	// Who needs WithdrawnState? I don't know.

	//  XGrabServer (dpy);
	//  XSync (dpy, 0); // wait for DestroyNotifies -- i had problems with
	// gimp, this helped

	Window w = x.xunmap.window;

	if (XCheckTypedWindowEvent (dpy, w, DestroyNotify, &x))
		handle_DestroyNotify ();
	else if (win_list.findw (&sw, x.xunmap.window)) {
		//    log_window.pr ("Unmap %s", sw->name);
		if (x.xunmap.send_event==TRUE && x.xunmap.event==root_window && x.xunmap.from_configure==FALSE) {
			// log_window.pr ("Normal->Withdrawn! %s", sw->name);
			// synthetic UnmapNotify means transition Normal->Withdrawn

			// example: run XMMS with playlist, hide playlist (with window
			// command), then press "PL"; this will happen (since we won't
			// get the real unmap notify, which comes first (in other cases,
			// so that we don't wl.findw the window and don't get here))

			sw->withdraw ();
			win_list.rm (sw);
		}
		else if (sw->unmap_events) {
			// this UnmapNotify was generated by us; we generate them in
			// SWindow::hide ()
			sw->unmap_events--;
		}
		else if (sw->map_state == IconicState  &&  x.xunmap.send_event == FALSE) {
			//      log_window.pr ("%s", sw->name);
			; // do nothing
		}
		else {
			//      log_window.pr ("Normal->Withdrawn! %s", sw->name);

			// where to get ICCCM: see the X source files, xc/doc directory

			// ICCCM says: for compatibility with obsolete clients, we
			// should trigger the transition Normal->Withdrawn also on
			// receipt of real UnmapNotify

			sw->withdraw ();
			win_list.rm (sw);
		}
	}

	// this is for Netscape (see file BUGS)
	//  XSetInputFocus (dpy, PointerRoot, RevertToPointerRoot, CurrentTime);

	//  XUngrabServer (dpy);
}

int
Event::handle_EnterNotify ()
{
	/**/ if (win_list.findw (&sw, x.xcrossing.window)) {
		sw->focus ();
		#ifdef DEBUG
		//log_window.dbg("enternotify %s", event->sw->name);
		#endif
	}
	else if (win_list.find_pager_view (&sw, x.xcrossing.window)) {
		#ifdef DEBUG
		//log_window.dbg ("enternotify %s (pager)", sw->name);
		#endif
	//	sw->focus ();
	}
	else if (x.xcrossing.window == root_window) {
		//if (swm_mode != MODE_SCROLL)
		win_list.focus_root ();

		#ifdef DEBUG
		//log_window.dbg ("enternotify <root>");
		#endif

		// observation: xterm will not "empty" its cursor when it's alone on a desktop
		// (it does when xcrossing to non-root or when on a desktop together with another
		// windows) dont' know why -- it does so when crossing to root for the first time)

		// XSetInputFocus (dpy, PointerRoot, RevertToPointerRoot, CurrentTime);
		// log_window.dbg("enternotify root");
	}
	else {
		active_list.handle_event ();
		#ifdef DEBUG
		// log_window.dbg ("enternotify <active area> or <unknown>");
		#endif
		//    wl.focus_root ();
	}
}

int
Event::handle_ConfigureNotify ()
{
	if (x.xconfigure.window == log_window.w)
		log_window.handle_ConfigureNotify ();
}

int
Event::handle_ConfigureRequest () {
#define cre x.xconfigurerequest
	if (win_list.findw (&sw, cre.window))
		sw->handle_ConfigureRequest ();
	else {
		//    log_window.pr ("cfg_req");

		XWindowChanges xwc;
		unsigned long mask;
	
		//    mask = cre.value_mask;
		mask = cre.value_mask & (CWX | CWY | CWWidth | CWHeight | CWBorderWidth | CWSibling | CWStackMode);
		//    log_window.pr ("%d %d", cre.value_mask, mask);
		xwc.x = cre.x;
		xwc.y = cre.y;
		xwc.width = cre.width;
		xwc.height = cre.height;
		xwc.border_width = cre.border_width;
		xwc.sibling = cre.above;
		xwc.stack_mode = cre.detail;

		//XConfigureWindow (dpy, cre.window, cre.value_mask, &xwc);
		XConfigureWindow (dpy, cre.window, mask, &xwc);
		XSync (dpy, 0);
	}
#undef cre
}
