/* Cairo - a vector graphics library with display and print output
 *
 * Copyright © 2009 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.
 */

#include "cairo-boilerplate-private.h"

#include <cairo-vg.h>

 /* XXX Not sure how to handle library specific context initialization */
//#define USE_SHIVA
//#define USE_AMANITH

#if CAIRO_HAS_GLX_FUNCTIONS

#include <X11/Xlib.h>
#include <GL/glx.h>

typedef struct _vg_closure {
    Display *dpy;
    int screen;
    Window win;

    GLXContext ctx;
    cairo_surface_t *surface;
} vg_closure_glx_t;

static void
_cairo_boilerplate_vg_cleanup_glx (void *closure)
{
    vg_closure_glx_t *vgc = closure;

#ifdef USE_AMANITH
    vgDestroyContextAM ();
#endif
#ifdef USE_SHIVA
    vgDestroyContextSH ();
#endif

    glXDestroyContext (vgc->dpy, vgc->ctx);
    XDestroyWindow (vgc->dpy, vgc->win);
    XCloseDisplay (vgc->dpy);
    free (vgc);
}

static cairo_surface_t *
_cairo_boilerplate_vg_create_surface_glx (const char		    *name,
					  cairo_content_t	     content,
					  double		     width,
					  double		     height,
					  double		     max_width,
					  double		     max_height,
					  cairo_boilerplate_mode_t   mode,
					  void			   **closure)
{
    int rgba_attribs[] = {
	GLX_RGBA,
	GLX_RED_SIZE, 1,
	GLX_GREEN_SIZE, 1,
	GLX_BLUE_SIZE, 1,
	GLX_ALPHA_SIZE, 1,
	GLX_DOUBLEBUFFER,
	None
    };
    int rgb_attribs[] = {
	GLX_RGBA,
	GLX_RED_SIZE, 1,
	GLX_GREEN_SIZE, 1,
	GLX_BLUE_SIZE, 1,
	GLX_DOUBLEBUFFER,
	None
    };
    XVisualInfo *vi;
    Display *dpy;
    Colormap cmap;
    XSetWindowAttributes swa;
    cairo_surface_t *surface;
    cairo_vg_context_t *context;
    vg_closure_glx_t *vgc;

    vgc = malloc (sizeof (vg_closure_glx_t));
    *closure = vgc;

    if (width == 0)
	width = 1;
    if (height == 0)
	height = 1;

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

    if (content == CAIRO_CONTENT_COLOR)
	vi = glXChooseVisual (dpy, DefaultScreen (dpy), rgb_attribs);
    else
	vi = glXChooseVisual (dpy, DefaultScreen (dpy), rgba_attribs);

    if (vi == NULL) {
	fprintf (stderr, "Failed to create RGB, double-buffered visual\n");
	XCloseDisplay (dpy);
	free (vgc);
	return NULL;
    }

    vgc->ctx = glXCreateContext (dpy, vi, NULL, True);
    cmap = XCreateColormap (dpy,
			    RootWindow (dpy, vi->screen),
			    vi->visual,
			    AllocNone);
    swa.colormap = cmap;
    swa.border_pixel = 0;
    vgc->win = XCreateWindow (dpy, RootWindow (dpy, vi->screen),
			      -1, -1, 1, 1, 0,
			      vi->depth,
			      InputOutput,
			      vi->visual,
			      CWBorderPixel | CWColormap, &swa);
    XFreeColormap (dpy, cmap);
    XFree (vi);

    XMapWindow (dpy, vgc->win);

    /* we need an active context to initialise VG */
    glXMakeContextCurrent (dpy, vgc->win, vgc->win, vgc->ctx);

#ifdef USE_AMANITH
    vgInitContextAM (width, height, VG_FALSE, VG_TRUE);
#endif
#ifdef USE_SHIVA
    vgCreateContextSH (width, height);
#endif

    context = cairo_vg_context_create_for_glx (dpy, vgc->ctx);
    vgc->surface = cairo_vg_surface_create (context, content, width, height);
    cairo_vg_context_destroy (context);

    surface = vgc->surface;
    if (cairo_surface_status (surface))
	_cairo_boilerplate_vg_cleanup_glx (vgc);

    return surface;
}
#endif

#if CAIRO_HAS_EGL_FUNCTIONS
typedef struct _vg_closure_egl {
    EGLDisplay *dpy;
    EGLContext *ctx;
    EGLSurface *dummy;
} vg_closure_egl_t;

static void
_cairo_boilerplate_vg_cleanup_egl (void *closure)
{
    vg_closure_egl_t *vgc = closure;

#ifdef USE_AMANITH
    vgDestroyContextAM ();
#endif
#ifdef USE_SHIVA
    vgDestroyContextSH ();
#endif

    eglDestroyContext (vgc->dpy, vgc->ctx);
    eglDestroySurface (vgc->dpy, vgc->dummy);
    eglTerminate (vgc->dpy);
    free (vgc);
}

static cairo_surface_t *
_cairo_boilerplate_vg_create_surface_egl (const char		    *name,
					  cairo_content_t	     content,
					  double		     width,
					  double		     height,
					  double		     max_width,
					  double		     max_height,
					  cairo_boilerplate_mode_t   mode,
					  void			   **closure)
{
    int rgba_attribs[] = {
	EGL_RED_SIZE, 8,
	EGL_GREEN_SIZE, 8,
	EGL_BLUE_SIZE, 8,
	EGL_ALPHA_SIZE, 8,
	EGL_SURFACE_TYPE, EGL_PBUFFER_BIT,
	EGL_RENDERABLE_TYPE, EGL_OPENVG_BIT,
	None
    };
    int rgb_attribs[] = {
	EGL_RED_SIZE, 8,
	EGL_GREEN_SIZE, 8,
	EGL_BLUE_SIZE, 8,
	EGL_ALPHA_SIZE, 8,
	EGL_VG_ALPHA_FORMAT, EGL_VG_ALPHA_FORMAT_PRE_BIT,
	EGL_SURFACE_TYPE, EGL_PBUFFER_BIT,
	EGL_RENDERABLE_TYPE, EGL_OPENVG_BIT,
	None
    };
    int dummy_attribs[] = {
	EGL_WIDTH, 8, EGL_HEIGHT, 8,
	EGL_NONE
    };
    EGLDisplay *dpy;
    int major, minor;
    EGLConfig config;
    int num_configs;
    EGLContext *egl_context;
    EGLSurface *dummy;
    cairo_vg_context_t *context;
    cairo_surface_t *surface;
    vg_closure_egl_t *vgc;

    dpy = eglGetDisplay (EGL_DEFAULT_DISPLAY);

    if (! eglInitialize (dpy, &major, &minor))
	return NULL;

    eglBindAPI (EGL_OPENVG_API);

    if (! eglChooseConfig (dpy,
			   content == CAIRO_CONTENT_COLOR_ALPHA ?
			   rgba_attribs : rgb_attribs,
			   &config, 1, &num_configs) ||
	num_configs != 1)
    {
	return NULL;
    }

    egl_context = eglCreateContext (dpy, config, NULL, NULL);
    if (egl_context == NULL)
	return NULL;

    /* Create a dummy surface in order to enable a context to initialise VG */
    dummy = eglCreatePbufferSurface (dpy, config, dummy_attribs);
    if (dummy == NULL)
	return NULL;
    if (! eglMakeCurrent (dpy, dummy, dummy, egl_context))
	return NULL;

#ifdef USE_AMANITH
    vgInitContextAM (width, height, VG_FALSE, VG_TRUE);
#endif
#ifdef USE_SHIVA
    vgCreateContextSH (width, height);
#endif

    vgc = xmalloc (sizeof (vg_closure_egl_t));
    vgc->dpy = dpy;
    vgc->ctx = egl_context;
    vgc->dummy = dummy;
    *closure = vgc;

    context = cairo_vg_context_create_for_egl (vgc->dpy, vgc->ctx);
    surface = cairo_vg_surface_create (context, content, width, height);
    cairo_vg_context_destroy (context);

    if (cairo_surface_status (surface))
	_cairo_boilerplate_vg_cleanup_egl (vgc);

    return surface;
}
#endif

static void
_cairo_boilerplate_vg_synchronize (void *closure)
{
    vgFinish ();
}

static const cairo_boilerplate_target_t targets[] = {
#if CAIRO_HAS_GLX_FUNCTIONS
    {
	"vg-glx", "vg", NULL, NULL,
	CAIRO_SURFACE_TYPE_VG, CAIRO_CONTENT_COLOR_ALPHA, 1,
	"cairo_vg_context_create_for_glx",
	_cairo_boilerplate_vg_create_surface_glx,
	cairo_surface_create_similar,
	NULL, NULL,
	_cairo_boilerplate_get_image_surface,
	cairo_surface_write_to_png,
	_cairo_boilerplate_vg_cleanup_glx,
	_cairo_boilerplate_vg_synchronize,
        NULL,
	TRUE, FALSE, FALSE
    },
    {
	"vg-glx", "vg", NULL, NULL,
	CAIRO_SURFACE_TYPE_VG, CAIRO_CONTENT_COLOR, 1,
	"cairo_vg_context_create_for_glx",
	_cairo_boilerplate_vg_create_surface_glx,
	cairo_surface_create_similar,
	NULL, NULL,
	_cairo_boilerplate_get_image_surface,
	cairo_surface_write_to_png,
	_cairo_boilerplate_vg_cleanup_glx,
	_cairo_boilerplate_vg_synchronize,
        NULL,
	FALSE, FALSE, FALSE
    },
#endif
#if CAIRO_HAS_EGL_FUNCTIONS
    {
	"vg-egl", "vg", NULL, NULL,
	CAIRO_SURFACE_TYPE_VG, CAIRO_CONTENT_COLOR_ALPHA, 1,
	"cairo_vg_context_create_for_egl",
	_cairo_boilerplate_vg_create_surface_egl,
	cairo_surface_create_similar,
	NULL, NULL,
	_cairo_boilerplate_get_image_surface,
	cairo_surface_write_to_png,
	_cairo_boilerplate_vg_cleanup_egl,
	_cairo_boilerplate_vg_synchronize,
        NULL,
	TRUE, FALSE, FALSE
    },
    {
	"vg-egl", "vg", NULL, NULL,
	CAIRO_SURFACE_TYPE_VG, CAIRO_CONTENT_COLOR, 1,
	"cairo_vg_context_create_for_egl",
	_cairo_boilerplate_vg_create_surface_egl,
	cairo_surface_create_similar,
	NULL, NULL,
	_cairo_boilerplate_get_image_surface,
	cairo_surface_write_to_png,
	_cairo_boilerplate_vg_cleanup_egl,
	_cairo_boilerplate_vg_synchronize,
        NULL,
	FALSE, FALSE, FALSE
    },
#endif
};
CAIRO_BOILERPLATE (vg, targets)
