/*
 * Copyright 2001-2004 Red Hat Inc., Durham, North Carolina.
 *
 * 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 on 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
 * NON-INFRINGEMENT.  IN NO EVENT SHALL RED HAT AND/OR THEIR 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.
 */

/*
 * Authors:
 *   Kevin E. Martin <kem@redhat.com>
 *
 */

/** \file
 * This file provides support for GCs. */

#ifdef HAVE_DMX_CONFIG_H
#include <dmx-config.h>
#endif

#include "dmx.h"
#include "dmxsync.h"
#include "dmxgc.h"
#include "dmxgcops.h"
#include "dmxpixmap.h"
#include "dmxfont.h"

#include "gcstruct.h"
#include "pixmapstr.h"
#include "migc.h"

static GCFuncs dmxGCFuncs = {
    dmxValidateGC,
    dmxChangeGC,
    dmxCopyGC,
    dmxDestroyGC,
    dmxChangeClip,
    dmxDestroyClip,
    dmxCopyClip,
};

static GCOps dmxGCOps = {
    dmxFillSpans,
    dmxSetSpans,
    dmxPutImage,
    dmxCopyArea,
    dmxCopyPlane,
    dmxPolyPoint,
    dmxPolylines,
    dmxPolySegment,
    dmxPolyRectangle,
    dmxPolyArc,
    dmxFillPolygon,
    dmxPolyFillRect,
    dmxPolyFillArc,
    dmxPolyText8,
    dmxPolyText16,
    dmxImageText8,
    dmxImageText16,
    dmxImageGlyphBlt,
    dmxPolyGlyphBlt,
    dmxPushPixels
};

/** Initialize the GC on \a pScreen */
Bool dmxInitGC(ScreenPtr pScreen)
{
    if (!dixRegisterPrivateKey(&dmxGCPrivateKeyRec, PRIVATE_GC, sizeof(dmxGCPrivRec)))
            return FALSE;
    return TRUE;
}

/** Create the GC on the back-end server. */
void dmxBECreateGC(ScreenPtr pScreen, GCPtr pGC)
{
    DMXScreenInfo *dmxScreen = &dmxScreens[pScreen->myNum];
    dmxGCPrivPtr   pGCPriv = DMX_GET_GC_PRIV(pGC);
    int            i;

    for (i = 0; i < dmxScreen->beNumPixmapFormats; i++) {
	if (pGC->depth == dmxScreen->bePixmapFormats[i].depth) {
	    unsigned long  mask;
	    XGCValues      gcvals;

	    mask = GCGraphicsExposures;
	    gcvals.graphics_exposures = FALSE;

	    /* Create GC in the back-end servers */
	    pGCPriv->gc = XCreateGC(dmxScreen->beDisplay,
				    dmxScreen->scrnDefDrawables[i],
				    mask, &gcvals);
	    break;
	}
    }
}

/** Create a graphics context on the back-end server associated /a pGC's
 *  screen. */
Bool dmxCreateGC(GCPtr pGC)
{
    ScreenPtr      pScreen = pGC->pScreen;
    DMXScreenInfo *dmxScreen = &dmxScreens[pScreen->myNum];
    dmxGCPrivPtr   pGCPriv = DMX_GET_GC_PRIV(pGC);
    Bool           ret;

    DMX_UNWRAP(CreateGC, dmxScreen, pScreen);
    if ((ret = pScreen->CreateGC(pGC))) {
	/* Save the old funcs */
	pGCPriv->funcs = pGC->funcs;
	pGCPriv->ops   = NULL;

	pGC->funcs = &dmxGCFuncs;

	if (dmxScreen->beDisplay) {
	    dmxBECreateGC(pScreen, pGC);
	} else {
	    pGCPriv->gc = NULL;
	}

	/* Check for "magic special case"
	 * 1. see CreateGC in dix/gc.c for more info
	 * 2. see dmxChangeGC for more info
	 */
	pGCPriv->msc = (!pGC->tileIsPixel && !pGC->tile.pixmap);
    }
    DMX_WRAP(CreateGC, dmxCreateGC, dmxScreen, pScreen);

    return ret;
}

/** Validate a graphics context, \a pGC, locally in the DMX server and
 *  recompute the composite clip, if necessary. */
void dmxValidateGC(GCPtr pGC, unsigned long changes, DrawablePtr pDrawable)
{
    dmxGCPrivPtr pGCPriv = DMX_GET_GC_PRIV(pGC);

    DMX_GC_FUNC_PROLOGUE(pGC);
#if 0
    pGC->funcs->ValidateGC(pGC, changes, pDrawable);
#endif

    if (pDrawable->type == DRAWABLE_WINDOW ||
	pDrawable->type == DRAWABLE_PIXMAP) {
	/* Save the old ops, since we're about to change the ops in the
	 * epilogue.
	 */
	pGCPriv->ops = pGC->ops;
    } else {
	pGCPriv->ops = NULL;
    }

    /* If the client clip is different or moved OR the subwindowMode has
     * changed OR the window's clip has changed since the last
     * validation, then we need to recompute the composite clip.
     */
    if ((changes & (GCClipXOrigin |
		    GCClipYOrigin |
		    GCClipMask |
		    GCSubwindowMode)) ||
	(pDrawable->serialNumber !=
	 (pGC->serialNumber & DRAWABLE_SERIAL_BITS))) {
	miComputeCompositeClip(pGC, pDrawable);
    }

    DMX_GC_FUNC_EPILOGUE(pGC);
}

/** Set the values in the graphics context on the back-end server
 *  associated with \a pGC's screen. */
void dmxChangeGC(GCPtr pGC, unsigned long mask)
{
    ScreenPtr      pScreen = pGC->pScreen;
    DMXScreenInfo *dmxScreen = &dmxScreens[pScreen->myNum];
    dmxGCPrivPtr   pGCPriv = DMX_GET_GC_PRIV(pGC);
    XGCValues      v;

    DMX_GC_FUNC_PROLOGUE(pGC);
#if 0
    pGC->funcs->ChangeGC(pGC, mask);
#endif

    /* Handle "magic special case" from CreateGC */
    if (pGCPriv->msc) {
	/* The "magic special case" is used to handle the case where a
	 * foreground pixel is set when the GC is created so that a
	 * "pseudo default-tile" can be created and used in case the
	 * fillstyle was set to FillTiled.  This specific case is tested
	 * in xtest (XCreateGC test #3).  What has happened in dix by
	 * the time it reaches here is (1) the pGC->tile.pixel has been
	 * set to pGC->fgPixel and pGC->tileIsPixel is set, (2) if a
	 * tile has also been set, then pGC->tileIsPixel is unset and
	 * pGC->tile.pixmap is initialized; else, the default tile is
	 * created and pGC->tileIsPixel is unset and pGC->tile.pixmap is
	 * initialized to the "pseudo default-tile".  In either case,
	 * pGC->tile.pixmap is set; however, in the "magic special case"
	 * the mask is not updated to allow us to detect that we should
	 * initialize the GCTile in the back-end server.  Thus, we catch
	 * this case in dmxCreateGC and add GCTile to the mask here.
	 * Are there any cases that I've missed?
	 */

	/* Make sure that the tile.pixmap is set, just in case the user
         * set GCTile in the mask but forgot to set vals.pixmap
	 */
	if (pGC->tile.pixmap) mask |= GCTile;

	/* This only happens once when the GC is created */
	pGCPriv->msc = FALSE;
    }

    /* Update back-end server's gc */
    if (mask & GCFunction)          v.function = pGC->alu;
    if (mask & GCPlaneMask)         v.plane_mask = pGC->planemask;
    if (mask & GCForeground)        v.foreground = pGC->fgPixel;
    if (mask & GCBackground)        v.background = pGC->bgPixel;
    if (mask & GCLineWidth)         v.line_width = pGC->lineWidth;
    if (mask & GCLineStyle)         v.line_style = pGC->lineStyle;
    if (mask & GCCapStyle)          v.cap_style = pGC->capStyle;
    if (mask & GCJoinStyle)         v.join_style = pGC->joinStyle;
    if (mask & GCFillStyle)         v.fill_style = pGC->fillStyle;
    if (mask & GCFillRule)          v.fill_rule = pGC->fillRule;
    if (mask & GCTile) {
	if (pGC->tileIsPixel) {
	    mask &= ~GCTile;
	} else {
	    dmxPixPrivPtr  pPixPriv = DMX_GET_PIXMAP_PRIV(pGC->tile.pixmap);
	    v.tile = (Drawable)pPixPriv->pixmap;
	}
    }
    if (mask & GCStipple) {
	dmxPixPrivPtr  pPixPriv = DMX_GET_PIXMAP_PRIV(pGC->stipple);
	v.stipple = (Drawable)pPixPriv->pixmap;
    }
    if (mask & GCTileStipXOrigin)   v.ts_x_origin = pGC->patOrg.x; 
    if (mask & GCTileStipYOrigin)   v.ts_y_origin = pGC->patOrg.y;
    if (mask & GCFont) {
	if (dmxScreen->beDisplay) {
	    dmxFontPrivPtr  pFontPriv;
	    pFontPriv = FontGetPrivate(pGC->font, dmxFontPrivateIndex);
	    v.font = pFontPriv->font[pScreen->myNum]->fid;
	} else {
	    mask &= ~GCFont;
	}
    }
    if (mask & GCSubwindowMode)     v.subwindow_mode = pGC->subWindowMode;

    /* Graphics exposures are not needed on the back-ends since they can
       be generated on the front-end thereby saving bandwidth. */
    if (mask & GCGraphicsExposures) mask &= ~GCGraphicsExposures;

    if (mask & GCClipXOrigin)       v.clip_x_origin = pGC->clipOrg.x;
    if (mask & GCClipYOrigin)       v.clip_y_origin = pGC->clipOrg.y;
    if (mask & GCClipMask)          mask &= ~GCClipMask; /* See ChangeClip */
    if (mask & GCDashOffset)        v.dash_offset = pGC->dashOffset;
    if (mask & GCDashList) {
	mask &= ~GCDashList;
	if (dmxScreen->beDisplay)
	    XSetDashes(dmxScreen->beDisplay, pGCPriv->gc,
		       pGC->dashOffset, (char *)pGC->dash,
		       pGC->numInDashList);
    }
    if (mask & GCArcMode)           v.arc_mode = pGC->arcMode;

    if (mask && dmxScreen->beDisplay) {
	XChangeGC(dmxScreen->beDisplay, pGCPriv->gc, mask, &v);
	dmxSync(dmxScreen, FALSE);
    }

    DMX_GC_FUNC_EPILOGUE(pGC);
}

/** Copy \a pGCSrc to \a pGCDst on the back-end server associated with
 *  \a pGCSrc's screen. */
void dmxCopyGC(GCPtr pGCSrc, unsigned long changes, GCPtr pGCDst)
{
    ScreenPtr      pScreen = pGCSrc->pScreen;
    DMXScreenInfo *dmxScreen = &dmxScreens[pScreen->myNum];
    dmxGCPrivPtr   pGCSrcPriv = DMX_GET_GC_PRIV(pGCSrc);
    dmxGCPrivPtr   pGCDstPriv = DMX_GET_GC_PRIV(pGCDst);

    DMX_GC_FUNC_PROLOGUE(pGCDst);
    pGCDst->funcs->CopyGC(pGCSrc, changes, pGCDst);

    /* Copy the GC on the back-end server */
    if (dmxScreen->beDisplay)
	XCopyGC(dmxScreen->beDisplay, pGCSrcPriv->gc, changes, pGCDstPriv->gc);

    DMX_GC_FUNC_EPILOGUE(pGCDst);
}

/** Free the \a pGC on the back-end server. */
Bool dmxBEFreeGC(GCPtr pGC)
{
    ScreenPtr      pScreen   = pGC->pScreen;
    DMXScreenInfo *dmxScreen = &dmxScreens[pScreen->myNum];
    dmxGCPrivPtr   pGCPriv   = DMX_GET_GC_PRIV(pGC);

    if (pGCPriv->gc) {
	XFreeGC(dmxScreen->beDisplay, pGCPriv->gc);
	pGCPriv->gc = NULL;
	return TRUE;
    }

    return FALSE;
}

/** Destroy the graphics context, \a pGC and free the corresponding GC
 *  on the back-end server. */
void dmxDestroyGC(GCPtr pGC)
{
    ScreenPtr      pScreen   = pGC->pScreen;
    DMXScreenInfo *dmxScreen = &dmxScreens[pScreen->myNum];

    DMX_GC_FUNC_PROLOGUE(pGC);

    /* Free the GC on the back-end server */
    if (dmxScreen->beDisplay)
	dmxBEFreeGC(pGC);

    pGC->funcs->DestroyGC(pGC);
    DMX_GC_FUNC_EPILOGUE(pGC);
}

/** Change the clip rects for a GC. */
void dmxChangeClip(GCPtr pGC, int type, pointer pvalue, int nrects)
{
    ScreenPtr      pScreen = pGC->pScreen;
    DMXScreenInfo *dmxScreen = &dmxScreens[pScreen->myNum];
    dmxGCPrivPtr   pGCPriv = DMX_GET_GC_PRIV(pGC);
    XRectangle    *pRects;
    BoxPtr         pBox;
    int            i, nRects;

    DMX_GC_FUNC_PROLOGUE(pGC);
    pGC->funcs->ChangeClip(pGC, type, pvalue, nrects);

    /* Set the client clip on the back-end server */
    switch (pGC->clientClipType) {
    case CT_NONE:
	if (dmxScreen->beDisplay)
	    XSetClipMask(dmxScreen->beDisplay, pGCPriv->gc, None);
	break;

    case CT_REGION:
	if (dmxScreen->beDisplay) {
	    nRects = RegionNumRects((RegionPtr)pGC->clientClip);
	    pRects = malloc(nRects * sizeof(*pRects));
	    pBox   = RegionRects((RegionPtr)pGC->clientClip);

	    for (i = 0; i < nRects; i++) {
		pRects[i].x      = pBox[i].x1;
		pRects[i].y      = pBox[i].y1;
		pRects[i].width  = pBox[i].x2 - pBox[i].x1;
		pRects[i].height = pBox[i].y2 - pBox[i].y1;
	    }

	    XSetClipRectangles(dmxScreen->beDisplay, pGCPriv->gc,
			       pGC->clipOrg.x, pGC->clipOrg.y,
			       pRects, nRects, Unsorted);

	    free(pRects);
	}
	break;

    case CT_PIXMAP:
    case CT_UNSORTED:
    case CT_YSORTED:
    case CT_YXSORTED:
    case CT_YXBANDED:
	/* These clip types are condensed down to either NONE or REGION
           in the mi code */
	break;
    }

    DMX_GC_FUNC_EPILOGUE(pGC);
}

/** Destroy a GC's clip rects. */
void dmxDestroyClip(GCPtr pGC)
{
    ScreenPtr      pScreen = pGC->pScreen;
    DMXScreenInfo *dmxScreen = &dmxScreens[pScreen->myNum];
    dmxGCPrivPtr   pGCPriv = DMX_GET_GC_PRIV(pGC);

    DMX_GC_FUNC_PROLOGUE(pGC);
    pGC->funcs->DestroyClip(pGC);

    /* Set the client clip on the back-end server to None */
    if (dmxScreen->beDisplay)
	XSetClipMask(dmxScreen->beDisplay, pGCPriv->gc, None);

    DMX_GC_FUNC_EPILOGUE(pGC);
}

/** Copy a GC's clip rects. */
void dmxCopyClip(GCPtr pGCDst, GCPtr pGCSrc)
{
    DMX_GC_FUNC_PROLOGUE(pGCDst);
    pGCDst->funcs->CopyClip(pGCDst, pGCSrc);
    DMX_GC_FUNC_EPILOGUE(pGCDst);
}