/* -*- Mode: c; c-basic-offset: 4; indent-tabs-mode: t; tab-width: 8; -*- */
/*
 * Copyright © 2004,2006 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
 * Red Hat, Inc. not be used in advertising or publicity pertaining to
 * distribution of the software without specific, written prior
 * permission. Red Hat, Inc. makes no representations about the
 * suitability of this software for any purpose.  It is provided "as
 * is" without express or implied warranty.
 *
 * RED HAT, INC. DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS
 * SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
 * FITNESS, IN NO EVENT SHALL RED HAT, INC. 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: Carl D. Worth <cworth@cworth.org>
 */

#include "cairo-boilerplate-private.h"
#include "cairo-boilerplate-xlib.h"

#include <cairo-xlib.h>
#if CAIRO_HAS_XLIB_XRENDER_SURFACE
#include <cairo-xlib-xrender.h>
#endif

#include <X11/Xutil.h> /* for XDestroyImage */

#if !CAIRO_HAS_XLIB_XRENDER_SURFACE
#define PolyModePrecise			    0
#endif

static const cairo_user_data_key_t key;

typedef struct _xlib_target_closure {
    Display *dpy;
    Drawable drawable;
    cairo_bool_t drawable_is_pixmap;
} xlib_target_closure_t;

static void
_cairo_boilerplate_xlib_cleanup (void *closure)
{
    xlib_target_closure_t *xtc = closure;

    if (xtc->drawable) {
	if (xtc->drawable_is_pixmap)
	    XFreePixmap (xtc->dpy, xtc->drawable);
	else
	    XDestroyWindow (xtc->dpy, xtc->drawable);
    }
    XCloseDisplay (xtc->dpy);
    free (xtc);
}

static void
_cairo_boilerplate_xlib_synchronize (void *closure)
{
    xlib_target_closure_t *xtc = closure;
    XImage *ximage;

    ximage = XGetImage (xtc->dpy, xtc->drawable,
			0, 0, 1, 1, AllPlanes, ZPixmap);
    if (ximage != NULL)
	XDestroyImage (ximage);
}

static cairo_bool_t
_cairo_boilerplate_xlib_check_screen_size (Display *dpy,
					   int	    screen,
					   int	    width,
					   int	    height)
{
    Screen *scr = XScreenOfDisplay (dpy, screen);
    return width <= WidthOfScreen (scr) && height <= HeightOfScreen (scr);
}

static void
_cairo_boilerplate_xlib_setup_test_surface (cairo_surface_t *surface)
{

    /* For testing purposes, tell the X server to strictly adhere to the
     * Render specification.
     */
    cairo_xlib_device_debug_set_precision(cairo_surface_get_device(surface),
					  PolyModePrecise);
}


#if CAIRO_HAS_XLIB_XRENDER_SURFACE
/* For the xlib backend we distinguish between TEST and PERF mode in a
 * couple of ways.
 *
 * For TEST, we always test against pixmaps of depth 32 (for
 * COLOR_ALPHA) or 24 (for COLOR) and we use XSynchronize to make it
 * easier to debug problems.
 *
 * For PERF, we test against 32-bit pixmaps for COLOR_ALPHA, but for
 * COLOR we test against _windows_ at the depth of the default visual.
 * For obvious reasons, we don't use XSynchronize.
 */
static cairo_surface_t *
_cairo_boilerplate_xlib_test_create_surface (Display		   *dpy,
					     cairo_content_t	    content,
					     int		    width,
					     int		    height,
					     xlib_target_closure_t *xtc)
{
    XRenderPictFormat *xrender_format;
    cairo_surface_t *surface;

    /* This kills performance, but it makes debugging much
     * easier. That's why we have it here when in TEST mode, but not
     * over in PERF mode. */
    XSynchronize (xtc->dpy, 1);

    /* XXX: Currently we don't do any xlib testing when the X server
     * doesn't have the Render extension. We could do better here,
     * (perhaps by converting the tests from ARGB32 to RGB24). One
     * step better would be to always test the non-Render fallbacks
     * for each test even if the server does have the Render
     * extension. That would probably be through another
     * cairo_boilerplate_target which would use an extended version of
     * cairo_test_xlib_disable_render.	*/
    switch (content) {
    case CAIRO_CONTENT_COLOR_ALPHA:
	xrender_format = XRenderFindStandardFormat (dpy, PictStandardARGB32);
	break;
    case CAIRO_CONTENT_COLOR:
	xrender_format = XRenderFindStandardFormat (dpy, PictStandardRGB24);
	break;
    case CAIRO_CONTENT_ALPHA:
    default:
	CAIRO_BOILERPLATE_DEBUG (("Invalid content for xlib test: %d\n", content));
	return NULL;
    }
    if (xrender_format == NULL) {
	CAIRO_BOILERPLATE_DEBUG (("X server does not have the Render extension.\n"));
	return NULL;
    }

    xtc->drawable = XCreatePixmap (dpy, DefaultRootWindow (dpy),
				   width, height, xrender_format->depth);
    xtc->drawable_is_pixmap = TRUE;

    surface = cairo_xlib_surface_create_with_xrender_format (dpy, xtc->drawable,
							  DefaultScreenOfDisplay (dpy),
							  xrender_format,
							  width, height);

    _cairo_boilerplate_xlib_setup_test_surface(surface);

    return surface;
}

static cairo_surface_t *
_cairo_boilerplate_xlib_perf_create_surface (Display		   *dpy,
					     cairo_content_t	    content,
					     int		    width,
					     int		    height,
					     xlib_target_closure_t *xtc)
{
    XSetWindowAttributes attr;
    XRenderPictFormat *xrender_format;
    Visual *visual;

    switch (content) {
    case CAIRO_CONTENT_COLOR_ALPHA:
	xrender_format = XRenderFindStandardFormat (dpy, PictStandardARGB32);
	if (xrender_format == NULL) {
	    CAIRO_BOILERPLATE_DEBUG (("X server does not have the Render extension.\n"));
	    return NULL;
	}

	xtc->drawable = XCreatePixmap (dpy, DefaultRootWindow (dpy),
				       width, height, xrender_format->depth);
	xtc->drawable_is_pixmap = TRUE;
	break;

    case CAIRO_CONTENT_COLOR:
	if (! _cairo_boilerplate_xlib_check_screen_size (dpy,
							 DefaultScreen (dpy),
							 width, height)) {
	    CAIRO_BOILERPLATE_DEBUG (("Surface is larger than the Screen.\n"));
	    return NULL;
	}

	visual = DefaultVisual (dpy, DefaultScreen (dpy));
	xrender_format = XRenderFindVisualFormat (dpy, visual);
	if (xrender_format == NULL) {
	    CAIRO_BOILERPLATE_DEBUG (("X server does not have the Render extension.\n"));
	    return NULL;
	}

	attr.override_redirect = True;
	xtc->drawable = XCreateWindow (dpy, DefaultRootWindow (dpy), 0, 0,
				       width, height, 0, xrender_format->depth,
				       InputOutput, visual, CWOverrideRedirect, &attr);
	XMapWindow (dpy, xtc->drawable);
	xtc->drawable_is_pixmap = FALSE;
	break;

    case CAIRO_CONTENT_ALPHA:
    default:
	CAIRO_BOILERPLATE_DEBUG (("Invalid content for xlib test: %d\n", content));
	return NULL;
    }

    return cairo_xlib_surface_create_with_xrender_format (dpy, xtc->drawable,
							  DefaultScreenOfDisplay (dpy),
							  xrender_format,
							  width, height);
}

struct similar {
	Display *dpy;
	Pixmap pixmap;
};

static void _destroy_similar (void *closure)
{
    struct similar *similar = closure;

    XFreePixmap (similar->dpy, similar->pixmap);
    free (similar);
}

static cairo_surface_t *
_cairo_boilerplate_xlib_create_similar (cairo_surface_t		*other,
					cairo_content_t		 content,
					int			 width,
					int			 height)
{
    XRenderPictFormat *xrender_format;
    uint32_t format;
    struct similar *similar;
    cairo_surface_t *surface;

    similar = malloc (sizeof (*similar));
    similar->dpy = cairo_xlib_surface_get_display (other);

    switch (content) {
    default:
    case CAIRO_CONTENT_COLOR_ALPHA: format = PictStandardARGB32; break;
    case CAIRO_CONTENT_COLOR: format = PictStandardRGB24; break;
    case CAIRO_CONTENT_ALPHA: format = PictStandardA8; break;
    }

    xrender_format = XRenderFindStandardFormat (similar->dpy, format);
    similar->pixmap = XCreatePixmap (similar->dpy,
				     DefaultRootWindow (similar->dpy),
				     width, height,
				     xrender_format->depth);

    surface =
	    cairo_xlib_surface_create_with_xrender_format (similar->dpy,
							   similar->pixmap,
							   DefaultScreenOfDisplay (similar->dpy),
							   xrender_format,
							   width, height);

    cairo_surface_set_user_data (surface, &key, similar, _destroy_similar);

    return surface;
}

static cairo_surface_t *
_cairo_boilerplate_xlib_create_surface (const char		  *name,
					cairo_content_t		   content,
					double			   width,
					double			   height,
					double			   max_width,
					double			   max_height,
					cairo_boilerplate_mode_t   mode,
					void			 **closure)
{
    xlib_target_closure_t *xtc;
    Display *dpy;
    cairo_surface_t *surface;

    *closure = xtc = xcalloc (1, sizeof (xlib_target_closure_t));

    width = ceil (width);
    if (width < 1)
	width = 1;

    height = ceil (height);
    if (height < 1)
	height = 1;

    xtc->dpy = dpy = XOpenDisplay (NULL);
    if (xtc->dpy == NULL) {
	free (xtc);
	CAIRO_BOILERPLATE_DEBUG (("Failed to open display: %s\n", XDisplayName(0)));
	return NULL;
    }

    if (mode == CAIRO_BOILERPLATE_MODE_TEST)
	surface = _cairo_boilerplate_xlib_test_create_surface (dpy, content, width, height, xtc);
    else /* mode == CAIRO_BOILERPLATE_MODE_PERF */
	surface = _cairo_boilerplate_xlib_perf_create_surface (dpy, content, width, height, xtc);

    if (surface == NULL || cairo_surface_status (surface))
	_cairo_boilerplate_xlib_cleanup (xtc);

    return surface;
}

static cairo_surface_t *
_cairo_boilerplate_xlib_render_0_0_create_surface (const char		  *name,
						   cairo_content_t		   content,
						   double			   width,
						   double			   height,
						   double			   max_width,
						   double			   max_height,
						   cairo_boilerplate_mode_t   mode,
						   void			 **closure)
{
    xlib_target_closure_t *xtc;
    Display *dpy;
    int screen;
    Pixmap pixmap;
    cairo_surface_t *surface, *dummy;

    *closure = xtc = xcalloc (1, sizeof (xlib_target_closure_t));

    width = ceil (width);
    if (width < 1)
	width = 1;

    height = ceil (height);
    if (height < 1)
	height = 1;

    xtc->dpy = dpy = XOpenDisplay (NULL);
    if (xtc->dpy == NULL) {
	free (xtc);
	CAIRO_BOILERPLATE_DEBUG (("Failed to open display: %s\n", XDisplayName(0)));
	return NULL;
    }


    screen = DefaultScreen (dpy);
    pixmap = XCreatePixmap (dpy, DefaultRootWindow (dpy), 1, 1,
			    DefaultDepth (dpy, screen));
    dummy = cairo_xlib_surface_create (dpy, pixmap,
				       DefaultVisual (dpy, screen),
				       1, 1);
    cairo_xlib_device_debug_cap_xrender_version (cairo_surface_get_device (dummy),
						 0, 0);

    if (mode == CAIRO_BOILERPLATE_MODE_TEST)
	surface = _cairo_boilerplate_xlib_test_create_surface (dpy, content, width, height, xtc);
    else /* mode == CAIRO_BOILERPLATE_MODE_PERF */
	surface = _cairo_boilerplate_xlib_perf_create_surface (dpy, content, width, height, xtc);

    cairo_surface_destroy (dummy);
    XFreePixmap (dpy, pixmap);

    if (surface == NULL || cairo_surface_status (surface))
	_cairo_boilerplate_xlib_cleanup (xtc);

    return surface;
}

static cairo_surface_t *
_cairo_boilerplate_xlib_window_create_surface (const char		 *name,
					       cairo_content_t		  content,
					       double			  width,
					       double			  height,
					       double			  max_width,
					       double			  max_height,
					       cairo_boilerplate_mode_t   mode,
					       void			**closure)
{
    xlib_target_closure_t *xtc;
    Display *dpy;
    int screen;
    XSetWindowAttributes attr;
    cairo_surface_t *surface;

    /* We're not yet bothering to support perf mode for the
     * xlib-fallback surface. */
    if (mode == CAIRO_BOILERPLATE_MODE_PERF)
	return NULL;

    /* We also don't support drawing with destination-alpha in the
     * xlib-fallback surface. */
    if (content == CAIRO_CONTENT_COLOR_ALPHA)
	return NULL;

    *closure = xtc = xmalloc (sizeof (xlib_target_closure_t));

    width = ceil (width);
    if (width < 1)
	width = 1;

    height = ceil (height);
    if (height < 1)
	height = 1;

    xtc->dpy = dpy = XOpenDisplay (NULL);
    if (xtc->dpy == NULL) {
	CAIRO_BOILERPLATE_DEBUG (("Failed to open display: %s\n", XDisplayName(0)));
	free (xtc);
	return NULL;
    }

    /* This kills performance, but it makes debugging much
     * easier. That's why we have it here only after explicitly not
     * supporting PERF mode.*/
    XSynchronize (dpy, 1);

    screen = DefaultScreen (dpy);
    if (! _cairo_boilerplate_xlib_check_screen_size (dpy, screen,
						     width, height)) {
	CAIRO_BOILERPLATE_DEBUG (("Surface is larger than the Screen.\n"));
	XCloseDisplay (dpy);
	free (xtc);
	return NULL;
    }

    attr.override_redirect = True;
    xtc->drawable = XCreateWindow (dpy, DefaultRootWindow (dpy),
				   0, 0,
				   width, height, 0,
				   DefaultDepth (dpy, screen),
				   InputOutput,
				   DefaultVisual (dpy, screen),
				   CWOverrideRedirect, &attr);
    XMapWindow (dpy, xtc->drawable);
    xtc->drawable_is_pixmap = FALSE;

    surface = cairo_xlib_surface_create (dpy, xtc->drawable,
					 DefaultVisual (dpy, screen),
					 width, height);
    if (cairo_surface_status (surface))
	_cairo_boilerplate_xlib_cleanup (xtc);

    _cairo_boilerplate_xlib_setup_test_surface(surface);

    return surface;
}
#endif


#if CAIRO_HAS_XLIB_SURFACE
/* The xlib-fallback target differs from the xlib target in two ways:
 *
 * 1. It creates its surfaces without relying on the Render extension
 *
 * 2. It disables use of the Render extension for its surfaces
 *
 * This provides testing of the non-Render fallback paths we have in
 * cairo-xlib-surface.c
 */
static cairo_surface_t *
_cairo_boilerplate_xlib_fallback_create_surface (const char		   *name,
						 cairo_content_t	    content,
						 double 		    width,
						 double 		    height,
						 double 		    max_width,
						 double 		    max_height,
						 cairo_boilerplate_mode_t   mode,
						 void			  **closure)
{
    xlib_target_closure_t *xtc;
    Display *dpy;
    int screen;
    XSetWindowAttributes attr;
    cairo_surface_t *surface, *dummy;

    /* We're not yet bothering to support perf mode for the
     * xlib-fallback surface. */
    if (mode == CAIRO_BOILERPLATE_MODE_PERF)
	return NULL;

    /* We also don't support drawing with destination-alpha in the
     * xlib-fallback surface. */
    if (content == CAIRO_CONTENT_COLOR_ALPHA)
	return NULL;

    *closure = xtc = xmalloc (sizeof (xlib_target_closure_t));

    width = ceil (width);
    if (width < 1)
	width = 1;

    height = ceil (height);
    if (height < 1)
	height = 1;

    xtc->dpy = dpy = XOpenDisplay (NULL);
    if (xtc->dpy == NULL) {
	CAIRO_BOILERPLATE_DEBUG (("Failed to open display: %s\n", XDisplayName(0)));
	free (xtc);
	return NULL;
    }

    /* This kills performance, but it makes debugging much
     * easier. That's why we have it here only after explicitly not
     * supporting PERF mode.*/
    XSynchronize (dpy, 1);

    screen = DefaultScreen (dpy);
    if (! _cairo_boilerplate_xlib_check_screen_size (dpy, screen,
						     width, height)) {
	CAIRO_BOILERPLATE_DEBUG (("Surface is larger than the Screen.\n"));
	XCloseDisplay (dpy);
	free (xtc);
	return NULL;
    }

    attr.override_redirect = True;
    xtc->drawable = XCreateWindow (dpy, DefaultRootWindow (dpy),
				   0, 0,
				   width, height, 0,
				   DefaultDepth (dpy, screen),
				   InputOutput,
				   DefaultVisual (dpy, screen),
				   CWOverrideRedirect, &attr);
    XMapWindow (dpy, xtc->drawable);
    xtc->drawable_is_pixmap = FALSE;

    dummy = cairo_xlib_surface_create (dpy, xtc->drawable,
				       DefaultVisual (dpy, screen),
				       width, height);
    cairo_xlib_device_debug_cap_xrender_version (cairo_surface_get_device (dummy),
						 -1, -1);

    surface = cairo_xlib_surface_create (dpy, xtc->drawable,
					 DefaultVisual (dpy, screen),
					 width, height);
    cairo_surface_destroy (dummy);
    if (cairo_surface_status (surface))
	_cairo_boilerplate_xlib_cleanup (xtc);

    _cairo_boilerplate_xlib_setup_test_surface(surface);

    return surface;
}
#endif

static const cairo_boilerplate_target_t targets[] = {
#if CAIRO_HAS_XLIB_XRENDER_SURFACE
    /* Acceleration architectures may make the results differ by a
     * bit, so we set the error tolerance to 1. */
    {
	"xlib", "traps", NULL, "xlib-fallback",
	CAIRO_SURFACE_TYPE_XLIB, CAIRO_CONTENT_COLOR_ALPHA, 1,
	"cairo_xlib_surface_create_with_xrender_format",
	_cairo_boilerplate_xlib_create_surface,
	_cairo_boilerplate_xlib_create_similar,
	NULL, NULL,
	_cairo_boilerplate_get_image_surface,
	cairo_surface_write_to_png,
	_cairo_boilerplate_xlib_cleanup,
	_cairo_boilerplate_xlib_synchronize,
        NULL,
	TRUE, FALSE, FALSE
    },
    {
	"xlib", "traps", NULL, "xlib-fallback",
	CAIRO_SURFACE_TYPE_XLIB, CAIRO_CONTENT_COLOR, 1,
	"cairo_xlib_surface_create_with_xrender_format",
	_cairo_boilerplate_xlib_create_surface,
	_cairo_boilerplate_xlib_create_similar,
	NULL, NULL,
	_cairo_boilerplate_get_image_surface,
	cairo_surface_write_to_png,
	_cairo_boilerplate_xlib_cleanup,
	_cairo_boilerplate_xlib_synchronize,
        NULL,
	FALSE, FALSE, FALSE
    },
    {
	"xlib-window", "traps", NULL, NULL,
	CAIRO_SURFACE_TYPE_XLIB, CAIRO_CONTENT_COLOR, 1,
	"cairo_xlib_surface_create",
	_cairo_boilerplate_xlib_window_create_surface,
	cairo_surface_create_similar,
	NULL, NULL,
	_cairo_boilerplate_get_image_surface,
	cairo_surface_write_to_png,
	_cairo_boilerplate_xlib_cleanup,
	_cairo_boilerplate_xlib_synchronize,
        NULL,
	FALSE, FALSE, FALSE
    },
    {
	"xlib-render-0_0", "mask", NULL, NULL,
	CAIRO_SURFACE_TYPE_XLIB, CAIRO_CONTENT_COLOR, 1,
	"cairo_xlib_surface_create",
	_cairo_boilerplate_xlib_render_0_0_create_surface,
	cairo_surface_create_similar,
	NULL, NULL,
	_cairo_boilerplate_get_image_surface,
	cairo_surface_write_to_png,
	_cairo_boilerplate_xlib_cleanup,
	_cairo_boilerplate_xlib_synchronize,
        NULL,
	FALSE, FALSE, FALSE
    },
#endif
#if CAIRO_HAS_XLIB_SURFACE
    /* This is a fallback surface which uses xlib fallbacks instead of
     * the Render extension. */
    {
	"xlib-fallback", "image", NULL, NULL,
	CAIRO_SURFACE_TYPE_XLIB, CAIRO_CONTENT_COLOR, 1,
	"cairo_xlib_surface_create",
	_cairo_boilerplate_xlib_fallback_create_surface,
	cairo_surface_create_similar,
	NULL, NULL,
	_cairo_boilerplate_get_image_surface,
	cairo_surface_write_to_png,
	_cairo_boilerplate_xlib_cleanup,
	_cairo_boilerplate_xlib_synchronize,
        NULL,
	FALSE, FALSE, FALSE
    },
#endif
};
CAIRO_BOILERPLATE (xlib, targets)
