/*
 * Copyright (C) 1996 Frank Fejes
 * Copyright (C) 1995 Bo Yang
 * Copyright (C) 1993 Robert Nation
 *
 * 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 of the License, 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., 675 Mass Ave, Cambridge, MA 02139, USA.
 *
 */

#include "../configure.h"

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

#include "afterstep.h"
#include "menus.h"
#include "misc.h"
#include "parse.h"
#include "screen.h"

void SmartPlacement(ASWindow * t, int width, int height, int *x, int *y)
{
    int temp_h, temp_w;
    int test_x = 0, test_y = 0;
    int loc_ok = False, tw, tx, ty, th;
    ASWindow *test_window;

    temp_h = height;
    temp_w = width;

    while (((test_y + temp_h) < (Scr.MyDisplayHeight)) && (!loc_ok)) {
	test_x = 0;
	while (((test_x + temp_w) < (Scr.MyDisplayWidth)) && (!loc_ok)) {
	    loc_ok = True;
	    test_window = Scr.ASRoot.next;
	    while ((test_window != (ASWindow *) 0) && (loc_ok == True)) {
		if (test_window->Desk == Scr.CurrentDesk) {
		    if (StubbornPlacement && (test_window->flags & ICONIFIED) &&
			test_window->icon_pixmap_w &&
			(test_window != t)) {
			tw = test_window->icon_p_width;
			th = test_window->icon_p_height;
			tx = test_window->icon_x_loc;
			ty = test_window->icon_y_loc;

			if ((tx < (test_x + width + 1)) && ((tx + tw + 1) > test_x) &&
			    (ty < (test_y + height + 1)) && ((ty + th + 1) > test_y)) {
			    loc_ok = False;
			}
		    }
		    if (!(test_window->flags & ICONIFIED) && (test_window != t)) {
			tw = test_window->frame_width + 2 * test_window->bw;
			th = test_window->frame_height + 2 * test_window->bw;
			tx = test_window->frame_x;
			ty = test_window->frame_y;
			if ((tx <= (test_x + width)) && ((tx + tw) >= test_x) &&
			    (ty <= (test_y + height)) && ((ty + th) >= test_y)) {
			    loc_ok = False;
			    test_x = tx + tw;
			}
		    }
		}
		test_window = test_window->next;
	    }
	    test_x += 1;
	}
	test_y += 1;
    }
    if (loc_ok == False) {
	*x = -1;
	*y = -1;
	return;
    }
    *x = test_x;
    *y = test_y;
}


/**************************************************************************
 *
 * Handles initial placement and sizing of a new window
 * Returns False in the event of a lost window.
 *
 **************************************************************************/
Bool
PlaceWindow(ASWindow * tmp_win, unsigned long tflag, int Desk)
{
    ASWindow *t;
    int xl = -1, yt, DragWidth, DragHeight;
    int gravx, gravy;		/* gravity signs for positioning */
    extern Bool PPosOverride;

    GetGravityOffsets(tmp_win, &gravx, &gravy);


    /* Select a desk to put the window on (in list of priority):
     * 1. Sticky Windows stay on the current desk.
     * 2. Windows specified with StartsOnDesk go where specified
     * 3. Put it on the desk it was on before the restart.
     * 4. Transients go on the same desk as their parents.
     * 5. Window groups stay together (completely untested)
     */
    tmp_win->Desk = Scr.CurrentDesk;
    if (tflag & STICKY_FLAG)
	tmp_win->Desk = Scr.CurrentDesk;
    else if (tflag & STAYSONDESK_FLAG)
	tmp_win->Desk = Desk;
    else {
	Atom atype;
	int aformat;
	unsigned long nitems, bytes_remain;
	unsigned char *prop;

	if ((tmp_win->wmhints) && (tmp_win->wmhints->flags & WindowGroupHint) &&
	    (tmp_win->wmhints->window_group != None) &&
	    (tmp_win->wmhints->window_group != Scr.Root)) {
	    /* Try to find the group leader or another window
	     * in the group */
	    for (t = Scr.ASRoot.next; t != NULL; t = t->next) {
		if ((t->w == tmp_win->wmhints->window_group) ||
		((t->wmhints) && (t->wmhints->flags & WindowGroupHint) &&
		 (t->wmhints->window_group == tmp_win->wmhints->window_group)))
		    tmp_win->Desk = t->Desk;
	    }
	}
	if ((tmp_win->flags & TRANSIENT) && (tmp_win->transientfor != None) &&
	    (tmp_win->transientfor != Scr.Root)) {
	    /* Try to find the parent's desktop */
	    for (t = Scr.ASRoot.next; t != NULL; t = t->next) {
		if (t->w == tmp_win->transientfor)
		    tmp_win->Desk = t->Desk;
	    }
	}
	if ((XGetWindowProperty(dpy, tmp_win->w, _XA_WM_DESKTOP, 0L, 1L, True,
				_XA_WM_DESKTOP, &atype, &aformat, &nitems,
				&bytes_remain, &prop)) == Success) {
	    if (prop != NULL) {
		tmp_win->Desk = *(unsigned long *) prop;
		XFree(prop);
	    }
	}
    }
    /* I think it would be good to switch to the selected desk
     * whenever a new window pops up, except during initialization */
    if (!PPosOverride)
	changeDesks(0, tmp_win->Desk);


    /* Desk has been selected, now pick a location for the window */
    /*
     *  If
     *     o  the window is a transient, or
     * 
     *     o  a USPosition was requested
     * 
     *   then put the window where requested.
     *
     *   If RandomPlacement was specified,
     *       then place the window in a psuedo-random location
     */
    if (!(tmp_win->flags & TRANSIENT) &&
	!(tmp_win->hints.flags & USPosition) &&
    ((Scr.flags & NoPPosition) || !(tmp_win->hints.flags & PPosition)) &&
	!(PPosOverride) &&
	!((tmp_win->wmhints) &&
	  (tmp_win->wmhints->flags & StateHint) &&
	  (tmp_win->wmhints->initial_state == IconicState))) {
	/* Get user's window placement, unless RandomPlacement is specified */
	if (Scr.flags & RandomPlacement) {
	    if (Scr.flags & SMART_PLACEMENT)
		SmartPlacement(tmp_win, tmp_win->frame_width + 2 * tmp_win->bw,
			       tmp_win->frame_height + 2 * tmp_win->bw,
			       &xl, &yt);
	    if (xl < 0) {
		/* plase window in a random location */
		if ((Scr.randomx += Scr.TitleHeight) > Scr.MyDisplayWidth / 2)
		    Scr.randomx = Scr.TitleHeight;
		if ((Scr.randomy += 2 * Scr.TitleHeight) > Scr.MyDisplayHeight / 2)
		    Scr.randomy = 2 * Scr.TitleHeight;
		tmp_win->attr.x = Scr.randomx - tmp_win->old_bw;
		tmp_win->attr.y = Scr.randomy - tmp_win->old_bw;
	    } else {
		tmp_win->attr.x = xl - tmp_win->old_bw;
		tmp_win->attr.y = yt - tmp_win->old_bw;
	    }
	    /* patches 11/93 to try to keep the window on the
	     * screen */
	    tmp_win->frame_x = tmp_win->attr.x + tmp_win->old_bw - tmp_win->bw;
	    tmp_win->frame_y = tmp_win->attr.y + tmp_win->old_bw - tmp_win->bw;

	    if (tmp_win->frame_x + tmp_win->frame_width +
		2 * tmp_win->boundary_width > Scr.MyDisplayWidth) {
		tmp_win->attr.x = Scr.MyDisplayWidth - tmp_win->attr.width
		    - tmp_win->old_bw + tmp_win->bw - 2 * tmp_win->boundary_width;
		Scr.randomx = 0;
	    }
	    if (tmp_win->frame_y + 2 * tmp_win->boundary_width + tmp_win->title_height
		+ tmp_win->frame_height > Scr.MyDisplayHeight) {
		tmp_win->attr.y = Scr.MyDisplayHeight - tmp_win->attr.height
		    - tmp_win->old_bw + tmp_win->bw - tmp_win->title_height -
		    2 * tmp_win->boundary_width;;
		Scr.randomy = 0;
	    }
	    tmp_win->xdiff = tmp_win->attr.x - tmp_win->bw;
	    tmp_win->ydiff = tmp_win->attr.y - tmp_win->bw;
	} else {
	    xl = -1;
	    yt = -1;
	    if (Scr.flags & SMART_PLACEMENT)
		SmartPlacement(tmp_win, tmp_win->frame_width + 2 * tmp_win->bw,
			       tmp_win->frame_height + 2 * tmp_win->bw,
			       &xl, &yt);
	    if (xl < 0) {
		if (GrabEm(POSITION)) {
		    /* Grabbed the pointer - continue */
		    XGrabServer(dpy);
		    if (XGetGeometry(dpy, tmp_win->w, &JunkRoot, &JunkX, &JunkY,
				     (unsigned int *) &DragWidth,
				     (unsigned int *) &DragHeight,
				     &JunkBW, &JunkDepth) == 0) {
			free((char *) tmp_win);
			XUngrabServer(dpy);
			return False;
		    }
		    DragWidth += 2 * tmp_win->boundary_width;
		    /* modified 02/17/97 */
		    DragHeight +=
			tmp_win->title_height + NS_HANDLE_HEIGHT;

		    XMapRaised(dpy, Scr.SizeWindow);
		    moveLoop(tmp_win, 0, 0, DragWidth, DragHeight,
			     &xl, &yt, False, True);
		    XUnmapWindow(dpy, Scr.SizeWindow);
		    XUngrabServer(dpy);
		    UngrabEm();
		} else {
		    /* couldn't grab the pointer - better do something */
		    XBell(dpy, Scr.screen);
		    xl = 0;
		    yt = 0;
		}
	    }
	    tmp_win->attr.y = yt - tmp_win->old_bw + tmp_win->bw;
	    tmp_win->attr.x = xl - tmp_win->old_bw + tmp_win->bw;
	    tmp_win->xdiff = xl;
	    tmp_win->ydiff = yt;
	}
    } else {
	/* the USPosition was specified, or the window is a transient, 
	 * or it starts iconic so place it automatically */

	tmp_win->xdiff = tmp_win->attr.x;
	tmp_win->ydiff = tmp_win->attr.y;
	/* put it where asked, mod title bar */
	/* if the gravity is towards the top, move it by the title height */
	tmp_win->attr.y -= gravy * (tmp_win->bw - tmp_win->old_bw);
	tmp_win->attr.x -= gravx * (tmp_win->bw - tmp_win->old_bw);
	if (gravy > 0)
	    tmp_win->attr.y -= 2 * tmp_win->boundary_width + tmp_win->title_height;
	if (gravx > 0)
	    tmp_win->attr.x -= 2 * tmp_win->boundary_width;
    }
    return True;
}



/************************************************************************
 *
 *  Procedure:
 *	GetGravityOffsets - map gravity to (x,y) offset signs for adding
 *		to x and y when window is mapped to get proper placement.
 * 
 ************************************************************************/
struct _gravity_offset {
    int x, y;
};

void GetGravityOffsets(ASWindow * tmp, int *xp, int *yp)
{
    static struct _gravity_offset gravity_offsets[11] =
    {
	{0, 0},			/* ForgetGravity */
	{-1, -1},		/* NorthWestGravity */
	{0, -1},		/* NorthGravity */
	{1, -1},		/* NorthEastGravity */
	{-1, 0},		/* WestGravity */
	{0, 0},			/* CenterGravity */
	{1, 0},			/* EastGravity */
	{-1, 1},		/* SouthWestGravity */
	{0, 1},			/* SouthGravity */
	{1, 1},			/* SouthEastGravity */
	{0, 0},			/* StaticGravity */
    };
    register int g = ((tmp->hints.flags & PWinGravity)
		      ? tmp->hints.win_gravity : NorthWestGravity);

    if (g < ForgetGravity || g > StaticGravity)
	*xp = *yp = 0;
    else {
	*xp = (int) gravity_offsets[g].x;
	*yp = (int) gravity_offsets[g].y;
    }
    return;
}
