/*
 * Copyright © 2004 David Reveman
 *
 * 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
 * David Reveman not be used in advertising or publicity pertaining to
 * distribution of the software without specific, written prior permission.
 * David Reveman makes no representations about the suitability of this
 * software for any purpose. It is provided "as is" without express or
 * implied warranty.
 *
 * DAVID REVEMAN DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE,
 * INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN
 * NO EVENT SHALL DAVID REVEMAN 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.
 *
 * Author: David Reveman <davidr@novell.com>
 */

#include "xgl.h"
#include "fb.h"
#include "gcstruct.h"
#include "migc.h"

#define XGL_GC_OP_FALLBACK_PROLOGUE(pDrawable) \
    xglSyncDamageBoxBits (pDrawable);	       \
    XGL_GC_UNWRAP (funcs);		       \
    XGL_GC_UNWRAP (ops)

#define XGL_GC_OP_FALLBACK_EPILOGUE(pDrawable)	  \
    XGL_GC_WRAP (funcs, (GCFuncs *) &xglGCFuncs); \
    XGL_GC_WRAP (ops, (GCOps *) &xglGCOps);	  \
    xglAddCurrentSurfaceDamage (pDrawable)

#define XGL_GC_FILL_OP_FALLBACK_PROLOGUE(pDrawable)		 \
    switch (pGC->fillStyle) {					 \
    case FillSolid:						 \
	break;							 \
    case FillStippled:						 \
    case FillOpaqueStippled:					 \
	if (!xglSyncBits (&pGC->stipple->drawable, NullBox))	 \
	    FatalError (XGL_SW_FAILURE_STRING);			 \
	break;							 \
    case FillTiled:						 \
	if (!xglSyncBits (&pGC->tile.pixmap->drawable, NullBox)) \
	    FatalError (XGL_SW_FAILURE_STRING);			 \
	break;							 \
    }								 \
    XGL_GC_OP_FALLBACK_PROLOGUE (pDrawable)

static const GCFuncs xglGCFuncs = {
    xglValidateGC,
    miChangeGC,
    miCopyGC,
    xglDestroyGC,
    miChangeClip,
    miDestroyClip,
    miCopyClip
};

static const GCOps xglGCOps = {
    xglFillSpans,
    xglSetSpans,
    xglPutImage,
    xglCopyArea,
    xglCopyPlane,
    xglPolyPoint,
    xglPolylines,
    xglPolySegment,
    miPolyRectangle,
    xglPolyArc,
    miFillPolygon,
    xglPolyFillRect,
    xglPolyFillArc,
    miPolyText8,
    miPolyText16,
    miImageText8,
    miImageText16,
    xglImageGlyphBlt,
    xglPolyGlyphBlt,
    xglPushPixels
};

void
xglFillSpans (DrawablePtr pDrawable,
	      GCPtr	  pGC,
	      int	  nspans,
	      DDXPointPtr ppt,
	      int	  *pwidth,
	      int	  fSorted)
{
    XGL_GC_PRIV (pGC);

    if (pGCPriv->flags || pGC->fillStyle == FillStippled)
    {
	XGL_GC_FILL_OP_FALLBACK_PROLOGUE (pDrawable);
	(*pGC->ops->FillSpans) (pDrawable, pGC, nspans, ppt, pwidth, fSorted);
	XGL_GC_OP_FALLBACK_EPILOGUE (pDrawable);
    }
    else
    {
	/* xglFillSpan handles fall-back */
	xglFillSpan (pDrawable, pGC, nspans, ppt, pwidth);
    }
}

void
xglSetSpans (DrawablePtr pDrawable,
	     GCPtr	 pGC,
	     char	 *psrc,
	     DDXPointPtr ppt,
	     int	 *pwidth,
	     int	 nspans,
	     int	 fSorted)
{
    XGL_GC_PRIV (pGC);

    XGL_GC_OP_FALLBACK_PROLOGUE (pDrawable);
    (*pGC->ops->SetSpans) (pDrawable, pGC, psrc, ppt, pwidth, nspans, fSorted);
    XGL_GC_OP_FALLBACK_EPILOGUE (pDrawable);
}

void
xglPutImage (DrawablePtr pDrawable,
	     GCPtr	 pGC,
	     int	 depth,
	     int	 x,
	     int	 y,
	     int	 w,
	     int	 h,
	     int	 leftPad,
	     int	 format,
	     char	 *bits)
{
    XGL_GC_PRIV (pGC);

    if (pGC->alu != GXcopy || (pGCPriv->flags & xglGCPlaneMaskFlag))
    {
	XGL_GC_OP_FALLBACK_PROLOGUE (pDrawable);
	(*pGC->ops->PutImage) (pDrawable, pGC, depth,
			       x, y, w, h, leftPad, format, bits);
	XGL_GC_OP_FALLBACK_EPILOGUE (pDrawable);
    }
    else
    {
	RegionPtr pClip = pGC->pCompositeClip;
	RegionRec region;
	BoxRec	  box;

	XGL_DRAWABLE_PIXMAP (pDrawable);

	if (!xglMapPixmapBits (pPixmap))
	    FatalError (XGL_SW_FAILURE_STRING);

	XGL_GC_UNWRAP (funcs);
	XGL_GC_UNWRAP (ops);

	(*pGC->ops->PutImage) (pDrawable, pGC, depth,
			       x, y, w, h, leftPad, format, bits);

	XGL_GC_WRAP (funcs, (GCFuncs *) &xglGCFuncs);
	XGL_GC_WRAP (ops, (GCOps *) &xglGCOps);

	box.x1 = pDrawable->x + x;
	box.y1 = pDrawable->y + y;
	box.x2 = box.x1 + w;
	box.y2 = box.y1 + h;

	REGION_INIT (pDrawable->pScreen, &region, &box, 1);
	REGION_INTERSECT (pDrawable->pScreen, &region, pClip, &region);

	xglAddSurfaceDamage (pDrawable, &region);

	REGION_UNINIT (pDrawable->pScreen, &region);
    }
}

RegionPtr
xglCopyArea (DrawablePtr pSrc,
	     DrawablePtr pDst,
	     GCPtr	 pGC,
	     int	 srcX,
	     int	 srcY,
	     int	 w,
	     int	 h,
	     int	 dstX,
	     int	 dstY)
{
    RegionPtr pRegion;
    BoxRec    box;

    XGL_GC_PRIV (pGC);

    box.x1 = pSrc->x + srcX;
    box.y1 = pSrc->y + srcY;
    box.x2 = box.x1 + w;
    box.y2 = box.y1 + h;

    if (pGC->alu != GXcopy || pGCPriv->flags)
    {
	if (!xglSyncBits (pSrc, &box))
	    FatalError (XGL_SW_FAILURE_STRING);

	XGL_GC_OP_FALLBACK_PROLOGUE (pDst);
	pRegion = (*pGC->ops->CopyArea) (pSrc, pDst, pGC,
					 srcX, srcY, w, h, dstX, dstY);
	XGL_GC_OP_FALLBACK_EPILOGUE (pDst);
    }
    else
    {
	/* xglCopyProc handles fall-back */
	pRegion = fbDoCopy (pSrc, pDst, pGC,
			    srcX, srcY,
			    w, h,
			    dstX, dstY,
			    xglCopyProc, 0,
			    (void *) &box);
    }

    return pRegion;
}

RegionPtr
xglCopyPlane (DrawablePtr   pSrc,
	      DrawablePtr   pDst,
	      GCPtr	    pGC,
	      int	    srcX,
	      int	    srcY,
	      int	    w,
	      int	    h,
	      int	    dstX,
	      int	    dstY,
	      unsigned long bitPlane)
{
    RegionPtr pRegion;
    BoxRec    box;

    XGL_GC_PRIV (pGC);

    box.x1 = pSrc->x + srcX;
    box.y1 = pSrc->y + srcY;
    box.x2 = box.x1 + w;
    box.y2 = box.y1 + h;

    if (!xglSyncBits (pSrc, &box))
	FatalError (XGL_SW_FAILURE_STRING);

    XGL_GC_OP_FALLBACK_PROLOGUE (pDst);
    pRegion = (*pGC->ops->CopyPlane) (pSrc, pDst, pGC,
				      srcX, srcY, w, h, dstX, dstY,
				      bitPlane);
    XGL_GC_OP_FALLBACK_EPILOGUE (pDst);

    return pRegion;
}

void
xglPolyPoint (DrawablePtr pDrawable,
	      GCPtr       pGC,
	      int	  mode,
	      int	  npt,
	      DDXPointPtr pptInit)
{
    XGL_GC_PRIV (pGC);

    XGL_GC_OP_FALLBACK_PROLOGUE (pDrawable);
    (*pGC->ops->PolyPoint) (pDrawable, pGC, mode, npt, pptInit);
    XGL_GC_OP_FALLBACK_EPILOGUE (pDrawable);
}

void
xglPolylines (DrawablePtr pDrawable,
	      GCPtr       pGC,
	      int	  mode,
	      int	  npt,
	      DDXPointPtr ppt)
{
    if (pGC->lineWidth == 0)
    {
	XGL_GC_PRIV (pGC);

	if (!pGCPriv->flags)
	{
	    if (pGC->lineStyle == LineSolid)
	    {
		if (xglFillLine (pDrawable, pGC, mode, npt, ppt))
		    return;
	    }
	}

	XGL_GC_FILL_OP_FALLBACK_PROLOGUE (pDrawable);
	(*pGC->ops->Polylines) (pDrawable, pGC, mode, npt, ppt);
	XGL_GC_OP_FALLBACK_EPILOGUE (pDrawable);
    }
    else
    {
	if (pGC->lineStyle != LineSolid)
	    miWideDash (pDrawable, pGC, mode, npt, ppt);
	else
	    miWideLine (pDrawable, pGC, mode, npt, ppt);
    }
}

void
xglPolySegment (DrawablePtr pDrawable,
		GCPtr	    pGC,
		int	    nsegInit,
		xSegment    *pSegInit)
{
    if (pGC->lineWidth == 0)
    {
	XGL_GC_PRIV (pGC);

	if (!pGCPriv->flags)
	{
	    if (pGC->lineStyle == LineSolid)
	    {
		if (xglFillSegment (pDrawable, pGC, nsegInit, pSegInit))
		    return;
	    }
	}

	XGL_GC_FILL_OP_FALLBACK_PROLOGUE (pDrawable);
	(*pGC->ops->PolySegment) (pDrawable, pGC, nsegInit, pSegInit);
	XGL_GC_OP_FALLBACK_EPILOGUE (pDrawable);
    } else
	miPolySegment (pDrawable, pGC, nsegInit, pSegInit);
}

void
xglPolyArc (DrawablePtr pDrawable,
	    GCPtr	pGC,
	    int		narcs,
	    xArc	*pArcs)
{
    if (pGC->lineWidth == 0)
    {
	XGL_GC_PRIV (pGC);

	XGL_GC_FILL_OP_FALLBACK_PROLOGUE (pDrawable);
	(*pGC->ops->PolyArc) (pDrawable, pGC, narcs, pArcs);
	XGL_GC_OP_FALLBACK_EPILOGUE (pDrawable);
    } else
	miPolyArc (pDrawable, pGC, narcs, pArcs);
}

void
xglPolyFillRect (DrawablePtr pDrawable,
		 GCPtr	     pGC,
		 int	     nrect,
		 xRectangle  *prect)
{
    XGL_GC_PRIV (pGC);

    if (pGC->fillStyle == FillStippled || pGCPriv->flags)
    {
	XGL_GC_FILL_OP_FALLBACK_PROLOGUE (pDrawable);
	(*pGC->ops->PolyFillRect) (pDrawable, pGC, nrect, prect);
	XGL_GC_OP_FALLBACK_EPILOGUE (pDrawable);
    }
    else
    {
	/* xglFillRect handles fall-back */
	xglFillRect (pDrawable, pGC, nrect, prect);
    }
}

void
xglPolyFillArc (DrawablePtr pDrawable,
		GCPtr	    pGC,
		int	    narcs,
		xArc	    *pArcs)
{
    XGL_GC_PRIV (pGC);

    XGL_GC_FILL_OP_FALLBACK_PROLOGUE (pDrawable);
    (*pGC->ops->PolyFillArc) (pDrawable, pGC, narcs, pArcs);
    XGL_GC_OP_FALLBACK_EPILOGUE (pDrawable);
}

void
xglImageGlyphBlt (DrawablePtr  pDrawable,
		  GCPtr	       pGC,
		  int	       x,
		  int	       y,
		  unsigned int nglyph,
		  CharInfoPtr  *ppci,
		  pointer      pglyphBase)
{
    XGL_GC_PRIV (pGC);

    if (!pGCPriv->flags)
    {
	if (xglSolidGlyph (pDrawable,
			   pGC,
			   x,
			   y,
			   nglyph,
			   ppci,
			   pglyphBase))
	    return;
    }

    XGL_GC_OP_FALLBACK_PROLOGUE (pDrawable);
    (*pGC->ops->ImageGlyphBlt) (pDrawable, pGC, x, y, nglyph, ppci,
				pglyphBase);
    XGL_GC_OP_FALLBACK_EPILOGUE (pDrawable);
}

void
xglPolyGlyphBlt (DrawablePtr  pDrawable,
		 GCPtr	      pGC,
		 int	      x,
		 int	      y,
		 unsigned int nglyph,
		 CharInfoPtr  *ppci,
		 pointer      pglyphBase)
{
    XGL_GC_PRIV (pGC);

    if (!pGCPriv->flags)
    {
	if (xglFillGlyph (pDrawable,
			  pGC,
			  x,
			  y,
			  nglyph,
			  ppci,
			  pglyphBase))
	    return;
    }

    XGL_GC_FILL_OP_FALLBACK_PROLOGUE (pDrawable);
    (*pGC->ops->PolyGlyphBlt) (pDrawable, pGC, x, y, nglyph, ppci, pglyphBase);
    XGL_GC_OP_FALLBACK_EPILOGUE (pDrawable);
}

void
xglPushPixels (GCPtr	   pGC,
	       PixmapPtr   pBitmap,
	       DrawablePtr pDrawable,
	       int	   w,
	       int	   h,
	       int	   x,
	       int	   y)
{
    XGL_GC_PRIV (pGC);

    if (!xglSyncBits (&pBitmap->drawable, NullBox))
	FatalError (XGL_SW_FAILURE_STRING);

    XGL_GC_OP_FALLBACK_PROLOGUE (pDrawable);
    (*pGC->ops->PushPixels) (pGC, pBitmap, pDrawable, w, h, x, y);
    XGL_GC_OP_FALLBACK_EPILOGUE (pDrawable);
}

Bool
xglCreateGC (GCPtr pGC)
{
    ScreenPtr pScreen = pGC->pScreen;
    Bool      ret;

    XGL_SCREEN_PRIV (pScreen);
    XGL_GC_PRIV (pGC);

    XGL_SCREEN_UNWRAP (CreateGC);
    ret = (*pScreen->CreateGC) (pGC);
    XGL_SCREEN_WRAP (CreateGC, xglCreateGC);

    XGL_GC_WRAP (funcs, (GCFuncs *) &xglGCFuncs);
    XGL_GC_WRAP (ops, (GCOps *) &xglGCOps);

    pGCPriv->flags = 0;
    pGCPriv->op = GLITZ_OPERATOR_SRC;

    pGCPriv->fg = NULL;
    pGCPriv->bg = NULL;
    pGCPriv->id = ~0;

    return ret;
}

void
xglDestroyGC (GCPtr pGC)
{
    XGL_GC_PRIV (pGC);

    if (pGCPriv->fg)
	glitz_surface_destroy (pGCPriv->fg);

    if (pGCPriv->bg)
	glitz_surface_destroy (pGCPriv->bg);

    XGL_GC_UNWRAP (funcs);
    XGL_GC_UNWRAP (ops);
    (*pGC->funcs->DestroyGC) (pGC);
    XGL_GC_WRAP (funcs, (GCFuncs *) &xglGCFuncs);
    XGL_GC_WRAP (ops, (GCOps *) &xglGCOps);
}

void
xglValidateGC (GCPtr	     pGC,
	       unsigned long changes,
	       DrawablePtr   pDrawable)
{
    XGL_GC_PRIV (pGC);

    if (changes & GCTile)
    {
	if (!pGC->tileIsPixel &&
	    FbEvenTile (pGC->tile.pixmap->drawable.width *
			pDrawable->bitsPerPixel))
	    xglSyncBits (&pGC->tile.pixmap->drawable, NULL);
    }

    if (changes & GCStipple)
    {
	if (pGC->stipple)
	    xglSyncBits (&pGC->stipple->drawable, NULL);
    }

    XGL_GC_UNWRAP (funcs);
    XGL_GC_UNWRAP (ops);
    (*pGC->funcs->ValidateGC) (pGC, changes, pDrawable);
    XGL_GC_WRAP (funcs, (GCFuncs *) &xglGCFuncs);
    XGL_GC_WRAP (ops, (GCOps *) &xglGCOps);

    if (pDrawable->serialNumber != (pGC->serialNumber & DRAWABLE_SERIAL_BITS))
    {
	XGL_DRAWABLE_PIXMAP_PRIV (pDrawable);

	if (pPixmapPriv->pVisual && pPixmapPriv->pVisual->format.surface)
	{
	    glitz_format_t *format;

	    format = pPixmapPriv->pVisual->format.surface;
	    if (format->id != pGCPriv->id)
	    {
		XGL_SCREEN_PRIV (pDrawable->pScreen);

		pGCPriv->flags |= xglGCSoftwareDrawableFlag;

		if (pGCPriv->fg)
		    glitz_surface_destroy (pGCPriv->fg);

		pGCPriv->fg = glitz_surface_create (pScreenPriv->drawable,
						    format, 1, 1, 0, NULL);
		if (pGCPriv->fg)
		    glitz_surface_set_fill (pGCPriv->fg, GLITZ_FILL_REPEAT);

		if (pGCPriv->bg)
		    glitz_surface_destroy (pGCPriv->bg);

		pGCPriv->bg = glitz_surface_create (pScreenPriv->drawable,
						    format, 1, 1, 0, NULL);
		if (pGCPriv->bg)
		    glitz_surface_set_fill (pGCPriv->bg, GLITZ_FILL_REPEAT);

		pGCPriv->id = format->id;

		if (pGCPriv->fg && pGCPriv->bg)
		{
		    changes |= (GCForeground | GCBackground);
		    pGCPriv->flags &= ~xglGCSoftwareDrawableFlag;
		}
	    }
	}
	else
	    pGCPriv->flags |= xglGCSoftwareDrawableFlag;
    }

    if (changes & GCFunction)
    {
	switch (pGC->alu) {
	case GXclear:
	    pGCPriv->op = GLITZ_OPERATOR_CLEAR;
	    pGCPriv->flags &= ~xglGCBadFunctionFlag;
	    break;
	case GXcopy:
	    pGCPriv->op = GLITZ_OPERATOR_SRC;
	    pGCPriv->flags &= ~xglGCBadFunctionFlag;
	    break;
	case GXnoop:
	    pGCPriv->op = GLITZ_OPERATOR_DST;
	    pGCPriv->flags &= ~xglGCBadFunctionFlag;
	    break;
	default:
	    pGCPriv->flags |= xglGCBadFunctionFlag;
	    break;
	}
    }

    if (changes & GCPlaneMask)
    {
	FbBits mask;

	mask = FbFullMask (pDrawable->depth);

	if ((pGC->planemask & mask) != mask)
	    pGCPriv->flags |= xglGCPlaneMaskFlag;
	else
	    pGCPriv->flags &= ~xglGCPlaneMaskFlag;
    }

    if (!(pGCPriv->flags & xglGCSoftwareDrawableFlag))
    {
	if (changes & (GCForeground | GCBackground))
	{
	    glitz_pixel_format_t format;
	    glitz_buffer_t	 *buffer;
	    CARD32		 pixel;

	    XGL_DRAWABLE_PIXMAP_PRIV (pDrawable);

	    format.fourcc	  = GLITZ_FOURCC_RGB;
	    format.masks	  = pPixmapPriv->pVisual->pPixel->masks;
	    format.xoffset	  = 0;
	    format.skip_lines     = 0;
	    format.bytes_per_line = sizeof (CARD32);
	    format.scanline_order = GLITZ_PIXEL_SCANLINE_ORDER_BOTTOM_UP;

	    buffer = glitz_buffer_create_for_data (&pixel);

	    if (changes & GCForeground)
	    {
		pixel = pGC->fgPixel;
		glitz_set_pixels (pGCPriv->fg, 0, 0, 1, 1, &format, buffer);
	    }

	    if (changes & GCBackground)
	    {
		pixel = pGC->bgPixel;
		glitz_set_pixels (pGCPriv->bg, 0, 0, 1, 1, &format, buffer);
	    }

	    glitz_buffer_destroy (buffer);
	}
    }
}