/**************************************************************************
 * 
 * Copyright 2003 Tungsten Graphics, Inc., Cedar Park, Texas.
 * 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, sub license, 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 NON-INFRINGEMENT.
 * IN NO EVENT SHALL TUNGSTEN GRAPHICS AND/OR ITS SUPPLIERS 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.
 * 
 **************************************************************************/

#include "glheader.h"
#include "enums.h"
#include "mtypes.h"
#include "macros.h"
#include "swrast/swrast.h"

#include "intel_screen.h"
#include "intel_context.h"
#include "intel_ioctl.h"
#include "intel_batchbuffer.h"



static GLboolean
check_color( const GLcontext *ctx, GLenum type, GLenum format,
	     const struct gl_pixelstore_attrib *packing,
	     const void *pixels, GLint sz, GLint pitch )
{
   intelContextPtr intel = INTEL_CONTEXT(ctx);
   GLuint cpp = intel->intelScreen->cpp;

   if (INTEL_DEBUG & DEBUG_PIXEL)
      fprintf(stderr, "%s\n", __FUNCTION__);

   if (	(pitch & 63) ||
	ctx->_ImageTransferState ||
	packing->SwapBytes ||
	packing->LsbFirst) {
      if (INTEL_DEBUG & DEBUG_PIXEL)
	 fprintf(stderr, "%s: failed 1\n", __FUNCTION__);
      return GL_FALSE;
   }

   if ( type == GL_UNSIGNED_INT_8_8_8_8_REV && 
	cpp == 4 && 
	format == GL_BGRA ) {
      if (INTEL_DEBUG & DEBUG_PIXEL)
	 fprintf(stderr, "%s: passed 2\n", __FUNCTION__);
      return GL_TRUE;
   }

   if (INTEL_DEBUG & DEBUG_PIXEL)
      fprintf(stderr, "%s: failed\n", __FUNCTION__);

   return GL_FALSE;
}

static GLboolean
check_color_per_fragment_ops( const GLcontext *ctx )
{
   int result;
   result = (!(     ctx->Color.AlphaEnabled || 
		    ctx->Depth.Test ||
		    ctx->Fog.Enabled ||
		    ctx->Scissor.Enabled ||
		    ctx->Stencil.Enabled ||
		    !ctx->Color.ColorMask[0] ||
		    !ctx->Color.ColorMask[1] ||
		    !ctx->Color.ColorMask[2] ||
		    !ctx->Color.ColorMask[3] ||
		    ctx->Color.ColorLogicOpEnabled ||
		    ctx->Texture._EnabledUnits ||
		    ctx->Depth.OcclusionTest
           ) &&
	   ctx->Current.RasterPosValid);
   
   return result;
}



static GLboolean
clip_pixelrect( const GLcontext *ctx,
		const GLframebuffer *buffer,
		GLint *x, GLint *y,
		GLsizei *width, GLsizei *height,
		GLint *size )
{
   intelContextPtr intel = INTEL_CONTEXT(ctx);

   /* left clipping */
   if (*x < buffer->_Xmin) {
      *width -= (buffer->_Xmin - *x);
      *x = buffer->_Xmin;
   }

   /* right clipping */
   if (*x + *width > buffer->_Xmax)
      *width -= (*x + *width - buffer->_Xmax - 1);

   if (*width <= 0)
      return GL_FALSE;

   /* bottom clipping */
   if (*y < buffer->_Ymin) {
      *height -= (buffer->_Ymin - *y);
      *y = buffer->_Ymin;
   }

   /* top clipping */
   if (*y + *height > buffer->_Ymax)
      *height -= (*y + *height - buffer->_Ymax - 1);

   if (*height <= 0)
      return GL_FALSE;

   *size = ((*y + *height - 1) * intel->intelScreen->frontPitch +
	    (*x + *width - 1) * intel->intelScreen->cpp);

   return GL_TRUE;
}

static GLboolean
intelTryReadPixels( GLcontext *ctx,
		  GLint x, GLint y, GLsizei width, GLsizei height,
		  GLenum format, GLenum type,
		  const struct gl_pixelstore_attrib *pack,
		  GLvoid *pixels )
{
   intelContextPtr intel = INTEL_CONTEXT(ctx);
   GLint size = 0;
   GLint pitch = pack->RowLength ? pack->RowLength : width;

   if (INTEL_DEBUG & DEBUG_PIXEL)
      fprintf(stderr, "%s\n", __FUNCTION__);

   /* Only accelerate reading to agp buffers.
    */
   if ( !intelIsAgpMemory(intel, pixels, 
			pitch * height * intel->intelScreen->cpp ) ) {
      if (INTEL_DEBUG & DEBUG_PIXEL)
	 fprintf(stderr, "%s: dest not agp\n", __FUNCTION__);
      return GL_FALSE;
   }

   /* Need GL_PACK_INVERT_MESA to cope with upsidedown results from
    * blitter:
    */
   if (!pack->Invert) {
      if (INTEL_DEBUG & DEBUG_PIXEL)
	 fprintf(stderr, "%s: MESA_PACK_INVERT not set\n", __FUNCTION__);
      return GL_FALSE;
   }

   if (!check_color(ctx, type, format, pack, pixels, size, pitch))
      return GL_FALSE;

   switch ( intel->intelScreen->cpp ) {
   case 4:
      break;
   default:
      return GL_FALSE;
   }


   /* Although the blits go on the command buffer, need to do this and
    * fire with lock held to guarentee cliprects and drawOffset are
    * correct.
    *
    * This is an unusual situation however, as the code which flushes
    * a full command buffer expects to be called unlocked.  As a
    * workaround, immediately flush the buffer on aquiring the lock.
    */
   intelFlush( &intel->ctx );
   LOCK_HARDWARE( intel );
   {
      __DRIdrawablePrivate *dPriv = intel->driDrawable;
      int nbox = dPriv->numClipRects;
      int src_offset = intel->drawOffset;
      int src_pitch = intel->intelScreen->frontPitch;
      int dst_offset = intelAgpOffsetFromVirtual( intel, pixels);
      drm_clip_rect_t *box = dPriv->pClipRects;
      int i;

      if (!clip_pixelrect(ctx, ctx->ReadBuffer, &x, &y, &width, &height,
			  &size)) {
	 UNLOCK_HARDWARE( intel );
	 if (INTEL_DEBUG & DEBUG_PIXEL)
	    fprintf(stderr, "%s totally clipped -- nothing to do\n",
		    __FUNCTION__);
	 return GL_TRUE;
      }


      y = dPriv->h - y - height;
      x += dPriv->x;
      y += dPriv->y;


      if (INTEL_DEBUG & DEBUG_PIXEL)
	 fprintf(stderr, "readpixel blit src_pitch %d dst_pitch %d\n",
		 src_pitch, pitch);

      for (i = 0 ; i < nbox ; i++)
      {
	 GLint bx = box[i].x1;
	 GLint by = box[i].y1;
	 GLint bw = box[i].x2 - bx;
	 GLint bh = box[i].y2 - by;
	 
	 if (bx < x) bw -= x - bx, bx = x;
	 if (by < y) bh -= y - by, by = y;
	 if (bx + bw > x + width) bw = x + width - bx;
	 if (by + bh > y + height) bh = y + height - by;
	 if (bw <= 0) continue;
	 if (bh <= 0) continue;

	 intelEmitCopyBlitLocked( intel,
			    intel->intelScreen->cpp,
			    src_pitch, src_offset,
			    pitch, dst_offset,
			    bx, by,
			    bx - x, by - y,
			    bw, bh );
      }
   }
   UNLOCK_HARDWARE( intel );
   intelFinish( &intel->ctx );

   return GL_TRUE;
}

static void
intelReadPixels( GLcontext *ctx,
		 GLint x, GLint y, GLsizei width, GLsizei height,
		 GLenum format, GLenum type,
		 const struct gl_pixelstore_attrib *pack,
		 GLvoid *pixels )
{
   if (INTEL_DEBUG & DEBUG_PIXEL)
      fprintf(stderr, "%s\n", __FUNCTION__);

   if (!intelTryReadPixels( ctx, x, y, width, height, format, type, pack, 
			   pixels))
      _swrast_ReadPixels( ctx, x, y, width, height, format, type, pack, 
			  pixels);
}




static void do_draw_pix( GLcontext *ctx,
			 GLint x, GLint y, GLsizei width, GLsizei height,
			 GLint pitch,
			 const void *pixels,
			 GLuint dest )
{
   intelContextPtr intel = INTEL_CONTEXT(ctx);
   __DRIdrawablePrivate *dPriv = intel->driDrawable;
   drm_clip_rect_t *box = dPriv->pClipRects;
   int nbox = dPriv->numClipRects;
   int i;
   int size;
   int src_offset = intelAgpOffsetFromVirtual( intel, pixels);
   int src_pitch = pitch;

   if (INTEL_DEBUG & DEBUG_PIXEL)
      fprintf(stderr, "%s\n", __FUNCTION__);

   intelFlush( &intel->ctx );
   LOCK_HARDWARE( intel );
   if (ctx->DrawBuffer)
   {
      y -= height;			/* cope with pixel zoom */
   
      if (!clip_pixelrect(ctx, ctx->DrawBuffer,
			  &x, &y, &width, &height,
			  &size)) {
	 UNLOCK_HARDWARE( intel );
	 return;
      }

      y = dPriv->h - y - height; 	/* convert from gl to hardware coords */
      x += dPriv->x;
      y += dPriv->y;


      for (i = 0 ; i < nbox ; i++ )
      {
	 GLint bx = box[i].x1;
	 GLint by = box[i].y1;
	 GLint bw = box[i].x2 - bx;
	 GLint bh = box[i].y2 - by;

	 if (bx < x) bw -= x - bx, bx = x;
	 if (by < y) bh -= y - by, by = y;
	 if (bx + bw > x + width) bw = x + width - bx;
	 if (by + bh > y + height) bh = y + height - by;
	 if (bw <= 0) continue;
	 if (bh <= 0) continue;

	 intelEmitCopyBlitLocked( intel,
			    intel->intelScreen->cpp,
			    src_pitch, src_offset,
			    intel->intelScreen->frontPitch,
			      intel->drawOffset,
			    bx - x, by - y,
			    bx, by,
			    bw, bh );
      }
   }
   UNLOCK_HARDWARE( intel );
   intelFinish( &intel->ctx );
}




static GLboolean
intelTryDrawPixels( GLcontext *ctx,
		  GLint x, GLint y, GLsizei width, GLsizei height,
		  GLenum format, GLenum type,
		  const struct gl_pixelstore_attrib *unpack,
		  const GLvoid *pixels )
{
   intelContextPtr intel = INTEL_CONTEXT(ctx);
   GLint pitch = unpack->RowLength ? unpack->RowLength : width;
   GLuint dest;
   GLuint cpp = intel->intelScreen->cpp;
   GLint size = width * pitch * cpp;

   if (INTEL_DEBUG & DEBUG_PIXEL)
      fprintf(stderr, "%s\n", __FUNCTION__);

   switch (format) {
   case GL_RGB:
   case GL_RGBA:
   case GL_BGRA:
      dest = intel->drawOffset;

      /* Planemask doesn't have full support in blits.
       */
      if (!ctx->Color.ColorMask[RCOMP] ||
	  !ctx->Color.ColorMask[GCOMP] ||
	  !ctx->Color.ColorMask[BCOMP] ||
	  !ctx->Color.ColorMask[ACOMP]) {
	 if (INTEL_DEBUG & DEBUG_PIXEL)
	    fprintf(stderr, "%s: planemask\n", __FUNCTION__);
	 return GL_FALSE;	
      }

      /* Can't do conversions on agp reads/draws. 
       */
      if ( !intelIsAgpMemory( intel, pixels, size ) ) {
	 if (INTEL_DEBUG & DEBUG_PIXEL)
	    fprintf(stderr, "%s: not agp memory\n", __FUNCTION__);
	 return GL_FALSE;
      }

      if (!check_color(ctx, type, format, unpack, pixels, size, pitch)) {
	 return GL_FALSE;
      }
      if (!check_color_per_fragment_ops(ctx)) {
	 return GL_FALSE;
      }

      if (ctx->Pixel.ZoomX != 1.0F ||
	  ctx->Pixel.ZoomY != -1.0F)
	 return GL_FALSE;
      break;

   default:
      return GL_FALSE;
   }

   if ( intelIsAgpMemory(intel, pixels, size) )
   {
      do_draw_pix( ctx, x, y, width, height, pitch, pixels,
		   dest );
      return GL_TRUE;
   }
   else if (0)
   {
      /* Pixels is in regular memory -- get dma buffers and perform
       * upload through them.  No point doing this for regular uploads
       * but once we remove some of the restrictions above (colormask,
       * pixelformat conversion, zoom?, etc), this could be a win.
       */
   }
   else
      return GL_FALSE;
}

static void
intelDrawPixels( GLcontext *ctx,
		 GLint x, GLint y, GLsizei width, GLsizei height,
		 GLenum format, GLenum type,
		 const struct gl_pixelstore_attrib *unpack,
		 const GLvoid *pixels )
{
   if (INTEL_DEBUG & DEBUG_PIXEL)
      fprintf(stderr, "%s\n", __FUNCTION__);

   if (!intelTryDrawPixels( ctx, x, y, width, height, format, type,
			  unpack, pixels ))
      _swrast_DrawPixels( ctx, x, y, width, height, format, type,
			  unpack, pixels );
}




/**
 * Implement glCopyPixels for the front color buffer (or back buffer Pixmap)
 * for the color buffer.  Don't support zooming, pixel transfer, etc.
 * We do support copying from one window to another, ala glXMakeCurrentRead.
 */
static void
intelCopyPixels( GLcontext *ctx,
		 GLint srcx, GLint srcy, GLsizei width, GLsizei height,
		 GLint destx, GLint desty, GLenum type )
{
#if 0
   const XMesaContext xmesa = XMESA_CONTEXT(ctx);
   const SWcontext *swrast = SWRAST_CONTEXT( ctx );
   XMesaDisplay *dpy = xmesa->xm_visual->display;
   const XMesaDrawable drawBuffer = xmesa->xm_draw_buffer->buffer;
   const XMesaDrawable readBuffer = xmesa->xm_read_buffer->buffer;
   const XMesaGC gc = xmesa->xm_draw_buffer->gc;

   ASSERT(dpy);
   ASSERT(gc);

   if (drawBuffer &&  /* buffer != 0 means it's a Window or Pixmap */
       readBuffer &&
       type == GL_COLOR &&
       (swrast->_RasterMask & ~CLIP_BIT) == 0 && /* no blend, z-test, etc */
       ctx->_ImageTransferState == 0 &&  /* no color tables, scale/bias, etc */
       ctx->Pixel.ZoomX == 1.0 &&        /* no zooming */
       ctx->Pixel.ZoomY == 1.0) {
      /* Note: we don't do any special clipping work here.  We could,
       * but X will do it for us.
       */
      srcy = FLIP(xmesa->xm_read_buffer, srcy) - height + 1;
      desty = FLIP(xmesa->xm_draw_buffer, desty) - height + 1;
      XCopyArea(dpy, readBuffer, drawBuffer, gc,
                srcx, srcy, width, height, destx, desty);
   }
#else
   _swrast_CopyPixels(ctx, srcx, srcy, width, height, destx, desty, type );
#endif
}




void intelInitPixelFuncs( struct dd_function_table *functions )
{
   /* Pixel path fallbacks.
    */
   functions->Accum = _swrast_Accum;
   functions->Bitmap = _swrast_Bitmap;
   functions->CopyPixels = intelCopyPixels;

   if (!getenv("INTEL_NO_BLITS")) {
      functions->ReadPixels = intelReadPixels;  
      functions->DrawPixels = intelDrawPixels; 
   }
   else {
      functions->ReadPixels = _swrast_ReadPixels;
      functions->DrawPixels = _swrast_DrawPixels;
   }
}