/* cairo - a vector graphics library with display and print output
 *
 * Copyright © 2009 Intel Corporation
 *
 * This library is free software; you can redistribute it and/or
 * modify it either under the terms of the GNU Lesser General Public
 * License version 2.1 as published by the Free Software Foundation
 * (the "LGPL") or, at your option, under the terms of the Mozilla
 * Public License Version 1.1 (the "MPL"). If you do not alter this
 * notice, a recipient may use your version of this file under either
 * the MPL or the LGPL.
 *
 * You should have received a copy of the LGPL along with this library
 * in the file COPYING-LGPL-2.1; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Suite 500, Boston, MA 02110-1335, USA
 * You should have received a copy of the MPL along with this library
 * in the file COPYING-MPL-1.1
 *
 * The contents of this file are subject to the Mozilla Public License
 * Version 1.1 (the "License"); you may not use this file except in
 * compliance with the License. You may obtain a copy of the License at
 * http://www.mozilla.org/MPL/
 *
 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY
 * OF ANY KIND, either express or implied. See the LGPL or the MPL for
 * the specific language governing rights and limitations.
 *
 * Contributor(s):
 *	Chris Wilson <chris@chris-wilson.co.uk>
 */

#include "cairoint.h"

#include "cairo-boxes-private.h"
#include "cairo-xcb-private.h"
#include "cairo-image-surface-private.h"
#include "cairo-surface-backend-private.h"

/* XXX dithering */

typedef struct _cairo_xcb_pixmap {
    cairo_surface_t base;

    cairo_xcb_connection_t *connection;
    cairo_xcb_screen_t *screen;

    cairo_surface_t *owner;
    xcb_pixmap_t pixmap;
    int width;
    int height;
    int depth;
    int x0, y0;
    cairo_bool_t repeat;
} cairo_xcb_pixmap_t;

static cairo_status_t
_cairo_xcb_pixmap_finish (void *abstract_surface)
{
    cairo_xcb_pixmap_t *surface = abstract_surface;
    cairo_status_t status;

    if (surface->owner != NULL) {
	cairo_surface_destroy (surface->owner);
    } else {
	status = _cairo_xcb_connection_acquire (surface->connection);
	if (unlikely (status))
	    return status;

	xcb_free_pixmap (surface->connection->xcb_connection,
					   surface->pixmap);
	_cairo_xcb_connection_release (surface->connection);
    }

    return CAIRO_STATUS_SUCCESS;
}

static const cairo_surface_backend_t _cairo_xcb_pixmap_backend = {
    CAIRO_SURFACE_TYPE_XCB,
    _cairo_xcb_pixmap_finish,
};

static cairo_xcb_pixmap_t *
_cairo_xcb_pixmap_create (cairo_xcb_surface_t *target,
			  int width, int height)
{
    cairo_xcb_pixmap_t *surface;

    surface = _cairo_malloc (sizeof (cairo_xcb_pixmap_t));
    if (unlikely (surface == NULL))
	return (cairo_xcb_pixmap_t *)
	    _cairo_surface_create_in_error (_cairo_error (CAIRO_STATUS_NO_MEMORY));

    _cairo_surface_init (&surface->base,
			 &_cairo_xcb_pixmap_backend,
			 NULL,
			 target->base.content,
			 FALSE); /* is_vector */

    surface->connection = target->connection;
    surface->screen = target->screen;
    surface->owner = NULL;
    surface->width = width;
    surface->height = height;
    surface->depth = target->depth;
    surface->x0 = surface->y0 = 0;
    surface->repeat = FALSE;

    surface->pixmap =
	_cairo_xcb_connection_create_pixmap (surface->connection,
					     surface->depth,
					     target->drawable,
					     width, height);

    return surface;
}

static cairo_xcb_pixmap_t *
_cairo_xcb_pixmap_copy (cairo_xcb_surface_t *target)
{
    cairo_xcb_pixmap_t *surface;

    surface = _cairo_malloc (sizeof (cairo_xcb_pixmap_t));
    if (unlikely (surface == NULL))
	return (cairo_xcb_pixmap_t *)
	    _cairo_surface_create_in_error (_cairo_error (CAIRO_STATUS_NO_MEMORY));

    _cairo_surface_init (&surface->base,
			 &_cairo_xcb_pixmap_backend,
			 NULL,
			 target->base.content,
			 FALSE); /* is_vector */

    surface->connection = target->connection;
    surface->screen = target->screen;
    surface->pixmap = target->drawable;
    surface->owner = cairo_surface_reference (&target->base);
    surface->width = target->width;
    surface->height = target->height;
    surface->depth = target->depth;
    surface->x0 = surface->y0 = 0;
    surface->repeat = FALSE;

    return surface;
}

#if CAIRO_HAS_XCB_SHM_FUNCTIONS
static cairo_status_t
_cairo_xcb_shm_image_create_shm (cairo_xcb_connection_t *connection,
				 pixman_format_code_t pixman_format,
				 int width, int height,
				 cairo_image_surface_t **image_out,
				 cairo_xcb_shm_info_t **shm_info_out)
{
    cairo_surface_t *image = NULL;
    cairo_xcb_shm_info_t *shm_info = NULL;
    cairo_status_t status;
    size_t size, stride;

    if (! (connection->flags & CAIRO_XCB_HAS_SHM))
	return CAIRO_INT_STATUS_UNSUPPORTED;

    if (unlikely (width > XLIB_COORD_MAX || height > XLIB_COORD_MAX))
	return CAIRO_INT_STATUS_UNSUPPORTED;

    stride = CAIRO_STRIDE_FOR_WIDTH_BPP (width, PIXMAN_FORMAT_BPP (pixman_format));
    size = stride * height;
    if (size <= CAIRO_XCB_SHM_SMALL_IMAGE)
	return CAIRO_INT_STATUS_UNSUPPORTED;

    status = _cairo_xcb_connection_allocate_shm_info (connection, size,
						      FALSE, &shm_info);
    if (unlikely (status))
	return status;

    image = _cairo_image_surface_create_with_pixman_format (shm_info->mem,
							    pixman_format,
							    width, height,
							    stride);
    status = image->status;
    if (unlikely (status)) {
	_cairo_xcb_shm_info_destroy (shm_info);
	return status;
    }

    status = _cairo_user_data_array_set_data (&image->user_data,
					      (const cairo_user_data_key_t *) connection,
					      shm_info,
					      (cairo_destroy_func_t) _cairo_xcb_shm_info_destroy);

    if (unlikely (status)) {
	cairo_surface_destroy (image);
	_cairo_xcb_shm_info_destroy (shm_info);
	return status;
    }

    *image_out = (cairo_image_surface_t *) image;
    *shm_info_out = shm_info;
    return CAIRO_STATUS_SUCCESS;
}
#else
static cairo_status_t
_cairo_xcb_shm_image_create_shm (cairo_xcb_connection_t *connection,
				 pixman_format_code_t pixman_format,
				 int width, int height,
				 cairo_image_surface_t **image_out,
				 cairo_xcb_shm_info_t **shm_info_out)
{
    return CAIRO_INT_STATUS_UNSUPPORTED;
}
#endif

cairo_status_t
_cairo_xcb_shm_image_create (cairo_xcb_connection_t *connection,
			     pixman_format_code_t pixman_format,
			     int width, int height,
			     cairo_image_surface_t **image_out,
			     cairo_xcb_shm_info_t **shm_info_out)
{
    cairo_surface_t *image = NULL;
    cairo_xcb_shm_info_t *shm_info = NULL;
    cairo_status_t status;

    status = _cairo_xcb_shm_image_create_shm (connection,
					      pixman_format,
					      width,
					      height,
					      image_out,
					      shm_info_out);

    if (status != CAIRO_STATUS_SUCCESS) {
	image = _cairo_image_surface_create_with_pixman_format (NULL,
								pixman_format,
								width, height,
								0);
	status = image->status;
	if (unlikely (status))
	    return status;

	*image_out = (cairo_image_surface_t *) image;
	*shm_info_out = shm_info;
    }

    return CAIRO_STATUS_SUCCESS;
}

static cairo_xcb_pixmap_t *
_pixmap_from_image (cairo_xcb_surface_t *target,
		    xcb_render_pictformat_t format,
		    cairo_image_surface_t *image,
		    cairo_xcb_shm_info_t *shm_info)
{
    xcb_gcontext_t gc;
    cairo_xcb_pixmap_t *pixmap;

    pixmap = _cairo_xcb_pixmap_create (target,
				       image->width,
				       image->height);
    if (unlikely (pixmap->base.status))
	return pixmap;

    gc = _cairo_xcb_screen_get_gc (target->screen, pixmap->pixmap, image->depth);

    if (shm_info != NULL) {
	_cairo_xcb_connection_shm_put_image (target->connection,
					     pixmap->pixmap, gc,
					     image->width, image->height,
					     0, 0,
					     image->width, image->height,
					     0, 0,
					     image->depth,
					     shm_info->shm,
					     shm_info->offset);
    } else {
	int len;

	/* Do we need to trim the image? */
	len = CAIRO_STRIDE_FOR_WIDTH_BPP (image->width,
					  PIXMAN_FORMAT_BPP (image->pixman_format));
	if (len == image->stride) {
	    _cairo_xcb_connection_put_image (target->connection,
					     pixmap->pixmap, gc,
					     image->width, image->height,
					     0, 0,
					     image->depth,
					     image->stride,
					     image->data);
	} else {
	    _cairo_xcb_connection_put_subimage (target->connection,
						pixmap->pixmap, gc,
						0, 0,
						image->width, image->height,
						PIXMAN_FORMAT_BPP (image->pixman_format) / 8,
						image->stride,
						0, 0,
						image->depth,
						image->data);

	}
    }

    _cairo_xcb_screen_put_gc (target->screen, image->depth, gc);

    return pixmap;
}

static cairo_xcb_pixmap_t *
_render_to_pixmap (cairo_xcb_surface_t *target,
		   const cairo_pattern_t *pattern,
		   const cairo_rectangle_int_t *extents)
{
    cairo_image_surface_t *image;
    cairo_xcb_shm_info_t *shm_info;
    cairo_pattern_union_t copy;
    cairo_status_t status;
    cairo_xcb_pixmap_t *pixmap;

    status = _cairo_xcb_shm_image_create (target->screen->connection,
					  target->pixman_format,
					  extents->width, extents->height,
					  &image, &shm_info);
    if (unlikely (status))
	return (cairo_xcb_pixmap_t *) _cairo_surface_create_in_error (status);

    _cairo_pattern_init_static_copy (&copy.base, pattern);
    cairo_matrix_translate (&copy.base.matrix, -extents->x, -extents->y);
    status = _cairo_surface_paint (&image->base,
				   CAIRO_OPERATOR_SOURCE,
				   &copy.base,
				   NULL);
    if (unlikely (status)) {
	cairo_surface_destroy (&image->base);
	return (cairo_xcb_pixmap_t *) _cairo_surface_create_in_error (status);
    }

    pixmap = _pixmap_from_image (target, target->xrender_format, image, shm_info);
    cairo_surface_destroy (&image->base);

    if (unlikely (pixmap->base.status))
	return pixmap;

    pixmap->x0 = -extents->x;
    pixmap->y0 = -extents->y;
    return pixmap;
}

static cairo_xcb_pixmap_t *
_copy_to_pixmap (cairo_xcb_surface_t *source)
{
    cairo_xcb_pixmap_t *pixmap;

    /* If the source may be a window, we need to copy it and its children
     * via a temporary pixmap so that we can IncludeInferiors on the source
     * and use ClipByChildren on the destination.
     */
    if (source->owns_pixmap) {
	pixmap = _cairo_xcb_pixmap_copy (source);
	if (unlikely (pixmap->base.status))
	    return pixmap;
    } else {
	uint32_t values[1];
	xcb_gcontext_t gc;

	pixmap = _cairo_xcb_pixmap_create (source,
					   source->width,
					   source->height);
	if (unlikely (pixmap->base.status))
	    return pixmap;

	gc = _cairo_xcb_screen_get_gc (source->screen,
				       pixmap->pixmap,
				       pixmap->depth);

	values[0] = TRUE;
	_cairo_xcb_connection_change_gc (pixmap->connection, gc,
					 XCB_GC_SUBWINDOW_MODE, values);

	_cairo_xcb_connection_copy_area (pixmap->connection,
					 source->drawable,
					 pixmap->pixmap, gc,
					 0, 0,
					 0, 0,
					 source->width,
					 source->height);

	values[0] = FALSE;
	_cairo_xcb_connection_change_gc (pixmap->connection, gc,
					 XCB_GC_SUBWINDOW_MODE, values);

	_cairo_xcb_screen_put_gc (source->screen,
				  pixmap->depth,
				  gc);
    }

    return pixmap;
}
static cairo_xcb_pixmap_t *
_cairo_xcb_surface_pixmap (cairo_xcb_surface_t *target,
			   const cairo_surface_pattern_t *pattern,
			   const cairo_rectangle_int_t *extents,
			   int tx, int ty)
{
    cairo_surface_t *source;
    cairo_xcb_pixmap_t *pixmap;

    source =  pattern->surface;
    pixmap = (cairo_xcb_pixmap_t *)
	_cairo_surface_has_snapshot (source, &_cairo_xcb_pixmap_backend);
    if (pixmap != NULL && pixmap->screen == target->screen)
	return (cairo_xcb_pixmap_t *) cairo_surface_reference (&pixmap->base);

    if (_cairo_surface_is_xcb(source) &&
	((cairo_xcb_surface_t *) source)->screen == target->screen)
    {
	cairo_xcb_surface_t *xcb_source = (cairo_xcb_surface_t *) source;

	if (xcb_source->depth == target->depth)
	    pixmap = _copy_to_pixmap (xcb_source);
    }
#if CAIRO_HAS_XLIB_XCB_FUNCTIONS
    else if (source->type == CAIRO_SURFACE_TYPE_XLIB &&
	     ((cairo_xlib_xcb_surface_t *) source)->xcb->screen == target->screen)
    {
	cairo_xcb_surface_t *xcb_source = ((cairo_xlib_xcb_surface_t *) source)->xcb;

	if (xcb_source->depth == target->depth)
	    pixmap = _copy_to_pixmap (xcb_source);
    }
#endif

    if (pixmap == NULL) {
	cairo_rectangle_int_t rect;

	if (! _cairo_surface_get_extents (source, &rect)) {
	    rect.x = rect.y = 0;
	    rect.width  = target->width;
	    rect.height = target->height;
	}

	pixmap = _render_to_pixmap (target, &pattern->base, &rect);
    }

    if (unlikely (pixmap->base.status))
	return pixmap;

    _cairo_surface_attach_snapshot (source, &pixmap->base, NULL);

    if (pattern->base.extend != CAIRO_EXTEND_NONE) {
	if (extents->x < 0 || extents->y < 0 ||
	    extents->x + extents->width  > pixmap->width ||
	    extents->y + extents->height > pixmap->height)
	{
	    pixmap->repeat = TRUE;
	}
    }

    pixmap->x0 += tx;
    pixmap->y0 += ty;

    return pixmap;
}

static cairo_xcb_pixmap_t *
_cairo_xcb_pixmap_for_pattern (cairo_xcb_surface_t *target,
			       const cairo_pattern_t *pattern,
			       const cairo_rectangle_int_t *extents)
{
    int tx, ty;

    switch (pattern->type) {
    case CAIRO_PATTERN_TYPE_SURFACE:
	/* Core can only perform a native, unscaled blit, but can handle tiles */
	if (_cairo_matrix_is_integer_translation (&pattern->matrix, &tx, &ty)) {
	    switch (pattern->extend) {
	    case CAIRO_EXTEND_NONE:
	    case CAIRO_EXTEND_REPEAT:
		return _cairo_xcb_surface_pixmap (target,
						  (cairo_surface_pattern_t *) pattern,
						  extents, tx, ty);

	    default:
	    case CAIRO_EXTEND_PAD:
	    case CAIRO_EXTEND_REFLECT:
		break;
	    }
	}
	/* fallthrough */
    case CAIRO_PATTERN_TYPE_LINEAR:
    case CAIRO_PATTERN_TYPE_RADIAL:
    case CAIRO_PATTERN_TYPE_MESH:
    case CAIRO_PATTERN_TYPE_RASTER_SOURCE:
	return _render_to_pixmap (target, pattern, extents);

    default:
    case CAIRO_PATTERN_TYPE_SOLID:
	ASSERT_NOT_REACHED;
	return NULL;
    }
}

cairo_status_t
_cairo_xcb_surface_core_copy_boxes (cairo_xcb_surface_t		*dst,
				   const cairo_pattern_t	*src_pattern,
				   const cairo_rectangle_int_t	*extents,
				   const cairo_boxes_t		*boxes)
{
    cairo_xcb_pixmap_t *src;
    const struct _cairo_boxes_chunk *chunk;
    xcb_gcontext_t gc;
    cairo_status_t status;

    status = _cairo_xcb_connection_acquire (dst->connection);
    if (unlikely (status))
	return status;

    src = _cairo_xcb_pixmap_for_pattern (dst, src_pattern, extents);
    status = src->base.status;
    if (unlikely (status))
	goto CLEANUP_CONNECTION;

    assert (src->depth == dst->depth);

    gc = _cairo_xcb_screen_get_gc (dst->screen, src->pixmap, src->depth);

    if (src->repeat) {
	uint32_t mask =
	    XCB_GC_FILL_STYLE |
	    XCB_GC_TILE |
	    XCB_GC_TILE_STIPPLE_ORIGIN_X |
	    XCB_GC_TILE_STIPPLE_ORIGIN_Y;
	uint32_t values[] = {
	    XCB_FILL_STYLE_TILED,
	    src->pixmap,
	    - src->x0, - src->y0,
	};
	xcb_rectangle_t *xcb_rects;

	_cairo_xcb_connection_change_gc (dst->connection, gc, mask, values);

	for (chunk = &boxes->chunks; chunk != NULL; chunk = chunk->next) {
	    int i;

	    xcb_rects = (xcb_rectangle_t *) chunk->base;

	    for (i = 0; i < chunk->count; i++) {
		int x1 = _cairo_fixed_integer_round (chunk->base[i].p1.x);
		int x2 = _cairo_fixed_integer_round (chunk->base[i].p2.x);
		int y1 = _cairo_fixed_integer_round (chunk->base[i].p1.y);
		int y2 = _cairo_fixed_integer_round (chunk->base[i].p2.y);

		xcb_rects[i].x = x1;
		xcb_rects[i].y = y1;
		xcb_rects[i].width  = x2 - x1;
		xcb_rects[i].height = y2 - y1;
	    }
	    _cairo_xcb_connection_poly_fill_rectangle (dst->connection,
						       dst->drawable,
						       gc, chunk->count, xcb_rects);
	}

	values[0] = 0;
	_cairo_xcb_connection_change_gc (dst->connection, gc, XCB_GC_FILL_STYLE, values);
    } else {
	for (chunk = &boxes->chunks; chunk != NULL; chunk = chunk->next) {
	    int i;

	    for (i = 0; i < chunk->count; i++) {
		int x1 = _cairo_fixed_integer_round (chunk->base[i].p1.x);
		int x2 = _cairo_fixed_integer_round (chunk->base[i].p2.x);
		int y1 = _cairo_fixed_integer_round (chunk->base[i].p1.y);
		int y2 = _cairo_fixed_integer_round (chunk->base[i].p2.y);

		_cairo_xcb_connection_copy_area (dst->connection,
						 src->pixmap,
						 dst->drawable, gc,
						 src->x0 + x1,
						 src->y0 + y1,
						 x1, y1,
						 x2 - x1, y2 - y1);
	    }
	}
    }

    _cairo_xcb_screen_put_gc (dst->screen, src->depth, gc);
    cairo_surface_destroy (&src->base);

  CLEANUP_CONNECTION:
    _cairo_xcb_connection_release (dst->connection);

    return status;
}

cairo_status_t
_cairo_xcb_surface_core_fill_boxes (cairo_xcb_surface_t *dst,
				    const cairo_color_t	*color,
				    cairo_boxes_t *boxes)
{
    struct _cairo_boxes_chunk *chunk;
    xcb_gcontext_t gc;
    cairo_status_t status;

    status = _cairo_xcb_connection_acquire (dst->connection);
    if (unlikely (status))
	return status;

    gc = _cairo_xcb_screen_get_gc (dst->screen, dst->drawable, dst->depth);

#if 0
    xcb_pixmap_t source;

    source = _dither_source (dst, color);
    XSetTSOrigin (surface->dpy, gc, 0, 0);
    XSetTile (surface->dpy, gc, source);
#endif

    for (chunk = &boxes->chunks; chunk != NULL; chunk = chunk->next) {
	xcb_rectangle_t *xcb_rects;
	int i;

	xcb_rects = (xcb_rectangle_t *) chunk->base;
	for (i = 0; i < chunk->count; i++) {
	    int x1 = _cairo_fixed_integer_round (chunk->base[i].p1.x);
	    int x2 = _cairo_fixed_integer_round (chunk->base[i].p2.x);
	    int y1 = _cairo_fixed_integer_round (chunk->base[i].p1.y);
	    int y2 = _cairo_fixed_integer_round (chunk->base[i].p2.y);

	    xcb_rects[i].x = x1;
	    xcb_rects[i].y = y1;
	    xcb_rects[i].width  = x2 - x1;
	    xcb_rects[i].height = y2 - y1;
	}

	_cairo_xcb_connection_poly_fill_rectangle (dst->connection,
						   dst->drawable, gc,
						   chunk->count, xcb_rects);
    }

    _cairo_xcb_screen_put_gc (dst->screen, dst->depth, gc);
    _cairo_xcb_connection_release (dst->connection);

    return CAIRO_STATUS_SUCCESS;
}
