/*
 * Occupation handling bits
 *
 * This is in fact pretty tightly tied and extremely similar to the
 * handling of the WorkSpaceManager in workmgr.c, and used to be there.
 * It makes sense to consider them together (and indeed, many of the
 * configs that affect how this works are really WorkSpaceManager* or
 * WMgr* commands.  But having them crammed together in one file is
 * unwieldy.
 */

#include "ctwm.h"

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

#include <X11/Xatom.h>

#include "add_window.h"
#include "ctwm_atoms.h"
#include "drawing.h"
#include "events.h"
#include "iconmgr.h"
#include "list.h"
#include "screen.h"
#include "occupation.h"
#include "otp.h"
#include "util.h"
#include "vscreen.h"
#include "win_iconify.h"
#include "win_regions.h"
#include "win_utils.h"
#include "workspace_manager.h"
#include "workspace_utils.h"


static int GetMaskFromResource(TwmWindow *win, char *res);
static char *mk_nullsep_string(const char *prop, int len);

static bool CanChangeOccupation(TwmWindow **twm_winp);

int fullOccupation = 0;

/*
 * The window whose occupation is currently being manipulated.
 *
 * XXX Should probably be static, but currently needed in
 * WMapRemoveWindow().  Revisit.
 */
TwmWindow *occupyWin = NULL;


/* XXX Share with captive.c? */
static XrmOptionDescRec table [] = {
	{"-xrm",            NULL,           XrmoptionResArg, (XPointer) NULL},
};




/*
 ****************************************************************
 *
 * First, funcs related to setting and changing a window's occupation.
 *
 ****************************************************************
 */


/*
 * Setup the occupation of a TwmWindow.  Called as part of the
 * AddWindow() process.
 *
 * XXX The logic flow in this is kinda weird, and it's not at all clear
 * to what extent it's really doing the right on on what should override
 * what, or which things should expand/contract on others...
 */
void
SetupOccupation(TwmWindow *twm_win, int occupation_hint)
{
	char      **cliargv = NULL;
	int       cliargc;
	WorkSpace *ws;

	/* If there aren't any config'd workspaces, there's only 0 */
	if(! Scr->workSpaceManagerActive) {
		twm_win->occupation = 1 << 0;   /* occupy workspace #0 */
		/* more?... */

		return;
	}

	/* Workspace manager window doesn't get futzed with */
	if(twm_win->iswspmgr) {
		return;
	}

	/*twm_win->occupation = twm_win->iswinbox ? fullOccupation : 0;*/
	twm_win->occupation = 0;

	/* Specified in any Occupy{} config params? */
	for(ws = Scr->workSpaceMgr.workSpaceList; ws != NULL; ws = ws->next) {
		if(LookInList(ws->clientlist, twm_win->name, &twm_win->class)) {
			twm_win->occupation |= 1 << ws->number;
		}
	}

	/* OccupyAll{} */
	if(LookInList(Scr->OccupyAll, twm_win->name, &twm_win->class)) {
		twm_win->occupation = fullOccupation;
	}

	/* See if it specified in -xrm stuff */
	if(XGetCommand(dpy, twm_win->w, &cliargv, &cliargc)) {
		Bool status;
		char *str_type;
		XrmValue value;
		XrmDatabase db = NULL;

		XrmParseCommand(&db, table, 1, "ctwm", &cliargc, cliargv);
		XFreeStringList(cliargv);
		status = XrmGetResource(db, "ctwm.workspace", "Ctwm.Workspace",
		                        &str_type, &value);
		if((status == True) && (value.size != 0)) {
			/* Copy the value.addr because it's in XRM memory not ours */
			char wrkSpcList[512];
			safe_strncpy(wrkSpcList, value.addr, MIN(value.size, 512));

			twm_win->occupation = GetMaskFromResource(twm_win, wrkSpcList);
		}
		XrmDestroyDatabase(db);
	}

	/* Does it have a property telling us */
	if(RestartPreviousState) {
		Atom actual_type;
		int actual_format;
		unsigned long nitems, bytesafter;
		unsigned char *prop;

		if(XGetWindowProperty(dpy, twm_win->w, XA_WM_OCCUPATION, 0L, 2500, False,
		                      XA_STRING, &actual_type, &actual_format, &nitems,
		                      &bytesafter, &prop) == Success) {
			if(nitems != 0) {
				twm_win->occupation = GetMaskFromProperty(prop, nitems);
				XFree(prop);
			}
		}
	}

#ifdef EWMH
	/* Maybe EWMH has something to tell us? */
	if(twm_win->occupation == 0) {
		twm_win->occupation = EwmhGetOccupation(twm_win);
	}
#endif /* EWMH */

	/* Icon Managers shouldn't get altered */
	/* XXX Should this be up near the top? */
	if(twm_win->isiconmgr) {
		return;        /* someone tried to modify occupation of icon managers */
	}


	/*
	 * Transient-ish things go with their parents unless
	 * TransientHasOccupation set in the config.
	 */
	if(! Scr->TransientHasOccupation) {
		TwmWindow *t;

		if(twm_win->istransient) {
			t = GetTwmWindow(twm_win->transientfor);
			if(t != NULL) {
				twm_win->occupation = t->occupation;
			}
		}
		else if(twm_win->group != 0) {
			t = GetTwmWindow(twm_win->group);
			if(t != NULL) {
				twm_win->occupation = t->occupation;
			}
		}
	}


	/* If we were told something specific, go with that */
	if(occupation_hint != 0) {
		twm_win->occupation = occupation_hint;
	}

	/* If it's apparently-nonsensical, put it in its vs's workspace */
	if((twm_win->occupation & fullOccupation) == 0) {
		twm_win->occupation = 1 << twm_win->vs->wsw->currentwspc->number;
	}

	/*
	 * If the occupation would not show it in the current vscreen,
	 * make it vanish.
	 *
	 * If it could be shown in one of the other vscreens, change the vscreen.
	 */
	if(!OCCUPY(twm_win, twm_win->vs->wsw->currentwspc)) {
		VirtualScreen *vs;

		twm_win->vs = NULL;

		if(Scr->numVscreens > 1) {
			for(vs = Scr->vScreenList; vs != NULL; vs = vs->next) {
				if(OCCUPY(twm_win, vs->wsw->currentwspc)) {
					twm_win->vs = vs;
					twm_win->parent_vs = vs;
					break;
				}
			}
		}
	}


	/* Set the property for the occupation */
	{
		long eventMask;
		char *wsstr;
		int  len;

		/* Ignore the PropertyChange we're about to do */
		if((eventMask = mask_out_event(twm_win->w, PropertyChangeMask)) < 0) {
			/* Window is horked, not much we can do */
			return;
		}

		/* Set the property for the occupation */
		len = GetPropertyFromMask(twm_win->occupation, &wsstr);
		XChangeProperty(dpy, twm_win->w, XA_WM_OCCUPATION, XA_STRING, 8,
		                PropModeReplace, (unsigned char *) wsstr, len);
		free(wsstr);

#ifdef EWMH
		EwmhSet_NET_WM_DESKTOP(twm_win);
#endif

		/* Restore event mask */
		restore_mask(twm_win->w, eventMask);
	}

	/* Set WM_STATE prop */
	{
		int state = NormalState;
		Window icon;

		if(!(RestartPreviousState
		                && GetWMState(twm_win->w, &state, &icon)
		                && (state == NormalState || state == IconicState
		                    || state == InactiveState))) {
			if(twm_win->wmhints->flags & StateHint) {
				state = twm_win->wmhints->initial_state;
			}
		}
		if(visible(twm_win)) {
			if(state == InactiveState) {
				SetMapStateProp(twm_win, NormalState);
			}
		}
		else {
			if(state == NormalState) {
				SetMapStateProp(twm_win, InactiveState);
			}
		}
	}
}


/*
 * Make sure a window is marked in a given workspace.  f.addtoworkspace.
 * Also gets called as part of the process of mapping a window; if we're
 * mapping it here, it should know that it's here.  And Xinerama magic
 * moves.
 */
void
AddToWorkSpace(char *wname, TwmWindow *twm_win)
{
	WorkSpace *ws;
	int newoccupation;

	if(!CanChangeOccupation(&twm_win)) {
		return;
	}
	ws = GetWorkspace(wname);
	if(!ws) {
		return;
	}

	if(twm_win->occupation & (1 << ws->number)) {
		return;
	}
	newoccupation = twm_win->occupation | (1 << ws->number);
	ChangeOccupation(twm_win, newoccupation);
}


/*
 * Converse of the above.  f.removefromworkspace, also called from
 * Xinerama-related magic.
 */
void
RemoveFromWorkSpace(char *wname, TwmWindow *twm_win)
{
	WorkSpace *ws;
	int newoccupation;

	if(!CanChangeOccupation(&twm_win)) {
		return;
	}
	ws = GetWorkspace(wname);
	if(!ws) {
		return;
	}

	newoccupation = twm_win->occupation & ~(1 << ws->number);
	if(!newoccupation) {
		return;
	}
	ChangeOccupation(twm_win, newoccupation);
}


/* f.toggleoccupation - flip setting for [current] workspace */
void
ToggleOccupation(char *wname, TwmWindow *twm_win)
{
	WorkSpace *ws;
	int newoccupation;

	if(!CanChangeOccupation(&twm_win)) {
		return;
	}
	ws = GetWorkspace(wname);
	if(!ws) {
		return;
	}

	newoccupation = twm_win->occupation ^ (1 << ws->number);
	if(!newoccupation) {
		/* Don't allow de-occupying _every_ ws */
		return;
	}
	ChangeOccupation(twm_win, newoccupation);
}


/* f.movetonextworkspace */
void
MoveToNextWorkSpace(VirtualScreen *vs, TwmWindow *twm_win)
{
	WorkSpace *wlist1, *wlist2;
	int newoccupation;

	if(!CanChangeOccupation(&twm_win)) {
		return;
	}

	wlist1 = vs->wsw->currentwspc;
	wlist2 = wlist1->next;
	wlist2 = wlist2 ? wlist2 : Scr->workSpaceMgr.workSpaceList;

	/* Out of (here), into (here+1) */
	newoccupation = (twm_win->occupation ^ (1 << wlist1->number))
	                | (1 << wlist2->number);
	ChangeOccupation(twm_win, newoccupation);
}


/* f.movetonextworkspaceandfollow */
void
MoveToNextWorkSpaceAndFollow(VirtualScreen *vs, TwmWindow *twm_win)
{
	if(!CanChangeOccupation(&twm_win)) {
		return;
	}

	MoveToNextWorkSpace(vs, twm_win);
	GotoNextWorkSpace(vs);
#if 0
	OtpRaise(twm_win, WinWin);  /* XXX really do this? */
#endif
}


/* f.movetoprevworkspaceand */
void
MoveToPrevWorkSpace(VirtualScreen *vs, TwmWindow *twm_win)
{
	WorkSpace *wlist1, *wlist2;
	int newoccupation;

	if(!CanChangeOccupation(&twm_win)) {
		return;
	}

	wlist1 = Scr->workSpaceMgr.workSpaceList;
	wlist2 = vs->wsw->currentwspc;
	if(wlist1 == NULL) {
		return;
	}

	while(wlist1->next != wlist2 && wlist1->next != NULL) {
		wlist1 = wlist1->next;
	}

	/* Out of (here), into (here-1) */
	newoccupation = (twm_win->occupation ^ (1 << wlist2->number))
	                | (1 << wlist1->number);
	ChangeOccupation(twm_win, newoccupation);
}


/* f.movetoprevworkspaceandfollow */
void
MoveToPrevWorkSpaceAndFollow(VirtualScreen *vs, TwmWindow *twm_win)
{
	if(!CanChangeOccupation(&twm_win)) {
		return;
	}

	MoveToPrevWorkSpace(vs, twm_win);
	GotoPrevWorkSpace(vs);
#if 0
	OtpRaise(twm_win, WinWin);          /* XXX really do this? */
#endif
}


/*
 * Set the occupation based on the window name.  This is called if
 * AutoOccupy is set, when we get a notification about a window name
 * change.
 */
void
WmgrRedoOccupation(TwmWindow *win)
{
	WorkSpace *ws;
	int       newoccupation;

	if(LookInList(Scr->OccupyAll, win->name, &win->class)) {
		newoccupation = fullOccupation;
	}
	else {
		newoccupation = 0;
		for(ws = Scr->workSpaceMgr.workSpaceList; ws != NULL; ws = ws->next) {
			if(LookInList(ws->clientlist, win->name, &win->class)) {
				newoccupation |= 1 << ws->number;
			}
		}
	}
	if(newoccupation != 0) {
		ChangeOccupation(win, newoccupation);
	}
}


/* f.vanish */
void
WMgrRemoveFromCurrentWorkSpace(VirtualScreen *vs, TwmWindow *win)
{
	WorkSpace *ws;
	int       newoccupation;

	ws = vs->wsw->currentwspc;
	if(!ws) {
		/* Impossible? */
		return;
	}
	if(! OCCUPY(win, ws)) {
		return;
	}

	newoccupation = win->occupation & ~(1 << ws->number);
	if(newoccupation == 0) {
		return;
	}

	ChangeOccupation(win, newoccupation);
}


/* f.warphere */
void
WMgrAddToCurrentWorkSpaceAndWarp(VirtualScreen *vs, char *winname)
{
	TwmWindow *tw;
	int       newoccupation;

	/* Find named window on this screen */
	for(tw = Scr->FirstWindow; tw != NULL; tw = tw->next) {
		if(match(winname, tw->name)) {
			break;
		}
	}

	/* Didn't find it by name?  Try by class */
	if(!tw) {
		for(tw = Scr->FirstWindow; tw != NULL; tw = tw->next) {
			if(match(winname, tw->class.res_name)) {
				break;
			}
		}
	}
	if(!tw) {
		for(tw = Scr->FirstWindow; tw != NULL; tw = tw->next) {
			if(match(winname, tw->class.res_class)) {
				break;
			}
		}
	}

	/* Still didn't find?  Beep at the user and bail. */
	if(!tw) {
		XBell(dpy, 0);
		return;
	}

	/* If WarpUnmapped isn't set and this isn't mapped, beep and bail */
	if((! Scr->WarpUnmapped) && (! tw->mapped)) {
		XBell(dpy, 0);
		return;
	}

	/* Move it here if it's not */
	if(! OCCUPY(tw, vs->wsw->currentwspc)) {
		newoccupation = tw->occupation | (1 << vs->wsw->currentwspc->number);
		ChangeOccupation(tw, newoccupation);
	}

	/* If we get here, WarpUnmapped is set, so map it if we need to */
	if(! tw->mapped) {
		DeIconify(tw);
	}

	/* And go */
	WarpToWindow(tw, Scr->RaiseOnWarp);
}


/* f.occupyall backend */
void
OccupyAll(TwmWindow *twm_win)
{
	IconMgr *save;

	if(!CanChangeOccupation(&twm_win)) {
		return;
	}

	/*
	 * Temporarily alter Scr->iconmgr because stuff down in
	 * ChangeOccupation winds up adding/removing bits, and that doesn't
	 * work right when we're setting all?  XXX Investigate further.
	 */
	save = Scr->iconmgr;
	Scr->iconmgr = Scr->workSpaceMgr.workSpaceList->iconmgr;
	ChangeOccupation(twm_win, fullOccupation);
	Scr->iconmgr = save;
}



/*
 ****************************************************************
 *
 * Pieces related to the Occupy window
 *
 ****************************************************************
 */

static ColorPair occupyButtoncp;

static char *ok_string         = "OK",
             *cancel_string     = "Cancel",
              *everywhere_string = "All";

/*
 * Create the Occupy window.  Part of startup process.
 *
 * Do not do the layout of the parts, only calculate the initial total
 * size. For the layout, call ResizeOccupyWindow() at the end.
 *
 * There is only one Occupy window (per Screen), it is reparented to each
 * virtual screen as needed.
 */
void
CreateOccupyWindow(void)
{
	int           width; // Caculated and altered, unlike most others
	int           Dummy = 1;
	TwmWindow     *tmp_win;
	/* Shorthands for the Occupy window */
	OccupyWindow  *occwin = Scr->workSpaceMgr.occupyWindow;
	Window        w; // occwin->w
	/* Misc other shorthands */
	const int lines   = Scr->workSpaceMgr.lines;
	const int columns = Scr->workSpaceMgr.columns;
	const int bwidth  = Scr->vScreenList->wsw->bwidth;
	const int bheight = Scr->vScreenList->wsw->bheight;
	const int vspace = occwin->vspace;
	const int hspace = occwin->hspace;
	const int height = ((bheight + vspace) * lines) + bheight + (2 * vspace);

	/* Struct embedded in [struct embedded in] Scr, so memory's waiting */

	/* There isn't anything we should do without workspaces... */
	if(!Scr->workSpaceManagerActive) {
		return;
	}

	/* Initialize font and colorpair bits */
	occwin->font     = Scr->IconManagerFont;
	occwin->cp       = Scr->IconManagerC;
#ifdef COLOR_BLIND_USER
	occwin->cp.shadc = Scr->White;
	occwin->cp.shadd = Scr->Black;
#else
	if(!Scr->BeNiceToColormap) {
		GetShadeColors(&occwin->cp);
	}
#endif

	/* We already know that these should be too */
	occwin->lines   = lines;
	occwin->columns = columns;


	/*
	 * Work out the necessary size of the OK/Cancel/All buttons at the
	 * bottom.
	 */
	{
		XRectangle inc_rect;
		XRectangle logical_rect;
		MyFont font = occwin->font;
		int bbwidth;  // Bottom button width
		/* Window min width based on bottom vs. workspace btns */
		int bb_width, ws_width;

		/* Buttons gotta be as wide as the biggest of the three strings */
		XmbTextExtents(font.font_set, ok_string, strlen(ok_string),
		               &inc_rect, &logical_rect);
		bbwidth = logical_rect.width;

		XmbTextExtents(font.font_set, cancel_string, strlen(cancel_string),
		               &inc_rect, &logical_rect);
		bbwidth = MAX(bbwidth, logical_rect.width);

		XmbTextExtents(font.font_set, everywhere_string,
		               strlen(everywhere_string),
		               &inc_rect, &logical_rect);
		bbwidth = MAX(bbwidth, logical_rect.width);

		/* Plus the padding width */
		bbwidth += hspace;

		/*
		 * So, the final width of those bottom buttons is that, plus the
		 * 3d button look extra on both sides, plus a little extra.  I
		 * guess that extra + 2 is similar to TitlePadding or
		 * ButtonIndent on titlebars, but we don't have a config param
		 * for it on the workspace manager (which is the config used for
		 * the occupy window), so leave it as a magic constant for now.
		 */
		occwin->owidth = bbwidth + 2 * Scr->WMgrButtonShadowDepth + 2;

		/*
		 * The whole thing has to be at least triple the min width of
		 * those bottom buttons, since there are three of them.  The
		 * layout is "hspace button hspace button [...] hspace", to pad
		 * between and on both sides.
		 */
		bb_width = 3 * (bbwidth + hspace) + hspace;

		/*
		 * It also has to be the width of our per-WS buttons.  Per-ws
		 * buttons are sized the same as in the button-state WSM, and
		 * then we add the padding to them as above.
		 */
		ws_width = columns * (bwidth + hspace) + hspace;

		/* So the window has to be as wide as the wider of those */
		width = MAX(bb_width, ws_width);
	}


	/* Now we know the size, so make the window */
	w = occwin->w = XCreateSimpleWindow(dpy, Scr->Root, 0, 0, width, height,
	                                    1, Scr->Black, occwin->cp.back);

	/* Take those base sizes as a minimum */
	occwin->minwidth  = width;
	occwin->minheight = height;


	/*
	 * Make subwindows as buttons for the workspaces.  They're laid out
	 * in a grid mirroring the workspace manager's.
	 */
	{
		int i = 0, j = 0;
		WorkSpace *ws;

		occwin->obuttonw = calloc(Scr->workSpaceMgr.count, sizeof(Window));

		for(ws = Scr->workSpaceMgr.workSpaceList; ws != NULL; ws = ws->next) {
			int idx = (j * columns) + i;

			/*
			 * Make and map.  Note that we're not setting the size or
			 * location at all here; ResizeOccupyWindow() does all that.
			 * We just make 'em.
			 */
			occwin->obuttonw[idx] = XCreateSimpleWindow(dpy, w,
			                        Dummy /* x */,
			                        Dummy /* y */,
			                        Dummy /* width */,
			                        Dummy /* height */,
			                        0, Scr->Black, ws->cp.back);
			XMapWindow(dpy, occwin->obuttonw[idx]);

			/* Inc around to the next location */
			i++;
			if(i == columns) {
				i = 0;
				j++;
			}
		}
	}


	/*
	 * Now start putting together the OK/Cancel/All buttons
	 */

	/* Background for them is hardcoded */
	GetColor(Scr->Monochrome, &(occupyButtoncp.back), "gray50");

	/* Foreground (not used here) is too */
	occupyButtoncp.fore = Scr->White;

	/* Override (probably historical */
	if(!Scr->BeNiceToColormap) {
		GetShadeColors(&occupyButtoncp);
	}

	/* Make 'em */
	{
		Window tw;

		tw = XCreateSimpleWindow(dpy, w, Dummy, Dummy, Dummy, Dummy, 0,
		                         Scr->Black, occupyButtoncp.back);
		XMapWindow(dpy, tw);
		occwin->OK = tw;

		tw = XCreateSimpleWindow(dpy, w, Dummy, Dummy, Dummy, Dummy, 0,
		                         Scr->Black, occupyButtoncp.back);
		XMapWindow(dpy, tw);
		occwin->cancel = tw;

		tw = XCreateSimpleWindow(dpy, w, Dummy, Dummy, Dummy, Dummy, 0,
		                         Scr->Black, occupyButtoncp.back);
		XMapWindow(dpy, tw);
		occwin->allworkspc = tw;
	}


	/* Setup various window properties */
	{
		XSizeHints sizehints;
		XWMHints wmhints;

		sizehints.flags       = PBaseSize | PMinSize | PMaxSize;
		sizehints.min_width   = width;
		sizehints.min_height  = height;
		sizehints.base_width  = width;
		sizehints.base_height = height;
		sizehints.max_width   = width;
		sizehints.max_height  = height;

		wmhints.flags         = InputHint | StateHint;
		wmhints.input         = True;
		wmhints.initial_state = NormalState;

		XmbSetWMProperties(dpy, w, occwin->name, occwin->icon_name,
		                   NULL, 0, &sizehints, &wmhints, NULL);
	}


	/*
	 * Create the TwmWindow wrapping around it, with decorations etc.  We
	 * do this so early in startup that we're not listening for window
	 * creation events yet.
	 */
	tmp_win = AddWindow(w, AWT_OCCUPY, Scr->iconmgr, Scr->currentvs);
	if(! tmp_win) {
		fprintf(stderr, "cannot create occupy window, exiting...\n");
		exit(1);
	}
	tmp_win->vs = NULL;
	tmp_win->occupation = 0;

	/* tmp_win is more convenient the rest of the func, but put in place */
	occwin->twm_win = tmp_win;


	/*
	 * Setup the window to have a button-pushing cursor and listen for
	 * clicks.
	 */
	{
		unsigned long attrmask;
		XSetWindowAttributes attr;
		XWindowAttributes wattr;

		attr.cursor = Scr->ButtonCursor;
		attrmask = CWCursor;
		XChangeWindowAttributes(dpy, w, attrmask, &attr);

		XGetWindowAttributes(dpy, w, &wattr);
		attrmask = wattr.your_event_mask | KeyPressMask | KeyReleaseMask
		           | ExposureMask;
		XSelectInput(dpy, w, attrmask);
	}


	/*
	 * Now for each of the buttons (workspaces + OK/Cancel/All), we mark
	 * them as listening to click and exposure events.  We also stash
	 * away the screen and wrapping TwmWindow in contexts so other code
	 * can dredge them up.
	 */
#define EVT (ButtonPressMask | ButtonReleaseMask | ExposureMask)
#define BTN_IPT_CTX(win) \
        XSelectInput(dpy, (win), EVT); \
        XSaveContext(dpy, (win), TwmContext, (XPointer) tmp_win); \
        XSaveContext(dpy, (win), ScreenContext, (XPointer) Scr);

	for(WorkSpace *ws = Scr->workSpaceMgr.workSpaceList
	                    ; ws != NULL ; ws = ws->next) {
		BTN_IPT_CTX(occwin->obuttonw[ws->number]);
	}

	BTN_IPT_CTX(occwin->OK);
	BTN_IPT_CTX(occwin->cancel);
	BTN_IPT_CTX(occwin->allworkspc);

#undef BTN_IPT_CTX
#undef EVT


	/* Mark that we're not mapped */
	SetMapStateProp(tmp_win, WithdrawnState);

	/* Now call that func that sizes all the buttons */
	ResizeOccupyWindow(tmp_win);
}


/*
 * Slightly misleading name: layout the internals of the Occupy window
 * based on its current size.  That does happen when it's resized, but
 * also when it's initially created.  I guess you could call "creation" a
 * resize of a sort...
 */
void
ResizeOccupyWindow(TwmWindow *win)
{
	int        bwidth, bheight, owidth, oheight;
	int        hspace, vspace;
	int        lines, columns;
	int        neww, newh;
	WorkSpace  *ws;
	int        i, j, x, y;
	OccupyWindow *occwin = Scr->workSpaceMgr.occupyWindow;

	/* Floor at the original size */
	neww = MAX(win->attr.width,  occwin->minwidth);
	newh = MAX(win->attr.height, occwin->minheight);
	if(occwin->width == neww && occwin->height == newh) {
		return;
	}

	/* Space between WS buttons.  From WMgr{Horiz,Vert}ButtonIndent. */
	hspace  = occwin->hspace;
	vspace  = occwin->vspace;

	/* Lines/cols in the layout.  Same as WorkspaceManager's */
	lines   = Scr->workSpaceMgr.lines;
	columns = Scr->workSpaceMgr.columns;

	/* Width/height of each button, based on the above and window size */
	bwidth  = (neww -  columns    * hspace) / columns;
	bheight = (newh - (lines + 2) * vspace) / (lines + 1);

	/* Width/height of the OK/Cancel/All buttons */
	owidth  = occwin->owidth;
	oheight = bheight;


	/*
	 * Lay out the workspace buttons
	 */
	i = 0;
	j = 0;
	for(ws = Scr->workSpaceMgr.workSpaceList; ws != NULL; ws = ws->next) {
		XMoveResizeWindow(dpy, occwin->obuttonw [j * columns + i],
		                  i * (bwidth  + hspace) + (hspace / 2),
		                  j * (bheight + vspace) + (vspace / 2),
		                  bwidth, bheight);
		i++;
		if(i == columns) {
			i = 0;
			j++;
		}
	}


	/*
	 * Now the action buttons
	 */
	hspace = (neww - 3 * owidth) / 4;  // Padding between
	x = hspace;
	y = ((bheight + vspace) * lines) + ((3 * vspace) / 2);
	XMoveResizeWindow(dpy, occwin->OK, x, y, owidth, oheight);
	x += owidth + hspace;
	XMoveResizeWindow(dpy, occwin->cancel, x, y, owidth, oheight);
	x += owidth + hspace;
	XMoveResizeWindow(dpy, occwin->allworkspc, x, y, owidth, oheight);


	/* Save all those dimensions we figured */
	occwin->width   = neww;
	occwin->height  = newh;
	occwin->bwidth  = bwidth;
	occwin->bheight = bheight;
	occwin->owidth  = owidth;

	/* Don't need to repaint it; it'll get expose events */
}


/*
 * Draw the window when we need to (e.g., on expose)
 */
void
PaintOccupyWindow(void)
{
	WorkSpace    *ws;
	OccupyWindow *occwin;
	int          width, height;

	occwin = Scr->workSpaceMgr.occupyWindow;
	width  = occwin->width;
	height = occwin->height;

	Draw3DBorder(occwin->w, 0, 0, width, height, 2, occwin->cp, off, true, false);

	for(ws = Scr->workSpaceMgr.workSpaceList; ws != NULL; ws = ws->next) {
		Window bw = occwin->obuttonw [ws->number];
		ButtonState bs = (occwin->tmpOccupation & (1 << ws->number)) ? on : off;

		PaintWsButton(OCCUPYWINDOW, NULL, bw, ws->label, ws->cp, bs);
	}
	PaintWsButton(OCCUPYBUTTON, NULL, occwin->OK,         ok_string,
	              occupyButtoncp, off);
	PaintWsButton(OCCUPYBUTTON, NULL, occwin->cancel,     cancel_string,
	              occupyButtoncp, off);
	PaintWsButton(OCCUPYBUTTON, NULL, occwin->allworkspc, everywhere_string,
	              occupyButtoncp, off);
}


/*
 * Somebody clicked in the Occupy window
 */
void
OccupyHandleButtonEvent(XEvent *event)
{
	WorkSpace    *ws;
	OccupyWindow *occupyW;
	Window       buttonW;

	/*
	 * Doesn't make sense that this can even happen if there are no
	 * workspaces...
	 */
	if(! Scr->workSpaceManagerActive) {
		return;
	}

	/* ... or if there's no Occupy window up for anything */
	if(occupyWin == NULL) {
		return;
	}

	/* Which sub-window (button) was clicked */
	buttonW = event->xbutton.window;
	if(buttonW == 0) {
		return;        /* icon */
	}

	/* Grab onto the pointer for the duration of our action */
	XGrabPointer(dpy, Scr->Root, True,
	             ButtonPressMask | ButtonReleaseMask,
	             GrabModeAsync, GrabModeAsync,
	             Scr->Root, None, CurrentTime);

	/* Find the workspace button that was clicked */
	occupyW = Scr->workSpaceMgr.occupyWindow;
	for(ws = Scr->workSpaceMgr.workSpaceList; ws != NULL; ws = ws->next) {
		if(occupyW->obuttonw [ws->number] == buttonW) {
			break;
		}
	}

	if(ws != NULL) {
		/* If one was, toggle it */
		int mask = 1 << ws->number;
		ButtonState bs = (occupyW->tmpOccupation & mask) ? off : on;

		PaintWsButton(OCCUPYWINDOW, NULL, occupyW->obuttonw [ws->number],
		              ws->label, ws->cp, bs);
		occupyW->tmpOccupation ^= mask;
	}
	else if(buttonW == occupyW->OK) {
		/* Else if we clicked OK, set things and close the window */
		if(occupyW->tmpOccupation == 0) {
			return;
		}
		ChangeOccupation(occupyWin, occupyW->tmpOccupation);
		XUnmapWindow(dpy, occupyW->twm_win->frame);
		occupyW->twm_win->mapped = false;
		occupyW->twm_win->occupation = 0;
		occupyWin = NULL;
		XSync(dpy, 0);
	}
	else if(buttonW == occupyW->cancel) {
		/* Or cancel, do nothing and close the window */
		XUnmapWindow(dpy, occupyW->twm_win->frame);
		occupyW->twm_win->mapped = false;
		occupyW->twm_win->occupation = 0;
		occupyWin = NULL;
		XSync(dpy, 0);
	}
	else if(buttonW == occupyW->allworkspc) {
		/* Or All, set 'em all */
		for(ws = Scr->workSpaceMgr.workSpaceList; ws != NULL; ws = ws->next) {
			PaintWsButton(OCCUPYWINDOW, NULL, occupyW->obuttonw [ws->number],
			              ws->label, ws->cp, on);
		}
		occupyW->tmpOccupation = fullOccupation;
	}

	/* Release the pointer, if ??? */
	if(ButtonPressed == -1) {
		XUngrabPointer(dpy, CurrentTime);
	}
}


/*
 * f.occupy backend - pop up Occupy control for some window
 */
void
Occupy(TwmWindow *twm_win)
{
	int          x, y;
	unsigned int width, height;
	int          xoffset, yoffset;
	Window       w;
	struct OccupyWindow    *occupyWindow;
	TwmWindow *occupy_twm;

	/* Don't pop up on stuff we can't change */
	if(!CanChangeOccupation(&twm_win)) {
		return;
	}

	/* Grab our one screen-wide f.occupy window */
	occupyWindow = Scr->workSpaceMgr.occupyWindow;
	occupyWindow->tmpOccupation = twm_win->occupation;
	w = occupyWindow->w;

	/* Figure where to put it so it's centered on the cursor */
	XGetGeometry(dpy, w, &JunkRoot, &JunkX, &JunkY, &width, &height,
	             &JunkBW, &JunkDepth);
	XQueryPointer(dpy, Scr->Root, &JunkRoot, &JunkRoot, &JunkX, &JunkY,
	              &x, &y, &JunkMask);
	x -= (width  / 2);
	y -= (height / 2);
	if(x < 0) {
		x = 0;
	}
	if(y < 0) {
		y = 0;
	}
	xoffset = width  + 2 * Scr->BorderWidth;
	yoffset = height + 2 * Scr->BorderWidth + Scr->TitleHeight;

	/* ... (but not off the screen!) */
	if((x + xoffset) > Scr->rootw) {
		x = Scr->rootw - xoffset;
	}
	if((y + yoffset) > Scr->rooth) {
		y = Scr->rooth - yoffset;
	}

	occupy_twm = occupyWindow->twm_win;
	occupy_twm->occupation = twm_win->occupation;

	/* Move the occupy window to where it should be */
	if(occupy_twm->parent_vs != twm_win->parent_vs) {
		occupy_twm->vs = twm_win->parent_vs;
		occupy_twm->frame_x = x;
		occupy_twm->frame_y = y;
		/*
		 * XXX Should this be using DisplayWin() like everything else,
		 * rather than manually grubbing beneath it?
		 */
		ReparentFrameAndIcon(occupy_twm);
	}
	else {
		XMoveWindow(dpy, occupyWindow->twm_win->frame, x, y);
	}

	/* And show it */
	SetMapStateProp(occupy_twm, NormalState);
	XMapWindow(dpy, occupyWindow->w);
	XMapWindow(dpy, occupy_twm->frame);

	/* XXX Must be a better way to express "all the way on top" */
	OtpSetPriority(occupy_twm, WinWin, 0, Above);

	/* Mark it shown, and stash what window we're showing it for */
	occupyWindow->twm_win->mapped = true;
	occupyWin = twm_win;
}




/*
 ****************************************************************
 *
 * Backend and misc
 *
 ****************************************************************
 */


/*
 * The actual meat of occupation-changing; [re-]set the occupation for
 * the window.  This is the func that actually sets and saves the new
 * occupation, moves the window where it should be, etc.  Should maybe be
 * called something more like "SetOccupation()".
 */
void
ChangeOccupation(TwmWindow *tmp_win, int newoccupation)
{
	TwmWindow *t;
	WorkSpace *ws;
	int oldoccupation;
	int changedoccupation;

	if((newoccupation == 0)
	                || (newoccupation == tmp_win->occupation)) {
		/*
		 * occupation=0 we interpret as "leave it alone".  == current,
		 * ditto.  Go ahead and re-set the WM_OCCUPATION property though,
		 * in case it's been broken by another client.
		 */
		char *namelist;
		int  len;
		long eventMask;

		/* Mask out the PropertyChange events while we change the prop */
		eventMask = mask_out_event(tmp_win->w, PropertyChangeMask);

		len = GetPropertyFromMask(tmp_win->occupation, &namelist);
		XChangeProperty(dpy, tmp_win->w, XA_WM_OCCUPATION, XA_STRING, 8,
		                PropModeReplace, (unsigned char *) namelist, len);
		free(namelist);
#ifdef EWMH
		EwmhSet_NET_WM_DESKTOP(tmp_win);
#endif

		/* Reset event mask */
		restore_mask(tmp_win->w, eventMask);
		return;
	}

	/*
	 * OK, there's something to change.  Stash the current state.
	 */
	oldoccupation = tmp_win->occupation;

	/*
	 * Add it to IconManager in the new WS[en], remove from old.  We have
	 * to do the rather odd dance because AddIconManager() loops through
	 * workspaces, and will add it to any workspaces it occupies (even if
	 * it's already there).  RemoveIconManager() goes over the window's
	 * list of what icon managers it's on and removes it from any that
	 * don't match the current occupation, so it can just be told "here's
	 * where I should be".
	 */
	tmp_win->occupation = newoccupation & ~oldoccupation;
	AddIconManager(tmp_win);
	tmp_win->occupation = newoccupation;
	RemoveIconManager(tmp_win);

	/* If it shouldn't be "here", vanish it */
	if(tmp_win->vs && !OCCUPY(tmp_win, tmp_win->vs->wsw->currentwspc)) {
		Vanish(tmp_win->vs, tmp_win);
	}

	/*
	 * Try to find an(other) virtual screen which shows a workspace
	 * where the window has occupation, so that the window can be shown
	 * there now.
	 */
	if(!tmp_win->vs) {
		VirtualScreen *vs;
		for(vs = Scr->vScreenList; vs != NULL; vs = vs->next) {
			if(OCCUPY(tmp_win, vs->wsw->currentwspc)) {
				DisplayWin(vs, tmp_win);
				break;
			}
		}
	}

	/*
	 * Loop over workspaces.  Find the first one that it used to be in.
	 * If it's not there anymore, take it out of the WindowRegion there
	 * (RWFR() silently returns if we're not using WindowRegion's), and
	 * add it the WindowRegion in the first WS it now occupies.
	 *
	 * XXX I'm not sure this is entirely sensible; it seems like just
	 * unconditionally Remove/Place'ing would have the same effect?
	 */
	for(ws = Scr->workSpaceMgr.workSpaceList; ws != NULL; ws = ws->next) {
		int mask = 1 << ws->number;
		if(oldoccupation & mask) {
			if(!(newoccupation & mask)) {
				int final_x, final_y;
				RemoveWindowFromRegion(tmp_win);
				if(PlaceWindowInRegion(tmp_win, &final_x, &final_y)) {
					XMoveWindow(dpy, tmp_win->frame, final_x, final_y);
				}
			}
			break;
		}
	}

	/* Now set the WM_OCCUPATION property */
	{
		char *namelist;
		int  len;
		long eventMask;

		eventMask = mask_out_event(tmp_win->w, PropertyChangeMask);

		len = GetPropertyFromMask(newoccupation, &namelist);
		XChangeProperty(dpy, tmp_win->w, XA_WM_OCCUPATION, XA_STRING, 8,
		                PropModeReplace, (unsigned char *) namelist, len);
		free(namelist);
#ifdef EWMH
		EwmhSet_NET_WM_DESKTOP(tmp_win);
#endif

		restore_mask(tmp_win->w, eventMask);
	}


	/*
	 * Handle showing it up in the workspace map in the appropriate
	 * places.
	 *
	 * Note that this whole block messes with the {new,old}occupation
	 * vars.  That's "safe" because they're no longer used for their
	 * original purposes, only for the WSmap changes, but it's still
	 * kinda fugly.  Change to local vars at the drop of a hat with later
	 * changes...
	 */
	if(!WMapWindowMayBeAdded(tmp_win)) {
		/* Not showing in the map, so pretend it's nowhere */
		newoccupation = 0;
	}
	if(Scr->workSpaceMgr.noshowoccupyall) {
		/*
		 * Don't show OccupyAll.  Note that this means both OccupyAll
		 * window, AND windows manually set to occupy everything.  We
		 * don't have to adjust newoccupation, because the above
		 * conditional would have caught it, so we only need to edit old.
		 */
		if(oldoccupation == fullOccupation) {
			oldoccupation = 0;
		}
	}

	/* Flip the ones that need flipping */
	changedoccupation = oldoccupation ^ newoccupation;
	for(ws = Scr->workSpaceMgr.workSpaceList; ws != NULL; ws = ws->next) {
		int mask = 1 << ws->number;
		if(changedoccupation & mask) {
			if(newoccupation & mask) {
				/* Add to WS */
				WMapAddWindowToWorkspace(tmp_win, ws);
			}
			else {
				/*
				 * Remove from WS.  We have to take it out of saved focus
				 * if it were there.  Maybe there are other places we
				 * might need to remove it from (warpring?)?
				 */
				WMapRemoveWindowFromWorkspace(tmp_win, ws);
				if(Scr->SaveWorkspaceFocus && ws->save_focus == tmp_win) {
					ws->save_focus = NULL;
				}
			}
		}
	}


	/*
	 * If transients don't have their own occupation, find any transients
	 * of this window and move them with it.
	 */
	if(! Scr->TransientHasOccupation) {
		for(t = Scr->FirstWindow; t != NULL; t = t->next) {
			if(t != tmp_win &&
			                ((t->istransient && t->transientfor == tmp_win->w) ||
			                 t->group == tmp_win->w)) {
				ChangeOccupation(t, tmp_win->occupation);
			}
		}
	}

	/* All done */
	return;
}


/*
 * There are various reasons you might not be able to change the
 * occupation of a window (either due to attributes of it, or the state
 * of your session/WM), so provide a function to check them all when we
 * try a change.
 *
 * Note that this is _not_ called from ChangeOccupation(); only from
 * other things that wrap it.  Since CO() gets called from states where
 * this would [falsely] fail, it would be a bad idea to put it there.
 */
static bool
CanChangeOccupation(TwmWindow **twm_winp)
{
	TwmWindow *twm_win;

	/* No workspaces config'd?  Changing is nonsensical. */
	if(!Scr->workSpaceManagerActive) {
		return false;
	}

	/*
	 * f.occupy window up?  Can't change in the middle of changing.
	 * Though if it's not mapped, still pull it up, else iconifying the
	 * occupy window breaks it forever.
	 */
	if(occupyWin != NULL && Scr->workSpaceMgr.occupyWindow->twm_win->mapped) {
		return false;
	}

	/* XXX Can we jut do this in the init?  Check all callers. */
	twm_win = *twm_winp;

	/* Don't change occupation of icon managers */
	if(twm_win->isiconmgr) {
		return false;
	}

	/* XXX Should check iswspmgr here too? */

	/*
	 * If transients don't have their own occupation, check
	 * transient/group bits.
	 */
	if(!Scr->TransientHasOccupation) {
		if(twm_win->istransient) {
			return false;
		}
		if(twm_win->group != (Window) 0 && twm_win->group != twm_win->w) {
			/*
			 * When trying to modify a group member window,
			 * operate on the group leader instead
			 * (and thereby on all group member windows as well).
			 * If we can't find the group leader, pretend it isn't set.
			 */
			twm_win = GetTwmWindow(twm_win->group);
			if(!twm_win) {
				return true;
			}
			*twm_winp = twm_win;
		}
	}

	/* Sure, go ahead, change it */
	return true;
}


/*
 * Add a client name to a list determining which workspaces it will
 * occupy.  Used in handling the Occupy { } block in config file.
 */
bool
AddToClientsList(char *workspace, char *client)
{
	WorkSpace *ws;

	/* "all" is a magic workspace value which makes it occupy anywhere */
	if(strcmp(workspace, "all") == 0) {
		for(ws = Scr->workSpaceMgr.workSpaceList; ws != NULL; ws = ws->next) {
			AddToList(&ws->clientlist, client, "");
		}
		return true;
	}

	/* If prefixed with "ws:", strip the prefix and lookup by WS name */
	if(strncmp(workspace, "ws:", 3) == 0) {
		if((ws = GetWorkspace(workspace + 3)) != NULL) {
			AddToList(&ws->clientlist, client, "");
			return true;
		}
	}

	/* Else find that named workspace and all this to it */
	if((ws = GetWorkspace(workspace)) != NULL) {
		AddToList(&ws->clientlist, client, "");
		return true;
	}

	/* Couldn't figure where to put it */
	return false;
}


/*
 * Turn a ctwm.workspace resource string into an occupation mask.  n.b.;
 * this changes the 'res' arg in-place.
 */
static int
GetMaskFromResource(TwmWindow *win, char *res)
{
	WorkSpace *ws;
	int       mask;
	enum { O_SET, O_ADD, O_REM } mode;
	char *wrkSpcName, *tokst;

	/*
	 * This can set the occupation to a specific set of workspaces ("ws1
	 * ws3"), add to the set it woudl have otherwise ("+ws1 ws3"), or
	 * remove from the set it would otherwise ("-ws1 ws3").  The +/-
	 * apply to the whole expression, not to the individual entries in
	 * it.  So first, figure out what we're doing.
	 */
	mode = O_SET;
	if(*res == '+') {
		mode = O_ADD;
		res++;
	}
	else if(*res == '-') {
		mode = O_REM;
		res++;
	}

	/*
	 * Walk through the string adding the workspaces specified into the
	 * mask of what we're doing.
	 */
	mask = 0;
	for(wrkSpcName = strtok_r(res, " ", &tokst) ; wrkSpcName
	                ; wrkSpcName = strtok_r(NULL, " ", &tokst)) {
		if(strcmp(wrkSpcName, "all") == 0) {
			mask = fullOccupation;
			break;
		}
		if(strcmp(wrkSpcName, "current") == 0) {
			VirtualScreen *vs = Scr->currentvs;
			if(vs) {
				mask |= (1 << vs->wsw->currentwspc->number);
			}
			continue;
		}

		ws = GetWorkspace(wrkSpcName);
		if(ws != NULL) {
			mask |= (1 << ws->number);
		}
		else {
			fprintf(stderr, "unknown workspace : %s\n", wrkSpcName);
		}
	}

	/*
	 * And return that mask, with necessary alterations depending on +/-
	 * specified.
	 */
	switch(mode) {
		case O_SET:
			return (mask);
		case O_ADD:
			return (mask | win->occupation);
		case O_REM:
			return (win->occupation & ~mask);
	}

	/* Can't get here */
	fprintf(stderr, "%s(): Unreachable.\n", __func__);
	return 0;
}


/*
 * Turns a \0-separated buffer of workspace names into an occupation
 * bitmask.
 */
unsigned int
GetMaskFromProperty(unsigned char *_prop, unsigned long len)
{
	char         wrkSpcName[256];
	WorkSpace    *ws;
	unsigned int mask;
	int          l;
	char         *prop;

	mask = 0;
	l = 0;
	prop = (char *) _prop;
	while(l < len) {
		/* If you have WS names longer than 256 chars, that's just Too Bad */
		safe_strncpy(wrkSpcName, prop, 256);
		l    += strlen(prop) + 1;
		prop += strlen(prop) + 1;
		if(strcmp(wrkSpcName, "all") == 0) {
			mask = fullOccupation;
			break;
		}

		ws = GetWorkspace(wrkSpcName);
		if(ws != NULL) {
			mask |= (1 << ws->number);
		}
		else {
			fprintf(stderr, "unknown workspace : %s\n", wrkSpcName);
		}
	}

#if 0
	{
		char *dbs = mk_nullsep_string((char *)_prop, len);
		fprintf(stderr, "%s('%s') -> 0x%x\n", __func__, dbs, mask);
		free(dbs);
	}
#endif

	return (mask);
}


/*
 * Turns an occupation mask into a \0-separated buffer (not really a
 * string) of the workspace names.
 */
int
GetPropertyFromMask(unsigned int mask, char **prop)
{
	WorkSpace *ws;
	int       len;
	char      *wss[MAXWORKSPACE];
	int       i;

	/* If it's everything, just say 'all' */
	if(mask == fullOccupation) {
		*prop = strdup("all");
		return 3;
	}

	/* Stash up pointers to all the labels for WSen it's in */
	memset(wss, 0, sizeof(wss));
	i = 0;
	len = 0;
	for(ws = Scr->workSpaceMgr.workSpaceList; ws != NULL; ws = ws->next) {
		if(mask & (1 << ws->number)) {
			wss[i++] = ws->label;
			len += strlen(ws->label) + 1;
		}
	}

	/* Assemble them into \0-separated string */
	*prop = malloc(len);
	len = 0;
	for(i = 0 ; wss[i] != NULL ; i++) {
		strcpy((*prop + len), wss[i]);
		len += strlen(wss[i]) + 1; // Skip past \0
	}

#if 0
	{
		char *dbs = mk_nullsep_string(*prop, len);
		fprintf(stderr, "%s(0x%x) -> %d:'%s'\n", __func__, mask, len, dbs);
		free(dbs);
	}
#endif

	return len;
}


/*
 * Generate a printable variant of the null-separated strings we use for
 * stashing in XA_WM_OCCUPATION.  Used for debugging
 * Get{Property,Mask}From{Mask,Property}().
 */
#ifdef __GNUC__
# pragma GCC diagnostic push
# pragma GCC diagnostic ignored "-Wunused-function"
#endif
static char *
mk_nullsep_string(const char *prop, int len)
{
	char *dbs;
	int i, j;

	/*
	 * '\0' => "\\0" means we need longer than input; *2 is overkill,
	 * but always sufficient, and it's cheap.
	 */
	dbs = malloc(len * 2);
	i = j = 0;
	while(i < len) {
		size_t slen = strlen(prop + i);

		strcpy(dbs + j, (prop + i));
		i += slen + 1;
		strcpy(dbs + j + slen, "\\0");
		j += slen + 2;
	}

	return dbs;
}
#ifdef __GNUC__
# pragma GCC diagnostic pop
#endif
