/* 
 * Oroborus Window Manager
 *
 * Copyright (C) 2001 Ken Lynch
 * Copyright (C) 2002 Stefan Pfetzing
 *
 * 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 <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <glib.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 "xerror.h"

#define DBUG_VERBOSE(x)			// DBUG(x)


Atom intern_atoms[INTERN_ATOM_COUNT];
							   
int
get_property(Display *dpy, Window w, Atom name, Atom type, void *addr, int size, int min, int on_error)
{
        Atom ret_type;
        int ret_format;
        unsigned long items;
        unsigned long bytes;
        unsigned char *ptr = NULL;
        int ret = XGetWindowProperty(dpy, w, name, 0, (size / 4) + 1, False, 
						type, &ret_type, &ret_format, &items, &bytes, &ptr);
        dbg("XGetWindowProperty: ret %i, ret_type: %u, ret_format: %u, items: %i, bytes: %i\n", 
						ret, ret_type, ret_format, items, bytes);
        if(ret != Success) {
                if(on_error >= 1) {
                        fprintf(stderr, "XGetWindowProperty failed\n");
                        if(on_error >= 2)
                                exit(2);
                }
                return -1;
        }

        int nbytes;
        switch (ret_format) {
        case 32: nbytes = sizeof(long); break;
        case 16: nbytes = sizeof(short); break;
        case 8: nbytes = 1; break;
        case 0: nbytes = 0; break;
        default: abort();
        }

        unsigned long gotten = nbytes * items;

        if (gotten < min) {
                if(on_error >= 1) {
                        fprintf(stderr, "property not big enough, items: %lu gotten: %lu format: %i bytes: %lu\n", items, gotten, ret_format, bytes);
                        if(on_error >= 2)
                                exit(2);
                }
                XFree(ptr);
                return -1;
        }

        if(addr)
                memcpy(addr,ptr,MIN(gotten, size));
        XFree(ptr);
        return MIN(gotten,size);
}


void *getPropDataWithItems(Window w, Atom prop, Atom type, gulong *items_ret)
{
	Atom type_ret;
	int format_ret, ok;
	gulong after_ret;
	unsigned char *prop_data = NULL;

	DBUG_VERBOSE("entering getPropDataWithItems");

	ok = XGetWindowProperty(dpy, w, prop, 0, 0x7fffffff, False, type, &type_ret,
							&format_ret, items_ret, &after_ret, &prop_data);

	if ((ok == Success) && prop_data && (*items_ret > 0) && (format_ret == 32))
	{
		return prop_data;
	}

	x_free(prop_data);
	return NULL;
}

static void *getPropData(Window w, Atom prop, Atom type)
{
	gulong items;

	return getPropDataWithItems(w, prop, type, &items);
}




static char *InternAtomNames[] = {
			
	/* ICCM */
	"WM_STATE",
	"WM_CHANGE_STATE",
	"WM_TAKE_FOCUS",
	"WM_DELETE_WINDOW",
	"WM_SAVE_YOURSELF",
	"WM_PROTOCOLS",
	"WM_WINDOW_ROLE",
	"WM_CLIENT_LEADER",
	"SM_CLIENT_ID",
	"UTF8_STRING",
	
	/* ICCM  */
	"WM_TRANSIENT_FOR",
	"WM_COLORMAP_WINDOWS",
	"WM_CLIENT_MACHINE",

	/* Motif */
	"_MOTIF_WM_HINTS",
		
	/* EWMH */
	"_NET_SUPPORTED",
	"_NET_CLIENT_LIST",
	"_NET_CLIENT_LIST_STACKING",

	"_NET_WM_WINDOW_TYPE",
	"_NET_WM_WINDOW_TYPE_NORMAL",
	"_NET_WM_WINDOW_TYPE_DESKTOP",
	"_NET_WM_WINDOW_TYPE_DOCK",
	"_NET_WM_WINDOW_TYPE_DIALOG",
	"_NET_WM_WINDOW_TYPE_TOOLBAR",
	"_NET_WM_WINDOW_TYPE_MENU",
	"_NET_WM_WINDOW_TYPE_UTILITY",
	"_NET_WM_WINDOW_TYPE_SPLASH",
	"_NET_WM_WINDOW_TYPE_INPUT",

	"_NET_WM_STATE",
	"_NET_WM_STATE_MODAL",
	"_NET_WM_STATE_STICKY",
	"_NET_WM_STATE_MAXIMIZED_VERT",
	"_NET_WM_STATE_MAXIMIZED_HORZ",
	"_NET_WM_STATE_SHADED",
	"_NET_WM_STATE_SKIP_TASKBAR",
	"_NET_WM_STATE_SKIP_PAGER",
	"_NET_WM_STATE_HIDDEN",
	"_NET_WM_STATE_ABOVE",
	"_NET_WM_STATE_BELOW",
	"_NET_WM_STATE_FULLSCREEN",

	"_NET_WM_NAME",
	"_NET_WM_DESKTOP",
	"_NET_NUMBER_OF_DESKTOPS",
	"_NET_CURRENT_DESKTOP",
	"_NET_ACTIVE_WINDOW",
	"_NET_SUPPORTING_WM_CHECK",
	"_NET_WM_WORKAREA",
	"_NET_WORKAREA",
	"_NET_CLOSE_WINDOW",
	"_NET_WM_STRUT",
	"_NET_WM_STRUT_PARTIAL",
	"_NET_WM_ICON_NAME",
	"_NET_WM_MOVERESIZE",
	"_NET_DESKTOP_VIEWPORT",
	"_NET_DESKTOP_GEOMETRY",

	"_NET_WM_ALLOWED_ACTIONS",
	"_NET_WM_ACTION_MOVE",
	"_NET_WM_ACTION_RESIZE",
	"_NET_WM_ACTION_MINIMIZE",
	"_NET_WM_ACTION_SHADE",
	"_NET_WM_ACTION_STICK",
	"_NET_WM_ACTION_MAXIMIZE_HORZ",
	"_NET_WM_ACTION_MAXIMIZE_VERT",
	"_NET_WM_ACTION_FULLSCREEN",
	"_NET_WM_ACTION_CHANGE_DESKTOP",
	"_NET_WM_ACTION_CLOSE",
	"_NET_SHOWING_DESKTOP",
	"_NET_FRAME_EXTENTS",
	"_NET_REQUEST_FRAME_EXTENTS",

	
#ifdef NOT_YET_IMPLEMENTED
	"_NET_DESKTOP_LAYOUT",
	"_NET_DESKTOP_NAMES",

	"_NET_WM_ICON",
	"_NET_WM_ICON_GEOMETRY",

	"_NET_WM_SYNC_REQUEST_COUNTER",
    "_NET_WM_SYNC_REQUEST",
	
	"_NET_WM_CONTEXT_HELP",
    "_NET_WM_CONTEXT_ACCEPT",
	"_NET_WM_CONTEXT_CUSTOM",	
#endif

#ifdef HAVE_STARTUP_NOTIFICATION
	"_NET_STARTUP_ID",
#endif
	"_NET_WM_PING",
	"_NET_WM_PID"
};

void initHints(void)
{
	XInternAtoms(dpy, InternAtomNames, INTERN_ATOM_COUNT, False, intern_atoms);
	XChangeProperty(dpy, root, intern_atoms[NET_SUPPORTED], XA_ATOM, 32, PropModeReplace,
					 (unsigned char *) &intern_atoms[NET_SUPPORTED+1], INTERN_ATOM_COUNT-NET_SUPPORTED-1);
}

void get_size_hints(Client * c)
{
	long dummy;
	c->gravity = NorthWestGravity;
	g_return_if_fail(c->size != NULL);
	if (XGetWMNormalHints(dpy, c->window, c->size, &dummy))
	{
		if (c->size->flags & PWinGravity)
			c->gravity = c->size->win_gravity;
	}
	//c->wmhints = XGetWMHints(dpy, c->window);
}

void get_transientFor(Client * c, Display * dpy, int screen)
{
	Window transient_for;

	DBUG("entering getTransientFor");

	if (XGetTransientForHint(dpy, c->window, &transient_for))
	{
		if (transient_for == None)
		{
			/* Treat transient for "none" same as transient for root */
			// FIXME: root is not in client struct, so we can't set it here
			transient_for = RootWindow(dpy, screen);
			goto set_transient;
		}
		else if (transient_for != c->window)
		{
		  set_transient:
			c->transientFor = transient_for;
		}
	}


#ifdef DEBUG
	if (c->transientFor)
	{
		DBUG("TransientFor Hint:");
		DBUG("Window");
		if (c->class.res_name)
			DBUG(c->class.res_name);
		DBUG("is transient for");

		if (c->transientFor != None)
		{
			/*Client *ct = client_of_window(c->transientFor);       
			   if (ct && ct->transientFor->class.res_name)
			   DBUG(ct->transientFor->class.res_name); */
		}
	}
#endif
}

gboolean get_property_card32(Window w, Atom a, gulong *value)
{
	DBUG_VERBOSE("get_xproperty_card32()");

	gulong *data = getPropData(w, a, XA_CARDINAL);

	if (data)
	{
		*value = *data;
		XFree(data);
		return TRUE;
	}

	*value = 0;
	return FALSE;
}


void set_property_card32(Window w, Atom a, gulong value)
{
	DBUG_VERBOSE("set_property_card32()");

	XChangeProperty(dpy, w, a, XA_CARDINAL, 32, PropModeReplace, (unsigned char *) &value, 1);
}

void set_property_xy(Window w, Atom a, gulong x, gulong y)
{
	DBUG_VERBOSE("set_property_xy()");
	gulong xy[2] = { x, y };
	XChangeProperty(dpy, w, a, XA_CARDINAL, 32, PropModeReplace, (unsigned char *)xy, 2);
}


gulong get_wm_state(Window w)
/* in: Window out: WM_STATE || WithdrawnState */
{
	DBUG_VERBOSE("entering getWmState");

	misc_trap_xerrors();
	
	gulong state = WithdrawnState;
	gulong *data = getPropData(w, xa_wm_state, xa_wm_state);
	if (data)
		state = *data;
	
  	if (misc_untrap_xerrors()) 	/* Just in case */
    	state = WithdrawnState;

	x_free(data);
	
	return state;
}

void set_wm_state(Window w, gulong state)
{
	gulong data[2];

	DBUG_VERBOSE("entering set_wm_state");

	data[0] = state;
	data[1] = None;

	XChangeProperty(dpy, w, xa_wm_state, xa_wm_state, 32, PropModeReplace,
					(unsigned char *) data, 2);
}

void get_wm_protocols(Client * c)
{
	int items = 0;
	Atom *atoms = None;
	gulong nitems = 0;
	Atom atype;
	int aformat;
	gulong bytes_remain;

	gulong wm_protocols = 0;
	
	if (XGetWMProtocols(dpy, c->window, &atoms, &items))
		nitems = (gulong) items;
	else if ((XGetWindowProperty(dpy, c->window, xa_wm_protocols, 0L, 10L, FALSE,
								 xa_wm_protocols, &atype, &aformat, &nitems, &bytes_remain,
								 (unsigned char **) &atoms)) == Success)
		items = (int) nitems;
	else
		goto out;


#ifdef DEBUG
	if (items)
			dbg("wm_protocols on: %s/%s\n\n", c->class.res_class, c->class.res_name);
#endif
	while (--items >= 0)
	{
		if (atoms[items] == xa_wm_take_focus)
		{
			DBUG("wm_take_focus");
			wm_protocols |= PROTOCOL_WM_TAKE_FOCUS;
		}
		else if (atoms[items] == xa_wm_delete_window)
		{
			DBUG("wm_delete_window");
			wm_protocols |= PROTOCOL_WM_DELETE_WINDOW;
		}
		else if (atoms[items] == xa_wm_save_yourself)
		{
			DBUG("wm_save_yourself");
			wm_protocols |= PROTOCOL_WM_SAVE_YOURSELF;
		}
		else if (atoms[items] == intern_atoms[NET_WM_PING])
		{
			DBUG("NET_WM_PING");
			wm_protocols |= PROTOCOL_WM_PING;
		}
#ifdef USE_XSYNC
	   else if (atoms[items] == intern_atoms[NET_WM_SYNC_REQUEST])
	     {
		   wm_protocols |= PROTOCOL_WM_SYNC;
	       dbg("%s() client has _NET_WM_SYNC_REQUEST\n", __func__);
	     }
#endif

	};

  out:
	c->wm_protocols = wm_protocols;
	x_free(atoms);
	

}


void get_net_wm_type(Client * c)
{

	gulong items = 0;
	Atom *atoms;

	DBUG_VERBOSE("entering get_net_wm_type");

	c->type_atom = None;
	c->type = WINDOW_NORMAL;

	if ((atoms = getPropDataWithItems(c->window, intern_atoms[NET_WM_WINDOW_TYPE], XA_ATOM, &items)))
	{
		while (--items >= 0)
		{
			Atom atom = atoms[items];
			int type = NET_WM_WINDOW_TYPE;

			while ((++type <= NET_WM_WINDOW_TYPE_SPLASH))
				if (atom == intern_atoms[type])
				{
					c->type_atom = atom;
					c->type = 1 << (type - (NET_WM_WINDOW_TYPE + 1));
					goto out;
				}
		}
	  out:
		XFree(atoms);
	}

#ifdef DEBUG
	fprintf(stderr, "type=%d\n", c->type);
#endif

}

void get_net_wm_state(Client * c)
{

	DBUG_VERBOSE("entering get_net_wm_state");

	/* We know this is only on initial window creation,
	 * clients don't change the property.
	 */

	gulong items = 0;
	Atom *atoms;

	c->state = 0;

	if ((atoms = getPropDataWithItems(c->window, intern_atoms[NET_WM_STATE], XA_ATOM, &items)))
	{
		//printf("items=%ld\n",items);

		if (items)
			do
			{
				int state = NET_WM_STATE;

				while ((++state <= NET_WM_STATE_FULLSCREEN))
					if (atoms[items - 1] == intern_atoms[state])
						c->state |= 1 << (state - (NET_WM_STATE + 1));
			} while (--items);

		XFree(atoms);
	}

#ifdef DEBUG
	fprintf(stderr, "state=%ld\n", c->state);
#endif

	//recalc_window_type (window);

}



void set_net_wm_state(Client * c)
{
	gulong data[16];			/* 64bit align */
	int i = 0;
	int n = 0;

	do
	{
		if ((c->state & (1 << i)))
			data[n++] = intern_atoms[NET_WM_STATE + 1 + i];
	} while (++i <= 10);

//  if (n > 0)
	XChangeProperty(dpy, c->window, intern_atoms[NET_WM_STATE],
					XA_ATOM, 32, PropModeReplace, (char *) data, n);
	//else /* the docs say that's the right thing, but it doesn't work :( */
	//XDeleteProperty(dpy, c->window, intern_atoms[NET_WM_STATE]);

}

void set_allowed_actions_hint(Client * c)
{
	gulong data[16];			/* 64bit align */
	int i = 0;
	int n = 0;

	do
	{
		if ((c->actions & (1 << i)))
			data[n++] = intern_atoms[NET_WM_ALLOWED_ACTIONS + 1 + i];
	} while (++i <= 9);

	//if (n > 0)
	XChangeProperty(dpy, c->window, intern_atoms[NET_WM_ALLOWED_ACTIONS],
					XA_ATOM, 32, PropModeReplace, (char *) data, n);
	//else
	//    XDeleteProperty(dpy, c->window, intern_atoms[NET_WM_ALLOWED_ACTIONS]);

}

void set_mapping_state(Client * c, long state)
{
	if (state == IconicState)
		c->state |= STATE_HIDDEN;
	else
		c->state &= ~STATE_HIDDEN;

	set_wm_state(c->window, state);
	set_net_wm_state(c);
}


void get_mwm_hints(Client * c)
{
	PropMwmHints *hints;
	MWMHints mwm_hints = 0;

	if (!(hints = getPropData(c->window, xa_motif_wm_hints, xa_motif_wm_hints)))
	{
		DBUG_VERBOSE("Window has no MWM hints");
		goto out;
	}

	/* We support those MWM hints deemed non-stupid */

	DBUG_VERBOSE("Window has MWM hints");

	mwm_hints =
		(MWM_HAS_CLOSE_FUNC | MWM_HAS_MINIMIZE_FUNC | MWM_HAS_MAXIMIZE_FUNC | MWM_HAS_MOVE_FUNC |
		 MWM_HAS_RESIZE_FUNC);

	if (hints->flags & MWM_HINTS_FUNCTIONS)
	{
		mwm_hints &= hints->functions;
#ifdef DEBUG
		if (c->class.res_name)
			fprintf(stderr, "Window %s sets MWM_HINTS_FUNCTIONS 0x%lx\n", c->class.res_name,
					hints->functions);
#endif
		/* If _ALL is specified, then other flags indicate what to turn off;
		   if ALL is not specified, flags are what to turn on. at least, I think so */

		if ((hints->functions & MWM_FUNC_ALL) == 0)
		{
#ifdef DEBUG
			if (c->class.res_name)
				fprintf(stderr, "Window %s disables all funcs then reenables some\n",
						c->class.res_name);
#endif
		}
		else
		{
#ifdef DEBUG
			if (c->class.res_name)
				fprintf(stderr, "Window %s enables all funcs then disables some\n",
						c->class.res_name);
#endif
			mwm_hints ^=
				(MWM_FUNC_CLOSE | MWM_FUNC_MINIMIZE | MWM_FUNC_MAXIMIZE | MWM_FUNC_MOVE |
				 MWM_FUNC_RESIZE);
		}

	}
	else
		DBUG_VERBOSE("Functions flag unset");

	if (hints->flags & MWM_HINTS_DECORATIONS)
	{
#ifdef DEBUG
		if (c->class.res_name)
			fprintf(stderr, "Window %s sets MWM_HINTS_DECORATIONS 0x%lx\n",
					c->class.res_name, hints->functions);
#endif
		if (hints->decorations != 0)
		{
			/* some input methods use this */
			if (hints->decorations == MWM_DECOR_BORDER)
				mwm_hints |= MWM_BORDER_ONLY;
			goto set_decorated;
		}
	}
	else
	{
		DBUG_VERBOSE("Decorations flag unset");
	  set_decorated:
		mwm_hints |= MWM_DECORATED;
	}

	XFree(hints);
	mwm_hints |= MWM_HAS_HINTS;

  out:
	c->mwm_hints = mwm_hints;
}

inline static gboolean equals(char *left, char *right)
{
	return (left && right && (strcmp(left, right) == 0));
}

gboolean get_window_role(Display * dpy, Window window, char **role)
{
	XTextProperty tp;

	tp.value = NULL;
	tp.nitems = 0;

	DBUG("entering get_window_role");

	g_return_val_if_fail(role != NULL, FALSE);
	*role = NULL;
	g_return_val_if_fail(window != None, FALSE);

	if (XGetTextProperty(dpy, window, &tp, xa_wm_window_role))
	{
		if ((tp.value) && (tp.encoding == XA_STRING) && (tp.format == 8) && (tp.nitems != 0))
		{
			*role = strdup((char *) tp.value);
			XFree(tp.value);
			return TRUE;
		}
	}

	return FALSE;
}

Window get_client_leader(Display * dpy, Window window)
{
	Window client_leader = None;
	Atom actual_type;
	int actual_format;
	gulong nitems;
	gulong bytes_after;
	unsigned char *prop = NULL;

	DBUG("entering get_client_leader");

	g_return_val_if_fail(window != None, None);

	if (XGetWindowProperty(dpy, window, xa_wm_client_leader, 0L, 1L, FALSE,
						   AnyPropertyType, &actual_type, &actual_format, &nitems,
						   &bytes_after, &prop) == Success)
	{
		if ((prop) && (actual_type == XA_WINDOW) && (actual_format == 32)
			&& (nitems == 1) && (bytes_after == 0))
		{
			client_leader = *((Window *) prop);
		}
		x_free(prop);
	}
	return client_leader;
}

gboolean get_client_id(Display * dpy, Window window, char **client_id)
{
	Window id;
	XTextProperty tp;

	tp.value = NULL;
	tp.nitems = 0;

	DBUG("entering get_client_id");

	g_return_val_if_fail(client_id != NULL, FALSE);
	*client_id = NULL;
	g_return_val_if_fail(window != None, FALSE);

	if ((id = get_client_leader(dpy, window)))
	{

		if (XGetTextProperty(dpy, id, &tp, xa_sm_client_id))
		{

			if (tp.encoding == XA_STRING && tp.format == 8 && tp.nitems != 0)
			{
				*client_id = g_strdup((char *) tp.value);
				XFree(tp.value);
				return TRUE;
			}
		}
	}

	return FALSE;
}

gboolean get_window_command(Display * dpy, Window window, char ***argv, int *argc)
{
	Window id;

	*argc = 0;
	g_return_val_if_fail(window != None, FALSE);

	if (XGetCommand(dpy, window, argv, argc) && (*argc > 0))
	{
		return TRUE;
	}
	if ((id = get_client_leader(dpy, window)))
	{
		if (XGetCommand(dpy, id, argv, argc) && (*argc > 0))
		{
			return TRUE;
		}
	}
	return FALSE;
}



void set_window_hint(Atom a, Window w)
{
	dbg("%s\n",__func__);
	XChangeProperty(dpy, root, a, XA_WINDOW, 32, PropModeReplace, (unsigned char *) &w, 1);
}

