/*-
 * 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	11	/* 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)
{
   // check to see if the client could have had focus ...
	if (!client->flags.nofocus)
   {
		focus_unfocus(client);
		focus_list_rm(client);
	}

   /* If icon's aren't sticky, then set the workspace to NULL. This
      will make the icon be drawn in every workspace */
   if( options.sticky_icons == 0 )
    {
       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;


   log( 2, "workspace_add_bypos -> Trying to add a client whose x is: %d", client->x );

	/*
	 * 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;

      log( 2, "new space is current space: %d", newspace );
	}
   else
   {
		/* get the offsets in the workspace grid */

      log( 2, "locating client");
      log( 2, "\tclient->x: %d", client->x );
      log( 2, "\tdesktop->viewx: %d", desktop->viewx );
      log( 2, "\tdesktop->width: %d", desktop->width );
      log( 2, "\tdesktop->height: %d", desktop->height );
      log( 2, "\tclient->screen->width: %d", client->screen->width );

		vx = (client->x + desktop->viewx * client->screen->width)
			/ client->screen->width;
		vy = (client->y + desktop->viewy * client->screen->height)
			/ client->screen->height;

      log( 2, "\tvx: %d", vx );
      log( 2, "\tvy: %d", vy );

		/* 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;

      log( 2, "\tvx new: %d", vx );
      log( 2, "\tvy new: %d", vy );

		/* get new workspace */
      log( 2, "looking for workspace: %d", vx + (vy * desktop->width) );
		newspace = desktop->workspaces[vx + (vy * desktop->width)];

      log( 2, "new space is: %d", newspace );
	}

	/* make sure the client isn't already on this space */
	if( client->workspace == newspace)
   {
      log( 2, "already there ....");
		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);
   }
}

void workspace_add_by_index( desktop_t *desktop,
                             client_t *client,
                             int viewx,
                             int viewy )
{
	workspace_t *newspace;
	int newdesk = 0;
	int vx, vy;

   vx = viewx;
   vy = viewy;

   log( 2, "\tvx: %d", vx );
   log( 2, "\tvy: %d", vy );


   /* get new workspace */
   log( 2, "looking for workspace: %d", vx + (vy * desktop->width) );

   newspace = desktop->workspaces[vx + (vy * desktop->width)];

   log( 2, "workspace_add_by_index: new space is: %d", newspace );

	/* make sure the client isn't already on this space */
	if (client->workspace == newspace)
   {
      log( 2, "already there ....");
		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;
*/
	/* wbh */
	tmpx = desktop->viewx + xmove;
	if ( tmpx < 0 ) tmpx = 0; else
	if ( tmpx >= desktop->width ) tmpx = desktop->width - 1;
	xmove = tmpx - desktop->viewx;

	/* wbh */
	tmpy = desktop->viewy + ymove;
	if ( tmpy < 0 ) tmpy = 0;
	if ( tmpy >= desktop->height ) tmpy = desktop->height - 1;
	ymove = tmpy - desktop->viewy;
	
	/* 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);
            log( 6, "^^^^^^ KEEPCLIENT ^^^^^^^ changing clientx to: %d", client->x );

				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;
}
