/* Copyright (c) 1994-1996 David Hogan, see README for licence details */
#include <stdio.h>
#include <stdlib.h>
#include <X11/X.h>
#include <X11/Xos.h>
#include <X11/Xlib.h>
#include <X11/Xutil.h>
#include <X11/Xatom.h>
#include <X11/extensions/shape.h>
#include "dat.h"
#include "fns.h"
#include "patchlevel.h"

void
mainloop(int shape_event)
{
	XEvent ev;

	for(;;){
		getevent(&ev);

#ifdef	DEBUG_EV
		if(debug){
			ShowEvent(&ev);
			printf("\n");
		}
#endif
		switch (ev.type){
		default:
#ifdef	SHAPE
			if(shape && ev.type == shape_event)
				shapenotify((XShapeEvent *)&ev);
			else
#endif
				fprintf(stderr, "rio: unknown ev.type %d\n", ev.type);
			break;
		case KeyPress:
			keypress(&ev.xkey);
			break;
		case KeyRelease:
			keyrelease(&ev.xkey);
			break;
		case ButtonPress:
			button(&ev.xbutton);
			break;
		case ButtonRelease:
			break;
		case MapRequest:
			mapreq(&ev.xmaprequest);
			break;
		case ConfigureRequest:
			configurereq(&ev.xconfigurerequest);
			break;
		case CirculateRequest:
			circulatereq(&ev.xcirculaterequest);
			break;
		case UnmapNotify:
			unmap(&ev.xunmap);
			break;
		case CreateNotify:
			newwindow(&ev.xcreatewindow);
			break;
		case DestroyNotify:
			destroy(ev.xdestroywindow.window);
			break;
		case ClientMessage:
			clientmesg(&ev.xclient);
			break;
		case ColormapNotify:
			cmap(&ev.xcolormap);
			break;
		case PropertyNotify:
			property(&ev.xproperty);
			break;
		case SelectionClear:
			fprintf(stderr, "rio: SelectionClear (this should not happen)\n");
			break;
		case SelectionNotify:
			fprintf(stderr, "rio: SelectionNotify (this should not happen)\n");
			break;
		case SelectionRequest:
			fprintf(stderr, "rio: SelectionRequest (this should not happen)\n");
			break;
		case EnterNotify:
			enter(&ev.xcrossing);
			break;
		case LeaveNotify:
			leave(&ev.xcrossing);
			break;
		case ReparentNotify:
			reparent(&ev.xreparent);
			break;
		case FocusIn:
			focusin(&ev.xfocus);
			break;
		case MotionNotify:
			motionnotify(&ev.xmotion);
			break;
		case Expose:
		case NoExpose:
		case FocusOut:
		case ConfigureNotify:
		case MapNotify:
		case MappingNotify:
		case GraphicsExpose:
			/* not interested */
			trace("ignore", 0, &ev);
			break;
		}
	}
}


void
configurereq(XConfigureRequestEvent *e)
{
	XWindowChanges wc;
	Client *c;

	/* we don't set curtime as nothing here uses it */
	c = getclient(e->window, 0);
	trace("configurereq", c, (XEvent *)e);

	e->value_mask &= ~CWSibling;

	if(c){
		if(e->value_mask & CWX)
			c->x = e->x;
		if(e->value_mask & CWY)
			c->y = e->y;
		if(e->value_mask & CWWidth)
			c->dx = e->width;
		if(e->value_mask & CWHeight)
			c->dy = e->height;
		if(e->value_mask & CWBorderWidth)
			c->border = e->border_width;

		if(c->dx >= c->screen->width && c->dy >= c->screen->height)
			c->border = 0;
		else
			c->border = BORDER;

		if(e->value_mask & CWStackMode){
			if(e->detail == Above)
				top(c);
			else
				e->value_mask &= ~CWStackMode;
		}
		e->value_mask |= CWX|CWY|CWHeight|CWWidth;

		if(c->parent != c->screen->root && c->window == e->window){
			wc.x = c->x - c->border;
			wc.y = c->y - c->border;
			wc.width = c->dx+c->border+c->border;
			wc.height = c->dy+c->border+c->border;
			wc.border_width = 1;
			wc.sibling = None;
			wc.stack_mode = e->detail;
			XConfigureWindow(dpy, c->parent, e->value_mask, &wc);

			if(e->value_mask & CWStackMode){
				top(c);
				active(c);
			}
		}
	}

	if(c && c->parent != c->screen->root){
		wc.x = c->border;
		wc.y = c->border;
	}else {
		wc.x = c->x;
		wc.y = c->y;
	}
	wc.width = c->dx;
	wc.height = c->dy;
	wc.border_width = 0;
	wc.sibling = None;
	wc.stack_mode = Above;
	e->value_mask &= ~CWStackMode;
	e->value_mask |= CWBorderWidth;
	XConfigureWindow(dpy, c->window, e->value_mask, &wc);
}

void
mapreq(XMapRequestEvent *e)
{
	Client *c;
	int i;

	curtime = CurrentTime;
	c = getclient(e->window, 0);
	trace("mapreq", c, (XEvent *)e);

	if(c == 0 || c->window != e->window){
		/* workaround for stupid NCDware */
		fprintf(stderr, "rio: bad mapreq c %p w %x, rescanning\n",
			(void*)c, (int)e->window);
		for(i = 0; i < num_screens; i++)
			scanwins(&screens[i]);
		c = getclient(e->window, 0);
		if(c == 0 || c->window != e->window){
			fprintf(stderr, "rio: window not found after rescan\n");
			return;
		}
	}

	switch (c->state){
	case WithdrawnState:
		if(c->parent == c->screen->root){
			if(!manage(c, 0))
				return;
			break;
		}
		XReparentWindow(dpy, c->window, c->parent, BORDER-1, BORDER-1);
		XAddToSaveSet(dpy, c->window);
		/* fall through... */
	case NormalState:
		XMapWindow(dpy, c->window);
		XMapRaised(dpy, c->parent);
		top(c);
		setstate(c, NormalState);
		if(c->trans != None && current && c->trans == current->window)
				active(c);
		break;
	case IconicState:
		unhidec(c, 1);
		break;
	}
}

void
unmap(XUnmapEvent *e)
{
	Client *c;

	curtime = CurrentTime;
	c = getclient(e->window, 0);
	if(c){
		switch (c->state){
		case IconicState:
			if(e->send_event){
				unhidec(c, 0);
				withdraw(c);
			}
			break;
		case NormalState:
			if(c == current)
				nofocus();
			if(!c->reparenting)
				withdraw(c);
			break;
		}
		c->reparenting = 0;
	}
}

void
circulatereq(XCirculateRequestEvent *e)
{
	fprintf(stderr, "It must be the warlock Krill!\n");  /* ☺ */
}

void
newwindow(XCreateWindowEvent *e)
{
	Client *c;
	ScreenInfo *s;

	/* we don't set curtime as nothing here uses it */
	if(e->override_redirect)
		return;
	c = getclient(e->window, 1);
	if(c && c->window == e->window && (s = getscreen(e->parent))){
		c->x = e->x;
		c->y = e->y;
		c->dx = e->width;
		c->dy = e->height;
		c->border = e->border_width;
		c->screen = s;
		if(c->parent == None)
			c->parent = c->screen->root;
	}
}

void
destroy(Window w)
{
	int i;
	Client *c;

	curtime = CurrentTime;
	c = getclient(w, 0);
	if(c == 0)
		return;

	if(numvirtuals > 1)
		for(i=0; i<numvirtuals; i++)
			if(currents[i] == c)
				currents[i] = 0;

	rmclient(c);

	/* flush any errors generated by the window's sudden demise */
	ignore_badwindow = 1;
	XSync(dpy, False);
	ignore_badwindow = 0;
}

void
clientmesg(XClientMessageEvent *e)
{
	Client *c;

	curtime = CurrentTime;
	if(e->message_type == exit_rio){
		cleanup();
		exit(0);
	}
	if(e->message_type == restart_rio){
		fprintf(stderr, "*** rio restarting ***\n");
		cleanup();
		execvp(myargv[0], myargv);
		perror("rio: exec failed");
		exit(1);
	}
	if(e->message_type == wm_protocols)
		return;
	if(e->message_type == wm_change_state){
		c = getclient(e->window, 0);
		if(e->format == 32 && e->data.l[0] == IconicState && c != 0){
			if(normal(c))
				hide(c);
		}
		else
			fprintf(stderr, "rio: WM_CHANGE_STATE: format %d data %d w 0x%x\n",
				(int)e->format, (int)e->data.l[0], (int)e->window);
		return;
	}
	if(e->message_type == wm_state){
//		c = getclient(e->window, 0);
//		if(e->format == 32 && e->data.l[1] == wm_state_fullscreen){
//		}else
		fprintf(stderr, "rio: WM_STATE: format %d data %d %d w 0x%x\n",
			(int)e->format, (int)e->data.l[0], (int)e->data.l[1],
			(int)e->window);
		return;
	}
	fprintf(stderr, "rio: strange ClientMessage, type 0x%x window 0x%x\n",
		(int)e->message_type, (int)e->window);
}

void
cmap(XColormapEvent *e)
{
	Client *c;
	int i;

	/* we don't set curtime as nothing here uses it */
	if(e->new){
		c = getclient(e->window, 0);
		if(c){
			c->cmap = e->colormap;
			if(c == current)
				cmapfocus(c);
		}
		else
			for(c = clients; c; c = c->next){
				for(i = 0; i < c->ncmapwins; i++)
					if(c->cmapwins[i] == e->window){
						c->wmcmaps[i] = e->colormap;
						if(c == current)
							cmapfocus(c);
						return;
					}
			}
	}
}

void
property(XPropertyEvent *e)
{
	Atom a;
	int delete;
	Client *c;
	long msize;

	/* we don't set curtime as nothing here uses it */
	a = e->atom;
	delete = (e->state == PropertyDelete);
	c = getclient(e->window, 0);
	if(c == 0)
		return;

	switch (a){
	case XA_WM_ICON_NAME:
		if(c->iconname != 0)
			XFree((char*) c->iconname);
		c->iconname = delete ? 0 : getprop(c->window, a);
		setlabel(c);
		renamec(c, c->label);
		return;
	case XA_WM_NAME:
		if(c->name != 0)
			XFree((char*) c->name);
		c->name = delete ? 0 : getprop(c->window, a);
		setlabel(c);
		renamec(c, c->label);
		return;
	case XA_WM_TRANSIENT_FOR:
		gettrans(c);
		return;
	case XA_WM_HINTS:
	case XA_WM_SIZE_HINTS:
	case XA_WM_ZOOM_HINTS:
		/* placeholders to not forget.  ignore for now.  -Axel */
		return;
	case XA_WM_NORMAL_HINTS:
		if(XGetWMNormalHints(dpy, c->window, &c->size, &msize) == 0 || c->size.flags == 0)
			c->size.flags = PSize;	/* not specified - punt */
		return;
	}
	if(a == _rio_hold_mode){
		c->hold = getiprop(c->window, _rio_hold_mode);
		if(c == current)
			draw_border(c, 1);
	}
	else if(a == wm_colormaps){
		getcmaps(c);
		if(c == current)
			cmapfocus(c);
	}
}

void
reparent(XReparentEvent *e)
{
	Client *c;
	XWindowAttributes attr;
	ScreenInfo *s;

	/* we don't set curtime as nothing here uses it */
	if(!getscreen(e->event) || e->override_redirect)
		return;
	if((s = getscreen(e->parent)) != 0){
		c = getclient(e->window, 1);
		if(c != 0 && (c->dx == 0 || c->dy == 0)){
			/* flush any errors */
			ignore_badwindow = 1;
			XGetWindowAttributes(dpy, c->window, &attr);
			XSync(dpy, False);
			ignore_badwindow = 0;

			c->x = attr.x;
			c->y = attr.y;
			c->dx = attr.width;
			c->dy = attr.height;
			c->border = attr.border_width;
			c->screen = s;
			if(c->parent == None)
				c->parent = c->screen->root;
		}
	}
	else {
		c = getclient(e->window, 0);
		if(c != 0 && (c->parent == c->screen->root || withdrawn(c)))
			rmclient(c);
	}
}

#ifdef	SHAPE
void
shapenotify(XShapeEvent *e)
{
	Client *c;

	/* we don't set curtime as nothing here uses it */
	c = getclient(e->window, 0);
	if(c == 0)
		return;

	setshape(c);
}
#endif

void
enter(XCrossingEvent *e)
{
	Client *c;

	curtime = e->time;
	if(!ffm)
	if(e->mode != NotifyGrab || e->detail != NotifyNonlinearVirtual)
		return;
	c = getclient(e->window, 0);
	if(c != 0 && c != current){
		/* someone grabbed the pointer; make them current */
		if(!ffm)
			XMapRaised(dpy, c->parent);
		top(c);
		active(c);
	}
}

void
leave(XCrossingEvent *e)
{
	Client *c;

	c = getclient(e->window, 0);
	if(c)
		XUndefineCursor(dpy, c->parent);
/* 	XDefineCursor(dpy, c->parent, c->screen->arrow); */
}

void
focusin(XFocusChangeEvent *e)
{
	Client *c;

	curtime = CurrentTime;
	if(e->detail != NotifyNonlinearVirtual)
		return;
	c = getclient(e->window, 0);
	if(c != 0 && c->window == e->window && c != current){
		/* someone grabbed keyboard or seized focus; make them current */
		XMapRaised(dpy, c->parent);
		top(c);
		active(c);
	}
}

BorderOrient
borderorient(Client *c, int x, int y)
{
	if(x <= BORDER){
		if(y <= CORNER){
			if(debug) fprintf(stderr, "topleft\n");
			return BorderWNW;
		}
		if(y >= (c->dy + 2*BORDER) - CORNER){
			if(debug) fprintf(stderr, "botleft\n");
			return BorderWSW;
		}
		if(y > CORNER &&
			y < (c->dy + 2*BORDER) - CORNER){
			if(debug) fprintf(stderr, "left\n");
			return BorderW;
		}
	} else if(x <= CORNER){
		if(y <= BORDER){
			if(debug) fprintf(stderr, "topleft\n");
			return BorderNNW;
		}
		if  (y >= (c->dy + BORDER)){
			if(debug) fprintf(stderr, "botleft\n");
			return BorderSSW;
		}
	} else if(x >= (c->dx + BORDER)){
		if(y <= CORNER){
			if(debug) fprintf(stderr, "topright\n");
			return BorderENE;
		}
		if(y >= (c->dy + 2*BORDER) - CORNER){
			if(debug) fprintf(stderr, "botright\n");
			return BorderESE;
		}
		if(y > CORNER &&
			y < (c->dy + 2*BORDER) - CORNER){
			if(debug) fprintf(stderr, "right\n");
			return BorderE;
		}
	} else if(x >= (c->dx + 2*BORDER) - CORNER){
		if(y <= BORDER){
			if(debug) fprintf(stderr, "topright\n");
			return BorderNNE;
		}
		if  (y >= (c->dy + BORDER)){
			if(debug) fprintf(stderr, "botright\n");
			return BorderSSE;
		}
	} else if(x > CORNER &&
			x < (c->dx + 2*BORDER) - CORNER){
		if(y <= BORDER){
			if(debug) fprintf(stderr, "top\n");
			return BorderN;
		}
		if(y >= (c->dy + BORDER)){
			if(debug) fprintf(stderr, "bot\n");
			return BorderS;
		}
	}
	return BorderUnknown;
}

void
motionnotify(XMotionEvent *e)
{
	Client *c;
	BorderOrient bl;

	c = getclient(e->window, 0);
	if(c){
		bl = borderorient(c, e->x, e->y);
		if(bl == BorderUnknown)
			XUndefineCursor(dpy, c->parent);
		else
			XDefineCursor(dpy, c->parent, c->screen->bordcurs[bl]);
	}
}
