/* -*- indent-tabs-mode: nil; tab-width: 4; c-basic-offset: 4; -*-

   AZClient+Place.m for the Azalea window manager
   Copyright (c) 2006        Yen-Ju Chen

   place.c for the Openbox window manager
   Copyright (c) 2003        Ben Jansens

   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.

   See the COPYING file for a copy of the GNU General Public License.
*/

#import "AZScreen.h"
#import "AZDock.h"
#import "AZGroup.h"
#import "AZClient+Place.h"
#import "AZFocusManager.h"
#import "config.h"

static Rect* pick_head(AZClient *c)
{
  AZScreen *screen = [AZScreen defaultScreen];
    /* try direct parent first */
    if ([c transient_for] && [c transient_for] != OB_TRAN_GROUP) {
	return [screen areaOfDesktop: [c desktop]
		       monitor: [[c transient_for] monitor]];
    }

    /* more than one guy in his group (more than just him) */
    if ([c hasGroupSiblings]) {
        /* try on the client's desktop */
        int i, count = [[[c group] members] count];
	for (i = 0; i < count; i++) {
	  AZClient *itc = [[c group] memberAtIndex: i];
            if (itc != c &&
                ([itc desktop] == [c desktop] ||
                 [itc desktop] == DESKTOP_ALL || [c desktop] == DESKTOP_ALL))
	    {
		return [screen areaOfDesktop: [c desktop]
			       monitor: [itc monitor]];
	    }
        }

        /* try on all desktops */
        count = [[[c group] members] count];
	for (i = 0; i < count; i++) {
	  AZClient *itc = [[c group] memberAtIndex: i];
            if (itc != c)
	    {
		return [screen areaOfDesktop: [c desktop]
			       monitor: [itc monitor]];
	    }
        }
    }

    return NULL;
}

static BOOL place_random(AZClient *client, int *x, int *y)
{
    int l, r, t, b;
    Rect *area;

    area = pick_head(client);
    if (!area)
    {
	AZScreen *screen = [AZScreen defaultScreen];
	area = [screen areaOfDesktop: [client desktop]
		       monitor: (random() % [screen numberOfMonitors])];
    }

    l = area->x;
    t = area->y;
    r = area->x + area->width - [[client frame] area].width;
    b = area->y + area->height - [[client frame] area].height;

    if (r > l) *x = (random() % (r-l+1)) + l;
    else       *x = 0;
    if (b > t) *y = (random() % (b-t+1)) + t;
    else       *y = 0;

    return YES;
}

/* FIXME: it is only used by smart placement 
 * and is designed to work-around Rect struct */
@interface AZFakeRect: NSObject
{
  Rect *r;
  AZClient *client;
}
+ (AZFakeRect *) fakeRectWithR: (Rect *) r; /* client is nil */
- (Rect *) r;
- (AZClient *) client;
- (void) set_r: (Rect *) r;
- (void) set_client: (AZClient *) c;

- (NSComparisonResult) compareFakeRect: (AZFakeRect *) other;
@end

@implementation AZFakeRect
- (NSComparisonResult) compareFakeRect: (AZFakeRect *) other
{
    AZClient *c = client;
    Rect temp = [[c frame] area];
    Rect *carea = &temp;
    const Rect *a1 = r, *a2 = [other r];
    BOOL diffhead = NO;
    unsigned int i;
    Rect *a;
    AZScreen *screen = [AZScreen defaultScreen];

    for (i = 0; i < [screen numberOfMonitors]; ++i) {
	a = [screen physicalAreaOfMonitor: i];
        if (RECT_CONTAINS(*a, a1->x, a1->y) &&
            !RECT_CONTAINS(*a, a2->x, a2->y))
        {
            diffhead = YES;
            break;
        }
    }

    /* has to be more than me in the group */
    if (diffhead && [c hasGroupSiblings]) {
        unsigned int *num, most;

        /* find how many clients in the group are on each monitor, use the
           monitor with the most in it */
        num = calloc(sizeof(unsigned int), [screen numberOfMonitors]);
        int i, count = [[[c group] members] count];
	for (i = 0; i < count; i++) {
	  AZClient *data = [[c group] memberAtIndex: i];
          if (data != c)
                ++num[[data monitor]];
	}
        most = 0;
        for (i = 1; i < [screen numberOfMonitors]; ++i)
            if (num[i] > num[most])
                most = i;

	a = [screen physicalAreaOfMonitor: most];
        if (RECT_CONTAINS(*a, a1->x, a1->y))
            return -1;
        if (RECT_CONTAINS(*a, a2->x, a2->y))
            return 1;
    }

    int diff = MIN((a1->width - carea->width), (a1->height - carea->height)) -
        MIN((a2->width - carea->width), (a2->height - carea->height));

    if (diff < 0) return NSOrderedAscending;
    else if (diff > 0) return NSOrderedDescending;
    else return NSOrderedSame;
}

+ (AZFakeRect *) fakeRectWithR: (Rect *) r
{
  AZFakeRect *fr = [[AZFakeRect alloc] init];
  [fr set_r: r];
  return AUTORELEASE(fr);
}
- (Rect *) r { return r; }
- (AZClient *) client { return client; }
- (void) set_r: (Rect *) _r { r = _r; }
- (void) set_client: (AZClient *) _c { client = _c; }
@end

NSArray *area_add(NSArray *list, Rect *a)
{
    NSMutableArray *array;
    if (list)
      array = [NSMutableArray arrayWithArray: list];
    else
      array = AUTORELEASE([[NSMutableArray alloc] init]);

    Rect *r = calloc(sizeof(Rect), 1);
    *r = *a;
    AZFakeRect *fr = AUTORELEASE([[AZFakeRect alloc] init]);
    [fr set_r: r];
    [array addObject: fr];
    return array;
}

NSArray* area_remove(NSArray *list, Rect *a)
{
    NSMutableArray *result = AUTORELEASE([[NSMutableArray alloc] init]);
    int i, count = [list count];
//    AZFakeRect* fr = nil;

    for (i = 0; i < count; i++) {
        Rect *r = [[list objectAtIndex: i] r];

        if (!RECT_INTERSECTS_RECT(*r, *a)) {
	    [result addObject: [AZFakeRect fakeRectWithR: r]];
            r = NULL; /* dont free it */
        } else {
            Rect isect, extra;

            /* Use an intersection of a and r to determine the space
               around r that we can use.

               NOTE: the spaces calculated can overlap.
            */

            RECT_SET_INTERSECTION(isect, *r, *a);

            if (RECT_LEFT(isect) > RECT_LEFT(*r)) {
                RECT_SET(extra, r->x, r->y,
                         RECT_LEFT(isect) - r->x, r->height);
                result = [NSMutableArray arrayWithArray: area_add(result, &extra)];
            }

            if (RECT_TOP(isect) > RECT_TOP(*r)) {
                RECT_SET(extra, r->x, r->y,
                         r->width, RECT_TOP(isect) - r->y + 1);
                result = [NSMutableArray arrayWithArray: area_add(result, &extra)];
            }

            if (RECT_RIGHT(isect) < RECT_RIGHT(*r)) {
                RECT_SET(extra, RECT_RIGHT(isect) + 1, r->y,
                         RECT_RIGHT(*r) - RECT_RIGHT(isect), r->height);
                result = [NSMutableArray arrayWithArray: area_add(result, &extra)];
            }

            if (RECT_BOTTOM(isect) < RECT_BOTTOM(*r)) {
                RECT_SET(extra, r->x, RECT_BOTTOM(isect) + 1,
                         r->width, RECT_BOTTOM(*r) - RECT_BOTTOM(isect));
                result = [NSMutableArray arrayWithArray: area_add(result, &extra)];
            }
        }

        free(r);
    }
    return result;
}

typedef enum
{
    SMART_FULL,
    SMART_GROUP,
    SMART_FOCUSED
} ObSmartType;

#define SMART_IGNORE(placer, c) \
    (placer == c || ![[c frame] visible] || [c shaded] || ![c normal] || \
     ([c desktop] != DESKTOP_ALL && \
      [c desktop] != ([placer desktop] == DESKTOP_ALL ? \
                     ([[AZScreen defaultScreen] desktop]) : [placer desktop])))

static BOOL place_smart(AZClient *client, int *x, int *y,
                            ObSmartType type)
{
    unsigned int i;
    BOOL ret = NO;
    NSArray *spaces = nil;
    AZScreen *screen = [AZScreen defaultScreen];
    AZStacking *stacking = [AZStacking stacking];
    int j, index = NSNotFound, count = [stacking count];
    int k, kcount;
    id <AZWindow> temp;

    for (i = 0; i < [screen numberOfMonitors]; ++i)
        spaces = area_add(spaces, [screen areaOfDesktop: [client desktop]
			            monitor: i]);

    /* stay out from under windows in higher layers */
    for (j = 0; j < count; j++) {
	temp = [stacking windowAtIndex: j];
        AZClient *c = nil;

        if (WINDOW_IS_CLIENT(temp)) {
            c = (AZClient *)temp;
            if ([c fullscreen])
                continue;
        } else
            continue;

        if ([c layer] > [client layer]) {
            if (!SMART_IGNORE(client, c)) {
		Rect temp = [[c frame] area];
                spaces = area_remove(spaces, &temp);
		[[c frame] setArea: temp];
	    }
        } else {
	    index = j;
            break;
	}
    }

    if ([client type] == OB_CLIENT_TYPE_NORMAL) {
        if (type == SMART_FULL || type == SMART_FOCUSED) {
            BOOL found_foc = NO, stop = NO;
            AZClient *foc;
	    unsigned int d = ([client desktop] == DESKTOP_ALL ? [screen desktop] : [client desktop]);

	    // foc can be nil.
	    foc = [[AZFocusManager defaultManager] focusOrder: 0 inScreen: d];

	    for (j = index; j < count && !stop; j++) {
		temp = [stacking windowAtIndex: j];
                AZClient *c;

                if (WINDOW_IS_CLIENT(temp)) {
                    c = (AZClient *)temp;
                    if ([c fullscreen])
                        continue;
                } else
                    continue;

                if (!SMART_IGNORE(client, c)) {
                    if (type == SMART_FOCUSED)
                        if (found_foc)
                            stop = YES;
                    if (!stop) {
			Rect temp = [[c frame] area];
                        spaces = area_remove(spaces, &temp);
			[[c frame] setArea: temp];
		    }
                }

                if (c == foc)
                    found_foc = YES;
            }
        } else if (type == SMART_GROUP) {
            /* has to be more than me in the group */
            if (![client hasGroupSiblings])
                return NO;

            int i, count = [[[client group] members] count];
	    for (i = 0; i < count; i++) {
	      AZClient *c = [[client group] memberAtIndex: i];
              if (!SMART_IGNORE(client, c)) {
		    Rect temp = [[c frame] area];
                    spaces = area_remove(spaces, &temp);
		    [[c frame] setArea: temp];
	      }
            }
        } else {
	    NSLog(@"Internal Error: should not reach here");
	}
    }

    kcount = [spaces count];
    for (k = 0; k < kcount; k++) {
      [[spaces objectAtIndex: k] set_client: client];
    }
    spaces = [spaces sortedArrayUsingSelector: @selector(compareFakeRect:)];
    kcount = [spaces count];
    for (k = 0; k < kcount; k++) {
        Rect *r = [[spaces objectAtIndex: k] r];

        if (!ret) {
            if (r->width >= [[client frame] area].width &&
                r->height >= [[client frame] area].height) {
                ret = YES;
                if ([client type] == OB_CLIENT_TYPE_DIALOG ||
                    type != SMART_FULL)
                {
                    *x = r->x + (r->width - [[client frame] area].width) / 2;
                    *y = r->y + (r->height - [[client frame] area].height) / 2;
                } else {
                    *x = r->x;
                    *y = r->y;
                }
            }
        }

        free(r);
    }

    return ret;
}

static BOOL place_under_mouse(AZClient *client, int *x, int *y)
{
    unsigned int i;
    int l, r, t, b;
    int px, py;
    Rect *area;
    AZScreen *screen = [AZScreen defaultScreen];

    [screen pointerPosAtX: &px y: &py];

    for (i = 0; i < [screen numberOfMonitors]; ++i) {
	area = [screen areaOfDesktop: [client desktop]
		       monitor: i];
        if (RECT_CONTAINS(*area, px, py))
            break;
    }
    if (i == [screen numberOfMonitors])
	area = [screen areaOfDesktop: [client desktop]
		       monitor: 0];

    l = area->x;
    t = area->y;
    r = area->x + area->width - [[client frame] area].width;
    b = area->y + area->height - [[client frame] area].height;

    *x = px - [client area].width / 2 - [[client frame] size].left;
    *x = MIN(MAX(*x, l), r);
    *y = py - [client area].height / 2 - [[client frame] size].top;
    *y = MIN(MAX(*y, t), b);

    return YES;
}

static BOOL place_transient(AZClient *client, int *x, int *y)
{
    if ([client transient_for]) {
        if ([client transient_for] != OB_TRAN_GROUP) {
            AZClient *c = client;
            AZClient *p = [client transient_for];
            *x = ([[p frame] area].width - [[c frame] area].width) / 2 +
                [[p frame] area].x;
            *y = ([[p frame] area].height - [[c frame] area].height) / 2 +
                [[p frame] area].y;
            return YES;
        } else {
            BOOL first = YES;
            int l, r, t, b;
            int i, count = [[[client group] members] count];
	    for (i = 0; i < count; i++) {
	      AZClient *m = [[client group] memberAtIndex: i];
                if (!(m == client || [m transient_for])) {
                    if (first) {
                        l = RECT_LEFT([[m frame] area]);
                        t = RECT_TOP([[m frame] area]);
                        r = RECT_RIGHT([[m frame] area]);
                        b = RECT_BOTTOM([[m frame] area]);
                        first = NO;
                    } else {
                        l = MIN(l, RECT_LEFT([[m frame] area]));
                        t = MIN(t, RECT_TOP([[m frame] area]));
                        r = MAX(r, RECT_RIGHT([[m frame] area]));
                        b = MAX(b, RECT_BOTTOM([[m frame] area]));
                    }
                }
            }
            if (!first) {
                *x = ((r + 1 - l) - [[client frame] area].width) / 2 + l; 
                *y = ((b + 1 - t) - [[client frame] area].height) / 2 + t;
                return YES;
            }
        }
    }
    return NO;
}

@implementation AZClient (AZPlace)

/* Return TRUE if we want client.c to enforce on-screen-keeping */
- (BOOL) placeAtX: (int *) x y: (int *) y
{
    BOOL ret = NO;
    if ([self positioned])
        return NO;
    if (place_transient(self , x, y))
        ret = YES;
    else if (!(
        ((config_place_policy == OB_PLACE_POLICY_MOUSE) ?
         place_under_mouse(self , x, y) :
         place_smart(self , x, y, SMART_FULL)    ||
         place_smart(self , x, y, SMART_GROUP)   ||
         place_smart(self , x, y, SMART_FOCUSED) ||
         place_random(self, x, y))))
    {
        NSAssert(0, @"Should not reach here"); /* the last one better succeed */
    }
    /* get where the client should be */
    [[self frame] frameGravityAtX: x y: y];
    return ret;
}

@end
