/*
 * 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 <math.h>
#include <X11/Xlib.h>
#if USE_XRENDER
#define FX_USE_RENDER 1
#include <X11/extensions/Xrender.h>
#endif

#include "E.h"
#include "animation.h"
#include "desktops.h"
#include "dialog.h"
#include "ecompmgr.h"
#include "eimage.h"
#include "emodule.h"
#include "settings.h"
#include "util.h"
#include "xwin.h"

#define M_2PI_F ((float)(2 * M_PI))

#define FX_OP_ENABLE  1         /* Enable, start */
#define FX_OP_DISABLE 2         /* Disable, stop */
#define FX_OP_START   3         /* Start (if enabled) */
#define FX_OP_PAUSE   4
#define FX_OP_DESK    5

typedef struct {
    const char     *name;
    void            (*init_func)(const char *name);
    void            (*ops_func)(int op);
    char            enabled;
    char            active;
} FXHandler;

typedef struct {
    Win             win;
    EX_Window       root;
    EX_Pixmap       above;
    int             count;
    float           incv, inch, incx;
#if FX_USE_RENDER
    EX_Picture      pict_buf;
    EX_Picture      pict_win;
#if USE_COMPOSITE
    EX_Picture      pict_win_clip;
#endif
#else
    GC              gc1;
#endif
} FXData;

static void
_FxSetup(FXData *d, unsigned int height)
{
    EObj           *bgeo;

    if (!d->above)
    {
        bgeo = DeskGetBackgroundObj(DesksGetCurrent());

        d->win = EobjGetWin(bgeo);
#if USE_COMPOSITE
        if (ECompMgrIsActive() && !Mode.wm.window)
            d->root = ECompMgrRootWin();
        else
#endif
            d->root = EobjGetXwin(bgeo);
        d->above = ECreatePixmap(d->win, WinGetW(VROOT), height, 0);

#if FX_USE_RENDER
        XRenderPictureAttributes pa;

        d->pict_buf = EPictureCreate(NULL, d->above);
        d->pict_win = EPictureCreate(NULL, d->root);
#if USE_COMPOSITE
        d->pict_win_clip = EPictureCreate(NULL, d->root);
#endif
        pa.subwindow_mode = ClipByChildren;
        XRenderChangePicture(disp, d->pict_win, CPSubwindowMode, &pa);
#else
        XGCValues       xgcv;

        xgcv.subwindow_mode = ClipByChildren;
        if (!d->gc1)
            d->gc1 = EXCreateGC(d->root, GCSubwindowMode, &xgcv);
#endif
    }

#if USE_COMPOSITE
#if FX_USE_RENDER
    EPictureSetClip(d->pict_win_clip, ECompMgrChildClipRegion());
#else
    /* As of composite 0.4 we need to set the clip region */
    EGCSetClip(d->gc1, ECompMgrChildClipRegion());
#endif
#endif
}

static void
_FxCleanup(FXData *d, int h)
{
    if (h == 0)
    {
        EFreePixmap(d->above);
        d->above = NoXID;
        d->count = 0;
    }
    else
    {
#if FX_USE_RENDER
        EPictureDestroy(d->pict_buf);
        EPictureDestroy(d->pict_win);
#if USE_COMPOSITE
        EPictureDestroy(d->pict_win_clip);
#endif
#else
        EXFreeGC(d->gc1);
#endif

        EClearArea(d->win, 0, WinGetH(VROOT) - h, WinGetW(VROOT), h);
#if USE_COMPOSITE
        ECompMgrDamageArea(0, WinGetH(VROOT) - h, WinGetW(VROOT), h);
#endif
    }
}

static void
_FxCopyArea(FXData *d, int out, int src_x, int src_y, int dst_x, int dst_y,
            unsigned int width, unsigned int height)
{
    EX_ID           src, dst;

#if FX_USE_RENDER
    if (out)
    {
        src = d->pict_buf;
#if USE_COMPOSITE
        dst = d->pict_win_clip;
#else
        dst = d->pict_win;
#endif
    }
    else
    {
        src = d->pict_win;
        dst = d->pict_buf;
    }

    XRenderComposite(disp, PictOpSrc, src, NoXID, dst,
                     src_x, src_y, 0, 0, dst_x, dst_y, width, height);
#else
    if (out)
    {
        src = d->above;
        dst = d->root;

        EXCopyAreaGC(src, dst, d->gc1, src_x, src_y, width, height,
                     dst_x, dst_y);
    }
    else
    {
        src = d->root;
        dst = d->above;

        EXCopyArea(src, dst, src_x, src_y, width, height, dst_x, dst_y);
    }
#endif
}

/****************************** RIPPLES *************************************/

#define FX_RIPPLE_WATERH 64

static Animator *fx_ripple = NULL;

static int
FX_ripple_timeout(EObj *eo __UNUSED__, int run __UNUSED__, void *state)
{
    FXData         *d = (FXData *) state;
    int             y;

    _FxSetup(d, FX_RIPPLE_WATERH * 2);

    if (d->count == 0)
        _FxCopyArea(d, 0, 0, WinGetH(VROOT) - (FX_RIPPLE_WATERH * 3), 0, 0,
                    WinGetW(VROOT), FX_RIPPLE_WATERH * 2);

    d->count++;
    if (d->count > 32)
        d->count = 0;

    d->incv += 0.40f;
    if (d->incv > M_2PI_F)
        d->incv = 0;
    d->inch += 0.32f;
    if (d->inch > M_2PI_F)
        d->inch = 0;

    for (y = 0; y < FX_RIPPLE_WATERH; y++)
    {
        float           a, p;
        int             xoff, yoff, yy;

        p = (float)(FX_RIPPLE_WATERH - y) / (float)FX_RIPPLE_WATERH;

        a = p * p * 48 + d->incv;
        yoff = y + (int)(sinf(a) * 7) + 8;      /* 0:63 + -7:7 + 8 = 1:78 */
        yy = (FX_RIPPLE_WATERH * 2) - yoff;     /* 128 - 1:78 = 127:50    */

        a = p * p * 64 + d->inch;
        xoff = (int)(sinf(a) * 10 * (1 - p));

        _FxCopyArea(d, 1, 0, yy, xoff, WinGetH(VROOT) - FX_RIPPLE_WATERH + y,
                    WinGetW(VROOT), 1);
    }

    return 4;
}

static void
FX_Ripple_Init(const char *name __UNUSED__)
{
    FXData          fxd;

    memset(&fxd, 0, sizeof(fxd));

    fx_ripple = AnimatorAdd(NULL, ANIM_FX_RIPPLES, FX_ripple_timeout, -1, 0,
                            sizeof(fxd), &fxd);
}

static void
FX_Ripple_Ops(int op)
{
    FXData         *d;

    d = (FXData *) AnimatorGetData(fx_ripple);
    if (!d)
        return;

    _FxCleanup(d, 0);

    if (op != FX_OP_DISABLE)
        return;

    _FxCleanup(d, FX_RIPPLE_WATERH);

    AnimatorDel(NULL, fx_ripple);
    fx_ripple = NULL;
}

/****************************** WAVES ***************************************/
/* by tsade :)                                                              */
/****************************************************************************/

#define FX_WAVE_WATERH 64
#define FX_WAVE_WATERW 64
#define FX_WAVE_DEPTH  10
#define FX_WAVE_CROSSPERIOD 0.42f

static Animator *fx_waves = NULL;

static int
FX_Wave_timeout(EObj *eo __UNUSED__, int run __UNUSED__, void *state)
{
    FXData         *d = (FXData *) state;
    float           incx2;
    int             y;

    _FxSetup(d, FX_WAVE_WATERH * 2);

    /* On the zero, grab the desktop again. */
    if (d->count == 0)
        _FxCopyArea(d, 0, 0, WinGetH(VROOT) - (FX_WAVE_WATERH * 3), 0, 0,
                    WinGetW(VROOT), FX_WAVE_WATERH * 2);

    /* Increment and roll the counter */
    d->count++;
    if (d->count > 32)
        d->count = 0;

    /* Increment and roll some other variables */
    d->incv += 0.40f;
    if (d->incv > M_2PI_F)
        d->incv = 0;

    d->inch += 0.32f;
    if (d->inch > M_2PI_F)
        d->inch = 0;

    d->incx += 0.32f;
    if (d->incx > M_2PI_F)
        d->incx = 0;

    /* Go through the bottom couple (FX_WAVE_WATERH) lines of the window */
    for (y = 0; y < FX_WAVE_WATERH; y++)
    {
        /* Variables */
        float           a, p;
        int             xoff, yoff, yy;
        int             x;

        /* Figure out the side-to-side movement */
        p = (float)(FX_WAVE_WATERH - y) / (float)FX_WAVE_WATERH;

        a = p * p * 48 + d->incv;
        yoff = y + (int)(sinf(a) * 7) + 8;      /* 0:63 + -7:7 + 8 = 1:78 */
        yy = (FX_WAVE_WATERH * 2) - yoff;       /* 128 - 1:78 = 127:50    */

        a = p * p * 64 + d->inch;
        xoff = (int)(sinf(a) * 10 * (1 - p));

        /* Set up the next part */
        incx2 = d->incx;

        /* Go through the width of the screen, in block sizes */
        for (x = 0; x < WinGetW(VROOT); x += FX_WAVE_WATERW)
        {
            /* Variables */
            int             sx;

            /* Add something to incx2 and roll it */
            incx2 += FX_WAVE_CROSSPERIOD;

            if (incx2 > M_2PI_F)
                incx2 = 0;

            /* Figure it out */
            sx = (int)(sinf(incx2) * FX_WAVE_DEPTH);

            /* Display this block */
            _FxCopyArea(d, 1, x, yy, xoff + x,
                        WinGetH(VROOT) - FX_WAVE_WATERH + y + sx,
                        FX_WAVE_WATERW, 1);
        }
    }

    return 4;
}

static void
FX_Waves_Init(const char *name __UNUSED__)
{
    FXData          fxd;

    memset(&fxd, 0, sizeof(fxd));

    fx_waves = AnimatorAdd(NULL, ANIM_FX_WAVES, FX_Wave_timeout, -1, 0,
                           sizeof(fxd), &fxd);
}

static void
FX_Waves_Ops(int op)
{
    FXData         *d;

    d = (FXData *) AnimatorGetData(fx_waves);
    if (!d)
        return;

    _FxCleanup(d, 0);

    if (op != FX_OP_DISABLE)
        return;

    _FxCleanup(d, 2 * FX_WAVE_WATERH + FX_WAVE_DEPTH);

    AnimatorDel(NULL, fx_waves);
    fx_waves = NULL;
}

/****************************************************************************/

#define fx_rip fx_handlers[0]
#define fx_wav fx_handlers[1]

static FXHandler fx_handlers[] = {
    { "ripples", FX_Ripple_Init, FX_Ripple_Ops, 0, 0 },
    { "waves", FX_Waves_Init, FX_Waves_Ops, 0, 0 },
};

/****************************** Effect handlers *****************************/

static void
FX_Op(FXHandler *fxh, int op)
{
    switch (op)
    {
    case FX_OP_ENABLE:
        if (fxh->enabled)
            break;
        fxh->enabled = 1;
        goto do_start;

    case FX_OP_DISABLE:
        if (!fxh->enabled)
            break;
        fxh->enabled = 0;
        goto do_stop;

    case FX_OP_START:
        if (!fxh->enabled)
            break;
      do_start:
        if (fxh->active)
            break;
        fxh->init_func(fxh->name);
        fxh->active = 1;
        break;

    case FX_OP_PAUSE:
        if (!fxh->enabled)
            break;
      do_stop:
        if (!fxh->active)
            break;
        fxh->ops_func(FX_OP_DISABLE);
        fxh->active = 0;
        break;

    case FX_OP_DESK:
        if (!fxh->enabled)
            break;
        fxh->ops_func(FX_OP_DESK);
        break;
    }
}

static void
FX_OpForEach(int op)
{
    unsigned int    i;

    for (i = 0; i < E_ARRAY_SIZE(fx_handlers); i++)
        FX_Op(&fx_handlers[i], op);
}

static void
FxCfgFunc(void *item __UNUSED__, const char *value)
{
    FXHandler      *fxh = NULL;

    if (item == &fx_rip.enabled)
        fxh = &fx_rip;
    else if (item == &fx_wav.enabled)
        fxh = &fx_wav;
    if (!fxh)
        return;

    FX_Op(fxh, atoi(value) ? FX_OP_ENABLE : FX_OP_DISABLE);
}

/****************************************************************************/

/*
 * Fx Module
 */

static void
FxSighan(int sig, void *prm __UNUSED__)
{
    switch (sig)
    {
    case ESIGNAL_START:
        FX_OpForEach(FX_OP_START);
        break;
    case ESIGNAL_DESK_SWITCH_START:
        break;
    case ESIGNAL_DESK_SWITCH_DONE:
        FX_OpForEach(FX_OP_DESK);
        break;
    case ESIGNAL_ANIMATION_SUSPEND:
        FX_OpForEach(FX_OP_PAUSE);
        break;
    case ESIGNAL_ANIMATION_RESUME:
        FX_OpForEach(FX_OP_START);
        break;
    }
}

#if ENABLE_DIALOGS
static char     tmp_effect_ripples;
static char     tmp_effect_waves;

static void
_DlgApplyFx(Dialog *d __UNUSED__, int val __UNUSED__, void *data __UNUSED__)
{
    FX_Op(&fx_rip, tmp_effect_ripples ? FX_OP_ENABLE : FX_OP_DISABLE);
    FX_Op(&fx_wav, tmp_effect_waves ? FX_OP_ENABLE : FX_OP_DISABLE);

    autosave();
}

static void
_DlgFillFx(Dialog *d __UNUSED__, DItem *table, void *data __UNUSED__)
{
    DItem          *di;

    tmp_effect_ripples = fx_rip.enabled;
    tmp_effect_waves = fx_wav.enabled;

    DialogItemTableSetOptions(table, 1, 0, 0, 0);

    /* Effects */
    di = DialogAddItem(table, DITEM_TEXT);
    DialogItemSetText(di, _("Effects"));
    di = DialogAddItem(table, DITEM_CHECKBUTTON);
    DialogItemSetText(di, _("Ripples"));
    DialogItemCheckButtonSetPtr(di, &tmp_effect_ripples);
    di = DialogAddItem(table, DITEM_CHECKBUTTON);
    DialogItemSetText(di, _("Waves"));
    DialogItemCheckButtonSetPtr(di, &tmp_effect_waves);
}

const DialogDef DlgFx = {
    "CONFIGURE_FX",
    N_("FX"), N_("Special FX Settings"),
    0,
    SOUND_SETTINGS_FX,
    "pix/fx.png",
    N_("Enlightenment Special Effects\n" "Settings Dialog"),
    _DlgFillFx,
    DLG_OAC, _DlgApplyFx, NULL
};
#endif                          /* ENABLE_DIALOGS */

#define CFR_FUNC_BOOL(conf, name, dflt, func) \
    { #name, &conf, ITEM_TYPE_BOOL, dflt, func }

static const CfgItem FxCfgItems[] = {
    CFR_FUNC_BOOL(fx_handlers[0].enabled, ripples.enabled, 0, FxCfgFunc),
    CFR_FUNC_BOOL(fx_handlers[1].enabled, waves.enabled, 0, FxCfgFunc),
};

/*
 * Module descriptor
 */
extern const EModule ModEffects;

const EModule   ModEffects = {
    "effects", "fx",
    FxSighan,
    { 0, NULL },
    MOD_ITEMS(FxCfgItems)
};
