#include "global.hh"

#include <ctype.h>
#include <stdlib.h>
#include <X11/Xlib.h>
#include <X11/keysym.h>

#include "bindings.hh"

#include "commands.hh"
#include "config.hh"
#include "events.hh"
#include "main.hh"
#include "log.hh"

BindList bind_list;
ActiveList active_list;

int double_click_ms;

unsigned int mods_used = (ShiftMask | ControlMask | Mod1Mask | Mod4Mask);

int
Binding::parse_mods ()
{
	mods = 0;

	char c;
	lps->skip_space ();
	while (!lps->space() && lps->get_char (c)) {
		if (isupper (c))
			c = tolower (c);
		
		// the keys must be in lower case!
		/**/ if (c == 'n'); // no modifier
		else if (c == 's') mods |= ShiftMask;
		else if (c == 'c') mods |= ControlMask;
		else if (c == 'm') mods |= Mod1Mask; // Alt = Meta
		else if (c == 'a') mods |= AnyModifier;
		else if (c == 'w') mods |= Mod4Mask; // "winlogo" key = "Super"
		else lps->error ("bad modifiers: ");
	}
}

int
MouseBind::parse_context ()
{
	context = 0;
	
	char c;
	lps->skip_space ();
	while (!lps->space() && lps->get_char (c)) {
		if (isupper (c))
			c = tolower (c);
		
		// the keys must be in lower case!
		if (c == 'w') context |= C_WINDOW;
		else if (c == 'r') context |= C_ROOT;
		else if (c == 'd') context |= C_DECOR;
		else if (c == 'x') context |= C_DBL_DECOR;
		else if (c == 'a') context |= C_WINDOW | C_ROOT | C_DECOR;
	};

	if (context == 0)
		lps->error ("bad context");
}

MouseBind::MouseBind ()
{
	lps->num (button);
	parse_context ();
	parse_mods ();
	lps->cmd (cmd);
}

int
MouseBind::grab (Window w)
{
	if (context & C_WINDOW)	{
		if (button > 0)
			XGrabButton (dpy, button, mods,
			 w, TRUE, ButtonPressMask,
			 GrabModeAsync, GrabModeAsync, None, None);
		else
			XGrabButton (dpy, AnyButton, mods, 
			 w, TRUE, ButtonPressMask,
			 GrabModeAsync, GrabModeAsync, None, None);
	}
}

int
MouseBind::handle_ButtonPress ()
{
	if ((button == event->x.xbutton.button || button == 0) &&
			(context & event->context) && 
			(mods == (event->x.xbutton.state & mods_used) || mods == AnyModifier))
	{
		cmd->exec ();
		event->handled = 1;
	}
}

int
KeyBind::parse_context ()
{
	context = 0;
	
	char c;
	lps->skip_space ();
	while (!lps->space() && lps->get_char (c)) {
		if (isupper (c))
			c = tolower (c);
		
		// the keys must be in lower case!
		if (c == 'w') context |= C_WINDOW;
		else if (c == 'r') context |= C_ROOT;
		else if (c == 'a') context |= C_WINDOW | C_ROOT;
	};

	if (context == 0)
		lps->error ("bad context");
}

KeyBind::KeyBind ()
{
	char *key_tok;
	lps->token (key_tok);
	char *tail;
	KeySym keysym = XStringToKeysym (key_tok);
	if (keysym != NoSymbol)
		keycode = XKeysymToKeycode (dpy, keysym);
	else {
		keycode = strtol (key_tok, &tail, 10);
		if (keycode == (KeyCode) 0) lps->error ("bad keysym or keycode: ");
	};
	delete key_tok;
	
	parse_context ();
	parse_mods ();
	lps->cmd (cmd);
}

int
KeyBind::grab (Window w)
{
	// keycode 0 means AnyKey; don't want to pass this through
	if (keycode != 0  &&  context & C_WINDOW) {
		XGrabKey (dpy, keycode, mods, w, TRUE, GrabModeAsync, GrabModeAsync);

		if (mods != AnyModifier) {
			XGrabKey (dpy, keycode, mods | LockMask /* CapsLock */, w, TRUE, GrabModeAsync, GrabModeAsync);
			XGrabKey (dpy, keycode, mods | Mod2Mask /* NumLock */, w, TRUE, GrabModeAsync, GrabModeAsync);
			XGrabKey (dpy, keycode, mods | Mod2Mask | LockMask /* both */, w, TRUE, GrabModeAsync, GrabModeAsync);
		}
	}
}

int
KeyBind::handle_KeyPress ()
{
	if (keycode == event->x.xkey.keycode
		&& (mods == (event->x.xkey.state & mods_used) || mods == AnyModifier)
		&& context & event->context	)
	{
		cmd->exec ();
		event->handled = 1;
	}
}

BindList::BindList ()
{
	first = NULL;
}

int
BindList::add (Binding *n)
{
	n->next = first;
	first = n;
}

BindList::~BindList ()
{
	Binding *t;
	while (first) {
		t = first;
		first = first->next;
		delete t;
	};
}

int
BindList::handle_event ()
{
	Binding *i;
	switch (event->x.type)
	{
	case KeyPress:
		for (i = first; i != NULL && !event->handled; i = i->next) i->handle_KeyPress ();
		break;
	case ButtonPress:
		for (i = first; i != NULL && !event->handled; i = i->next) i->handle_ButtonPress ();
		break;
	}
}

int
BindList::grab (Window w)
{ 
	// grab needed events for the window
	Binding *i;
	for (i = first; i != NULL; i = i->next) i->grab (w);
}

int
BindList::ungrab (Window w)
{
	XUngrabKey (dpy, AnyKey, AnyModifier, w);
	XUngrabButton (dpy, AnyButton, AnyModifier, w);
}

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

ActiveList::ActiveList ()
{
	back.name = black;
	n = 0;
	first = NULL;
}

int
ActiveList::add (ActiveArea *t)
{
	n++;
	t->next = first;
	first = t; 
}

int
ActiveList::init ()
{
	back.init ();

	ActiveArea *aa;
	for (aa = first; aa; aa = aa->next)
		aa->init ();
}

int
ActiveList::find (ActiveArea *&aa, Window w)
{
	for (aa = first; aa; aa = aa->next)
		if (aa->w == w)
			return 1;

	return 0;
}

int
ActiveList::find (Window w)
{
	ActiveArea *aa;
	return find(aa, w);
}

int
ActiveList::handle_event ()
{
	ActiveArea *i;
	switch (event->x.type)
	{
	case EnterNotify:
		if (find (i, event->x.xcrossing.window) && i->type==AA_HOVER) {
			i->cmd->exec ();
			event->handled = 1;
		};
		break;
	case ButtonPress:
		if (find (i, event->x.xany.window) && i->type==AA_CLICK) {
			i->cmd->exec ();
			event->handled = 1;
		};
		break;
	}
}

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

ActiveArea::ActiveArea (int itype)
{
	type = itype;
	lps->geom (x, y, wd, ht);
	lps->cmd (cmd);
}

int
ActiveArea::init ()
{
	XSetWindowAttributes at;
	at.background_pixel = active_list.back.pixel;
	at.cursor = cursor;
	at.do_not_propagate_mask =
		//    KeyPressMask |
		ButtonPressMask | ButtonReleaseMask;
	at.event_mask =
		KeyPressMask | KeyReleaseMask
		| ButtonPressMask | ButtonReleaseMask | PointerMotionMask
		| ButtonMotionMask | EnterWindowMask;

	w = XCreateWindow
		(dpy, root_window, x, y, wd, ht, 0,
		 CopyFromParent, InputOutput, CopyFromParent,
		 CWBackPixel | CWCursor | CWDontPropagate | CWEventMask, &at);

	XMapRaised (dpy, w);
}
