/*
 * Mesa 3-D graphics library
 * Version: 0.1
 *
 * Copyright (C) 1999-2002  Brian Paul   All Rights Reserved.
 *
 * 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 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
 * BRIAN PAUL 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.
 */


/* Minimal swrast-based DRI-loadable driver.
 *
 * Derived from fb_dri.c, the difference being that one works for
 * framebuffers without X, whereas this points Mesa at an X surface
 * to draw on.
 *
 * This is basically just a wrapper around src/mesa/drivers/x11 to make it
 * look like a DRI driver.
 */

#define GLX_DIRECT_RENDERING

#include <assert.h>
#include <errno.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <dlfcn.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <sys/mman.h>
#include <sys/types.h>
#include <sys/shm.h>
#include <sys/stat.h>
#include <linux/kd.h>
#include <linux/vt.h>

#include "dri_util.h"

#include "GL/xmesa.h"
#include "xmesaP.h"

#include "mtypes.h"
#include "context.h"
#include "extensions.h"
#include "imports.h"
#include "matrix.h"
#include "texformat.h"
#include "texstore.h"
#include "teximage.h"
#include "array_cache/acache.h"
#include "swrast/swrast.h"
#include "swrast_setup/swrast_setup.h"
#include "tnl/tnl.h"
#include "tnl/t_context.h"
#include "tnl/t_pipeline.h"
#include "drivers/common/driverfuncs.h"

#include "x11_dri.h"

typedef struct {
   GLcontext *glCtx;		/* Mesa context */

   struct {
      __DRIcontextPrivate *context;	
      __DRIscreenPrivate *screen;	
      __DRIdrawablePrivate *drawable; /* drawable bound to this ctx */
   } dri;
} x11Context, *x11ContextPtr;

#define X11_CONTEXT(ctx)		((x11ContextPtr)(ctx->DriverCtx))

static const GLubyte *
get_string(GLcontext *ctx, GLenum pname)
{
   (void) ctx;
   switch (pname) {
      case GL_RENDERER:
         return (const GLubyte *) "Mesa X11 hack";
      default:
         return NULL;
   }
}

static void
update_state(GLcontext *ctx, GLuint new_state)
{
   /* not much to do here - pass it on */
   _swrast_InvalidateState(ctx, new_state);
   _swsetup_InvalidateState(ctx, new_state);
   _ac_InvalidateState(ctx, new_state);
   _tnl_InvalidateState(ctx, new_state);
}

/**
 * Called by ctx->Driver.GetBufferSize from in core Mesa to query the
 * current framebuffer size.
 */
static void
get_buffer_size(GLframebuffer *buffer, GLuint *width, GLuint *height)
{
   GET_CURRENT_CONTEXT(ctx);
   x11ContextPtr x11mesa = X11_CONTEXT(ctx);

   *width  = x11mesa->dri.drawable->w;
   *height = x11mesa->dri.drawable->h;
}

static void
init_core_functions(struct dd_function_table *functions)
{
   functions->GetString = get_string;
   functions->UpdateState = update_state;
   functions->ResizeBuffers = _swrast_alloc_buffers;
   functions->GetBufferSize = get_buffer_size;

   functions->Clear = _swrast_Clear;  /* could accelerate with blits */
}

/* Initialize the driver specific screen private data.
 */
static GLboolean
x11InitDriver(__DRIscreenPrivate *sPriv)
{
   sPriv->private = NULL;
   return GL_TRUE;
}

static void
x11DestroyScreen(__DRIscreenPrivate *sPriv)
{
}

/* placeholders, disables rendering */
static void
nullwrite(void *a, int b, int c, int d, void *e, void *f)
{
}

static void
set_buffer( GLcontext *ctx, GLframebuffer *buffer, GLuint bufferBit )
{
}

/* Create the device specific context. */
static GLboolean
x11CreateContext(const __GLcontextModes *glVisual,
		 __DRIcontextPrivate *driContextPriv,
		 void *sharedContextPrivate)
{
   x11ContextPtr x11mesa;
   GLcontext *ctx, *shareCtx;
   struct dd_function_table functions;

   assert(glVisual);
   assert(driContextPriv);

   /* Allocate the Fb context */
   x11mesa = (x11ContextPtr) CALLOC(sizeof(*x11mesa));
   if (!x11mesa)
      return GL_FALSE;

   /* Init default driver functions then plug in our own functions */
   _mesa_init_driver_functions(&functions);
   init_core_functions(&functions);

   /* Allocate the Mesa context */
   if (sharedContextPrivate)
      shareCtx = ((x11ContextPtr) sharedContextPrivate)->glCtx;
   else
      shareCtx = NULL;

   ctx = x11mesa->glCtx = _mesa_create_context(glVisual, shareCtx, 
					      &functions, (void *) x11mesa);
   if (!x11mesa->glCtx) {
      FREE(x11mesa);
      return GL_FALSE;
   }
   driContextPriv->driverPrivate = x11mesa;

   /* Create module contexts */
   _swrast_CreateContext(ctx);
   _ac_CreateContext(ctx);
   _tnl_CreateContext(ctx);
   _swsetup_CreateContext(ctx);
   _swsetup_Wakeup(ctx);

   /* swrast init */
   {
      struct swrast_device_driver *swdd;
      swdd = _swrast_GetDeviceDriverReference(ctx);
      swdd->SetBuffer = set_buffer;
      if (!glVisual->rgbMode) {
         swdd->WriteCI32Span = 
         swdd->WriteCI32Span = 
         swdd->WriteCI8Span = 
         swdd->WriteMonoCISpan = 
         swdd->WriteCI32Pixels = 
         swdd->WriteMonoCIPixels = 
         swdd->ReadCI32Span = 
         swdd->ReadCI32Pixels = nullwrite;
      }
      else if (glVisual->rgbBits == 24 &&
	       glVisual->alphaBits == 0) {
         swdd->WriteRGBASpan = 
         swdd->WriteRGBSpan = 
         swdd->WriteMonoRGBASpan = 
         swdd->WriteRGBAPixels = 
         swdd->WriteMonoRGBAPixels = 
         swdd->ReadRGBASpan = 
         swdd->ReadRGBAPixels = nullwrite;
      }
      else if (glVisual->rgbBits == 32 &&
	       glVisual->alphaBits == 8) {
         swdd->WriteRGBASpan = 
         swdd->WriteRGBSpan = 
         swdd->WriteMonoRGBASpan = 
         swdd->WriteRGBAPixels = 
         swdd->WriteMonoRGBAPixels = 
         swdd->ReadRGBASpan = 
         swdd->ReadRGBAPixels = nullwrite;
      }
      else if (glVisual->rgbBits == 16 &&
	       glVisual->alphaBits == 0) {
         swdd->WriteRGBASpan = 
         swdd->WriteRGBSpan = 
         swdd->WriteMonoRGBASpan = 
         swdd->WriteRGBAPixels = 
         swdd->WriteMonoRGBAPixels = 
         swdd->ReadRGBASpan = 
         swdd->ReadRGBAPixels = nullwrite;
      }
      else if (glVisual->rgbBits == 15 &&
	       glVisual->alphaBits == 0) {
         swdd->WriteRGBASpan = 
         swdd->WriteRGBSpan = 
         swdd->WriteMonoRGBASpan = 
         swdd->WriteRGBAPixels = 
         swdd->WriteMonoRGBAPixels = 
         swdd->ReadRGBASpan = 
         swdd->ReadRGBAPixels = nullwrite;
      }
      else {
         _mesa_printf("bad pixelformat rgb %d alpha %d\n",
		      glVisual->rgbBits, 
		      glVisual->alphaBits );
      }
   }

   /* use default TCL pipeline */
   {
      TNLcontext *tnl = TNL_CONTEXT(ctx);
      tnl->Driver.RunPipeline = _tnl_run_pipeline;
   }

   _mesa_enable_sw_extensions(ctx);

   return GL_TRUE;
}


static void
x11DestroyContext(__DRIcontextPrivate *driContextPriv)
{
   GET_CURRENT_CONTEXT(ctx);
   x11ContextPtr x11mesa = (x11ContextPtr) driContextPriv->driverPrivate;
   x11ContextPtr current = ctx ? X11_CONTEXT(ctx) : NULL;

   /* check if we're deleting the currently bound context */
   if (x11mesa == current) {
      _mesa_make_current2(NULL, NULL, NULL);
   }

   /* Free x11 context resources */
   if (x11mesa) {
      _swsetup_DestroyContext(x11mesa->glCtx);
      _tnl_DestroyContext(x11mesa->glCtx);
      _ac_DestroyContext(x11mesa->glCtx);
      _swrast_DestroyContext(x11mesa->glCtx);

      /* free the Mesa context */
      x11mesa->glCtx->DriverCtx = NULL;
      _mesa_destroy_context(x11mesa->glCtx);

      FREE(x11mesa);
   }
}


/* Create and initialize the Mesa and driver specific pixmap buffer
 * data.
 */
static GLboolean
x11CreateBuffer(__DRIscreenPrivate *driScrnPriv,
		__DRIdrawablePrivate *driDrawPriv,
		const __GLcontextModes *mesaVis,
		GLboolean isPixmap)
{
   if (isPixmap) {
      return GL_FALSE; /* not implemented */
   }
   else {
      const GLboolean swDepth = mesaVis->depthBits > 0;
      const GLboolean swAlpha = mesaVis->alphaBits > 0;
      const GLboolean swAccum = mesaVis->accumRedBits > 0;
      const GLboolean swStencil = mesaVis->stencilBits > 0;
      driDrawPriv->driverPrivate = (void *)
         _mesa_create_framebuffer(mesaVis,
                                  swDepth,
                                  swStencil,
                                  swAccum,
                                  swAlpha);

      if (!driDrawPriv->driverPrivate)
	 return 0;
      
      /* Replace the framebuffer back buffer with a malloc'ed one --
       * big speedup.
       */
/*
      if (driDrawPriv->backBuffer)
	 driDrawPriv->backBuffer = malloc(driDrawPriv->currentPitch * driDrawPriv->h);
*/

      return 1;
   }
}


static void
x11DestroyBuffer(__DRIdrawablePrivate *driDrawPriv)
{
   _mesa_destroy_framebuffer((GLframebuffer *) (driDrawPriv->driverPrivate));
/*   free(driDrawPriv->backBuffer); */
}



/* If the backbuffer is on a videocard, this is extraordinarily slow!
 */
static void
x11SwapBuffers(__DRIdrawablePrivate *dPriv)
{

   if (dPriv->driContextPriv && dPriv->driContextPriv->driverPrivate) {
      x11ContextPtr x11mesa;
      GLcontext *ctx;
      x11mesa = (x11ContextPtr) dPriv->driContextPriv->driverPrivate;
      ctx = x11mesa->glCtx;
      if (ctx->Visual.doubleBufferMode) {
	 int i;
	 int offset = 0;
	 char *tmp /*= malloc(dPriv->currentPitch) */ ;

         _mesa_notifySwapBuffers(ctx);  /* flush pending rendering comands */

/*
	 ASSERT(dPriv->frontBuffer);
	 ASSERT(dPriv->backBuffer);

	 for (i = 0 ; i < dPriv->h ; i++ ) {
	    memcpy(tmp, (char *)dPriv->frontBuffer + offset, dPriv->currentPitch);
	    memcpy((char *)dPriv->backBuffer + offset, tmp, dPriv->currentPitch);
	    offset += dPriv->currentPitch;
	 }
	    
	 free(tmp);
*/
      }
   }
   else {
      /* XXX this shouldn't be an error but we can't handle it for now */
      _mesa_problem(NULL, "x11SwapBuffers: drawable has no context!\n");
   }
}


/* Force the context `c' to be the current context and associate with it
 * buffer `b'.
 */
static GLboolean
x11MakeCurrent(__DRIcontextPrivate *driContextPriv,
	       __DRIdrawablePrivate *driDrawPriv,
	       __DRIdrawablePrivate *driReadPriv)
{
   if (driContextPriv) {
      x11ContextPtr newFbCtx = 
	 (x11ContextPtr) driContextPriv->driverPrivate;

      newFbCtx->dri.drawable = driDrawPriv;

      _mesa_make_current2(newFbCtx->glCtx,
			  (GLframebuffer *) driDrawPriv->driverPrivate,
			  (GLframebuffer *) driReadPriv->driverPrivate);

      if (!newFbCtx->glCtx->Viewport.Width) {
	 _mesa_set_viewport(newFbCtx->glCtx, 0, 0,
			    driDrawPriv->w, driDrawPriv->h);
      }
   } else {
      _mesa_make_current(0, 0);
   }

   return GL_TRUE;
}


/* Force the context `c' to be unbound from its buffer.
 */
static GLboolean
x11UnbindContext(__DRIcontextPrivate *driContextPriv)
{
   return GL_TRUE;
}

static GLboolean
x11OpenCloseFullScreen(__DRIcontextPrivate *driContextPriv)
{
   return GL_TRUE;
}

static struct __DriverAPIRec x11API = {
   x11InitDriver,
   x11DestroyScreen,
   x11CreateContext,
   x11DestroyContext,
   x11CreateBuffer,
   x11DestroyBuffer,
   x11SwapBuffers,
   x11MakeCurrent,
   x11UnbindContext,
   x11OpenCloseFullScreen,
   x11OpenCloseFullScreen
};

void
__driRegisterExtensions(void)
{
}

/*
 * This is the bootstrap function for the driver.
 * The __driCreateScreen name is the symbol that libGL.so fetches.
 * Return:  pointer to a __DRIscreenPrivate.
 */
void *
__driCreateScreen(Display *dpy, int scrn, __DRIscreen *psc,
		  int numConfigs, __GLXvisualConfig *config)
{
   __DRIscreenPrivate *psp;
   psp = __driUtilCreateScreen(dpy, scrn, psc, numConfigs, config, &x11API);
   return (void *) psp;
}

/**
 * \brief Establish the set of modes available for the display.
 *
 * \param ctx display handle.
 * \param numModes will receive the number of supported modes.
 * \param modes will point to the list of supported modes.
 *
 * \return one on success, or zero on failure.
 * 
 * Allocates a single visual and fills it with information according to the
 * display bit depth. Supports only 16 and 32 bpp bit depths, aborting
 * otherwise.
 */
const __GLcontextModes __glModes[] = {
    /* 32 bit, RGBA Depth=24 Stencil=8 */
    {.rgbMode = GL_TRUE, .colorIndexMode = GL_FALSE, .doubleBufferMode = GL_TRUE, .stereoMode = GL_FALSE,
     .haveAccumBuffer = GL_FALSE, .haveDepthBuffer = GL_TRUE, .haveStencilBuffer = GL_TRUE,
     .redBits = 8, .greenBits = 8, .blueBits = 8, .alphaBits = 8,
     .redMask = 0xff0000, .greenMask = 0xff00, .blueMask = 0xff, .alphaMask = 0xff000000,
     .rgbBits = 32, .indexBits = 0,
     .accumRedBits = 0, .accumGreenBits = 0, .accumBlueBits = 0, .accumAlphaBits = 0,
     .depthBits = 24, .stencilBits = 8,
     .numAuxBuffers= 0, .level = 0, .pixmapMode = GL_FALSE, },

    /* 16 bit, RGB Depth=16 */
    {.rgbMode = GL_TRUE, .colorIndexMode = GL_FALSE, .doubleBufferMode = GL_TRUE, .stereoMode = GL_FALSE,
     .haveAccumBuffer = GL_FALSE, .haveDepthBuffer = GL_TRUE, .haveStencilBuffer = GL_FALSE,
     .redBits = 5, .greenBits = 6, .blueBits = 5, .alphaBits = 0,
     .redMask = 0xf800, .greenMask = 0x07e0, .blueMask = 0x001f, .alphaMask = 0x0,
     .rgbBits = 16, .indexBits = 0,
     .accumRedBits = 0, .accumGreenBits = 0, .accumBlueBits = 0, .accumAlphaBits = 0,
     .depthBits = 16, .stencilBits = 0,
     .numAuxBuffers= 0, .level = 0, .pixmapMode = GL_FALSE, },
};
