/*
 * Copyright (C) 2000-2007 Carsten Haitzler, Geoff Harrison and various contributors
 * Copyright (C) 2003-2023 Kim Woelders
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to
 * deal in the Software without restriction, including without limitation the
 * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
 * sell copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies of the Software, its documentation and marketing & publicity
 * materials, and acknowledgment shall be given in the documentation, materials
 * and software packages that this Software was used.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
 * THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
 * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
 * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 */
#include "E.h"
#include "ewins.h"
#include "hints.h"
#include "screen.h"
#include "slide.h"

#define DEBUG_SIZE 0
#if DEBUG_SIZE
#define Dprintf Eprintf
#else
#define Dprintf(fmt...)
#endif

#define ENABLE_SMART_MAXIMISE   1

#define MAX_ABSOLUTE     0      /* Fill screen */
#define MAX_AVAILABLE    1      /* Expand until don't cover */
#define MAX_CONSERVATIVE 2      /* Expand until something */
#define MAX_XINERAMA     3      /* Fill Xinerama screen */
#define MAX_HALF_N       4      /* Expand to North half */
#define MAX_HALF_S       5      /* Expand to South half */
#define MAX_HALF_E       6      /* Expand to East half */
#define MAX_HALF_W       7      /* Expand to West half */

static int
_ignore(const EWin *ewin, int type)
{
    if (ewin->state.iconified || EoIsFloating(ewin) ||
        ewin->props.ignorearrange ||
        ((type == MAX_AVAILABLE ||
          type == MAX_HALF_N || type == MAX_HALF_S ||
          type == MAX_HALF_E || type == MAX_HALF_W) &&
         !ewin->props.never_use_area))
        return 1;
    return 0;
}

static void
_get_span_x(const EWin *ewin, int type, EWin *const *lst, int num,
            int *px1, int *px2)
{
    int             i, sx1 = *px1, sx2 = *px2;
    int             wx1, wx2, tx1, tx2;
    EWin           *e;

    wx1 = EoGetX(ewin);
    wx2 = wx1 + EoGetW(ewin);

    for (i = 0; i < num; i++)
    {
        e = lst[i];
        if (e == ewin || _ignore(e, type) ||
            !SPANS_COMMON(EoGetY(ewin), EoGetH(ewin), EoGetY(e), EoGetH(e)))
            continue;

        tx1 = EoGetX(e);
        tx2 = tx1 + EoGetW(e);
        if (tx2 <= wx1 && tx2 > sx1)
            sx1 = tx2;
        else if (tx1 >= wx2 && tx1 < sx2)
            sx2 = tx1;
    }

    *px1 = sx1;
    *px2 = sx2;
}

static void
_get_span_y(const EWin *ewin, int type, EWin *const *lst, int num,
            int *py1, int *py2)
{
    int             i, sy1 = *py1, sy2 = *py2;
    int             wy1, wy2, ty1, ty2;
    const EWin     *e;

    wy1 = EoGetY(ewin);
    wy2 = wy1 + EoGetH(ewin);

    for (i = 0; i < num; i++)
    {
        e = lst[i];
        if (e == ewin || _ignore(e, type) ||
            !SPANS_COMMON(EoGetX(ewin), EoGetW(ewin), EoGetX(e), EoGetW(e)))
            continue;

        ty1 = EoGetY(e);
        ty2 = ty1 + EoGetH(e);
        if (ty2 <= wy1 && ty2 > sy1)
            sy1 = ty2;
        else if (ty1 >= wy2 && ty1 < sy2)
            sy2 = ty1;
    }

    *py1 = sy1;
    *py2 = sy2;
}

#if ENABLE_SMART_MAXIMISE
/*
 * Smart window sizing algorithm.
 * Based on pareto frontiers and constraint unification.
 * Blame it on Dan Manjarres.
 *
 * The algorithm is to treat the center of the window being maximized as the
 * center of a cartesian coordinate system, and to find a pareto frontier
 * in each quadrant of the cartesian plane. The frontier is found from points
 * that are the corners of other windows or the nearest point on the edge of a
 * window that spans 2 quadrants. Windows that span more than 2 quadrants are
 * ignored since they already overlap with the original window.
 *
 * The frontiers encode a discrete set of possible window edges, which are sorted
 * and tested in sequence until the one with the largest area is found.
 *
 * Filtering is done to ignore windows that are visually "under"
 * a window that is overlapped so that hidden windows are not counted as
 * constraining maximization. Windows that have the never use flag are not
 * ignored in this manner. Windows with aspect ratio hints are respected, and
 * placed as close as possible to their original position. All windows may be
 * slightly shifted to get more area, as the only point that represents the
 * window is its center, around which the constraints are placed. The new
 * center may be located anywhere in region of the screen defined by partial
 * orders of windows along x and y, and conflicts between a windows existing edges
 * and resizing the window are completely bypassed. In other words you don't have
 * to grab the mouse to move the window before maximizing it, and you don't need the
 * mouse to maximizing it either. Alt-Tab and <maximize key command> are all you need.
 *
 * computation proceeeds as
 *
 * 1) list all windows
 * 2) filter out windows that overlap maximizing window, and all windows under them.
 * 3) record the 4 corners of each window in a constraint array
 * 4) compute the nearest point of any window that crosses the x or y axis and store
 *    it as a  constraint.
 * 5) make 4 copies of the array and adjust for different search directions
 * 6) for each copy filter in preparation for pareto criteria for each corner of the window
 * 6a) for upper corners ignore windows that are below window center
 * 6b) for lower corners ignore windows that are above below window center
 * 6c) for left  corners ignore windows that are right of window center
 * 6d) for right corners ignore windows that are left of window center
 * 7) perform pareto frontier filtering for each quadrant
 *     after this step the remaining points  are the pareto frontier for each corner
 *     of the window in isolation, and form a not-too-nonconvex hull topologically equal
 *     to a circle around the original window's center.
 *
 *     Each discrete x value and y value represents a potential location
 *     for sides of the window, and each window side will touch
 *     at least one of its potential corner points.
 *
 * 8) combine the adjacent corners for each side into arrays holding potential
 *    window edge locations and maximum spans sorted by distance from center.
 * 9) for each pair of top and bottom constraints find the available area from
 *    the left and right constraints.
 * 10) adjust for aspect ratio.
 * 11) pick the biggest one
 * 12) and on the seventh day rest
 * */

typedef struct {
    int             dx;
    int             dy;
    int             dominated;
} point;

static int
_gti(const void *p1, const void *p2)
{
    return (*(int *)p1) - *((int *)p2);
}

static int
_lti(const void *p1, const void *p2)
{
    return (*(int *)p2) - *((int *)p1);
}

static int
_gtx(const void *p1, const void *p2)
{
    return ((point *) p1)->dx - ((point *) p2)->dx;
}

static int
_ltx(const void *p1, const void *p2)
{
    return ((point *) p2)->dx - ((point *) p1)->dx;
}

#if 0
static int
_gty(const void *p1, const void *p2)
{
    return ((point *) p1)->dy - ((point *) p2)->dy;
}

static int
_lty(const void *p1, const void *p2)
{
    return ((point *) p2)->dy - ((point *) p1)->dy;
}
#endif

static void
sort_ints(int *is, int n, int ascending)
{
    if (ascending >= 0)
        qsort(is, n, sizeof(int), _gti);
    else
        qsort(is, n, sizeof(int), _lti);
}

static void
sort_points_x(point *ps, int n, int ascending)
{
    if (ascending >= 0)
        qsort(ps, n, sizeof(point), _gtx);
    else
        qsort(ps, n, sizeof(point), _ltx);
}

#if 0
static void
sort_points_y(point *ps, int n, int ascending)
{
    if (ascending >= 0)
        qsort(ps, n, sizeof(point), _gty);
    else
        qsort(ps, n, sizeof(point), _lty);
}
#endif

static void
uniq_ints(int *is, int *n)
{
    int             i, j;

    for (i = 0, j = 0; i < *n - 1; i++)
    {
        if (is[i] != is[i + 1])
        {
            is[j] = is[i];
            j++;
        }
    }
    is[j] = is[i];
    j++;
    *n = j;
}

static void
filter_points(point *p, int *np, int max_dx, int max_dy)
{
    int             i, j, n = *np;

    /* this is step 6 from above */
    for (i = 0; i < n; i++)
    {
        if (p[i].dx < 0)
            p[i].dominated = 1;
        if (p[i].dy < 0)
            p[i].dominated = 1;
        /* clip to screen */
        if (p[i].dx > max_dx)
            p[i].dx = max_dx;
        if (p[i].dy > max_dy)
            p[i].dy = max_dy;
    }

    /* this is step 7 from above */
    for (i = 0; i < n; i++)
    {
        if (p[i].dominated)
            continue;

        for (j = 0; j < n; j++)
        {
            if (i == j)
                continue;       /* self dominance = no */
            if ((p[i].dx <= p[j].dx) && (p[i].dy <= p[j].dy))
            {
                p[j].dominated = 1;
            }
        }
    }

    /* actual pareto frontier filtering */
    for (i = 0, j = 0; i < n; i++)
    {
        if (!p[i].dominated)
        {
            p[j] = p[i];
            j++;
        }
    }

    *np = j;
}

#define _get_span_xy pareto_maximizer
static void
pareto_maximizer(EWin *ewin, int type, EWin *const *lst, int num,
                 int *avail_x1, int *avail_x2, int *avail_y1, int *avail_y2)
{
    int             x, y, w, h;
    int             cx, cy;     /* center */
    int             i, j, k;
    point          *constraints_tr = NULL;      /* top right   */
    point          *constraints_tl = NULL;      /* top left    */
    point          *constraints_br = NULL;      /* botom right */
    point          *constraints_bl = NULL;      /* botom left  */
    int             num_tr, num_tl, num_br, num_bl;
    int            *top_ds = NULL;
    int            *bottom_ds = NULL;
    point          *left_ds = NULL;
    point          *right_ds = NULL;
    int            *tc, *bc;
    point          *lc, *rc;    /* temp cursors */
    int             num_t, num_b, num_l, num_r;
    float           aspect;
    EWin          **filtered_lst, **stacked_lst, *pe, *pe2;
    char           *done_stacking_flag;
    char           *stacked_above_flag;
    int             num_stacked, stacked_above;
    int             area, new_area;
    int             recenter, new_recenter;
    int             td, bd, ld, rd;     /* displacement to maximized edges */

    Dprintf("searching within %d-%d %d-%d\n",
            *avail_x1, *avail_x2, *avail_y1, *avail_y2);
    x = EoGetX(ewin);
    y = EoGetY(ewin);
    h = EoGetH(ewin);
    w = EoGetW(ewin);
    cx = x + w / 2;
    cy = y + h / 2;

    /* center must be within available region */
    cx = (cx >= *avail_x2) ? *avail_x2 - 1 : cx;
    cy = (cy >= *avail_y2) ? *avail_y2 - 1 : cy;
    cx = (cx < *avail_x1) ? *avail_x1 : cx;
    cy = (cy < *avail_y1) ? *avail_y1 : cy;

    filtered_lst = EMALLOC(EWin *, num);
    stacked_lst = EMALLOC(EWin *, num + 1);
    stacked_above_flag = ECALLOC(char, num);
    done_stacking_flag = ECALLOC(char, num);

    if (!filtered_lst || !stacked_lst || !done_stacking_flag ||
        !stacked_above_flag)
        goto freedom;

    stacked_lst[0] = ewin;
    num_stacked = 1;
    /* ignore windows already overlapping ours and any windows UNDER them */

    /* start by detecting windows we overlap */
    for (i = 0, stacked_above = 0; i < num; i++)
    {
        pe = lst[i];

        if (pe == ewin)
        {
            stacked_above = 1;
        }
        if ((pe == ewin) || _ignore(pe, type))
        {
            done_stacking_flag[i] = 1;
            Dprintf("ignoring #%d %s\n", i, EwinGetTitle(pe));
            continue;
        }

        if (SPANS_COMMON(x + 1, w - 2, EoGetX(pe), EoGetW(pe)) &&
            SPANS_COMMON(y + 1, h - 2, EoGetY(pe), EoGetH(pe)))
        {
            stacked_lst[num_stacked] = pe;
            stacked_above_flag[num_stacked] = stacked_above;
            num_stacked++;
            done_stacking_flag[i] = 1;
            Dprintf("overlap #%d %s\n", i, EwinGetTitle(pe));
        }
        else
        {
            Dprintf("do not overlap #%d %s\n", i, EwinGetTitle(pe));
        }
    }

    /* extend the stacked list to windows that are UNDER other items in the stacked list */
    for (i = 1; i < num_stacked; i++)
    {
        int             sx, sy, sw, sh;

        pe2 = stacked_lst[i];
        sx = EoGetX(pe2);
        sy = EoGetY(pe2);
        sh = EoGetH(pe2);
        sw = EoGetW(pe2);

        /* find stacked_lst window pe2 in the general window list */
        for (j = 0; j < num; j++)
        {
            pe = lst[j];
            if (pe == pe2)
                break;
        }
        if (stacked_above_flag[i])
        {
            Dprintf("metaoverlap testing from understacked %s\n",
                    EwinGetTitle(pe));
        }
        else
        {
            Dprintf("NOT metaoverlap testing from overstacked %s\n",
                    EwinGetTitle(pe));
            continue;
        }

        for (; j < num; j++)
        {
            pe = lst[j];

            if (done_stacking_flag[j])
                continue;
            if (pe->props.never_use_area)
            {
                Dprintf("not using area %s\n", EwinGetTitle(pe));
                continue;
            }

            if (SPANS_COMMON(sx + 1, sw - 2, EoGetX(pe), EoGetW(pe)) &&
                SPANS_COMMON(sy + 1, sh - 2, EoGetY(pe), EoGetH(pe)))
            {
                /* the list is already top down so if it overlaps it's also under */
                /* we hope! */
                stacked_lst[num_stacked] = pe;
                stacked_above_flag[num_stacked] = 1;
                num_stacked++;
                done_stacking_flag[j] = 1;
                Dprintf("metaoverlap #%d %s\n", j, EwinGetTitle(pe));
            }
            else
            {
                Dprintf("no metaoverlap #%d %s\n", j, EwinGetTitle(pe));
            }
        }
    }

    /* copy remaining windows to our working set */
    for (i = 0, j = 0; i < num; i++)
    {
        pe = lst[i];

        if (done_stacking_flag[i])
        {
            Dprintf("no stacked constraint from #%d %s\n", i, EwinGetTitle(pe));
            continue;
        }

        if ((pe == ewin) || _ignore(pe, type) ||
            /* ignore windws that do not overlap with current search area */
            !(SPANS_COMMON(*avail_x1, *avail_x2 - *avail_x1,
                           EoGetX(pe), EoGetW(pe)) &&
              SPANS_COMMON(*avail_y1, *avail_y2 - *avail_y1,
                           EoGetY(pe), EoGetH(pe))))
        {
            Dprintf("no constraint from %s\n", EwinGetTitle(pe));
            continue;
        }
        Dprintf("constraint from %s\n", EwinGetTitle(pe));
        filtered_lst[j] = pe;
        j++;
    }

    /* allocate memory to hold constraints */
    num = j + 1;
    num_tl = num_bl = num_br = num_tr = num * 5;
    constraints_tl = EMALLOC(point, num * 5);
    constraints_tr = EMALLOC(point, num * 5);
    constraints_bl = EMALLOC(point, num * 5);
    constraints_br = ECALLOC(point, num * 5);

    if (!constraints_tl || !constraints_tr ||
        !constraints_bl || !constraints_br)
        goto freedom;

    for (i = 1; i < num; i++)
    {
        pe = filtered_lst[i - 1];
        /* store window corners as candidate constraints */
        constraints_br[5 * i + 0].dx = EoGetX(pe);
        constraints_br[5 * i + 0].dy = EoGetY(pe);
        constraints_br[5 * i + 1].dx = EoGetX(pe) + EoGetW(pe);
        constraints_br[5 * i + 1].dy = EoGetY(pe);
        constraints_br[5 * i + 2].dx = EoGetX(pe) + EoGetW(pe);
        constraints_br[5 * i + 2].dy = EoGetY(pe) + EoGetH(pe);
        constraints_br[5 * i + 3].dx = EoGetX(pe);
        constraints_br[5 * i + 3].dy = EoGetY(pe) + EoGetH(pe);

        /* if window occupies 2 quadrants add a constraint for the
         * intersection of the x or y axis and the window */
        if (SPANS_COMMON(EoGetX(pe), EoGetW(pe), cx, 1))
        {
            Dprintf("got horiz edge contraint\n");
            constraints_br[5 * i + 4].dx = cx;
            if (EoGetY(pe) > cy)
            {
                constraints_br[5 * i + 4].dy = EoGetY(pe);
            }
            else
            {
                constraints_br[5 * i + 4].dy = EoGetY(pe) + EoGetH(pe);
            }
        }
        else if (SPANS_COMMON(EoGetY(pe), EoGetH(pe), cy, 1))
        {
            Dprintf("got ver edge contraint\n");
            constraints_br[5 * i + 4].dy = cy;
            if (EoGetX(pe) > cx)
            {
                constraints_br[5 * i + 4].dx = EoGetX(pe);
            }
            else
            {
                constraints_br[5 * i + 4].dx = EoGetX(pe) + EoGetW(pe);
            }
        }
        else
        {
            constraints_br[5 * i + 4].dominated = 1;
            Dprintf("got no edge constraint from win #%d %s\n", i - 1,
                    EwinGetTitle(pe));
        }
    }

    /* add constraints to keep the window on-screen */
    constraints_br[0].dx = cx;
    constraints_br[0].dy = *avail_y1 - 1;
    constraints_br[1].dx = cx;
    constraints_br[1].dy = *avail_y2 + 1;
    constraints_br[2].dx = *avail_x1 - 1;
    constraints_br[2].dy = cy;
    constraints_br[3].dx = *avail_x2 + 1;
    constraints_br[3].dy = cy;
    constraints_br[4].dominated = 1;

    /* subtract out center to get distance to constraints */
    for (i = 0; i < num_tr; i++)
    {
        constraints_br[i].dx -= cx;
        constraints_br[i].dy -= cy;
    }

    /* make 4 copies: one for each corner to optimize */
    memcpy(constraints_tl, constraints_br, sizeof(point) * num_tl);
    memcpy(constraints_bl, constraints_br, sizeof(point) * num_tl);
    memcpy(constraints_tr, constraints_br, sizeof(point) * num_tl);

    for (i = 0; i < num_tr; i++)
    {
        /* correct displacements to be positive in the direction of expansion */
        constraints_tl[i].dx *= -1;
        constraints_tl[i].dy *= -1;

        constraints_bl[i].dx *= -1;

        constraints_tr[i].dy *= -1;
    }

    /* bust out that pareto frontier */
    filter_points(constraints_tl, &num_tl, cx - *avail_x1, cy - *avail_y1);
    filter_points(constraints_tr, &num_tr, *avail_x2 - cx, cy - *avail_y1);
    filter_points(constraints_bl, &num_bl, cx - *avail_x1, *avail_y2 - cy);
    filter_points(constraints_br, &num_br, *avail_x2 - cx, *avail_y2 - cy);

    /* now need to convert corner constraints to candidate edge constraints */
    num_t = num_tl + num_tr;
    num_b = num_bl + num_br;
    num_l = num_tl + num_bl;
    num_r = num_tr + num_br;
    tc = top_ds = EMALLOC(int, num_t);
    bc = bottom_ds = EMALLOC(int, num_b);

    lc = left_ds = EMALLOC(point, num_l);
    rc = right_ds = EMALLOC(point, num_r);

    if (!tc || !bc || !lc || !rc)
        goto freedom;

    /* convert constraint distances back to to constraint displacements
     * using cursor pointers to accumulate constraints for each edge */
    for (i = 0; i < num_tr; i++)
    {
        rc->dx = constraints_tr[i].dx;
        rc->dy = -constraints_tr[i].dy;
        *tc = -constraints_tr[i].dy;
        tc++;
        rc++;
    }
    for (i = 0; i < num_tl; i++)
    {
        lc->dx = -constraints_tl[i].dx;
        lc->dy = -constraints_tl[i].dy;
        *tc = -constraints_tl[i].dy;
        tc++;
        lc++;
    }
    for (i = 0; i < num_br; i++)
    {
        rc->dx = constraints_br[i].dx;
        rc->dy = constraints_br[i].dy;
        *bc = constraints_br[i].dy;
        bc++;
        rc++;
    }
    for (i = 0; i < num_bl; i++)
    {
        lc->dx = -constraints_bl[i].dx;
        lc->dy = constraints_bl[i].dy;
        *bc = constraints_bl[i].dy;
        bc++;
        lc++;
    }

    /* sort the lists for easy searching */
    sort_points_x(left_ds, num_l, +1);
    sort_points_x(right_ds, num_r, -1);
    sort_ints(top_ds, num_t, +1);
    sort_ints(bottom_ds, num_b, -1);
    uniq_ints(top_ds, &num_t);
    uniq_ints(bottom_ds, &num_b);

    /* brute force test the combinitorics:
     * for each pair of possible top and bottom displacements,
     * find the best possible pair of left and right displacements */

    area = 0;
    recenter = 0;

    for (i = 0; i < num_t; i++) /* top indices */
    {
        for (j = 0; j < num_b; j++)     /* bottom indices */
        {
            int             new_w, new_h, trim;

            td = top_ds[i];     /* displacement to top */
            bd = bottom_ds[j];  /* displacement to bottom */
            Dprintf("starting search in td-bd %d-%d\n", td, bd);
            ld = left_ds[0].dx;
            rd = right_ds[0].dx;
            Dprintf("search in y from %d to %d\n", td, bd);

            /* find furthest left given top, bottom */
            for (k = 0; k < num_l; k++)
            {
                if (left_ds[k].dy <= td)        /* constraint point is above top, skip it */
                {
                    Dprintf("left ignoring above point %d,%d\n",
                            left_ds[k].dx, left_ds[k].dy);
                    continue;
                }
                if (left_ds[k].dy >= bd)        /* constraint point is below bottom, skip it */
                {
                    Dprintf("left ignoring below point %d,%d\n",
                            left_ds[k].dx, left_ds[k].dy);
                    continue;
                }
                Dprintf("left using point %d,%d\n", left_ds[k].dx,
                        left_ds[k].dy);
                if (left_ds[k].dx > ld)
                    ld = left_ds[k].dx + 1;
            }

            /* find furthest right given top, bottom */
            for (k = 0; k < num_r; k++)
            {
                if (right_ds[k].dy <= td)       /* constraint point is above top, skip it */
                {
                    Dprintf("right ignoring above point %d,%d\n",
                            right_ds[k].dx, right_ds[k].dy);
                    continue;
                }
                if (right_ds[k].dy >= bd)       /* constraint point is below bottom, skip it */
                {
                    Dprintf("right ignoring below point %d,%d\n",
                            right_ds[k].dx, right_ds[k].dy);
                    continue;
                }
                Dprintf("right using point %d,%d\n", right_ds[k].dx,
                        right_ds[k].dy);
                if (right_ds[k].dx < rd)
                    rd = right_ds[k].dx - 1;
            }

            /* almost there..... need to correct for aspect ratio
             * and keep center as close as possible to old center
             * in case of duplicates...... */

            new_w = 1 - ld + rd;
            new_h = 1 - td + bd;
            aspect = (float)new_w / new_h;
            new_area = new_w * new_h;

            if (new_area > area)
            {
                if (aspect > ewin->icccm.aspect_max)
                {
                    do
                    {
                        if (abs(ld) < abs(rd))
                        {
                            Dprintf("trimming right for aspect\n");
                            rd--;
                        }
                        else
                        {
                            Dprintf("trimming left for aspect\n");
                            ld++;
                        }
                        new_w = 1 - ld + rd;
                        trim = new_w - (int)(new_h * ewin->icccm.aspect_min);
                    }
                    while (trim > 0);
                    Dprintf("ld now %d, rd now %d\n", ld, rd);
                    Dprintf("td now %d, bd now %d\n", td, bd);
                    Dprintf("\n");
                }
                else if (aspect < ewin->icccm.aspect_min)
                {
                    do
                    {
                        if (abs(td) < abs(bd))
                        {
                            Dprintf("trimming bottom for aspect\n");
                            bd--;
                        }
                        else
                        {
                            Dprintf("trimming top for aspect\n");
                            td++;
                        }
                        new_h = 1 - td + bd;
                        trim = new_h - (int)(new_w / ewin->icccm.aspect_min);
                    }
                    while (trim > 0);
                    Dprintf("ld now %d, rd now %d\n", ld, rd);
                    Dprintf("td now %d, bd now %d\n", td, bd);
                    Dprintf("\n");
                }

                new_area = new_w * new_h;
                new_recenter = abs(td) + abs(bd) + abs(ld) + abs(rd);

                if ((new_area > area) ||
                    ((new_area == area) && (new_recenter < recenter)))
                {
                    Dprintf("new area %ld old area %ld\n", new_area, area);
                    Dprintf("%d-%d x %d-%d\n", x, x + w, y, y + h);
                    area = new_area;
                    recenter = new_recenter;
                    x = cx + ld;
                    y = cy + td;
                    w = (1 - ld + rd);
                    h = (1 - td + bd);
                }
            }
            Dprintf("===========================\n");
        }
    }

    *avail_x1 = x;
    *avail_x2 = x + w;
    *avail_y1 = y;
    *avail_y2 = y + h;

    /* rest a while */

  freedom:
    Efree(top_ds);
    Efree(bottom_ds);
    Efree(left_ds);
    Efree(right_ds);
    Efree(constraints_tl);
    Efree(constraints_tr);
    Efree(constraints_bl);
    Efree(constraints_br);
    Efree(filtered_lst);
    Efree(stacked_lst);
    Efree(done_stacking_flag);
    Efree(stacked_above_flag);
}
#endif                          /* ENABLE_SMART_MAXIMISE */

void
MaxSizeHV(EWin *ewin, const char *resize_type, int hor, int ver, int flags)
{
    int             x, y, w, h, x1, x2, y1, y2, type, bl, br, bt, bb;
    Area            area;
    EWin           *const *lst;
    int             num, speed;
    int             old_hor = ewin->state.maximized_horz != 0;
    int             old_ver = ewin->state.maximized_vert != 0;

    if (!ewin)
        return;

    type = MAX_ABSOLUTE;        /* Select default */
    if (!resize_type || !resize_type[0])
        type = Conf.movres.mode_maximize_default;
    else if (!strcmp(resize_type, "absolute"))
        type = MAX_ABSOLUTE;
    else if (!strcmp(resize_type, "available"))
        type = MAX_AVAILABLE;
    else if (!strcmp(resize_type, "conservative"))
        type = MAX_CONSERVATIVE;
    else if (!strcmp(resize_type, "xinerama"))
        type = MAX_XINERAMA;
    else if (!strcmp(resize_type, "half_N"))
        type = MAX_HALF_N;
    else if (!strcmp(resize_type, "half_S"))
        type = MAX_HALF_S;
    else if (!strcmp(resize_type, "half_E"))
        type = MAX_HALF_E;
    else if (!strcmp(resize_type, "half_W"))
        type = MAX_HALF_W;

    if (ewin->state.inhibit_max_hor)
        hor = 0;
    if (ewin->state.inhibit_max_ver)
        ver = 0;

    if (!hor && !ver)
        return;

    if ((type == MAX_HALF_E || type == MAX_HALF_W) && hor && !ver)
        old_ver = 0;
    if ((type == MAX_HALF_N || type == MAX_HALF_S) && ver && !hor)
        old_hor = 0;

    if (!old_hor && !old_ver)
    {
        ewin->save_max.x = EoGetX(ewin);
        ewin->save_max.y = EoGetY(ewin);
        ewin->save_max.w = ewin->client.w;
        ewin->save_max.h = ewin->client.h;
    }

    /* Figure out target state */
    if (hor && ver)
    {
        hor = ver = ((old_hor && old_ver) ? 0 : 1);
    }
    else
    {
        hor = (hor) ? !old_hor : old_hor;
        ver = (ver) ? !old_ver : old_ver;
    }

    ewin->state.maximized_horz = hor;
    ewin->state.maximized_vert = ver;

    Dprintf("h/v old = %d/%d new=%d/%d\n", old_hor, old_ver, hor, ver);
    if (!hor && !ver)
    {
        /* Restore regular state */
        x = ewin->save_max.x;
        y = ewin->save_max.y;
        w = ewin->save_max.w;
        h = ewin->save_max.h;
        goto do_resize;
    }
    if (old_ver == ver && old_hor && !hor)
    {
        /* Turn off horizontal maxsize */
        x = ewin->save_max.x;
        y = EoGetY(ewin);
        w = ewin->save_max.w;
        h = ewin->client.h;
        goto do_resize;
    }
    if (old_hor == hor && old_ver && !ver)
    {
        /* Turn off vertical maxsize */
        x = EoGetX(ewin);
        y = ewin->save_max.y;
        w = ewin->client.w;
        h = ewin->save_max.h;
        goto do_resize;
    }

    /* Default is no change */
    x = EoGetX(ewin);
    y = EoGetY(ewin);
    h = EoGetH(ewin);
    w = EoGetW(ewin);

    switch (type)
    {
    case MAX_XINERAMA:
        if (hor)
        {
            x = 0;
            w = WinGetW(VROOT);
        }
        if (ver)
        {
            y = 0;
            h = WinGetH(VROOT);
        }
        break;

    default:
    case MAX_ABSOLUTE:
    case MAX_AVAILABLE:
    case MAX_CONSERVATIVE:
    case MAX_HALF_N:
    case MAX_HALF_S:
    case MAX_HALF_E:
    case MAX_HALF_W:
        ScreenGetAvailableArea(x + w / 2, y + h / 2, &area,
                               Conf.place.ignore_struts_maximize);
        x1 = area.x;
        y1 = area.y;
        x2 = x1 + area.w;
        y2 = y1 + area.h;

        if (Conf.movres.dragbar_nocover && type != MAX_ABSOLUTE)
        {
            /* Leave room for the dragbar */
            switch (Conf.desks.dragdir)
            {
            case 0:            /* left */
                if (x1 < Conf.desks.dragbar_width)
                    x1 = Conf.desks.dragbar_width;
                break;

            case 1:            /* right */
                if (x2 > WinGetW(VROOT) - Conf.desks.dragbar_width)
                    x2 = WinGetW(VROOT) - Conf.desks.dragbar_width;
                break;

            case 2:            /* top */
                if (y1 < Conf.desks.dragbar_width)
                    y1 = Conf.desks.dragbar_width;
                break;

            case 3:            /* bottom */
                if (y2 > WinGetH(VROOT) - Conf.desks.dragbar_width)
                    y2 = WinGetH(VROOT) - Conf.desks.dragbar_width;
                break;

            default:
                break;
            }
        }

        if (type == MAX_ABSOLUTE)
        {
            /* Simply ignore all windows */
            lst = NULL;
            num = 0;
        }
        else
        {
            lst = EwinListGetForDesk(&num, EoGetDesk(ewin));
        }

        if (type == MAX_HALF_E && hor)
        {
            x1 += (x2 - x1) / 2;
        }
        if (type == MAX_HALF_W && hor)
        {
            x2 -= (x2 - x1) / 2;
        }
        if (type == MAX_HALF_N && ver)
        {
            y2 -= (y2 - y1) / 2;
        }
        if (type == MAX_HALF_S && ver)
        {
            y1 += (y2 - y1) / 2;
        }

#if ENABLE_SMART_MAXIMISE
        if (type == MAX_CONSERVATIVE && ver && hor &&
            ( /*(!old_hor && !old_ver) || */ Conf.movres.enable_smart_max_hv))
        {
            _get_span_xy(ewin, type, lst, num, &x1, &x2, &y1, &y2);
            x = x1;
            w = x2 - x1;
            y = y1;
            h = y2 - y1;
            break;
        }
#endif

        if (ver)
        {
            _get_span_y(ewin, type, lst, num, &y1, &y2);
            y = y1;
            h = y2 - y1;
        }

        if (hor)
        {
            _get_span_x(ewin, type, lst, num, &x1, &x2);
            x = x1;
            w = x2 - x1;
        }

        break;
    }

    EwinBorderGetSize(ewin, &bl, &br, &bt, &bb);
    w -= (bl + br);
    if (w < 10)
        w = 10;
    h -= (bt + bb);
    if (h < 10)
        h = 10;

  do_resize:
    speed = Conf.movres.maximize_animate ? Conf.movres.maximize_speed : 0;
    EwinSlideSizeTo(ewin, x, y, w, h, speed, 0, flags | SLIDE_WARP);

    HintsSetWindowState(ewin);
}
