#include "common.h"

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <X11/Xlib.h>
#include <X11/extensions/XShm.h>
#include <X11/Xutil.h>
#ifdef HAVE_X11_SHM_FD
#include <X11/Xlib-xcb.h>
#include <xcb/shm.h>
#include <sys/mman.h>
#endif
#include <sys/ipc.h>
#include <sys/shm.h>

#include "x11_ximage.h"

static void     __imlib_FlushXImage(const ImlibContextX11 * x11);

static signed char x_does_shm = -1;

#ifdef HAVE_X11_SHM_FD
static signed char x_does_shm_fd = 0;
#endif

typedef struct {
    XImage         *xim;
    XShmSegmentInfo *si;
    Display        *dpy;
    char            used;
} xim_cache_rec_t;

static xim_cache_rec_t *xim_cache = NULL;

static int      list_num = 0;
static int      list_mem_use = 0;
static int      list_max_mem = 1024 * 1024 * 1024;
static int      list_max_count = 0;

/* temporary X error catcher we use later */
static char     _x_err = 0;

/* the function we use for catching the error */
static int
TmpXError(Display *d, XErrorEvent *ev)
{
    _x_err = 1;
    return 0;
}

static void
ShmCheck(Display *d)
{
    const char     *s;
    int             val;

    /* if its there set x_does_shm flag */
    if (XShmQueryExtension(d))
    {
#ifdef HAVE_X11_SHM_FD
        int             major, minor;
        Bool            pixmaps;
#endif
        x_does_shm = 2;         /* 2: __imlib_ShmGetXImage tests first XShmAttach */
#ifdef HAVE_X11_SHM_FD
        if (XShmQueryVersion(d, &major, &minor, &pixmaps))
        {
            x_does_shm_fd = (major == 1 && minor >= 2) || major > 1;
        }
#endif
    }
    /* clear the flag - no shm at all */
    else
    {
        x_does_shm = 0;
        return;
    }

    /* Modify SHM handling if requested */
    s = getenv("IMLIB2_SHM_OPT");
    if (s)
    {
        val = atoi(s);
#ifdef HAVE_X11_SHM_FD
        if (val == 0)
            x_does_shm = x_does_shm_fd = 0;     /* Disable SHM entirely */
        else if (val == 1)
            x_does_shm_fd = 0;  /* Disable SHM-FD */
        printf("%s: x_does_shm=%d x_does_shm_fd=%d\n", __func__,
               x_does_shm, x_does_shm_fd);
#else
        if (val == 0)
            x_does_shm = 0;     /* Disable SHM entirely */
        printf("%s: x_does_shm=%d\n", __func__, x_does_shm);
#endif
    }

    /* Set ximage cache list_max_count */
    s = getenv("IMLIB2_XIMAGE_CACHE_COUNT");
    if (s)
    {
        val = atoi(s);
        if (val > 0)
            list_max_count = val;

        printf("%s: list_max_count=%d\n", __func__, list_max_count);
    }
}

XImage         *
__imlib_ShmGetXImage(const ImlibContextX11 *x11, Drawable draw, int depth,
                     int x, int y, int w, int h, XShmSegmentInfo *si)
{
    XImage         *xim;

    if (x_does_shm < 0)
        ShmCheck(x11->dpy);

    if (!x_does_shm)
        return NULL;

    /* try create an shm image */
    xim = XShmCreateImage(x11->dpy, x11->vis, depth, ZPixmap, NULL, si, w, h);
    if (!xim)
        return NULL;

#ifdef HAVE_X11_SHM_FD
    if (x_does_shm_fd)
    {
        xcb_generic_error_t *error = NULL;
        xcb_shm_create_segment_cookie_t cookie;
        xcb_shm_create_segment_reply_t *reply;
        xcb_connection_t *c = XGetXCBConnection(x11->dpy);
        size_t          segment_size = xim->bytes_per_line * xim->height;

        si->shmaddr = NULL;
        si->shmseg = xcb_generate_id(c);
        si->readOnly = False;

        cookie =
            xcb_shm_create_segment(c, si->shmseg, segment_size, si->readOnly);
        reply = xcb_shm_create_segment_reply(c, cookie, &error);
        if (reply)
        {
            int            *fds;

            fds = reply->nfd == 1 ?
                xcb_shm_create_segment_reply_fds(c, reply) : NULL;
            if (fds)
            {
                si->shmaddr = mmap(0, segment_size, PROT_READ | PROT_WRITE,
                                   MAP_SHARED, fds[0], 0);
                close(fds[0]);
                if (si->shmaddr == MAP_FAILED)
                    si->shmaddr = NULL;
            }
            if (si->shmaddr == NULL)
            {
                xcb_shm_detach(c, si->shmseg);
            }
            free(reply);
        }
        free(error);

        if (si->shmaddr)
        {
            xim->data = si->shmaddr;
            if (draw != None)
                XShmGetImage(x11->dpy, draw, xim, x, y, 0xffffffff);

            return xim;
        }
        else
        {
            x_does_shm = 0;
        }
    }
    else
#endif
    {
        /* get an shm id of this image */
        si->shmid = shmget(IPC_PRIVATE, xim->bytes_per_line * xim->height,
                           IPC_CREAT | 0666);
        /* if the get succeeds */
        if (si->shmid != -1)
        {
            /* set the params for the shm segment */
            si->readOnly = False;
            si->shmaddr = xim->data = shmat(si->shmid, 0, 0);
            /* get the shm addr for this data chunk */
            if (xim->data != (char *)-1)
            {
                XErrorHandler   ph = NULL;

                if (x_does_shm == 2)
                {
                    /* setup a temporary error handler */
                    _x_err = 0;
                    XSync(x11->dpy, False);
                    ph = XSetErrorHandler(TmpXError);
                }
                /* ask X to attach to the shared mem segment */
                XShmAttach(x11->dpy, si);
                if (draw != None)
                    XShmGetImage(x11->dpy, draw, xim, x, y, 0xffffffff);
                if (x_does_shm == 2)
                {
                    /* wait for X to reply and do this */
                    XSync(x11->dpy, False);
                    /* reset the error handler */
                    XSetErrorHandler(ph);
                    x_does_shm = 1;
                }

                /* if we attached without an error we're set */
                if (_x_err == 0)
                    return xim;

                /* attach by X failed... must be remote client */
                /* flag shm forever to not work - remote */
                x_does_shm = 0;

                /* detach */
                shmdt(si->shmaddr);
            }

            /* get failed - out of shm id's or shm segment too big ? */
            /* remove the shm id we created */
            shmctl(si->shmid, IPC_RMID, 0);
        }
    }

    /* couldnt create SHM image ? */
    /* destroy previous image */
    XDestroyImage(xim);

    return NULL;
}

void
__imlib_ShmDestroyXImage(const ImlibContextX11 *x11, XImage *xim,
                         XShmSegmentInfo *si)
{
    XSync(x11->dpy, False);
    XShmDetach(x11->dpy, si);
#ifdef HAVE_X11_SHM_FD
    if (x_does_shm_fd)
    {
        munmap(si->shmaddr, xim->bytes_per_line * xim->height);
    }
    else
#endif
    {
        shmdt(si->shmaddr);
        shmctl(si->shmid, IPC_RMID, 0);
    }
    XDestroyImage(xim);
}

void
__imlib_SetXImageCacheCountMax(const ImlibContextX11 *x11, int num)
{
    list_max_count = num;
    __imlib_FlushXImage(x11);
}

int
__imlib_GetXImageCacheCountMax(const ImlibContextX11 *x11)
{
    return list_max_count;
}

int
__imlib_GetXImageCacheCountUsed(const ImlibContextX11 *x11)
{
    return list_num;
}

void
__imlib_SetXImageCacheSizeMax(const ImlibContextX11 *x11, int num)
{
    list_max_mem = num;
    __imlib_FlushXImage(x11);
}

int
__imlib_GetXImageCacheSizeMax(const ImlibContextX11 *x11)
{
    return list_max_mem;
}

int
__imlib_GetXImageCacheSizeUsed(const ImlibContextX11 *x11)
{
    return list_mem_use;
}

void
__imlib_FlushXImage(const ImlibContextX11 *x11)
{
    int             i, j;
    XImage         *xim;
    char            did_free = 1;

    while (((list_mem_use > list_max_mem) || (list_num > list_max_count)) &&
           (did_free))
    {
        did_free = 0;
        for (i = 0; i < list_num;)
        {
            if (xim_cache[i].used)
            {
                i++;
                continue;
            }

            xim = xim_cache[i].xim;
            list_mem_use -= xim->bytes_per_line * xim->height;

            if (xim_cache[i].si)
            {
                __imlib_ShmDestroyXImage(x11, xim, xim_cache[i].si);
                free(xim_cache[i].si);
            }
            else
            {
                XDestroyImage(xim);
            }

            list_num--;
            for (j = i; j < list_num; j++)
            {
                xim_cache[j] = xim_cache[j + 1];
            }

            if (list_num == 0)
            {
                free(xim_cache);
                xim_cache = NULL;
            }
            else
            {
                xim_cache =
                    realloc(xim_cache, sizeof(xim_cache_rec_t) * list_num);
            }

            did_free = 1;
        }
    }
}

/* free (consume == opposite of produce) the XImage (mark as unused) */
void
__imlib_ConsumeXImage(const ImlibContextX11 *x11, XImage *xim)
{
    int             i;

    /* march through the XImage list */
    for (i = 0; i < list_num; i++)
    {
        /* find a match */
        if (xim_cache[i].xim == xim)
        {
            /* we have a match = mark as unused */
            xim_cache[i].used = 0;
            /* flush the XImage list to get rud of stuff we dont want */
            __imlib_FlushXImage(x11);
            /* return */
            return;
        }
    }
}

/* create a new XImage or find it on our list of currently available ones so */
/* we dont need to create a new one */
XImage         *
__imlib_ProduceXImage(const ImlibContextX11 *x11, int depth, int w, int h,
                      char *shared)
{
    XImage         *xim;
    xim_cache_rec_t *xim_cache_tmp;
    int             i;

    /* find a cached XImage (to avoid server to & fro) that is big enough */
    /* for our needs and the right depth */
    *shared = 0;
    /* go thru the current image list */
    for (i = 0; i < list_num; i++)
    {
        if (xim_cache[i].used)
            continue;

        xim = xim_cache[i].xim;

        /* if the image has the same depth, width and height - recycle it */
        if ((xim->depth == depth) && (xim->width >= w) && (xim->height >= h))
            /* && (xim_cache[i].dpy == d) */
        {
            xim_cache[i].used = 1;
            /* if its shared set shared flag */
            if (xim_cache[i].si)
                *shared = 1;
            /* return it */
            return xim;
        }
    }

    /* can't find a usable XImage on the cache - create one */
    /* add the new XImage to the XImage cache */
    list_num++;
    xim_cache_tmp = realloc(xim_cache, sizeof(xim_cache_rec_t) * list_num);
    if (!xim_cache_tmp)
    {
        /* failed to allocate memory */
        list_num--;
        return NULL;
    }
    xim_cache = xim_cache_tmp;
    xim_cache[list_num - 1].si = malloc(sizeof(XShmSegmentInfo));
    if (!xim_cache[list_num - 1].si)
    {
        /* failed to allocate memory */
        list_num--;
        return NULL;
    }

    /* work on making a shared image */
    xim = __imlib_ShmGetXImage(x11, None, depth, 0, 0, w, h,
                               xim_cache[list_num - 1].si);
    /* ok if xim == NULL it all failed - fall back to XImages */
    if (xim)
    {
        *shared = 1;
    }
    else
    {
        /* get rid of out shm info struct */
        free(xim_cache[list_num - 1].si);
        /* flag it as NULL ot indicate a normal XImage */
        xim_cache[list_num - 1].si = NULL;
        /* create a normal ximage */
        xim =
            XCreateImage(x11->dpy, x11->vis, depth, ZPixmap, 0, NULL, w, h, 32,
                         0);
        /* allocate data for it */
        if (xim)
            xim->data = malloc(xim->bytes_per_line * xim->height);
        if (!xim || !xim->data)
        {
            /* failed to create XImage or allocate data memory */
            if (xim)
                XDestroyImage(xim);
            list_num--;
            return NULL;
        }
    }
    /* add xim to our list */
    xim_cache[list_num - 1].xim = xim;
    /* incriment our memory count */
    list_mem_use += xim->bytes_per_line * xim->height;
    /* mark image as used */
    xim_cache[list_num - 1].used = 1;
    /* remember what display that XImage was for */
    xim_cache[list_num - 1].dpy = x11->dpy;

    /* flush unused images from the image list */
    __imlib_FlushXImage(x11);

    /* set the byte order of the XImage to the byte_order of the Xclient */
    /* (rather than the Xserver) */
#ifdef WORDS_BIGENDIAN
    xim->byte_order = MSBFirst;
    xim->bitmap_bit_order = MSBFirst;
#else
    xim->byte_order = LSBFirst;
    xim->bitmap_bit_order = LSBFirst;
#endif

    /* return out image */
    return xim;
}
