/*
 * 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.
 */
/*
 * Feeble attempt to collect hint stuff in one place
 */
#include "config.h"

#include <X11/Xlib.h>
#include <X11/Xatom.h>

#include "E.h"
#include "borders.h"            /* FIXME - Should not be here */
#include "desktops.h"           /* FIXME - Should not be here */
#include "events.h"
#include "ewins.h"
#include "hints.h"
#include "xprop.h"
#include "xwin.h"

static void     EHintsSetDeskInfo(void);
static void     EHintsSetAreaInfo(void);

static unsigned int desk_info = 0;

void
HintsInit(void)
{
    EX_Window       win;

    win = XCreateSimpleWindow(disp, WinGetXwin(VROOT), -200, -200, 5, 5,
                              0, 0, 0);

    ex_atoms_init();
    ICCCM_Init();
    MWM_SetInfo();
    EWMH_Init(win);

    ex_window_prop_string_set(WinGetXwin(VROOT), ea_m.ENLIGHTENMENT_VERSION,
                              e_wm_version);

    if (Mode.wm.window)
    {
        HintsSetWindowName(VROOT, "Enlightenment");
        HintsSetWindowClass(VROOT, "Virtual-Root", "Enlightenment");
    }

    Mode.root.ext_pmap = HintsGetRootPixmap(VROOT);
    Mode.root.ext_pmap_valid = EXDrawableOk(Mode.root.ext_pmap);
}

/*
 * Functions that set X11-properties from E-internals
 */

void
HintsSetRootHints(Win win __UNUSED__)
{
    /* Nothing done here for now */
}

void
HintsSetClientList(void)
{
    EWMH_SetClientList();
    EWMH_SetClientStacking();
}

void
HintsSetClientStacking(void)
{
    EWMH_SetClientStacking();
}

void
HintsSetDesktopNames(void)
{
    EWMH_SetDesktopNames();
}

void
HintsSetDesktopConfig(void)
{
    EWMH_SetDesktopCount();
    EWMH_SetDesktopRoots();
    EWMH_SetWorkArea();
}

void
HintsSetViewportConfig(void)
{
    EWMH_SetDesktopSize();
}

void
HintsSetCurrentDesktop(void)
{
    EWMH_SetCurrentDesktop();
    EHintsSetDeskInfo();
}

void
HintsSetDesktopViewport(void)
{
    EWMH_SetDesktopViewport();
    EHintsSetAreaInfo();
}

void
HintsSetActiveWindow(EX_Window win)
{
    EWMH_SetActiveWindow(win);
}

void
HintsSetWindowName(Win win, const char *name)
{
    if (!name)
        return;

    ex_icccm_title_set(WinGetXwin(win), name);

    EWMH_SetWindowName(WinGetXwin(win), name);
}

void
HintsSetWindowClass(Win win, const char *name, const char *clss)
{
    if (!name)
        name = "NoName";
    if (!clss)
        clss = "NoClass";

    ex_icccm_name_class_set(WinGetXwin(win), name, clss);
}

void
HintsSetWindowDesktop(const EWin *ewin)
{
    EWMH_SetWindowDesktop(ewin);
}

void
HintsSetWindowArea(const EWin *ewin __UNUSED__)
{
}

void
HintsSetWindowState(const EWin *ewin)
{
    EWMH_SetWindowState(ewin);
    EWMH_SetWindowActions(ewin);
}

void
HintsSetWindowOpacity(EWin *ewin)
{
    EWMH_SetWindowOpacity(ewin);
}

void
HintsSetWindowBorder(const EWin *ewin)
{
    EWMH_SetWindowBorder(ewin);
}

/*
 * Functions that set E-internals from X11-properties
 */

void
HintsGetWindowHints(EWin *ewin)
{
    EWMH_GetWindowHints(ewin);
}

/*
 * Functions that delete X11-properties
 */

void
HintsDelWindowHints(const EWin *ewin)
{
    EWMH_DelWindowHints(ewin);
}

/*
 * Functions processing received X11 messages
 */

void
HintsProcessPropertyChange(EWin *ewin, XEvent *ev)
{
    if (ICCCM_ProcessPropertyChange(ewin, ev->xproperty.atom))
        return;
    if (EWMH_ProcessPropertyChange(ewin, ev->xproperty.atom))
        return;
#if 0
    if (EDebug(1))
    {
        Eprintf("%s:\n", __func__);
        EventShow(ev);
    }
#endif
}

void
HintsProcessClientClientMessage(EWin *ewin, XClientMessageEvent *event)
{
    if (ICCCM_ProcessClientClientMessage(ewin, event))
        return;
    if (EWMH_ProcessClientClientMessage(ewin, event))
        return;
    if (EDebug(1))
    {
        Eprintf("%s:\n", __func__);
        EventShow((XEvent *) event);
    }
}

void
HintsProcessRootClientMessage(XClientMessageEvent *event)
{
    if (ICCCM_ProcessRootClientMessage(event))
        return;
    if (EWMH_ProcessRootClientMessage(event))
        return;
    if (EDebug(1))
    {
        Eprintf("%s:\n", __func__);
        EventShow((XEvent *) event);
    }
}

EX_Pixmap
HintsGetRootPixmap(Win win)
{
    EX_Pixmap       pm;

    pm = NoXID;
    ex_window_prop_xid_get(WinGetXwin(win), ea_m._XROOTPMAP_ID, XA_PIXMAP, &pm,
                           1);

    return pm;
}

void
HintsSetRootInfo(Win win, EX_Pixmap pmap, unsigned int color)
{
    EX_Pixmap       pm;

    pm = pmap;
    ex_window_prop_xid_set(WinGetXwin(win), ea_m._XROOTPMAP_ID, XA_PIXMAP, &pm,
                           1);

    ex_window_prop_card32_set(WinGetXwin(win), ea_m._XROOTCOLOR_PIXEL, &color,
                              1);
}

typedef union {
    struct {
        unsigned        version:8;
        unsigned        rsvd:22;
        unsigned        docked:1;
        unsigned        iconified:1;
    } b;
    unsigned        all:32;
} EWinInfoFlags;

#define ENL_DATA_ITEMS      12
#define ENL_DATA_VERSION     0

void
EHintsSetInfo(const EWin *ewin)
{
    int             c[ENL_DATA_ITEMS];
    unsigned int    flags[2];
    EWinInfoFlags   f;

    if (EwinIsInternal(ewin))
        return;

    f.all = 0;
    f.b.version = ENL_DATA_VERSION;
    f.b.docked = ewin->state.docked;
    f.b.iconified = ewin->state.iconified;
    c[0] = f.all;

    EwinFlagsEncode(ewin, flags);
    c[1] = flags[0];
    c[2] = flags[1];

    c[3] = ewin->save_max.x;
    c[4] = ewin->save_max.y;
    c[5] = ewin->save_max.w;
    c[6] = ewin->save_max.h;
    c[7] = ewin->save_fs.x;
    c[8] = ewin->save_fs.y;
    c[9] = ewin->save_fs.w;
    c[10] = ewin->save_fs.h;
    c[11] = ewin->save_fs.layer;

    ex_window_prop_card32_set(EwinGetClientXwin(ewin), ea_m.ENL_WIN_DATA,
                              (unsigned int *)c, ENL_DATA_ITEMS);

    ex_window_prop_string_set(EwinGetClientXwin(ewin), ea_m.ENL_WIN_BORDER,
                              BorderGetName(ewin->normal_border));

    if (EDebug(EDBUG_TYPE_SNAPS))
        Eprintf("Snap set einf  %#x: %4d+%4d %4dx%4d: %s\n",
                EwinGetClientXwin(ewin), ewin->client.x, ewin->client.y,
                ewin->client.w, ewin->client.h, EwinGetTitle(ewin));
}

void
EHintsGetInfo(EWin *ewin)
{
    char           *str;
    int             num;
    int             c[ENL_DATA_ITEMS + 1];
    unsigned int    flags[2];
    EWinInfoFlags   f;

    if (EwinIsInternal(ewin))
        return;

    /* Fix window position on hidden desks if restarting after a crash */
    if (desk_info & (1U << EoGetDeskNum(ewin)))
        ewin->client.x -= WinGetW(VROOT);

    num =
        ex_window_prop_card32_get(EwinGetClientXwin(ewin), ea_m.ENL_WIN_DATA,
                                  (unsigned int *)c, ENL_DATA_ITEMS + 1);
    if (num < 0)
        return;

    ewin->state.identified = 1;
    ewin->state.placed = 1;

    if (num < 2)
        return;

    f.all = c[0];
    if (f.b.version != ENL_DATA_VERSION)
        return;
    ewin->icccm.start_iconified = f.b.iconified;
    ewin->state.docked = f.b.docked;

    flags[0] = c[1];
    flags[1] = c[2];
    EwinFlagsDecode(ewin, flags);

    if (num == ENL_DATA_ITEMS)
    {
        ewin->save_max.x = c[3];
        ewin->save_max.y = c[4];
        ewin->save_max.w = c[5];
        ewin->save_max.h = c[6];
        ewin->save_fs.x = c[7];
        ewin->save_fs.y = c[8];
        ewin->save_fs.w = c[9];
        ewin->save_fs.h = c[10];
        ewin->save_fs.layer = c[11];
    }

    str =
        ex_window_prop_string_get(EwinGetClientXwin(ewin), ea_m.ENL_WIN_BORDER);
    if (str)
        EwinBorderSetInitially(ewin, str);
    Efree(str);

    if (EDebug(EDBUG_TYPE_SNAPS))
        Eprintf("Snap get einf  %#x: %4d+%4d %4dx%4d: %s\n",
                EwinGetClientXwin(ewin), ewin->client.x, ewin->client.y,
                ewin->client.w, ewin->client.h, EwinGetTitle(ewin));
}

static void
EHintsSetDeskInfo(void)
{
    int             i, ax, ay, n_desks;
    unsigned int    c[2], desk_pos_info;

    if (!DesksGetCurrent())     /* Quit if current desk isn't assigned yet */
        return;

    n_desks = DesksGetNumber();
    if (n_desks <= 0)
        return;

    desk_pos_info = 0;
    for (i = 0; i < n_desks; i++)
    {
        Desk           *dsk = DeskGet(i);

        DeskGetArea(dsk, &ax, &ay);
        if (EoGetY(dsk) == 0 && EoGetX(dsk) == WinGetW(VROOT))
            desk_pos_info |= 1U << i;   /* Desk is "hidden" */
    }

    c[0] = DesksGetCurrentNum();
    c[1] = Mode.wm.exiting ? 0 : desk_pos_info;
    ex_window_prop_card32_set(WinGetXwin(VROOT),
                              ea_m.ENL_INTERNAL_DESK_DATA, c, 2);

    if (Mode.wm.exiting && Mode.root.ext_pmap_valid)
    {
        HintsSetRootInfo(VROOT, Mode.root.ext_pmap, 0);
        ESetWindowBackgroundPixmap(VROOT, Mode.root.ext_pmap, 1);
    }
}

static void
EHintsSetAreaInfo(void)
{
    int             i, ax, ay, n_desks;
    unsigned int   *c;

    n_desks = DesksGetNumber();
    if (n_desks <= 0)
        return;

    c = EMALLOC(unsigned int, 2 * n_desks);
    if (!c)
        return;

    for (i = 0; i < n_desks; i++)
    {
        Desk           *dsk = DeskGet(i);

        DeskGetArea(dsk, &ax, &ay);
        c[(i * 2)] = ax;
        c[(i * 2) + 1] = ay;
    }

    ex_window_prop_card32_set(WinGetXwin(VROOT),
                              ea_m.ENL_INTERNAL_AREA_DATA, c, 2 * n_desks);

    Efree(c);
}

void
EHintsGetDeskInfo(void)
{
    unsigned int   *c;
    int             num, i, n_desks;

    n_desks = DesksGetNumber();
    c = EMALLOC(unsigned int, 2 * n_desks);

    if (!c)
        return;

    num = ex_window_prop_card32_get(WinGetXwin(VROOT),
                                    ea_m.ENL_INTERNAL_AREA_DATA,
                                    c, 2 * n_desks);
    if (num > 0)
    {
        for (i = 0; i < (num / 2); i++)
            DeskSetArea(DeskGet(i), c[(i * 2)], c[(i * 2) + 1]);
    }

    num = ex_window_prop_card32_get(WinGetXwin(VROOT),
                                    ea_m.ENL_INTERNAL_DESK_DATA, c, 2);
    if (num > 0)
    {
        DesksSetCurrent(DeskGet(c[0]));
        if (num > 1)
            desk_info = c[1];
    }
    else
    {
        /* Used to test if we should run cmd_init */
        Mode.wm.session_start = 1;
    }

    Efree(c);
}

void
EHintsSetInfoOnAll(void)
{
    int             i, num;
    EWin           *const *lst;

    if (EDebug(EDBUG_TYPE_SESSION))
        Eprintf("%s\n", __func__);

    lst = EwinListGetAll(&num);
    for (i = 0; i < num; i++)
        if (!EwinIsInternal(lst[i]))
            EHintsSetInfo(lst[i]);

    EHintsSetDeskInfo();
}

/*
 * Selections.
 */

struct _selection {
    EX_Atom         atom;
    EX_Time         time;
    Win             win;
    EventCallbackFunc *func;
    void           *data;
};

ESelection     *
SelectionAcquire(const char *name, EventCallbackFunc *func, void *data)
{
    ESelection     *sel;
    char            buf[128];
    EX_Window       powner;
    int             i;

    sel = ECALLOC(ESelection, 1);
    if (!sel)
        return sel;

    Esnprintf(buf, sizeof(buf), "%s%d", name, Dpy.screen);

    sel->atom = ex_atom_get(buf);
    sel->time = EGetTimestamp();
    sel->win = ECreateEventWindow(VROOT, -100, -100, 1, 1);

    sel->func = func;
    sel->data = data;

    /* Get current selection owner */
    powner = XGetSelectionOwner(disp, sel->atom);

    XSetSelectionOwner(disp, sel->atom, WinGetXwin(sel->win), sel->time);
    if (XGetSelectionOwner(disp, sel->atom) != WinGetXwin(sel->win))
        goto bail;

    if (powner != NoXID)
    {
        /* Wait for previous selection owner to be destroyed (max 200 ms) */
        for (i = 2; i > 0; i--)
        {
            if (EDebug(EDBUG_TYPE_SELECTION))
                Eprintf("Check old %s owner %#x\n", buf, powner);
            if (!EXWindowOk(powner))
                break;
            SleepUs(100000);
        }
        if (i == 0)
            goto bail;
    }

    if (sel->func)
    {
        ESelectInput(sel->win, SubstructureNotifyMask);
        EventCallbackRegister(sel->win, sel->func, sel->data);
    }

    ex_client_message32_send(WinGetXwin(VROOT), ea_m.MANAGER,
                             StructureNotifyMask, CurrentTime, sel->atom,
                             WinGetXwin(sel->win), 0, 0);

    if (EDebug(EDBUG_TYPE_SELECTION))
        Eprintf("Window %#x is now %s owner, time=%u\n",
                WinGetXwin(sel->win), buf, sel->time);

    return sel;

  bail:
    DialogOK(_("Selection Error!"), _("Could not acquire selection: %s"), buf);
    EDestroyWindow(sel->win);
    Efree(sel);
    return NULL;
}

void
SelectionRelease(ESelection *sel)
{
    if (!sel)
        return;

    if (EDebug(EDBUG_TYPE_SELECTION))
        Eprintf("Window %#x is no longer %s owner\n",
                WinGetXwin(sel->win), XGetAtomName(disp, sel->atom));

    XSetSelectionOwner(disp, sel->atom, NoXID, sel->time);
    if (sel->func)
    {
        EventCallbackUnregister(sel->win, sel->func, sel->data);
    }
    EDestroyWindow(sel->win);
    Efree(sel);
}
