/*
 * 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>
 */


#include "clutter/clutter-build-config.h"

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

#include <pango/pango.h>

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

#include "clutter/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 (ClutterBackend *clutter_backend)
{
  CoglContext *cogl_context;
  CoglColor cogl_color;
  GType node_type G_GNUC_UNUSED;

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

  cogl_context = clutter_backend_get_cogl_context (clutter_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 (cogl_context);
  cogl_pipeline_set_color (default_color_pipeline, &cogl_color);

  default_texture_pipeline = cogl_pipeline_new (cogl_context);
  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);
}

/*
 * ClutterRootNode:
 *
 * 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;

  g_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 = g_object_ref (framebuffer);
  res->clear_flags = clear_flags;

  return (ClutterPaintNode *) res;
}

/*
 * ClutterTransformNode:
 */

struct _ClutterTransformNode
{
  ClutterPaintNode parent_instance;

  graphene_matrix_t 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)
{
  graphene_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 graphene_matrix_t *transform)
{
  ClutterTransformNode *res;

  res = _clutter_paint_node_create (CLUTTER_TYPE_TRANSFORM_NODE);
  if (transform)
    graphene_matrix_init_from_matrix (&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;

  g_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 = g_object_ref (framebuffer);

  return res;
}

/**
 * ClutterPipelineNode:
 */

struct _ClutterPipelineNode
{
  ClutterPaintNode parent_instance;

  CoglPipeline *pipeline;
};

/**
 * ClutterPipelineNodeClass:
 *
 * The `ClutterPipelineNodeClass` structure is an opaque
 * type whose members cannot be directly accessed.
 */
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_TEX_RECTS:
          cogl_framebuffer_draw_textured_rectangles (fb,
                                                     pnode->pipeline,
                                                     (float *) op->coords->data,
                                                     op->coords->len / 8);
          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->coords->data,
                                                         op->coords->len);
          break;

        case PAINT_OP_PRIMITIVE:
          cogl_primitive_draw (op->op.primitive,
                               fb,
                               pnode->pipeline);
          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:
 * @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.
 */
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;
}

/**
 * ClutterColorNode:
 */

struct _ClutterColorNode
{
  ClutterPipelineNode parent_instance;
};

/**
 * ClutterColorNodeClass:
 *
 * The `ClutterColorNodeClass` structure is an
 * opaque type whose members cannot be directly accessed.
 */
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
 */
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;
}

/**
 * ClutterTextureNode:
 *
 */

struct _ClutterTextureNode
{
  ClutterPipelineNode parent_instance;
};

/**
 * ClutterTextureNodeClass:
 *
 * The `ClutterTextureNodeClass` structure is an
 * opaque type whose members cannot be directly accessed.
 */
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
 */
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;
}


/**
 * ClutterTextNode:
 */
struct _ClutterTextNode
{
  ClutterPaintNode parent_instance;

  PangoLayout *layout;
  CoglColor color;
};

/**
 * ClutterTextNodeClass:
 *
 * The `ClutterTextNodeClass` structure is an opaque
 * type whose contents cannot be directly accessed.
 */
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_TEX_RECTS:
        case PAINT_OP_MULTITEX_RECT:
        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
 */
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;
}

/**
 * ClutterClipNode:
 */
struct _ClutterClipNode
{
  ClutterPaintNode parent_instance;
};

/**
 * ClutterClipNodeClass:
 *
 * The `ClutterClipNodeClass` structure is an opaque
 * type whose members cannot be directly accessed.
 */
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_TEX_RECTS:
        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_TEX_RECT:
          cogl_framebuffer_pop_clip (fb);
          break;

        case PAINT_OP_TEX_RECTS:
        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.
 */
ClutterPaintNode *
clutter_clip_node_new (void)
{
  return _clutter_paint_node_create (CLUTTER_TYPE_CLIP_NODE);
}

/**
 * ClutterActorNode:
 */

struct _ClutterActorNode
{
  ClutterPaintNode parent_instance;

  ClutterActor *actor;
  int opacity_override;
  int saved_opacity_override;
};

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);

  if (actor_node->opacity_override != -1)
    {
      actor_node->saved_opacity_override =
        clutter_actor_get_opacity_override (actor_node->actor);
      clutter_actor_set_opacity_override (actor_node->actor,
                                          actor_node->opacity_override);
    }

  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);

 if (actor_node->opacity_override != -1)
    {
      clutter_actor_set_opacity_override (actor_node->actor,
                                          actor_node->saved_opacity_override);
    }
}

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
 * @opacity: opacity to draw the actor with, or -1 to use the actor's opacity
 *
 * 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,
                        int           opacity)
{
  ClutterActorNode *res;

  g_assert (actor != NULL);

  res = _clutter_paint_node_create (CLUTTER_TYPE_ACTOR_NODE);
  res->actor = actor;
  res->opacity_override = CLAMP (opacity, -1, 255);

  return (ClutterPaintNode *) res;
}


/*
 * ClutterEffectNode
 */

struct _ClutterEffectNode
{
  ClutterPaintNode parent_instance;

  ClutterEffect *effect;
};

struct _ClutterEffectNodeClass
{
  ClutterPaintNodeClass parent_class;
};

G_DEFINE_TYPE (ClutterEffectNode, clutter_effect_node, CLUTTER_TYPE_PAINT_NODE)

static JsonNode *
clutter_effect_node_serialize (ClutterPaintNode *node)
{
  ClutterEffectNode *effect_node = CLUTTER_EFFECT_NODE (node);
  ClutterActorMeta *effect_meta = CLUTTER_ACTOR_META (effect_node->effect);
  g_autoptr (JsonBuilder) builder = NULL;
  g_autoptr (GString) string = NULL;
  const char *meta_name;

  meta_name = clutter_actor_meta_get_name (effect_meta);

  string = g_string_new (NULL);
  g_string_append (string, G_OBJECT_TYPE_NAME (effect_node->effect));
  g_string_append (string, " (");
  if (meta_name)
    g_string_append_printf (string, "\"%s\"", meta_name);
  else
    g_string_append (string, "unnamed");
  g_string_append (string, ")");

  builder = json_builder_new ();

  json_builder_begin_object (builder);
  json_builder_set_member_name (builder, "effect");
  json_builder_add_string_value (builder, string->str);
  json_builder_end_object (builder);

  return json_builder_get_root (builder);
}

static void
clutter_effect_node_class_init (ClutterEffectNodeClass *klass)
{
  ClutterPaintNodeClass *node_class;

  node_class = CLUTTER_PAINT_NODE_CLASS (klass);
  node_class->serialize = clutter_effect_node_serialize;
}

static void
clutter_effect_node_init (ClutterEffectNode *self)
{
}

/**
 * clutter_effect_node_new:
 * @effect: the actor to paint
 *
 * Creates a new #ClutterEffectNode.
 *
 * Return value: (transfer full): the newly created #ClutterEffectNode.
 *   Use clutter_paint_node_unref() when done.
 */
ClutterPaintNode *
clutter_effect_node_new (ClutterEffect *effect)
{
  ClutterEffectNode *res;

  g_assert (CLUTTER_IS_EFFECT (effect));

  res = _clutter_paint_node_create (CLUTTER_TYPE_EFFECT_NODE);
  res->effect = effect;

  return (ClutterPaintNode *) res;
}

/*
 * ClutterLayerNode
 */

struct _ClutterLayerNode
{
  ClutterPaintNode parent_instance;

  cairo_rectangle_t viewport;

  graphene_matrix_t projection;

  float fbo_width;
  float fbo_height;

  CoglPipeline *pipeline;
  CoglFramebuffer *offscreen;

  guint8 opacity;

  gboolean needs_fbo_setup : 1;
};

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;
  graphene_matrix_t 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 (lnode->needs_fbo_setup)
    {
      /* 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);
      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);
    }

  clutter_paint_context_push_framebuffer (paint_context, lnode->offscreen);

  /* 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);

  if (!node->operations)
    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:
          /* now we need to paint the texture */
          cogl_framebuffer_draw_textured_rectangle (fb,
                                                    lnode->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_TEX_RECTS:
          cogl_framebuffer_draw_textured_rectangles (fb,
                                                     lnode->pipeline,
                                                     (float *) op->coords->data,
                                                     op->coords->len / 8);
          break;

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

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

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

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

  g_clear_object (&lnode->offscreen);

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

static JsonNode *
clutter_layer_node_serialize (ClutterPaintNode *node)
{
  ClutterLayerNode *layer_node = CLUTTER_LAYER_NODE (node);
  g_autoptr (JsonBuilder) builder = NULL;
  g_autofree char *framebuffer_ptr = NULL;

  builder = json_builder_new ();

  framebuffer_ptr = g_strdup_printf ("%p", layer_node->offscreen);

  json_builder_begin_object (builder);
  json_builder_set_member_name (builder, "framebuffer");
  json_builder_add_string_value (builder, framebuffer_ptr);
  json_builder_end_object (builder);

  return json_builder_get_root (builder);
}

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;
  node_class->serialize = clutter_layer_node_serialize;
}

static void
clutter_layer_node_init (ClutterLayerNode *self)
{
  graphene_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.
 */
ClutterPaintNode *
clutter_layer_node_new (const graphene_matrix_t *projection,
                        const cairo_rectangle_t *viewport,
                        float                    width,
                        float                    height,
                        guint8                   opacity)
{
  ClutterLayerNode *lnode;
  CoglContext *context;
  CoglTexture2D *tex_2d;
  CoglTexture *texture;
  CoglColor color;
  g_autoptr (CoglOffscreen) offscreen = NULL;
  g_autoptr (GError) error = NULL;

  lnode = _clutter_paint_node_create (CLUTTER_TYPE_LAYER_NODE);

  lnode->needs_fbo_setup = TRUE;
  lnode->projection = *projection;
  lnode->viewport = *viewport;
  lnode->fbo_width = width;
  lnode->fbo_height = height;
  lnode->opacity = opacity;

  /* the texture backing the FBO */
  context = clutter_backend_get_cogl_context (clutter_get_default_backend ());

  tex_2d = cogl_texture_2d_new_with_size (context,
                                          MAX (lnode->fbo_width, 1),
                                          MAX (lnode->fbo_height, 1));
  texture = COGL_TEXTURE (tex_2d);
  cogl_texture_set_premultiplied (texture, TRUE);

  offscreen = cogl_offscreen_new_with_texture (texture);
  if (!cogl_framebuffer_allocate (COGL_FRAMEBUFFER (offscreen), &error))
    {
      g_warning ("Unable to create an allocate paint node offscreen: %s",
                 error->message);
      cogl_object_unref (texture);
      return NULL;
    }

  lnode->offscreen = COGL_FRAMEBUFFER (g_steal_pointer (&offscreen));

  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
   */
  lnode->pipeline = cogl_pipeline_copy (default_texture_pipeline);
  cogl_pipeline_set_layer_filters (lnode->pipeline, 0,
                                   COGL_PIPELINE_FILTER_NEAREST,
                                   COGL_PIPELINE_FILTER_NEAREST);
  cogl_pipeline_set_layer_texture (lnode->pipeline, 0, texture);
  cogl_pipeline_set_color (lnode->pipeline, &color);

  cogl_object_unref (texture);

  return (ClutterPaintNode *) lnode;
}

/**
 * clutter_layer_node_new_to_framebuffer:
 * @framebuffer: a #CoglFramebuffer
 * @pipeline: a #CoglPipeline
 *
 * Creates a new #ClutterLayerNode that will redirect drawing at
 * @framebuffer. It will then use @pipeline to paint the stored
 * operations.
 *
 * When using this constructor, the caller is responsible for setting
 * up @framebuffer, including its modelview and projection matrices,
 * and the viewport, and the @pipeline as well.
 *
 * Return value: (transfer full): the newly created #ClutterLayerNode.
 *   Use clutter_paint_node_unref() when done.
 */
ClutterPaintNode *
clutter_layer_node_new_to_framebuffer (CoglFramebuffer *framebuffer,
                                       CoglPipeline    *pipeline)
{
  ClutterLayerNode *res;

  g_return_val_if_fail (COGL_IS_FRAMEBUFFER (framebuffer), NULL);
  g_return_val_if_fail (cogl_is_pipeline (pipeline), NULL);

  res = _clutter_paint_node_create (CLUTTER_TYPE_LAYER_NODE);

  res->needs_fbo_setup = FALSE;
  res->fbo_width = cogl_framebuffer_get_width (framebuffer);
  res->fbo_height = cogl_framebuffer_get_height (framebuffer);
  res->offscreen = g_object_ref (framebuffer);
  res->pipeline = cogl_pipeline_copy (pipeline);

  return (ClutterPaintNode *) res;
}

/*
 * ClutterBlitNode
 */

struct _ClutterBlitNode
{
  ClutterPaintNode parent_instance;

  CoglFramebuffer *src;
};

G_DEFINE_TYPE (ClutterBlitNode, clutter_blit_node, CLUTTER_TYPE_PAINT_NODE)

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

static void
clutter_blit_node_draw (ClutterPaintNode    *node,
                        ClutterPaintContext *paint_context)
{
  ClutterBlitNode *blit_node = CLUTTER_BLIT_NODE (node);
  g_autoptr (GError) error = NULL;
  CoglFramebuffer *framebuffer;
  unsigned int i;

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

  framebuffer = get_target_framebuffer (node, paint_context);

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

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

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

        case PAINT_OP_TEX_RECT:
          op_width = op->op.texrect[6] - op->op.texrect[4];
          op_height = op->op.texrect[7] - op->op.texrect[5];

          cogl_blit_framebuffer (blit_node->src,
                                 framebuffer,
                                 op->op.texrect[0],
                                 op->op.texrect[1],
                                 op->op.texrect[4],
                                 op->op.texrect[5],
                                 op_width,
                                 op_height,
                                 &error);

          if (error)
            {
              g_warning ("Error blitting framebuffers: %s", error->message);
              return;
            }
          break;

        case PAINT_OP_TEX_RECTS:
        case PAINT_OP_MULTITEX_RECT:
        case PAINT_OP_PRIMITIVE:
          break;
        }
    }
}

static void
clutter_blit_node_finalize (ClutterPaintNode *node)
{
  ClutterBlitNode *blit_node = CLUTTER_BLIT_NODE (node);

  g_clear_object (&blit_node->src);

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

static JsonNode *
clutter_blit_node_serialize (ClutterPaintNode *node)
{
  ClutterBlitNode *blit_node = CLUTTER_BLIT_NODE (node);
  g_autoptr (JsonBuilder) builder = NULL;
  g_autofree char *src_ptr = NULL;

  src_ptr = g_strdup_printf ("%p", blit_node->src);

  builder = json_builder_new ();
  json_builder_begin_object (builder);
  json_builder_set_member_name (builder, "source");
  json_builder_add_string_value (builder, src_ptr);
  json_builder_end_object (builder);

  return json_builder_get_root (builder);
}

static void
clutter_blit_node_class_init (ClutterBlitNodeClass *klass)
{
  ClutterPaintNodeClass *node_class;

  node_class = CLUTTER_PAINT_NODE_CLASS (klass);
  node_class->pre_draw = clutter_blit_node_pre_draw;
  node_class->draw = clutter_blit_node_draw;
  node_class->finalize = clutter_blit_node_finalize;
  node_class->serialize = clutter_blit_node_serialize;
}

static void
clutter_blit_node_init (ClutterBlitNode *self)
{
}

/**
 * clutter_blit_node_new:
 * @src: the source #CoglFramebuffer
 *
 * Creates a new #ClutterBlitNode that blits @src into the current
 * draw framebuffer.
 *
 * You must only add rectangles using [method@BlitNode.add_blit_rectangle].
 *
 * Return value: (transfer full): the newly created #ClutterBlitNode.
 *   Use clutter_paint_node_unref() when done.
 */
ClutterPaintNode *
clutter_blit_node_new (CoglFramebuffer *src)
{
  ClutterBlitNode *res;

  g_return_val_if_fail (COGL_IS_FRAMEBUFFER (src), NULL);

  res = _clutter_paint_node_create (CLUTTER_TYPE_BLIT_NODE);
  res->src = g_object_ref (src);

  return (ClutterPaintNode *) res;
}

/**
 * clutter_blit_node_add_blit_rectangle:
 * @blit_node: a #ClutterBlitNode
 * @src_x: Source x position
 * @src_y: Source y position
 * @dst_x: Destination x position
 * @dst_y: Destination y position
 * @width: Width of region to copy
 * @height: Height of region to copy
 *
 * Adds a new blit rectangle to the stack of rectangles. All the
 * constraints of [func@Cogl.blit_framebuffer] apply here.
 */
void
clutter_blit_node_add_blit_rectangle (ClutterBlitNode *blit_node,
                                      int              src_x,
                                      int              src_y,
                                      int              dst_x,
                                      int              dst_y,
                                      int              width,
                                      int              height)
{
  g_return_if_fail (CLUTTER_IS_BLIT_NODE (blit_node));

  clutter_paint_node_add_texture_rectangle (CLUTTER_PAINT_NODE (blit_node),
                                            &(ClutterActorBox) {
                                              .x1 = src_x,
                                              .y1 = src_y,
                                              .x2 = src_x + width,
                                              .y2 = src_y + height,
                                            },
                                            dst_x,
                                            dst_y,
                                            dst_x + width,
                                            dst_y + height);
}

/**
 * ClutterBlurNode:
 */

struct _ClutterBlurNode
{
  ClutterLayerNode parent_instance;

  ClutterBlur *blur;
  unsigned int sigma;
};

G_DEFINE_TYPE (ClutterBlurNode, clutter_blur_node, CLUTTER_TYPE_LAYER_NODE)

static void
clutter_blur_node_post_draw (ClutterPaintNode    *node,
                             ClutterPaintContext *paint_context)
{
  ClutterPaintNodeClass *parent_class =
    CLUTTER_PAINT_NODE_CLASS (clutter_blur_node_parent_class);
  ClutterBlurNode *blur_node = CLUTTER_BLUR_NODE (node);

  clutter_blur_apply (blur_node->blur);

  parent_class->post_draw (node, paint_context);
}

static void
clutter_blur_node_finalize (ClutterPaintNode *node)
{
  ClutterBlurNode *blur_node = CLUTTER_BLUR_NODE (node);

  g_clear_pointer (&blur_node->blur, clutter_blur_free);

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

static JsonNode *
clutter_blur_node_serialize (ClutterPaintNode *node)
{
  ClutterBlurNode *blur_node = CLUTTER_BLUR_NODE (node);
  g_autoptr (JsonBuilder) builder = NULL;
  g_autofree char *src_ptr = NULL;

  src_ptr = g_strdup_printf ("%d", blur_node->sigma);

  builder = json_builder_new ();
  json_builder_begin_object (builder);
  json_builder_set_member_name (builder, "sigma");
  json_builder_add_string_value (builder, src_ptr);
  json_builder_end_object (builder);

  return json_builder_get_root (builder);
}

static void
clutter_blur_node_class_init (ClutterBlurNodeClass *klass)
{
  ClutterPaintNodeClass *node_class;

  node_class = CLUTTER_PAINT_NODE_CLASS (klass);
  node_class->post_draw = clutter_blur_node_post_draw;
  node_class->finalize = clutter_blur_node_finalize;
  node_class->serialize = clutter_blur_node_serialize;
}

static void
clutter_blur_node_init (ClutterBlurNode *blur_node)
{
}

/**
 * clutter_blur_node_new:
 * @width width of the blur layer
 * @height: height of the blur layer
 * @sigma: sigma value of the blur
 *
 * Creates a new #ClutterBlurNode.
 *
 * Children of this node will be painted inside a separate framebuffer,
 * which will be blurred and painted on the current draw framebuffer.
 *
 * Return value: (transfer full): the newly created #ClutterBlurNode.
 *   Use clutter_paint_node_unref() when done.
 */
ClutterPaintNode *
clutter_blur_node_new (unsigned int width,
                       unsigned int height,
                       float        sigma)
{
  g_autoptr (CoglOffscreen) offscreen = NULL;
  g_autoptr (GError) error = NULL;
  ClutterLayerNode *layer_node;
  ClutterBlurNode *blur_node;
  CoglTexture2D *tex_2d;
  CoglContext *context;
  CoglTexture *texture;
  ClutterBlur *blur;

  g_return_val_if_fail (sigma >= 0.0, NULL);

  blur_node = _clutter_paint_node_create (CLUTTER_TYPE_BLUR_NODE);
  blur_node->sigma = sigma;
  context = clutter_backend_get_cogl_context (clutter_get_default_backend ());
  tex_2d = cogl_texture_2d_new_with_size (context, width, height);

  texture = COGL_TEXTURE (tex_2d);
  cogl_texture_set_premultiplied (texture, TRUE);

  offscreen = cogl_offscreen_new_with_texture (texture);
  cogl_object_unref (tex_2d);
  if (!cogl_framebuffer_allocate (COGL_FRAMEBUFFER (offscreen), &error))
    {
      g_warning ("Unable to allocate paint node offscreen: %s",
                 error->message);
      goto out;
    }

  blur = clutter_blur_new (texture, sigma);
  blur_node->blur = blur;

  if (!blur)
    {
      g_warning ("Failed to create blur pipeline");
      goto out;
    }

  layer_node = CLUTTER_LAYER_NODE (blur_node);
  layer_node->offscreen = COGL_FRAMEBUFFER (g_steal_pointer (&offscreen));
  layer_node->pipeline = cogl_pipeline_copy (default_texture_pipeline);
  cogl_pipeline_set_layer_filters (layer_node->pipeline, 0,
                                   COGL_PIPELINE_FILTER_LINEAR,
                                   COGL_PIPELINE_FILTER_LINEAR);
  cogl_pipeline_set_layer_texture (layer_node->pipeline,
                                   0,
                                   clutter_blur_get_texture (blur));

  cogl_framebuffer_orthographic (layer_node->offscreen,
                                 0.0, 0.0,
                                 width, height,
                                 0.0, 1.0);

out:
  return (ClutterPaintNode *) blur_node;
}
