/* 
 * OroboROX Window Manager
 * 
 * Copyright (C) 2004 Guido Schimmels
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2, or (at your option)
 * any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software Foundation,
 * Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.  
 */

#include "config.h"
#include <glib.h>
#include <stdio.h>
#include <stdlib.h>
#include <X11/Xlib.h>
#include <X11/Xutil.h>
#include <X11/Xmd.h>
#include <ft2build.h>
#include <X11/Xft/Xft.h>
#include "main.h"
#include "hints.h"
#include "client.h"
#include "workspaces.h"
#include "keyboard.h"
#include "pixmap.h"
#include "settings.h"
#include "stacking.h"
#include "focus.h"
#include "xerror.h"



/* X.h */

/* Window stacking method (in configureWindow)  */

//#define Above                   0 Window is placed just above sibling.
//#define Below                   1 Window is placed just below sibling.
//#define TopIf                   2 If sibling obscures window, then window is placed at the top of the stack.
//#define BottomIf                3 If window obscures sibling, then window is placed at the bottom of the stack.
//#define Opposite                4 If any sibling occludes window, then window is placed at the top of the stack, else if window occludes any sibling, then window is placed at the bottom of the stack.

/* Circulation direction */

//#define RaiseLowest             0
//#define LowerHighest            1
//
//
//
//

GList *clients = NULL;
GArray *client_list = NULL, *client_list_stacking = NULL;
static Client *raise_client = NULL;
static guint timeout_id = -1;

WindowLayer client_layer(Client * c)
{
	/*Windows that are transient for another window should be kept above this window. */
	/* FIXME: Stops windows of other apps from raising above the transient window. Why? */
	/*if (c->transientFor != None)
	   return client_layer(get_client_of_window(c->transientFor)) + 1;
	 */
	if (c->type == WINDOW_DESKTOP)
		return LAYER_DESKTOP;

	int state = c->state;

	if ((state & STATE_FULLSCREEN) && get_input_focus() == c->window)
		return LAYER_FULLSCREEN;

	if (state & STATE_BELOW)
		return LAYER_BELOW;

	if ((state & STATE_ABOVE) || (c->type == WINDOW_DOCK))
		return LAYER_ABOVE;

	return LAYER_NORMAL;
}



void put_on_top_of_stack(Client * c)
{
	int i = client_list_stacking->len;

	while (--i >= 0)
		if (g_array_index(client_list_stacking, Window, i) == c->window)
		{
			client_list_stacking = g_array_remove_index(client_list_stacking, i);
			client_list_stacking = g_array_append_val(client_list_stacking, c->window);
			break;
		}

	client_sync_client_list();
}

void shuffle_below_normal_layer(Client * c)
{
	int i, j;

	if ((i = client_list_stacking->len) < 2)
		return;

	j = i - 1;

	while (--i >= 0)
		if (g_array_index(client_list_stacking, Window, i) == c->window)
			client_list_stacking = g_array_remove_index(client_list_stacking, i);

	while (--j >= 0)
	{
		Client *c2 = client_of_window(g_array_index(client_list_stacking, Window, j));

		if ((client_layer(c2) < LAYER_NORMAL) || c->non_focusing)
			break;
	}

	client_list_stacking = g_array_insert_val(client_list_stacking, j, c->window);

	client_sync_client_list();
}

void client_lower(Client * c)
{
	int i = client_list_stacking->len;

	while (--i >= 0)
		if (g_array_index(client_list_stacking, Window, i) == c->window)
		{
			client_list_stacking = g_array_remove_index(client_list_stacking, i);
			client_list_stacking = g_array_prepend_val(client_list_stacking, c->window);
			break;
		}

	client_sync_client_list();
}



void client_lower_force(Client * c)
{
	g_return_if_fail(c);

	dbg("lowering client: %s\n", c->class.res_name);
	
	if ((c->type != WINDOW_DESKTOP) && !(c->state & STATE_BELOW))
	{
		XWindowChanges wc;

		wc.stack_mode = Below;
		clientConfigure(c, &wc, CWStackMode);
		shuffle_below_normal_layer(c);
		//apply_focus_policy(c);
	}

	client_lower(c);
}

void cancel_pending_raise(void)
{
	DBUG("cancel_pending_raise()");
	if (timeout_id >= 0)
		g_source_remove(timeout_id);
	timeout_id = -1;
}

gboolean delayed_raise(gpointer data)
{
	DBUG("delayed_raised()");

	if (timeout_id >= 0)
		client_raise(raise_client);
	
	timeout_id = -1;
	return FALSE; /* only once */
}

void apply_raise_policy(Client *c)
{
	cancel_pending_raise();
	if ((raise_policy & RAISE_UNIX) && (raise_delay >= 0))
	{		
		timeout_id = g_timeout_add(raise_delay, delayed_raise, NULL);
		raise_client = c;
	}
	else if ((c->decor_state == ACTIVE) && (raise_policy != RAISE_RISCOS))
	{
		if (g_array_index(client_list_stacking, Window, 
							(client_list_stacking->len)-1) != c->window)
			client_raise(c);
	}

}

void client_raise(Client * c)
{
	if (!client_exists(c))
		return;

	cancel_pending_raise();
	
#ifdef DEBUG
	DBUG("raising client:");
	if (c->class.res_name)
		DBUG(c->class.res_name);
#endif

	XWindowChanges wc;

	wc.stack_mode = Above;
	clientConfigure(c, &wc, CWStackMode);

	put_on_top_of_stack(c);

	GList *l;

	for (l = clients; l != NULL; l = l->next)
	{
		Client *c2 = (Client *) l->data;

		if (c2->transientFor == c->window)
			client_raise(c2);
	}
}

static void client_raise_force(Client * c)
{
	g_return_if_fail(c);

	if (c->non_focusing)
	{
#ifdef DEBUG
		DBUG("NOT raising non_focusing client:");
		if (c->class.res_name)
			DBUG(c->class.res_name);
#endif
		return;
	}

	client_raise(c);
}


void client_inc_layer(Client * c)
{
	if (c->state & STATE_BELOW)
		c->state ^= STATE_BELOW;
	else if (!(c->state & STATE_ABOVE))
		c->state ^= STATE_ABOVE;
	set_net_wm_state(c);
}

void client_dec_layer(Client * c)
{
	if (c->state & STATE_ABOVE)
		c->state ^= STATE_ABOVE;
	else if (!(c->state & STATE_BELOW))
		c->state ^= STATE_BELOW;
	set_net_wm_state(c);
}



Client *clientGetTopMostShaded(int layer, int non_shaded)
{
	Window w1, w2, *wins;
	unsigned int i, count;
	XWindowAttributes attr;
	Client *top = NULL, *c;

	dbg("entering clientGetTopMostShaded()");

	XQueryTree(dpy, root, &w1, &w2, &wins, &count);
	for (i = 0; i < count; i++)
	{
		XGetWindowAttributes(dpy, wins[i], &attr);
		c = client_of_decor(wins[i]);

		if (c && attr.map_state == IsViewable)
		{
			if (client_layer(c) <= layer)
				top = c;
			else
				break;
		}
	}
	x_free(wins);
	return top;
}

Client *clientGetTopMost(int layer)
{
	return clientGetTopMostShaded(layer, 0);
}

Client *clientGetBottomMost(int layer)
{
	Window w1, w2, *wins;
	unsigned int i, count;
	XWindowAttributes attr;
	Client *c = NULL;

	dbg("entering clientGetBottomMost");

	XQueryTree(dpy, root, &w1, &w2, &wins, &count);
	for (i = 0; i < count; i++)
	{
		XGetWindowAttributes(dpy, wins[i], &attr);
		c = client_of_decor(wins[i]);
		if (c && (attr.map_state == IsViewable))
		{
			if (client_layer(c) >= layer)
				break;
		}
	}
	x_free(wins);
	return c;
}
