/*
 * Copyright 2010 Canonical Ltd.
 *
 * This program is free software: you can redistribute it and/or modify it
 * under the terms of either or both of the following licenses:
 *
 * 1) the GNU Lesser General Public License version 3, as published by the
 * Free Software Foundation; and/or
 * 2) the GNU Lesser General Public License version 2.1, as published by
 * the Free Software Foundation.
 *
 * This program is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranties of
 * MERCHANTABILITY, SATISFACTORY QUALITY or FITNESS FOR A PARTICULAR
 * PURPOSE.  See the applicable version of the GNU Lesser General Public
 * License for more details.
 *
 * You should have received a copy of both the GNU Lesser General Public
 * License version 3 and version 2.1 along with this program.  If not, see
 * <http://www.gnu.org/licenses/>
 *
 * Authored by: Mirco Müller <mirco.mueller@canonical.com>
 */
/** 
 * SECTION:ctk-layer
 * @short_description: Helper class for implementing layered actors
 * @see_also: #CtkLayerActor
 * @include ctk-layer.h
 *
 * #CtkLayer is a class holding an image or solid color with an associated, but
 * optional, mask. It cannot render itself but is used for painting in class
 * #CtkLayerActor.
 */
#if HAVE_CONFIG_H
#include <config.h>
#endif

#include <GL/glew.h>
#include <GL/glxew.h>

#include "ctk-gfx-private.h"
#include "ctk-arb-asm-private.h"
#include "ctk-actor.h"
#include "ctk-layer.h"
#include "ctk-private.h"
#include "ctk-actor.h"

G_DEFINE_TYPE (CtkLayer, ctk_layer, G_TYPE_OBJECT);

enum
{
  PROP_0 = 0,
  PROP_LAYER_WIDTH,        // read/write
  PROP_LAYER_HEIGHT,       // read/write
  PROP_LAYER_COLOR,        // read/write
  PROP_LAYER_OPACITY,      // read/write
  PROP_LAYER_IMAGE_PIXBUF, // read/write
  PROP_LAYER_IMAGE_ID,     // read
  PROP_LAYER_IMAGE_REPEAT, // read/write
  PROP_LAYER_MASK_PIXBUF,  // read/write
  PROP_LAYER_MASK_ID,      // read
  PROP_LAYER_MASK_REPEAT,  // read/write
  PROP_LAYER_VALID,        // read
  PROP_LAYER_ENABLED       // read/write
};

#define GET_PRIVATE(o) \
  (G_TYPE_INSTANCE_GET_PRIVATE ((o), CTK_TYPE_LAYER, CtkLayerPrivate))

struct _CtkLayerPrivate
{
  guint              width;
  guint              height;
  ClutterColor       color; // alpha-component acts as opacity
  GdkPixbuf*         image_pixbuf;
  guint              image_id;
  gboolean           is_external_image_id;
  guint              image_width;
  guint              image_height;
  CtkLayerRepeatMode image_repeat;
  GdkPixbuf*         mask_pixbuf;
  guint              mask_id;
  gboolean           is_external_mask_id;
  guint              mask_width;
  guint              mask_height;
  CtkLayerRepeatMode mask_repeat;
  gboolean           valid;
  gboolean           enabled;
};

// GObject internals ///////////////////////////////////////////////////////////

static void
ctk_layer_finalize (GObject* object)
{
  CtkLayerPrivate* priv;

  priv = GET_PRIVATE (object);

  // delete any existing image-texture
  if ((priv->image_id != 0) && (priv->is_external_image_id == FALSE))
    glDeleteTextures (1, &priv->image_id);

  // delete any existing mask-texture
  if ((priv->mask_id != 0) && (priv->is_external_mask_id == FALSE))
    glDeleteTextures (1, &priv->mask_id);

  if (priv->image_pixbuf)
    g_object_unref (priv->image_pixbuf);

  if (priv->mask_pixbuf)
    g_object_unref (priv->mask_pixbuf);
}

static void
ctk_layer_set_property (GObject*      object,
                        guint         prop_id,
                        const GValue* value,
                        GParamSpec*   pspec)
{
  CtkLayer* layer = CTK_LAYER (object);

  switch (prop_id)
  {
    case PROP_LAYER_WIDTH:
      ctk_layer_set_width (layer, g_value_get_uint (value));
    break;

    case PROP_LAYER_HEIGHT:
      ctk_layer_set_height (layer, g_value_get_uint (value));
    break;

    case PROP_LAYER_COLOR:
    {
      ClutterColor* color = NULL;

      if ((color = g_value_get_boxed (value)))
        ctk_layer_set_color (layer, color);
    }
    break;

    case PROP_LAYER_IMAGE_PIXBUF:
      ctk_layer_set_image_pixbuf (layer, g_value_get_object (value));
    break;

    case PROP_LAYER_MASK_PIXBUF:
      ctk_layer_set_mask_pixbuf (layer, g_value_get_object (value));
    break;

    case PROP_LAYER_OPACITY:
      ctk_layer_set_opacity (layer, g_value_get_uint (value));
    break;

    case PROP_LAYER_IMAGE_REPEAT:
      ctk_layer_set_image_repeat_mode (layer, g_value_get_uint (value));
    break;

    case PROP_LAYER_MASK_REPEAT:
      ctk_layer_set_mask_repeat_mode (layer, g_value_get_uint (value));
    break;

    case PROP_LAYER_ENABLED:
      ctk_layer_set_enabled (layer, g_value_get_boolean (value));
    break;

    default:
      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
    break;
  }
}

static void
ctk_layer_get_property (GObject*    object,
                        guint       prop_id,
                        GValue*     value,
                        GParamSpec* pspec)
{
  CtkLayer* layer = CTK_LAYER (object);

  switch (prop_id)
  {
    case PROP_LAYER_WIDTH:
      g_value_set_uint (value, ctk_layer_get_width (layer));
    break;

    case PROP_LAYER_HEIGHT:
      g_value_set_uint (value, ctk_layer_get_height (layer));
    break;

    case PROP_LAYER_COLOR:
    {
      ClutterColor color;
      ctk_layer_get_color (layer, &color);
      g_value_set_boxed (value, &color);
    }
    break;

    case PROP_LAYER_OPACITY:
      g_value_set_uint (value, ctk_layer_get_opacity (layer));
    break;

    case PROP_LAYER_IMAGE_PIXBUF:
      g_value_set_object (value, ctk_layer_get_image_pixbuf (layer));
    break;

    case PROP_LAYER_MASK_PIXBUF:
      g_value_set_object (value, ctk_layer_get_image_pixbuf (layer));
    break;

    case PROP_LAYER_IMAGE_ID:
      g_value_set_uint (value, ctk_layer_get_image_id (layer));
    break;

    case PROP_LAYER_IMAGE_REPEAT:
      g_value_set_uint (value, ctk_layer_get_image_repeat_mode (layer));
    break;

    case PROP_LAYER_MASK_ID:
      g_value_set_uint (value, ctk_layer_get_mask_id (layer));
    break;

    case PROP_LAYER_MASK_REPEAT:
      g_value_set_uint (value, ctk_layer_get_mask_repeat_mode (layer));
    break;

    case PROP_LAYER_VALID:
      g_value_set_boolean (value, ctk_layer_is_valid (layer));
    break;

    case PROP_LAYER_ENABLED:
      g_value_set_boolean (value, ctk_layer_get_enabled (layer));
    break;

    default:
      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
    break;
  }
}

static void
ctk_layer_class_init (CtkLayerClass* klass)
{
  GObjectClass* obj_class = G_OBJECT_CLASS (klass);
  GParamSpec*   pspec;

  obj_class->finalize     = ctk_layer_finalize;
  obj_class->set_property = ctk_layer_set_property;
  obj_class->get_property = ctk_layer_get_property;

  // install properties
  pspec = g_param_spec_uint ("width",
                             "width",
                             "Width of layer in pixels.",
                             0,
                             G_MAXUINT,
                             0,
                             G_PARAM_READWRITE);
  g_object_class_install_property (obj_class, PROP_LAYER_WIDTH, pspec);

  pspec = g_param_spec_uint ("height",
                             "height",
                             "Height of layer in pixels.",
                             0,
                             G_MAXUINT,
                             0,
                             G_PARAM_READWRITE);
  g_object_class_install_property (obj_class, PROP_LAYER_HEIGHT, pspec);

  pspec = g_param_spec_boxed ("color",
                              "Color",
                              "Solid RGBA-color of layer.",
                              CLUTTER_TYPE_COLOR,
                              G_PARAM_READWRITE);
  g_object_class_install_property (obj_class, PROP_LAYER_COLOR, pspec);

  pspec = g_param_spec_uint ("opacity",
                             "opacity",
                             "Opacity of layer (A-component of color).",
                             0,
                             G_MAXUINT,
                             0,
                             G_PARAM_READWRITE);
  g_object_class_install_property (obj_class, PROP_LAYER_OPACITY, pspec);

  pspec = g_param_spec_object ("image-pixbuf",
                               "image-pixbuf",
                               "GdkPixbuf of layer-image.",
                               GDK_TYPE_PIXBUF,
                               G_PARAM_READWRITE);
  g_object_class_install_property (obj_class, PROP_LAYER_IMAGE_PIXBUF, pspec);

  pspec = g_param_spec_object ("mask-pixbuf",
                               "mask-pixbuf",
                               "GdkPixbuf of layer-mask.",
                               GDK_TYPE_PIXBUF,
                               G_PARAM_READWRITE);
  g_object_class_install_property (obj_class, PROP_LAYER_MASK_PIXBUF, pspec);

  pspec = g_param_spec_uint ("image-id",
                             "image-id",
                             "OpenGL ID of layer-image.",
                             0,
                             G_MAXUINT,
                             0,
                             G_PARAM_READABLE);
  g_object_class_install_property (obj_class, PROP_LAYER_IMAGE_ID, pspec);

  pspec = g_param_spec_uint ("image-repeat-mode",
                             "image-repeat-mode",
                             "Repeat-mode of layer-image.",
                             CTK_LAYER_REPEAT_NONE,
                             CTK_LAYER_REPEAT_X | CTK_LAYER_REPEAT_Y,
                             CTK_LAYER_REPEAT_NONE,
                             G_PARAM_READWRITE);
  g_object_class_install_property (obj_class, PROP_LAYER_IMAGE_REPEAT, pspec);

  pspec = g_param_spec_uint ("mask-id",
                             "mask-id",
                             "OpenGL ID of layer-mask.",
                             0,
                             G_MAXUINT,
                             0,
                             G_PARAM_READABLE);
  g_object_class_install_property (obj_class, PROP_LAYER_MASK_ID, pspec);

  pspec = g_param_spec_uint ("mask-repeat-mode",
                             "mask-repeat-mode",
                             "Repeat-mode of layer-mask.",
                             CTK_LAYER_REPEAT_NONE,
                             CTK_LAYER_REPEAT_X | CTK_LAYER_REPEAT_Y,
                             CTK_LAYER_REPEAT_NONE,
                             G_PARAM_READWRITE);
  g_object_class_install_property (obj_class, PROP_LAYER_MASK_REPEAT, pspec);

  pspec = g_param_spec_boolean ("valid",
                                "valid",
                                "State-indicator",
                                FALSE,
                                G_PARAM_READABLE);
  g_object_class_install_property (obj_class, PROP_LAYER_VALID, pspec);

  pspec = g_param_spec_boolean ("enabled",
                                "enabled",
                                "Indicator-flag telling if layer is enabled",
                                TRUE,
                                G_PARAM_READWRITE);
  g_object_class_install_property (obj_class, PROP_LAYER_ENABLED, pspec);

  // install private struct
  g_type_class_add_private (obj_class, sizeof (CtkLayerPrivate));
}

static void
ctk_layer_init (CtkLayer* layer)
{
  CtkLayerPrivate* priv;

  priv = GET_PRIVATE (layer);

  priv->width        = 0;
  priv->height       = 0;
  priv->color.red    = 0;
  priv->color.green  = 0;
  priv->color.blue   = 0;
  priv->color.alpha  = 0;
  priv->image_pixbuf = NULL;
  priv->image_id     = 0;
  priv->is_external_image_id = FALSE;
  priv->image_width  = 0;
  priv->image_height = 0;
  priv->image_repeat = CTK_LAYER_REPEAT_NONE;;
  priv->mask_pixbuf  = NULL;
  priv->mask_id      = 0;
  priv->is_external_mask_id = FALSE;
  priv->mask_width   = 0;
  priv->mask_height  = 0;
  priv->mask_repeat  = CTK_LAYER_REPEAT_NONE;
  priv->valid        = FALSE;
  priv->enabled      = TRUE;
}

// private API /////////////////////////////////////////////////////////////////

void
_upload_texture (guint   tex_id,
                 gint    internal_format,
                 guint   width,
                 guint   height,
                 gint    format,
                 guchar* data)
{
  gint unpack = 0;

  glActiveTextureARB (GL_TEXTURE0);
  glGetIntegerv (GL_UNPACK_ALIGNMENT, &unpack);
  glPixelStorei (GL_UNPACK_ALIGNMENT, 1);
  glPixelStorei (GL_UNPACK_ROW_LENGTH, 0);
  glPixelStorei (GL_UNPACK_IMAGE_HEIGHT, 0);
  glPixelStorei (GL_UNPACK_SKIP_PIXELS, 0);
  glPixelStorei (GL_UNPACK_SKIP_ROWS, 0);

  glBindTexture (GL_TEXTURE_2D, tex_id);
  glTexImage2D (GL_TEXTURE_2D,
                0,
                internal_format,
                width,
                height,
                0,
                format,
                GL_UNSIGNED_BYTE,
                data);

  glPixelStorei (GL_UNPACK_ALIGNMENT, unpack);
  glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
  glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
  glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
  glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
}

guint
_set_from_file (gchar* filename,
                guint  layer_width,
                guint  layer_height)
{
  GdkPixbuf* pixbuf         = NULL;
  GdkPixbuf* flipped_pixbuf = NULL;
  GError*    error          = NULL;
  guint      width          = 0;
  guint      height         = 0;
  gint       channels       = 0;
  guchar*    pixels         = NULL;
  guint      tex_id         = 0;

  // sanity checks
  if (!filename)
    return 0;

  pixbuf = gdk_pixbuf_new_from_file (filename, &error);
  if (error)
  {
    g_error_free (error);

    if (pixbuf)
      g_object_unref (pixbuf);

    return 0;
  }

  // limit checks
  width = gdk_pixbuf_get_width (pixbuf);
  height = gdk_pixbuf_get_height (pixbuf);
  if (layer_width < width || layer_height < height)
  {
    g_object_unref (pixbuf);

    return 0;
  }

  channels = gdk_pixbuf_get_n_channels (pixbuf);
  if (channels != 3 && channels != 4)
  {
    g_object_unref (pixbuf);

    return 0;
  }

  // flip pixbuf vertically so we don't have to deal with this in GL
  flipped_pixbuf = gdk_pixbuf_flip (pixbuf, FALSE);

  pixels = gdk_pixbuf_get_pixels (flipped_pixbuf);
  if (!pixels)
  {
    g_object_unref (pixbuf);
    g_object_unref (flipped_pixbuf);

    return 0;
  }

  // create new texture-id
  glGenTextures (1, &tex_id);

  // upload pixel-data
  _upload_texture (tex_id,
                   channels,
                   width,
                   height,
                   (channels == 4) ? GL_RGBA : GL_RGB,
                   pixels);

  g_object_unref (pixbuf);
  g_object_unref (flipped_pixbuf);

  return tex_id;
}

guint
_set_from_pixbuf (GdkPixbuf* pixbuf,
                  guint      layer_width,
                  guint      layer_height)
{
  guint      width          = 0;
  guint      height         = 0;
  gint       channels       = 0;
  guchar*    pixels         = NULL;
  guint      tex_id         = 0;
  GdkPixbuf* flipped_pixbuf = NULL;

  // sanity checks
  if (!pixbuf)
    return 0;

  // limit checks
  width = gdk_pixbuf_get_width (pixbuf);
  height = gdk_pixbuf_get_height (pixbuf);
  if (layer_width < width || layer_height < height)
    return 0;

  channels = gdk_pixbuf_get_n_channels (pixbuf);
  if (channels != 3 && channels != 4)
    return 0;

  // flip pixbuf vertically so we don't have to deal with this in GL
  flipped_pixbuf = gdk_pixbuf_flip (pixbuf, FALSE);

  pixels = gdk_pixbuf_get_pixels (flipped_pixbuf);
  if (!pixels)
  {
    g_object_unref (flipped_pixbuf);

    return 0;
  }

  // create new texture-id
  glGenTextures (1, &tex_id);

  // upload pixbuf-data
  _upload_texture (tex_id,
                   channels,
                   width,
                   height,
                   (channels == 4) ? GL_RGBA : GL_RGB,
                   pixels);

  g_object_unref (flipped_pixbuf);

  return tex_id;
}

guint
_set_from_surface (cairo_surface_t* surface,
                   guint            layer_width,
                   guint            layer_height)
{
  guint          width          = 0;
  guint          height         = 0;
  cairo_format_t format;
  guchar*        data           = NULL;
  guint          tex_id         = 0;
  GdkPixbuf*     pixbuf         = NULL;
  GdkPixbuf*     flipped_pixbuf = NULL;
  guchar*        pixels         = NULL;
  gint           stride         = 0;

  // sanity checks
  if (!surface)
    return 0;

  if (cairo_surface_status (surface) != CAIRO_STATUS_SUCCESS)
    return 0;

  // limit checks
  if (cairo_surface_get_type (surface) != CAIRO_SURFACE_TYPE_IMAGE)
    return 0;

  width  = (guint) cairo_image_surface_get_width (surface);
  height = (guint) cairo_image_surface_get_height (surface);
  if (layer_width < width || layer_height < height)
    return 0;

  format = cairo_image_surface_get_format (surface);
  if (format != CAIRO_FORMAT_ARGB32 && format != CAIRO_FORMAT_RGB24)
    return 0;

  data = (guchar*) cairo_image_surface_get_data (surface);
  if (!data)
    return 0;

  stride = cairo_image_surface_get_stride (surface);

  // generate a pixbuf from the cairo-surface
  pixbuf = gdk_pixbuf_new_from_data (data,
                                     GDK_COLORSPACE_RGB,
                                     (format == CAIRO_FORMAT_ARGB32) ? TRUE : FALSE,
                                     8,
                                     width,
                                     height,
                                     stride,
                                     NULL,
                                     NULL);

  // flip pixbuf vertically so we don't have to deal with this in GL
  flipped_pixbuf = gdk_pixbuf_flip (pixbuf, FALSE);

  pixels = gdk_pixbuf_get_pixels (flipped_pixbuf);

  // create new texture-id
  glGenTextures (1, &tex_id);

  // upload surface-data
  _upload_texture (tex_id,
                   (format == CAIRO_FORMAT_ARGB32) ? 4 : 3,
                   width,
                   height,
                   (format == CAIRO_FORMAT_ARGB32) ? GL_RGBA : GL_RGB,
                   pixels);

  g_object_unref (pixbuf);
  g_object_unref (flipped_pixbuf);

  return tex_id;
}

void
_revalidate (CtkLayer* self)
{
  CtkLayerPrivate* priv = GET_PRIVATE (self);

  if ((priv->mask_width == priv->image_width) &&
      (priv->mask_height == priv->image_height))
    priv->valid = TRUE;
  else
    priv->valid = FALSE;
}

// public API //////////////////////////////////////////////////////////////////

/** 
 * ctk_layer_new:
 * @width: a #guint
 * @height: a #guint
 * @image_repeat: a #CtkLayerRepeatMode
 * @mask_repeat: a #CtkLayerRepeatMode
 *
 * Creates a new #CtkLayer with width @width and height @height. @image_repeat
 * and @mask_repeat need to be passed in too, but are not taking into account
 * yet. Only #CTK_LAYER_REPEAT_NONE is used for both at the moment.
 *
 * Returns: a #CtkLayer or NULL in an error-case
 */
CtkLayer*
ctk_layer_new (guint              width,
               guint              height,
               CtkLayerRepeatMode image_repeat,
               CtkLayerRepeatMode mask_repeat)
{
  return g_object_new (CTK_TYPE_LAYER,
                       "width", width,
                       "height", height,
                       "image-repeat-mode", image_repeat,
                       "mask-repeat-mode", mask_repeat,
                       NULL);
}

/** 
 * ctk_layer_get_width:
 * @self: a #CtkLayer
 *
 * Get the width of @self.
 *
 * Returns: a #guint
 */
guint
ctk_layer_get_width (CtkLayer* self)
{
  // sanity check
  if (!CTK_IS_LAYER (self))
    return 0;

  return GET_PRIVATE (self)->width;
}

/** 
 * ctk_layer_set_width:
 * @self: a #CtkLayer
 * @width: a #guint
 *
 * Set the width of @self. If @width is set after a previously set image or mask
 * and does not match their respective width, @self will be marked as invalid
 * and thus will not be rendered, if added to a #CtkLayerActor.
 */
void
ctk_layer_set_width (CtkLayer* self,
                     guint     width)
{
  CtkLayerPrivate* priv = NULL;

  // sanity check
  if (!CTK_IS_LAYER (self))
    return;

  priv = GET_PRIVATE (self);

  if (priv->width != width)
  {
    priv->width = width;
    _revalidate (self);
  }
}

/** 
 * ctk_layer_get_height:
 * @self: a #CtkLayer
 *
 * Get the height of @self.
 *
 * Returns: a #guint
 */
guint
ctk_layer_get_height (CtkLayer* self)
{
  // sanity check
  if (!CTK_IS_LAYER (self))
    return 0;

  return GET_PRIVATE (self)->height;
}

/** 
 * ctk_layer_set_height:
 * @self: a #CtkLayer
 * @height: a #guint
 *
 * Set the height of @self. If @height is set after a previously set image or
 * mask and does not match their respective height, @self will be marked as
 * invalid and thus will not be rendered, if added to a #CtkLayerActor.
 */
void
ctk_layer_set_height (CtkLayer* self,
                      guint     height)
{
  CtkLayerPrivate* priv = NULL;

  // sanity check
  if (!CTK_IS_LAYER (self))
    return;

  priv = GET_PRIVATE (self);

  if (priv->height != height)
  {
    priv->height = height;
    _revalidate (self);
  }
}

/** 
 * ctk_layer_get_color:
 * @self: a #CtkLayer
 * @color: a #ClutterColor
 *
 * Copy the color of @self to the #ClutterColor pointed to by @color. If you
 * pass a NULL-pointer for @color nothing happens.
 *
 * If you only need to get the opacity use #ctk_layer_get_opacity() instead.
 */
void
ctk_layer_get_color (CtkLayer*     self,
                     ClutterColor* color)
{
  CtkLayerPrivate* priv = NULL;

  // sanity check
  if (!CTK_IS_LAYER (self) || !color)
    return;

  priv = GET_PRIVATE (self);

  color->red   = priv->color.red;
  color->green = priv->color.green;
  color->blue  = priv->color.blue;
  color->alpha = priv->color.alpha;
}

/** 
 * ctk_layer_set_color:
 * @self: a #CtkLayer
 * @color: a #ClutterColor
 *
 * Set the red-, green-, blue- and alpha-component of @self. If @color or @self
 * is a NULL-pointer nothing happens.
 *
 * @color is used if @self only consists of a mask but no image.
 *
 * If you only need to set the opacity use #ctk_layer_set_opacity() instead.
 */
void
ctk_layer_set_color (CtkLayer*     self,
                     ClutterColor* color)
{
  CtkLayerPrivate* priv = NULL;

  // sanity check
  if (!CTK_IS_LAYER (self) || !color)
    return;

  priv = GET_PRIVATE (self);

  priv->color.red   = color->red;
  priv->color.green = color->green;
  priv->color.blue  = color->blue;
  priv->color.alpha = color->alpha;
}

/** 
 * ctk_layer_get_opacity:
 * @self: a #CtkLayer
 *
 * Grab the opacity- or alpha-component of @self. If you pass in a NULL-pointer
 * for @self this will return 0.
 *
 * Returns: a #guint8
 */
guint8
ctk_layer_get_opacity (CtkLayer* self)
{
  // sanity check
  if (!CTK_IS_LAYER (self))
    return 0;

  return GET_PRIVATE (self)->color.alpha;
}

/** 
 * ctk_layer_set_opacity:
 * @self: a #CtkLayer
 * @opacity: a #guint8
 *
 * Set the alpha- or opacity-component of @self. This is mostly used with layers
 * which have an image and mask defined.
 *
 * If you pass in a NULL-pointer for @self this call does nothing.
 */
void
ctk_layer_set_opacity (CtkLayer* self,
                       guint8    opacity)
{
  // sanity check
  if (!CTK_IS_LAYER (self))
    return;

  GET_PRIVATE (self)->color.alpha = opacity;
}

/** 
 * ctk_layer_set_image_pixbuf:
 * @self: a #CtkLayer
 * @pixbuf: a pointer to a #GdkPixbuf
 *
 * Set the image's pixbuf of @self directly. This will reference @pixbuf.
 */
void
ctk_layer_set_image_pixbuf (CtkLayer*  self,
                            GdkPixbuf* pixbuf)
{
  // sanity check
  if (!CTK_IS_LAYER (self))
    return;

  GET_PRIVATE (self)->image_pixbuf = g_object_ref (pixbuf);
}

/** 
 * ctk_layer_get_image_pixbuf:
 * @self: a #CtkLayer
 *
 * Get the pixbuf of @self's image, if one is set.
 *
 * Returns: a pointer to a #GdkPixbuf
 */
GdkPixbuf*
ctk_layer_get_image_pixbuf (CtkLayer* self)
{
  // sanity check
  if (!CTK_IS_LAYER (self))
    return NULL;

  return GET_PRIVATE (self)->image_pixbuf;
}

/** 
 * ctk_layer_set_mask_pixbuf:
 * @self: a #CtkLayer
 * @pixbuf: a pointer to a #GdkPixbuf
 *
 * Set the mask's pixbuf of @self directly. This will reference @pixbuf.
 */
void
ctk_layer_set_mask_pixbuf (CtkLayer*  self,
                           GdkPixbuf* pixbuf)
{
  // sanity check
  if (!CTK_IS_LAYER (self))
    return;

  GET_PRIVATE (self)->mask_pixbuf = g_object_ref (pixbuf);
}

/** 
 * ctk_layer_get_mask_pixbuf:
 * @self: a #CtkLayer
 *
 * Get the pixbuf of @self's mask, if one is set.
 *
 * Returns: a pointer to a #GtkPixbuf
 */
GdkPixbuf*
ctk_layer_get_mask_pixbuf (CtkLayer* self)
{
  // sanity check
  if (!CTK_IS_LAYER (self))
    return NULL;

  return GET_PRIVATE (self)->mask_pixbuf;
}

/** 
 * ctk_layer_set_image_from_file:
 * @self: a #CtkLayer
 * @filename: a pointer to a #gchar
 *
 * Set the image of @self by loading the image from @filename. Any pre-existing
 * image will be replaced. If something during loading @filename goes wrong
 * @self will be marked as invalid.
 */
void
ctk_layer_set_image_from_file (CtkLayer* self,
                               gchar*    filename)
{
  CtkLayerPrivate* priv = NULL;

  // sanity checks
  if (!CTK_IS_LAYER (self) || !filename)
    return;

  priv = GET_PRIVATE (self);

  // delete any prexisting image-texture
  if (priv->image_id != 0)
    glDeleteTextures (1, &priv->image_id);

  // create new texture-id for image and upload pixel-data
  priv->image_id = _set_from_file (filename, priv->width, priv->height);
  if (priv->image_id == 0)
    priv->valid = FALSE;
}

/** 
 * ctk_layer_set_image_from_pixbuf:
 * @self: a #CtkLayer
 * @pixbuf: a #GdkPixbuf
 *
 * Set the image of @self by grabbing the pixel-data from @pixbuf.
 *
 * After this call you can free and delete @pixbuf. No copy or reference of it
 * is kept.
 */
void
ctk_layer_set_image_from_pixbuf (CtkLayer*  self,
                                 GdkPixbuf* pixbuf)
{
  CtkLayerPrivate* priv = NULL;

  // sanity checks
  if (!CTK_IS_LAYER (self) || !pixbuf)
    return;

  priv = GET_PRIVATE (self);

  // delete any prexisting image-texture
  if (priv->image_id != 0)
    glDeleteTextures (1, &priv->image_id);

  // create new texture-id for image and upload pixbuf-data
  priv->image_id = _set_from_pixbuf (pixbuf, priv->width, priv->height);
  if (priv->image_id == 0)
	priv->valid = FALSE;
}

/** 
 * ctk_layer_set_image_from_surface:
 * @self: a #CtkLayer
 * @surface: a #cairo_surface_t*
 *
 * Set the image of @self by grabbing the pixel-data from @surface. @surface
 * needs to be a cairo image-surface. Any pre-existing image will be replaced.
 *
 * After this call you can free and delete @surface. No copy or reference of it
 * is kept.
 */
void
ctk_layer_set_image_from_surface (CtkLayer*        self,
                                  cairo_surface_t* surface)
{
  CtkLayerPrivate* priv = NULL;

  // sanity checks
  if (!CTK_IS_LAYER (self) || !surface)
    return;

  priv = GET_PRIVATE (self);

  // delete any prexisting image-texture
  if (priv->image_id != 0)
    glDeleteTextures (1, &priv->image_id);

  // create new texture-id for image and upload surface-data
  priv->image_id = _set_from_surface (surface, priv->width, priv->height);
  if (priv->image_id == 0)
    priv->valid = FALSE;
}

/** 
 * ctk_layer_set_mask_from_file:
 * @self: a #CtkLayer
 * @filename: a pointer to a #gchar
 *
 * Set the mask of @self by loading the image from @filename. Any pre-existing
 * mask will be replaced. If something during loading @filename goes wrong @self
 * will be marked as invalid.
 */
void
ctk_layer_set_mask_from_file (CtkLayer* self,
                              gchar*    filename)
{
  CtkLayerPrivate* priv = NULL;

  // sanity checks
  if (!CTK_IS_LAYER (self) || !filename)
    return;

  priv = GET_PRIVATE (self);

  // delete any prexisting mask-texture
  if (priv->mask_id != 0)
    glDeleteTextures (1, &priv->mask_id);

  // create new texture-id for mask and upload pixel-data
  priv->mask_id = _set_from_file (filename, priv->width, priv->height);
  if (priv->mask_id == 0)
    priv->valid = FALSE;
}

/** 
 * ctk_layer_set_mask_from_pixbuf:
 * @self: a #CtkLayer
 * @pixbuf: a #GdkPixbuf
 *
 * Sets @self's mask from a #GdkPixbuf @pixbuf. Any pre-existing mask will be
 * removed by this call. Remember that the passed in @surface needs to be of the
 * same width and height as @self. If @surface is not an image-surface this call
 * has no effect.
 *
 * After this call you can free @pixbuf. It is not referenced or copied. A
 * texture is created and uploaded to the GPU.
 */
void
ctk_layer_set_mask_from_pixbuf (CtkLayer*  self,
                                GdkPixbuf* pixbuf)
{
  CtkLayerPrivate* priv = NULL;

  // sanity checks
  if (!CTK_IS_LAYER (self) || !pixbuf)
    return;

  priv = GET_PRIVATE (self);

  // delete any prexisting mask-texture
  if (priv->mask_id != 0)
    glDeleteTextures (1, &priv->mask_id);

  // create new texture-id for mask and upload pixbuf-data
  priv->mask_id = _set_from_pixbuf (pixbuf, priv->width, priv->height);
  if (priv->mask_id == 0)
    priv->valid = FALSE;
}

/** 
 * ctk_layer_set_mask_from_surface:
 * @self: a #CtkLayer
 * @surface: a #cairo_surface_t
 *
 * Sets @self's mask from a cairo image-surface @surface. Any pre-existing mask
 * will be removed by this call. Remember that the passed in @surface needs to
 * be of the same width and height as @self. If @surface is not an image-surface
 * this call has no effect.
 *
 * After this call you can free @surface. It is not referenced or copied. A
 * texture is created and uploaded to the GPU.
 */
void
ctk_layer_set_mask_from_surface (CtkLayer*        self,
                                 cairo_surface_t* surface)
{
  CtkLayerPrivate* priv = NULL;

  // sanity checks
  if (!CTK_IS_LAYER (self) || !surface)
    return;

  priv = GET_PRIVATE (self);

  // delete any prexisting mask-texture
  if (priv->mask_id != 0)
    glDeleteTextures (1, &priv->mask_id);

  // create new texture-id for mask and upload surface-data
  priv->mask_id = _set_from_surface (surface, priv->width, priv->height);
  if (priv->mask_id == 0)
    priv->valid = FALSE;
}

/** 
 * ctk_layer_set_image_from_id:
 * @self: a #CtkLayer
 * @id: a #guint
 *
 * Sets @self's image from an OpenGL-texture ID @id. Any pre-existing image will
 * be removed by this call. Remember that the passed in @id needs to belong to
 * a texture with the same width and height as @self has.
 *
 * The texture pointed to by @id is NOT cloned. Don't delete after this call!
 */
void
ctk_layer_set_image_from_id (CtkLayer* self,  guint id)
{
  CtkLayerPrivate* priv = NULL;

  // sanity checks
  if (!CTK_IS_LAYER (self) || !id)
    return;

  priv = GET_PRIVATE (self);

  // delete any prexisting image-texture
  if (priv->image_id != 0)
    glDeleteTextures (1, &priv->image_id);

  priv->image_id =id;
  if (priv->image_id == 0)
    priv->valid = FALSE;
  priv->is_external_image_id = TRUE;
}

/** 
 * ctk_layer_set_mask_from_id:
 * @self: a #CtkLayer
 * @id: a #guint
 *
 * Sets @self's mask from an OpenGL-texture ID @id. Any pre-existing mask will
 * be removed by this call. Remember that the passed in @id needs to belong to
 * a texture with the same width and height as @self has.
 *
 * The texture pointed to by @id is NOT cloned. Don't delete after this call!
 */
void
ctk_layer_set_mask_from_id (CtkLayer* self,  guint id)
{
  CtkLayerPrivate* priv = NULL;

  // sanity checks
  if (!CTK_IS_LAYER (self) || !id)
    return;

  priv = GET_PRIVATE (self);

  // delete any prexisting mask-texture
  if (priv->mask_id != 0)
    glDeleteTextures (1, &priv->mask_id);

  priv->mask_id = id;
  if (priv->mask_id == 0)
    priv->valid = FALSE;
  priv->is_external_mask_id = TRUE;
}

/** 
 * ctk_layer_get_image_id:
 * @self: a #CtkLayer
 *
 * Gets the OpenGL-texture ID of @self's image.
 *
 * Returns: a #guint
 */
guint
ctk_layer_get_image_id (CtkLayer* self)
{
  // sanity check
  if (!CTK_IS_LAYER (self))
    return 0;

  return GET_PRIVATE (self)->image_id;
}

/** 
 * ctk_layer_get_mask_id:
 * @self: a #CtkLayer
 *
 * Gets the OpenGL-texture ID of @self's mask.
 *
 * Returns: a #guint
 */
guint
ctk_layer_get_mask_id (CtkLayer* self)
{
  // sanity check
  if (!CTK_IS_LAYER (self))
    return 0;

  return GET_PRIVATE (self)->mask_id;
}

/** 
 * ctk_layer_set_image_repeat_mode:
 * @self: a #CtkLayer
 * @repeat: a #CtkLayerRepeatMode
 *
 * Set the repeat-mode of @self's image. This can be set, but is not taken into
 * account yet. Sofar only CTK_LAYER_REPEAT_NONE is always applied no matter
 * what you set here.
 */
void
ctk_layer_set_image_repeat_mode (CtkLayer*          self,
                                 CtkLayerRepeatMode repeat)
{
  // sanity checks
  if (!CTK_IS_LAYER (self))
    return;

  if (repeat < CTK_LAYER_REPEAT_NONE ||
      repeat > (CTK_LAYER_REPEAT_X | CTK_LAYER_REPEAT_Y))
    return;

  GET_PRIVATE (self)->image_repeat = repeat;
}

/** 
 * ctk_layer_get_image_repeat_mode:
 * @self: a #CtkLayer
 *
 * Get the of repeat-mode of @self's image.
 *
 * Returns: a #CtkLayerRepeatMode
 */
CtkLayerRepeatMode
ctk_layer_get_image_repeat_mode (CtkLayer* self)
{
  // sanity check
  if (!CTK_IS_LAYER (self))
    return 0;

  return GET_PRIVATE (self)->image_repeat;
}

/** 
 * ctk_layer_set_mask_repeat_mode:
 * @self: a #CtkLayer
 * @repeat: a #CtkLayerRepeatMode
 *
 * Set the repeat-mode of @self's mask. This can be set, but is not taken into
 * account yet. Sofar only CTK_LAYER_REPEAT_NONE is always applied no matter
 * what you set here.
 */
void
ctk_layer_set_mask_repeat_mode (CtkLayer*          self,
                                CtkLayerRepeatMode repeat)
{
  // sanity checks
  if (!CTK_IS_LAYER (self))
    return;

  if (repeat < CTK_LAYER_REPEAT_NONE ||
      repeat > (CTK_LAYER_REPEAT_X | CTK_LAYER_REPEAT_Y))
    return;

  GET_PRIVATE (self)->mask_repeat = repeat;
}

/** 
 * ctk_layer_get_mask_repeat_mode:
 * @self: a #CtkLayer
 *
 * Get the of repeat-mode of @self's mask.
 *
 * Returns: a #CtkLayerRepeatMode
 */
CtkLayerRepeatMode
ctk_layer_get_mask_repeat_mode (CtkLayer* self)
{
  // sanity check
  if (!CTK_IS_LAYER (self))
    return 0;

  return GET_PRIVATE (self)->mask_repeat;
}

/** 
 * ctk_layer_is_valid:
 * @self: a #CtkLayer
 *
 * Get the validity-state of @self. A #CtkLayer may be invalid if image and mask
 * provided for it are not of the same size.
 *
 * Returns: a #gboolean, TRUE if valid or FALSE if not or an error occured
 */
gboolean
ctk_layer_is_valid (CtkLayer* self)
{
  // sanity check
  if (!CTK_IS_LAYER (self))
    return 0;

  return GET_PRIVATE (self)->valid;
}

/** 
 * ctk_layer_get_enabled:
 * @self: a #CtkLayer
 *
 * Get the enabled-state of @self.
 *
 * Returns: a #gboolean, TRUE if enabled or FALSE if not or an error occured
 */
gboolean
ctk_layer_get_enabled (CtkLayer* self)
{
  // sanity check
  if (!CTK_IS_LAYER (self))
    return FALSE;

  return GET_PRIVATE (self)->enabled;
}

/** 
 * ctk_layer_set_enable:
 * @self: a #CtkLayer
 * @enabled: a #gboolean
 *
 * Enable (TRUE) or disable (FALSE) @self to control if it will be painted or
 * not, when being added to a #CtkLayerActor with #ctk_layer_actor_add_layer().
 * The enabled-state also controls if @self gets taken into account, if a
 * #CtkLayerActor, which @self might belong to, is flattened using
 * #ctk_layer_actor_is_flattened().
 */
void
ctk_layer_set_enabled (CtkLayer* self,
                       gboolean  enabled)
{
  // sanity checks
  if (!CTK_IS_LAYER (self))
    return;

  GET_PRIVATE (self)->enabled = enabled;
}
