/*
    SDL - Simple DirectMedia Layer
    Copyright (C) 1997-2006 Sam Lantinga

    This library is free software; you can redistribute it and/or
    modify it under the terms of the GNU Lesser General Public
    License as published by the Free Software Foundation; either
    version 2.1 of the License, or (at your option) any later version.

    This library is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
    Lesser General Public License for more details.

    You should have received a copy of the GNU Lesser General Public
    License along with this library; if not, write to the Free Software
    Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA

    Sam Lantinga
    slouken@libsdl.org
*/
#include "SDL_config.h"

#include <stdio.h>
#include <unistd.h>

#include "SDL_endian.h"
#include "../../events/SDL_events_c.h"
#include "SDL_x11image_c.h"

#ifndef NO_SHARED_MEMORY

/* Shared memory error handler routine */
static int shm_error;
static int (*X_handler) (Display *, XErrorEvent *) = NULL;
static int
shm_errhandler(Display * d, XErrorEvent * e)
{
    if (e->error_code == BadAccess) {
        shm_error = True;
        return (0);
    } else
        return (X_handler(d, e));
}

static void
try_mitshm(_THIS, SDL_Surface * screen)
{
    /* Dynamic X11 may not have SHM entry points on this box. */
    if ((use_mitshm) && (!SDL_X11_HAVE_SHM))
        use_mitshm = 0;

    if (!use_mitshm)
        return;
    shminfo.shmid = shmget(IPC_PRIVATE, screen->h * screen->pitch,
                           IPC_CREAT | 0777);
    if (shminfo.shmid >= 0) {
        shminfo.shmaddr = (char *) shmat(shminfo.shmid, 0, 0);
        shminfo.readOnly = False;
        if (shminfo.shmaddr != (char *) -1) {
            shm_error = False;
            X_handler = XSetErrorHandler(shm_errhandler);
            XShmAttach(SDL_Display, &shminfo);
            XSync(SDL_Display, True);
            XSetErrorHandler(X_handler);
            if (shm_error)
                shmdt(shminfo.shmaddr);
        } else {
            shm_error = True;
        }
        shmctl(shminfo.shmid, IPC_RMID, NULL);
    } else {
        shm_error = True;
    }
    if (shm_error)
        use_mitshm = 0;
    if (use_mitshm)
        screen->pixels = shminfo.shmaddr;
}
#endif /* ! NO_SHARED_MEMORY */

/* Various screen update functions available */
static void X11_NormalUpdate(_THIS, int numrects, SDL_Rect * rects);
static void X11_MITSHMUpdate(_THIS, int numrects, SDL_Rect * rects);

int
X11_SetupImage(_THIS, SDL_Surface * screen)
{
#ifndef NO_SHARED_MEMORY
    try_mitshm(this, screen);
    if (use_mitshm) {
        SDL_Ximage = XShmCreateImage(SDL_Display, SDL_Visual,
                                     this->hidden->depth, ZPixmap,
                                     shminfo.shmaddr, &shminfo,
                                     screen->w, screen->h);
        if (!SDL_Ximage) {
            XShmDetach(SDL_Display, &shminfo);
            XSync(SDL_Display, False);
            shmdt(shminfo.shmaddr);
            screen->pixels = NULL;
            goto error;
        }
        this->UpdateRects = X11_MITSHMUpdate;
    }
    if (!use_mitshm)
#endif /* not NO_SHARED_MEMORY */
    {
        int bpp;
        screen->pixels = SDL_malloc(screen->h * screen->pitch);
        if (screen->pixels == NULL) {
            SDL_OutOfMemory();
            return -1;
        }
        bpp = screen->format->BytesPerPixel;
        SDL_Ximage = XCreateImage(SDL_Display, SDL_Visual,
                                  this->hidden->depth, ZPixmap, 0,
                                  (char *) screen->pixels,
                                  screen->w, screen->h, 32, 0);
        if (SDL_Ximage == NULL)
            goto error;
        /* XPutImage will convert byte sex automatically */
        SDL_Ximage->byte_order = (SDL_BYTEORDER == SDL_BIG_ENDIAN)
            ? MSBFirst : LSBFirst;
        this->UpdateRects = X11_NormalUpdate;
    }
    screen->pitch = SDL_Ximage->bytes_per_line;
    return (0);

  error:
    SDL_SetError("Couldn't create XImage");
    return 1;
}

void
X11_DestroyImage(_THIS, SDL_Surface * screen)
{
    if (SDL_Ximage) {
        XDestroyImage(SDL_Ximage);
#ifndef NO_SHARED_MEMORY
        if (use_mitshm) {
            XShmDetach(SDL_Display, &shminfo);
            XSync(SDL_Display, False);
            shmdt(shminfo.shmaddr);
        }
#endif /* ! NO_SHARED_MEMORY */
        SDL_Ximage = NULL;
    }
    if (screen) {
        screen->pixels = NULL;
    }
}

/* Determine the number of CPUs in the system */
static int
num_CPU(void)
{
    static int num_cpus = 0;

    if (!num_cpus) {
#if defined(__LINUX__)
        char line[BUFSIZ];
        FILE *pstat = fopen("/proc/stat", "r");
        if (pstat) {
            while (fgets(line, sizeof(line), pstat)) {
                if (SDL_memcmp(line, "cpu", 3) == 0 && line[3] != ' ') {
                    ++num_cpus;
                }
            }
            fclose(pstat);
        }
#elif defined(__IRIX__)
        num_cpus = sysconf(_SC_NPROC_ONLN);
#elif defined(_SC_NPROCESSORS_ONLN)
        /* number of processors online (SVR4.0MP compliant machines) */
        num_cpus = sysconf(_SC_NPROCESSORS_ONLN);
#elif defined(_SC_NPROCESSORS_CONF)
        /* number of processors configured (SVR4.0MP compliant machines) */
        num_cpus = sysconf(_SC_NPROCESSORS_CONF);
#endif
        if (num_cpus <= 0) {
            num_cpus = 1;
        }
    }
    return num_cpus;
}

int
X11_ResizeImage(_THIS, SDL_Surface * screen, Uint32 flags)
{
    int retval;

    X11_DestroyImage(this, screen);
    if (flags & SDL_INTERNALOPENGL) {   /* No image when using GL */
        retval = 0;
    } else {
        retval = X11_SetupImage(this, screen);
        /* We support asynchronous blitting on the display */
        if (flags & SDL_ASYNCBLIT) {
            /* This is actually slower on single-CPU systems,
               probably because of CPU contention between the
               X server and the application.
               Note: Is this still true with XFree86 4.0?
             */
            if (num_CPU() > 1) {
                screen->flags |= SDL_ASYNCBLIT;
            }
        }
    }
    return (retval);
}

/* We don't actually allow hardware surfaces other than the main one */
int
X11_AllocHWSurface(_THIS, SDL_Surface * surface)
{
    return (-1);
}

void
X11_FreeHWSurface(_THIS, SDL_Surface * surface)
{
    return;
}

int
X11_LockHWSurface(_THIS, SDL_Surface * surface)
{
    if ((surface == SDL_VideoSurface) && blit_queued) {
        XSync(GFX_Display, False);
        blit_queued = 0;
    }
    return (0);
}

void
X11_UnlockHWSurface(_THIS, SDL_Surface * surface)
{
    return;
}

int
X11_FlipHWSurface(_THIS, SDL_Surface * surface)
{
    return (0);
}

static void
X11_NormalUpdate(_THIS, int numrects, SDL_Rect * rects)
{
    int i;

    for (i = 0; i < numrects; ++i) {
        if (rects[i].w == 0 || rects[i].h == 0) {       /* Clipped? */
            continue;
        }
        XPutImage(GFX_Display, SDL_Window, SDL_GC, SDL_Ximage,
                  rects[i].x, rects[i].y,
                  rects[i].x, rects[i].y, rects[i].w, rects[i].h);
    }
    if (SDL_VideoSurface->flags & SDL_ASYNCBLIT) {
        XFlush(GFX_Display);
        blit_queued = 1;
    } else {
        XSync(GFX_Display, False);
    }
}

static void
X11_MITSHMUpdate(_THIS, int numrects, SDL_Rect * rects)
{
#ifndef NO_SHARED_MEMORY
    int i;

    for (i = 0; i < numrects; ++i) {
        if (rects[i].w == 0 || rects[i].h == 0) {       /* Clipped? */
            continue;
        }
        XShmPutImage(GFX_Display, SDL_Window, SDL_GC, SDL_Ximage,
                     rects[i].x, rects[i].y,
                     rects[i].x, rects[i].y, rects[i].w, rects[i].h, False);
    }
    if (SDL_VideoSurface->flags & SDL_ASYNCBLIT) {
        XFlush(GFX_Display);
        blit_queued = 1;
    } else {
        XSync(GFX_Display, False);
    }
#endif /* ! NO_SHARED_MEMORY */
}

/* There's a problem with the automatic refreshing of the display.
   Even though the XVideo code uses the GFX_Display to update the
   video memory, it appears that updating the window asynchronously
   from a different thread will cause "blackouts" of the window.
   This is a sort of a hacked workaround for the problem.
*/
static int enable_autorefresh = 1;

void
X11_DisableAutoRefresh(_THIS)
{
    --enable_autorefresh;
}

void
X11_EnableAutoRefresh(_THIS)
{
    ++enable_autorefresh;
}

void
X11_RefreshDisplay(_THIS)
{
    /* Don't refresh a display that doesn't have an image (like GL)
       Instead, post an expose event so the application can refresh.
     */
    if (!SDL_Ximage || (enable_autorefresh <= 0)) {
        SDL_PrivateExpose();
        return;
    }
#ifndef NO_SHARED_MEMORY
    if (this->UpdateRects == X11_MITSHMUpdate) {
        XShmPutImage(SDL_Display, SDL_Window, SDL_GC, SDL_Ximage,
                     0, 0, SDL_CurrentWindow.offset_x,
                     SDL_CurrentWindow.offset_y,
                     SDL_CurrentDisplay.current_mode.w,
                     SDL_CurrentDisplay.current_mode.h, False);
    } else
#endif /* ! NO_SHARED_MEMORY */
    {
        XPutImage(SDL_Display, SDL_Window, SDL_GC, SDL_Ximage,
                  0, 0, SDL_CurrentWindow.offset_x,
                  SDL_CurrentWindow.offset_y,
                  SDL_CurrentDisplay.current_mode.w,
                  SDL_CurrentDisplay.current_mode.h);
    }
    XSync(SDL_Display, False);
}

/* vi: set ts=4 sw=4 expandtab: */
