/*
 * Copyright (C) 2004-2022 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 "desktops.h"
#include "eobj.h"
#include "ewins.h"

#define ENABLE_DEBUG_STACKING 1

#define EobjGetCwin(p) \
    ((p->type == EOBJ_TYPE_EWIN) ? EwinGetClientXwin(((EWin*)(p))) : NoXID)

typedef struct {
    const char     *name;
    struct {
        int             nalloc;
        int             nobjs;
        EObj          **list;
    } o;
    struct {
        int             nalloc;
        int             nwins;
        EWin          **list;
    } e;
    char            layered;
    char            changed;    /* Used by _EwinListGet() */
} EobjList;

static int      EobjListRaise(EobjList * eol, EObj * eo, int test);
static int      EobjListLower(EobjList * eol, EObj * eo, int test);

#if ENABLE_DEBUG_STACKING
static void
EobjListShow(const char *txt, EobjList *eol)
{
    int             i;
    EObj           *eo;

    if (EDebug(EDBUG_TYPE_STACKING) < 2)
        return;

    Eprintf("%s-%s:\n", eol->name, txt);
    for (i = 0; i < eol->o.nobjs; i++)
    {
        eo = eol->o.list[i];
        Eprintf(" %2d: %#10x %#10x %d %d %s\n", i, EobjGetXwin(eo),
                EobjGetCwin(eo), eo->desk->num, eo->ilayer, EobjGetName(eo));
    }
}
#else
#define EobjListShow(txt, eol)
#endif

static int
EobjListGetIndex(EobjList *eol, EObj *eo)
{
    int             i;

    for (i = 0; i < eol->o.nobjs; i++)
        if (eol->o.list[i] == eo)
            return i;

    return -1;
}

static void
EobjListAdd(EobjList *eol, EObj *eo, int ontop)
{
    int             i;

    /* Quit if already in list */
    i = EobjListGetIndex(eol, eo);
    if (i >= 0)
        return;

    if (eol->o.nobjs >= eol->o.nalloc)
    {
        eol->o.nalloc += 16;
        eol->o.list = EREALLOC(EObj *, eol->o.list, eol->o.nalloc);
    }

    if (eol->layered)
    {
        /* The simple way for now (add, raise/lower) */
        if (ontop)
        {
            eol->o.list[eol->o.nobjs] = eo;
            eol->o.nobjs++;
            EobjListRaise(eol, eo, 0);
        }
        else
        {
            memmove(eol->o.list + 1, eol->o.list,
                    eol->o.nobjs * sizeof(EObj *));
            eol->o.list[0] = eo;
            eol->o.nobjs++;
            EobjListLower(eol, eo, 0);
        }
        if (eo->stacked == 0)
            DeskSetDirtyStack(eo->desk, eo);
    }
    else
    {
        if (ontop)
        {
            memmove(eol->o.list + 1, eol->o.list,
                    eol->o.nobjs * sizeof(EObj *));
            eol->o.list[0] = eo;
        }
        else
        {
            eol->o.list[eol->o.nobjs] = eo;
        }
        eol->o.nobjs++;
    }

    eol->changed = 1;

    EobjListShow("EobjListAdd", eol);
}

static void
EobjListDel(EobjList *eol, EObj *eo)
{
    int             i, n;

    /* Quit if not in list */
    i = EobjListGetIndex(eol, eo);
    if (i < 0)
        return;

    eol->o.nobjs--;
    n = eol->o.nobjs - i;
    if (n > 0)
    {
        memmove(eol->o.list + i, eol->o.list + i + 1, n * sizeof(EObj *));
    }
    else if (eol->o.nobjs <= 0)
    {
        /* Enables autocleanup at shutdown, if ever implemented */
        EFREE_NULL(eol->o.list);
        eol->o.nalloc = 0;
    }

    eol->changed = 1;

    EobjListShow("EobjListDel", eol);
}

static int
EobjListLower(EobjList *eol, EObj *eo, int test)
{
    int             i, j, n;

    /* Quit if not in list */
    i = EobjListGetIndex(eol, eo);
    if (i < 0)
        return 0;

    j = eol->o.nobjs - 1;
    if (eol->layered)
    {
        /* Take the layer into account */
        for (; j >= 0; j--)
            if (i != j && eo->ilayer <= eol->o.list[j]->ilayer)
                break;
        if (j < i)
            j++;
    }

    n = j - i;
    if (test)
        return n;

    if (n > 0)
    {
        memmove(eol->o.list + i, eol->o.list + i + 1, n * sizeof(EObj *));
        eol->o.list[j] = eo;
        if (eol->layered && eo->stacked > 0)
            DeskSetDirtyStack(eo->desk, eo);
        eol->changed = 1;
    }
    else if (n < 0)
    {
        memmove(eol->o.list + j + 1, eol->o.list + j, -n * sizeof(EObj *));
        eol->o.list[j] = eo;
        if (eol->layered && eo->stacked > 0)
            DeskSetDirtyStack(eo->desk, eo);
        eol->changed = 1;
    }

    EobjListShow("EobjListLower", eol);
    return n;
}

static int
EobjListRaise(EobjList *eol, EObj *eo, int test)
{
    int             i, j, n;

    /* Quit if not in list */
    i = EobjListGetIndex(eol, eo);
    if (i < 0)
        return 0;

    j = 0;
    if (eol->layered)
    {
        /* Take the layer into account */
        for (; j < eol->o.nobjs; j++)
            if (j != i && eo->ilayer >= eol->o.list[j]->ilayer)
                break;
        if (j > i)
            j--;
    }

    n = j - i;
    if (test)
        return n;

    if (n > 0)
    {
        memmove(eol->o.list + i, eol->o.list + i + 1, n * sizeof(EObj *));
        eol->o.list[j] = eo;
        if (eol->layered && eo->stacked > 0)
            DeskSetDirtyStack(eo->desk, eo);
        eol->changed = 1;
    }
    else if (n < 0)
    {
        memmove(eol->o.list + j + 1, eol->o.list + j, -n * sizeof(EObj *));
        eol->o.list[j] = eo;
        if (eol->layered && eo->stacked > 0)
            DeskSetDirtyStack(eo->desk, eo);
        eol->changed = 1;
    }

    EobjListShow("EobjListRaise", eol);
    return n;
}

static EObj    *
EobjListFind(const EobjList *eol, EX_Window win)
{
    int             i;

    for (i = 0; i < eol->o.nobjs; i++)
        if (EobjGetXwin(eol->o.list[i]) == win)
            return eol->o.list[i];

    return NULL;
}

#if 0
static int
EobjListTypeCount(const EobjList *eol, int type)
{
    int             i, n;

    for (i = n = 0; i < eol->o.nobjs; i++)
        if (eol->o.list[i]->type == type)
            n++;

    return n;
}
#endif

/*
 * The global object/client lists
 */
#define EOBJ_LIST(_name, _layered) \
    { .name = _name, .layered = _layered }

static EobjList EobjListStack = EOBJ_LIST("Stack", 1);
static EobjList EwinListFocus = EOBJ_LIST("Focus", 0);
static EobjList EobjListOrder = EOBJ_LIST("Order", 0);

static EObj    *const *
EobjListGet(EobjList *eol, int *num)
{
    *num = eol->o.nobjs;
    return eol->o.list;
}

int
EobjListStackCheck(EObj *eo)
{
    return EobjListGetIndex(&EobjListStack, eo);
}

EObj           *
EobjListStackFind(EX_Window win)
{
    return EobjListFind(&EobjListStack, win);
}

EObj           *const *
EobjListStackGet(int *num)
{
    return EobjListGet(&EobjListStack, num);
}

void
EobjListStackAdd(EObj *eo, int ontop)
{
    EobjListAdd(&EobjListStack, eo, ontop);
}

void
EobjListStackDel(EObj *eo)
{
    EobjListDel(&EobjListStack, eo);
}

int
EobjListStackRaise(EObj *eo, int test)
{
    return EobjListRaise(&EobjListStack, eo, test);
}

int
EobjListStackLower(EObj *eo, int test)
{
    return EobjListLower(&EobjListStack, eo, test);
}

void
EobjListFocusAdd(EObj *eo, int ontop)
{
    EobjListAdd(&EwinListFocus, eo, ontop);
}

void
EobjListFocusDel(EObj *eo)
{
    EobjListDel(&EwinListFocus, eo);
}

int
EobjListFocusRaise(EObj *eo)
{
    return EobjListRaise(&EwinListFocus, eo, 0);
}

static EWin    *const *
_EwinListGet(EobjList *eol, int *pnum)
{
    int             i, j;
    EObj           *eo;

    if (EDebug(EDBUG_TYPE_STACKING))
        Eprintf("%s: %s changed=%d\n", __func__, eol->name, eol->changed);

    if (!eol->changed)
        goto done;

    for (i = j = 0; i < eol->o.nobjs; i++)
    {
        eo = eol->o.list[i];
        if (eo->type != EOBJ_TYPE_EWIN)
            continue;

        if (eol->e.nalloc <= j)
        {
            eol->e.nalloc += 16;        /* 16 at the time */
            eol->e.list = EREALLOC(EWin *, eol->e.list, eol->e.nalloc);
        }

        eol->e.list[j++] = (EWin *) eo;
    }

    eol->e.nwins = j;
    eol->changed = 0;

  done:
    *pnum = eol->e.nwins;
    return eol->e.list;
}

EWin           *const *
EwinListStackGet(int *num)
{
    return _EwinListGet(&EobjListStack, num);
}

EWin           *const *
EwinListFocusGet(int *num)
{
    return (EWin * const *)EobjListGet(&EwinListFocus, num);
}

EWin           *const *
EwinListGetForDesk(int *num, Desk *dsk)
{
    static EWin   **lst = NULL;
    static int      nalloc = 0;
    const EobjList *eol;
    int             i, j;
    EObj           *eo;

    eol = &EobjListStack;

    for (i = j = 0; i < eol->o.nobjs; i++)
    {
        eo = eol->o.list[i];
        if (eo->type != EOBJ_TYPE_EWIN || eo->desk != dsk)
            continue;

        if (nalloc <= j)
        {
            nalloc += 16;       /* 16 at the time */
            lst = EREALLOC(EWin *, lst, nalloc);
        }

        lst[j++] = (EWin *) eo;
    }

    *num = j;
    return lst;
}

EObj           *const *
EobjListStackGetForDesk(int *num, Desk *dsk)
{
    static EObj   **lst = NULL;
    static int      nalloc = 0;
    const EobjList *eol;
    int             i, j;
    EObj           *eo;

    eol = &EobjListStack;

    /* Too many - who cares. */
    if (nalloc < eol->o.nobjs)
    {
        nalloc = (eol->o.nobjs + 16) & ~0xf;    /* 16 at the time */
        lst = EREALLOC(EObj *, lst, nalloc);
    }

    for (i = j = 0; i < eol->o.nobjs; i++)
    {
        eo = eol->o.list[i];
        if (eo->desk != dsk)
            continue;

        lst[j++] = eo;
    }

    *num = j;
    return lst;
}

int
EwinListStackIsRaised(const EWin *ewin)
{
    const EobjList *eol;
    int             i;
    const EObj     *eo, *eox;

    eol = &EobjListStack;
    eox = EoObj(ewin);

    for (i = 0; i < eol->o.nobjs; i++)
    {
        eo = eol->o.list[i];
        if (eo->type != EOBJ_TYPE_EWIN)
            continue;
        if (eo->desk != eox->desk)
            continue;
        if (eo->ilayer > eox->ilayer)
            continue;
        if (EwinGetTransientFor((EWin *) eo) == EwinGetClientXwin(ewin))
            continue;
        return eo == eox;
    }

    return 1;                   /* It should be impossible to get here */
}

void
EobjListOrderAdd(EObj *eo)
{
    EobjListAdd(&EobjListOrder, eo, 0);
}

void
EobjListOrderDel(EObj *eo)
{
    EobjListDel(&EobjListOrder, eo);
}

EObj           *const *
EobjListOrderGet(int *num)
{
    return EobjListGet(&EobjListOrder, num);
}

EWin           *const *
EwinListOrderGet(int *num)
{
    return _EwinListGet(&EobjListOrder, num);
}
