/*
 * 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 <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"

/* RTLD_LOCAL is not defined on Cygwin */
#ifdef __CYGWIN__
#ifndef RTLD_LOCAL
#define RTLD_LOCAL 0
#endif
#endif

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;
};

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)
{
    __GLXDRIscreen *screen = (__GLXDRIscreen *) baseScreen;

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

    dlclose(screen->driver);

    __glXScreenDestroy(baseScreen);

    free(screen);
}

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

    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);

    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 */
    }
}

static __GLXscreen *
__glXDRIscreenProbe(ScreenPtr pScreen)
{
    const char *driverName = "swrast";
    __GLXDRIscreen *screen;
    const __DRIconfig **driConfigs;

    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,
					   &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, 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)
        dlclose(screen->driver);

    free(screen);

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

    return NULL;
}

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