/* -*- mode: C; c-basic-offset: 2 -*-
 *
 * Pycairo - Python bindings for cairo
 *
 * Copyright © 2004-2006 Steve Chaplin
 *
 * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 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.
 */

#define PY_SSIZE_T_CLEAN
#include <Python.h>

#include "private.h"


/* Class Pattern ---------------------------------------------------------- */

PyObject *
PycairoPattern_FromPattern (cairo_pattern_t *pattern, PyObject *base) {
  PyTypeObject *type = NULL;
  PyObject *o;

  assert (pattern != NULL);

  if (Pycairo_Check_Status (cairo_pattern_status (pattern))) {
    cairo_pattern_destroy (pattern);
    return NULL;
  }

  switch (cairo_pattern_get_type (pattern)) {
  case CAIRO_PATTERN_TYPE_SOLID:
    type = &PycairoSolidPattern_Type;
    break;
  case CAIRO_PATTERN_TYPE_SURFACE:
    type = &PycairoSurfacePattern_Type;
    break;
  case CAIRO_PATTERN_TYPE_LINEAR:
    type = &PycairoLinearGradient_Type;
    break;
  case CAIRO_PATTERN_TYPE_RADIAL:
    type = &PycairoRadialGradient_Type;
    break;
  case CAIRO_PATTERN_TYPE_MESH:
    type = &PycairoMeshPattern_Type;
    break;
  case CAIRO_PATTERN_TYPE_RASTER_SOURCE:
    type = &PycairoRasterSourcePattern_Type;
    break;
  default:
    type = &PycairoPattern_Type;
    break;
  }

  o = type->tp_alloc(type, 0);
  if (o == NULL) {
    cairo_pattern_destroy (pattern);
  } else {
    ((PycairoPattern *)o)->pattern = pattern;
    Py_XINCREF(base);
    ((PycairoPattern *)o)->base = base;
  }
  return o;
}

static void
pattern_dealloc (PycairoPattern *o) {
  if (o->pattern) {
    cairo_pattern_destroy (o->pattern);
    o->pattern = NULL;
  }
  Py_CLEAR(o->base);

  Py_TYPE(o)->tp_free(o);
}

static PyObject *
pattern_new (PyTypeObject *type, PyObject *args, PyObject *kwds) {
  PyErr_SetString(PyExc_TypeError,
		  "The Pattern type cannot be instantiated");
  return NULL;
}

static PyObject *
pattern_get_extend (PycairoPattern *o, PyObject *ignored) {
  RETURN_INT_ENUM (Extend, cairo_pattern_get_extend (o->pattern));
}

static PyObject *
pattern_get_matrix (PycairoPattern *o, PyObject *ignored) {
  cairo_matrix_t matrix;
  cairo_pattern_get_matrix (o->pattern, &matrix);
  return PycairoMatrix_FromMatrix (&matrix);
}

static PyObject *
pattern_set_extend (PycairoPattern *o, PyObject *args) {
  cairo_extend_t extend;
  int extend_arg;

  if (!PyArg_ParseTuple (args, "i:Pattern.set_extend", &extend_arg))
    return NULL;

  extend = (cairo_extend_t)extend_arg;

  cairo_pattern_set_extend (o->pattern, extend);
  Py_RETURN_NONE;
}

static PyObject *
pattern_set_matrix (PycairoPattern *o, PyObject *args) {
  PycairoMatrix *m;

  if (!PyArg_ParseTuple (args, "O!:Pattern.set_matrix",
			 &PycairoMatrix_Type, &m))
    return NULL;

  cairo_pattern_set_matrix (o->pattern, &m->matrix);
  Py_RETURN_NONE;
}

static PyObject *
pattern_get_filter (PycairoPattern *o, PyObject *ignored) {
  cairo_filter_t filter;

  Py_BEGIN_ALLOW_THREADS;
  filter = cairo_pattern_get_filter (o->pattern);
  Py_END_ALLOW_THREADS;

  RETURN_INT_ENUM (Filter, filter);
}

static PyObject *
pattern_set_filter (PycairoPattern *o, PyObject *args) {
  cairo_filter_t filter;
  int filter_arg;

  if (!PyArg_ParseTuple (args, "i:Pattern.set_filter", &filter_arg))
    return NULL;

  filter = (cairo_filter_t)filter_arg;

  Py_BEGIN_ALLOW_THREADS;
  cairo_pattern_set_filter (o->pattern, filter);
  Py_END_ALLOW_THREADS;

  Py_RETURN_NONE;
}

static PyMethodDef pattern_methods[] = {
  /* methods never exposed in a language binding:
   * cairo_pattern_destroy()
   * cairo_pattern_get_type()
   * cairo_pattern_reference()
   *
   * cairo_pattern_status()
   * - not needed since Pycairo handles status checking
   */
  {"get_extend", (PyCFunction)pattern_get_extend,          METH_NOARGS },
  {"get_matrix", (PyCFunction)pattern_get_matrix,          METH_NOARGS },
  {"set_extend", (PyCFunction)pattern_set_extend,          METH_VARARGS },
  {"set_matrix", (PyCFunction)pattern_set_matrix,          METH_VARARGS },
  {"get_filter", (PyCFunction)pattern_get_filter,          METH_NOARGS },
  {"set_filter", (PyCFunction)pattern_set_filter,          METH_VARARGS },
  {NULL, NULL, 0, NULL},
};

static PyObject*
pattern_richcompare (PyObject *self, PyObject *other, int op)
{
  if (Py_TYPE(self) == Py_TYPE(other))
    return Pycairo_richcompare (
      ((PycairoPattern *)self)->pattern,
      ((PycairoPattern *)other)->pattern,
      op);
  else {
    Py_INCREF(Py_NotImplemented);
    return Py_NotImplemented;
  }
}

static PYCAIRO_Py_hash_t
pattern_hash (PyObject *self)
{
  return PYCAIRO_Py_hash_t_FromVoidPtr (((PycairoPattern *)self)->pattern);
}

PyTypeObject PycairoPattern_Type = {
  PyVarObject_HEAD_INIT(NULL, 0)
  "cairo.Pattern",                    /* tp_name */
  sizeof(PycairoPattern),             /* tp_basicsize */
  0,                                  /* tp_itemsize */
  (destructor)pattern_dealloc,        /* tp_dealloc */
  0,                                  /* tp_print */
  0,                                  /* tp_getattr */
  0,                                  /* tp_setattr */
  0,                                  /* tp_compare */
  0,                                  /* tp_repr */
  0,                                  /* tp_as_number */
  0,                                  /* tp_as_sequence */
  0,                                  /* tp_as_mapping */
  pattern_hash,                       /* tp_hash */
  0,                                  /* tp_call */
  0,                                  /* tp_str */
  0,                                  /* tp_getattro */
  0,                                  /* tp_setattro */
  0,                                  /* tp_as_buffer */
  Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE,/* tp_flags */
  0,                                  /* tp_doc */
  0,                                  /* tp_traverse */
  0,                                  /* tp_clear */
  pattern_richcompare,                /* tp_richcompare */
  0,                                  /* tp_weaklistoffset */
  0,                                  /* tp_iter */
  0,                                  /* tp_iternext */
  pattern_methods,                    /* tp_methods */
  0,                                  /* tp_members */
  0,                                  /* tp_getset */
  0,                                  /* tp_base */
  0,                                  /* tp_dict */
  0,                                  /* tp_descr_get */
  0,                                  /* tp_descr_set */
  0,                                  /* tp_dictoffset */
  0,                                  /* tp_init */
  0,                                  /* tp_alloc */
  (newfunc)pattern_new,               /* tp_new */
  0,                                  /* tp_free */
  0,                                  /* tp_is_gc */
  0,                                  /* tp_bases */
};


/* Class SolidPattern ----------------------------------------------------- */

static PyObject *
solid_pattern_new (PyTypeObject *type, PyObject *args, PyObject *kwds) {
  double r, g, b, a = 1.0;
  if (!PyArg_ParseTuple (args, "ddd|d:SolidPattern.__new__", &r, &g, &b, &a))
    return NULL;
  return PycairoPattern_FromPattern (cairo_pattern_create_rgba (r, g, b, a),
				     NULL);
}

static PyObject *
solid_pattern_get_rgba (PycairoSolidPattern *o, PyObject *ignored) {
  double red, green, blue, alpha;
  cairo_pattern_get_rgba (o->pattern, &red, &green, &blue, &alpha);
  return Py_BuildValue("(dddd)", red, green, blue, alpha);
}

static PyMethodDef solid_pattern_methods[] = {
  {"get_rgba",       (PyCFunction)solid_pattern_get_rgba,      METH_NOARGS },
  {NULL, NULL, 0, NULL},
};

PyTypeObject PycairoSolidPattern_Type = {
  PyVarObject_HEAD_INIT(NULL, 0)
  "cairo.SolidPattern",               /* tp_name */
  sizeof(PycairoSolidPattern),        /* tp_basicsize */
  0,                                  /* tp_itemsize */
  0,                                  /* tp_dealloc */
  0,                                  /* tp_print */
  0,                                  /* tp_getattr */
  0,                                  /* tp_setattr */
  0,                                  /* tp_compare */
  0,                                  /* tp_repr */
  0,                                  /* tp_as_number */
  0,                                  /* tp_as_sequence */
  0,                                  /* tp_as_mapping */
  0,                                  /* tp_hash */
  0,                                  /* tp_call */
  0,                                  /* tp_str */
  0,                                  /* tp_getattro */
  0,                                  /* tp_setattro */
  0,                                  /* tp_as_buffer */
  Py_TPFLAGS_DEFAULT,                 /* tp_flags */
  0,                                  /* tp_doc */
  0,                                  /* tp_traverse */
  0,                                  /* tp_clear */
  0,                                  /* tp_richcompare */
  0,                                  /* tp_weaklistoffset */
  0,                                  /* tp_iter */
  0,                                  /* tp_iternext */
  solid_pattern_methods,              /* tp_methods */
  0,                                  /* tp_members */
  0,                                  /* tp_getset */
  &PycairoPattern_Type,               /* tp_base */
  0,                                  /* tp_dict */
  0,                                  /* tp_descr_get */
  0,                                  /* tp_descr_set */
  0,                                  /* tp_dictoffset */
  0,                                  /* tp_init */
  0,                                  /* tp_alloc */
  (newfunc)solid_pattern_new,         /* tp_new */
  0,                                  /* tp_free */
  0,                                  /* tp_is_gc */
  0,                                  /* tp_bases */
};

/* Class SurfacePattern --------------------------------------------------- */

static PyObject *
surface_pattern_new (PyTypeObject *type, PyObject *args, PyObject *kwds) {
  PycairoSurface *s;
  if (!PyArg_ParseTuple (args, "O!:SurfacePattern.__new__",
			 &PycairoSurface_Type, &s))
    return NULL;
  return PycairoPattern_FromPattern (
	     cairo_pattern_create_for_surface (s->surface), NULL);
}

static PyObject *
surface_pattern_get_surface (PycairoSurfacePattern *o, PyObject *ignored) {
  cairo_surface_t *surface;
  RETURN_NULL_IF_CAIRO_ERROR(cairo_pattern_get_surface (o->pattern, &surface));
  return PycairoSurface_FromSurface(cairo_surface_reference (surface), NULL);
}

static PyMethodDef surface_pattern_methods[] = {
  {"get_surface",   (PyCFunction)surface_pattern_get_surface, METH_NOARGS },
  {NULL, NULL, 0, NULL},
};

PyTypeObject PycairoSurfacePattern_Type = {
  PyVarObject_HEAD_INIT(NULL, 0)
  "cairo.SurfacePattern",             /* tp_name */
  sizeof(PycairoSurfacePattern),      /* tp_basicsize */
  0,                                  /* tp_itemsize */
  0,                                  /* tp_dealloc */
  0,                                  /* tp_print */
  0,                                  /* tp_getattr */
  0,                                  /* tp_setattr */
  0,                                  /* tp_compare */
  0,                                  /* tp_repr */
  0,                                  /* tp_as_number */
  0,                                  /* tp_as_sequence */
  0,                                  /* tp_as_mapping */
  0,                                  /* tp_hash */
  0,                                  /* tp_call */
  0,                                  /* tp_str */
  0,                                  /* tp_getattro */
  0,                                  /* tp_setattro */
  0,                                  /* tp_as_buffer */
  Py_TPFLAGS_DEFAULT,                 /* tp_flags */
  0,                                  /* tp_doc */
  0,                                  /* tp_traverse */
  0,                                  /* tp_clear */
  0,                                  /* tp_richcompare */
  0,                                  /* tp_weaklistoffset */
  0,                                  /* tp_iter */
  0,                                  /* tp_iternext */
  surface_pattern_methods,            /* tp_methods */
  0,                                  /* tp_members */
  0,                                  /* tp_getset */
  &PycairoPattern_Type,               /* tp_base */
  0,                                  /* tp_dict */
  0,                                  /* tp_descr_get */
  0,                                  /* tp_descr_set */
  0,                                  /* tp_dictoffset */
  0,                                  /* tp_init */
  0,                                  /* tp_alloc */
  (newfunc)surface_pattern_new,       /* tp_new */
  0,                                  /* tp_free */
  0,                                  /* tp_is_gc */
  0,                                  /* tp_bases */
};

/* Class Gradient --------------------------------------------------------- */

static PyObject *
gradient_new (PyTypeObject *type, PyObject *args, PyObject *kwds) {
  PyErr_SetString(PyExc_TypeError,
		  "The Gradient type cannot be instantiated");
  return NULL;
}

static PyObject *
gradient_add_color_stop_rgb (PycairoGradient *o, PyObject *args) {
  double offset, red, green, blue;
  if (!PyArg_ParseTuple(args, "dddd:Gradient.add_color_stop_rgb",
			&offset, &red, &green, &blue))
    return NULL;
  cairo_pattern_add_color_stop_rgb (o->pattern, offset, red, green, blue);
  RETURN_NULL_IF_CAIRO_PATTERN_ERROR(o->pattern);
  Py_RETURN_NONE;
}

static PyObject *
gradient_add_color_stop_rgba (PycairoGradient *o, PyObject *args) {
  double offset, red, green, blue, alpha;
  if (!PyArg_ParseTuple(args, "ddddd:Gradient.add_color_stop_rgba",
			&offset, &red, &green, &blue, &alpha))
    return NULL;
  cairo_pattern_add_color_stop_rgba (o->pattern, offset, red,
				     green, blue, alpha);
  RETURN_NULL_IF_CAIRO_PATTERN_ERROR(o->pattern);
  Py_RETURN_NONE;
}

static PyObject *
gradient_get_color_stops_rgba (PycairoGradient *obj, PyObject *ignored) {
  cairo_status_t status;
  double offset, red, green, blue, alpha;
  int count;
  PyObject *list, *tuple;
  int result, i;

  status = cairo_pattern_get_color_stop_count (obj->pattern, &count);
  RETURN_NULL_IF_CAIRO_ERROR (status);

  list = PyList_New (0);
  if (list == NULL)
    return NULL;

  for (i = 0; i < count; i++) {
    status = cairo_pattern_get_color_stop_rgba (
      obj->pattern, i, &offset, &red, &green, &blue, &alpha);
    if (status != CAIRO_STATUS_SUCCESS)
      goto error;

    tuple = Py_BuildValue("(ddddd)", offset, red, green, blue, alpha);
    if (tuple == NULL)
      goto error;

    result = PyList_Append (list, tuple);
    Py_DECREF (tuple);
    if (result == -1)
      goto error;
  }

  return list;
error:
  Py_DECREF (list);
  if (status != CAIRO_STATUS_SUCCESS)
    Pycairo_Check_Status (status);
  return NULL;
}

static PyMethodDef gradient_methods[] = {
  {"add_color_stop_rgb",(PyCFunction)gradient_add_color_stop_rgb,
   METH_VARARGS },
  {"add_color_stop_rgba",(PyCFunction)gradient_add_color_stop_rgba,
   METH_VARARGS },
  {"get_color_stops_rgba",(PyCFunction)gradient_get_color_stops_rgba,
   METH_NOARGS },
  {NULL, NULL, 0, NULL},
};

PyTypeObject PycairoGradient_Type = {
  PyVarObject_HEAD_INIT(NULL, 0)
  "cairo.Gradient",                   /* tp_name */
  sizeof(PycairoGradient),            /* tp_basicsize */
  0,                                  /* tp_itemsize */
  0,                                  /* tp_dealloc */
  0,                                  /* tp_print */
  0,                                  /* tp_getattr */
  0,                                  /* tp_setattr */
  0,                                  /* tp_compare */
  0,                                  /* tp_repr */
  0,                                  /* tp_as_number */
  0,                                  /* tp_as_sequence */
  0,                                  /* tp_as_mapping */
  0,                                  /* tp_hash */
  0,                                  /* tp_call */
  0,                                  /* tp_str */
  0,                                  /* tp_getattro */
  0,                                  /* tp_setattro */
  0,                                  /* tp_as_buffer */
  Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE,/* tp_flags */
  0,                                  /* tp_doc */
  0,                                  /* tp_traverse */
  0,                                  /* tp_clear */
  0,                                  /* tp_richcompare */
  0,                                  /* tp_weaklistoffset */
  0,                                  /* tp_iter */
  0,                                  /* tp_iternext */
  gradient_methods,                   /* tp_methods */
  0,                                  /* tp_members */
  0,                                  /* tp_getset */
  &PycairoPattern_Type,               /* tp_base */
  0,                                  /* tp_dict */
  0,                                  /* tp_descr_get */
  0,                                  /* tp_descr_set */
  0,                                  /* tp_dictoffset */
  0,                                  /* tp_init */
  0,                                  /* tp_alloc */
  (newfunc)gradient_new,              /* tp_new */
  0,                                  /* tp_free */
  0,                                  /* tp_is_gc */
  0,                                  /* tp_bases */
};

/* Class LinearGradient --------------------------------------------------- */

static PyObject *
linear_gradient_new (PyTypeObject *type, PyObject *args, PyObject *kwds) {
  double x0, y0, x1, y1;
  if (!PyArg_ParseTuple(args, "dddd:LinearGradient.__new__",
			&x0, &y0, &x1, &y1))
    return NULL;
  return PycairoPattern_FromPattern (
	       cairo_pattern_create_linear (x0, y0, x1, y1), NULL);
}

static PyObject *
linear_gradient_get_linear_points (PycairoLinearGradient *o, PyObject *ignored) {
  double x0, y0, x1, y1;
  cairo_pattern_get_linear_points (o->pattern, &x0, &y0, &x1, &y1);
  return Py_BuildValue("(dddd)", x0, y0, x1, y1);
}

static PyMethodDef linear_gradient_methods[] = {
  {"get_linear_points", (PyCFunction)linear_gradient_get_linear_points,
   METH_NOARGS },
  {NULL, NULL, 0, NULL},
};

PyTypeObject PycairoLinearGradient_Type = {
  PyVarObject_HEAD_INIT(NULL, 0)
  "cairo.LinearGradient",             /* tp_name */
  sizeof(PycairoLinearGradient),      /* tp_basicsize */
  0,                                  /* tp_itemsize */
  0,                                  /* tp_dealloc */
  0,                                  /* tp_print */
  0,                                  /* tp_getattr */
  0,                                  /* tp_setattr */
  0,                                  /* tp_compare */
  0,                                  /* tp_repr */
  0,                                  /* tp_as_number */
  0,                                  /* tp_as_sequence */
  0,                                  /* tp_as_mapping */
  0,                                  /* tp_hash */
  0,                                  /* tp_call */
  0,                                  /* tp_str */
  0,                                  /* tp_getattro */
  0,                                  /* tp_setattro */
  0,                                  /* tp_as_buffer */
  Py_TPFLAGS_DEFAULT,                 /* tp_flags */
  0,                                  /* tp_doc */
  0,                                  /* tp_traverse */
  0,                                  /* tp_clear */
  0,                                  /* tp_richcompare */
  0,                                  /* tp_weaklistoffset */
  0,                                  /* tp_iter */
  0,                                  /* tp_iternext */
  linear_gradient_methods,            /* tp_methods */
  0,                                  /* tp_members */
  0,                                  /* tp_getset */
  &PycairoGradient_Type,              /* tp_base */
  0,                                  /* tp_dict */
  0,                                  /* tp_descr_get */
  0,                                  /* tp_descr_set */
  0,                                  /* tp_dictoffset */
  0,                                  /* tp_init */
  0,                                  /* tp_alloc */
  (newfunc)linear_gradient_new,       /* tp_new */
  0,                                  /* tp_free */
  0,                                  /* tp_is_gc */
  0,                                  /* tp_bases */
};

/* Class RadialGradient --------------------------------------------------- */

static PyObject *
radial_gradient_new (PyTypeObject *type, PyObject *args, PyObject *kwds) {
  double cx0, cy0, radius0, cx1, cy1, radius1;
  if (!PyArg_ParseTuple(args, "dddddd:RadialGradient.__new__",
			&cx0, &cy0, &radius0, &cx1, &cy1, &radius1))
    return NULL;
  return PycairoPattern_FromPattern (
	    cairo_pattern_create_radial (cx0, cy0, radius0, cx1, cy1, radius1),
	    NULL);
}

static PyObject *
radial_gradient_get_radial_circles (PycairoRadialGradient *o, PyObject *ignored) {
  double x0, y0, r0, x1, y1, r1;
  cairo_pattern_get_radial_circles (o->pattern, &x0, &y0, &r0,
				    &x1, &y1, &r1);
  return Py_BuildValue("(dddddd)", x0, y0, r0, x1, y1, r1);
}

static PyMethodDef radial_gradient_methods[] = {
  {"get_radial_circles", (PyCFunction)radial_gradient_get_radial_circles,
   METH_NOARGS },
  {NULL, NULL, 0, NULL},
};

PyTypeObject PycairoRadialGradient_Type = {
  PyVarObject_HEAD_INIT(NULL, 0)
  "cairo.RadialGradient",             /* tp_name */
  sizeof(PycairoRadialGradient),      /* tp_basicsize */
  0,                                  /* tp_itemsize */
  0,                                  /* tp_dealloc */
  0,                                  /* tp_print */
  0,                                  /* tp_getattr */
  0,                                  /* tp_setattr */
  0,                                  /* tp_compare */
  0,                                  /* tp_repr */
  0,                                  /* tp_as_number */
  0,                                  /* tp_as_sequence */
  0,                                  /* tp_as_mapping */
  0,                                  /* tp_hash */
  0,                                  /* tp_call */
  0,                                  /* tp_str */
  0,                                  /* tp_getattro */
  0,                                  /* tp_setattro */
  0,                                  /* tp_as_buffer */
  Py_TPFLAGS_DEFAULT,                 /* tp_flags */
  0,                                  /* tp_doc */
  0,                                  /* tp_traverse */
  0,                                  /* tp_clear */
  0,                                  /* tp_richcompare */
  0,                                  /* tp_weaklistoffset */
  0,                                  /* tp_iter */
  0,                                  /* tp_iternext */
  radial_gradient_methods,            /* tp_methods */
  0,                                  /* tp_members */
  0,                                  /* tp_getset */
  &PycairoGradient_Type,              /* tp_base */
  0,                                  /* tp_dict */
  0,                                  /* tp_descr_get */
  0,                                  /* tp_descr_set */
  0,                                  /* tp_dictoffset */
  0,                                  /* tp_init */
  0,                                  /* tp_alloc */
  (newfunc)radial_gradient_new,       /* tp_new */
  0,                                  /* tp_free */
  0,                                  /* tp_is_gc */
  0,                                  /* tp_bases */
};

static PyObject *
mesh_pattern_new (PyTypeObject *type, PyObject *args, PyObject *kwds) {
  if (!PyArg_ParseTuple (args, ":Mesh.__new__"))
    return NULL;

  return PycairoPattern_FromPattern (cairo_pattern_create_mesh (), NULL);
}

static PyObject *
mesh_pattern_begin_patch (PycairoMeshPattern *obj, PyObject *ignored) {
  Py_BEGIN_ALLOW_THREADS;
  cairo_mesh_pattern_begin_patch (obj->pattern);
  Py_END_ALLOW_THREADS;

  RETURN_NULL_IF_CAIRO_PATTERN_ERROR (obj->pattern);

  Py_RETURN_NONE;
}

static PyObject *
mesh_pattern_end_patch (PycairoMeshPattern *obj, PyObject *ignored) {
  Py_BEGIN_ALLOW_THREADS;
  cairo_mesh_pattern_end_patch (obj->pattern);
  Py_END_ALLOW_THREADS;

  RETURN_NULL_IF_CAIRO_PATTERN_ERROR (obj->pattern);

  Py_RETURN_NONE;
}

static PyObject *
mesh_pattern_curve_to (PycairoMeshPattern *obj, PyObject *args) {
  double x1, y1, x2, y2, x3, y3;

  if (!PyArg_ParseTuple(args, "dddddd:MeshPattern.curve_to",
      &x1, &y1, &x2, &y2, &x3, &y3))
    return NULL;

  Py_BEGIN_ALLOW_THREADS;
  cairo_mesh_pattern_curve_to (obj->pattern, x1, y1, x2, y2, x3, y3);
  Py_END_ALLOW_THREADS;

  RETURN_NULL_IF_CAIRO_PATTERN_ERROR (obj->pattern);

  Py_RETURN_NONE;
}

static PyObject *
mesh_pattern_get_control_point (PycairoMeshPattern *obj, PyObject *args) {
  double x, y;
  unsigned int patch_num, point_num;
  cairo_status_t status;

  if (!PyArg_ParseTuple(args, "II:MeshPattern.get_control_point",
      &patch_num, &point_num))
    return NULL;

  Py_BEGIN_ALLOW_THREADS;
  status = cairo_mesh_pattern_get_control_point (
    obj->pattern, patch_num, point_num, &x, &y);
  Py_END_ALLOW_THREADS;

  RETURN_NULL_IF_CAIRO_ERROR (status);

  return Py_BuildValue("(dd)", x, y);
}

static PyObject *
mesh_pattern_get_corner_color_rgba (PycairoMeshPattern *obj, PyObject *args) {
  double red, green, blue, alpha;
  unsigned int patch_num, corner_num;
  cairo_status_t status;

  if (!PyArg_ParseTuple(args, "II:MeshPattern.get_corner_color_rgba",
      &patch_num, &corner_num))
    return NULL;

  Py_BEGIN_ALLOW_THREADS;
  status = cairo_mesh_pattern_get_corner_color_rgba (
    obj->pattern, patch_num, corner_num, &red, &green, &blue, &alpha);
  Py_END_ALLOW_THREADS;

  RETURN_NULL_IF_CAIRO_ERROR (status);

  return Py_BuildValue("(dddd)", red, green, blue, alpha);
}

static PyObject *
mesh_pattern_get_patch_count (PycairoMeshPattern *obj, PyObject *ignored) {
  unsigned int count;
  cairo_status_t status;

  Py_BEGIN_ALLOW_THREADS;
  status = cairo_mesh_pattern_get_patch_count (obj->pattern, &count);
  Py_END_ALLOW_THREADS;

  RETURN_NULL_IF_CAIRO_ERROR (status);

  return PYCAIRO_PyLong_FromLong ((long)count);
}

static PyObject *
mesh_pattern_get_path (PycairoMeshPattern *obj, PyObject *args) {
  unsigned int patch_num;
  cairo_path_t *path;

  if (!PyArg_ParseTuple(args, "I:MeshPattern.get_path", &patch_num))
    return NULL;

  Py_BEGIN_ALLOW_THREADS;
  path = cairo_mesh_pattern_get_path (obj->pattern, patch_num);
  Py_END_ALLOW_THREADS;

  return PycairoPath_FromPath (path);
}

static PyObject *
mesh_pattern_line_to (PycairoMeshPattern *obj, PyObject *args) {
  double x, y;

  if (!PyArg_ParseTuple(args, "dd:MeshPattern.line_to", &x, &y))
    return NULL;

  Py_BEGIN_ALLOW_THREADS;
  cairo_mesh_pattern_line_to (obj->pattern, x, y);
  Py_END_ALLOW_THREADS;

  RETURN_NULL_IF_CAIRO_PATTERN_ERROR (obj->pattern);

  Py_RETURN_NONE;
}

static PyObject *
mesh_pattern_move_to (PycairoMeshPattern *obj, PyObject *args) {
  double x, y;

  if (!PyArg_ParseTuple(args, "dd:MeshPattern.move_to", &x, &y))
    return NULL;

  Py_BEGIN_ALLOW_THREADS;
  cairo_mesh_pattern_move_to (obj->pattern, x, y);
  Py_END_ALLOW_THREADS;

  RETURN_NULL_IF_CAIRO_PATTERN_ERROR (obj->pattern);

  Py_RETURN_NONE;
}

static PyObject *
mesh_pattern_set_control_point (PycairoMeshPattern *obj, PyObject *args) {
  double x, y;
  unsigned int point_num;

  if (!PyArg_ParseTuple(args, "Idd:MeshPattern.set_control_point",
      &point_num, &x, &y))
    return NULL;

  Py_BEGIN_ALLOW_THREADS;
  cairo_mesh_pattern_set_control_point (obj->pattern, point_num, x, y);
  Py_END_ALLOW_THREADS;

  RETURN_NULL_IF_CAIRO_PATTERN_ERROR (obj->pattern);

  Py_RETURN_NONE;
}

static PyObject *
mesh_pattern_set_corner_color_rgb (PycairoMeshPattern *obj, PyObject *args) {
  double red, green, blue;
  unsigned int corner_num;

  if (!PyArg_ParseTuple(args, "Iddd:MeshPattern.set_corner_color_rgb",
      &corner_num, &red, &green, &blue))
    return NULL;

  Py_BEGIN_ALLOW_THREADS;
  cairo_mesh_pattern_set_corner_color_rgb (
    obj->pattern, corner_num, red, green, blue);
  Py_END_ALLOW_THREADS;

  RETURN_NULL_IF_CAIRO_PATTERN_ERROR (obj->pattern);

  Py_RETURN_NONE;
}

static PyObject *
mesh_pattern_set_corner_color_rgba (PycairoMeshPattern *obj, PyObject *args) {
  double red, green, blue, alpha;
  unsigned int corner_num;

  if (!PyArg_ParseTuple(args, "Idddd:MeshPattern.set_corner_color_rgba",
      &corner_num, &red, &green, &blue, &alpha))
    return NULL;

  Py_BEGIN_ALLOW_THREADS;
  cairo_mesh_pattern_set_corner_color_rgba (
    obj->pattern, corner_num, red, green, blue, alpha);
  Py_END_ALLOW_THREADS;

  RETURN_NULL_IF_CAIRO_PATTERN_ERROR (obj->pattern);

  Py_RETURN_NONE;
}

static PyMethodDef mesh_pattern_methods[] = {
  {"begin_patch",       (PyCFunction)mesh_pattern_begin_patch, METH_NOARGS},
  {"end_patch",         (PyCFunction)mesh_pattern_end_patch,   METH_NOARGS},
  {"curve_to",          (PyCFunction)mesh_pattern_curve_to,    METH_VARARGS},
  {"get_control_point",
   (PyCFunction)mesh_pattern_get_control_point, METH_VARARGS},
  {"get_corner_color_rgba",
   (PyCFunction)mesh_pattern_get_corner_color_rgba, METH_VARARGS},
  {"get_patch_count",
   (PyCFunction)mesh_pattern_get_patch_count, METH_NOARGS},
  {"get_path",          (PyCFunction)mesh_pattern_get_path,    METH_VARARGS},
  {"line_to",           (PyCFunction)mesh_pattern_line_to,     METH_VARARGS},
  {"move_to",           (PyCFunction)mesh_pattern_move_to,     METH_VARARGS},
  {"set_control_point",
   (PyCFunction)mesh_pattern_set_control_point, METH_VARARGS},
  {"set_corner_color_rgb",
   (PyCFunction)mesh_pattern_set_corner_color_rgb, METH_VARARGS},
  {"set_corner_color_rgba",
   (PyCFunction)mesh_pattern_set_corner_color_rgba, METH_VARARGS},
  {NULL, NULL, 0, NULL},
};

PyTypeObject PycairoMeshPattern_Type = {
  PyVarObject_HEAD_INIT(NULL, 0)
  "cairo.MeshPattern",                /* tp_name */
  sizeof(PycairoMeshPattern),         /* tp_basicsize */
  0,                                  /* tp_itemsize */
  0,                                  /* tp_dealloc */
  0,                                  /* tp_print */
  0,                                  /* tp_getattr */
  0,                                  /* tp_setattr */
  0,                                  /* tp_compare */
  0,                                  /* tp_repr */
  0,                                  /* tp_as_number */
  0,                                  /* tp_as_sequence */
  0,                                  /* tp_as_mapping */
  0,                                  /* tp_hash */
  0,                                  /* tp_call */
  0,                                  /* tp_str */
  0,                                  /* tp_getattro */
  0,                                  /* tp_setattro */
  0,                                  /* tp_as_buffer */
  Py_TPFLAGS_DEFAULT,                 /* tp_flags */
  0,                                  /* tp_doc */
  0,                                  /* tp_traverse */
  0,                                  /* tp_clear */
  0,                                  /* tp_richcompare */
  0,                                  /* tp_weaklistoffset */
  0,                                  /* tp_iter */
  0,                                  /* tp_iternext */
  mesh_pattern_methods,               /* tp_methods */
  0,                                  /* tp_members */
  0,                                  /* tp_getset */
  &PycairoPattern_Type,               /* tp_base */
  0,                                  /* tp_dict */
  0,                                  /* tp_descr_get */
  0,                                  /* tp_descr_set */
  0,                                  /* tp_dictoffset */
  0,                                  /* tp_init */
  0,                                  /* tp_alloc */
  (newfunc)mesh_pattern_new,          /* tp_new */
  0,                                  /* tp_free */
  0,                                  /* tp_is_gc */
  0,                                  /* tp_bases */
};


static PyObject *
raster_source_pattern_new (PyTypeObject *type, PyObject *args, PyObject *kwds) {
  cairo_content_t content;
  int width, height, content_arg;

  if (!PyArg_ParseTuple (args, "iii:RasterSourcePattern.__new__",
                         &content_arg, &width, &height))
    return NULL;

  content = (cairo_content_t)content_arg;

  return PycairoPattern_FromPattern (
    cairo_pattern_create_raster_source (NULL, content, width, height), NULL);
}

static const cairo_user_data_key_t raster_source_acquire_key;
static const cairo_user_data_key_t raster_source_release_key;

static void
_decref_destroy_func (void *user_data) {
  PyGILState_STATE gstate = PyGILState_Ensure ();
  Py_DECREF (user_data);
  PyGILState_Release (gstate);
}

static void
_raster_source_release_func (cairo_pattern_t *pattern,
                             void *callback_data,
                             cairo_surface_t *surface) {
  void *user_data;
  PyObject *pysurface, *result;
  PyGILState_STATE gstate;

  pattern = (cairo_pattern_t *)callback_data;

  user_data = cairo_pattern_get_user_data (
    pattern, &raster_source_release_key);
  /* in case there is a acquire callback but no release one, this gets called
   * anyway so we can free the surface
   */
  if (user_data == NULL) {
    cairo_surface_destroy (surface);
    return;
  }

  gstate = PyGILState_Ensure ();

  pysurface = PycairoSurface_FromSurface (
    cairo_surface_reference (surface), NULL);
  if (pysurface == NULL)
    goto error;

  result = PyObject_CallFunction ((PyObject *)user_data, "(O)", pysurface);
  if (result == NULL)
    goto error;

  if (result != Py_None) {
    Py_DECREF (result);
    PyErr_SetString (PyExc_TypeError,
      "Return value of release callback needs to be None");
    result = NULL;
    goto error;
  }

  Py_DECREF (pysurface);
  PyGILState_Release (gstate);
  cairo_surface_destroy (surface);
  return;
error:
  if (PyErr_Occurred ()) {
    PyErr_Print ();
    PyErr_Clear ();
  }
  Py_XDECREF (pysurface);
  PyGILState_Release (gstate);
  cairo_surface_destroy (surface);
  return;
}

static cairo_surface_t*
_raster_source_acquire_func (cairo_pattern_t *pattern, void *callback_data,
                             cairo_surface_t *target,
                             const cairo_rectangle_int_t *extents) {
  void *user_data;
  PyGILState_STATE gstate;
  PyObject *result;
  PyObject *pysurface = NULL, *pyrect = NULL;
  cairo_surface_t *result_surface;

  /* https://bugs.freedesktop.org/show_bug.cgi?id=101866
   * If something changes the callback data, we are screwed, but not much
   * we can do to detect that..
   */
  pattern = (cairo_pattern_t *)callback_data;

  gstate = PyGILState_Ensure ();

  user_data = cairo_pattern_get_user_data (
    pattern, &raster_source_acquire_key);
  if (user_data == NULL)
    goto error;

  pysurface = PycairoSurface_FromSurface (
    cairo_surface_reference (target), NULL);
  if (pysurface == NULL)
    goto error;

  pyrect = PycairoRectangleInt_FromRectangleInt (extents);
  if (pyrect == NULL)
    goto error;

  result = PyObject_CallFunction (
    (PyObject *)user_data, "(OO)", pysurface, pyrect);

  if (result != NULL) {
    if (!PyObject_TypeCheck (result, &PycairoSurface_Type)) {
      Py_DECREF (result);
      PyErr_SetString (PyExc_TypeError,
        "Return value of acquire callback needs to be of type Surface");
      result = NULL;
    }
  }

  if (result == NULL)
    goto error;

  Py_DECREF (pysurface);
  Py_DECREF (pyrect);
  result_surface = ((PycairoSurface *)result)->surface;
  cairo_surface_reference (result_surface);
  Py_DECREF (result);
  PyGILState_Release (gstate);
  return result_surface;

error:
  if (PyErr_Occurred ()) {
    PyErr_Print ();
    PyErr_Clear ();
  }
  Py_XDECREF (pysurface);
  Py_XDECREF (pyrect);
  PyGILState_Release (gstate);
  return NULL;
}

static PyObject *
raster_source_pattern_set_acquire (PycairoRasterSourcePattern *obj,
                                   PyObject *args) {
  PyObject *acquire_callable, *release_callable;
  cairo_status_t status;
  cairo_pattern_t *pattern;
  void *callback_data;
  cairo_raster_source_acquire_func_t acquire_func;
  cairo_raster_source_release_func_t release_func;
  void *acquire_user_data, *release_user_data;

  if (!PyArg_ParseTuple (args, "OO:RasterSourcePattern.set_acquire",
      &acquire_callable, &release_callable))
    return NULL;

  pattern = obj->pattern;

  callback_data = cairo_raster_source_pattern_get_callback_data (pattern);
  if (callback_data != NULL && callback_data != pattern) {
    PyErr_SetString (PyExc_RuntimeError,
      "Callback is set, but not through Pycairo. Replacing not supported.");
    return NULL;
  }

  if (!PyCallable_Check (acquire_callable) && acquire_callable != Py_None) {
    PyErr_SetString (
      PyExc_TypeError, "argument needs to be a callable or None");
    return NULL;
  }

  if (!PyCallable_Check (release_callable) && release_callable != Py_None) {
    PyErr_SetString (
      PyExc_TypeError, "argument needs to be a callable or None");
    return NULL;
  }

  if (acquire_callable == Py_None) {
    acquire_func = NULL;
    acquire_user_data = NULL;
  } else {
    acquire_func = _raster_source_acquire_func;
    acquire_user_data = acquire_callable;
  }

  if (release_callable == Py_None) {
    release_func = NULL;
    release_user_data = NULL;
  } else {
    release_func = _raster_source_release_func;
    release_user_data = release_callable;
  }

  /* in case acquire is set we have to clean up anyway */
  if (acquire_func != NULL && release_func == NULL) {
    release_func = _raster_source_release_func;
  }

  status = cairo_pattern_set_user_data (
    pattern, &raster_source_acquire_key, acquire_user_data,
    (acquire_user_data) ? _decref_destroy_func : NULL);
  RETURN_NULL_IF_CAIRO_ERROR (status);
  if (acquire_user_data != NULL)
    Py_INCREF (acquire_user_data);

  status = cairo_pattern_set_user_data (
    pattern, &raster_source_release_key, release_user_data,
    (release_user_data) ? _decref_destroy_func : NULL);
  if (status != CAIRO_STATUS_SUCCESS) {
    cairo_pattern_set_user_data (
      pattern, &raster_source_acquire_key, NULL, NULL);
    RETURN_NULL_IF_CAIRO_ERROR (status);
  }
  if (release_user_data != NULL)
    Py_INCREF (release_user_data);

  cairo_raster_source_pattern_set_callback_data (pattern, pattern);

  Py_BEGIN_ALLOW_THREADS;
  cairo_raster_source_pattern_set_acquire (
    pattern, acquire_func, release_func);
  Py_END_ALLOW_THREADS;

  Py_RETURN_NONE;
}

static PyObject *
raster_source_pattern_get_acquire (PycairoRasterSourcePattern *obj, PyObject *ignored) {
  cairo_pattern_t *pattern;
  void *user_data;
  PyObject *acquire_callable, *release_callable;

  pattern = obj->pattern;

  user_data = cairo_pattern_get_user_data (
    pattern, &raster_source_acquire_key);
  if (user_data == NULL) {
    acquire_callable = Py_None;
  } else {
    acquire_callable = user_data;
  }

  user_data = cairo_pattern_get_user_data (
    pattern, &raster_source_release_key);
  if (user_data == NULL) {
    release_callable = Py_None;
  } else {
    release_callable = user_data;
  }

  return Py_BuildValue ("(OO)", acquire_callable, release_callable);
}

static PyMethodDef raster_source_pattern_methods[] = {
  {"set_acquire",
   (PyCFunction)raster_source_pattern_set_acquire, METH_VARARGS},
  {"get_acquire",
   (PyCFunction)raster_source_pattern_get_acquire, METH_NOARGS},
  {NULL, NULL, 0, NULL},
};

PyTypeObject PycairoRasterSourcePattern_Type = {
  PyVarObject_HEAD_INIT(NULL, 0)
  "cairo.RasterSourcePattern",        /* tp_name */
  sizeof(PycairoRasterSourcePattern), /* tp_basicsize */
  0,                                  /* tp_itemsize */
  0,                                  /* tp_dealloc */
  0,                                  /* tp_print */
  0,                                  /* tp_getattr */
  0,                                  /* tp_setattr */
  0,                                  /* tp_compare */
  0,                                  /* tp_repr */
  0,                                  /* tp_as_number */
  0,                                  /* tp_as_sequence */
  0,                                  /* tp_as_mapping */
  0,                                  /* tp_hash */
  0,                                  /* tp_call */
  0,                                  /* tp_str */
  0,                                  /* tp_getattro */
  0,                                  /* tp_setattro */
  0,                                  /* tp_as_buffer */
  Py_TPFLAGS_DEFAULT,                 /* tp_flags */
  0,                                  /* tp_doc */
  0,                                  /* tp_traverse */
  0,                                  /* tp_clear */
  0,                                  /* tp_richcompare */
  0,                                  /* tp_weaklistoffset */
  0,                                  /* tp_iter */
  0,                                  /* tp_iternext */
  raster_source_pattern_methods,      /* tp_methods */
  0,                                  /* tp_members */
  0,                                  /* tp_getset */
  &PycairoPattern_Type,               /* tp_base */
  0,                                  /* tp_dict */
  0,                                  /* tp_descr_get */
  0,                                  /* tp_descr_set */
  0,                                  /* tp_dictoffset */
  0,                                  /* tp_init */
  0,                                  /* tp_alloc */
  (newfunc)raster_source_pattern_new, /* tp_new */
  0,                                  /* tp_free */
  0,                                  /* tp_is_gc */
  0,                                  /* tp_bases */
};
