/*
 * 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 "config.h"

#include <X11/Xlib.h>

#include "E.h"
#include "animation.h"
#include "desktops.h"
#include "ecompmgr.h"
#include "eobj.h"
#include "ewins.h"              /* FIXME - Should not be here */
#include "hints.h"
#include "xprop.h"
#include "xwin.h"

int
OpacityFix(int op, int op_0)
{
    if (op <= 0)
        op = op_0;
    else if (op > 255)
        op = 100;
    else if (op > 100)          /* Hack to convert old 0-255 range */
        op = (100 * op) / 255;
    return op;
}

unsigned int
OpacityFromPercent(int opx)
{
    unsigned int    op = (unsigned int)opx;

    /* op is 0-100, extend to 32 bit */
    /* op <= 0 and op > 100 is mapped to 100 (opaque) */
    if (op >= 100)
        return 0xffffffff;
    return op * 42949672;
}

int
OpacityToPercent(unsigned int opacity)
{
    return (int)(opacity / 42949672);
}

const char     *
EobjGetNameSafe(const EObj *eo)
{
    const char     *name = NULL;

    if (!eo)
        goto done;

    name = EobjGetName(eo);

  done:
    return name ? name : "None";
}

void
EobjSetName(EObj *eo, const char *name)
{
    if (name != eo->icccm.wm_name)
        EFREE_DUP(eo->icccm.wm_name, name);

    HintsSetWindowName(EobjGetWin(eo), eo->icccm.wm_name);
}

void
EobjSetLayer(EObj *eo, int layer)
{
    int             ilayer = eo->ilayer;

    eo->layer = layer;
    /*
     * For usual EWin's the internal layer is the "old" E-layer * 10.
     *
     * Internal layers:
     *   0: Root
     *   3: Desktop type apps
     *   5: Below buttons
     *  10: Lowest ewins
     *  15: Normal buttons
     *  20: Normal below ewins
     *  40: Normal ewins
     *  60: Above ewins
     *  75: Above buttons
     *  80: Ontop ewins
     * 100: E-Dialogs
     * 512-: Floating windows
     * + 0: Virtual desktops
     * +30: E-Menus
     * +40: Override redirects
     * +40: E-Tooltips
     */

    switch (eo->type)
    {
    case EOBJ_TYPE_EWIN:
        eo->ilayer = 10 * eo->layer;
        if (eo->ilayer == 0)
            eo->ilayer = 3;
        break;

    case EOBJ_TYPE_BUTTON:
        if (eo->layer > 0)
            eo->ilayer = 75;    /* Ontop */
        else if (eo->layer == 0)
            eo->ilayer = 15;    /* Normal */
        else if (eo->layer < 0)
            eo->ilayer = 5;     /* Below */
        if (eo->layer > 0 && eo->sticky)
            eo->floating = 1;
        break;

    default:
        eo->ilayer = 10 * eo->layer;
        break;
    }

    if (eo->floating)
        eo->ilayer |= 512;
    else
        eo->ilayer &= ~512;
    if (eo->ghost)
        eo->ilayer |= 1024;

    if (eo->ilayer != ilayer)
        EobjRaise(eo);
}

void
EobjSetFloating(EObj *eo, int floating)
{
    if (floating == eo->floating)
        return;

#if 0
    switch (eo->type)
    {
    default:
        break;
    case EOBJ_TYPE_EWIN:
        if (floating > 1)
            eo->desk = 0;
        break;
    }
#endif

    eo->floating = floating;
    EobjSetLayer(eo, eo->layer);
}

bool
EobjShadowOk(const EObj *eo, bool sharp)
{
    if (!eo->shadow)
        return false;           /* Disabled by configuration */

    switch (eo->type)
    {
    default:
        if (sharp)
            return true;        /* Enabled if sharp */
        if (eo->shaped)
            return false;       /* Disabled if shaped */
        break;
    case EOBJ_TYPE_EWIN:
        if (((EWin *) eo)->state.fullscreen)
            return false;       /* Disabled if fullscreen */
        if (sharp)
            return true;        /* Enabled if sharp */
        if (((EWin *) eo)->state.shaped)
            return false;       /* Disabled if client is shaped */
    }

    return true;
}

#if USE_GLX
#define WINTYPE(t) ((t == EOBJ_TYPE_GLX) ? WIN_TYPE_GLX : WIN_TYPE_INTERNAL)
#else
#define WINTYPE(t) WIN_TYPE_INTERNAL
#endif

void
EobjInit(EObj *eo, int type, Win win, int x, int y, int w, int h,
         int su, const char *name)
{
    if (!eo->desk)
        eo->desk = DeskGet(0);

    if (!win)
    {
        if (type == EOBJ_TYPE_EVENT)
        {
            win = ECreateEventWindow(VROOT, x, y, w, h);
            eo->inputonly = 1;
        }
        else
        {
            win = ECreateObjectWindow(EoGetWin(eo->desk), x, y, w, h, su,
                                      WINTYPE(type), NULL);
        }
    }
    eo->type = type;
    eo->win = win;
    eo->shaped = -1;
    if (!win)
        return;

    if (type == EOBJ_TYPE_EXT)
    {
        eo->icccm.wm_name = ex_icccm_title_get(WinGetXwin(win));
        ex_icccm_name_class_get(WinGetXwin(win),
                                &eo->icccm.wm_res_name,
                                &eo->icccm.wm_res_class);
    }
    else if (name)
        eo->icccm.wm_name = Estrdup(name);
    if (!eo->icccm.wm_name)
        eo->icccm.wm_name = Estrdup("-?-");

    if (type != EOBJ_TYPE_EWIN && type != EOBJ_TYPE_EXT)
        HintsSetWindowName(EobjGetWin(eo), eo->icccm.wm_name);

#if USE_COMPOSITE
    switch (type)
    {
    case EOBJ_TYPE_EVENT:
    case EOBJ_TYPE_MISC_NR:
    case EOBJ_TYPE_ROOT_BG:
        eo->noredir = 1;
        break;
    }
    ECompMgrWinNew(eo);
#endif
    if (EobjGetXwin(eo) != WinGetXwin(VROOT))
    {
        EobjListOrderAdd(eo);
        EobjListStackAdd(eo, 1);
    }

    if (EDebug(EDBUG_TYPE_EWINS))
        Eprintf("%s: %#x %s\n", __func__, EobjGetXwin(eo), EobjGetName(eo));
}

void
EobjFini(EObj *eo)
{
    if (EDebug(EDBUG_TYPE_EWINS))
        Eprintf("%s: %#x %s\n", __func__, EobjGetXwin(eo), EobjGetName(eo));

    EobjListStackDel(eo);
    EobjListOrderDel(eo);

#if USE_COMPOSITE
    if (!eo->external)
        eo->gone = 1;           /* Actually not yet (but soon) */

    if (eo->cmhook)
        ECompMgrWinDel(eo);
#endif

    if (eo->external)
        EUnregisterWindow(EobjGetWin(eo));
    else
        EDestroyWindow(EobjGetWin(eo));

    Efree(eo->icccm.wm_name);
    Efree(eo->icccm.wm_res_name);
    Efree(eo->icccm.wm_res_class);
    AnimatorsFree(eo);
}

void
EobjDestroy(EObj *eo)
{
    if (EDebug(EDBUG_TYPE_EWINS))
        Eprintf("%s: %#x %s\n", __func__, EobjGetXwin(eo), EobjGetName(eo));

    EobjFini(eo);

    Efree(eo);
}

EObj           *
EobjWindowCreate(int type, int x, int y, int w, int h, int su, const char *name)
{
    EObj           *eo;

    eo = ECALLOC(EObj, 1);

    eo->floating = 1;
    EobjSetLayer(eo, 20);
    EobjInit(eo, type, EobjGetWin(eo), x, y, w, h, su, name);
    if (!eo->win)
    {
        EFREE_NULL(eo);
    }

    return eo;
}

void
EobjWindowDestroy(EObj *eo)
{
#if USE_COMPOSITE
    if (eo->shown && !eo->inputonly)
        EobjUnmap(eo);
#endif
    EobjDestroy(eo);
}

EObj           *
EobjRegisterOR(EX_Window xwin __UNUSED__, XWindowAttributes *pxwa __UNUSED__,
               int mapped __UNUSED__)
{
    EObj           *eo = NULL;

#if USE_COMPOSITE
    XWindowAttributes attr;
    Win             win;

    if (!ECompMgrIsActive())
        return NULL;

    eo = EobjListStackFind(xwin);
    if (eo)
    {
        if (eo->external)
            goto done;
        else
            return eo;
    }

    if (!pxwa)
    {
        pxwa = &attr;
        if (!EXGetWindowAttributes(xwin, &attr))
            return NULL;
    }
    if (!pxwa->override_redirect)
        return NULL;
#ifndef __cplusplus
#define c_class class
#endif
    if (pxwa->c_class != InputOutput)
        return NULL;

    win = ERegisterWindow(xwin, pxwa);
    if (!win)
        return NULL;

    eo = ECALLOC(EObj, 1);
    if (!eo)
        return eo;

    eo->external = 1;
    eo->fade = 1;
    eo->shadow = 1;

    EobjInit(eo, EOBJ_TYPE_EXT, win,
             pxwa->x, pxwa->y, pxwa->width, pxwa->height, 0, NULL);

    if (win->argb)
        eo->shadow = 0;
    EobjSetFloating(eo, 1);
    EobjSetLayer(eo, 4);

  done:
    if (mapped)
    {
        eo->shown = 1;
        EobjListStackRaise(eo, 0);
        ECompMgrWinMap(eo);
    }

#if 0
    Eprintf("%s: %#x depth=%d argb=%d %s\n", __func__,
            EobjGetXwin(eo), win->depth, win->argb, EobjGetName(eo));
#endif

#endif                          /* USE_COMPOSITE */

    return eo;
}

void
EobjUnregister(EObj *eo)
{
#if 0
    Eprintf("%s: %#x type=%d: %s\n", __func__, EobjGetXwin(eo), eo->type,
            EobjGetName(eo));
#endif
    EobjDestroy(eo);
}

void
EobjMap(EObj *eo, int raise)
{
    if (eo->shown)
        return;
    eo->shown = 1;

    if (raise)
        EobjListStackRaise(eo, 0);

    if (eo->stacked <= 0 || raise > 1)
        DeskRestack(eo->desk);

    if (eo->shaped < 0)
        EobjShapeUpdate(eo, 0);

    EMapWindow(EobjGetWin(eo));
#if USE_COMPOSITE
    ECompMgrWinMap(eo);
#endif
}

void
EobjUnmap(EObj *eo)
{
    if (!eo->shown)
        return;

#if USE_COMPOSITE
    if (eo->cmhook)
        ECompMgrWinUnmap(eo);
#endif
    EUnmapWindow(EobjGetWin(eo));
    eo->shown = 0;
}

void
EobjMoveResize(EObj *eo, int x, int y, int w, int h)
{
#if USE_COMPOSITE
    int             move, resize;

    move = x != EobjGetX(eo) || y != EobjGetY(eo);
    resize = w != EobjGetW(eo) || h != EobjGetH(eo);
#endif

    EMoveResizeWindow(EobjGetWin(eo), x, y, w, h);

#if USE_COMPOSITE
    if (eo->cmhook)
        ECompMgrWinMoveResize(eo, move, resize, 0);
#endif
}

void
EobjMove(EObj *eo, int x, int y)
{
    EobjMoveResize(eo, x, y, EobjGetW(eo), EobjGetH(eo));
}

void
EobjResize(EObj *eo, int w, int h)
{
    EobjMoveResize(eo, EobjGetX(eo), EobjGetY(eo), w, h);
}

#if USE_COMPOSITE
void
EobjDamage(EObj *eo)
{
    if (eo->cmhook)
        ECompMgrWinDamageArea(eo, 0, 0, 0, 0);
}
#else
void
EobjDamage(EObj *eo __UNUSED__)
{
}
#endif

void
EobjReparent(EObj *eo, EObj *dst, int x, int y)
{
#if USE_COMPOSITE
    int             move;

    move = x != EobjGetX(eo) || y != EobjGetY(eo);
#endif

    EReparentWindow(EobjGetWin(eo), EobjGetWin(dst), x, y);
    if (dst->type == EOBJ_TYPE_DESK)
    {
        Desk           *dsk = (Desk *) dst;

        if (eo->stacked < 0)
        {
            eo->desk = NULL;
            eo->stacked = 0;
        }
        if (eo->desk != dsk)
            DeskSetDirtyStack(dsk, eo);
#if USE_COMPOSITE
        if (eo->cmhook)
            ECompMgrWinReparent(eo, dsk, move);
#endif
        eo->desk = dsk;
    }
    else
    {
        EobjListStackDel(eo);
#if USE_COMPOSITE
        if (eo->cmhook)
            ECompMgrWinDel(eo);
#endif
    }
}

int
EobjRaise(EObj *eo)
{
#if USE_COMPOSITE
    int             num;

    num = EobjListStackRaise(eo, 1);
    if (num == 0)
        return num;

    if (num < 0)
        num = EobjListStackRaise(eo, 0);
    if (eo->shown && eo->cmhook)
        ECompMgrWinRaiseLower(eo, num);
    if (num > 0)
        num = EobjListStackRaise(eo, 0);

    return num;
#else
    return EobjListStackRaise(eo, 0);
#endif
}

int
EobjLower(EObj *eo)
{
#if USE_COMPOSITE
    int             num;

    num = EobjListStackLower(eo, 1);
    if (num == 0)
        return num;

    if (num < 0)
        num = EobjListStackLower(eo, 0);
    if (eo->shown && eo->cmhook)
        ECompMgrWinRaiseLower(eo, num);
    if (num > 0)
        num = EobjListStackLower(eo, 0);

    return num;
#else
    return EobjListStackLower(eo, 0);
#endif
}

void
EobjShapeUpdate(EObj *eo, int propagate)
{
#if USE_COMPOSITE
    int             was_shaped = eo->shaped;
#endif

    if (propagate)
        eo->shaped = EShapePropagate(EobjGetWin(eo)) != 0;
    else
        eo->shaped = EShapeCheck(EobjGetWin(eo)) != 0;

#if USE_COMPOSITE
    if (was_shaped <= 0 && eo->shaped <= 0)
        return;

    /* Shape may still be unchanged. Well ... */
    if (eo->shown && eo->cmhook)
        ECompMgrWinChangeShape(eo);
#endif
}

#if USE_COMPOSITE
EX_Pixmap
EobjGetPixmap(const EObj *eo)
{
    return ECompMgrWinGetPixmap(eo);
}
#else
EX_Pixmap
EobjGetPixmap(const EObj *eo __UNUSED__)
{
    return NoXID;
}
#endif

#if USE_COMPOSITE
/* Update now */
void
EobjChangeOpacityNow(EObj *eo, unsigned int opacity)
{
    if (eo->opacity == opacity)
        return;
    eo->opacity = opacity;
    ECompMgrWinSetOpacity(eo, opacity);
}

/* Change through fade, if enabled */
void
EobjChangeOpacity(EObj *eo, unsigned int opacity)
{
    if (eo->opacity == opacity)
        return;
    eo->opacity = opacity;
    ECompMgrWinChangeOpacity(eo, opacity);
}

void
EobjChangeShadow(EObj *eo, int shadow)
{
    ECompMgrWinChangeShadow(eo, shadow);
}
#else
void
EobjChangeOpacityNow(EObj *eo __UNUSED__, unsigned int opacity __UNUSED__)
{
}

void
EobjChangeOpacity(EObj *eo __UNUSED__, unsigned int opacity __UNUSED__)
{
}

void
EobjChangeShadow(EObj *eo __UNUSED__, int shadow __UNUSED__)
{
}
#endif

void
EobjsRepaint(void)
{
#if USE_COMPOSITE
    if (ECompMgrIsActive())
        ECompMgrRepaint();
    else
#endif
        ESync(0);
}

#if USE_COMPOSITE
void
EobjsOpacityUpdate(int op_or)
{
    EObj           *eo, *const *lst;
    int             i, num;

    lst = EobjListStackGet(&num);
    for (i = 0; i < num; i++)
    {
        eo = lst[i];
        switch (eo->type)
        {
        default:
            break;
        case EOBJ_TYPE_EWIN:
            EwinUpdateOpacity((EWin *) eo);
            break;
        case EOBJ_TYPE_EXT:
            EobjChangeOpacity(eo, OpacityFromPercent(op_or));
            break;
        }
    }
}
#endif
