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

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

#include <string.h>

#include "fb.h"

#include "picturestr.h"
#include "mipict.h"
#include "fbpict.h"

void
fbComposite (CARD8      op,
	     PicturePtr pSrc,
	     PicturePtr pMask,
	     PicturePtr pDst,
	     INT16      xSrc,
	     INT16      ySrc,
	     INT16      xMask,
	     INT16      yMask,
	     INT16      xDst,
	     INT16      yDst,
	     CARD16     width,
	     CARD16     height)
{
    pixman_image_t *src, *mask, *dest;
    int src_xoff, src_yoff;
    int msk_xoff, msk_yoff;
    int dst_xoff, dst_yoff;
    
    miCompositeSourceValidate (pSrc, xSrc - xDst, ySrc - yDst, width, height);
    if (pMask)
	miCompositeSourceValidate (pMask, xMask - xDst, yMask - yDst, width, height);
    
    src = image_from_pict (pSrc, FALSE, &src_xoff, &src_yoff);
    mask = image_from_pict (pMask, FALSE, &msk_xoff, &msk_yoff);
    dest = image_from_pict (pDst, TRUE, &dst_xoff, &dst_yoff);

    if (src && dest && !(pMask && !mask))
    {
	pixman_image_composite (op, src, mask, dest,
				xSrc + src_xoff, ySrc + src_yoff,
				xMask + msk_xoff, yMask + msk_yoff,
				xDst + dst_xoff, yDst + dst_yoff,
				width, height);
    }

    free_pixman_pict (pSrc, src);
    free_pixman_pict (pMask, mask);
    free_pixman_pict (pDst, dest);
}

static pixman_image_t *
create_solid_fill_image (PicturePtr pict)
{
    PictSolidFill *solid = &pict->pSourcePict->solidFill;
    pixman_color_t color;
    CARD32 a, r, g, b;
    
    a = (solid->color & 0xff000000) >> 24;
    r = (solid->color & 0x00ff0000) >> 16;
    g = (solid->color & 0x0000ff00) >>  8;
    b = (solid->color & 0x000000ff) >>  0;
    
    color.alpha = (a << 8) | a;
    color.red =   (r << 8) | r;
    color.green = (g << 8) | g;
    color.blue =  (b << 8) | b;
    
    return pixman_image_create_solid_fill (&color);
}

static pixman_image_t *
create_linear_gradient_image (PictGradient *gradient)
{
    PictLinearGradient *linear = (PictLinearGradient *)gradient;
    pixman_point_fixed_t p1;
    pixman_point_fixed_t p2;
    
    p1.x = linear->p1.x;
    p1.y = linear->p1.y;
    p2.x = linear->p2.x;
    p2.y = linear->p2.y;
    
    return pixman_image_create_linear_gradient (
	&p1, &p2, (pixman_gradient_stop_t *)gradient->stops, gradient->nstops);
}

static pixman_image_t *
create_radial_gradient_image (PictGradient *gradient)
{
    PictRadialGradient *radial = (PictRadialGradient *)gradient;
    pixman_point_fixed_t c1;
    pixman_point_fixed_t c2;
    
    c1.x = radial->c1.x;
    c1.y = radial->c1.y;
    c2.x = radial->c2.x;
    c2.y = radial->c2.y;
    
    return pixman_image_create_radial_gradient (
	&c1, &c2, radial->c1.radius,
	radial->c2.radius,
	(pixman_gradient_stop_t *)gradient->stops, gradient->nstops);
}

static pixman_image_t *
create_conical_gradient_image (PictGradient *gradient)
{
    PictConicalGradient *conical = (PictConicalGradient *)gradient;
    pixman_point_fixed_t center;
    
    center.x = conical->center.x;
    center.y = conical->center.y;
    
    return pixman_image_create_conical_gradient (
	&center, conical->angle, (pixman_gradient_stop_t *)gradient->stops,
	gradient->nstops);
}

static pixman_image_t *
create_bits_picture (PicturePtr pict,
		     Bool       has_clip,
		     int	*xoff,
		     int	*yoff)
{
    PixmapPtr pixmap;
    FbBits *bits;
    FbStride stride;
    int bpp;
    pixman_image_t *image;
    
    fbGetDrawablePixmap (pict->pDrawable, pixmap, *xoff, *yoff);
    fbGetPixmapBitsData(pixmap, bits, stride, bpp);

    image = pixman_image_create_bits (
	pict->format,
	pixmap->drawable.width, pixmap->drawable.height,
	(uint32_t *)bits, stride * sizeof (FbStride));
    
    
#ifdef FB_ACCESS_WRAPPER
#if FB_SHIFT==5
    
    pixman_image_set_accessors (image,
				(pixman_read_memory_func_t)wfbReadMemory,
				(pixman_write_memory_func_t)wfbWriteMemory);
    
#else
    
#error The pixman library only works when FbBits is 32 bits wide
    
#endif
#endif
    
    /* pCompositeClip is undefined for source pictures, so
     * only set the clip region for pictures with drawables
     */
    if (has_clip)
    {
	if (pict->clientClipType != CT_NONE)
	    pixman_image_set_has_client_clip (image, TRUE);

	if (*xoff || *yoff)
	    pixman_region_translate (pict->pCompositeClip, *xoff, *yoff);

	pixman_image_set_clip_region (image, pict->pCompositeClip);

	if (*xoff || *yoff)
	    pixman_region_translate (pict->pCompositeClip, -*xoff, -*yoff);
    }
    
    /* Indexed table */
    if (pict->pFormat->index.devPrivate)
	pixman_image_set_indexed (image, pict->pFormat->index.devPrivate);

    /* Add in drawable origin to position within the image */
    *xoff += pict->pDrawable->x;
    *yoff += pict->pDrawable->y;

    return image;
}

static pixman_image_t *
image_from_pict_internal (PicturePtr pict, Bool has_clip, int *xoff, int *yoff, Bool is_alpha_map);

static void
set_image_properties (pixman_image_t *image, PicturePtr pict, Bool has_clip, int *xoff, int *yoff, Bool is_alpha_map)
{
    pixman_repeat_t repeat;
    pixman_filter_t filter;
    
    if (pict->transform)
    {
	/* For source images, adjust the transform to account
	 * for the drawable offset within the pixman image,
	 * then set the offset to 0 as it will be used
	 * to compute positions within the transformed image.
	 */
	if (!has_clip) {
	    struct pixman_transform	adjusted;

	    adjusted = *pict->transform;
	    pixman_transform_translate(&adjusted,
				       NULL,
				       pixman_int_to_fixed(*xoff),
				       pixman_int_to_fixed(*yoff));
	    pixman_image_set_transform (image, &adjusted);
	    *xoff = 0;
	    *yoff = 0;
	} else
	    pixman_image_set_transform (image, pict->transform);
    }
    
    switch (pict->repeatType)
    {
    default:
    case RepeatNone:
	repeat = PIXMAN_REPEAT_NONE;
	break;
	
    case RepeatPad:
	repeat = PIXMAN_REPEAT_PAD;
	break;
	
    case RepeatNormal:
	repeat = PIXMAN_REPEAT_NORMAL;
	break;
	
    case RepeatReflect:
	repeat = PIXMAN_REPEAT_REFLECT;
	break;
    }
    
    pixman_image_set_repeat (image, repeat);
    
    /* Fetch alpha map unless 'pict' is being used
     * as the alpha map for this operation
     */
    if (pict->alphaMap && !is_alpha_map)
    {
	int alpha_xoff, alpha_yoff;
	pixman_image_t *alpha_map = image_from_pict_internal (pict->alphaMap, FALSE, &alpha_xoff, &alpha_yoff, TRUE);
	
	pixman_image_set_alpha_map (
	    image, alpha_map, pict->alphaOrigin.x, pict->alphaOrigin.y);
	
	free_pixman_pict (pict->alphaMap, alpha_map);
    }
    
    pixman_image_set_component_alpha (image, pict->componentAlpha);

    switch (pict->filter)
    {
    default:
    case PictFilterNearest:
    case PictFilterFast:
	filter = PIXMAN_FILTER_NEAREST;
	break;
	
    case PictFilterBilinear:
    case PictFilterGood:
	filter = PIXMAN_FILTER_BILINEAR;
	break;
	
    case PictFilterConvolution:
	filter = PIXMAN_FILTER_CONVOLUTION;
	break;
    }
    
    pixman_image_set_filter (image, filter, (pixman_fixed_t *)pict->filter_params, pict->filter_nparams);
    pixman_image_set_source_clipping (image, TRUE);
}

static pixman_image_t *
image_from_pict_internal (PicturePtr pict, Bool has_clip, int *xoff, int *yoff, Bool is_alpha_map)
{
    pixman_image_t *image = NULL;

    if (!pict)
	return NULL;

    if (pict->pDrawable)
    {
	image = create_bits_picture (pict, has_clip, xoff, yoff);
    }
    else if (pict->pSourcePict)
    {
	SourcePict *sp = pict->pSourcePict;
	
	if (sp->type == SourcePictTypeSolidFill)
	{
	    image = create_solid_fill_image (pict);
	}
	else
	{
	    PictGradient *gradient = &pict->pSourcePict->gradient;
	    
	    if (sp->type == SourcePictTypeLinear)
		image = create_linear_gradient_image (gradient);
	    else if (sp->type == SourcePictTypeRadial)
		image = create_radial_gradient_image (gradient);
	    else if (sp->type == SourcePictTypeConical)
		image = create_conical_gradient_image (gradient);
	}
	*xoff = *yoff = 0;
    }
    
    if (image)
	set_image_properties (image, pict, has_clip, xoff, yoff, is_alpha_map);
    
    return image;
}

pixman_image_t *
image_from_pict (PicturePtr pict, Bool has_clip, int *xoff, int *yoff)
{
    return image_from_pict_internal (pict, has_clip, xoff, yoff, FALSE);
}

void
free_pixman_pict (PicturePtr pict, pixman_image_t *image)
{
    if (image && pixman_image_unref (image) && pict->pDrawable)
	fbFinishAccess (pict->pDrawable);
}

Bool
fbPictureInit (ScreenPtr pScreen, PictFormatPtr formats, int nformats)
{

    PictureScreenPtr    ps;

    if (!miPictureInit (pScreen, formats, nformats))
	return FALSE;
    ps = GetPictureScreen(pScreen);
    ps->Composite = fbComposite;
    ps->Glyphs = miGlyphs;
    ps->CompositeRects = miCompositeRects;
    ps->RasterizeTrapezoid = fbRasterizeTrapezoid;
    ps->AddTraps = fbAddTraps;
    ps->AddTriangles = fbAddTriangles;

    return TRUE;
}