/*
 * Copyright © 2008 George Sapountzis <gsap7@yahoo.gr>
 * Copyright © 2008 Red Hat, Inc
 *
 * Permission to use, copy, modify, distribute, and sell this software
 * and its documentation for any purpose is hereby granted without
 * fee, provided that the above copyright notice appear in all copies
 * and that both that copyright notice and this permission notice
 * appear in supporting documentation, and that the name of the
 * copyright holders not be used in advertising or publicity
 * pertaining to distribution of the software without specific,
 * written prior permission.  The copyright holders make no
 * representations about the suitability of this software for any
 * purpose.  It is provided "as is" without express or implied
 * warranty.
 *
 * THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS
 * SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
 * FITNESS, IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY
 * SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN
 * AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING
 * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
 * SOFTWARE.
 */

#ifdef HAVE_DIX_CONFIG_H
#include <dix-config.h>
#endif
#include "glheader.h"

#include <stdint.h>
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <sys/time.h>
#include <dlfcn.h>

#include <GL/gl.h>
#include <GL/internal/dri_interface.h>
#include <GL/glxtokens.h>

#include "scrnintstr.h"
#include "pixmapstr.h"
#include "gcstruct.h"
#include "os.h"

#include "glxserver.h"
#include "glxutil.h"
#include "glxdricommon.h"

#include "glapitable.h"
#include "glapi.h"
#include "glthread.h"
#include "dispatch.h"
#include "extension_string.h"

typedef struct __GLXDRIscreen __GLXDRIscreen;
typedef struct __GLXDRIcontext __GLXDRIcontext;
typedef struct __GLXDRIdrawable __GLXDRIdrawable;

struct __GLXDRIscreen {
    __GLXscreen base;
    __DRIscreen *driScreen;
    void *driver;

    const __DRIcoreExtension *core;
    const __DRIswrastExtension *swrast;
    const __DRIcopySubBufferExtension *copySubBuffer;
    const __DRItexBufferExtension *texBuffer;
    const __DRIconfig **driConfigs;
};

struct __GLXDRIcontext {
    __GLXcontext base;
    __DRIcontext *driContext;
};

struct __GLXDRIdrawable {
    __GLXdrawable base;
    __DRIdrawable *driDrawable;
    __GLXDRIscreen *screen;

    GCPtr gc;                   /* scratch GC for span drawing */
    GCPtr swapgc;               /* GC for swapping the color buffers */
};

static void
__glXDRIdrawableDestroy(__GLXdrawable * drawable)
{
    __GLXDRIdrawable *private = (__GLXDRIdrawable *) drawable;
    const __DRIcoreExtension *core = private->screen->core;

    (*core->destroyDrawable) (private->driDrawable);

    FreeGC(private->gc, (GContext) 0);
    FreeGC(private->swapgc, (GContext) 0);

    __glXDrawableRelease(drawable);

    free(private);
}

static GLboolean
__glXDRIdrawableSwapBuffers(ClientPtr client, __GLXdrawable * drawable)
{
    __GLXDRIdrawable *private = (__GLXDRIdrawable *) drawable;
    const __DRIcoreExtension *core = private->screen->core;

    (*core->swapBuffers) (private->driDrawable);

    return TRUE;
}

static void
__glXDRIdrawableCopySubBuffer(__GLXdrawable * basePrivate,
                              int x, int y, int w, int h)
{
    __GLXDRIdrawable *private = (__GLXDRIdrawable *) basePrivate;
    const __DRIcopySubBufferExtension *copySubBuffer =
        private->screen->copySubBuffer;

    if (copySubBuffer)
        (*copySubBuffer->copySubBuffer) (private->driDrawable, x, y, w, h);
}

static void
__glXDRIcontextDestroy(__GLXcontext * baseContext)
{
    __GLXDRIcontext *context = (__GLXDRIcontext *) baseContext;
    __GLXDRIscreen *screen = (__GLXDRIscreen *) context->base.pGlxScreen;

    (*screen->core->destroyContext) (context->driContext);
    __glXContextDestroy(&context->base);
    free(context);
}

static int
__glXDRIcontextMakeCurrent(__GLXcontext * baseContext)
{
    __GLXDRIcontext *context = (__GLXDRIcontext *) baseContext;
    __GLXDRIdrawable *draw = (__GLXDRIdrawable *) baseContext->drawPriv;
    __GLXDRIdrawable *read = (__GLXDRIdrawable *) baseContext->readPriv;
    __GLXDRIscreen *screen = (__GLXDRIscreen *) context->base.pGlxScreen;

    return (*screen->core->bindContext) (context->driContext,
                                         draw->driDrawable, read->driDrawable);
}

static int
__glXDRIcontextLoseCurrent(__GLXcontext * baseContext)
{
    __GLXDRIcontext *context = (__GLXDRIcontext *) baseContext;
    __GLXDRIscreen *screen = (__GLXDRIscreen *) context->base.pGlxScreen;

    return (*screen->core->unbindContext) (context->driContext);
}

static int
__glXDRIcontextCopy(__GLXcontext * baseDst, __GLXcontext * baseSrc,
                    unsigned long mask)
{
    __GLXDRIcontext *dst = (__GLXDRIcontext *) baseDst;
    __GLXDRIcontext *src = (__GLXDRIcontext *) baseSrc;
    __GLXDRIscreen *screen = (__GLXDRIscreen *) dst->base.pGlxScreen;

    return (*screen->core->copyContext) (dst->driContext,
                                         src->driContext, mask);
}

#ifdef __DRI_TEX_BUFFER

static int
__glXDRIbindTexImage(__GLXcontext * baseContext,
                     int buffer, __GLXdrawable * glxPixmap)
{
    __GLXDRIdrawable *drawable = (__GLXDRIdrawable *) glxPixmap;
    const __DRItexBufferExtension *texBuffer = drawable->screen->texBuffer;
    __GLXDRIcontext *context = (__GLXDRIcontext *) baseContext;

    if (texBuffer == NULL)
        return Success;

#if __DRI_TEX_BUFFER_VERSION >= 2
    if (texBuffer->base.version >= 2 && texBuffer->setTexBuffer2 != NULL) {
        (*texBuffer->setTexBuffer2) (context->driContext,
                                     glxPixmap->target,
                                     glxPixmap->format, drawable->driDrawable);
    }
    else
#endif
        texBuffer->setTexBuffer(context->driContext,
                                glxPixmap->target, drawable->driDrawable);

    return Success;
}

static int
__glXDRIreleaseTexImage(__GLXcontext * baseContext,
                        int buffer, __GLXdrawable * pixmap)
{
    /* FIXME: Just unbind the texture? */
    return Success;
}

#else

static int
__glXDRIbindTexImage(__GLXcontext * baseContext,
                     int buffer, __GLXdrawable * glxPixmap)
{
    return Success;
}

static int
__glXDRIreleaseTexImage(__GLXcontext * baseContext,
                        int buffer, __GLXdrawable * pixmap)
{
    return Success;
}

#endif

static __GLXtextureFromPixmap __glXDRItextureFromPixmap = {
    __glXDRIbindTexImage,
    __glXDRIreleaseTexImage
};

static void
__glXDRIscreenDestroy(__GLXscreen * baseScreen)
{
    int i;

    __GLXDRIscreen *screen = (__GLXDRIscreen *) baseScreen;

    (*screen->core->destroyScreen) (screen->driScreen);

#ifdef _MSC_VER
    FreeLibrary(screen->driver);
#else
    dlclose(screen->driver);
#endif

    __glXScreenDestroy(baseScreen);

    if (screen->driConfigs) {
        for (i = 0; screen->driConfigs[i] != NULL; i++)
            free((__DRIconfig **) screen->driConfigs[i]);
        free((void*)screen->driConfigs);
    }

    free(screen);
}

static __GLXcontext *
__glXDRIscreenCreateContext(__GLXscreen * baseScreen,
                            __GLXconfig * glxConfig,
                            __GLXcontext * baseShareContext,
                            unsigned num_attribs,
                            const uint32_t *attribs,
                            int *error)
{
    __GLXDRIscreen *screen = (__GLXDRIscreen *) baseScreen;
    __GLXDRIcontext *context, *shareContext;
    __GLXDRIconfig *config = (__GLXDRIconfig *) glxConfig;
    const __DRIcoreExtension *core = screen->core;
    __DRIcontext *driShare;

    /* DRISWRAST won't support createContextAttribs, so these parameters will
     * never be used.
     */
    (void) num_attribs;
    (void) attribs;
    (void) error;

    shareContext = (__GLXDRIcontext *) baseShareContext;
    if (shareContext)
        driShare = shareContext->driContext;
    else
        driShare = NULL;

    context = calloc(1, sizeof *context);
    if (context == NULL)
        return NULL;

    context->base.destroy = __glXDRIcontextDestroy;
    context->base.makeCurrent = __glXDRIcontextMakeCurrent;
    context->base.loseCurrent = __glXDRIcontextLoseCurrent;
    context->base.copy = __glXDRIcontextCopy;
    context->base.textureFromPixmap = &__glXDRItextureFromPixmap;

    context->driContext =
        (*core->createNewContext) (screen->driScreen,
                                   config->driConfig, driShare, context);

    return &context->base;
}

static __GLXdrawable *
__glXDRIscreenCreateDrawable(ClientPtr client,
                             __GLXscreen * screen,
                             DrawablePtr pDraw,
                             XID drawId,
                             int type, XID glxDrawId, __GLXconfig * glxConfig)
{
    XID gcvals[2];
    int status;
    __GLXDRIscreen *driScreen = (__GLXDRIscreen *) screen;
    __GLXDRIconfig *config = (__GLXDRIconfig *) glxConfig;
    __GLXDRIdrawable *private;

    private = calloc(1, sizeof *private);
    if (private == NULL)
        return NULL;

    private->screen = driScreen;
    if (!__glXDrawableInit(&private->base, screen,
                           pDraw, type, glxDrawId, glxConfig)) {
        free(private);
        return NULL;
    }

    private->base.destroy = __glXDRIdrawableDestroy;
    private->base.swapBuffers = __glXDRIdrawableSwapBuffers;
    private->base.copySubBuffer = __glXDRIdrawableCopySubBuffer;

    gcvals[0] = GXcopy;
    private->gc =
        CreateGC(pDraw, GCFunction, gcvals, &status, (XID) 0, serverClient);
    gcvals[1] = FALSE;
    private->swapgc =
        CreateGC(pDraw, GCFunction | GCGraphicsExposures, gcvals, &status,
                 (XID) 0, serverClient);

    private->driDrawable =
        (*driScreen->swrast->createNewDrawable) (driScreen->driScreen,
                                                 config->driConfig, private);
    if (!private->driDrawable)
    {
        FreeGC(private->gc, (GContext)0);
        FreeGC(private->swapgc, (GContext)0);
        free(private);
        return NULL;
    }

    return &private->base;
}

static void
swrastGetDrawableInfo(__DRIdrawable * draw,
                      int *x, int *y, int *w, int *h, void *loaderPrivate)
{
    __GLXDRIdrawable *drawable = loaderPrivate;
    DrawablePtr pDraw = drawable->base.pDraw;

    *x = pDraw->x;
    *y = pDraw->x;
    *w = pDraw->width;
    *h = pDraw->height;
}

static void
swrastPutImage(__DRIdrawable * draw, int op,
               int x, int y, int w, int h, char *data, void *loaderPrivate)
{
    __GLXDRIdrawable *drawable = loaderPrivate;
    DrawablePtr pDraw = drawable->base.pDraw;
    GCPtr gc;

    switch (op) {
    case __DRI_SWRAST_IMAGE_OP_DRAW:
        gc = drawable->gc;
        break;
    case __DRI_SWRAST_IMAGE_OP_SWAP:
        gc = drawable->swapgc;
        break;
    default:
        return;
    }

    ValidateGC(pDraw, gc);

    gc->ops->PutImage(pDraw, gc, pDraw->depth, x, y, w, h, 0, ZPixmap, data);
}

static void
swrastGetImage(__DRIdrawable * draw,
               int x, int y, int w, int h, char *data, void *loaderPrivate)
{
    __GLXDRIdrawable *drawable = loaderPrivate;
    DrawablePtr pDraw = drawable->base.pDraw;
    ScreenPtr pScreen = pDraw->pScreen;

    pScreen->GetImage(pDraw, x, y, w, h, ZPixmap, ~0L, data);
}

static const __DRIswrastLoaderExtension swrastLoaderExtension = {
    {__DRI_SWRAST_LOADER, __DRI_SWRAST_LOADER_VERSION},
    swrastGetDrawableInfo,
    swrastPutImage,
    swrastGetImage
};

static const __DRIextension *loader_extensions[] = {
    &systemTimeExtension.base,
    &swrastLoaderExtension.base,
    NULL
};

static void
initializeExtensions(__GLXDRIscreen * screen)
{
    const __DRIextension **extensions;
    int i;

    extensions = screen->core->getExtensions(screen->driScreen);

    for (i = 0; extensions[i]; i++) {
#ifdef __DRI_COPY_SUB_BUFFER
        if (strcmp(extensions[i]->name, __DRI_COPY_SUB_BUFFER) == 0) {
            screen->copySubBuffer =
                (const __DRIcopySubBufferExtension *) extensions[i];
            /* GLX_MESA_copy_sub_buffer is always enabled. */
        }
#endif

#ifdef __DRI_TEX_BUFFER
        if (strcmp(extensions[i]->name, __DRI_TEX_BUFFER) == 0) {
            screen->texBuffer = (const __DRItexBufferExtension *) extensions[i];
            /* GLX_EXT_texture_from_pixmap is always enabled. */
        }
#endif
        /* Ignore unknown extensions */
    }
}

extern Bool g_fswrastwgl;

static __GLXscreen *
__glXDRIscreenProbe(ScreenPtr pScreen)
{
    const char *driverName;
    __GLXDRIscreen *screen;
    
    if (g_fswrastwgl)
      driverName = "swrastwgl";
    else
      driverName = "swrast";

    screen = calloc(1, sizeof *screen);
    if (screen == NULL)
        return NULL;

    screen->base.destroy = __glXDRIscreenDestroy;
    screen->base.createContext = __glXDRIscreenCreateContext;
    screen->base.createDrawable = __glXDRIscreenCreateDrawable;
    screen->base.swapInterval = NULL;
    screen->base.pScreen = pScreen;

    screen->driver = glxProbeDriver(driverName,
                                    (void **) &screen->core,
                                    __DRI_CORE, __DRI_CORE_VERSION,
                                    (void **) &screen->swrast,
                                    __DRI_SWRAST, __DRI_SWRAST_VERSION);
    if (screen->driver == NULL) {
        goto handle_error;
    }

    screen->driScreen =
        (*screen->swrast->createNewScreen) (pScreen->myNum,
                                            loader_extensions,
                                            &screen->driConfigs, screen);

    if (screen->driScreen == NULL) {
        LogMessage(X_ERROR, "AIGLX error: Calling driver entry point failed\n");
        goto handle_error;
    }

    initializeExtensions(screen);

    screen->base.fbconfigs = glxConvertConfigs(screen->core, screen->driConfigs,
                                               GLX_WINDOW_BIT |
                                               GLX_PIXMAP_BIT |
                                               GLX_PBUFFER_BIT);

    __glXScreenInit(&screen->base, pScreen);

    screen->base.GLXmajor = 1;
    screen->base.GLXminor = 4;

    LogMessage(X_INFO, "AIGLX: Loaded and initialized %s\n", driverName);

    return &screen->base;

 handle_error:
    if (screen->driver)
#ifdef _MSC_VER
        FreeLibrary(screen->driver);
#else
        dlclose(screen->driver);
#endif

    free(screen);

    LogMessage(X_ERROR, "GLX: could not load software renderer\n");

    return NULL;
}

_X_EXPORT __GLXprovider __glXDRISWRastProvider = {
    __glXDRIscreenProbe,
    "DRISWRAST",
    NULL
};