/*-
 * Copyright (c) 2001 Jordan DeLong
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. Neither the name of the author nor the names of contributors may be
 *    used to endorse or promote products derived from this software
 *    without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 */
#include "wm.h"

/* number of steps to use for the sliding of windows */
#define SLIDESTEPS	5	/* XXX: make an option? */

/* add a client to a workspace */
void workspace_add_client(workspace_t *workspace, client_t *client) {
	client->workspace = workspace;
	if (!client->flags.nofocus)
		focus_list_add(client);
}

/* remove a client from the workspace it is on */
void workspace_rm_client(client_t *client) {
	if (!client->flags.nofocus) {
		focus_unfocus(client);
		focus_list_rm(client);
	}
	client->workspace = NULL;
}

/*
 * add a client to the appropriate workspace based on it's position.
 * we handle any removal from a workspace that the client is on here: to
 * allow us to not do anything if it's on the workspace that this would move
 * it to.  this function makes calls to desktop_add_client and
 * desktop_rm_cilent when a client's old workspace is on a different desktop
 * than the new one.
 */
void workspace_add_bypos(desktop_t *desktop, client_t *client) {
	workspace_t *newspace;
	dgroup_t *dgroup;
	int newdesk = 0;
	int vx, vy;

	/*
	 * if the client is even partially visible on this workspace
	 * it must be in this workspace; otherwise it's  nonimportant which
	 * workspace it is in if it crosses over lines: we favor up and left;
	 * if it becomes visible on a current_space it isn't part of
	 * workspace_viewport_move will take care of switching it as neccessary.
	 */
	dgroup = client->dgroup;
	if (client->x > -FULLWIDTH(client) && client->y > -FULLHEIGHT(client)
			&& client->x < client->screen->width
			&& client->y < client->screen->height) {
		newspace = desktop->current_space;
	} else {
		/* get the offsets in the workspace grid */	
		vx = (client->x + desktop->viewx * client->screen->width)
			/ client->screen->width;
		vy = (client->y + desktop->viewy * client->screen->height)
			/ client->screen->height;

		/* clip them to the desktop's size */
		if (vx < 0)
			vx = 0;
		else if (vx > desktop->width)
			vx = desktop->width - 1;
		if (vy < 0)
			vy = 0;
		else if (vy > desktop->height)
			vy = desktop->height - 1;

		/* get new workspace */
		newspace = desktop->workspaces[vx + (vy * desktop->width)];
	}

	/* make sure the client isn't already on this space */
	if (client->workspace == newspace)
		return;

	/*
	 * remove from the old, add to the new.  if the client
	 * used to be on an old workspace it might have also been
	 * on a differnt desktop, so we handle that too.
	 */
	if (client->workspace) {
		if (client->workspace->desktop != desktop) {
			desktop_rm_client(client);
			newdesk = 1;
		}
		workspace_rm_client(client);
	} else
		newdesk = 1;
	workspace_add_client(newspace, client);
	if (newdesk)
		desktop_add_client(client);
}

/*
 * modify the effective viewport for a large workspace, xmove and ymove
 * are relative (i.e., 1 or -1 or 0, etc) and in terms of screens.
 *
 * we do virtual desktops by moving windows in the opposite direction of
 * the viewport.  to make stuff make more sense to the user we maintain
 * focus lists for each viewport; which means that here we need to handle
 * clients that'll end up still visible (i.e. are half between viewports)
 * by putting them into the new focuslist.
 */
int workspace_viewport_move(screen_t *screen, desktop_t *desktop,
		int xmove, int ymove) {
	XEvent ev;
	client_t *focusthis = NULL;
	client_t *client;
	workspace_t *workspace;
	int tmpx, tmpy;
	int i, modx, mody;
	int moving;

	/* get new offsets */
	tmpx = desktop->viewx + xmove;
	if (tmpx >= desktop->width || tmpx < 0)
		return 0;
	tmpy = desktop->viewy + ymove;
	if (tmpy >= desktop->height || tmpy < 0)
		return 0;

	/* don't move to the current workspace */
	if (tmpx == desktop->viewx && tmpy == desktop->viewy)
		return 0;

	/* get the new workspace */
	workspace = desktop->workspaces[tmpx + (tmpy * desktop->width)];

	/* slide the windows to their new position */
	if (options.workspace_slide && desktop == screen->desktop) {
		moving = 0;
		modx = (xmove * screen->width) / SLIDESTEPS;
		mody = (ymove * screen->height) / SLIDESTEPS;
		for (i = 0; i < SLIDESTEPS; i++) {
			LIST_FOREACH(client, &client_list, c_list) {
				if (client->state == NormalState && !client->flags.internal
						&& client->workspace->desktop == desktop
						&& !client->flags.sticky) {
					XMoveWindow(display, client->frame,
						client->x - (modx * i),
						client->y - (mody * i));
					moving = 1;
				}
			}

			/* if nothing is moving we just move on */
			if (!moving)
				break;

			/*
			 * delay for this frame of the sliding.  it also
			 * necessary to handle any exposures we may be
			 * getting to keep our visuals looking good.
			 */
			XSync(display, 0);
			while (XCheckMaskEvent(display, ExposureMask, &ev))
				event_handle(&ev);
			usleep(5);
		}
	}

/* keep a client by putting it into the new workspace's focuslist */
#define KEEPCLIENT(c) do {					\
	if (desktop->current_space->focused == (c)) {		\
		if (focusthis == NULL)				\
			focusthis = (c);			\
	}							\
	workspace_rm_client((c));				\
	workspace_add_client(workspace, (c));			\
} while (0)

	/* now switch to the new workspace */
	LIST_FOREACH(client, &client_list, c_list)
		if (client->state == NormalState && !client->flags.internal
				&& client->workspace->desktop == desktop) {
			if (!client->flags.sticky) {
				client->x -= (xmove * screen->width);
				client->y -= (ymove * screen->height);
				client_sizeframe(client);

				/*
				 * clients recieve a synthetic config, because they have
				 * moved, even though the illusion is that the 'viewport'
				 * is what moved.
				 */
				action_send_config(client);

				/*
				 * if the client is visible but thinks it's on a diff
				 * wspace it must be kept.
				 */
				if (client->workspace != workspace
						&& client->x > -FULLWIDTH(client)
						&& client->y > -FULLHEIGHT(client)
						&& client->x < screen->width
						&& client->y < screen->height)
					KEEPCLIENT(client);
			} else
				KEEPCLIENT(client);
		}

#undef KEEPCLIENT

	/* set the new offsets */
	if (tmpx == desktop->viewx && tmpy == desktop->viewy)
		return 0;
	desktop->viewx = tmpx;
	desktop->viewy = tmpy;

	/* set the new current_space */
	desktop->current_space = workspace;

	/* set the focus */
	if (focusthis)
		focus_client(focusthis);
	else if (workspace->focused)
		focus_client(workspace->focused);
	else
		focus_none(screen);

	/* let plugins know */
	plugin_workspace_change(screen, desktop);

	return 1;
}
