/*
 * 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: Neil Jagdish Patel <neil.patel@canonical.com>
 *              Jay Taoko <jay.taoko@canonical.com>
 */
/** 
 * SECTION:ctk-effect-context
 * @short_description: A per-stage singleton that controls the render queue for
 * effects
 * @include: ctk-effect-context.h
 *
 * #CtkEffectContext is a per-stage singleton that controls the render queue for
 * #CtkEffects. It manages the global pool for #CtkRenderTargets available to
 * effects  and holds the current active #CtkRenderTarget for effects to
 * render to.
 */

#if HAVE_CONFIG_H
#include <config.h>
#endif

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

#include "ctk-effect-context.h"
#include "ctk-gfx-private.h"
#include "ctk-private.h"

G_DEFINE_TYPE (CtkEffectContext, ctk_effect_context, G_TYPE_OBJECT);

#define CTK_EFFECT_CONTEXT_GET_PRIVATE(obj) (G_TYPE_INSTANCE_GET_PRIVATE ((obj),\
  CTK_TYPE_EFFECT_CONTEXT, \
  CtkEffectContextPrivate))

#define EFFECT_CTX_QUARK "ctk-render-context-quark"

struct _CtkEffectContextPrivate
{
  ClutterStage *stage;

  GSList *sized_cache;
  GQueue *render_queue;

  /* An utility render target, not part of the global pool*/
  CtkRenderTarget *utility_render_target;
};

enum
{
  PROP_0,

  PROP_STAGE
};

/* Globals */
static GQuark _effect_context_quark = 0;

/* Forwards */

/*
 * GObject stuff
 */
static void
ctk_effect_context_finalize (GObject *object)
{
  CtkEffectContextPrivate *priv = CTK_EFFECT_CONTEXT (object)->priv;

  if (priv->sized_cache)
    {
      g_slist_foreach (priv->sized_cache, (GFunc)ctk_render_target_free, NULL);
      g_slist_free (priv->sized_cache);
      priv->sized_cache = NULL;
    }

  if (priv->render_queue)
    {
      g_queue_free (priv->render_queue);
      priv->render_queue = NULL;
    }

  if (priv->utility_render_target)
    {
      ctk_render_target_free (priv->utility_render_target);
      priv->utility_render_target = NULL;
    }

  G_OBJECT_CLASS (ctk_effect_context_parent_class)->finalize (object);
  
  ctk_delete_shader_program(g_shTexture);
  ctk_delete_shader_program(g_shTextureAlpha);
  ctk_delete_shader_program(g_shBlurH0);
  ctk_delete_shader_program(g_shBlurV0);
  ctk_delete_shader_program(g_shBlurH1);
  ctk_delete_shader_program(g_shBlurV1);
  ctk_delete_shader_program(g_shExp);
}


static void
ctk_effect_context_set_property (GObject      *object,
                                 guint         prop_id,
                                 const GValue *value,
                                 GParamSpec   *pspec)
{
  CtkEffectContextPrivate *priv = CTK_EFFECT_CONTEXT (object)->priv;

  switch (prop_id)
    {
    case PROP_STAGE:
      if (priv->stage)
        g_warning ("Stage can only be set once per instance");
      else
        priv->stage = g_value_get_pointer (value);
      break;

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


static void
ctk_effect_context_get_property (GObject    *object,
                                 guint       prop_id,
                                 GValue     *value,
                                 GParamSpec *pspec)
{
  CtkEffectContextPrivate *priv = CTK_EFFECT_CONTEXT (object)->priv;

  switch (prop_id)
    {
    case PROP_STAGE:
      g_value_set_pointer (value, priv->stage);
      break;

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


static void
ctk_effect_context_class_init (CtkEffectContextClass *klass)
{
  GObjectClass *obj_class = G_OBJECT_CLASS (klass);
  GParamSpec   *pspec;
  
  obj_class->finalize     = ctk_effect_context_finalize;
  obj_class->set_property = ctk_effect_context_set_property;
  obj_class->get_property = ctk_effect_context_get_property;

  /* Add properties */

  /**
   * CtkEffectContext:stage:
   *
   * The #ClutterStage that this #CtkEffectContext handles the effects for.
   **/
  pspec = g_param_spec_pointer ("stage", "Stage",
                                "The ClutterStage associated with this context",
                                G_PARAM_READWRITE |
                                G_PARAM_CONSTRUCT_ONLY |
                                G_PARAM_STATIC_STRINGS);
  g_object_class_install_property (obj_class, PROP_STAGE, pspec);

  /* Install private struct */
  g_type_class_add_private (obj_class, sizeof (CtkEffectContextPrivate));
}


static void
ctk_effect_context_init (CtkEffectContext *self)
{
  CtkEffectContextPrivate *priv;

  priv = self->priv = CTK_EFFECT_CONTEXT_GET_PRIVATE (self);

  priv->render_queue = g_queue_new ();

  priv->utility_render_target = ctk_render_target_new ();

}


/*
 * Private Methods 
 */
static void
on_stage_destroyed (CtkEffectContext *self)
{
  g_return_if_fail (CTK_IS_EFFECT_CONTEXT (self));

  g_object_unref (self);
}

/*
 * The 'real' function that handles the creation or getting of the context for
 * a stage.
 */
static CtkEffectContext *
ctk_effect_context_get_default_for_stage_real (ClutterStage *stage)
{
  CtkEffectContext *ret = NULL;

  g_return_val_if_fail (CLUTTER_IS_STAGE (stage), NULL);

  /* Create the render context quark if it doesn't exit. Using a GQuark speeds
   * up the lookup of the CtkEffectContext on the stage object
   */
  if (!_effect_context_quark)
    {
      _effect_context_quark = g_quark_from_static_string (EFFECT_CTX_QUARK);
    }

  /* See if there is an attached CtkEffectContext to the stage */
  ret = g_object_get_qdata (G_OBJECT (stage), _effect_context_quark);

  if (!CTK_IS_EFFECT_CONTEXT (ret))
    {
      ret = g_object_new (CTK_TYPE_EFFECT_CONTEXT,
                          "stage", stage,
                          NULL);

      /* Use _set_qdata_full as we'll can set a callback that get's called when
       * the stage is finalized, allowing us to also finalize, and therefore
       * we can avoid an explicit ctk_uninit function to clean-up
       */
      g_object_set_qdata_full (G_OBJECT (stage),
                              _effect_context_quark,
                              ret,
                              (GDestroyNotify)on_stage_destroyed);
    }

  return ret;
}

/*
 * Public Methods
 */

/**
 * ctk_effect_context_get_default_for_stage:
 * @stage: A #ClutterStage
 *
 * Creates a new #CtkEffectContext that will manage the @stage or will return
 * the default #CtkEffectContext for the @stage if one already exists.
 * 
 * Do not ref/unref the returned object, it will  automatically be finalized
 * when the stage it's attached to is finalized.
 *
 * Returns: The #CtkEffectContext that manages the @stage.
 **/
CtkEffectContext *
ctk_effect_context_get_default_for_stage (ClutterStage *stage)
{
  return ctk_effect_context_get_default_for_stage_real (stage);
}


/**
 * ctk_effect_context_get_default_for_actor:
 * @actor: A #ClutterActor
 *
 * Creates a new #CtkEffectContext that will manage the @stage that the actor
 * is bound to, or will return the default #CtkEffectContext for that @stage
 * if one already exists.
 * 
 * Do not ref/unref the returned object, it will  automatically be finalized
 * when the stage it's attached to is finalized.
 *
 * Returns: The #CtkEffectContext that manages the @actor's #ClutterStage.
 **/
CtkEffectContext *
ctk_effect_context_get_default_for_actor (ClutterActor *actor)
{
  ClutterActor *stage;

  g_return_val_if_fail (CLUTTER_IS_ACTOR (actor), NULL);

  stage = clutter_actor_get_stage (CLUTTER_ACTOR (actor));

  return ctk_effect_context_get_default_for_stage_real (CLUTTER_STAGE (stage));
}


/**
 * ctk_effect_context_grab_render_target:
 * @self: a #CtkEffectContext
 * @width: the desired width of the render-target
 * @height: the desired height of the render-target
 *
 * Returns: a #CtkRenderTarget from @self's pool. This target belongs to
 * @self and should not be freed
 **/
CtkRenderTarget *
ctk_effect_context_grab_render_target_for_size (CtkEffectContext *self,
                                       guint             width,
                                       guint             height)
{
  CtkEffectContextPrivate *priv;
  CtkRenderTarget *ret = NULL;
  GSList          *t;

  g_return_val_if_fail (CTK_IS_EFFECT_CONTEXT (self), NULL);
  priv = self->priv;

  /* Pull an sized target from the pool */
  for (t = priv->sized_cache; t; t = t->next)
    {
      CtkRenderTarget *target = t->data;
      CtkRenderTargetFlags flags = ctk_render_target_get_flags (target);

      if (!(flags & CTK_RENDER_TARGET_IN_USE))
        {
          ret = target;
          break;
        }
    }
  
  /* Create a new sized target if needed, and add it to the pool. Otherwise
   * resize the existing one to match what the developer wants
   */
  if (ret == NULL)
    {
      ret = ctk_render_target_new_sized (width, height);
      priv->sized_cache = g_slist_append (priv->sized_cache, ret);
    }
  else
    {
      ctk_render_target_resize (ret, width, height);
    }
  
  ctk_render_target_set_flags (ret,
                               ctk_render_target_get_flags (ret) 
                                 | CTK_RENDER_TARGET_IN_USE);
  return ret;
}


/**
 * ctk_effect_context_grab_render_target_for_actor:
 * @self: a #CtkEffectContext
 * @actor: a #ClutterActor
 *
 * This is a convenience function to get a #CtkRenderTarget from @self's pool
 * that matches the width and height of @actor. You should only use this call
 * while the #ClutterAllocation of @actor is valid (i.e. during a paint cycle).

 * Returns: a #CtkRenderTarget from @self's pool, sized to match the 
 * allocation of @actor
 **/
CtkRenderTarget *
ctk_effect_context_grab_render_target_for_actor (CtkEffectContext *self,
                                                 ClutterActor     *actor)
{
  ClutterActorBox box = { 0 };

  g_return_val_if_fail (CTK_IS_EFFECT_CONTEXT (self), NULL);
  g_return_val_if_fail (CLUTTER_IS_ACTOR (actor), NULL);

  clutter_actor_get_allocation_box (actor, &box);

  return ctk_effect_context_grab_render_target_for_size (self,
                                                box.x2 - box.x1,
                                                box.y2 - box.y1);
}

/**
 * ctk_effect_context_grab_render_target:
 * @self: a #CtkEffectContext
 *
 * Grab the first render target available without consideration of size.
 * 
 * Returns: a #CtkRenderTarget from @self's pool
 **/
CtkRenderTarget *
ctk_effect_context_grab_render_target (CtkEffectContext *self)
{
  CtkEffectContextPrivate *priv;
  CtkRenderTarget *ret = NULL;
  GSList          *t;

  g_return_val_if_fail (CTK_IS_EFFECT_CONTEXT (self), NULL);
  priv = self->priv;

  /* Pull an sized target from the pool */
  for (t = priv->sized_cache; t; t = t->next)
    {
      CtkRenderTarget *target = t->data;
      CtkRenderTargetFlags flags = ctk_render_target_get_flags (target);

      if (!(flags & CTK_RENDER_TARGET_IN_USE))
        {
          ret = target;
          break;
        }
    }

  /* Create a new target of size 1x1 if needed, and add it to the pool.
   */
  if (ret == NULL)
    {
      ret = ctk_render_target_new_sized (1, 1);
      priv->sized_cache = g_slist_append (priv->sized_cache, ret);
    }
  
  ctk_render_target_set_flags (ret,
                               ctk_render_target_get_flags (ret) 
                                 | CTK_RENDER_TARGET_IN_USE);
  return ret;

}

/**
 * ctk_effect_context_release_render_target:
 * @self: a #CtkEffectContext
 * @target: a #CtkRenderTarget
 *
 * Releases @target and adds it back into @self's render target pool
 * 
 * Returns: a #CtkRenderTarget from @self's cache, sized to match the 
 * allocation of @actor
 **/
void
ctk_effect_context_release_render_target (CtkEffectContext *self,
                                          CtkRenderTarget  *target)
{
  CtkEffectContextPrivate *priv;
  CtkRenderTargetFlags     flags;
  
  g_return_if_fail (CTK_IS_EFFECT_CONTEXT (self));
  g_return_if_fail (target);
  priv = self->priv;

  if (!g_slist_find (priv->sized_cache, target))
    {
      g_warning ("Cannot release a target that we do not own");
      return;
    }

  /* If the target isn't stacked, mark it as not in use, so we can use it at
   * a later time
   */
  flags = ctk_render_target_get_flags (target);

  if (flags & CTK_RENDER_TARGET_STACKED)
    {
      g_warning ("Cannot release a target that is stacked");
      return;
    }

  ctk_render_target_set_flags (target, flags &= ~CTK_RENDER_TARGET_IN_USE);
}


/**
 * ctk_effect_context_push_render_target:
 * @self: a #CtkEffectContext
 * @target: a #CtkRenderTarget
 *
 * Pushes @target to the top of the render queue, causing subsequent draws to
 * be rendered to it.
 **/
void
ctk_effect_context_push_render_target (CtkEffectContext *self,
                                       CtkRenderTarget  *target)
{
  CtkEffectContextPrivate *priv;
  CtkRenderTargetFlags     flags;

  g_return_if_fail (CTK_IS_EFFECT_CONTEXT (self));
  g_return_if_fail (target);
  priv = self->priv;

  flags = ctk_render_target_get_flags (target);

  if (flags & CTK_RENDER_TARGET_STACKED)
    {
      g_warning ("Render target already stacked");
      return;
    }

  CtkRenderTarget* top_rt = g_queue_peek_tail (priv->render_queue);
  if (top_rt == target)
    {
      /* This is a test similar to checking that the render target flags CTK_RENDER_TARGET_STACKED
      is not set. However, here we do it directly by checking the top of the stack.
      We should never get here */
      g_warning ("Render target already stacked");
      return;
    }
  
  ctk_render_target_set_flags (target, flags |= CTK_RENDER_TARGET_STACKED);
  g_queue_push_tail (priv->render_queue, target);
  ctk_render_target_bind (target);
}


/**
 * ctk_effect_context_pop_render_target:
 * @self: a #CtkEffectContext
 *
 * Pops the #CtkRenderTarget at the top of the render queue
 **/
CtkRenderTarget *
ctk_effect_context_pop_render_target (CtkEffectContext *self)
{
  CtkEffectContextPrivate *priv;
  CtkRenderTarget         *old_target;
  CtkRenderTarget         *new_target;

  g_return_val_if_fail (CTK_IS_EFFECT_CONTEXT (self), NULL);
  priv = self->priv;

  /* First let's pop a target as requested */
  old_target = g_queue_pop_tail (priv->render_queue);
  if (old_target)
    {
      CtkRenderTargetFlags flags;

      flags = ctk_render_target_get_flags (old_target);
      ctk_render_target_set_flags (old_target, flags &= ~CTK_RENDER_TARGET_STACKED);
    }

  /* If there is a valid target at the tail of the queue, then bind it to the
   * framebuffer
   */
  new_target = g_queue_peek_tail (priv->render_queue);
  if (new_target)
    {
      ctk_render_target_bind (new_target);
    }
  else
    {
      ctk_render_target_unbind();
    }
  return old_target;
}


/**
 * ctk_effect_context_peek_render_target:
 * @self: a #CtkEffectContext
 *
 * Returns: the #CtkRenderTarget at the top of the render queue
 **/
CtkRenderTarget *
ctk_effect_context_peek_render_target (CtkEffectContext *self)
{
  CtkEffectContextPrivate *priv;

  g_return_val_if_fail (CTK_IS_EFFECT_CONTEXT (self), NULL);
  priv = self->priv;

  return g_queue_peek_tail (priv->render_queue);
}

/**
 * ctk_effect_context_get_utility_render_target:
 * @self: a #CtkEffectContext
 *
 * Returns: the utility #CtkRenderTarget. The caller will use it to perform custom operations.
 **/
CtkRenderTarget *
ctk_effect_context_get_utility_render_target (CtkEffectContext *self)
{
  CtkEffectContextPrivate *priv;

  g_return_val_if_fail (CTK_IS_EFFECT_CONTEXT (self), NULL);
  priv = self->priv;
  
  return priv->utility_render_target;  
}

