/*
 * Copyright 2009 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-effect-drop-shadow
 * @short_description: A drop-shadow effect.
 * @include: ctk-effect-drop-shadow.h
 *
 * #CtkEffectDropShadow is an implementation of #CtkEffect that causes the
 * #CtkActor it's attached to, to be drawn with a drop-shadow.
 */

#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-effect-context.h"
#include "ctk-effect-drop-shadow.h"
#include "ctk-private.h"

G_DEFINE_TYPE (CtkEffectDropShadow, ctk_effect_drop_shadow, CTK_TYPE_EFFECT);

enum
{
  PROP_0,
  PROP_SIZE,
  PROP_OFFSET_X,
  PROP_OFFSET_Y
};

#define GET_PRIVATE(o) \
  (G_TYPE_INSTANCE_GET_PRIVATE ((o), CTK_TYPE_EFFECT_DROP_SHADOW, CtkEffectDropShadowPrivate))

struct _CtkEffectDropShadowPrivate {
  gfloat blur_factor;
  gint  offset_x;
  gint  offset_y;
  
  /* opengl texture id use to cache the effect. The texture is updated when the actor is damaged */
  guint cached_effect_texture;
  gboolean cached_init;
};

/* Globals */

/* Forwards */
static void ctk_effect_drop_shadow_paint (CtkEffect          *effect,
                                          CtkEffectPaintFunc  func,
                                          gboolean            is_last_effect);
static void ctk_effect_drop_shadow_paint_dummy (CtkEffect          *effect,
                                                CtkEffectPaintFunc  func,
                                                gboolean            is_last_effect);
static void ctk_effect_drop_shadow_set_property (GObject*      gobject,
                                                 guint         prop,
                                                 const GValue* value,
                                                 GParamSpec*   spec);
static void ctk_effect_drop_shadow_get_property (GObject*    gobject,
                                                 guint       prop,
                                                 GValue*     value,
                                                 GParamSpec* spec);

/*
 * GObject stuff
 */
static void
ctk_effect_drop_shadow_finalize (GObject *object)
{
  CtkEffectDropShadowPrivate *priv = GET_PRIVATE (CTK_EFFECT_DROP_SHADOW (object));
  g_return_if_fail (priv);

  if (priv->cached_effect_texture)
    {
      CHECKGL (glDeleteTextures (1, &priv->cached_effect_texture));
    }
}

static void
ctk_effect_drop_shadow_class_init (CtkEffectDropShadowClass *klass)
{
  CtkEffectClass* eff_class = CTK_EFFECT_CLASS (klass);
  GObjectClass*   obj_class = G_OBJECT_CLASS (klass);
  GParamSpec*     prop_size;
  GParamSpec*     prop_offset_x;
  GParamSpec*     prop_offset_y;

  g_type_class_add_private (klass, sizeof (CtkEffectDropShadowPrivate));

  if (ctk_has_capability (CTK_CAPABILITY_VERTEX_PROGRAM)
      && ctk_has_capability (CTK_CAPABILITY_FRAGMENT_PROGRAM)
      && ctk_asm_shaders_compiled_and_ready ())
    eff_class->paint = ctk_effect_drop_shadow_paint;
  else
    {
      if (!ctk_has_capability (CTK_CAPABILITY_FBO) || !ctk_has_capability (CTK_CAPABILITY_VERTEX_PROGRAM) || !ctk_has_capability (CTK_CAPABILITY_FRAGMENT_PROGRAM))
        {
          g_message ("WARNING: CtkEffectDropShadow cannot work without FBO and assembly shader program capabiltities");
        }
      else
        {
          if (!ctk_asm_shaders_compiled_and_ready ())
            g_message ("WARNING: Shaders programs are not ready");
        }
      eff_class->paint = ctk_effect_drop_shadow_paint_dummy;
    }

  obj_class->finalize     = ctk_effect_drop_shadow_finalize;
  obj_class->set_property = ctk_effect_drop_shadow_set_property;
  obj_class->get_property = ctk_effect_drop_shadow_get_property;

  prop_size = g_param_spec_float ("blur_factor",
                                 "Size",
                                 "Size/radius of drop-shadow in pixels",
                                 CTK_EFFECT_DROP_SHADOW_MIN_SIZE,
                                 CTK_EFFECT_DROP_SHADOW_MAX_SIZE,
                                 CTK_EFFECT_DROP_SHADOW_DEFAULT_SIZE,
                                 G_PARAM_READWRITE);
  g_object_class_install_property (obj_class, PROP_SIZE, prop_size);

  prop_offset_x = g_param_spec_int ("offset-x",
                                    "offset-x",
                                    "Horizontal offset of drop-shadow in pixels",
                                    CTK_EFFECT_DROP_SHADOW_MIN_OFFSET_X,
                                    CTK_EFFECT_DROP_SHADOW_MAX_OFFSET_X,
                                    CTK_EFFECT_DROP_SHADOW_DEFAULT_OFFSET_X,
                                    G_PARAM_READWRITE);
  g_object_class_install_property (obj_class, PROP_OFFSET_X, prop_offset_x);

  prop_offset_y = g_param_spec_int ("offset-y",
                                    "offset-y",
                                    "Vertical offset of drop-shadow in pixels",
                                    CTK_EFFECT_DROP_SHADOW_MIN_OFFSET_Y,
                                    CTK_EFFECT_DROP_SHADOW_MAX_OFFSET_Y,
                                    CTK_EFFECT_DROP_SHADOW_DEFAULT_OFFSET_Y,
                                    G_PARAM_READWRITE);
  g_object_class_install_property (obj_class, PROP_OFFSET_Y, prop_offset_y);
}

static void
ctk_effect_drop_shadow_init (CtkEffectDropShadow *self)
{
  CtkEffectDropShadowPrivate* priv         = NULL;
  priv = GET_PRIVATE (CTK_EFFECT_DROP_SHADOW (self));


  priv->cached_effect_texture = 0;
  CHECKGL (glGenTextures (1, &priv->cached_effect_texture));
  CHECKGL (glActiveTextureARB(GL_TEXTURE0) );
  CHECKGL (glBindTexture (GL_TEXTURE_2D, priv->cached_effect_texture));
  CHECKGL (glTexImage2D (GL_TEXTURE_2D, 0, GL_RGBA8, 1, 1, 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL));
  CHECKGL (glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR));
  CHECKGL (glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR));
  CHECKGL (glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP));
  CHECKGL (glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP));

  priv->cached_init = FALSE;
}

static void
ctk_effect_drop_shadow_set_property (GObject*      gobject,
                                     guint         prop,
                                     const GValue* value,
                                     GParamSpec*   spec)
{
  CtkEffectDropShadow* drop_shadow = NULL;

  drop_shadow = CTK_EFFECT_DROP_SHADOW (gobject);

  switch (prop)
  {
    case PROP_SIZE:
      ctk_effect_drop_shadow_set_blur_factor (drop_shadow, g_value_get_float (value));
    break;

    case PROP_OFFSET_X:
      ctk_effect_drop_shadow_set_offset_x (drop_shadow, g_value_get_int (value));
    break;

    case PROP_OFFSET_Y:
      ctk_effect_drop_shadow_set_offset_y (drop_shadow, g_value_get_int (value));
    break;

    default:
      G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, prop, spec);
    break;
  }
}

static void
ctk_effect_drop_shadow_get_property (GObject*    gobject,
                                     guint       prop,
                                     GValue*     value,
                                     GParamSpec* spec)
{
  CtkEffectDropShadow* drop_shadow = NULL;

  drop_shadow = CTK_EFFECT_DROP_SHADOW (gobject);

  switch (prop)
  {
    case PROP_SIZE:
      g_value_set_float (value, ctk_effect_drop_shadow_get_blur_factor (drop_shadow));
    break;

    case PROP_OFFSET_X:
      g_value_set_int (value, ctk_effect_drop_shadow_get_offset_x (drop_shadow));
    break;

    case PROP_OFFSET_Y:
      g_value_set_int (value, ctk_effect_drop_shadow_get_offset_y (drop_shadow));
    break;

    default:
      G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, prop, spec);
    break;
  }
}

/*
 * Private Methods
 */
static void
ctk_effect_drop_shadow_paint_dummy (CtkEffect          *effect,
                                    CtkEffectPaintFunc  paint_func,
                                    gboolean            is_last_effect)
{
  ClutterActor *actor = ctk_effect_get_actor (effect);

  /* Paint as normal */
  paint_func (actor);
}

static void
ctk_effect_drop_shadow_paint (CtkEffect          *effect,
                              CtkEffectPaintFunc  paint_func,
                              gboolean            is_last_effect)
{
  ClutterActor*               actor        = NULL;
  CtkEffectContext*           fxctx        = NULL;
  ClutterActor*               stage        = NULL;
  CtkRenderTarget*            top_rt       = NULL;
  CtkRenderTarget*            rt0          = NULL;
  CtkRenderTarget*            rt1          = NULL;
  CtkRenderTarget*            final_rt     = NULL;  
  CtkEffectDropShadowPrivate* priv         = NULL;
  gfloat                      stage_width  = 0.0f;
  gfloat                      stage_height = 0.0f;
  gfloat actor_screen_width, actor_screen_height;
  gfloat actor_screen_x, actor_screen_y;
  /* Make the rt a little bit bigger than the actor size. */
  gint margin = 0;
  /* The number of additional passes for the blur algorithm */
  gint num_passes = 0;
  
  float actor_opacity;
  float effect_opacity;
  gboolean UseCache = FALSE;
  ClutterVertex vtx[4];
  gboolean dirty_effect_cache = TRUE;
  
  actor = ctk_effect_get_actor (effect);
  g_return_if_fail (CLUTTER_IS_ACTOR (actor));

  priv = GET_PRIVATE (CTK_EFFECT_DROP_SHADOW (effect));
  margin = ctk_effect_get_margin (CTK_EFFECT (effect));
  num_passes = ctk_effect_get_strength (CTK_EFFECT (effect));
  dirty_effect_cache = ctk_effect_is_effect_cache_dirty (CTK_EFFECT (effect));

  /* before rendering an effect, render everything that has bee cahched so far */
  cogl_flush ();

  /* get effect-context to which this actor belongs */
  fxctx = ctk_effect_context_get_default_for_actor (actor);
  
  stage = clutter_actor_get_stage (actor);
  clutter_actor_get_size (CLUTTER_ACTOR (stage), &stage_width, &stage_height);

  ctk_get_actor_screen_position (CTK_ACTOR(actor),
      &actor_screen_x,
      &actor_screen_y,
      &actor_screen_width,
      &actor_screen_height, vtx);
  
  UseCache = TRUE; //ctk_actor_get_damaged (CTK_ACTOR (actor) );
  /* Get the size and position of the actor on screen */
  if ( (UseCache == FALSE) || (dirty_effect_cache == TRUE))
    {
      /* reserve 2 render targets */
      rt0 = ctk_effect_context_grab_render_target (fxctx);
      rt1 = ctk_effect_context_grab_render_target (fxctx);

      // make sure the render target has a color and depth buffer
      ctk_render_target_resize (rt0, stage_width, stage_height);
    
      ctk_render_target_unbind ();
    
      ctk_effect_context_push_render_target (fxctx, rt0);

      /* clear render target and prepare the surface */
      {
        CHECKGL (glViewport (0, 0, stage_width, stage_height));
        CHECKGL (glScissor (0, 0, stage_width, stage_height));
        CHECKGL (glDisable (GL_SCISSOR_TEST));
      
        /* make sure we are not using a shader program */
        CHECKGL (glClearDepth (1.0f));
        CHECKGL (glClearStencil (0));
        CHECKGL (glClearColor (0.0f, 0.0f, 0.0f, 0.0f));
        CHECKGL (glColorMask (GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE));
    
        /* clear the FBO content */
        CHECKGL (glClear (GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT));
      }

      /* Paint the actor and its children. If ctk_actor_set_effects_painting has been set to true
        for this actor, it will draw itself without any effect (avoiding an infinite loop). */
      paint_func (actor);
      cogl_flush ();

      /* Deactivate blending state set by clutter */
      glDisable(GL_BLEND); 
      
      if (rt0 != ctk_effect_context_pop_render_target(fxctx))
        {
          /* This is to make sure that all the right push and pop have been while drawing
          the children of this actor.*/
          g_warning("%s() at line %s: Invalid push/pop detected", G_STRFUNC, G_STRLOC);
        }
      
      /* Copy from a stage sized render target to an actor sized render target  */
      {
        float u0 = actor_screen_x/(float)ctk_render_target_get_width(rt0);
        float v0 = ((float)ctk_render_target_get_height(rt0) - actor_screen_y)/(float)ctk_render_target_get_height(rt0);
        float u1 = (actor_screen_x+actor_screen_width)/(float)ctk_render_target_get_width(rt0);
        float v1 = ((float)ctk_render_target_get_height(rt0) - (actor_screen_y + actor_screen_height))/(float)ctk_render_target_get_height(rt0);
      
        /* Make the rt a little bit bigger than the actor size. */
        ctk_render_target_resize(rt1, actor_screen_width+2*margin, actor_screen_height+2*margin);

        ctk_copy_rendertarget_to_rendertarget_asm (
          rt0,
          u0,
          v0,
          u1,
          v1,
          rt1, margin, margin, actor_screen_width, actor_screen_height);
    
        actor_screen_x -= margin;
        actor_screen_y -= margin;
        actor_screen_width += 2*margin;
        actor_screen_height += 2*margin;
        ctk_render_target_resize(rt0, actor_screen_width, actor_screen_height);
      }

      {
        int iPass = 0;
        ctk_render_target_bind (rt0);
        CHECKGL (glClear (GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT));
        ctk_render_quad_alpha_mask_asm (ctk_render_target_get_color_buffer_ogl_id(rt1), 
            ctk_render_target_get_width(rt1),
            ctk_render_target_get_height(rt1),              
    				0.0f, 
    				0.0f, 
    				0.0f, 
    				1.0f,
    				actor_screen_width,
    				actor_screen_height,
    				0,
    				0,
    				actor_screen_width,
    				actor_screen_height);

        for (iPass = 0; iPass < num_passes; iPass++)
    		  {
            ctk_render_target_bind(rt1);
            CHECKGL( glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT) );
    	    	opengl_blur_pass (ctk_render_target_get_color_buffer_ogl_id(rt0),
              ctk_render_target_get_width(rt0),
              ctk_render_target_get_height(rt0),
    				  0.0f,
    				  TRUE,
    				  actor_screen_width,
    				  actor_screen_height,
              FALSE);

            ctk_render_target_bind(rt0);
            CHECKGL( glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT) );
            opengl_blur_pass (ctk_render_target_get_color_buffer_ogl_id(rt1),
              ctk_render_target_get_width(rt1),
              ctk_render_target_get_height(rt1),
    				  0.0f,
    				  FALSE,
    				  actor_screen_width,
    				  actor_screen_height,
              FALSE);
    		  }

        final_rt = rt0;        
    	}
      /* Save the result in the cache texture */
      {
        ctk_copy_render_target_to_cached_texture_asm(fxctx, final_rt, priv->cached_effect_texture);
        ctk_effect_set_invalidate_effect_cache (CTK_EFFECT (effect), FALSE);
      }

      glEnable(GL_BLEND);
    }
    
  /* Now draw into the top render target on the stack */
  top_rt = ctk_effect_context_peek_render_target (fxctx);
  if(top_rt)
    {
      ctk_render_target_bind(top_rt);
      CHECKGL( glViewport(0, 0, ctk_render_target_get_width(top_rt), ctk_render_target_get_height(top_rt)) );
      CHECKGL( glScissor(0, 0, ctk_render_target_get_width(top_rt), ctk_render_target_get_height(top_rt)) );
    }
  else
    {
      /* There is no render target on the stack. Set back the regular frame buffer */
      ctk_render_target_unbind(); /* unbind whatever render target is binded */
      CHECKGL( glViewport(0, 0, stage_width, stage_height) );
      CHECKGL( glScissor(0, 0, stage_width, stage_height) );
    }

  actor_opacity = (float) clutter_actor_get_paint_opacity (CLUTTER_ACTOR (actor)) / 255.0f;
  effect_opacity = (float) ctk_effect_get_opacity (effect);
  {
    if (!UseCache)
      {
        ctk_render_quad_asm (
			    priv->cached_effect_texture,
          actor_screen_width,
          actor_screen_height,
          g_shTexture_asm,
			    stage_width,
			    stage_height,
			    actor_screen_x + (gfloat) priv->offset_x,
			    actor_screen_y + (gfloat) priv->offset_y,
			    actor_screen_width,
			    actor_screen_height,
          1.0f, 1.0f, 1.0f,
          effect_opacity);
      }
    else
      {
        /* this does not work if the actor is not screen aligned (ie rotated) */
        vtx[0].x += - margin;
        vtx[0].y += - margin;
        vtx[1].x += - margin;
        vtx[1].y += + margin;
        vtx[2].x += + margin;
        vtx[2].y += + margin;
        vtx[3].x += + margin;
        vtx[3].y += - margin;

        vtx[0].x += (gfloat) priv->offset_x;
        vtx[0].y += (gfloat) priv->offset_y;
        vtx[1].x += (gfloat) priv->offset_x;
        vtx[1].y += (gfloat) priv->offset_y;
        vtx[2].x += (gfloat) priv->offset_x;
        vtx[2].y += (gfloat) priv->offset_y;
        vtx[3].x += (gfloat) priv->offset_x;
        vtx[3].y += (gfloat) priv->offset_y;
        
        ctk_render_custom_quad_asm (
          priv->cached_effect_texture,
          actor_screen_width,
          actor_screen_height,
          g_shTexture_asm,
			    stage_width,
			    stage_height,
			    vtx,
          1.0f, 1.0f, 1.0f,
          actor_opacity*effect_opacity);
      }
  }

  /* render the actor on top of the glow */
  if (is_last_effect)
    {
      paint_func (actor);
      cogl_flush ();
    }

  /* leave and make sure the top render target on the stack is binded */
  if (top_rt)
    ctk_render_target_bind (top_rt);
  else
    ctk_render_target_unbind ();

  /* We cannot test ctk_actor_get_damaged anymore as it may have been changed during the last call to paint_func */
  if (rt0)
    {
      ctk_effect_context_release_render_target (fxctx, rt0);
    }
  if (rt1)
    {
      ctk_effect_context_release_render_target (fxctx, rt1);
    }
}

/*
 * Public Methods
 */

/**
 * ctk_effect_drop_shadow_new:
 * @size: An unsigned integer defining the desired shadow size in pixels
 * @offset_x: An integer describing the shadows horizontal offset in pixels
 * @offset_y: An integer describing the shadows vertical offset in pixels
 *
 * Create a new #CtkEffectDropShadow object with predefined size and offset. The
 * @size is meant to be understood as blur-kernel radius.
 *
 * Returns: A new #CtkEffectDropShadow
 **/
CtkEffectDropShadow*
ctk_effect_drop_shadow_new (gfloat blurfactor,
			    gint  offset_x,
			    gint  offset_y)
{
  return g_object_new (CTK_TYPE_EFFECT_DROP_SHADOW,
		       "blur_factor",     blurfactor,
		       "offset-x", offset_x,
		       "offset-y", offset_y,
                       NULL);
}

/**
 * ctk_effect_drop_shadow_set_size:
 * @drop_shadow: A pointer to a #CtkEffectDropShadow object
 * @size: An unsigned integer defining the desired shadow size in pixels
 *
 * Set the size (in pixels), aka blur-kernel radius, of @drop_shadow.
 **/
void
ctk_effect_drop_shadow_set_blur_factor (CtkEffectDropShadow* drop_shadow,
				 gfloat                size)
{
  g_return_if_fail (CTK_IS_EFFECT_DROP_SHADOW (drop_shadow));

  if (size <= CTK_EFFECT_DROP_SHADOW_MAX_SIZE &&
      size >= CTK_EFFECT_DROP_SHADOW_MIN_SIZE)
  {
    ClutterActor *actor;

    GET_PRIVATE (drop_shadow)->blur_factor = size;
    g_object_notify (G_OBJECT (drop_shadow), "blur_factor");
    
    actor = ctk_effect_get_actor (CTK_EFFECT (drop_shadow));
    if (CLUTTER_IS_ACTOR (actor) && CLUTTER_ACTOR_IS_VISIBLE (actor))
      clutter_actor_queue_redraw (actor);
  }
}

/**
 * ctk_effect_drop_shadow_get_size:
 * @drop_shadow: A pointer to a #CtkEffectDropShadow object
 *
 * Obtain the size (in pixels), aka blur-kernel radius, of @drop_shadow. 
 *
 * Returns: An unsigned integer describing the size of the shadow in pixels.
 * Should an invalid or NULL pointer be passed as @drop_shadow, 0 is returned to
 * the caller.
 **/
gfloat
ctk_effect_drop_shadow_get_blur_factor (CtkEffectDropShadow* drop_shadow)
{
  g_return_val_if_fail (CTK_IS_EFFECT_DROP_SHADOW (drop_shadow), 0);

  return GET_PRIVATE (drop_shadow)->blur_factor;
}

/**
 * ctk_effect_drop_shadow_set_offset_x:
 * @drop_shadow: A pointer to a #CtkEffectDropShadow object
 * @offset_x: An integer describing the shadows horizontal offset in pixels
 *
 * Set the horizontal offset (in pixels) for @drop_shadow.
 **/
void
ctk_effect_drop_shadow_set_offset_x (CtkEffectDropShadow* drop_shadow,
				     gint                 offset_x)
{
  g_return_if_fail (CTK_IS_EFFECT_DROP_SHADOW (drop_shadow));

  if (offset_x <= CTK_EFFECT_DROP_SHADOW_MAX_OFFSET_X &&
      offset_x >= CTK_EFFECT_DROP_SHADOW_MIN_OFFSET_X)
  {
    ClutterActor *actor;

    GET_PRIVATE (drop_shadow)->offset_x = offset_x;
    g_object_notify (G_OBJECT (drop_shadow), "offset-x");

    actor = ctk_effect_get_actor (CTK_EFFECT (drop_shadow));
    if (CLUTTER_IS_ACTOR (actor) && CLUTTER_ACTOR_IS_VISIBLE (actor))
      clutter_actor_queue_redraw (actor);
  }
}

/**
 * ctk_effect_drop_shadow_get_offset_x:
 * @drop_shadow: A pointer to a #CtkEffectDropShadow object
 *
 * Obtain the horizontal offset (in pixels) used for @drop_shadow.
 *
 * Returns: An integer describing the shadows horizontal offset in pixels.
 * Should an invalid or NULL pointer be passed as @drop_shadow, 0 is returned to
 * the caller.
 **/
gint
ctk_effect_drop_shadow_get_offset_x (CtkEffectDropShadow* drop_shadow)
{
  g_return_val_if_fail (CTK_IS_EFFECT_DROP_SHADOW (drop_shadow), 0);

  return GET_PRIVATE (drop_shadow)->offset_x;
}

/**
 * ctk_effect_drop_shadow_set_offset_y:
 * @drop_shadow: A pointer to a #CtkEffectDropShadow object
 * @offset_y: An integer describing the shadows vertical offset in pixels
 *
 * Set the vertical offset (in pixels) for @drop_shadow.
 **/
void
ctk_effect_drop_shadow_set_offset_y (CtkEffectDropShadow* drop_shadow,
				     gint                 offset_y)
{
  g_return_if_fail (CTK_IS_EFFECT_DROP_SHADOW (drop_shadow));

  if (offset_y <= CTK_EFFECT_DROP_SHADOW_MAX_OFFSET_Y &&
      offset_y >= CTK_EFFECT_DROP_SHADOW_MIN_OFFSET_Y)
  {
    ClutterActor *actor;

    GET_PRIVATE (drop_shadow)->offset_y = offset_y;
    g_object_notify (G_OBJECT (drop_shadow), "offset-y");

    actor = ctk_effect_get_actor (CTK_EFFECT (drop_shadow));
    if (CLUTTER_IS_ACTOR (actor) && CLUTTER_ACTOR_IS_VISIBLE (actor))
      clutter_actor_queue_redraw (actor);
  }
}

/**
 * ctk_effect_drop_shadow_get_offset_y:
 * @drop_shadow: A pointer to a #CtkEffectDropShadow object
 *
 * Obtain the vertical offset (in pixels) used for @drop_shadow.
 *
 * Returns: An integer describing the shadows vertical offset in pixels. Should
 * an invalid or NULL pointer be passed as @drop_shadow, 0 is returned to the
 * caller.
 **/
gint
ctk_effect_drop_shadow_get_offset_y (CtkEffectDropShadow* drop_shadow)
{
  g_return_val_if_fail (CTK_IS_EFFECT_DROP_SHADOW (drop_shadow), 0);

  return GET_PRIVATE (drop_shadow)->offset_y;
}
