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

#ifdef HAVE_DIX_CONFIG_H
#include <dix-config.h>
#endif

#include <stdlib.h>

#include "fb.h"
#include "miline.h"

#define fbBresShiftMask(mask,dir,bpp) ((bpp == FB_STIP_UNIT) ? 0 : \
					((dir < 0) ? FbStipLeft(mask,bpp) : \
					 FbStipRight(mask,bpp)))

void
fbBresSolid (DrawablePtr    pDrawable,
	     GCPtr	    pGC,
	     int	    dashOffset,
	     int	    signdx,
	     int	    signdy,
	     int	    axis,
	     int	    x1,
	     int	    y1,
	     int	    e,
	     int	    e1,
	     int	    e3,
	     int	    len)
{
    FbStip	*dst;
    FbStride	dstStride;
    int		dstBpp;
    int		dstXoff, dstYoff;
    FbGCPrivPtr	pPriv = fbGetGCPrivate (pGC);
    FbStip	and = (FbStip) pPriv->and;
    FbStip	xor = (FbStip) pPriv->xor;
    FbStip	mask, mask0;
    FbStip	bits;
    
    fbGetStipDrawable (pDrawable, dst, dstStride, dstBpp, dstXoff, dstYoff);
    dst += ((y1 + dstYoff) * dstStride);
    x1 = (x1 + dstXoff) * dstBpp;
    dst += x1 >> FB_STIP_SHIFT;
    x1 &= FB_STIP_MASK;
    mask0 = FbStipMask(0, dstBpp);
    mask = FbStipRight (mask0, x1);
    if (signdx < 0)
	mask0 = FbStipRight (mask0, FB_STIP_UNIT - dstBpp);
    if (signdy < 0)
	dstStride = -dstStride;
    if (axis == X_AXIS)
    {
	bits = 0;
	while (len--)
	{
	    bits |= mask;
	    mask = fbBresShiftMask(mask,signdx,dstBpp);
	    if (!mask)
	    {
		WRITE(dst, FbDoMaskRRop (READ(dst), and, xor, bits));
		bits = 0;
		dst += signdx;
		mask = mask0;
	    }
	    e += e1;
	    if (e >= 0)
	    {
		WRITE(dst, FbDoMaskRRop (READ(dst), and, xor, bits));
		bits = 0;
		dst += dstStride;
		e += e3;
	    }
	}
	if (bits)
	    WRITE(dst, FbDoMaskRRop (READ(dst), and, xor, bits));
    }
    else
    {
	while (len--)
	{
	    WRITE(dst, FbDoMaskRRop (READ(dst), and, xor, mask));
	    dst += dstStride;
	    e += e1;
	    if (e >= 0)
	    {
		e += e3;
		mask = fbBresShiftMask(mask,signdx,dstBpp);
		if (!mask)
		{
		    dst += signdx;
		    mask = mask0;
		}
	    }
	}
    }

    fbFinishAccess (pDrawable);
}

void
fbBresDash (DrawablePtr	pDrawable,
	    GCPtr	pGC,
	    int		dashOffset,
	    int		signdx,
	    int		signdy,
	    int		axis,
	    int		x1,
	    int		y1,
	    int		e,
	    int		e1,
	    int		e3,
	    int		len)
{
    FbStip	*dst;
    FbStride	dstStride;
    int		dstBpp;
    int		dstXoff, dstYoff;
    FbGCPrivPtr	pPriv = fbGetGCPrivate (pGC);
    FbStip	and = (FbStip) pPriv->and;
    FbStip	xor = (FbStip) pPriv->xor;
    FbStip	bgand = (FbStip) pPriv->bgand;
    FbStip	bgxor = (FbStip) pPriv->bgxor;
    FbStip	mask, mask0;
    FbDashDeclare;
    int		dashlen;
    Bool	even;
    Bool	doOdd;
    
    fbGetStipDrawable (pDrawable, dst, dstStride, dstBpp, dstXoff, dstYoff);
    doOdd = pGC->lineStyle == LineDoubleDash;

    FbDashInit (pGC, pPriv, dashOffset, dashlen, even);
    
    dst += ((y1 + dstYoff) * dstStride);
    x1 = (x1 + dstXoff) * dstBpp;
    dst += x1 >> FB_STIP_SHIFT;
    x1 &= FB_STIP_MASK;
    mask0 = FbStipMask(0, dstBpp);
    mask = FbStipRight (mask0, x1);
    if (signdx < 0)
	mask0 = FbStipRight (mask0, FB_STIP_UNIT - dstBpp);
    if (signdy < 0)
	dstStride = -dstStride;
    while (len--)
    {
	if (even)
	    WRITE(dst, FbDoMaskRRop (READ(dst), and, xor, mask));
	else if (doOdd)
	    WRITE(dst, FbDoMaskRRop (READ(dst), bgand, bgxor, mask));
	if (axis == X_AXIS)
	{
	    mask = fbBresShiftMask(mask,signdx,dstBpp);
	    if (!mask)
	    {
		dst += signdx;
		mask = mask0;
	    }
	    e += e1;
	    if (e >= 0)
	    {
		dst += dstStride;
		e += e3;
	    }
	}
	else
	{
	    dst += dstStride;
	    e += e1;
	    if (e >= 0)
	    {
		e += e3;
		mask = fbBresShiftMask(mask,signdx,dstBpp);
		if (!mask)
		{
		    dst += signdx;
		    mask = mask0;
		}
	    }
	}
	FbDashStep (dashlen, even);
    }

    fbFinishAccess (pDrawable);
}

void
fbBresFill (DrawablePtr	pDrawable,
	    GCPtr	pGC,
	    int		dashOffset,
	    int		signdx,
	    int		signdy,
	    int		axis,
	    int		x1,
	    int		y1,
	    int		e,
	    int		e1,
	    int		e3,
	    int		len)
{
    while (len--)
    {
	fbFill (pDrawable, pGC, x1, y1, 1, 1);
	if (axis == X_AXIS)
	{
	    x1 += signdx;
	    e += e1;
	    if (e >= 0)
	    {
		e += e3;
		y1 += signdy;
	    }
	}
	else
	{
	    y1 += signdy;
	    e += e1;
	    if (e >= 0)
	    {
		e += e3;
		x1 += signdx;
	    }
	}
    }
}

static void
fbSetFg (DrawablePtr	pDrawable,
	 GCPtr		pGC,
	 Pixel		fg)
{
    if (fg != pGC->fgPixel)
    {
	ChangeGCVal val;
	val.val = fg;
	ChangeGC (NullClient, pGC, GCForeground, &val);
	ValidateGC (pDrawable, pGC);
    }
}

void
fbBresFillDash (DrawablePtr pDrawable,
		GCPtr	    pGC,
		int	    dashOffset,
		int	    signdx,
		int	    signdy,
		int	    axis,
		int	    x1,
		int	    y1,
		int	    e,
		int	    e1,
		int	    e3,
		int	    len)
{
    FbGCPrivPtr	pPriv = fbGetGCPrivate (pGC);
    FbDashDeclare;
    int		dashlen;
    Bool	even;
    Bool	doOdd;
    Bool	doBg;
    Pixel	fg, bg;
    
    fg = pGC->fgPixel;
    bg = pGC->bgPixel;
    
    /* whether to fill the odd dashes */
    doOdd = pGC->lineStyle == LineDoubleDash;
    /* whether to switch fg to bg when filling odd dashes */
    doBg = doOdd && (pGC->fillStyle == FillSolid || 
		     pGC->fillStyle == FillStippled);
    
    /* compute current dash position */
    FbDashInit (pGC, pPriv, dashOffset, dashlen, even);
    
    while (len--)
    {
	if (even || doOdd)
	{
	    if (doBg)
	    {
		if (even)
		    fbSetFg (pDrawable, pGC, fg);
		else
		    fbSetFg (pDrawable, pGC, bg);
	    }
	    fbFill (pDrawable, pGC, x1, y1, 1, 1);
	}
	if (axis == X_AXIS)
	{
	    x1 += signdx;
	    e += e1;
	    if (e >= 0)
	    {
		e += e3;
		y1 += signdy;
	    }
	}
	else
	{
	    y1 += signdy;
	    e += e1;
	    if (e >= 0)
	    {
		e += e3;
		x1 += signdx;
	    }
	}
	FbDashStep (dashlen, even);
    }
    if (doBg)
	fbSetFg (pDrawable, pGC, fg);
}

#ifdef FB_24BIT
static void
fbBresSolid24RRop (DrawablePtr  pDrawable,
		   GCPtr	pGC,
		   int		dashOffset,
		   int		signdx,
		   int		signdy,
		   int		axis,
		   int		x1,
		   int		y1,
		   int		e,
		   int		e1,
		   int		e3,
		   int		len)
{
    FbStip	*dst;
    FbStride	dstStride;
    int		dstBpp;
    int		dstXoff, dstYoff;
    FbGCPrivPtr	pPriv = fbGetGCPrivate (pGC);
    FbStip	and = pPriv->and;
    FbStip	xor = pPriv->xor;
    FbStip	leftMask, rightMask;
    int		nl;
    FbStip	*d;
    int		x;
    int		rot;
    FbStip	andT, xorT;
    
    fbGetStipDrawable (pDrawable, dst, dstStride, dstBpp, dstXoff, dstYoff);
    dst += ((y1 + dstYoff) * dstStride);
    x1 = (x1 + dstXoff) * 24;
    if (signdy < 0)
	dstStride = -dstStride;
    signdx *= 24;
    while (len--)
    {
	d = dst + (x1 >> FB_STIP_SHIFT);
	x = x1 & FB_STIP_MASK;
	rot = FbFirst24Rot (x);
	andT = FbRot24Stip(and,rot);
	xorT = FbRot24Stip(xor,rot);
	FbMaskStip (x, 24, leftMask, nl, rightMask);
	if (leftMask)
	{
	    WRITE(d, FbDoMaskRRop (READ(d), andT, xorT, leftMask));
	    d++;
	    andT = FbNext24Stip (andT);
	    xorT = FbNext24Stip (xorT);
	}
	if (rightMask)
	    WRITE(d, FbDoMaskRRop (READ(d), andT, xorT, rightMask));
	if (axis == X_AXIS)
	{
	    x1 += signdx;
	    e += e1;
	    if (e >= 0)
	    {
		e += e3;
		dst += dstStride;
	    }
	}
	else
	{
	    dst += dstStride;
	    e += e1;
	    if (e >= 0)
	    {
		e += e3;
		x1 += signdx;
	    }
	}
    }

    fbFinishAccess (pDrawable);
}

static void
fbBresDash24RRop (DrawablePtr	pDrawable,
		  GCPtr		pGC,
		  int		dashOffset,
		  int		signdx,
		  int		signdy,
		  int		axis,
		  int		x1,
		  int		y1,
		  int		e,
		  int		e1,
		  int		e3,
		  int		len)
{
    FbStip	*dst;
    FbStride	dstStride;
    int		dstBpp;
    int		dstXoff, dstYoff;
    FbGCPrivPtr	pPriv = fbGetGCPrivate (pGC);
    FbStip	andT, xorT;
    FbStip	fgand = pPriv->and;
    FbStip	fgxor = pPriv->xor;
    FbStip	bgand = pPriv->bgand;
    FbStip	bgxor = pPriv->bgxor;
    FbStip	leftMask, rightMask;
    int		nl;
    FbStip	*d;
    int		x;
    int		rot;
    FbDashDeclare;
    int		dashlen;
    Bool	even;
    Bool	doOdd;
    
    fbGetStipDrawable (pDrawable, dst, dstStride, dstBpp, dstXoff, dstYoff);
    doOdd = pGC->lineStyle == LineDoubleDash;

    /* compute current dash position */
    FbDashInit(pGC, pPriv, dashOffset, dashlen, even);
    
    dst += ((y1 + dstYoff) * dstStride);
    x1 = (x1 + dstXoff) * 24;
    if (signdy < 0)
	dstStride = -dstStride;
    signdx *= 24;
    while (len--)
    {
	if (even || doOdd)
	{
	    if (even)
	    {
		andT = fgand;
		xorT = fgxor;
	    }
	    else
	    {
		andT = bgand;
		xorT = bgxor;
	    }
	    d = dst + (x1 >> FB_STIP_SHIFT);
	    x = x1 & FB_STIP_MASK;
	    rot = FbFirst24Rot (x);
	    andT = FbRot24Stip (andT, rot);
	    xorT = FbRot24Stip (xorT, rot);
	    FbMaskStip (x, 24, leftMask, nl, rightMask);
	    if (leftMask)
	    {
		WRITE(d, FbDoMaskRRop (READ(d), andT, xorT, leftMask));
		d++;
		andT = FbNext24Stip (andT);
		xorT = FbNext24Stip (xorT);
	    }
	    if (rightMask)
		WRITE(d, FbDoMaskRRop (READ(d), andT, xorT, rightMask));
	}
	if (axis == X_AXIS)
	{
	    x1 += signdx;
	    e += e1;
	    if (e >= 0)
	    {
		e += e3;
		dst += dstStride;
	    }
	}
	else
	{
	    dst += dstStride;
	    e += e1;
	    if (e >= 0)
	    {
		e += e3;
		x1 += signdx;
	    }
	}
	FbDashStep (dashlen, even);
    }

    fbFinishAccess (pDrawable);
}
#endif

/*
 * For drivers that want to bail drawing some lines, this
 * function takes care of selecting the appropriate rasterizer
 * based on the contents of the specified GC.
 */

FbBres *
fbSelectBres (DrawablePtr   pDrawable,
	      GCPtr	    pGC)
{
    FbGCPrivPtr	pPriv = fbGetGCPrivate(pGC);
    int		dstBpp = pDrawable->bitsPerPixel;
    FbBres *	bres;
    
    if (pGC->lineStyle == LineSolid)
    {
	bres = fbBresFill;
	if (pGC->fillStyle == FillSolid)
	{
	    bres = fbBresSolid;
#ifdef FB_24BIT
	    if (dstBpp == 24)
		bres = fbBresSolid24RRop;
#endif
#ifndef FBNOPIXADDR
	    if (pPriv->and == 0)
	    {
		switch (dstBpp) {
		case 8:	bres = fbBresSolid8; break;
		case 16: bres = fbBresSolid16; break;
#ifdef FB_24BIT
		case 24: bres = fbBresSolid24; break;
#endif
		case 32: bres = fbBresSolid32; break;
		}
	    }
#endif
	}
    }
    else
    {
	bres = fbBresFillDash;
	if (pGC->fillStyle == FillSolid)
	{
	    bres = fbBresDash;
#ifdef FB_24BIT
	    if (dstBpp == 24)
		bres = fbBresDash24RRop;
#endif
#ifndef FBNOPIXADDR
	    if (pPriv->and == 0 && 
		(pGC->lineStyle == LineOnOffDash || pPriv->bgand == 0))
	    {
		switch (dstBpp) {
		case 8:	bres = fbBresDash8; break;
		case 16: bres = fbBresDash16; break;
#ifdef FB_24BIT
		case 24: bres = fbBresDash24; break;
#endif
		case 32: bres = fbBresDash32; break;
		}
	    }
#endif
	}
    }
    return bres;
}

void
fbBres (DrawablePtr	pDrawable,
	GCPtr		pGC,
	int		dashOffset,
	int		signdx,
	int		signdy,
	int		axis,
	int		x1,
	int		y1,
	int		e,
	int		e1,
	int		e3,
	int		len)
{
    (*fbSelectBres (pDrawable, pGC)) (pDrawable, pGC, dashOffset,
				      signdx, signdy, axis, x1, y1,
				      e, e1, e3, len);
}

void
fbSegment (DrawablePtr	pDrawable,
	   GCPtr	pGC,
	   int		x1,
	   int		y1,
	   int		x2,
	   int		y2,
	   Bool		drawLast,
	   int		*dashOffset)
{
    FbBres *	bres;
    RegionPtr	pClip = fbGetCompositeClip(pGC);
    BoxPtr	pBox;
    int		nBox;
    int		adx;		/* abs values of dx and dy */
    int		ady;
    int		signdx;		/* sign of dx and dy */
    int		signdy;
    int		e, e1, e2, e3;		/* bresenham error and increments */
    int		len;			/* length of segment */
    int		axis;			/* major axis */
    int		octant;
    int		dashoff;
    int		doff;
    unsigned int bias = miGetZeroLineBias(pDrawable->pScreen);
    unsigned int oc1;	/* outcode of point 1 */
    unsigned int oc2;	/* outcode of point 2 */

    nBox = REGION_NUM_RECTS (pClip);
    pBox = REGION_RECTS (pClip);
    
    bres = fbSelectBres (pDrawable, pGC);
    
    CalcLineDeltas(x1, y1, x2, y2, adx, ady, signdx, signdy,
		   1, 1, octant);

    if (adx > ady)
    {
	axis = X_AXIS;
	e1 = ady << 1;
	e2 = e1 - (adx << 1);
	e = e1 - adx;
	len = adx;
    }
    else
    {
	axis = Y_AXIS;
	e1 = adx << 1;
	e2 = e1 - (ady << 1);
	e = e1 - ady;
	SetYMajorOctant(octant);
	len = ady;
    }

    FIXUP_ERROR (e, octant, bias);
    
    /*
     * Adjust error terms to compare against zero
     */
    e3 = e2 - e1;
    e = e - e1;
    
    /* we have bresenham parameters and two points.
       all we have to do now is clip and draw.
    */

    if (drawLast)
	len++;
    dashoff = *dashOffset;
    *dashOffset = dashoff + len;
    while(nBox--)
    {
	oc1 = 0;
	oc2 = 0;
	OUTCODES(oc1, x1, y1, pBox);
	OUTCODES(oc2, x2, y2, pBox);
	if ((oc1 | oc2) == 0)
	{
	    (*bres) (pDrawable, pGC, dashoff,
		     signdx, signdy, axis, x1, y1,
		     e, e1, e3, len);
	    break;
	}
	else if (oc1 & oc2)
	{
	    pBox++;
	}
	else
	{
	    int new_x1 = x1, new_y1 = y1, new_x2 = x2, new_y2 = y2;
	    int clip1 = 0, clip2 = 0;
	    int clipdx, clipdy;
	    int err;
	    
	    if (miZeroClipLine(pBox->x1, pBox->y1, pBox->x2-1,
			       pBox->y2-1,
			       &new_x1, &new_y1, &new_x2, &new_y2,
			       adx, ady, &clip1, &clip2,
			       octant, bias, oc1, oc2) == -1)
	    {
		pBox++;
		continue;
	    }

	    if (axis == X_AXIS)
		len = abs(new_x2 - new_x1);
	    else
		len = abs(new_y2 - new_y1);
	    if (clip2 != 0 || drawLast)
		len++;
	    if (len)
	    {
		/* unwind bresenham error term to first point */
		doff = dashoff;
		err = e;
		if (clip1)
		{
		    clipdx = abs(new_x1 - x1);
		    clipdy = abs(new_y1 - y1);
		    if (axis == X_AXIS)
		    {
			doff += clipdx;
			err  += e3 * clipdy + e1 * clipdx;
		    }
		    else
		    {
			doff += clipdy;
			err  += e3 * clipdx + e1 * clipdy;
		    }
		}
		(*bres) (pDrawable, pGC, doff,
			 signdx, signdy, axis, new_x1, new_y1,
			 err, e1, e3, len);
	    }
	    pBox++;
	}
    } /* while (nBox--) */
}