/* Cairo - a vector graphics library with display and print output
 *
 * Copyright © 2007 Chris Wilson
 *
 * 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.
 *
 * The Original Code is the cairo graphics library.
 *
 * The Initial Developer of the Original Code is Chris Wilson.
 *
 * Contributor(s):
 *	Karl Tomlinson <karlt+@karlt.net>, Mozilla Corporation
 */

#include "cairoint.h"

#if !CAIRO_HAS_XLIB_XCB_FUNCTIONS

#include "cairo-xlib-private.h"
#include "cairo-xlib-xrender-private.h"
#include "cairo-freelist-private.h"
#include "cairo-error-private.h"
#include "cairo-list-inline.h"

#include <X11/Xlibint.h>	/* For XESetCloseDisplay */

typedef int (*cairo_xlib_error_func_t) (Display     *display,
					XErrorEvent *event);

static cairo_xlib_display_t *_cairo_xlib_display_list;

static int
_noop_error_handler (Display     *display,
		     XErrorEvent *event)
{
    return False;		/* return value is ignored */
}

static void
_cairo_xlib_display_finish (void *abstract_display)
{
    cairo_xlib_display_t *display = abstract_display;
    Display *dpy = display->display;

    _cairo_xlib_display_fini_shm (display);

    if (! cairo_device_acquire (&display->base)) {
	cairo_xlib_error_func_t old_handler;

	/* protect the notifies from triggering XErrors */
	XSync (dpy, False);
	old_handler = XSetErrorHandler (_noop_error_handler);

	while (! cairo_list_is_empty (&display->fonts)) {
	    _cairo_xlib_font_close (cairo_list_first_entry (&display->fonts,
							    cairo_xlib_font_t,
							    link));
	}

	while (! cairo_list_is_empty (&display->screens)) {
	    _cairo_xlib_screen_destroy (display,
					cairo_list_first_entry (&display->screens,
								cairo_xlib_screen_t,
								link));
	}

	XSync (dpy, False);
	XSetErrorHandler (old_handler);

	cairo_device_release (&display->base);
    }
}

static void
_cairo_xlib_display_destroy (void *abstract_display)
{
    cairo_xlib_display_t *display = abstract_display;

    free (display);
}

static int
_cairo_xlib_close_display (Display *dpy, XExtCodes *codes)
{
    cairo_xlib_display_t *display, **prev, *next;

    CAIRO_MUTEX_LOCK (_cairo_xlib_display_mutex);
    for (display = _cairo_xlib_display_list; display; display = display->next)
	if (display->display == dpy)
	    break;
    CAIRO_MUTEX_UNLOCK (_cairo_xlib_display_mutex);
    if (display == NULL)
	return 0;

    cairo_device_finish (&display->base);

    /*
     * Unhook from the global list
     */
    CAIRO_MUTEX_LOCK (_cairo_xlib_display_mutex);
    prev = &_cairo_xlib_display_list;
    for (display = _cairo_xlib_display_list; display; display = next) {
	next = display->next;
	if (display->display == dpy) {
	    *prev = next;
	    break;
	} else
	    prev = &display->next;
    }
    CAIRO_MUTEX_UNLOCK (_cairo_xlib_display_mutex);

    display->display = NULL; /* catch any later invalid access */
    cairo_device_destroy (&display->base);

    /* Return value in accordance with requirements of
     * XESetCloseDisplay */
    return 0;
}

static const cairo_device_backend_t _cairo_xlib_device_backend = {
    CAIRO_DEVICE_TYPE_XLIB,

    NULL,
    NULL,

    NULL, /* flush */
    _cairo_xlib_display_finish,
    _cairo_xlib_display_destroy,
};

static void _cairo_xlib_display_select_compositor (cairo_xlib_display_t *display)
{
#if 1
    if (display->render_major > 0 || display->render_minor >= 4)
	display->compositor = _cairo_xlib_traps_compositor_get ();
    else if (display->render_major > 0 || display->render_minor >= 0)
	display->compositor = _cairo_xlib_mask_compositor_get ();
    else
	display->compositor = _cairo_xlib_core_compositor_get ();
#else
    display->compositor = _cairo_xlib_fallback_compositor_get ();
#endif
}

/**
 * _cairo_xlib_device_create:
 * @dpy: the display to create the device for
 *
 * Gets the device belonging to @dpy, or creates it if it doesn't exist yet.
 *
 * Returns: the device belonging to @dpy
 **/
cairo_device_t *
_cairo_xlib_device_create (Display *dpy)
{
    cairo_xlib_display_t *display;
    cairo_xlib_display_t **prev;
    cairo_device_t *device;
    XExtCodes *codes;
    const char *env;

    CAIRO_MUTEX_INITIALIZE ();

    /* There is an apparent deadlock between this mutex and the
     * mutex for the display, but it's actually safe. For the
     * app to call XCloseDisplay() while any other thread is
     * inside this function would be an error in the logic
     * app, and the CloseDisplay hook is the only other place we
     * acquire this mutex.
     */
    CAIRO_MUTEX_LOCK (_cairo_xlib_display_mutex);

    for (prev = &_cairo_xlib_display_list; (display = *prev); prev = &(*prev)->next)
    {
	if (display->display == dpy) {
	    /*
	     * MRU the list
	     */
	    if (prev != &_cairo_xlib_display_list) {
		*prev = display->next;
		display->next = _cairo_xlib_display_list;
		_cairo_xlib_display_list = display;
	    }
            device = cairo_device_reference (&display->base);
	    goto UNLOCK;
	}
    }

    display = malloc (sizeof (cairo_xlib_display_t));
    if (unlikely (display == NULL)) {
	device = _cairo_device_create_in_error (CAIRO_STATUS_NO_MEMORY);
	goto UNLOCK;
    }

    _cairo_device_init (&display->base, &_cairo_xlib_device_backend);

    display->display = dpy;
    cairo_list_init (&display->screens);
    cairo_list_init (&display->fonts);
    display->closed = FALSE;

    /* Xlib calls out to the extension close_display hooks in LIFO
     * order. So we have to ensure that all extensions that we depend
     * on in our close_display hook are properly initialized before we
     * add our hook. For now, that means Render, so we call into its
     * QueryVersion function to ensure it gets initialized.
     */
    display->render_major = display->render_minor = -1;
    XRenderQueryVersion (dpy, &display->render_major, &display->render_minor);
    env = getenv ("CAIRO_DEBUG");
    if (env != NULL && (env = strstr (env, "xrender-version=")) != NULL) {
	int max_render_major, max_render_minor;

	env += sizeof ("xrender-version=") - 1;
	if (sscanf (env, "%d.%d", &max_render_major, &max_render_minor) != 2)
	    max_render_major = max_render_minor = -1;

	if (max_render_major < display->render_major ||
	    (max_render_major == display->render_major &&
	     max_render_minor < display->render_minor))
	{
	    display->render_major = max_render_major;
	    display->render_minor = max_render_minor;
	}
    }

    _cairo_xlib_display_select_compositor (display);

    display->white = NULL;
    memset (display->alpha, 0, sizeof (display->alpha));
    memset (display->solid, 0, sizeof (display->solid));
    memset (display->solid_cache, 0, sizeof (display->solid_cache));
    memset (display->last_solid_cache, 0, sizeof (display->last_solid_cache));

    memset (display->cached_xrender_formats, 0,
	    sizeof (display->cached_xrender_formats));

    display->force_precision = -1;

    _cairo_xlib_display_init_shm (display);

    /* Prior to Render 0.10, there is no protocol support for gradients and
     * we call function stubs instead, which would silently consume the drawing.
     */
#if RENDER_MAJOR == 0 && RENDER_MINOR < 10
    display->buggy_gradients = TRUE;
#else
    display->buggy_gradients = FALSE;
#endif
    display->buggy_pad_reflect = FALSE;
    display->buggy_repeat = FALSE;

    /* This buggy_repeat condition is very complicated because there
     * are multiple X server code bases (with multiple versioning
     * schemes within a code base), and multiple bugs.
     *
     * The X servers:
     *
     *    1. The Vendor=="XFree86" code base with release numbers such
     *    as 4.7.0 (VendorRelease==40700000).
     *
     *    2. The Vendor=="X.Org" code base (a descendant of the
     *    XFree86 code base). It originally had things like
     *    VendorRelease==60700000 for release 6.7.0 but then changed
     *    its versioning scheme so that, for example,
     *    VendorRelease==10400000 for the 1.4.0 X server within the
     *    X.Org 7.3 release.
     *
     * The bugs:
     *
     *    1. The original bug that led to the buggy_repeat
     *    workaround. This was a bug that Owen Taylor investigated,
     *    understood well, and characterized against carious X
     *    servers. Confirmed X servers with this bug include:
     *
     *		"XFree86" <= 40500000
     *		"X.Org" <= 60802000 (only with old numbering >= 60700000)
     *
     *    2. A separate bug resulting in a crash of the X server when
     *    using cairo's extend-reflect test case, (which, surprisingly
     *    enough was not passing RepeatReflect to the X server, but
     *    instead using RepeatNormal in a workaround). Nobody to date
     *    has understood the bug well, but it appears to be gone as of
     *    the X.Org 1.4.0 server. This bug is coincidentally avoided
     *    by using the same buggy_repeat workaround. Confirmed X
     *    servers with this bug include:
     *
     *		"X.org" == 60900000 (old versioning scheme)
     *		"X.org"  < 10400000 (new numbering scheme)
     *
     *    For the old-versioning-scheme X servers we don't know
     *    exactly when second the bug started, but since bug 1 is
     *    present through 6.8.2 and bug 2 is present in 6.9.0 it seems
     *    safest to just blacklist all old-versioning-scheme X servers,
     *    (just using VendorRelease < 70000000), as buggy_repeat=TRUE.
     */
    if (_cairo_xlib_vendor_is_xorg (dpy)) {
	if (VendorRelease (dpy) >= 60700000) {
	    if (VendorRelease (dpy) < 70000000)
		display->buggy_repeat = TRUE;

	    /* We know that gradients simply do not work in early Xorg servers */
	    if (VendorRelease (dpy) < 70200000)
		display->buggy_gradients = TRUE;

	    /* And the extended repeat modes were not fixed until much later */
	    display->buggy_pad_reflect = TRUE;
	} else {
	    if (VendorRelease (dpy) < 10400000)
		display->buggy_repeat = TRUE;

	    /* Too many bugs in the early drivers */
	    if (VendorRelease (dpy) < 10699000)
		display->buggy_pad_reflect = TRUE;
	}
    } else if (strstr (ServerVendor (dpy), "XFree86") != NULL) {
	if (VendorRelease (dpy) <= 40500000)
	    display->buggy_repeat = TRUE;

	display->buggy_gradients = TRUE;
	display->buggy_pad_reflect = TRUE;
    }

    codes = XAddExtension (dpy);
    if (unlikely (codes == NULL)) {
	device = _cairo_device_create_in_error (CAIRO_STATUS_NO_MEMORY);
	free (display);
	goto UNLOCK;
    }

    XESetCloseDisplay (dpy, codes->extension, _cairo_xlib_close_display);
    cairo_device_reference (&display->base); /* add one for the CloseDisplay */

    display->next = _cairo_xlib_display_list;
    _cairo_xlib_display_list = display;

    device = &display->base;

UNLOCK:
    CAIRO_MUTEX_UNLOCK (_cairo_xlib_display_mutex);
    return device;
}

cairo_status_t
_cairo_xlib_display_acquire (cairo_device_t *device, cairo_xlib_display_t **display)
{
    cairo_status_t status;

    status = cairo_device_acquire (device);
    if (status)
        return status;

    *display = (cairo_xlib_display_t *) device;
    return CAIRO_STATUS_SUCCESS;
}

XRenderPictFormat *
_cairo_xlib_display_get_xrender_format_for_pixman(cairo_xlib_display_t *display,
						  pixman_format_code_t format)
{
    Display *dpy = display->display;
    XRenderPictFormat tmpl;
    int mask;

#define MASK(x) ((1<<(x))-1)

    tmpl.depth = PIXMAN_FORMAT_DEPTH(format);
    mask = PictFormatType | PictFormatDepth;

    switch (PIXMAN_FORMAT_TYPE(format)) {
    case PIXMAN_TYPE_ARGB:
	tmpl.type = PictTypeDirect;

	tmpl.direct.alphaMask = MASK(PIXMAN_FORMAT_A(format));
	if (PIXMAN_FORMAT_A(format))
	    tmpl.direct.alpha = (PIXMAN_FORMAT_R(format) +
				 PIXMAN_FORMAT_G(format) +
				 PIXMAN_FORMAT_B(format));

	tmpl.direct.redMask = MASK(PIXMAN_FORMAT_R(format));
	tmpl.direct.red = (PIXMAN_FORMAT_G(format) +
			   PIXMAN_FORMAT_B(format));

	tmpl.direct.greenMask = MASK(PIXMAN_FORMAT_G(format));
	tmpl.direct.green = PIXMAN_FORMAT_B(format);

	tmpl.direct.blueMask = MASK(PIXMAN_FORMAT_B(format));
	tmpl.direct.blue = 0;

	mask |= PictFormatRed | PictFormatRedMask;
	mask |= PictFormatGreen | PictFormatGreenMask;
	mask |= PictFormatBlue | PictFormatBlueMask;
	mask |= PictFormatAlpha | PictFormatAlphaMask;
	break;

    case PIXMAN_TYPE_ABGR:
	tmpl.type = PictTypeDirect;

	tmpl.direct.alphaMask = MASK(PIXMAN_FORMAT_A(format));
	if (tmpl.direct.alphaMask)
	    tmpl.direct.alpha = (PIXMAN_FORMAT_B(format) +
				 PIXMAN_FORMAT_G(format) +
				 PIXMAN_FORMAT_R(format));

	tmpl.direct.blueMask = MASK(PIXMAN_FORMAT_B(format));
	tmpl.direct.blue = (PIXMAN_FORMAT_G(format) +
			    PIXMAN_FORMAT_R(format));

	tmpl.direct.greenMask = MASK(PIXMAN_FORMAT_G(format));
	tmpl.direct.green = PIXMAN_FORMAT_R(format);

	tmpl.direct.redMask = MASK(PIXMAN_FORMAT_R(format));
	tmpl.direct.red = 0;

	mask |= PictFormatRed | PictFormatRedMask;
	mask |= PictFormatGreen | PictFormatGreenMask;
	mask |= PictFormatBlue | PictFormatBlueMask;
	mask |= PictFormatAlpha | PictFormatAlphaMask;
	break;

    case PIXMAN_TYPE_BGRA:
	tmpl.type = PictTypeDirect;

	tmpl.direct.blueMask = MASK(PIXMAN_FORMAT_B(format));
	tmpl.direct.blue = (PIXMAN_FORMAT_BPP(format) - PIXMAN_FORMAT_B(format));

	tmpl.direct.greenMask = MASK(PIXMAN_FORMAT_G(format));
	tmpl.direct.green = (PIXMAN_FORMAT_BPP(format) - PIXMAN_FORMAT_B(format) -
			     PIXMAN_FORMAT_G(format));

	tmpl.direct.redMask = MASK(PIXMAN_FORMAT_R(format));
	tmpl.direct.red = (PIXMAN_FORMAT_BPP(format) - PIXMAN_FORMAT_B(format) -
			   PIXMAN_FORMAT_G(format) - PIXMAN_FORMAT_R(format));

	tmpl.direct.alphaMask = MASK(PIXMAN_FORMAT_A(format));
	tmpl.direct.alpha = 0;

	mask |= PictFormatRed | PictFormatRedMask;
	mask |= PictFormatGreen | PictFormatGreenMask;
	mask |= PictFormatBlue | PictFormatBlueMask;
	mask |= PictFormatAlpha | PictFormatAlphaMask;
	break;

    case PIXMAN_TYPE_A:
	tmpl.type = PictTypeDirect;

	tmpl.direct.alpha = 0;
	tmpl.direct.alphaMask = MASK(PIXMAN_FORMAT_A(format));

	mask |= PictFormatAlpha | PictFormatAlphaMask;
	break;

    case PIXMAN_TYPE_COLOR:
    case PIXMAN_TYPE_GRAY:
	/* XXX Find matching visual/colormap */
	tmpl.type = PictTypeIndexed;
	//tmpl.colormap = screen->visuals[PIXMAN_FORMAT_VIS(format)].vid;
	//mask |= PictFormatColormap;
	return NULL;
    }
#undef MASK

    /* XXX caching? */
    return XRenderFindFormat(dpy, mask, &tmpl, 1);
}

XRenderPictFormat *
_cairo_xlib_display_get_xrender_format (cairo_xlib_display_t	*display,
	                                cairo_format_t		 format)
{
    XRenderPictFormat *xrender_format;

#if ! ATOMIC_OP_NEEDS_MEMORY_BARRIER
    xrender_format = display->cached_xrender_formats[format];
    if (likely (xrender_format != NULL))
	return xrender_format;
#endif

    xrender_format = display->cached_xrender_formats[format];
    if (xrender_format == NULL) {
	int pict_format = PictStandardNUM;

	switch (format) {
	case CAIRO_FORMAT_A1:
	    pict_format = PictStandardA1; break;
	case CAIRO_FORMAT_A8:
	    pict_format = PictStandardA8; break;
	case CAIRO_FORMAT_RGB24:
	    pict_format = PictStandardRGB24; break;
	case CAIRO_FORMAT_RGB16_565:
	    xrender_format = _cairo_xlib_display_get_xrender_format_for_pixman(display,
									       PIXMAN_r5g6b5);
	    break;
	case CAIRO_FORMAT_RGB30:
	    xrender_format = _cairo_xlib_display_get_xrender_format_for_pixman(display,
									       PIXMAN_x2r10g10b10);
	    break;
	case CAIRO_FORMAT_INVALID:
	default:
	    ASSERT_NOT_REACHED;
	case CAIRO_FORMAT_ARGB32:
	    pict_format = PictStandardARGB32; break;
	}
	if (pict_format != PictStandardNUM)
	    xrender_format =
		XRenderFindStandardFormat (display->display, pict_format);
	display->cached_xrender_formats[format] = xrender_format;
    }

    return xrender_format;
}

cairo_xlib_screen_t *
_cairo_xlib_display_get_screen (cairo_xlib_display_t *display,
				Screen *screen)
{
    cairo_xlib_screen_t *info;

    cairo_list_foreach_entry (info, cairo_xlib_screen_t, &display->screens, link) {
	if (info->screen == screen) {
            if (display->screens.next != &info->link)
                cairo_list_move (&info->link, &display->screens);
            return info;
        }
    }

    return NULL;
}

cairo_bool_t
_cairo_xlib_display_has_repeat (cairo_device_t *device)
{
    return ! ((cairo_xlib_display_t *) device)->buggy_repeat;
}

cairo_bool_t
_cairo_xlib_display_has_reflect (cairo_device_t *device)
{
    return ! ((cairo_xlib_display_t *) device)->buggy_pad_reflect;
}

cairo_bool_t
_cairo_xlib_display_has_gradients (cairo_device_t *device)
{
    return ! ((cairo_xlib_display_t *) device)->buggy_gradients;
}

/**
 * cairo_xlib_device_debug_cap_xrender_version:
 * @device: a #cairo_device_t for the Xlib backend
 * @major_version: major version to restrict to
 * @minor_version: minor version to restrict to
 *
 * Restricts all future Xlib surfaces for this devices to the specified version
 * of the RENDER extension. This function exists solely for debugging purpose.
 * It let's you find out how cairo would behave with an older version of
 * the RENDER extension.
 *
 * Use the special values -1 and -1 for disabling the RENDER extension.
 *
 * Since: 1.12
 **/
void
cairo_xlib_device_debug_cap_xrender_version (cairo_device_t *device,
					     int major_version,
					     int minor_version)
{
    cairo_xlib_display_t *display = (cairo_xlib_display_t *) device;

    if (device == NULL || device->status)
	return;

    if (device->backend->type != CAIRO_DEVICE_TYPE_XLIB)
	return;

    if (major_version < display->render_major ||
	(major_version == display->render_major &&
	 minor_version < display->render_minor))
    {
	display->render_major = major_version;
	display->render_minor = minor_version;
    }

    _cairo_xlib_display_select_compositor (display);
}

/**
 * cairo_xlib_device_debug_set_precision:
 * @device: a #cairo_device_t for the Xlib backend
 * @precision: the precision to use
 *
 * Render supports two modes of precision when rendering trapezoids. Set
 * the precision to the desired mode.
 *
 * Since: 1.12
 **/
void
cairo_xlib_device_debug_set_precision (cairo_device_t *device,
				       int precision)
{
    if (device == NULL || device->status)
	return;
    if (device->backend->type != CAIRO_DEVICE_TYPE_XLIB) {
	cairo_status_t status;

	status = _cairo_device_set_error (device, CAIRO_STATUS_DEVICE_TYPE_MISMATCH);
	(void) status;
	return;
    }

    ((cairo_xlib_display_t *) device)->force_precision = precision;
}

/**
 * cairo_xlib_device_debug_get_precision:
 * @device: a #cairo_device_t for the Xlib backend
 *
 * Get the Xrender precision mode.
 *
 * Returns: the render precision mode
 *
 * Since: 1.12
 **/
int
cairo_xlib_device_debug_get_precision (cairo_device_t *device)
{
    if (device == NULL || device->status)
	return -1;
    if (device->backend->type != CAIRO_DEVICE_TYPE_XLIB) {
	cairo_status_t status;

	status = _cairo_device_set_error (device, CAIRO_STATUS_DEVICE_TYPE_MISMATCH);
	(void) status;
	return -1;
    }

    return ((cairo_xlib_display_t *) device)->force_precision;
}

#endif /* !CAIRO_HAS_XLIB_XCB_FUNCTIONS */
