/*
 * Clutter.
 *
 * An OpenGL based 'interactive canvas' library.
 *
 * Copyright (C) 2011  Intel Corporation.
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library. If not, see <http://www.gnu.org/licenses/>.
 *
 * Author:
 *   Emmanuele Bassi <ebassi@linux.intel.com>
 */

/**
 * SECTION:clutter-paint-nodes
 * @Title: Paint Nodes
 * @Short_Description: ClutterPaintNode implementations
 *
 * Clutter provides a set of predefined #ClutterPaintNode implementations
 * that cover all the state changes available.
 */

#include "clutter-build-config.h"

#define CLUTTER_ENABLE_EXPERIMENTAL_API

#include "clutter-paint-node-private.h"

#include <pango/pango.h>
#include <cogl/cogl.h>

#include "clutter-actor-private.h"
#include "clutter-color.h"
#include "clutter-debug.h"
#include "clutter-private.h"
#include "clutter-paint-context-private.h"

#include "clutter-paint-nodes.h"

static CoglPipeline *default_color_pipeline   = NULL;
static CoglPipeline *default_texture_pipeline = NULL;

/*< private >
 * _clutter_paint_node_init_types:
 *
 * Initializes the required types for ClutterPaintNode subclasses
 */
void
_clutter_paint_node_init_types (void)
{
  CoglContext *ctx;
  CoglColor cogl_color;
  GType node_type G_GNUC_UNUSED;

  if (G_LIKELY (default_color_pipeline != NULL))
    return;

  ctx = clutter_backend_get_cogl_context (clutter_get_default_backend ());

  node_type = clutter_paint_node_get_type ();

  cogl_color_init_from_4f (&cogl_color, 1.0, 1.0, 1.0, 1.0);

  default_color_pipeline = cogl_pipeline_new (ctx);
  cogl_pipeline_set_color (default_color_pipeline, &cogl_color);

  default_texture_pipeline = cogl_pipeline_new (ctx);
  cogl_pipeline_set_layer_null_texture (default_texture_pipeline, 0);
  cogl_pipeline_set_color (default_texture_pipeline, &cogl_color);
  cogl_pipeline_set_layer_wrap_mode (default_texture_pipeline, 0,
                                     COGL_PIPELINE_WRAP_MODE_AUTOMATIC);
}

/*
 * Root node
 *
 * any frame can only have a since RootNode instance for each
 * top-level actor.
 */

#define clutter_root_node_get_type      clutter_root_node_get_type

struct _ClutterRootNode
{
  ClutterPaintNode parent_instance;

  CoglFramebuffer *framebuffer;

  CoglBufferBit clear_flags;
  CoglColor clear_color;
};

G_DEFINE_TYPE (ClutterRootNode, clutter_root_node, CLUTTER_TYPE_PAINT_NODE)

static gboolean
clutter_root_node_pre_draw (ClutterPaintNode    *node,
                            ClutterPaintContext *paint_context)
{
  ClutterRootNode *rnode = (ClutterRootNode *) node;

  clutter_paint_context_push_framebuffer (paint_context, rnode->framebuffer);

  cogl_framebuffer_clear (rnode->framebuffer,
                          rnode->clear_flags,
                          &rnode->clear_color);

  return TRUE;
}

static void
clutter_root_node_post_draw (ClutterPaintNode    *node,
                             ClutterPaintContext *paint_context)
{
  clutter_paint_context_pop_framebuffer (paint_context);
}

static void
clutter_root_node_finalize (ClutterPaintNode *node)
{
  ClutterRootNode *rnode = (ClutterRootNode *) node;

  cogl_object_unref (rnode->framebuffer);

  CLUTTER_PAINT_NODE_CLASS (clutter_root_node_parent_class)->finalize (node);
}

static CoglFramebuffer *
clutter_root_node_get_framebuffer (ClutterPaintNode *node)
{
  ClutterRootNode *rnode = (ClutterRootNode *) node;

  return rnode->framebuffer;
}

static void
clutter_root_node_class_init (ClutterRootNodeClass *klass)
{
  ClutterPaintNodeClass *node_class = CLUTTER_PAINT_NODE_CLASS (klass);

  node_class->pre_draw = clutter_root_node_pre_draw;
  node_class->post_draw = clutter_root_node_post_draw;
  node_class->finalize = clutter_root_node_finalize;
  node_class->get_framebuffer = clutter_root_node_get_framebuffer;
}

static void
clutter_root_node_init (ClutterRootNode *self)
{
}

ClutterPaintNode *
clutter_root_node_new (CoglFramebuffer    *framebuffer,
                       const ClutterColor *clear_color,
                       CoglBufferBit       clear_flags)
{
  ClutterRootNode *res;

  g_return_val_if_fail (framebuffer, NULL);

  res = _clutter_paint_node_create (CLUTTER_TYPE_ROOT_NODE);

  cogl_color_init_from_4ub (&res->clear_color,
                            clear_color->red,
                            clear_color->green,
                            clear_color->blue,
                            clear_color->alpha);
  cogl_color_premultiply (&res->clear_color);

  res->framebuffer = cogl_object_ref (framebuffer);
  res->clear_flags = clear_flags;

  return (ClutterPaintNode *) res;
}

/*
 * ClutterTransformNode
 */

struct _ClutterTransformNode
{
  ClutterPaintNode parent_instance;

  CoglMatrix transform;
};

struct _ClutterTransformNodeClass
{
  ClutterPaintNodeClass parent_class;
};

G_DEFINE_TYPE (ClutterTransformNode, clutter_transform_node, CLUTTER_TYPE_PAINT_NODE)

static gboolean
clutter_transform_node_pre_draw (ClutterPaintNode    *node,
                                 ClutterPaintContext *paint_context)
{
  ClutterTransformNode *transform_node = (ClutterTransformNode *) node;
  CoglFramebuffer *fb =
   clutter_paint_context_get_framebuffer (paint_context);

  cogl_framebuffer_push_matrix (fb);
  cogl_framebuffer_transform (fb, &transform_node->transform);

  return TRUE;
}

static void
clutter_transform_node_post_draw (ClutterPaintNode    *node,
                                  ClutterPaintContext *paint_context)
{
  CoglFramebuffer *fb =
   clutter_paint_context_get_framebuffer (paint_context);

  cogl_framebuffer_pop_matrix (fb);
}

static void
clutter_transform_node_class_init (ClutterTransformNodeClass *klass)
{
  ClutterPaintNodeClass *node_class;

  node_class = CLUTTER_PAINT_NODE_CLASS (klass);
  node_class->pre_draw = clutter_transform_node_pre_draw;
  node_class->post_draw = clutter_transform_node_post_draw;
}

static void
clutter_transform_node_init (ClutterTransformNode *self)
{
  cogl_matrix_init_identity (&self->transform);
}

/*
 * clutter_transform_node_new:
 * @transform: (nullable): the transform matrix to apply
 *
 * Return value: (transfer full): the newly created #ClutterTransformNode.
 *   Use clutter_paint_node_unref() when done.
 */
ClutterPaintNode *
clutter_transform_node_new (const CoglMatrix *transform)
{
  ClutterTransformNode *res;

  res = _clutter_paint_node_create (CLUTTER_TYPE_TRANSFORM_NODE);
  if (transform)
    res->transform = *transform;

  return (ClutterPaintNode *) res;
}

/*
 * Dummy node, private
 *
 * an empty node, used temporarily until we can drop API compatibility,
 * and we'll be able to build a full render tree for each frame.
 */

#define clutter_dummy_node_get_type      _clutter_dummy_node_get_type

typedef struct _ClutterDummyNode        ClutterDummyNode;
typedef struct _ClutterPaintNodeClass   ClutterDummyNodeClass;

struct _ClutterDummyNode
{
  ClutterPaintNode parent_instance;

  ClutterActor *actor;
  CoglFramebuffer *framebuffer;
};

G_DEFINE_TYPE (ClutterDummyNode, clutter_dummy_node, CLUTTER_TYPE_PAINT_NODE)

static gboolean
clutter_dummy_node_pre_draw (ClutterPaintNode    *node,
                             ClutterPaintContext *paint_context)
{
  return TRUE;
}

static JsonNode *
clutter_dummy_node_serialize (ClutterPaintNode *node)
{
  ClutterDummyNode *dnode = (ClutterDummyNode *) node;
  JsonBuilder *builder;
  JsonNode *res;

  if (dnode->actor == NULL)
    return json_node_new (JSON_NODE_NULL);

  builder = json_builder_new ();
  json_builder_begin_object (builder);

  json_builder_set_member_name (builder, "actor");
  json_builder_add_string_value (builder, _clutter_actor_get_debug_name (dnode->actor));

  json_builder_end_object (builder);

  res = json_builder_get_root (builder);
  g_object_unref (builder);

  return res;
}

static CoglFramebuffer *
clutter_dummy_node_get_framebuffer (ClutterPaintNode *node)
{
  ClutterDummyNode *dnode = (ClutterDummyNode *) node;

  return dnode->framebuffer;
}

static void
clutter_dummy_node_finalize (ClutterPaintNode *node)
{
  ClutterDummyNode *dnode = (ClutterDummyNode *) node;

  cogl_clear_object (&dnode->framebuffer);

  CLUTTER_PAINT_NODE_CLASS (clutter_dummy_node_parent_class)->finalize (node);
}

static void
clutter_dummy_node_class_init (ClutterDummyNodeClass *klass)
{
  ClutterPaintNodeClass *node_class = CLUTTER_PAINT_NODE_CLASS (klass);

  node_class->pre_draw = clutter_dummy_node_pre_draw;
  node_class->serialize = clutter_dummy_node_serialize;
  node_class->get_framebuffer = clutter_dummy_node_get_framebuffer;
  node_class->finalize = clutter_dummy_node_finalize;
}

static void
clutter_dummy_node_init (ClutterDummyNode *self)
{
}

ClutterPaintNode *
_clutter_dummy_node_new (ClutterActor    *actor,
                         CoglFramebuffer *framebuffer)
{
  ClutterPaintNode *res;
  ClutterDummyNode *dnode;

  res = _clutter_paint_node_create (_clutter_dummy_node_get_type ());

  dnode = (ClutterDummyNode *) res;
  dnode->actor = actor;
  dnode->framebuffer = cogl_object_ref (framebuffer);

  return res;
}

/*
 * Pipeline node
 */

struct _ClutterPipelineNode
{
  ClutterPaintNode parent_instance;

  CoglPipeline *pipeline;
};

/**
 * ClutterPipelineNodeClass:
 *
 * The `ClutterPipelineNodeClass` structure is an opaque
 * type whose members cannot be directly accessed.
 *
 * Since: 1.10
 */
struct _ClutterPipelineNodeClass
{
  ClutterPaintNodeClass parent_class;
};

G_DEFINE_TYPE (ClutterPipelineNode, clutter_pipeline_node, CLUTTER_TYPE_PAINT_NODE)

static void
clutter_pipeline_node_finalize (ClutterPaintNode *node)
{
  ClutterPipelineNode *pnode = CLUTTER_PIPELINE_NODE (node);

  if (pnode->pipeline != NULL)
    cogl_object_unref (pnode->pipeline);

  CLUTTER_PAINT_NODE_CLASS (clutter_pipeline_node_parent_class)->finalize (node);
}

static gboolean
clutter_pipeline_node_pre_draw (ClutterPaintNode    *node,
                                ClutterPaintContext *paint_context)
{
  ClutterPipelineNode *pnode = CLUTTER_PIPELINE_NODE (node);

  if (node->operations != NULL &&
      pnode->pipeline != NULL)
    return TRUE;

  return FALSE;
}

static CoglFramebuffer *
get_target_framebuffer (ClutterPaintNode    *node,
                        ClutterPaintContext *paint_context)
{
  CoglFramebuffer *framebuffer;

  framebuffer = clutter_paint_node_get_framebuffer (node);
  if (framebuffer)
    return framebuffer;

  return clutter_paint_context_get_framebuffer (paint_context);
}

static void
clutter_pipeline_node_draw (ClutterPaintNode    *node,
                            ClutterPaintContext *paint_context)
{
  ClutterPipelineNode *pnode = CLUTTER_PIPELINE_NODE (node);
  CoglFramebuffer *fb;
  guint i;

  if (pnode->pipeline == NULL)
    return;

  if (node->operations == NULL)
    return;

  fb = clutter_paint_context_get_framebuffer (paint_context);

  for (i = 0; i < node->operations->len; i++)
    {
      const ClutterPaintOperation *op;

      op = &g_array_index (node->operations, ClutterPaintOperation, i);

      switch (op->opcode)
        {
        case PAINT_OP_INVALID:
          break;

        case PAINT_OP_TEX_RECT:
          cogl_framebuffer_draw_textured_rectangle (fb,
                                                    pnode->pipeline,
                                                    op->op.texrect[0],
                                                    op->op.texrect[1],
                                                    op->op.texrect[2],
                                                    op->op.texrect[3],
                                                    op->op.texrect[4],
                                                    op->op.texrect[5],
                                                    op->op.texrect[6],
                                                    op->op.texrect[7]);
          break;

        case PAINT_OP_MULTITEX_RECT:
          cogl_framebuffer_draw_multitextured_rectangle (fb,
                                                         pnode->pipeline,
                                                         op->op.texrect[0],
                                                         op->op.texrect[1],
                                                         op->op.texrect[2],
                                                         op->op.texrect[3],
                                                         (float*) op->multitex_coords->data,
                                                         op->multitex_coords->len);
          break;

        case PAINT_OP_PATH:
          cogl_framebuffer_fill_path (fb, pnode->pipeline, op->op.path);
          break;

        case PAINT_OP_PRIMITIVE:
          cogl_framebuffer_draw_primitive (fb,
                                           pnode->pipeline,
                                           op->op.primitive);
          break;
        }
    }
}

static void
clutter_pipeline_node_post_draw (ClutterPaintNode    *node,
                                 ClutterPaintContext *paint_context)
{
}

static JsonNode *
clutter_pipeline_node_serialize (ClutterPaintNode *node)
{
  ClutterPipelineNode *pnode = CLUTTER_PIPELINE_NODE (node);
  JsonBuilder *builder;
  CoglColor color;
  JsonNode *res;

  if (pnode->pipeline == NULL)
    return json_node_new (JSON_NODE_NULL);

  builder = json_builder_new ();
  json_builder_begin_object (builder);

  cogl_pipeline_get_color (pnode->pipeline, &color);
  json_builder_set_member_name (builder, "color");
  json_builder_begin_array (builder);
  json_builder_add_double_value (builder, cogl_color_get_red (&color));
  json_builder_add_double_value (builder, cogl_color_get_green (&color));
  json_builder_add_double_value (builder, cogl_color_get_blue (&color));
  json_builder_add_double_value (builder, cogl_color_get_alpha (&color));
  json_builder_end_array (builder);

#if 0
  json_builder_set_member_name (builder, "layers");
  json_builder_begin_array (builder);
  cogl_pipeline_foreach_layer (pnode->pipeline,
                               clutter_pipeline_node_serialize_layer,
                               builder);
  json_builder_end_array (builder);
#endif

  json_builder_end_object (builder);

  res = json_builder_get_root (builder);
  g_object_unref (builder);

  return res;
}

static void
clutter_pipeline_node_class_init (ClutterPipelineNodeClass *klass)
{
  ClutterPaintNodeClass *node_class;

  node_class = CLUTTER_PAINT_NODE_CLASS (klass);
  node_class->pre_draw = clutter_pipeline_node_pre_draw;
  node_class->draw = clutter_pipeline_node_draw;
  node_class->post_draw = clutter_pipeline_node_post_draw;
  node_class->finalize = clutter_pipeline_node_finalize;
  node_class->serialize = clutter_pipeline_node_serialize;
}

static void
clutter_pipeline_node_init (ClutterPipelineNode *self)
{
}

/**
 * clutter_pipeline_node_new: (skip)
 * @pipeline: (allow-none): a Cogl pipeline state object, or %NULL
 *
 * Creates a new #ClutterPaintNode that will use the @pipeline to
 * paint its contents.
 *
 * This function will acquire a reference on the passed @pipeline,
 * so it is safe to call cogl_object_unref() when it returns.
 *
 * Return value: (transfer full): the newly created #ClutterPaintNode.
 *   Use clutter_paint_node_unref() when done.
 *
 * Since: 1.10
 */
ClutterPaintNode *
clutter_pipeline_node_new (CoglPipeline *pipeline)
{
  ClutterPipelineNode *res;

  g_return_val_if_fail (pipeline == NULL || cogl_is_pipeline (pipeline), NULL);

  res = _clutter_paint_node_create (CLUTTER_TYPE_PIPELINE_NODE);

  if (pipeline != NULL)
    res->pipeline = cogl_object_ref (pipeline);

  return (ClutterPaintNode *) res;
}

/*
 * Color node
 */

struct _ClutterColorNode
{
  ClutterPipelineNode parent_instance;
};

/**
 * ClutterColorNodeClass:
 *
 * The `ClutterColorNodeClass` structure is an
 * opaque type whose members cannot be directly accessed.
 *
 * Since: 1.10
 */
struct _ClutterColorNodeClass
{
  ClutterPipelineNodeClass parent_class;
};

G_DEFINE_TYPE (ClutterColorNode, clutter_color_node, CLUTTER_TYPE_PIPELINE_NODE)

static void
clutter_color_node_class_init (ClutterColorNodeClass *klass)
{

}

static void
clutter_color_node_init (ClutterColorNode *cnode)
{
  ClutterPipelineNode *pnode = CLUTTER_PIPELINE_NODE (cnode);

  g_assert (default_color_pipeline != NULL);
  pnode->pipeline = cogl_pipeline_copy (default_color_pipeline);
}

/**
 * clutter_color_node_new:
 * @color: (allow-none): the color to paint, or %NULL
 *
 * Creates a new #ClutterPaintNode that will paint a solid color
 * fill using @color.
 *
 * Return value: (transfer full): the newly created #ClutterPaintNode. Use
 *   clutter_paint_node_unref() when done
 *
 * Since: 1.10
 */
ClutterPaintNode *
clutter_color_node_new (const ClutterColor *color)
{
  ClutterPipelineNode *cnode;

  cnode = _clutter_paint_node_create (CLUTTER_TYPE_COLOR_NODE);

  if (color != NULL)
    {
      CoglColor cogl_color;

      cogl_color_init_from_4ub (&cogl_color,
                                color->red,
                                color->green,
                                color->blue,
                                color->alpha);
      cogl_color_premultiply (&cogl_color);

      cogl_pipeline_set_color (cnode->pipeline, &cogl_color);
    }

  return (ClutterPaintNode *) cnode;
}

/*
 * Texture node
 */

struct _ClutterTextureNode
{
  ClutterPipelineNode parent_instance;
};

/**
 * ClutterTextureNodeClass:
 *
 * The `ClutterTextureNodeClass` structure is an
 * opaque type whose members cannot be directly accessed.
 *
 * Since: 1.10
 */
struct _ClutterTextureNodeClass
{
  ClutterPipelineNodeClass parent_class;
};

G_DEFINE_TYPE (ClutterTextureNode, clutter_texture_node, CLUTTER_TYPE_PIPELINE_NODE)

static void
clutter_texture_node_class_init (ClutterTextureNodeClass *klass)
{
}

static void
clutter_texture_node_init (ClutterTextureNode *self)
{
  ClutterPipelineNode *pnode = CLUTTER_PIPELINE_NODE (self);

  g_assert (default_texture_pipeline != NULL);
  pnode->pipeline = cogl_pipeline_copy (default_texture_pipeline);
}

static CoglPipelineFilter
clutter_scaling_filter_to_cogl_pipeline_filter (ClutterScalingFilter filter)
{
  switch (filter)
    {
    case CLUTTER_SCALING_FILTER_NEAREST:
      return COGL_PIPELINE_FILTER_NEAREST;

    case CLUTTER_SCALING_FILTER_LINEAR:
      return COGL_PIPELINE_FILTER_LINEAR;

    case CLUTTER_SCALING_FILTER_TRILINEAR:
      return COGL_PIPELINE_FILTER_LINEAR_MIPMAP_LINEAR;
    }

  return COGL_PIPELINE_FILTER_LINEAR;
}

/**
 * clutter_texture_node_new:
 * @texture: a #CoglTexture
 * @color: (allow-none): a #ClutterColor used for blending, or %NULL
 * @min_filter: the minification filter for the texture
 * @mag_filter: the magnification filter for the texture
 *
 * Creates a new #ClutterPaintNode that will paint the passed @texture.
 *
 * This function will take a reference on @texture, so it is safe to
 * call cogl_object_unref() on @texture when it returns.
 *
 * The @color must not be pre-multiplied with its #ClutterColor.alpha
 * channel value; if @color is %NULL, a fully opaque white color will
 * be used for blending.
 *
 * Return value: (transfer full): the newly created #ClutterPaintNode.
 *   Use clutter_paint_node_unref() when done
 *
 * Since: 1.10
 */
ClutterPaintNode *
clutter_texture_node_new (CoglTexture          *texture,
                          const ClutterColor   *color,
                          ClutterScalingFilter  min_filter,
                          ClutterScalingFilter  mag_filter)
{
  ClutterPipelineNode *tnode;
  CoglColor cogl_color;
  CoglPipelineFilter min_f, mag_f;

  g_return_val_if_fail (cogl_is_texture (texture), NULL);

  tnode = _clutter_paint_node_create (CLUTTER_TYPE_TEXTURE_NODE);

  cogl_pipeline_set_layer_texture (tnode->pipeline, 0, texture);

  min_f = clutter_scaling_filter_to_cogl_pipeline_filter (min_filter);
  mag_f = clutter_scaling_filter_to_cogl_pipeline_filter (mag_filter);
  cogl_pipeline_set_layer_filters (tnode->pipeline, 0, min_f, mag_f);

  if (color != NULL)
    {
      cogl_color_init_from_4ub (&cogl_color,
                                color->red,
                                color->green,
                                color->blue,
                                color->alpha);
      cogl_color_premultiply (&cogl_color);
    }
  else
    cogl_color_init_from_4ub (&cogl_color, 255, 255, 255, 255);

  cogl_pipeline_set_color (tnode->pipeline, &cogl_color);

  return (ClutterPaintNode *) tnode;
}

/*
 * Text node
 */

struct _ClutterTextNode
{
  ClutterPaintNode parent_instance;

  PangoLayout *layout;
  CoglColor color;
};

/**
 * ClutterTextNodeClass:
 *
 * The `ClutterTextNodeClass` structure is an opaque
 * type whose contents cannot be directly accessed.
 *
 * Since: 1.10
 */
struct _ClutterTextNodeClass
{
  ClutterPaintNodeClass parent_class;
};

G_DEFINE_TYPE (ClutterTextNode, clutter_text_node, CLUTTER_TYPE_PAINT_NODE)

static void
clutter_text_node_finalize (ClutterPaintNode *node)
{
  ClutterTextNode *tnode = CLUTTER_TEXT_NODE (node);

  if (tnode->layout != NULL)
    g_object_unref (tnode->layout);

  CLUTTER_PAINT_NODE_CLASS (clutter_text_node_parent_class)->finalize (node);
}

static gboolean
clutter_text_node_pre_draw (ClutterPaintNode    *node,
                            ClutterPaintContext *paint_context)
{
  ClutterTextNode *tnode = CLUTTER_TEXT_NODE (node);

  return tnode->layout != NULL;
}

static void
clutter_text_node_draw (ClutterPaintNode    *node,
                        ClutterPaintContext *paint_context)
{
  ClutterTextNode *tnode = CLUTTER_TEXT_NODE (node);
  PangoRectangle extents;
  CoglFramebuffer *fb;
  guint i;

  if (node->operations == NULL)
    return;

  fb = get_target_framebuffer (node, paint_context);

  pango_layout_get_pixel_extents (tnode->layout, NULL, &extents);

  for (i = 0; i < node->operations->len; i++)
    {
      const ClutterPaintOperation *op;
      float op_width, op_height;
      gboolean clipped = FALSE;

      op = &g_array_index (node->operations, ClutterPaintOperation, i);

      switch (op->opcode)
        {
        case PAINT_OP_TEX_RECT:
          op_width = op->op.texrect[2] - op->op.texrect[0];
          op_height = op->op.texrect[3] - op->op.texrect[1];

          /* if the primitive size was smaller than the layout,
           * we clip the layout when drawin, to avoid spilling
           * it out
           */
          if (extents.width > op_width ||
              extents.height > op_height)
            {
              cogl_framebuffer_push_rectangle_clip (fb,
                                                    op->op.texrect[0],
                                                    op->op.texrect[1],
                                                    op->op.texrect[2],
                                                    op->op.texrect[3]);
              clipped = TRUE;
            }

          cogl_pango_show_layout (fb,
                                  tnode->layout,
                                  op->op.texrect[0],
                                  op->op.texrect[1],
                                  &tnode->color);

          if (clipped)
            cogl_framebuffer_pop_clip (fb);
          break;

        case PAINT_OP_MULTITEX_RECT:
        case PAINT_OP_PATH:
        case PAINT_OP_PRIMITIVE:
        case PAINT_OP_INVALID:
          break;
        }
    }
}

static JsonNode *
clutter_text_node_serialize (ClutterPaintNode *node)
{
  ClutterTextNode *tnode = CLUTTER_TEXT_NODE (node);
  JsonBuilder *builder;
  JsonNode *res;

  builder = json_builder_new ();

  json_builder_begin_object (builder);

  json_builder_set_member_name (builder, "layout");

  if (pango_layout_get_character_count (tnode->layout) > 12)
    {
      const char *text = pango_layout_get_text (tnode->layout);
      char *str;

      str = g_strndup (text, 12);
      json_builder_add_string_value (builder, str);
      g_free (str);
    }
  else
    json_builder_add_string_value (builder, pango_layout_get_text (tnode->layout));

  json_builder_set_member_name (builder, "color");
  json_builder_begin_array (builder);
  json_builder_add_double_value (builder, cogl_color_get_red (&tnode->color));
  json_builder_add_double_value (builder, cogl_color_get_green (&tnode->color));
  json_builder_add_double_value (builder, cogl_color_get_blue (&tnode->color));
  json_builder_add_double_value (builder, cogl_color_get_alpha (&tnode->color));
  json_builder_end_array (builder);

  json_builder_end_object (builder);

  res = json_builder_get_root (builder);
  g_object_unref (builder);

  return res;
}

static void
clutter_text_node_class_init (ClutterTextNodeClass *klass)
{
  ClutterPaintNodeClass *node_class = CLUTTER_PAINT_NODE_CLASS (klass);

  node_class->pre_draw = clutter_text_node_pre_draw;
  node_class->draw = clutter_text_node_draw;
  node_class->finalize = clutter_text_node_finalize;
  node_class->serialize = clutter_text_node_serialize;
}

static void
clutter_text_node_init (ClutterTextNode *self)
{
  cogl_color_init_from_4f (&self->color, 0.0, 0.0, 0.0, 1.0);
}

/**
 * clutter_text_node_new:
 * @layout: (allow-none): a #PangoLayout, or %NULL
 * @color: (allow-none): the color used to paint the layout,
 *   or %NULL
 *
 * Creates a new #ClutterPaintNode that will paint a #PangoLayout
 * with the given color.
 *
 * This function takes a reference on the passed @layout, so it
 * is safe to call g_object_unref() after it returns.
 *
 * Return value: (transfer full): the newly created #ClutterPaintNode.
 *   Use clutter_paint_node_unref() when done
 *
 * Since: 1.10
 */
ClutterPaintNode *
clutter_text_node_new (PangoLayout        *layout,
                       const ClutterColor *color)
{
  ClutterTextNode *res;

  g_return_val_if_fail (layout == NULL || PANGO_IS_LAYOUT (layout), NULL);

  res = _clutter_paint_node_create (CLUTTER_TYPE_TEXT_NODE);

  if (layout != NULL)
    res->layout = g_object_ref (layout);

  if (color != NULL)
    {
      cogl_color_init_from_4ub (&res->color,
                                color->red,
                                color->green,
                                color->blue,
                                color->alpha);
    }

  return (ClutterPaintNode *) res;
}

/*
 * Clip node
 */
struct _ClutterClipNode
{
  ClutterPaintNode parent_instance;
};

/**
 * ClutterClipNodeClass:
 *
 * The `ClutterClipNodeClass` structure is an opaque
 * type whose members cannot be directly accessed.
 *
 * Since: 1.10
 */
struct _ClutterClipNodeClass
{
  ClutterPaintNodeClass parent_class;
};

G_DEFINE_TYPE (ClutterClipNode, clutter_clip_node, CLUTTER_TYPE_PAINT_NODE)

static gboolean
clutter_clip_node_pre_draw (ClutterPaintNode    *node,
                            ClutterPaintContext *paint_context)
{
  gboolean retval = FALSE;
  CoglFramebuffer *fb;
  guint i;

  if (node->operations == NULL)
    return FALSE;

  fb = get_target_framebuffer (node, paint_context);

  for (i = 0; i < node->operations->len; i++)
    {
      const ClutterPaintOperation *op;

      op = &g_array_index (node->operations, ClutterPaintOperation, i);

      switch (op->opcode)
        {
        case PAINT_OP_TEX_RECT:
          cogl_framebuffer_push_rectangle_clip (fb,
                                                op->op.texrect[0],
                                                op->op.texrect[1],
                                                op->op.texrect[2],
                                                op->op.texrect[3]);
          retval = TRUE;
          break;

        case PAINT_OP_PATH:
          cogl_framebuffer_push_path_clip (fb, op->op.path);
          retval = TRUE;
          break;

        case PAINT_OP_MULTITEX_RECT:
        case PAINT_OP_PRIMITIVE:
        case PAINT_OP_INVALID:
          break;
        }
    }

  return retval;
}

static void
clutter_clip_node_post_draw (ClutterPaintNode    *node,
                             ClutterPaintContext *paint_context)
{
  CoglFramebuffer *fb;
  guint i;

  if (node->operations == NULL)
    return;

  fb = get_target_framebuffer (node, paint_context);

  for (i = 0; i < node->operations->len; i++)
    {
      const ClutterPaintOperation *op;

      op = &g_array_index (node->operations, ClutterPaintOperation, i);

      switch (op->opcode)
        {
        case PAINT_OP_PATH:
        case PAINT_OP_TEX_RECT:
          cogl_framebuffer_pop_clip (fb);
          break;

        case PAINT_OP_MULTITEX_RECT:
        case PAINT_OP_PRIMITIVE:
        case PAINT_OP_INVALID:
          break;
        }
    }
}

static void
clutter_clip_node_class_init (ClutterClipNodeClass *klass)
{
  ClutterPaintNodeClass *node_class;

  node_class = CLUTTER_PAINT_NODE_CLASS (klass);
  node_class->pre_draw = clutter_clip_node_pre_draw;
  node_class->post_draw = clutter_clip_node_post_draw;
}

static void
clutter_clip_node_init (ClutterClipNode *self)
{
}

/**
 * clutter_clip_node_new:
 *
 * Creates a new #ClutterPaintNode that will clip its child
 * nodes to the 2D regions added to it.
 *
 * Return value: (transfer full): the newly created #ClutterPaintNode.
 *   Use clutter_paint_node_unref() when done.
 *
 * Since: 1.10
 */
ClutterPaintNode *
clutter_clip_node_new (void)
{
  return _clutter_paint_node_create (CLUTTER_TYPE_CLIP_NODE);
}

/*
 * ClutterActorNode
 */

struct _ClutterActorNode
{
  ClutterPaintNode parent_instance;

  ClutterActor *actor;
};

struct _ClutterActorNodeClass
{
  ClutterPaintNodeClass parent_class;
};

G_DEFINE_TYPE (ClutterActorNode, clutter_actor_node, CLUTTER_TYPE_PAINT_NODE)

static gboolean
clutter_actor_node_pre_draw (ClutterPaintNode    *node,
                             ClutterPaintContext *paint_context)
{
  ClutterActorNode *actor_node = CLUTTER_ACTOR_NODE (node);

  CLUTTER_SET_PRIVATE_FLAGS (actor_node->actor, CLUTTER_IN_PAINT);

  return TRUE;
}

static void
clutter_actor_node_draw (ClutterPaintNode    *node,
                         ClutterPaintContext *paint_context)
{
  ClutterActorNode *actor_node = CLUTTER_ACTOR_NODE (node);

  clutter_actor_continue_paint (actor_node->actor, paint_context);
}

static void
clutter_actor_node_post_draw (ClutterPaintNode    *node,
                              ClutterPaintContext *paint_context)
{
  ClutterActorNode *actor_node = CLUTTER_ACTOR_NODE (node);

  CLUTTER_UNSET_PRIVATE_FLAGS (actor_node->actor, CLUTTER_IN_PAINT);
}

static JsonNode *
clutter_actor_node_serialize (ClutterPaintNode *node)
{
  ClutterActorNode *actor_node = CLUTTER_ACTOR_NODE (node);
  g_autoptr (JsonBuilder) builder = NULL;
  const char *debug_name;

  debug_name = _clutter_actor_get_debug_name (actor_node->actor);

  builder = json_builder_new ();

  json_builder_begin_object (builder);
  json_builder_set_member_name (builder, "actor");
  json_builder_add_string_value (builder, debug_name);
  json_builder_end_object (builder);

  return json_builder_get_root (builder);
}

static void
clutter_actor_node_class_init (ClutterActorNodeClass *klass)
{
  ClutterPaintNodeClass *node_class;

  node_class = CLUTTER_PAINT_NODE_CLASS (klass);
  node_class->pre_draw = clutter_actor_node_pre_draw;
  node_class->draw = clutter_actor_node_draw;
  node_class->post_draw = clutter_actor_node_post_draw;
  node_class->serialize = clutter_actor_node_serialize;
}

static void
clutter_actor_node_init (ClutterActorNode *self)
{
}

/*
 * clutter_actor_node_new:
 * @actor: the actor to paint
 *
 * Creates a new #ClutterActorNode.
 *
 * The actor is painted together with any effects
 * applied to it. Children of this node will draw
 * over the actor contents.
 *
 * Return value: (transfer full): the newly created #ClutterActorNode.
 *   Use clutter_paint_node_unref() when done.
 */
ClutterPaintNode *
clutter_actor_node_new (ClutterActor *actor)
{
  ClutterActorNode *res;

  g_assert (actor != NULL);

  res = _clutter_paint_node_create (CLUTTER_TYPE_ACTOR_NODE);
  res->actor = actor;

  return (ClutterPaintNode *) res;
}


/*
 * ClutterLayerNode
 */

struct _ClutterLayerNode
{
  ClutterPaintNode parent_instance;

  cairo_rectangle_t viewport;

  CoglMatrix projection;

  float fbo_width;
  float fbo_height;

  CoglPipeline *state;
  CoglFramebuffer *offscreen;
  CoglTexture *texture;

  guint8 opacity;
};

struct _ClutterLayerNodeClass
{
  ClutterPaintNodeClass parent_class;
};

G_DEFINE_TYPE (ClutterLayerNode, clutter_layer_node, CLUTTER_TYPE_PAINT_NODE)

static gboolean
clutter_layer_node_pre_draw (ClutterPaintNode *node,
                             ClutterPaintContext *paint_context)
{
  ClutterLayerNode *lnode = (ClutterLayerNode *) node;
  CoglFramebuffer *framebuffer;
  CoglMatrix matrix;

  /* if we were unable to create an offscreen buffer for this node, then
   * we simply ignore it
   */
  if (lnode->offscreen == NULL)
    return FALSE;

  /* if no geometry was submitted for this node then we simply ignore it */
  if (node->operations == NULL)
    return FALSE;

  /* copy the same modelview from the current framebuffer to the one we
   * are going to use
   */
  framebuffer = clutter_paint_context_get_framebuffer (paint_context);
  cogl_framebuffer_get_modelview_matrix (framebuffer, &matrix);

  clutter_paint_context_push_framebuffer (paint_context, lnode->offscreen);

  cogl_framebuffer_set_modelview_matrix (lnode->offscreen, &matrix);

  cogl_framebuffer_set_viewport (lnode->offscreen,
                                 lnode->viewport.x,
                                 lnode->viewport.y,
                                 lnode->viewport.width,
                                 lnode->viewport.height);

  cogl_framebuffer_set_projection_matrix (lnode->offscreen,
                                          &lnode->projection);

  /* clear out the target framebuffer */
  cogl_framebuffer_clear4f (lnode->offscreen,
                            COGL_BUFFER_BIT_COLOR | COGL_BUFFER_BIT_DEPTH,
                            0.f, 0.f, 0.f, 0.f);

  cogl_framebuffer_push_matrix (lnode->offscreen);

  /* every draw operation after this point will happen an offscreen
   * framebuffer
   */

  return TRUE;
}

static void
clutter_layer_node_post_draw (ClutterPaintNode    *node,
                              ClutterPaintContext *paint_context)
{
  ClutterLayerNode *lnode = CLUTTER_LAYER_NODE (node);
  CoglFramebuffer *fb;
  guint i;

  /* switch to the previous framebuffer */
  cogl_framebuffer_pop_matrix (lnode->offscreen);
  clutter_paint_context_pop_framebuffer (paint_context);

  fb = clutter_paint_context_get_framebuffer (paint_context);

  for (i = 0; i < node->operations->len; i++)
    {
      const ClutterPaintOperation *op;

      op = &g_array_index (node->operations, ClutterPaintOperation, i);
      switch (op->opcode)
        {
        case PAINT_OP_INVALID:
          break;

        case PAINT_OP_TEX_RECT:
          /* now we need to paint the texture */
          cogl_framebuffer_draw_textured_rectangle (fb,
                                                    lnode->state,
                                                    op->op.texrect[0],
                                                    op->op.texrect[1],
                                                    op->op.texrect[2],
                                                    op->op.texrect[3],
                                                    op->op.texrect[4],
                                                    op->op.texrect[5],
                                                    op->op.texrect[6],
                                                    op->op.texrect[7]);
          break;

        case PAINT_OP_MULTITEX_RECT:
          cogl_framebuffer_draw_multitextured_rectangle (fb,
                                                         lnode->state,
                                                         op->op.texrect[0],
                                                         op->op.texrect[1],
                                                         op->op.texrect[2],
                                                         op->op.texrect[3],
                                                         (float*) op->multitex_coords->data,
                                                         op->multitex_coords->len);
          break;

        case PAINT_OP_PATH:
          cogl_framebuffer_fill_path (fb, lnode->state, op->op.path);
          break;

        case PAINT_OP_PRIMITIVE:
          cogl_framebuffer_draw_primitive (fb, lnode->state, op->op.primitive);
          break;
        }
    }
}

static void
clutter_layer_node_finalize (ClutterPaintNode *node)
{
  ClutterLayerNode *lnode = CLUTTER_LAYER_NODE (node);

  if (lnode->state != NULL)
    cogl_object_unref (lnode->state);

  if (lnode->offscreen != NULL)
    cogl_object_unref (lnode->offscreen);

  CLUTTER_PAINT_NODE_CLASS (clutter_layer_node_parent_class)->finalize (node);
}

static void
clutter_layer_node_class_init (ClutterLayerNodeClass *klass)
{
  ClutterPaintNodeClass *node_class;

  node_class = CLUTTER_PAINT_NODE_CLASS (klass);
  node_class->pre_draw = clutter_layer_node_pre_draw;
  node_class->post_draw = clutter_layer_node_post_draw;
  node_class->finalize = clutter_layer_node_finalize;
}

static void
clutter_layer_node_init (ClutterLayerNode *self)
{
  cogl_matrix_init_identity (&self->projection);
}

/*
 * clutter_layer_node_new:
 * @projection: the projection matrix to use to set up the layer
 * @viewport: (type cairo.Rectangle): the viewport to use to set up the layer
 * @width: the width of the layer
 * @height: the height of the layer
 * @opacity: the opacity to be used when drawing the layer
 *
 * Creates a new #ClutterLayerNode.
 *
 * All children of this node will be painted inside a separate
 * framebuffer; the framebuffer will then be painted using the
 * given @opacity.
 *
 * Return value: (transfer full): the newly created #ClutterLayerNode.
 *   Use clutter_paint_node_unref() when done.
 *
 * Since: 1.10
 */
ClutterPaintNode *
clutter_layer_node_new (const CoglMatrix        *projection,
                        const cairo_rectangle_t *viewport,
                        float                    width,
                        float                    height,
                        guint8                   opacity)
{
  ClutterLayerNode *res;
  CoglColor color;

  res = _clutter_paint_node_create (CLUTTER_TYPE_LAYER_NODE);

  res->projection = *projection;
  res->viewport = *viewport;
  res->fbo_width = width;
  res->fbo_height = height;
  res->opacity = opacity;

  /* the texture backing the FBO */
  res->texture = cogl_texture_new_with_size (MAX (res->fbo_width, 1),
                                             MAX (res->fbo_height, 1),
                                             COGL_TEXTURE_NO_SLICING,
                                             COGL_PIXEL_FORMAT_RGBA_8888_PRE);

  res->offscreen = COGL_FRAMEBUFFER (cogl_offscreen_new_to_texture (res->texture));
  if (res->offscreen == NULL)
    {
      g_critical ("%s: Unable to create an offscreen buffer", G_STRLOC);

      cogl_object_unref (res->texture);
      res->texture = NULL;

      goto out;
    }

  cogl_color_init_from_4ub (&color, opacity, opacity, opacity, opacity);

  /* the pipeline used to paint the texture; we use nearest
   * interpolation filters because the texture is always
   * going to be painted at a 1:1 texel:pixel ratio
   */
  res->state = cogl_pipeline_copy (default_texture_pipeline);
  cogl_pipeline_set_layer_filters (res->state, 0,
                                   COGL_PIPELINE_FILTER_NEAREST,
                                   COGL_PIPELINE_FILTER_NEAREST);
  cogl_pipeline_set_layer_texture (res->state, 0, res->texture);
  cogl_pipeline_set_color (res->state, &color);
  cogl_object_unref (res->texture);

out:
  return (ClutterPaintNode *) res;
}
