/*
 * 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 "gcstruct.h"
#include "fb.h"

Bool
xglFill (DrawablePtr	pDrawable,
	 GCPtr		pGC,
	 xglGeometryPtr pGeometry,
	 int		x,
	 int		y,
	 int		width,
	 int		height,
	 BoxPtr		pBox,
	 int		nBox)
{
    XGL_GC_PRIV (pGC);

    switch (pGC->fillStyle) {
    case FillSolid:
	if (xglSolid (pDrawable,
		      pGCPriv->op, pGCPriv->fg,
		      pGeometry,
		      x, y,
		      width, height,
		      pBox, nBox))
	    return TRUE;
	break;
    case FillStippled:
    case FillOpaqueStippled:
	break;
    case FillTiled:
	if (xglTile (pDrawable,
		     pGCPriv->op, pGC->tile.pixmap,
		     -(pGC->patOrg.x + pDrawable->x),
		     -(pGC->patOrg.y + pDrawable->y),
		     pGeometry,
		     x, y,
		     width, height,
		     pBox, nBox))
	    return TRUE;
	break;
    }

    return FALSE;
}

static void
xglFillBox (DrawablePtr pDrawable,
	    GCPtr	pGC,
	    int		x,
	    int		y,
	    int		width,
	    int		height,
	    BoxPtr	pBox,
	    int		nBox)
{
    if (!nBox)
	return;

    if (!xglFill (pDrawable, pGC, NULL, x, y, width, height, pBox, nBox))
    {
	RegionRec region;

	XGL_DRAWABLE_PIXMAP (pDrawable);
	XGL_PIXMAP_PRIV (pPixmap);

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

	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;
	}

	pPixmapPriv->damageBox = miEmptyBox;

	while (nBox--)
	{
	    fbFill (pDrawable, pGC,
		    pBox->x1, pBox->y1,
		    pBox->x2 - pBox->x1, pBox->y2 - pBox->y1);

	    REGION_INIT (pDrawable->pScreen, &region, pBox, 1);
	    xglAddSurfaceDamage (pDrawable, &region);
	    REGION_UNINIT (pDrawable->pScreen, &region);

	    pBox++;
	}
    } else
	xglAddCurrentBitDamage (pDrawable);
}

#define N_STACK_BOX 1024

static BoxPtr
xglMoreBoxes (BoxPtr stackBox,
	      BoxPtr heapBox,
	      int    nBoxes)
{
    Bool stack = !heapBox;

    heapBox = xrealloc (heapBox, sizeof (BoxRec) * nBoxes);
    if (!heapBox)
	return NULL;

    if (stack)
	memcpy (heapBox, stackBox, sizeof (BoxRec) * N_STACK_BOX);

    return heapBox;
}

#define ADD_BOX(pBox, nBox, stackBox, heapBox, size, box)	\
    {								\
	if ((nBox) == (size))					\
	{							\
	    (size) *= 2;					\
	    (heapBox) = xglMoreBoxes (stackBox, heapBox, size);	\
	    if (heapBox)					\
	    {							\
		(pBox) = (heapBox) + (nBox);			\
		*(pBox)++ = (box);				\
		(nBox)++;					\
	    }							\
	}							\
	else							\
	{							\
	    *(pBox)++ = (box);					\
	    (nBox)++;						\
	}							\
    }

void
xglFillRect (DrawablePtr pDrawable,
	     GCPtr	 pGC,
	     int	 nrect,
	     xRectangle  *prect)
{
    RegionPtr pClip = pGC->pCompositeClip;
    BoxPtr    pClipBox;
    BoxPtr    pExtent = REGION_EXTENTS (pGC->pScreen, pClip);
    int	      fullX1, fullX2, fullY1, fullY2;
    BoxRec    part, full;
    BoxPtr    heapBox = NULL;
    BoxRec    stackBox[N_STACK_BOX];
    int       size = N_STACK_BOX;
    BoxPtr    pBox = stackBox;
    int	      nClip, nBox = 0;

    while (nrect--)
    {
	fullX1 = prect->x + pDrawable->x;
	fullY1 = prect->y + pDrawable->y;
	fullX2 = fullX1 + (int) prect->width;
	fullY2 = fullY1 + (int) prect->height;

	prect++;

	if (fullX1 < pExtent->x1)
	    fullX1 = pExtent->x1;
	if (fullY1 < pExtent->y1)
	    fullY1 = pExtent->y1;
	if (fullX2 > pExtent->x2)
	    fullX2 = pExtent->x2;
	if (fullY2 > pExtent->y2)
	    fullY2 = pExtent->y2;

	full.x1 = fullX1;
	full.y1 = fullY1;
	full.x2 = fullX2;
	full.y2 = fullY2;

	if (full.x1 >= full.x2 || full.y1 >= full.y2)
	    continue;

	nClip = REGION_NUM_RECTS (pClip);
	if (nClip == 1)
	{
	    ADD_BOX (pBox, nBox, stackBox, heapBox, size, full);
	}
	else
	{
	    pClipBox = REGION_RECTS (pClip);
	    while (nClip--)
	    {
		part = *pClipBox++;

		if (part.x1 < full.x1)
		    part.x1 = full.x1;
		if (part.y1 < full.y1)
		    part.y1 = full.y1;
		if (part.x2 > full.x2)
		    part.x2 = full.x2;
		if (part.y2 > full.y2)
		    part.y2 = full.y2;

		if (part.x1 < part.x2 && part.y1 < part.y2)
		    ADD_BOX (pBox, nBox, stackBox, heapBox, size, part);
	    }
	}
    }

    xglFillBox (pDrawable, pGC,
		pExtent->x1, pExtent->y1,
		pExtent->x2 - pExtent->x1, pExtent->y2 - pExtent->y1,
		(heapBox) ? heapBox : stackBox, nBox);

    if (heapBox)
	xfree (heapBox);
}

void
xglFillSpan (DrawablePtr pDrawable,
	     GCPtr	 pGC,
	     int	 n,
	     DDXPointPtr ppt,
	     int	 *pwidth)
{
    RegionPtr pClip = pGC->pCompositeClip;
    BoxPtr    pClipBox;
    BoxPtr    pExtent = REGION_EXTENTS (pGC->pScreen, pClip);
    int	      fullX1, fullX2, fullY1, fullY2;
    BoxRec    part, full;
    BoxPtr    heapBox = NULL;
    BoxRec    stackBox[N_STACK_BOX];
    int       size = N_STACK_BOX;
    BoxPtr    pBox = stackBox;
    int	      nClip, nBox = 0;

    while (n--)
    {
	fullX1 = ppt->x;
	fullY1 = ppt->y;
	fullX2 = fullX1 + *pwidth;
	fullY2 = fullY1 + 1;

	pwidth++;
	ppt++;

	if (fullX1 < pExtent->x1)
	    fullX1 = pExtent->x1;
	if (fullY1 < pExtent->y1)
	    fullY1 = pExtent->y1;
	if (fullX2 > pExtent->x2)
	    fullX2 = pExtent->x2;
	if (fullY2 > pExtent->y2)
	    fullY2 = pExtent->y2;

	full.x1 = fullX1;
	full.y1 = fullY1;
	full.x2 = fullX2;
	full.y2 = fullY2;

	if (full.x1 >= full.x2 || full.y1 >= full.y2)
	    continue;

	nClip = REGION_NUM_RECTS (pClip);
	if (nClip == 1)
	{
	    ADD_BOX (pBox, nBox, stackBox, heapBox, size, full);
	}
	else
	{
	    pClipBox = REGION_RECTS (pClip);
	    while (nClip--)
	    {
		part = *pClipBox++;

		if (part.x1 < full.x1)
		    part.x1 = full.x1;
		if (part.y1 < full.y1)
		    part.y1 = full.y1;
		if (part.x2 > full.x2)
		    part.x2 = full.x2;
		if (part.y2 > full.y2)
		    part.y2 = full.y2;

		if (part.x1 < part.x2 && part.y1 < part.y2)
		    ADD_BOX (pBox, nBox, stackBox, heapBox, size, part);
	    }
	}
    }

    xglFillBox (pDrawable, pGC,
		pExtent->x1, pExtent->y1,
		pExtent->x2 - pExtent->x1, pExtent->y2 - pExtent->y1,
		(heapBox) ? heapBox : stackBox, nBox);

    if (heapBox)
	xfree (heapBox);
}

Bool
xglFillLine (DrawablePtr pDrawable,
	     GCPtr       pGC,
	     int	 mode,
	     int	 npt,
	     DDXPointPtr ppt)
{
    RegionPtr	   pClip = pGC->pCompositeClip;
    BoxPtr	   pExtent = REGION_EXTENTS (pGC->pScreen, pClip);
    Bool	   coincidentEndpoints = FALSE;
    Bool	   horizontalAndVertical = TRUE;
    DDXPointPtr    pptTmp;
    int		   nptTmp;
    DDXPointRec    pt;
    xglGeometryPtr pGeometry;

    XGL_SCREEN_PRIV (pGC->pScreen);

    if (npt < 2)
	return TRUE;

    pt = *ppt;

    nptTmp = npt - 1;
    pptTmp = ppt + 1;

    if (mode == CoordModePrevious)
    {
	while (nptTmp--)
	{
	    if (pptTmp->x && pptTmp->y)
		horizontalAndVertical = FALSE;

	    pt.x += pptTmp->x;
	    pt.y += pptTmp->y;

	    pptTmp++;
	}

	if (pt.x == ppt->x && pt.y == ppt->y)
	    coincidentEndpoints = TRUE;
    }
    else
    {
	while (nptTmp--)
	{
	    if (pptTmp->x != pt.x && pptTmp->y != pt.y)
	    {
		horizontalAndVertical = FALSE;
		break;
	    }

	    pt = *pptTmp++;
	}

	if (ppt[npt - 1].x == ppt->x && ppt[npt - 1].y == ppt->y)
	    coincidentEndpoints = TRUE;
    }

    if (horizontalAndVertical)
    {
	BoxPtr pClipBox;
	int    fullX1, fullX2, fullY1, fullY2;
	BoxRec part, full;
	BoxPtr heapBox = NULL;
	BoxRec stackBox[N_STACK_BOX];
	int    size = N_STACK_BOX;
	BoxPtr pBox = stackBox;
	int    nClip, nBox = 0;
	int    dx, dy;

	pt = *ppt;

	ppt++;
	npt--;

	while (npt--)
	{
	    if (mode == CoordModePrevious)
	    {
		dx = ppt->x;
		dy = ppt->y;
	    }
	    else
	    {
		dx = ppt->x - pt.x;
		dy = ppt->y - pt.y;
	    }

	    if (dx)
	    {
		if (dx > 0)
		{
		    fullX1 = pt.x + pDrawable->x;

		    if (npt || coincidentEndpoints)
			fullX2 = fullX1 + dx;
		    else
			fullX2 = fullX1 + dx + 1;
		}
		else
		{
		    fullX2 = pt.x + pDrawable->x + 1;

		    if (npt || coincidentEndpoints)
			fullX1 = fullX2 + dx;
		    else
			fullX1 = fullX2 + dx - 1;
		}

		fullY1 = pt.y + pDrawable->y;
		fullY2 = fullY1 + 1;
	    }
	    else
	    {
		if (dy > 0)
		{
		    fullY1 = pt.y + pDrawable->y;

		    if (npt || coincidentEndpoints)
			fullY2 = fullY1 + dy;
		    else
			fullY2 = fullY1 + dy + 1;
		}
		else
		{
		    fullY2 = pt.y + pDrawable->y + 1;

		    if (npt || coincidentEndpoints)
			fullY1 = fullY2 + dy;
		    else
			fullY1 = fullY2 + dy - 1;
		}

		fullX1 = pt.x + pDrawable->x;
		fullX2 = fullX1 + 1;
	    }

	    pt.x += dx;
	    pt.y += dy;

	    ppt++;

	    if (fullX1 < pExtent->x1)
		fullX1 = pExtent->x1;
	    if (fullY1 < pExtent->y1)
		fullY1 = pExtent->y1;
	    if (fullX2 > pExtent->x2)
		fullX2 = pExtent->x2;
	    if (fullY2 > pExtent->y2)
		fullY2 = pExtent->y2;

	    full.x1 = fullX1;
	    full.y1 = fullY1;
	    full.x2 = fullX2;
	    full.y2 = fullY2;

	    if (full.x1 >= full.x2 || full.y1 >= full.y2)
		continue;

	    nClip = REGION_NUM_RECTS (pClip);
	    if (nClip == 1)
	    {
		ADD_BOX (pBox, nBox, stackBox, heapBox, size, full);
	    }
	    else
	    {
		pClipBox = REGION_RECTS (pClip);
		while (nClip--)
		{
		    part = *pClipBox++;

		    if (part.x1 < full.x1)
			part.x1 = full.x1;
		    if (part.y1 < full.y1)
			part.y1 = full.y1;
		    if (part.x2 > full.x2)
			part.x2 = full.x2;
		    if (part.y2 > full.y2)
			part.y2 = full.y2;

		    if (part.x1 < part.x2 && part.y1 < part.y2)
			ADD_BOX (pBox, nBox, stackBox, heapBox, size, part);
		}
	    }
	}

	xglFillBox (pDrawable, pGC,
		    pExtent->x1, pExtent->y1,
		    pExtent->x2 - pExtent->x1, pExtent->y2 - pExtent->y1,
		    (heapBox) ? heapBox : stackBox, nBox);

	if (heapBox)
	    xfree (heapBox);

	return TRUE;
    }

    if (!pScreenPriv->lines)
	return FALSE;

    if (coincidentEndpoints)
	npt--;

    pGeometry = xglGetScratchVertexGeometry (pGC->pScreen, npt);

    GEOMETRY_ADD_LINE (pGC->pScreen, pGeometry,
		       coincidentEndpoints, mode, npt, ppt);

    if (coincidentEndpoints)
	GEOMETRY_SET_VERTEX_PRIMITIVE (pGeometry, GLITZ_PRIMITIVE_LINE_LOOP);
    else
	GEOMETRY_SET_VERTEX_PRIMITIVE (pGeometry, GLITZ_PRIMITIVE_LINE_STRIP);

    /* Lines need a 0.5 translate */
    GEOMETRY_TRANSLATE_FIXED (pGeometry, 1 << 15, 1 << 15);

    GEOMETRY_TRANSLATE (pGeometry, pDrawable->x, pDrawable->y);

    pExtent = REGION_EXTENTS (pDrawable->pScreen, pGC->pCompositeClip);

    if (xglFill (pDrawable, pGC, pGeometry,
		 pExtent->x1, pExtent->y1,
		 pExtent->x2 - pExtent->x1, pExtent->y2 - pExtent->y1,
		 REGION_RECTS (pGC->pCompositeClip),
		 REGION_NUM_RECTS (pGC->pCompositeClip)))
    {
	xglAddCurrentBitDamage (pDrawable);
	return TRUE;
    }

    return FALSE;
}

Bool
xglFillSegment (DrawablePtr pDrawable,
		GCPtr	    pGC,
		int	    nSegInit,
		xSegment    *pSegInit)
{
    RegionPtr	   pClip = pGC->pCompositeClip;
    BoxPtr	   pExtent = REGION_EXTENTS (pGC->pScreen, pClip);
    Bool	   horizontalAndVertical = TRUE;
    xglGeometryPtr pGeometry;
    xSegment	   *pSeg;
    int		   nSeg;

    XGL_SCREEN_PRIV (pGC->pScreen);

    if (nSegInit < 1)
	return TRUE;

    pSeg = pSegInit;
    nSeg = nSegInit;
    while (nSeg--)
    {
	if (pSeg->x1 != pSeg->x2 && pSeg->y1 != pSeg->y2)
	    horizontalAndVertical = FALSE;

	pSeg++;
    }

    if (horizontalAndVertical)
    {
	BoxPtr pClipBox;
	int    fullX1, fullX2, fullY1, fullY2;
	BoxRec part, full;
	BoxPtr heapBox = NULL;
	BoxRec stackBox[N_STACK_BOX];
	int    size = N_STACK_BOX;
	BoxPtr pBox = stackBox;
	int    nClip, nBox = 0;

	while (nSegInit--)
	{
	    if (pSegInit->x1 != pSegInit->x2)
	    {
		if (pSegInit->x1 < pSegInit->x2)
		{
		    fullX1 = pSegInit->x1;
		    fullX2 = pSegInit->x2;
		    if (pGC->capStyle != CapNotLast)
			fullX2++;
		}
		else
		{
		    fullX1 = pSegInit->x2;
		    fullX2 = pSegInit->x1 + 1;
		    if (pGC->capStyle == CapNotLast)
			fullX1++;
		}

		fullX1 += pDrawable->x;
		fullX2 += pDrawable->x;
		fullY1  = pSegInit->y1 + pDrawable->y;
		fullY2  = fullY1 + 1;
	    }
	    else
	    {
		if (pSegInit->y1 < pSegInit->y2)
		{
		    fullY1 = pSegInit->y1;
		    fullY2 = pSegInit->y2;
		    if (pGC->capStyle != CapNotLast)
			fullY2++;
		}
		else
		{
		    fullY1 = pSegInit->y2;
		    fullY2 = pSegInit->y1 + 1;
		    if (pGC->capStyle == CapNotLast)
			fullY1++;
		}

		fullY1 += pDrawable->y;
		fullY2 += pDrawable->y;
		fullX1  = pSegInit->x1 + pDrawable->x;
		fullX2  = fullX1 + 1;
	    }

	    pSegInit++;

	    if (fullX1 < pExtent->x1)
		fullX1 = pExtent->x1;
	    if (fullY1 < pExtent->y1)
		fullY1 = pExtent->y1;
	    if (fullX2 > pExtent->x2)
		fullX2 = pExtent->x2;
	    if (fullY2 > pExtent->y2)
		fullY2 = pExtent->y2;

	    full.x1 = fullX1;
	    full.y1 = fullY1;
	    full.x2 = fullX2;
	    full.y2 = fullY2;

	    if (full.x1 >= full.x2 || full.y1 >= full.y2)
		continue;

	    nClip = REGION_NUM_RECTS (pClip);
	    if (nClip == 1)
	    {
		ADD_BOX (pBox, nBox, stackBox, heapBox, size, full);
	    }
	    else
	    {
		pClipBox = REGION_RECTS (pClip);
		while (nClip--)
		{
		    part = *pClipBox++;

		    if (part.x1 < full.x1)
			part.x1 = full.x1;
		    if (part.y1 < full.y1)
			part.y1 = full.y1;
		    if (part.x2 > full.x2)
			part.x2 = full.x2;
		    if (part.y2 > full.y2)
			part.y2 = full.y2;

		    if (part.x1 < part.x2 && part.y1 < part.y2)
			ADD_BOX (pBox, nBox, stackBox, heapBox, size, part);
		}
	    }
	}

	xglFillBox (pDrawable, pGC,
		    pExtent->x1, pExtent->y1,
		    pExtent->x2 - pExtent->x1, pExtent->y2 - pExtent->y1,
		    (heapBox) ? heapBox : stackBox, nBox);

	if (heapBox)
	    xfree (heapBox);

	return TRUE;
    }

    if (!pScreenPriv->lines)
	return FALSE;

    pGeometry = xglGetScratchVertexGeometry (pGC->pScreen, 2 * nSegInit);

    GEOMETRY_ADD_SEGMENT (pGC->pScreen, pGeometry, nSegInit, pSegInit);

    /* Line segments need 0.5 translate */
    GEOMETRY_TRANSLATE_FIXED (pGeometry, 1 << 15, 1 << 15);
    GEOMETRY_SET_VERTEX_PRIMITIVE (pGeometry, GLITZ_PRIMITIVE_LINES);

    GEOMETRY_TRANSLATE (pGeometry, pDrawable->x, pDrawable->y);

    if (xglFill (pDrawable, pGC, pGeometry,
		 pExtent->x1, pExtent->y1,
		 pExtent->x2 - pExtent->x1, pExtent->y2 - pExtent->y1,
		 REGION_RECTS (pGC->pCompositeClip),
		 REGION_NUM_RECTS (pGC->pCompositeClip)))
    {
	xglAddCurrentBitDamage (pDrawable);
	return TRUE;
    }

    return FALSE;
}

Bool
xglFillGlyph (DrawablePtr  pDrawable,
	      GCPtr	   pGC,
	      int	   x,
	      int	   y,
	      unsigned int nGlyph,
	      CharInfoPtr  *ppci,
	      pointer      pglyphBase)
{
    BoxPtr	   pExtent;
    xglGeometryRec geometry;

    if (nGlyph < 1)
	return TRUE;

    pExtent = REGION_EXTENTS (pDrawable->pScreen, pGC->pCompositeClip);

    x += pDrawable->x;
    y += pDrawable->y;

    GEOMETRY_INIT (pDrawable->pScreen, &geometry,
		   GLITZ_GEOMETRY_TYPE_BITMAP,
		   GEOMETRY_USAGE_SYSMEM, 0);

    GEOMETRY_FOR_GLYPH (pDrawable->pScreen,
			&geometry,
			nGlyph,
			ppci,
			pglyphBase);

    GEOMETRY_TRANSLATE (&geometry, x, y);

    if (xglFill (pDrawable, pGC, &geometry,
		 pExtent->x1, pExtent->y1,
		 pExtent->x2 - pExtent->x1, pExtent->y2 - pExtent->y1,
		 REGION_RECTS (pGC->pCompositeClip),
		 REGION_NUM_RECTS (pGC->pCompositeClip)))
    {
	GEOMETRY_UNINIT (&geometry);
	xglAddCurrentBitDamage (pDrawable);
	return TRUE;
    }

    GEOMETRY_UNINIT (&geometry);
    return FALSE;
}
