/*
 * Copyright 2002 by Alan Hourihane, Sychdyn, North Wales, UK.
 *
 * 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 Alan Hourihane not be used in
 * advertising or publicity pertaining to distribution of the software without
 * specific, written prior permission.  Alan Hourihane makes no representations
 * about the suitability of this software for any purpose.  It is provided
 * "as is" without express or implied warranty.
 *
 * ALAN HOURIHANE DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE,
 * INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO
 * EVENT SHALL ALAN HOURIHANE 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.
 *
 * Authors:  Alan Hourihane, <alanh@fairlite.demon.co.uk>
 *
 * Trident CyberBladeXP driver.
 *
 */
#include "trident_dri.h"
#include "trident_context.h"
#include "trident_lock.h"

#include "swrast/swrast.h"
#include "swrast_setup/swrast_setup.h"
#include "vbo/vbo.h"

#include "tnl/tnl.h"
#include "tnl/t_pipeline.h"

#include "main/context.h"
#include "main/simple_list.h"
#include "main/matrix.h"
#include "main/extensions.h"
#include "main/framebuffer.h"
#include "main/renderbuffer.h"
#include "main/viewport.h"
#if defined(USE_X86_ASM)
#include "x86/common_x86_asm.h"
#endif
#include "main/simple_list.h"
#include "main/mm.h"
#include "drirenderbuffer.h"

#include "drivers/common/driverfuncs.h"
#include "dri_util.h"
#include "utils.h"

static const struct tnl_pipeline_stage *trident_pipeline[] = {
   &_tnl_vertex_transform_stage, 
   &_tnl_normal_transform_stage, 
   &_tnl_lighting_stage,
   &_tnl_texgen_stage, 
   &_tnl_texture_transform_stage, 
   &_tnl_render_stage,		
   0,
};


static GLboolean
tridentCreateContext( const __GLcontextModes *glVisual,
                      __DRIcontextPrivate *driContextPriv,
                      void *sharedContextPrivate)
{
   GLcontext *ctx, *shareCtx;
   __DRIscreenPrivate *sPriv = driContextPriv->driScreenPriv;
   tridentContextPtr tmesa;
   tridentScreenPtr tridentscrn;
   struct dd_function_table functions;
#if 0
   drm_trident_sarea_t *saPriv=(drm_trident_sarea_t *)(((char*)sPriv->pSAREA)+
						 sizeof(XF86DRISAREARec));
#endif

   tmesa = (tridentContextPtr) CALLOC( sizeof(*tmesa) );
   if ( !tmesa ) return GL_FALSE;

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

   _mesa_init_driver_functions(&functions);

   tmesa->glCtx =
      _mesa_create_context(glVisual, shareCtx, &functions, (void *)tmesa);

   if (!tmesa->glCtx) {
      FREE(tmesa);
      return GL_FALSE;
   }

   tmesa->driContext = driContextPriv;
   tmesa->driScreen = sPriv;
   tmesa->driDrawable = NULL; /* Set by XMesaMakeCurrent */

   tmesa->hHWContext = driContextPriv->hHWContext;
   tmesa->driHwLock = (drmLock *)&sPriv->pSAREA->lock;
   tmesa->driFd = sPriv->fd;
#if 0
   tmesa->sarea = saPriv;
#endif

   tridentscrn = tmesa->tridentScreen = (tridentScreenPtr)(sPriv->private);

   ctx = tmesa->glCtx;

   ctx->Const.MaxTextureLevels = 13;  /* 4K by 4K?  Is that right? */
   ctx->Const.MaxTextureUnits = 1; /* Permedia 3 */

   ctx->Const.MinLineWidth = 0.0;
   ctx->Const.MaxLineWidth = 255.0;

   ctx->Const.MinLineWidthAA = 0.0;
   ctx->Const.MaxLineWidthAA = 65536.0;

   ctx->Const.MinPointSize = 0.0;
   ctx->Const.MaxPointSize = 255.0;

   ctx->Const.MinPointSizeAA = 0.5; /* 4x4 quality mode */
   ctx->Const.MaxPointSizeAA = 16.0; 
   ctx->Const.PointSizeGranularity = 0.25;

   ctx->Const.MaxDrawBuffers = 1;

#if 0
   tmesa->texHeap = mmInit( 0, tmesa->tridentScreen->textureSize );

   make_empty_list(&tmesa->TexObjList);
   make_empty_list(&tmesa->SwappedOut);

   tmesa->CurrentTexObj[0] = 0;
   tmesa->CurrentTexObj[1] = 0; /* Permedia 3, second texture */

   tmesa->RenderIndex = ~0;
#endif

   /* Initialize the software rasterizer and helper modules.
    */
   _swrast_CreateContext( ctx );
   _vbo_CreateContext( ctx );
   _tnl_CreateContext( ctx );
   _swsetup_CreateContext( ctx );

   /* Install the customized pipeline:
    */
   _tnl_destroy_pipeline( ctx );
   _tnl_install_pipeline( ctx, trident_pipeline );

   /* Configure swrast to match hardware characteristics:
    */
   _swrast_allow_pixel_fog( ctx, GL_FALSE );
   _swrast_allow_vertex_fog( ctx, GL_TRUE );

   tridentInitVB( ctx );
   tridentDDInitExtensions( ctx );
   tridentDDInitDriverFuncs( ctx );
   tridentDDInitStateFuncs( ctx );
#if 0
   tridentDDInitSpanFuncs( ctx );
   tridentDDInitTextureFuncs( ctx );
#endif
   tridentDDInitTriFuncs( ctx );
   tridentDDInitState( tmesa );

   driContextPriv->driverPrivate = (void *)tmesa;

   UNLOCK_HARDWARE(tmesa);

   return GL_TRUE;
}

static void 
tridentDestroyContext(__DRIcontextPrivate *driContextPriv)
{
    tridentContextPtr tmesa = (tridentContextPtr)driContextPriv->driverPrivate;

    if (tmesa) {
      _swsetup_DestroyContext( tmesa->glCtx );
      _tnl_DestroyContext( tmesa->glCtx );
      _vbo_DestroyContext( tmesa->glCtx );
      _swrast_DestroyContext( tmesa->glCtx );

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

      _mesa_free(tmesa);
      driContextPriv->driverPrivate = NULL;
    }
}


static GLboolean
tridentCreateBuffer( __DRIscreenPrivate *driScrnPriv,
                   __DRIdrawablePrivate *driDrawPriv,
                   const __GLcontextModes *mesaVis,
                   GLboolean isPixmap )
{
   tridentScreenPtr screen = (tridentScreenPtr) driScrnPriv->private;

   if (isPixmap) {
      return GL_FALSE; /* not implemented */
   }
   else {
      struct gl_framebuffer *fb = _mesa_create_framebuffer(mesaVis);

      {
         driRenderbuffer *frontRb
            = driNewRenderbuffer(GL_RGBA, NULL, screen->cpp,
                                 screen->frontOffset, screen->frontPitch,
                                 driDrawPriv);
         /*
         tridentSetSpanFunctions(frontRb, mesaVis);
         */
         _mesa_add_renderbuffer(fb, BUFFER_FRONT_LEFT, &frontRb->Base);
      }

      if (mesaVis->doubleBufferMode) {
         driRenderbuffer *backRb
            = driNewRenderbuffer(GL_RGBA, NULL, screen->cpp,
                                 screen->backOffset, screen->backPitch,
                                 driDrawPriv);
         /*
         tridentSetSpanFunctions(backRb, mesaVis);
         */
         _mesa_add_renderbuffer(fb, BUFFER_BACK_LEFT, &backRb->Base);
      }

      if (mesaVis->depthBits == 16) {
         driRenderbuffer *depthRb
            = driNewRenderbuffer(GL_DEPTH_COMPONENT16, NULL, screen->cpp,
                                 screen->depthOffset, screen->depthPitch,
                                 driDrawPriv);
         /*
         tridentSetSpanFunctions(depthRb, mesaVis);
         */
         _mesa_add_renderbuffer(fb, BUFFER_DEPTH, &depthRb->Base);
      }
      else if (mesaVis->depthBits == 24) {
         driRenderbuffer *depthRb
            = driNewRenderbuffer(GL_DEPTH_COMPONENT24, NULL, screen->cpp,
                                 screen->depthOffset, screen->depthPitch,
                                 driDrawPriv);
         /*
         tridentSetSpanFunctions(depthRb, mesaVis);
         */
         _mesa_add_renderbuffer(fb, BUFFER_DEPTH, &depthRb->Base);
      }

      /* no h/w stencil?
      if (mesaVis->stencilBits > 0 && !swStencil) {
         driRenderbuffer *stencilRb
            = driNewRenderbuffer(GL_STENCIL_INDEX8_EXT);
         tridentSetSpanFunctions(stencilRb, mesaVis);
         _mesa_add_renderbuffer(fb, BUFFER_STENCIL, &stencilRb->Base);
      }
      */

      _mesa_add_soft_renderbuffers(fb,
                                   GL_FALSE, /* color */
                                   GL_FALSE, /* depth */
                                   mesaVis->stencilBits > 0,
                                   mesaVis->accumRedBits > 0,
                                   GL_FALSE, /* alpha */
                                   GL_FALSE /* aux */);
      driDrawPriv->driverPrivate = (void *) fb;

      return (driDrawPriv->driverPrivate != NULL);
   }
}


static void
tridentDestroyBuffer(__DRIdrawablePrivate *driDrawPriv)
{
   _mesa_reference_framebuffer((GLframebuffer **)(&(driDrawPriv->driverPrivate)), NULL);
}

static void
tridentSwapBuffers(__DRIdrawablePrivate *drawablePrivate)
{
   __DRIdrawablePrivate *dPriv = (__DRIdrawablePrivate *) drawablePrivate;

   if (dPriv->driContextPriv && dPriv->driContextPriv->driverPrivate) {
      tridentContextPtr tmesa;
      GLcontext *ctx;
      tmesa = (tridentContextPtr) dPriv->driContextPriv->driverPrivate;
      ctx = tmesa->glCtx;
      if (ctx->Visual.doubleBufferMode) {
         _mesa_notifySwapBuffers( ctx );  /* flush pending rendering comands */
         tridentCopyBuffer( dPriv );
      }
   }
   else {
      /* XXX this shouldn't be an error but we can't handle it for now */
      _mesa_problem(NULL, "tridentSwapBuffers: drawable has no context!\n");
   }
}

static GLboolean 
tridentMakeCurrent(__DRIcontextPrivate *driContextPriv,
		 __DRIdrawablePrivate *driDrawPriv,
		 __DRIdrawablePrivate *driReadPriv)
{
    if (driContextPriv) {
	GET_CURRENT_CONTEXT(ctx);
	tridentContextPtr oldCtx = ctx ? TRIDENT_CONTEXT(ctx) : NULL;
	tridentContextPtr newCtx = (tridentContextPtr) driContextPriv->driverPrivate;

	if ( newCtx != oldCtx ) {
	    newCtx->dirty = ~0;
	}

	if (newCtx->driDrawable != driDrawPriv) {
	    newCtx->driDrawable = driDrawPriv;
#if 0
	    tridentUpdateWindow ( newCtx->glCtx );
	    tridentUpdateViewportOffset( newCtx->glCtx );
#endif
	}

   newCtx->drawOffset = newCtx->tridentScreen->backOffset;
   newCtx->drawPitch = newCtx->tridentScreen->backPitch;

	_mesa_make_current( newCtx->glCtx, 
                            (GLframebuffer *) driDrawPriv->driverPrivate,
                            (GLframebuffer *) driReadPriv->driverPrivate );

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


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


static tridentScreenPtr
tridentCreateScreen( __DRIscreenPrivate *sPriv )
{
   TRIDENTDRIPtr tDRIPriv = (TRIDENTDRIPtr)sPriv->pDevPriv;
   tridentScreenPtr tridentScreen;

   if (sPriv->devPrivSize != sizeof(TRIDENTDRIRec)) {
      fprintf(stderr,"\nERROR!  sizeof(TRIDENTDRIRec) does not match passed size from device driver\n");
      return GL_FALSE;
   }

    /* Allocate the private area */
    tridentScreen = (tridentScreenPtr) CALLOC( sizeof(*tridentScreen) );
    if ( !tridentScreen ) return NULL;

   tridentScreen->driScreen = sPriv;

   tridentScreen->frontOffset = tDRIPriv->frontOffset;
   tridentScreen->backOffset = tDRIPriv->backOffset;
   tridentScreen->depthOffset = tDRIPriv->depthOffset;
   tridentScreen->frontPitch = tDRIPriv->frontPitch;
   tridentScreen->backPitch = tDRIPriv->backPitch;
   tridentScreen->depthPitch = tDRIPriv->depthPitch;
   tridentScreen->width = tDRIPriv->width;
   tridentScreen->height = tDRIPriv->height;

printf("%d %d\n",tridentScreen->width,tridentScreen->height);
printf("%d %d\n",tridentScreen->frontPitch,tridentScreen->backPitch);
printf("offset 0x%x 0x%x\n",tridentScreen->backOffset,tridentScreen->depthOffset);

   tridentScreen->mmio.handle = tDRIPriv->regs;
   tridentScreen->mmio.size = 0x20000;
    
   if (drmMap(sPriv->fd,
	 	tridentScreen->mmio.handle, tridentScreen->mmio.size,
		(drmAddressPtr)&tridentScreen->mmio.map)) {
	    FREE(tridentScreen);
	    return GL_FALSE;
    }
printf("MAPPED at %p\n", tridentScreen->mmio.map);

   return tridentScreen;
}

/* Destroy the device specific screen private data struct.
 */
static void
tridentDestroyScreen( __DRIscreenPrivate *sPriv )
{
    tridentScreenPtr tridentScreen = (tridentScreenPtr)sPriv->private;

    FREE(tridentScreen);
}

static GLboolean 
tridentInitDriver(__DRIscreenPrivate *sPriv)
{
    sPriv->private = (void *) tridentCreateScreen( sPriv );

    if (!sPriv->private) {
	tridentDestroyScreen( sPriv );
	return GL_FALSE;
    }

    return GL_TRUE;
}

/**
 * This is the driver specific part of the createNewScreen entry point.
 * 
 * \todo maybe fold this into intelInitDriver
 *
 * \return the __GLcontextModes supported by this driver
 */
const __DRIconfig **tridentInitScreen(__DRIscreenPrivate *psp)
{
   static const __DRIversion ddx_expected = { 4, 0, 0 };
   static const __DRIversion dri_expected = { 3, 1, 0 };
   static const __DRIversion drm_expected = { 1, 0, 0 };
   
   if ( ! driCheckDriDdxDrmVersions2( "Trident",
				      &psp->dri_version, & dri_expected,
				      &psp->ddx_version, & ddx_expected,
				      &psp->drm_version, & drm_expected ) )
      return NULL;

   if (!tridentInitDriver(psp))
	return NULL;

    /* Wait... what?  This driver doesn't report any modes... */
#if 0
   TRIDENTDRIPtr dri_priv = (TRIDENTDRIPtr) psp->pDevPriv;
   *driver_modes = tridentFillInModes( dri_priv->bytesPerPixel * 8,
				       GL_TRUE );
#endif

   return NULL;
}

const struct __DriverAPIRec driDriverAPI = {
   tridentInitScreen,
   tridentDestroyScreen,
   tridentCreateContext,
   tridentDestroyContext,
   tridentCreateBuffer,
   tridentDestroyBuffer,
   tridentSwapBuffers,
   tridentMakeCurrent,
   tridentUnbindContext,
};