/*
 * 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-actor
 * @short_description: Class to easily stack images (with masks applied to them)
 * on top of each other
 * @see_also: #CtkLayer
 * @include ctk-layer-actor.h
 *
 * #CtkLayerActor allows to stack multiple images (with a mask applied) on top
 * of each other. It's like a layered image in gimp using normal alpha-
 * blending for the various layers. Each layer has to have the same size as the
 * actor itself. This restriction is meant to keep things simple for the moment.
 * In the future this can be loosened, should the need arise.
 */
#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-layer-actor.h"
#include "ctk-private.h"
#include "ctk-actor.h"

G_DEFINE_TYPE (CtkLayerActor, ctk_layer_actor, CTK_TYPE_ACTOR);

enum
{
  PROP_0 = 0,
  PROP_LAYER_ACTOR_WIDTH,      // read/write
  PROP_LAYER_ACTOR_HEIGHT,     // read/write
  PROP_LAYER_ACTOR_NUM_LAYERS, // read
  PROP_LAYER_ACTOR_FLATTENED   // read
};

#define GET_PRIVATE(o) \
  (G_TYPE_INSTANCE_GET_PRIVATE ((o), CTK_TYPE_LAYER_ACTOR, CtkLayerActorPrivate))

struct _CtkLayerActorPrivate
{
  guint    width;
  guint    height;
  GList*   layers;
  gboolean flattened;
};

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

static void
ctk_layer_actor_paint (ClutterActor* actor)
{
  CtkLayerActor*        self                = CTK_LAYER_ACTOR (actor);
  CtkLayerActorPrivate* priv                = NULL;
  guint                 i                   = 0;
  gfloat                x                   = 0.0f;
  gfloat                y                   = 0.0f;
  ClutterActor*         stage               = NULL;
  gfloat                stage_width         = 0.0f;
  gfloat                stage_height        = 0.0f;
  gfloat                actor_screen_x      = 0.0f;
  gfloat                actor_screen_y      = 0.0f;
  gfloat                actor_screen_width  = 0.0f;
  gfloat                actor_screen_height = 0.0f;
  gfloat                actor_opacity       = 0.0f;
  ClutterVertex         vertices[4]         = {{0.0f, 0.0f, 0.0f},
                                               {0.0f, 0.0f, 0.0f},
                                               {0.0f, 0.0f, 0.0f},
                                               {0.0f, 0.0f, 0.0f}};

  // before rendering, render everything that has been cached so far
  cogl_flush ();

  CtkEffectContext* fxctx = ctk_effect_context_get_default_for_actor (actor);
  /* Get the current render target */
  CtkRenderTarget* top_rt = ctk_effect_context_peek_render_target(fxctx);
   
  priv = GET_PRIVATE (self);
  stage = clutter_actor_get_stage (actor);
  actor_opacity = (gfloat) clutter_actor_get_paint_opacity (CLUTTER_ACTOR (actor)) / 255.0f;
  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,
                                 vertices);

  if (ctk_layer_actor_is_flattened (self))
  {
    // FIXME: to be implemented
  }
  else
  {
    for (i = 0; i < ctk_layer_actor_get_num_layers (self); i++)
    {
      CtkLayer*    layer = NULL;
      ClutterColor color = {0, 0, 0, 0};

      layer = ctk_layer_actor_get_layer (self, i);

      // skip this layer if it is disabled
      if (!ctk_layer_get_enabled (layer))
        continue;

      // skip this layer if it's invalid (e.g. sizes don't match)
      if (ctk_layer_is_valid (layer))
      {

        ctk_layer_get_color (layer, &color);
	clutter_actor_get_position (CLUTTER_ACTOR (self), &x, &y);

	// mask & image
        if (ctk_layer_get_mask_id (layer)  != 0 &&
            ctk_layer_get_image_id (layer) != 0)
        {
          ctk_render_masked2_texture_asm (ctk_layer_get_image_id (layer),
                                         ctk_layer_get_mask_id (layer),
                                         ctk_layer_get_width (layer),
                                         ctk_layer_get_height (layer),
                                         g_shTextureMask_asm,
                                         stage_width,
                                         stage_height,
                                         actor_screen_x,
                                         actor_screen_y,
                                         ctk_layer_get_width (layer),
                                         ctk_layer_get_height (layer),
                                         actor_opacity * (color.alpha/255.0f));
	}

	// mask & no image (only use layer-color)
        if (ctk_layer_get_mask_id (layer)  != 0 &&
            ctk_layer_get_image_id (layer) == 0)
        {
          ctk_render_quad_alpha_mask_asm (ctk_layer_get_mask_id (layer),
                                          ctk_layer_get_width (layer),
                                          ctk_layer_get_height (layer),
                                          color.red / 255.0f,
                                          color.green / 255.0f,
                                          color.blue / 255.0f,
                                          color.alpha / 255.0f,
                                          stage_width,
                                          stage_height,
                                          actor_screen_x,
                                          actor_screen_y,
                                          ctk_layer_get_width (layer),
                                          ctk_layer_get_height (layer));
	}
      }
    }
  }
  /* Restore the previous render target */
  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) );
    }
}

void
_unref_layer (gpointer data, gpointer user_data)
{
  CtkLayer* layer;

  if (!data)
    return;

  layer = CTK_LAYER (data);
  g_object_unref (layer);
}

static void
ctk_layer_actor_finalize (GObject* object)
{
  CtkLayerActor*        layer_actor = CTK_LAYER_ACTOR (object);
  CtkLayerActorPrivate* priv        = GET_PRIVATE (layer_actor);

  g_list_foreach (priv->layers, _unref_layer, NULL);
}

static void
ctk_layer_actor_set_property (GObject*      object,
                              guint         prop_id,
                              const GValue* value,
                              GParamSpec*   pspec)
{
  CtkLayerActor* layer_actor = CTK_LAYER_ACTOR (object);

  switch (prop_id)
  {
    case PROP_LAYER_ACTOR_WIDTH:
      ctk_layer_actor_set_width (layer_actor, g_value_get_uint (value));
    break;

    case PROP_LAYER_ACTOR_HEIGHT:
      ctk_layer_actor_set_height (layer_actor, g_value_get_uint (value));
    break;

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

static void
ctk_layer_actor_get_property (GObject*    object,
                              guint       prop_id,
                              GValue*     value,
                              GParamSpec* pspec)
{
  CtkLayerActor* layer_actor = CTK_LAYER_ACTOR (object);

  switch (prop_id)
  {
    case PROP_LAYER_ACTOR_WIDTH:
      g_value_set_uint (value, ctk_layer_actor_get_width (layer_actor));
    break;

    case PROP_LAYER_ACTOR_HEIGHT:
      g_value_set_uint (value, ctk_layer_actor_get_height (layer_actor));
    break;

    case PROP_LAYER_ACTOR_NUM_LAYERS:
      g_value_set_uint (value, ctk_layer_actor_get_num_layers (layer_actor));
    break;

    case PROP_LAYER_ACTOR_FLATTENED:
      g_value_set_uint (value, ctk_layer_actor_is_flattened (layer_actor));
    break;

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

static void
ctk_layer_actor_class_init (CtkLayerActorClass* klass)
{
  GObjectClass*      obj_class = G_OBJECT_CLASS (klass);
  ClutterActorClass* act_class = CLUTTER_ACTOR_CLASS (klass);
  GParamSpec*        pspec;

  act_class->paint        = ctk_layer_actor_paint;
  obj_class->finalize     = ctk_layer_actor_finalize;
  obj_class->set_property = ctk_layer_actor_set_property;
  obj_class->get_property = ctk_layer_actor_get_property;

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

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

  pspec = g_param_spec_uint ("num-layers",
                             "num-layers",
                             "Number of layers used in layer-actor.",
                             0,
                             G_MAXUINT,
                             0,
                             G_PARAM_READABLE);
  g_object_class_install_property (obj_class,PROP_LAYER_ACTOR_NUM_LAYERS,pspec);

  pspec = g_param_spec_boolean ("flattened",
                                "flattened",
                                "Flag indicating if layer-actor is flattened.",
                                FALSE,
                                G_PARAM_READABLE);
  g_object_class_install_property (obj_class, PROP_LAYER_ACTOR_FLATTENED,pspec);

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

static void
ctk_layer_actor_init (CtkLayerActor* layer_actor)
{
  CtkLayerActorPrivate* priv;

  priv = GET_PRIVATE (layer_actor);

  priv->width      = 0;
  priv->height     = 0;
  priv->layers     = NULL;
  priv->flattened  = FALSE;
}

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

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

/** 
 * ctk_layer_actor_new:
 * @width: a #guint
 * @height: a #guint
 *
 * Creates a new #CtkLayerActor, which can hold multiple layers (image/mask) to
 * form a composited actor.
 *
 * Returns: a #ClutterActor or NULL in case of an error
 */
ClutterActor*
ctk_layer_actor_new (guint width,
                     guint height)
{
  return g_object_new (CTK_TYPE_LAYER_ACTOR,
                       "width", width,
                       "height", height,
                       NULL);
}

/** 
 * ctk_layer_actor_get_width:
 * @self: a #CtkLayerActor
 *
 * Get the width of @self.
 *
 * Returns: a #guint, the width of @self
 */
guint
ctk_layer_actor_get_width (CtkLayerActor* self)
{
  // sanity check
  if (!CTK_IS_LAYER_ACTOR (self))
    return 0;

  return GET_PRIVATE (self)->width;
}

/** 
 * ctk_layer_actor_set_width:
 * @self: a #CtkLayerActor
 * @width: a #guint
 *
 * Set the width of @self. Do that before passing the first layer. Remember
 * width and height of @self and any layer added to it have to match exactly!
 */
void
ctk_layer_actor_set_width (CtkLayerActor* self,
                           guint          width)
{
  CtkLayerActorPrivate* priv = NULL;

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

  priv = GET_PRIVATE (self);

  if (priv->width != width)
    priv->width = width;
}

/** 
 * ctk_layer_actor_get_height:
 * @self: a #CtkLayerActor
 *
 * Get the height of @self.
 *
 * Returns: a #guint, the height of @self
 */
guint
ctk_layer_actor_get_height (CtkLayerActor* self)
{
  // sanity check
  if (!CTK_IS_LAYER_ACTOR (self))
    return 0;

  return GET_PRIVATE (self)->height;
}

/** 
 * ctk_layer_actor_set_height:
 * @self: a #CtkLayerActor
 * @height: a #guint
 *
 * Set the height of @self. Do that before passing the first layer. Remember
 * width and height of @self and any layer added to it have to match exactly!
 */
void
ctk_layer_actor_set_height (CtkLayerActor* self,
                            guint          height)
{
  CtkLayerActorPrivate* priv = NULL;

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

  priv = GET_PRIVATE (self);

  if (priv->height != height)
    priv->height = height;
}

/** 
 * ctk_layer_actor_get_num_layers:
 * @self: a #CtkLayerActor
 *
 * Get the number of layers in @self. If @self has been flattend before this
 * will return 1.
 *
 * Returns: a #guint holding the number of layers in @self (enabled and
 * non-enabled layers)
 */
guint
ctk_layer_actor_get_num_layers (CtkLayerActor* self)
{
  CtkLayerActorPrivate* priv = NULL;

  // sanity check
  if (!CTK_IS_LAYER_ACTOR (self))
    return 0;

  priv = GET_PRIVATE (self);

  if (!priv->layers)
    return 0;

  return g_list_length (priv->layers);
}

/**
 * ctk_layer_actor_add_layer:
 * @self: a #CtkLayerActor
 * @layer: a #CtkLayer
 *
 * Add @layer to @self. If @layer or @self are NULL nothing happens. Remember
 * that height and width of @layer have to exactly match those of @self!
 */
void
ctk_layer_actor_add_layer (CtkLayerActor* self,
                           CtkLayer*      layer)
{
  CtkLayerActorPrivate* priv = NULL;

  // sanity check
  if (!CTK_IS_LAYER_ACTOR (self) || !layer)
    return;

  priv = GET_PRIVATE (self);

  priv->layers = g_list_append (priv->layers, g_object_ref (layer));
}

/** 
 * ctk_layer_actor_get_layer:
 * @self: a #CtkLayerActor
 * @index: a #guint indicating, which layer (counting starts at 0) to grab
 *
 * Grab the pointer to of a layer indicated by @index. Don't unref the returned
 * pointer!
 *
 * Returns: a #CtkLayer or NULL, if @index passed in was out-of-bound or @self
 * was invalid
 */
CtkLayer*
ctk_layer_actor_get_layer (CtkLayerActor* self,
                           guint          index)
{
  CtkLayerActorPrivate* priv = NULL;

  // sanity checks
  if (!CTK_IS_LAYER_ACTOR (self))
    return NULL;

  priv = GET_PRIVATE (self);

  if (index >= g_list_length (priv->layers))
    return NULL;

  return CTK_LAYER (g_list_nth_data (priv->layers, index));
}

/** 
 * ctk_layer_actor_flatten:
 * @self: a #CtkLayerActor
 *
 * Collapse all (enabled) layers of a layer-actor into one single layer with a
 * full mask. Afterwards the @self will consist of only one layer. All other
 * layers will be freed and removed. This is an optimization you can do to
 * speed up the rendering of @self. Usually one layer means on pass, thus
 * keeping the number of layers to a minimum improves performance.
 *
 * This operation cannot be undone.
 *
 * Currently not implemented. Calling this does nothing.
 */
void
ctk_layer_actor_flatten (CtkLayerActor* self)
{
  // FIXME: not implemented yet
  /* g_warning ("%s(): not implemented yet!\n", G_STRFUNC); */
}

/** 
 * ctk_layer_actor_is_flattened:
 * @self: a #CtkLayerActor
 *
 * Check if the layer-actor is flattened already, meaning all enabled layers
 * were collapsed into a single layer with a full mask. A flattened layer-actor
 * will consist of only one single layer.
 *
 * Returns: a #gboolean, TRUE if @self is flattened, FALSE if not or in an
 * error-case
 */
gboolean
ctk_layer_actor_is_flattened (CtkLayerActor* self)
{
  return GET_PRIVATE (self)->flattened;
}
