/*
 * Copyright (C) 2000-2007 Carsten Haitzler, Geoff Harrison and various contributors
 * Copyright (C) 2004-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.
 */
#include "config.h"

#include <X11/Xlib.h>

#include "E.h"
#include "backgrounds.h"
#include "conf.h"
#include "desktops.h"
#include "eimage.h"
#include "emodule.h"
#include "iclass.h"
#include "list.h"
#include "tclass.h"
#include "xwin.h"

#define ENABLE_DESTROY 0        /* Broken */

struct _imagestate {
    char           *im_file;
    char           *real_file;
    char            got_colors;
    char            unloadable;
    char            transparent;
    char            pixmapfillstyle;
    char            bevelstyle;
    char            rotate;
    EImage         *im;
    EImageBorder   *border;
    unsigned int    bg, hi, lo, hihi, lolo;
    unsigned int    bg_pixel, hi_pixel, lo_pixel, hihi_pixel, lolo_pixel;
};

typedef struct {
    ImageState     *normal;
    ImageState     *hilited;
    ImageState     *clicked;
    ImageState     *disabled;
} ImageStateArray;

struct _imageclass {
    dlist_t         list;
    char           *name;
    ImageStateArray norm, active, sticky, sticky_active;
    EImageBorder    padding;
    unsigned int    ref_count;
};

static          LIST_HEAD(iclass_list);

static ImageClass *ImageclassGetFallback(void);

EImage         *
ThemeImageLoad(const char *file)
{
    EImage         *im;
    char           *f;

    if (!file)
        return NULL;

    if (file[0] == '/')
    {
        im = EImageLoad(file);
        return im;
    }

    f = ThemeFileFind(file, FILE_TYPE_IMAGE);
    if (f)
    {
        im = EImageLoad(f);
        Efree(f);
        return im;
    }

    return NULL;
}

static void
ImagestateColorsSetGray(ImageState *is,
                        unsigned int hihi, unsigned int hi,
                        unsigned int bg, unsigned int lo, unsigned int lolo)
{
    if (!is)
        return;
    COLOR32_FROM_RGB(is->hihi, hihi, hihi, hihi);
    COLOR32_FROM_RGB(is->hi, hi, hi, hi);
    COLOR32_FROM_RGB(is->bg, bg, bg, bg);
    COLOR32_FROM_RGB(is->lo, lo, lo, lo);
    COLOR32_FROM_RGB(is->lolo, lolo, lolo, lolo);
}

static ImageState *
ImagestateCreate(const char *file)
{
    ImageState     *is;

    is = ECALLOC(ImageState, 1);
    if (!is)
        return NULL;

    is->pixmapfillstyle = FILL_STRETCH;
    ImagestateColorsSetGray(is, 255, 200, 160, 120, 64);
    is->bevelstyle = BEVEL_NONE;
    is->im_file = Estrdup(file);

    return is;
}

static ImageState *
ImagestateCreateX(unsigned int hihi, unsigned int hi,
                  unsigned int lo, unsigned int lolo,
                  unsigned int bg_r, unsigned int bg_g, unsigned int bg_b)
{
    ImageState     *is;

    is = ImagestateCreate(NULL);
    if (!is)
        return NULL;

    ImagestateColorsSetGray(is, hihi, hi, 0, lo, lolo);
    COLOR32_FROM_RGB(is->bg, bg_r, bg_g, bg_b);
    is->bevelstyle = BEVEL_AMIGA;

    return is;
}

static void
ImagestateDestroy(ImageState *is)
{
    if (!is)
        return;

    Efree(is->im_file);
    Efree(is->real_file);

    if (is->im)
        EImageFree(is->im);

    Efree(is->border);

    Efree(is);
}

static ImageState *
ImagestateSet(ImageState **isp, const char *name)
{
    ImageState     *is;

    is = ImagestateCreate(name);

    if (*isp)
        ImagestateDestroy(*isp);
    *isp = is;

    return is;
}

static void
ImagestateColorsAlloc(ImageState *is)
{
    if (!is || is->got_colors)
        return;

    is->bg_pixel = EAllocColor(WinGetCmap(VROOT), is->bg);
    is->hi_pixel = EAllocColor(WinGetCmap(VROOT), is->hi);
    is->lo_pixel = EAllocColor(WinGetCmap(VROOT), is->lo);
    is->hihi_pixel = EAllocColor(WinGetCmap(VROOT), is->hihi);
    is->lolo_pixel = EAllocColor(WinGetCmap(VROOT), is->lolo);

    is->got_colors = 1;
}

static void
ImagestateRealize(ImageState *is)
{
    if (!is)
        return;
    if (is->im)                 /* Image is already loaded */
        return;

    if (!is->real_file)
    {
        if (!is->im_file)
            return;             /* No file - quit */
        /* not loaded, load and setup */
        is->real_file = ThemeFileFind(is->im_file, FILE_TYPE_IMAGE);
    }
    if (is->real_file)
    {
        if (is->rotate && !Conf.memory_paranoia)
        {
            is->im = EImageLoadOrientate(is->real_file, is->rotate);
        }
        else
        {
            is->im = EImageLoad(is->real_file);
            if (is->im && is->rotate)
                EImageOrientate(is->im, is->rotate);
        }
    }
    if (!is->im)
    {
#define S(s) ((s) ? (s) : "(null)")
        Eprintf
            ("%s: Hmmm... is->im is NULL (im_file=%s real_file=%s)\n", __func__,
             S(is->im_file), S(is->real_file));
        EFREE_NULL(is->real_file);
        return;
    }

    EFREE_NULL(is->im_file);

    EImageCheckAlpha(is->im);

    if (is->border)
        EImageSetBorder(is->im, is->border);

#if 0                           /* To be implemented? */
    if (is->colmod)
    {
        Imlib_set_image_red_curve(pImlib_Context, is->im, is->colmod->red.map);
        Imlib_set_image_green_curve(pImlib_Context, is->im,
                                    is->colmod->green.map);
        Imlib_set_image_blue_curve(pImlib_Context, is->im,
                                   is->colmod->blue.map);
    }
#endif
}

static ImageClass *
ImageclassCreate(const char *name)
{
    ImageClass     *ic;

    ic = ECALLOC(ImageClass, 1);
    if (!ic)
        return NULL;

    LIST_PREPEND(ImageClass, &iclass_list, ic);

    ic->name = Estrdup(name);

    return ic;
}

#if ENABLE_DESTROY
static void
FreeImageStateArray(ImageStateArray *isa)
{
    ImagestateDestroy(isa->normal);
    ImagestateDestroy(isa->hilited);
    ImagestateDestroy(isa->clicked);
    ImagestateDestroy(isa->disabled);
}

static void
ImageclassDestroy(ImageClass *ic)
{
    if (!ic)
        return;

    if (ic->ref_count > 0)
    {
        DialogOK("ImageClass Error!", _("%u references remain"), ic->ref_count);
        return;
    }

    LIST_REMOVE(ImageClass, &iclass_list, ic);

    Efree(ic->name);

    FreeImageStateArray(&(ic->norm));
    FreeImageStateArray(&(ic->active));
    FreeImageStateArray(&(ic->sticky));
    FreeImageStateArray(&(ic->sticky_active));

    Efree(ic);
}
#endif                          /* ENABLE_DESTROY */

ImageClass     *
ImageclassAlloc(const char *name, int fallback)
{
    ImageClass     *ic;

    if (!name || !name[0])
        return NULL;

    ic = ImageclassFind(name, fallback);
    if (ic)
        ic->ref_count++;

    return ic;
}

void
ImageclassFree(ImageClass *ic)
{
    if (ic)
        ic->ref_count--;
}

const char     *
ImageclassGetName(ImageClass *ic)
{
    return (ic) ? ic->name : NULL;
}

EImageBorder   *
ImageclassGetPadding(ImageClass *ic)
{
    return (ic) ? &(ic->padding) : NULL;
}

static int
_ImageclassMatchName(const void *data, const void *match)
{
    return strcmp(((const ImageClass *)data)->name, (const char *)match);
}

ImageClass     *
ImageclassFind(const char *name, int fallback)
{
    ImageClass     *ic = NULL;

    if (name)
        ic = LIST_FIND(ImageClass, &iclass_list, _ImageclassMatchName, name);
    if (ic || !fallback)
        return ic;

#if 0
    Eprintf("%s: Get fallback (%s)\n", __func__, name);
#endif
    return ImageclassGetFallback();
}

#define ISTATE_SET_STATE(which, fallback) \
   if (!ic->which) ic->which = ic->fallback;

static void
ImageclassPopulate(ImageClass *ic)
{
    if (!ic || !ic->norm.normal)
        return;

    ISTATE_SET_STATE(norm.hilited, norm.normal);
    ISTATE_SET_STATE(norm.clicked, norm.normal);
    ISTATE_SET_STATE(norm.disabled, norm.normal);

    ISTATE_SET_STATE(active.normal, norm.normal);
    ISTATE_SET_STATE(active.hilited, active.normal);
    ISTATE_SET_STATE(active.clicked, active.normal);
    ISTATE_SET_STATE(active.disabled, active.normal);

    ISTATE_SET_STATE(sticky.normal, norm.normal);
    ISTATE_SET_STATE(sticky.hilited, sticky.normal);
    ISTATE_SET_STATE(sticky.clicked, sticky.normal);
    ISTATE_SET_STATE(sticky.disabled, sticky.normal);

    ISTATE_SET_STATE(sticky_active.normal, norm.normal);
    ISTATE_SET_STATE(sticky_active.hilited, sticky_active.normal);
    ISTATE_SET_STATE(sticky_active.clicked, sticky_active.normal);
    ISTATE_SET_STATE(sticky_active.disabled, sticky_active.normal);
}

int
ImageclassConfigLoad(FILE *fs)
{
    int             err = 0;
    char            s[FILEPATH_LEN_MAX];
    char            s2[FILEPATH_LEN_MAX];
    char           *p2;
    int             i1;
    ImageClass     *ic = NULL;
    ImageState     *is = NULL;
    int             l, r, t, b;

    while (GetLine(s, sizeof(s), fs))
    {
        i1 = ConfigParseline1(s, s2, &p2, NULL);

        /* ic not needed */
        switch (i1)
        {
        case CONFIG_CLOSE:
            ImageclassPopulate(ic);
            goto done;
        case CONFIG_COLORMOD:
        case ICLASS_COLORMOD:
            continue;
        case CONFIG_CLASSNAME:
            if (ImageclassFind(s2, 0))
            {
                SkipTillEnd(fs);
                goto done;
            }
            ic = ImageclassCreate(s2);
            continue;
        }

        /* ic needed */
        if (!ic)
            break;

        switch (i1)
        {
        case CONFIG_INHERIT:
            {
                ImageClass     *ic2;

                ic2 = ImageclassFind(s2, 0);
                if (!ic2)
                    goto not_ok;
                ic->norm = ic2->norm;
                ic->active = ic2->active;
                ic->sticky = ic2->sticky;
                ic->sticky_active = ic2->sticky_active;
                ic->padding = ic2->padding;
            }
            continue;
        case ICLASS_PADDING:
            l = r = t = b = 0;
            sscanf(p2, "%i %i %i %i", &l, &r, &t, &b);
            ic->padding.left = l;
            ic->padding.right = r;
            ic->padding.top = t;
            ic->padding.bottom = b;
            continue;
        case ICLASS_NORMAL:
            is = ImagestateSet(&ic->norm.normal, s2);
            continue;
        case ICLASS_CLICKED:
            is = ImagestateSet(&ic->norm.clicked, s2);
            continue;
        case ICLASS_HILITED:
            is = ImagestateSet(&ic->norm.hilited, s2);
            continue;
        case ICLASS_DISABLED:
            is = ImagestateSet(&ic->norm.disabled, s2);
            continue;
        case ICLASS_STICKY_NORMAL:
            is = ImagestateSet(&ic->sticky.normal, s2);
            continue;
        case ICLASS_STICKY_CLICKED:
            is = ImagestateSet(&ic->sticky.clicked, s2);
            continue;
        case ICLASS_STICKY_HILITED:
            is = ImagestateSet(&ic->sticky.hilited, s2);
            continue;
        case ICLASS_STICKY_DISABLED:
            is = ImagestateSet(&ic->sticky.disabled, s2);
            continue;
        case ICLASS_ACTIVE_NORMAL:
            is = ImagestateSet(&ic->active.normal, s2);
            continue;
        case ICLASS_ACTIVE_CLICKED:
            is = ImagestateSet(&ic->active.clicked, s2);
            continue;
        case ICLASS_ACTIVE_HILITED:
            is = ImagestateSet(&ic->active.hilited, s2);
            continue;
        case ICLASS_ACTIVE_DISABLED:
            is = ImagestateSet(&ic->active.disabled, s2);
            continue;
        case ICLASS_STICKY_ACTIVE_NORMAL:
            is = ImagestateSet(&ic->sticky_active.normal, s2);
            continue;
        case ICLASS_STICKY_ACTIVE_CLICKED:
            is = ImagestateSet(&ic->sticky_active.clicked, s2);
            continue;
        case ICLASS_STICKY_ACTIVE_HILITED:
            is = ImagestateSet(&ic->sticky_active.hilited, s2);
            continue;
        case ICLASS_STICKY_ACTIVE_DISABLED:
            is = ImagestateSet(&ic->sticky_active.disabled, s2);
            continue;
        }

        /* is needed */
        if (!is)
            break;

        switch (i1)
        {
        case ICLASS_LRTB:
            if (!is->border)
                is->border = EMALLOC(EImageBorder, 1);
            l = r = t = b = 0;
            sscanf(p2, "%i %i %i %i", &l, &r, &t, &b);
            is->border->left = l;
            is->border->right = r;
            is->border->top = t;
            is->border->bottom = b;
            continue;
        case ICLASS_FILLRULE:
            is->pixmapfillstyle = atoi(s2);
            continue;
        case ICLASS_TRANSPARENT:
            is->transparent = strtoul(s2, NULL, 0);
            continue;
        case ICLASS_ROTATE:    /* flip goes here too */
            is->rotate = strtoul(s2, NULL, 0);
            continue;
        case ICLASS_BEVEL:
            {
                int             n, bevel;

                n = sscanf(p2, "%i %i %i %i %i %i",
                           &bevel, &is->hihi, &is->hi, &is->bg, &is->lo,
                           &is->lolo);
                if (n < 6)
                    goto not_ok;
                is->bevelstyle = bevel;
            }
            continue;
        default:
            goto not_ok;
        }
    }
  not_ok:
    err = -1;

  done:
    return err;
}

ImageClass     *
ImageclassCreateSimple(const char *name, const char *image)
{
    ImageClass     *ic;

    ic = ImageclassCreate(name);
    if (!ic)
        return NULL;

    ic->norm.normal = ImagestateCreate(image);
    ic->norm.normal->unloadable = 1;
    ImageclassPopulate(ic);

    ImagestateRealize(ic->norm.normal);

    return ic;
}

static ImageState *
ImageclassGetImageState1(ImageStateArray *pisa, int state)
{
    ImageState     *is;

    switch (state)
    {
    case STATE_NORMAL:
        is = pisa->normal;
        break;
    case STATE_HILITED:
        is = pisa->hilited;
        break;
    case STATE_CLICKED:
        is = pisa->clicked;
        break;
    case STATE_DISABLED:
        is = pisa->disabled;
        break;
    default:
        is = NULL;
        break;
    }

    return is;
}

ImageState     *
ImageclassGetImageState(ImageClass *ic, int state, int active, int sticky)
{
    ImageState     *is;

    if (active)
    {
        if (sticky)
            is = ImageclassGetImageState1(&ic->sticky_active, state);
        else
            is = ImageclassGetImageState1(&ic->active, state);
    }
    else
    {
        if (sticky)
            is = ImageclassGetImageState1(&ic->sticky, state);
        else
            is = ImageclassGetImageState1(&ic->norm, state);
    }

    return is;
}

EImage         *
ImageclassGetImage(ImageClass *ic, int active, int sticky, int state)
{
    EImage         *im;
    ImageState     *is;

    if (!ic)
        return NULL;

    is = ImageclassGetImageState(ic, state, active, sticky);
    if (!is)
        return NULL;

    if (!is->im)
        ImagestateRealize(is);

    im = is->im;
    if (!im)
        return NULL;
    is->im = NULL;

    return im;
}

char           *
ImageclassGetFile(ImageClass *ic)
{
    ImageState     *is;

    if (!ic)
        return NULL;

    is = ImageclassGetImageState(ic, STATE_NORMAL, 0, 0);
    if (!is || !is->im_file)
        return NULL;

    return ThemeFileFind(is->im_file, FILE_TYPE_IMAGE);
}

EImage         *
ImageclassGetImageBlended(ImageClass *ic, Win win __UNUSED__, int w, int h,
                          int active, int sticky, int state)
{
    ImageState     *is;
    EImage         *im, *bg;

    if (!ic)
        return NULL;

    is = ImageclassGetImageState(ic, state, active, sticky);
    if (!is)
        return NULL;

    if (!is->im)
        ImagestateRealize(is);
    im = is->im;
    if (!im)
        return NULL;

    if (is->pixmapfillstyle == FILL_STRETCH)
    {
        bg = EImageCreateScaled(im, 0, 0, 0, 0, w, h);
    }
    else
    {
        int             iw, ih, tw, th;

        EImageGetSize(im, &iw, &ih);

        tw = w;
        th = h;
        if (is->pixmapfillstyle & FILL_TILE_H)
            tw = iw;
        if (is->pixmapfillstyle & FILL_TILE_V)
            th = ih;

        bg = EImageCreate(w, h);
        EImageTile(bg, im, 0, tw, th, 0, 0, w, h, 0, 0);
    }

    if ((is->unloadable) || (Conf.memory_paranoia))
    {
        EImageFree(is->im);
        is->im = NULL;
    }

    return bg;
}

static void
ImagestateMakePmapMask(ImageState *is, Win win, PmapMask *pmm,
                       int pmapflags __UNUSED__, int w, int h)
{
    if (is->pixmapfillstyle == FILL_STRETCH)
    {
        pmm->type = 1;
        pmm->pmap = pmm->mask = NoXID;
        pmm->w = w;
        pmm->h = h;
        EImageRenderPixmaps(is->im, win, EIMAGE_ANTI_ALIAS, &pmm->pmap,
                            &pmm->mask, w, h);
    }
    else
    {
        int             ww, hh, cw, ch, pw, ph;

        EImageGetSize(is->im, &ww, &hh);

        pw = w;
        ph = h;
        if (is->pixmapfillstyle & FILL_TILE_H)
            pw = ww;
        if (is->pixmapfillstyle & FILL_TILE_V)
            ph = hh;
        if (is->pixmapfillstyle & FILL_INT_TILE_H)
        {
            cw = w / ww;
            if (cw * ww < w)
                cw++;
            if (cw < 1)
                cw = 1;
            pw = w / cw;
        }
        if (is->pixmapfillstyle & FILL_INT_TILE_V)
        {
            ch = h / hh;
            if (ch * hh < h)
                ch++;
            if (ch < 1)
                ch = 1;
            ph = h / ch;
        }
        pmm->type = 1;
        pmm->pmap = pmm->mask = NoXID;
        pmm->w = pw;
        pmm->h = ph;
        EImageRenderPixmaps(is->im, win, EIMAGE_ANTI_ALIAS, &pmm->pmap,
                            &pmm->mask, pw, ph);
    }
}

#define LINE(x1, y1, x2, y2) \
   XDrawLine(disp, win, gc, x + (x1), y + (y1), x + (x2), y + (y2))
#define RECT(x, y, w, h) \
    XDrawRectangle(disp, win, gc, x, y, w, h);

static void
ImagestateDrawBevel(ImageState *is, EX_Drawable win, int x, int y, int w, int h)
{
    GC              gc;

    gc = EXCreateGC(win, 0, NULL);

    ImagestateColorsAlloc(is);

    switch (is->bevelstyle)
    {
    case BEVEL_AMIGA:
        XSetForeground(disp, gc, is->hihi_pixel);
        LINE(0, 0, w - 2, 0);
        LINE(0, 0, 0, h - 2);
        XSetForeground(disp, gc, is->lolo_pixel);
        LINE(1, h - 1, w - 1, h - 1);
        LINE(w - 1, 1, w - 1, h - 1);
        break;
    case BEVEL_MOTIF:
        XSetForeground(disp, gc, is->hi_pixel);
        LINE(0, 0, w - 1, 0);
        LINE(0, 0, 0, h - 1);
        LINE(1, 1, w - 2, 1);
        LINE(1, 1, 1, h - 2);
        XSetForeground(disp, gc, is->lo_pixel);
        LINE(0, h - 1, w - 1, h - 1);
        LINE(w - 1, 1, w - 1, h - 1);
        LINE(1, h - 2, w - 2, h - 2);
        LINE(w - 2, 2, w - 2, h - 2);
        break;
    case BEVEL_NEXT:
        XSetForeground(disp, gc, is->hihi_pixel);
        LINE(0, 0, w - 1, 0);
        LINE(0, 0, 0, h - 1);
        XSetForeground(disp, gc, is->hi_pixel);
        LINE(1, 1, w - 2, 1);
        LINE(1, 1, 1, h - 2);
        XSetForeground(disp, gc, is->lolo_pixel);
        LINE(1, h - 1, w - 1, h - 1);
        LINE(w - 1, 1, w - 1, h - 1);
        XSetForeground(disp, gc, is->lo_pixel);
        LINE(2, h - 2, w - 2, h - 2);
        LINE(w - 2, 2, w - 2, h - 2);
        break;
    case BEVEL_DOUBLE:
        XSetForeground(disp, gc, is->hi_pixel);
        LINE(0, 0, w - 2, 0);
        LINE(0, 0, 0, h - 2);
        XSetForeground(disp, gc, is->lo_pixel);
        LINE(1, 1, w - 3, 1);
        LINE(1, 1, 1, h - 3);
        XSetForeground(disp, gc, is->lo_pixel);
        LINE(1, h - 1, w - 1, h - 1);
        LINE(w - 1, 1, w - 1, h - 1);
        XSetForeground(disp, gc, is->hi_pixel);
        LINE(2, h - 2, w - 2, h - 2);
        LINE(w - 2, 2, w - 2, h - 2);
        break;
    case BEVEL_WIDEDOUBLE:
        XSetForeground(disp, gc, is->hihi_pixel);
        LINE(0, 0, w - 1, 0);
        LINE(0, 0, 0, h - 1);
        XSetForeground(disp, gc, is->hi_pixel);
        LINE(1, 1, w - 2, 1);
        LINE(1, 1, 1, h - 2);
        LINE(3, h - 4, w - 4, h - 4);
        LINE(w - 4, 3, w - 4, h - 4);
        XSetForeground(disp, gc, is->lolo_pixel);
        LINE(1, h - 1, w - 1, h - 1);
        LINE(w - 1, 1, w - 1, h - 1);
        XSetForeground(disp, gc, is->lo_pixel);
        LINE(2, h - 2, w - 2, h - 2);
        LINE(w - 2, 2, w - 2, h - 2);
        LINE(3, 3, w - 4, 3);
        LINE(3, 3, 3, h - 4);
        break;
    case BEVEL_THINPOINT:
        XSetForeground(disp, gc, is->hi_pixel);
        LINE(0, 0, w - 2, 0);
        LINE(0, 0, 0, h - 2);
        XSetForeground(disp, gc, is->lo_pixel);
        LINE(1, h - 1, w - 1, h - 1);
        LINE(w - 1, 1, w - 1, h - 1);
        XSetForeground(disp, gc, is->hihi_pixel);
        LINE(0, 0, 1, 0);
        LINE(0, 0, 0, 1);
        XSetForeground(disp, gc, is->lolo_pixel);
        LINE(w - 2, h - 1, w - 1, h - 1);
        LINE(w - 1, h - 2, w - 1, h - 1);
        break;
    case BEVEL_THICKPOINT:
        XSetForeground(disp, gc, is->hi_pixel);
        RECT(x, y, w - 1, h - 1);
        break;
    default:
        break;
    }

    EXFreeGC(gc);
}

static void
ImagestateDrawNoImg(ImageState *is, EX_Drawable draw, int x, int y, int w,
                    int h)
{
    ImagestateColorsAlloc(is);

    EXFillAreaSolid(draw, x, y, w, h, is->bg_pixel);
    if (is->bevelstyle != BEVEL_NONE)
        ImagestateDrawBevel(is, draw, x, y, w, h);
}

void
ITApply(Win win, ImageClass *ic, ImageState *is,
        int state, int active, int sticky,
        TextClass *tc, TextState *ts, const char *text, int flags)
{
    int             w, h;

    if (!win || !ic)
        return;

    w = WinGetW(win);
    h = WinGetH(win);
    if (w <= 0 || h <= 0)
        return;

    if (!is)
        is = ImageclassGetImageState(ic, state, active, sticky);
    if (!is)
        return;

    if (tc && text)
    {
        if (!ts)
            ts = TextclassGetTextState(tc, state, active, sticky);
    }

    if (!is->im)
        ImagestateRealize(is);

    /* Imlib2 will not render pixmaps with dimensions > 8192 */
    if (is->im && w <= 8192 && h <= 8192)
    {
        PmapMask        pmm;

        ImagestateMakePmapMask(is, win, &pmm, IC_FLAG_MAKE_MASK, w, h);

        if (pmm.pmap)
        {
            EX_Pixmap       pmap = pmm.pmap;

            if ((ts && text) || (is->bevelstyle != BEVEL_NONE) ||
                (flags & ITA_BGPMAP))
            {
                if (pmm.type != 0)
                {
                    pmap = EGetWindowBackgroundPixmap(win);
                    EXCopyAreaTiled(pmm.pmap, NoXID, pmap, 0, 0, w, h, 0, 0);
                }

                if (is->bevelstyle != BEVEL_NONE)
                    ImagestateDrawBevel(is, pmap, 0, 0, w, h);

                if (ts && text)
                    TextstateTextDraw(ts, win, pmap, text, 0, 0, w, h,
                                      &(ic->padding),
                                      TextclassGetJustification(tc),
                                      flags & ITA_JUSTV);
            }

            /* Set window pixmap */
            if (pmap == pmm.pmap)
                ESetWindowBackgroundPixmap(win, pmap, 0);

            if (pmm.w == w && pmm.h == h)
                EShapeSetMask(win, 0, 0, pmm.mask);
            else if (pmm.mask)
                EShapeSetMaskTiled(win, 0, 0, pmm.mask, w, h);
        }

        PmapMaskFree(&pmm);

        if ((is->unloadable) || (Conf.memory_paranoia))
        {
            EImageFree(is->im);
            is->im = NULL;
        }
    }
    else
    {
        ImagestateColorsAlloc(is);

        if (is->bevelstyle == BEVEL_NONE && !text)
        {
            ESetWindowBackground(win, is->bg_pixel);
        }
        else
        {
            EX_Pixmap       pmap;

            pmap = EGetWindowBackgroundPixmap(win);
            ImagestateDrawNoImg(is, pmap, 0, 0, w, h);
            if (ts && text)
                TextstateTextDraw(ts, win, pmap, text, 0, 0, w, h,
                                  &(ic->padding),
                                  TextclassGetJustification(tc),
                                  flags & ITA_JUSTV);
        }
    }
    EClearWindow(win);
}

void
ImageclassApply(ImageClass *ic, Win win, int active, int sticky, int state)
{
    ITApply(win, ic, NULL, state, active, sticky, NULL, NULL, NULL, 0);
}

static void
PmapMaskTile(PmapMask *pmm, Win win, unsigned int w, unsigned int h)
{
    EX_Pixmap       pmap, mask;

    pmap = ECreatePixmap(win, w, h, 0);
    if (pmap == NoXID)
        return;
    EXCopyAreaTiled(pmm->pmap, NoXID, pmap, 0, 0, w, h, 0, 0);

    mask = NoXID;
    if (pmm->mask != NoXID)
        mask = ECreatePixmap(win, w, h, 1);
    if (mask != NoXID)
        EXCopyAreaTiled(pmm->mask, NoXID, mask, 0, 0, w, h, 0, 0);

    PmapMaskFree(pmm);
    pmm->type = 0;
    pmm->w = w;
    pmm->h = h;
    pmm->pmap = pmap;
    pmm->mask = mask;
}

void
ImageclassApplyCopy(ImageClass *ic, Win win, int w, int h,
                    int active, int sticky, int state,
                    PmapMask *pmm, int pmapflags)
{
    ImageState     *is;

    if (!pmm)
        return;

    pmm->type = 0;
    pmm->pmap = pmm->mask = 0;

    if ((!ic) || (!win) || (w <= 0) || (h <= 0))
        return;

    is = ImageclassGetImageState(ic, state, active, sticky);
    if (!is)
        return;

    if (!is->im)
        ImagestateRealize(is);

    /* Imlib2 will not render pixmaps with dimensions > 8192 */
    if (is->im && w <= 8192 && h <= 8192)
    {
        ImagestateMakePmapMask(is, win, pmm, pmapflags, w, h);

        if ((pmapflags & IC_FLAG_FULL_SIZE) && pmm->pmap &&
            (pmm->w != w || pmm->h != h))
        {
            /* Create new full sized pixmaps and fill them with the */
            /* pmap and mask tiles                                  */
            PmapMaskTile(pmm, win, w, h);
        }

        if ((is->unloadable) || (Conf.memory_paranoia))
        {
            EImageFree(is->im);
            is->im = NULL;
        }
    }
    else
    {
        if (pmm->pmap)
            Eprintf("%s: Hmm... pmm->pmap already set\n", __func__);

        pmm->type = 0;
        pmm->pmap = ECreatePixmap(win, w, h, 0);
        pmm->mask = 0;

        ImagestateDrawNoImg(is, pmm->pmap, 0, 0, w, h);
        /* FIXME - No text */
    }
}

void
ImageclassApplySimple(ImageClass *ic, Win win, EX_Drawable draw, int state,
                      int x, int y, int w, int h)
{
    EImage         *im;

    if (!ic)
        return;

    im = ImageclassGetImage(ic, 0, 0, state);
    if (im)
    {
        EImageRenderOnDrawable(im, win, draw, 0, x, y, w, h);
        EImageFree(im);
    }
    else
    {
        ImageState     *is;

        is = ImageclassGetImageState(ic, state, 0, 0);
        if (!is)
            return;

        ImagestateDrawNoImg(is, draw, x, y, w, h);
    }
}

static ImageClass *
ImageclassGetFallback(void)
{
    ImageClass     *ic;

    ic = ImageclassFind("__fb_ic", 0);
    if (ic)
        return ic;

    /* Create fallback imageclass */
    ic = ImageclassCreate("__fb_ic");
    if (!ic)
        return ic;

    ic->norm.normal = ImagestateCreateX(255, 255, 0, 0, 160, 160, 160);
    ic->norm.hilited = ImagestateCreateX(255, 255, 0, 0, 192, 192, 192);
    ic->norm.clicked = ImagestateCreateX(0, 0, 255, 255, 192, 192, 192);
    ic->active.normal = ImagestateCreateX(255, 255, 0, 0, 220, 0, 0);
    ic->active.hilited = ImagestateCreateX(255, 255, 0, 0, 255, 0, 0);
    ic->active.clicked = ImagestateCreateX(0, 0, 255, 255, 230, 190, 210);

    ic->padding.left = 4;
    ic->padding.right = 4;
    ic->padding.top = 4;
    ic->padding.bottom = 4;

    ImageclassPopulate(ic);

    return ic;
}

ImageClass     *
ImageclassGetBlack(void)
{
    ImageClass     *ic;

    ic = ImageclassFind("__BLACK", 0);
    if (ic)
        return ic;

    /* Create all black image class for filler borders */
    ic = ImageclassCreate("__BLACK");
    if (!ic)
        return ic;

    ic->norm.normal = ImagestateCreate(NULL);
    ImagestateColorsSetGray(ic->norm.normal, 0, 0, 0, 0, 0);

    ImageclassPopulate(ic);

    return ic;
}

/*
 * Imageclass Module
 */

static void
ImageclassIpc(const char *params)
{
    char            param1[1024];
    char            param2[1024];
    int             l;
    const char     *p;
    ImageClass     *ic;

    if (!params)
    {
        IpcPrintf("Please specify...\n");
        return;
    }

    p = params;
    l = 0;
    param1[0] = param2[0] = '\0';
    sscanf(p, "%1000s %1000s %n", param1, param2, &l);
    p += l;

    if (!strncmp(param1, "list", 2))
    {
        LIST_FOR_EACH(ImageClass, &iclass_list, ic) IpcPrintf("%s\n", ic->name);
        return;
    }

    if (!param1[0])
    {
        IpcPrintf("ImageClass not specified\n");
        return;
    }

    if (!strcmp(param2, "free_pixmap"))
    {
        EX_Pixmap       pmap;

        pmap = (EX_Pixmap) strtol(p, NULL, 0);
        EImagePixmapsFree(pmap, NoXID);
        return;
    }

    ic = ImageclassFind(param1, 0);
    if (!ic)
    {
        IpcPrintf("ImageClass not found: %s\n", param1);
        return;
    }

    if (!strcmp(param2, "get_padding"))
    {
        IpcPrintf("%i %i %i %i\n",
                  ic->padding.left, ic->padding.right,
                  ic->padding.top, ic->padding.bottom);
    }
    else if (!strcmp(param2, "get_image_size"))
    {
        ImagestateRealize(ic->norm.normal);
        if (ic->norm.normal->im)
        {
            int             w, h;

            EImageGetSize(ic->norm.normal->im, &w, &h);
            EImageFree(ic->norm.normal->im);
            IpcPrintf("%i %i\n", w, h);
        }
    }
    else if (!strcmp(param2, "apply"))
    {
        EX_Window       xwin;
        Win             win;
        char            state[20];
        int             st, w, h;

        /* 3:xwin 4:state 5:w 6:h */
        xwin = NoXID;
        state[0] = '\0';
        w = h = -1;
        sscanf(p, "%x %16s %d %d", &xwin, state, &w, &h);

        win = ECreateWinFromXwin(xwin);
        if (!win)
            return;

        if (!strcmp(state, "normal"))
            st = STATE_NORMAL;
        else if (!strcmp(state, "hilited"))
            st = STATE_HILITED;
        else if (!strcmp(state, "clicked"))
            st = STATE_CLICKED;
        else if (!strcmp(state, "disabled"))
            st = STATE_DISABLED;
        else
            st = STATE_NORMAL;

        ImageclassApply(ic, win, 0, 0, st);
        EDestroyWin(win);
    }
    else if (!strcmp(param2, "apply_copy"))
    {
        EX_Window       xwin;
        Win             win;
        char            state[20];
        int             st, w, h;
        PmapMask        pmm;

        /* 3:xwin 4:state 5:w 6:h */
        xwin = NoXID;
        state[0] = '\0';
        w = h = -1;
        sscanf(p, "%x %16s %d %d", &xwin, state, &w, &h);

        win = ECreateWinFromXwin(xwin);
        if (!win)
            return;

        if (!strcmp(state, "normal"))
            st = STATE_NORMAL;
        else if (!strcmp(state, "hilited"))
            st = STATE_HILITED;
        else if (!strcmp(state, "clicked"))
            st = STATE_CLICKED;
        else if (!strcmp(state, "disabled"))
            st = STATE_DISABLED;
        else
            st = STATE_NORMAL;

        if (w < 0 || h < 0)
        {
            IpcPrintf("Error:  missing width and/or height\n");
            return;
        }

        ImageclassApplyCopy(ic, win, w, h, 0, 0, st, &pmm,
                            IC_FLAG_MAKE_MASK | IC_FLAG_FULL_SIZE);
        IpcPrintf("0x%08x 0x%08x\n", pmm.pmap, pmm.mask);
        EDestroyWin(win);
    }
    else if (!strcmp(param2, "query"))
    {
        IpcPrintf("ImageClass %s found\n", ic->name);
    }
    else if (!strcmp(param2, "ref_count"))
    {
        IpcPrintf("%u references remain\n", ic->ref_count);
    }
    else
    {
        IpcPrintf("Error: unknown operation specified\n");
    }
}

static const IpcItem ImageclassIpcArray[] = {
    {
     ImageclassIpc,
     "imageclass", "ic",
     "List imageclasses, apply an imageclass",
     NULL }
    ,
};

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

const EModule   ModImageclass = {
    "imageclass", "ic",
    NULL,
    MOD_ITEMS(ImageclassIpcArray),
    { 0, NULL }
};
