/*
 * Copyright (C) 2000-2007 Carsten Haitzler, Geoff Harrison and various contributors
 * Copyright (C) 2004-2021 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 "cursors.h"
#include "eobj.h"
#include "events.h"
#include "ewins.h"
#include "grabs.h"
#include "hiwin.h"
#include "iclass.h"
#include "xwin.h"

#define DEBUG_HIWIN 0
#if DEBUG_HIWIN
#define Dprintf(fmt, ...) Eprintf("%s: " fmt, __func__, __VA_ARGS__)
#else
#define Dprintf(fmt...)
#endif

struct _hiwin {
    EObj            o;
    EWin           *ewin;
    int             zoom;
    int             xo, yo, wo, ho;
    void            (*evcb)(Win win, XEvent * ev, void *data);
    void           *data;
    char            animate;
    EImage         *im;
};

typedef struct {
    void            (*init)(Hiwin * phi);
    void            (*draw)(Hiwin * phi);
    void            (*fini)(Hiwin * phi, int shown);
} HiwinRender;

static ImageClass *hiwin_ic = NULL;

/* TBD: Move elsewhere? */
static EImage  *
EobjGetImage(EObj *eo, EX_Drawable draw)
{
    EImage         *im;
    EX_Pixmap       mask;

    mask = EWindowGetShapePixmap(EobjGetWin(eo));
    im = EImageGrabDrawable(draw, mask, 0, 0, EobjGetW(eo), EobjGetH(eo), 0);
    if (mask)
        EFreePixmap(mask);

    return im;
}

static void
HiwinRenderImageInit(Hiwin *phi)
{
    EWin           *ewin = phi->ewin;
    EX_Pixmap       pmap;

    pmap = EoGetPixmap(ewin);
    if (pmap)
    {
        phi->im = EobjGetImage(EoObj(ewin), pmap);
        /* Skip zoom effect if composite is active */
        phi->animate = 0;
    }
    else if (phi->zoom > 2 && EwinIsOnScreen(ewin))
    {
        phi->im = EobjGetImage(EoObj(ewin), EoGetXwin(ewin));
    }
    else
    {
        phi->im =
            EImageGrabDrawable(ewin->mini_pmm.pmap, ewin->mini_pmm.mask, 0, 0,
                               ewin->mini_pmm.w, ewin->mini_pmm.h, 0);
    }

    ESetWindowBackgroundPixmap(EoGetWin(phi), NoXID, 0);

    /* Reset shape */
    EShapeSetMask(EoGetWin(phi), 0, 0, NoXID);
    EoShapeUpdate(phi, 0);
}

static void
HiwinRenderImageDrawX(Hiwin *phi, EX_Drawable draw __UNUSED__)
{
    EImageApplyToWin(phi->im, EoGetWin(phi), EIMAGE_ANTI_ALIAS,
                     EoGetW(phi), EoGetH(phi));
    EoShapeUpdate(phi, 0);
}

static void
HiwinRenderImageDraw(Hiwin *phi)
{
    HiwinRenderImageDrawX(phi, EoGetXwin(phi));
}

static void
HiwinRenderImageFini(Hiwin *phi, int shown)
{
    if (shown)
        HiwinRenderImageDraw(phi);
    EImageDecache(phi->im);
    phi->im = NULL;
}

#if USE_COMPOSITE
static void
HiwinRenderImageUpdate(Hiwin *phi)
{
    EX_Pixmap       pmap;
    EWin           *ewin = phi->ewin;

    pmap = EoGetPixmap(ewin);
    if (pmap == NoXID)
        return;

    phi->im = EobjGetImage(EoObj(ewin), pmap);
    HiwinRenderImageDraw(phi);
    EImageDecache(phi->im);
    phi->im = NULL;
}
#endif

static const HiwinRender HiwinRenderImage = {
    HiwinRenderImageInit, HiwinRenderImageDraw, HiwinRenderImageFini
};

static void
HiwinRenderIclassInit(Hiwin *phi __UNUSED__)
{
}

static void
HiwinRenderIclassDraw(Hiwin *phi)
{
    ImageclassApply(hiwin_ic, EoGetWin(phi), 0, 0, STATE_NORMAL);
}

static void
HiwinRenderIclassFini(Hiwin *phi, int shown)
{
    if (shown)
        HiwinRenderIclassDraw(phi);
}

static const HiwinRender HiwinRenderIclass = {
    HiwinRenderIclassInit, HiwinRenderIclassDraw, HiwinRenderIclassFini
};

static void
HiwinRenderPixmapInit(Hiwin *phi __UNUSED__)
{
}

static void
HiwinRenderPixmapDrawX(Hiwin *phi, EX_Drawable draw)
{
    EXPaintRectangle(draw, 0, 0, EoGetW(phi), EoGetH(phi),
                     Dpy.pixel_black, Dpy.pixel_white);
}

static void
HiwinRenderPixmapDraw(Hiwin *phi)
{
    HiwinRenderPixmapDrawX(phi, EoGetXwin(phi));
    EClearWindow(EoGetWin(phi));
}

static void
HiwinRenderPixmapFini(Hiwin *phi, int shown)
{
    EX_Pixmap       pmap;

    if (shown)
    {
        pmap = EGetWindowBackgroundPixmap(EoGetWin(phi));
        HiwinRenderPixmapDrawX(phi, pmap);
        EClearWindow(EoGetWin(phi));
    }
}

static const HiwinRender HiwinRenderPixmap = {
    HiwinRenderPixmapInit, HiwinRenderPixmapDraw, HiwinRenderPixmapFini
};

static void
HiwinEvent(Win win, XEvent *ev, void *prm)
{
    Hiwin          *phi = (Hiwin *) prm;

    if (phi->evcb)
        phi->evcb(win, ev, phi->data);
}

#if USE_COMPOSITE
static void
HiwinEwinEvent(Win win __UNUSED__, XEvent *ev, void *prm)
{
    Hiwin          *phi = (Hiwin *) prm;

    Dprintf("type=%d %s\n", ev->type, EwinGetTitle(phi->ewin));

    switch (ev->type)
    {
    case EX_EVENT_DAMAGE_NOTIFY:
        HiwinRenderImageUpdate(phi);
        break;
    }
}
#endif

Hiwin          *
HiwinCreate(void)
{
    Hiwin          *phi;

    phi = ECALLOC(Hiwin, 1);
    if (!phi)
        return NULL;

    EoInit(phi, EOBJ_TYPE_MISC, NoXID, 0, 0, 3, 3, 1, "HiWin");
    EoSetFade(phi, 1);
    EoSetFloating(phi, 1);
    EoSetLayer(phi, 19);
    EventCallbackRegister(EoGetWin(phi), HiwinEvent, phi);
    ESelectInput(EoGetWin(phi),
                 ButtonPressMask | ButtonReleaseMask | PointerMotionMask |
                 EnterWindowMask | LeaveWindowMask);

    return phi;
}

void
HiwinSetGeom(Hiwin *phi, int x, int y, int w, int h)
{
    phi->xo = x;
    phi->yo = y;
    phi->wo = w;
    phi->ho = h;
}

void
HiwinInit(Hiwin *phi, EWin *ewin, EObj *parent)
{
    if (ewin == phi->ewin)
        return;

#if USE_COMPOSITE
    if (phi->ewin)
    {
        Dprintf("Unregister %s\n", EwinGetTitle(phi->ewin));
        EventCallbackUnregister(EoGetWin(phi->ewin), HiwinEwinEvent, phi);
    }
#endif

    phi->ewin = ewin;
    if (parent && EobjGetWin(parent) != EoGetParent(phi))
        EoReparent(phi, parent, 0, 0);

#if USE_COMPOSITE
    if (phi->ewin)
    {
        Dprintf("Register %s\n", EwinGetTitle(phi->ewin));
        EventCallbackRegister(EoGetWin(phi->ewin), HiwinEwinEvent, phi);
    }
#endif

    if (!hiwin_ic)
        hiwin_ic = ImageclassFind("PAGER_WIN", 0);
}

void
HiwinSetCallback(Hiwin *phi, void (*func)(Win win, XEvent *ev, void *data),
                 void *data)
{
    phi->evcb = func;
    phi->data = data;
}

void
HiwinGetXY(Hiwin *phi, int *x, int *y)
{
    *x = EoGetX(phi);
    *y = EoGetY(phi);
}

void
HiwinMove(Hiwin *phi, int x, int y)
{
    EoMove(phi, x, y);
}

EWin           *
HiwinGetEwin(Hiwin *phi, int check)
{
    EWin           *ewin;

    if (!phi)
        return NULL;
    if (!check || !phi->ewin)
        return phi->ewin;

    ewin = EwinFindByPtr(phi->ewin);

    return ewin;
}

void
HiwinHide(Hiwin *phi)
{
    if (!phi)
        return;

    if (EoIsShown(phi))
    {
        GrabPointerRelease();
        HiwinInit(phi, NULL, NULL);
        EoUnmap(phi);
    }

    phi->data = NULL;
    phi->evcb = NULL;
}

void
HiwinShow(Hiwin *phi, EWin *ewin, int zoom, int confine)
{
    const HiwinRender *pz;
    int             x, y, w, h, zold;
    int             xx, yy, ww, hh, i, i1, i2, step, px, py;

    if (!ewin)
        ewin = phi->ewin;
    if (!ewin)
        return;

    if (ewin->mini_pmm.pmap)
        pz = &HiwinRenderImage;
    else if (hiwin_ic)
        pz = &HiwinRenderIclass;
    else
        pz = &HiwinRenderPixmap;

    if (phi->zoom <= 2 && zoom == 2)
    {
        phi->zoom = 1;

        x = phi->xo + phi->wo / 2;
        y = phi->yo + phi->ho / 2;
        w = zoom * phi->wo;
        h = zoom * phi->ho;

        step = zoom - phi->zoom;
    }
    else if (zoom <= 2)
    {
        x = phi->xo + phi->wo / 2;
        y = phi->yo + phi->ho / 2;
        w = zoom * phi->wo;
        h = zoom * phi->ho;
        step = 0;
    }
    else
    {
        x = WinGetW(VROOT) / 2;
        y = WinGetH(VROOT) / 2;
        w = zoom * EoGetW(phi->ewin) / 4;
        h = zoom * EoGetH(phi->ewin) / 4;
        step = 0;
    }

#if DEBUG_HIWIN
    Eprintf("%s: %s zoom=%d->%d step=%d %d,%d %dx%d\n", __func__,
            EoGetName(ewin), phi->zoom, zoom, step, x, y, w, h);
#endif

    zold = phi->zoom;
    phi->zoom = zoom;
    phi->animate = 1;

    pz->init(phi);

    if (step && phi->animate)
    {
        x = phi->xo;
        y = phi->yo;
        w = phi->wo;
        h = phi->ho;

        if (w > h)
        {
            i1 = w * zold;
            i2 = w * zoom;
        }
        else
        {
            i1 = h * zold;
            i2 = h * zoom;
        }

        for (i = i1; i != i2; i += step)
        {
            int             on_screen;

            if (w > h)
            {
                ww = i;
                hh = (ww * h) / w;
            }
            else
            {
                hh = i;
                ww = (hh * w) / h;
            }
            xx = x + ((w - ww) / 2);
            yy = y + ((h - hh) / 2);
            EoMoveResize(phi, xx, yy, ww, hh);
            EoMap(phi, 0);
            pz->draw(phi);

            on_screen = EQueryPointer(NULL, &px, &py, NULL, NULL);
            if (!on_screen ||
                (px < x) || (py < y) || (px >= (x + w)) || (py >= (y + h)))
            {
                pz->fini(phi, 0);
                HiwinHide(phi);
                return;
            }
        }
    }
    else
    {
        EoMoveResize(phi, x - w / 2, y - h / 2, w, h);
        EoMap(phi, 0);
    }

    GrabPointerSet(EoGetWin(phi), ECSR_ACT_MOVE, confine);

    pz->fini(phi, 1);
}
