/*
 * Copyright 2008 George Sapountzis
 *
 * 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 (including the next
 * paragraph) shall be included in all copies or substantial portions of the
 * Software.
 *
 * 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 OR COPYRIGHT HOLDERS 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.
 */

#if defined(GLX_DIRECT_RENDERING) && !defined(GLX_USE_APPLEGL)

#include <X11/Xlib.h>
#include "glxclient.h"
#include <dlfcn.h>
#include "dri_common.h"

typedef struct __GLXDRIdisplayPrivateRec __GLXDRIdisplayPrivate;
typedef struct __GLXDRIcontextPrivateRec __GLXDRIcontextPrivate;
typedef struct __GLXDRIdrawablePrivateRec __GLXDRIdrawablePrivate;

struct __GLXDRIdisplayPrivateRec
{
   __GLXDRIdisplay base;
};

struct __GLXDRIcontextPrivateRec
{
   __GLXDRIcontext base;
   __DRIcontext *driContext;
   __GLXscreenConfigs *psc;
};

struct __GLXDRIdrawablePrivateRec
{
   __GLXDRIdrawable base;

   GC gc;
   GC swapgc;

   XVisualInfo *visinfo;
   XImage *ximage;
   int bpp;
};

/**
 * swrast loader functions
 */

static Bool
XCreateDrawable(__GLXDRIdrawablePrivate * pdp,
                Display * dpy, XID drawable, int visualid)
{
   XGCValues gcvalues;
   long visMask;
   XVisualInfo visTemp;
   int num_visuals;

   /* create GC's */
   pdp->gc = XCreateGC(dpy, drawable, 0, NULL);
   pdp->swapgc = XCreateGC(dpy, drawable, 0, NULL);

   gcvalues.function = GXcopy;
   gcvalues.graphics_exposures = False;
   XChangeGC(dpy, pdp->gc, GCFunction, &gcvalues);
   XChangeGC(dpy, pdp->swapgc, GCFunction, &gcvalues);
   XChangeGC(dpy, pdp->swapgc, GCGraphicsExposures, &gcvalues);

   /* create XImage  */
   visTemp.screen = DefaultScreen(dpy);
   visTemp.visualid = visualid;
   visMask = (VisualScreenMask | VisualIDMask);
   pdp->visinfo = XGetVisualInfo(dpy, visMask, &visTemp, &num_visuals);

   pdp->ximage = XCreateImage(dpy, pdp->visinfo->visual, pdp->visinfo->depth, ZPixmap, 0,       /* format, offset */
                              NULL,     /* data */
                              0, 0,     /* size */
                              32,       /* bitmap_pad */
                              0);       /* bytes_per_line */

   /* get the true number of bits per pixel */
   pdp->bpp = pdp->ximage->bits_per_pixel;

   return True;
}

static void
XDestroyDrawable(__GLXDRIdrawablePrivate * pdp, Display * dpy, XID drawable)
{
   XDestroyImage(pdp->ximage);
   XFree(pdp->visinfo);

   XFreeGC(dpy, pdp->gc);
   XFreeGC(dpy, pdp->swapgc);
}

static void
swrastGetDrawableInfo(__DRIdrawable * draw,
                      int *x, int *y, int *w, int *h, void *loaderPrivate)
{
   __GLXDRIdrawablePrivate *pdp = loaderPrivate;
   __GLXDRIdrawable *pdraw = &(pdp->base);
   Display *dpy = pdraw->psc->dpy;
   Drawable drawable;

   Window root;
   Status stat;
   unsigned int bw, depth;

   drawable = pdraw->xDrawable;

   stat = XGetGeometry(dpy, drawable, &root,
                       x, y, (unsigned int *) w, (unsigned int *) h,
                       &bw, &depth);
}

static inline int
bytes_per_line(int w, int bpp, unsigned mul)
{
   unsigned mask = mul - 1;

   return ((w * bpp + mask) & ~mask) / 8;
}

static void
swrastPutImage(__DRIdrawable * draw, int op,
               int x, int y, int w, int h, char *data, void *loaderPrivate)
{
   __GLXDRIdrawablePrivate *pdp = loaderPrivate;
   __GLXDRIdrawable *pdraw = &(pdp->base);
   Display *dpy = pdraw->psc->dpy;
   Drawable drawable;
   XImage *ximage;
   GC gc;

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

   drawable = pdraw->xDrawable;

   ximage = pdp->ximage;
   ximage->data = data;
   ximage->width = w;
   ximage->height = h;
   ximage->bytes_per_line = bytes_per_line(w, pdp->bpp, 32);

   XPutImage(dpy, drawable, gc, ximage, 0, 0, x, y, w, h);

   ximage->data = NULL;
}

static void
swrastGetImage(__DRIdrawable * draw,
               int x, int y, int w, int h, char *data, void *loaderPrivate)
{
   __GLXDRIdrawablePrivate *pdp = loaderPrivate;
   __GLXDRIdrawable *pdraw = &(pdp->base);
   Display *dpy = pdraw->psc->dpy;
   Drawable drawable;
   XImage *ximage;

   drawable = pdraw->xDrawable;

   ximage = pdp->ximage;
   ximage->data = data;
   ximage->width = w;
   ximage->height = h;
   ximage->bytes_per_line = bytes_per_line(w, pdp->bpp, 32);

   XGetSubImage(dpy, drawable, x, y, w, h, ~0L, ZPixmap, ximage, 0, 0);

   ximage->data = NULL;
}

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

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

/**
 * GLXDRI functions
 */

static void
driDestroyContext(__GLXDRIcontext * context,
                  __GLXscreenConfigs * psc, Display * dpy)
{
   __GLXDRIcontextPrivate *pcp = (__GLXDRIcontextPrivate *) context;
   const __DRIcoreExtension *core = pcp->psc->core;

   (*core->destroyContext) (pcp->driContext);

   Xfree(pcp);
}

static Bool
driBindContext(__GLXDRIcontext * context,
               __GLXDRIdrawable * draw, __GLXDRIdrawable * read)
{
   __GLXDRIcontextPrivate *pcp = (__GLXDRIcontextPrivate *) context;
   const __DRIcoreExtension *core = pcp->psc->core;

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

static void
driUnbindContext(__GLXDRIcontext * context)
{
   __GLXDRIcontextPrivate *pcp = (__GLXDRIcontextPrivate *) context;
   const __DRIcoreExtension *core = pcp->psc->core;

   (*core->unbindContext) (pcp->driContext);
}

static __GLXDRIcontext *
driCreateContext(__GLXscreenConfigs * psc,
                 const __GLcontextModes * mode,
                 GLXContext gc, GLXContext shareList, int renderType)
{
   __GLXDRIcontextPrivate *pcp, *pcp_shared;
   __GLXDRIconfigPrivate *config = (__GLXDRIconfigPrivate *) mode;
   const __DRIcoreExtension *core;
   __DRIcontext *shared = NULL;

   if (!psc || !psc->driScreen)
      return NULL;

   core = psc->core;

   if (shareList) {
      pcp_shared = (__GLXDRIcontextPrivate *) shareList->driContext;
      shared = pcp_shared->driContext;
   }

   pcp = Xmalloc(sizeof *pcp);
   if (pcp == NULL)
      return NULL;

   pcp->psc = psc;
   pcp->driContext =
      (*core->createNewContext) (psc->__driScreen,
                                 config->driConfig, shared, pcp);
   if (pcp->driContext == NULL) {
      Xfree(pcp);
      return NULL;
   }

   pcp->base.destroyContext = driDestroyContext;
   pcp->base.bindContext = driBindContext;
   pcp->base.unbindContext = driUnbindContext;

   return &pcp->base;
}

static void
driDestroyDrawable(__GLXDRIdrawable * pdraw)
{
   __GLXDRIdrawablePrivate *pdp = (__GLXDRIdrawablePrivate *) pdraw;
   const __DRIcoreExtension *core = pdraw->psc->core;

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

   XDestroyDrawable(pdp, pdraw->psc->dpy, pdraw->drawable);
   Xfree(pdp);
}

static __GLXDRIdrawable *
driCreateDrawable(__GLXscreenConfigs * psc,
                  XID xDrawable,
                  GLXDrawable drawable, const __GLcontextModes * modes)
{
   __GLXDRIdrawable *pdraw;
   __GLXDRIdrawablePrivate *pdp;
   __GLXDRIconfigPrivate *config = (__GLXDRIconfigPrivate *) modes;
   const __DRIswrastExtension *swrast = psc->swrast;

   /* Old dri can't handle GLX 1.3+ drawable constructors. */
   if (xDrawable != drawable)
      return NULL;

   pdp = Xmalloc(sizeof(*pdp));
   if (!pdp)
      return NULL;

   pdraw = &(pdp->base);
   pdraw->xDrawable = xDrawable;
   pdraw->drawable = drawable;
   pdraw->psc = psc;

   XCreateDrawable(pdp, psc->dpy, xDrawable, modes->visualID);

   /* Create a new drawable */
   pdraw->driDrawable =
      (*swrast->createNewDrawable) (psc->__driScreen, config->driConfig, pdp);

   if (!pdraw->driDrawable) {
      XDestroyDrawable(pdp, psc->dpy, xDrawable);
      Xfree(pdp);
      return NULL;
   }

   pdraw->destroyDrawable = driDestroyDrawable;

   return pdraw;
}

static void
driSwapBuffers(__GLXDRIdrawable * pdraw)
{
   (*pdraw->psc->core->swapBuffers) (pdraw->driDrawable);
}

static void
driDestroyScreen(__GLXscreenConfigs * psc)
{
   /* Free the direct rendering per screen data */
   (*psc->core->destroyScreen) (psc->__driScreen);
   psc->__driScreen = NULL;
   if (psc->driver)
      dlclose(psc->driver);
}

static __GLXDRIscreen *
driCreateScreen(__GLXscreenConfigs * psc, int screen,
                __GLXdisplayPrivate * priv)
{
   __GLXDRIscreen *psp;
   const __DRIconfig **driver_configs;
   const __DRIextension **extensions;
   const char *driverName = "swrast";
   int i;

   psp = Xcalloc(1, sizeof *psp);
   if (psp == NULL)
      return NULL;

   psc->driver = driOpenDriver(driverName);
   if (psc->driver == NULL)
      goto handle_error;

   extensions = dlsym(psc->driver, __DRI_DRIVER_EXTENSIONS);
   if (extensions == NULL) {
      ErrorMessageF("driver exports no extensions (%s)\n", dlerror());
      goto handle_error;
   }

   for (i = 0; extensions[i]; i++) {
      if (strcmp(extensions[i]->name, __DRI_CORE) == 0)
         psc->core = (__DRIcoreExtension *) extensions[i];
      if (strcmp(extensions[i]->name, __DRI_SWRAST) == 0)
         psc->swrast = (__DRIswrastExtension *) extensions[i];
   }

   if (psc->core == NULL || psc->swrast == NULL) {
      ErrorMessageF("core dri extension not found\n");
      goto handle_error;
   }

   psc->__driScreen =
      psc->swrast->createNewScreen(screen,
                                   loader_extensions, &driver_configs, psc);
   if (psc->__driScreen == NULL) {
      ErrorMessageF("failed to create dri screen\n");
      goto handle_error;
   }

   driBindExtensions(psc);
   driBindCommonExtensions(psc);

   psc->configs = driConvertConfigs(psc->core, psc->configs, driver_configs);
   psc->visuals = driConvertConfigs(psc->core, psc->visuals, driver_configs);

   psc->driver_configs = driver_configs;

   psp->destroyScreen = driDestroyScreen;
   psp->createContext = driCreateContext;
   psp->createDrawable = driCreateDrawable;
   psp->swapBuffers = driSwapBuffers;
   psp->waitX = NULL;
   psp->waitGL = NULL;

   return psp;

 handle_error:
   Xfree(psp);

   if (psc->driver)
      dlclose(psc->driver);

   ErrorMessageF("reverting to indirect rendering\n");

   return NULL;
}

/* Called from __glXFreeDisplayPrivate.
 */
static void
driDestroyDisplay(__GLXDRIdisplay * dpy)
{
   Xfree(dpy);
}

/*
 * Allocate, initialize and return a __DRIdisplayPrivate object.
 * This is called from __glXInitialize() when we are given a new
 * display pointer.
 */
_X_HIDDEN __GLXDRIdisplay *
driswCreateDisplay(Display * dpy)
{
   __GLXDRIdisplayPrivate *pdpyp;

   pdpyp = Xmalloc(sizeof *pdpyp);
   if (pdpyp == NULL)
      return NULL;

   pdpyp->base.destroyDisplay = driDestroyDisplay;
   pdpyp->base.createScreen = driCreateScreen;

   return &pdpyp->base;
}

#endif /* GLX_DIRECT_RENDERING */
