#include	<stdio.h>
#include	<stdlib.h>
#include	<string.h>
#include	<time.h>

#include	<unistd.h>

#include	<X11/X.h>
#include	<X11/Xlib.h>
#include	<X11/Xutil.h>

#include	"lwm.h"

Client *current;
static Client *clients;

Edge interacting_edge;

static void sendcmessage(Window, Atom, long, long);

void
setactive(Client *c, int on, long timestamp)
{
	int inhibit = isshaped(c->window);

	if (c == 0 || hidden(c))
		return;

	if (on && c->accepts_focus) {
		if (!inhibit) {
			XMoveResizeWindow(dpy, c->parent,
				c->size.x, c->size.y - titleheight(),
				c->size.width, c->size.height + titleheight());
			XMoveWindow(dpy, c->window, border, border + titleheight());
		}
		XSetInputFocus(dpy, c->window, RevertToPointerRoot, CurrentTime);
		if (c->proto & Ptakefocus)
			sendcmessage(c->window, wm_protocols, wm_take_focus, timestamp);
		cmapfocus(c);
	} else {
		if (!inhibit) {
			XMoveResizeWindow(dpy, c->parent, c->size.x, c->size.y,
				c->size.width, c->size.height);
			XMoveWindow(dpy, c->window, border, border);
		}
	}
	if (!inhibit)
		Client_DrawBorder(c, on);
}


void
Client_DrawBorder(Client *c, int active)
{
	int	quarter;

	if (c->parent == root || c->parent == 0)
		return;

	XSetWindowBackground(dpy, c->parent, active ? black : white);
	XClearWindow(dpy, c->parent);

	if (!active)
		XDrawRectangle(dpy, c->parent, gc_thin, border - 1, border - 1,
		    c->size.width - 2 * border + 1, c->size.height - 2 * border + 1);
	else if (c->accepts_focus) {
		/*
		 *	Draw control box.
		 */
		quarter = (border + titleheight()) / 4;
		XDrawRectangle(dpy, c->parent, gc,
			quarter, quarter, 2 * quarter, 2 * quarter);

		/*
		 *	Draw window title.
		 */
		if (c->name != 0)
			XDrawString(dpy, c->parent, gc, border + 3 * quarter,
			    font->ascent + font->descent, c->name, strlen(c->name));
	}
}


Client *
Client_Get(Window w)
{
	Client	*c;

	if (w == 0 || w == root)
		return 0;

	/* Search for the client corresponding to this window. */
	for (c = clients; c; c = c->next)
		if (c->window == w || c->parent == w)
			return c;

	/* Not found. */
	return 0;
}


Client *
Client_Add(Window w)
{
	Client	*c;

	if (w == 0 || w == root)
		return 0;

	/*
	 *	Search for the client corresponding to this window.
	 */
	for (c = clients; c != 0; c = c->next)
		if (c->window == w || c->parent == w)
			return c;

	c = calloc(1, sizeof *c);
	c->window = w;
	c->parent = root;
	c->state = WithdrawnState;
	c->internal_state = INormal;
	c->cmap = None;
	c->name = 0;
	c->ncmapwins = 0;
	c->cmapwins = 0;
	c->wmcmaps = 0;
	c->accepts_focus = 1;
	c->next = clients;

	/*
	 *	Add to head of list of clients.
	 */
	return (clients = c);
}


void
Client_Remove(Client *c)
{
	Client	*cc;

	if (c == 0)
		return;

	/*
	 *	Remove the client from our client list.
	 */
	if (c == clients)
		clients = c->next;
	for (cc = clients; cc && cc->next; cc = cc->next) {
		if (cc->next == c)
			cc->next = cc->next->next;
	}

	if (hidden(c))
		unhidec(c, 0);

	if (c == current)
		current = 0;

	if (c->parent != root)
		XDestroyWindow(dpy, c->parent);

	if (c->ncmapwins != 0) {
		XFree(c->cmapwins);
		free(c->wmcmaps);
	}

	if (c->name != 0)
		XFree(c->name);

	free(c);
}


void
Client_MakeSane(Client *c, Edge edge, int *x, int *y, int *dx, int *dy)
{
	Bool	horizontal_ok = True;
	Bool	vertical_ok = True;

	if (edge != ENone) {
		/*
		 *	Make sure we're not making the window too small.
		 */
		if (*dx < c->size.min_width)
			horizontal_ok = False;
		if (*dy < c->size.min_width)
			vertical_ok = False;

		/*
		 * Make sure we're not making the window too large.
		 */
		if (c->size.flags & PMaxSize) {
			if (*dx > c->size.max_width)
				horizontal_ok = False;
			if (*dy > c->size.max_width)
				vertical_ok = False;
		}

		/*
		 *	Make sure the window's width & height are multiples of
		 *	the width & height increments (not including the base size).
		 */

		if (c->size.width_inc > 1) {
			int apparent_dx = *dx - 2 * border - c->size.base_width;
			int x_fix = apparent_dx % c->size.width_inc;

			switch (edge) {
			case ELeft:
			case ETopLeft:
			case EBottomLeft:
				*x += x_fix;
				/*FALLTHROUGH*/
			case ERight:
			case ETopRight:
			case EBottomRight:
				*dx -= x_fix;
				break;
			default: break;
			}
		}

		if (c->size.height_inc > 1) {
			int apparent_dy = *dy - 2 * border - c->size.base_height;
			int y_fix = apparent_dy % c->size.height_inc;

			switch (edge) {
			case ETop:
			case ETopLeft:
			case ETopRight:
				*y += y_fix;
				/*FALLTHROUGH*/
			case EBottom:
			case EBottomLeft:
			case EBottomRight:
				*dy -= y_fix;
				break;
			default: break;
			}
		}

		/*
		 * Check that we may change the client horizontally and vertically.
		 */

		if (c->size.width_inc == 0)
			horizontal_ok = False;
		if (c->size.height_inc == 0)
			vertical_ok = False;
	}

	/*
	 * Ensure that, were the client to lose focus, it would still be accessible.
	 */
	if (*y + border >= display_height)
		*y = display_height - border;

	/*
	 * Update that part of the client information that we're happy with.
	 */
	if (interacting_edge != ENone) {
		if (horizontal_ok) {
			c->size.x = *x;
			c->size.width  = *dx;
		}
		if (vertical_ok) {
			c->size.y = *y;
			c->size.height = *dy;
		}
	} else {
		if (horizontal_ok)
			c->size.x = *x;
		if (vertical_ok)
			c->size.y = *y;
	}
}

static void
Client_OpaquePrimitive(Client *c, Edge edge)
{
	Cursor	cursor;
	int	ox, oy;

	if (c == 0 || c != current)
		return;

	/*
	 *	Find out where we've got hold of the window.
	 */
	getmouse(&ox, &oy);
	ox = c->size.x - ox;
	oy = c->size.y - oy;

	cursor = edgecursor(edge);
	XChangeActivePointerGrab(dpy, ButtonMask | PointerMotionHintMask |
		ButtonMotionMask | OwnerGrabButtonMask, cursor, CurrentTime);

	/*
	 * Store some state so that we can get back into the main event
	 * dispatching thing.
	 */
	interacting_edge = edge;
	start_x = ox;
	start_y = oy;
	mode = wm_reshaping;
}

void
Client_Close(Client *c)
{
	if (c == 0)
		return;

	/*
	 *	Terminate the client nicely if possible. Be brutal otherwise.
	 */
	if (c->internal_state != IPendingDeletion && (c->proto & Pdelete)) {
		sendcmessage(c->window, wm_protocols, wm_delete, CurrentTime);
		c->internal_state = IPendingDeletion;
	} else {
		XKillClient(dpy, c->window);
	}
}

void
Client_SetState(Client *c, int state)
{
	long	data[2];

	data[0] = (long) state;
	data[1] = (long) None;

	c->state = state;
	XChangeProperty(dpy, c->window, wm_state, wm_state, 32, PropModeReplace,
		(unsigned char *) data, 2);
}

static void
sendcmessage(Window w, Atom a, long data0, long data1)
{
	XEvent	ev;
	long	mask;

	memset(&ev, '\0', sizeof ev);
	ev.xclient.type = ClientMessage;
	ev.xclient.window = w;
	ev.xclient.message_type = a;
	ev.xclient.format = 32;
	ev.xclient.data.l[0] = data0;
	ev.xclient.data.l[1] = data1;
	mask = (w == root) ? SubstructureRedirectMask : 0L;

	XSendEvent(dpy, w, False, mask, &ev);
}

extern void
Client_FreeAll(void)
{
	Client *c;
	XWindowChanges wc;

	for (c = clients; c; c = c->next) {
		int not_mapped = !normal(c);

		/* Remap the window if it's hidden. */
		if (not_mapped) {
			XMapWindow(dpy, c->window);
			XMapWindow(dpy, c->parent);
		}

		/* Reparent it, and then push it to the bottom if it was hidden. */
		XReparentWindow(dpy, c->window, root, c->size.x, c->size.y);
		if (not_mapped)
			XLowerWindow(dpy, c->window);

		/* Give it back its initial border width. */
		wc.border_width = c->border;
		XConfigureWindow(dpy, c->window, CWBorderWidth, &wc);
	}
}

extern void
Client_ColourMap(XEvent *e)
{
	int	i;
	Client	*c;

	for (c = clients; c; c = c->next) {
		for (i = 0; i < c->ncmapwins; i++) {
			if (c->cmapwins[i] == e->xcolormap.window) {
				c->wmcmaps[i] = e->xcolormap.colormap;
				if (c == current)
					cmapfocus(c);
				return;
			}
		}
	}
}

extern void
Client_ReshapeEdge(Client *c, Edge e)
{
	Client_OpaquePrimitive(c, e);
}

extern void
Client_Move(Client *c)
{
	Client_OpaquePrimitive(c, ENone);
}

extern int
hidden(Client *c)
{
	return c->state == IconicState;
}

extern int
withdrawn(Client *c)
{
	return c->state == WithdrawnState;
}

extern int
normal(Client *c)
{
	return c->state == NormalState;
}
