/* GSK - The GTK Scene Kit
 *
 * Copyright 2016  Endless
 *
 * 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/>.
 */

#include "config.h"

#include "gsktransformnode.h"

#include "gskrendernodeprivate.h"
#include "gskrenderreplay.h"
#include "gskrectprivate.h"
#include "gsktransformprivate.h"
#include "gpu/gskgpuocclusionprivate.h"
#include "gpu/gskgpurenderpassprivate.h"

#include "gdk/gdkcairoprivate.h"
#include "gdk/gdkcolorprivate.h"
#include "gdk/gdkrectangleprivate.h"


static void
region_union_region_affine (cairo_region_t       *region,
                            const cairo_region_t *sub,
                            float                 scale_x,
                            float                 scale_y,
                            float                 offset_x,
                            float                 offset_y)
{
  cairo_rectangle_int_t rect;
  int i;
  
  for (i = 0; i < cairo_region_num_rectangles (sub); i++)
    {
      cairo_region_get_rectangle (sub, i, &rect);
      gdk_rectangle_transform_affine (&rect, scale_x, scale_y, offset_x, offset_y, &rect);
      cairo_region_union_rectangle (region, &rect);
    }
}

/**
 * GskTransformNode:
 *
 * A render node applying a `GskTransform` to its single child node.
 */
struct _GskTransformNode
{
  GskRenderNode render_node;

  GskRenderNode *child;
  GskTransform *transform;
};

static void
gsk_transform_node_finalize (GskRenderNode *node)
{
  GskTransformNode *self = (GskTransformNode *) node;
  GskRenderNodeClass *parent_class = g_type_class_peek (g_type_parent (GSK_TYPE_TRANSFORM_NODE));

  gsk_render_node_unref (self->child);
  gsk_transform_unref (self->transform);

  parent_class->finalize (node);
}

static void
gsk_transform_node_draw (GskRenderNode *node,
                         cairo_t       *cr,
                         GskCairoData  *data)
{
  GskTransformNode *self = (GskTransformNode *) node;
  float xx, yx, xy, yy, dx, dy;
  cairo_matrix_t ctm;

  if (gsk_transform_get_category (self->transform) < GSK_TRANSFORM_CATEGORY_2D)
    {
      GdkRGBA pink = { 255 / 255., 105 / 255., 180 / 255., 1.0 };
      gdk_cairo_set_source_rgba_ccs (cr, data->ccs, &pink);
      gdk_cairo_rect (cr, &node->bounds);
      cairo_fill (cr);
      return;
    }

  gsk_transform_to_2d (self->transform, &xx, &yx, &xy, &yy, &dx, &dy);
  cairo_matrix_init (&ctm, xx, yx, xy, yy, dx, dy);
  if (xx * yy == xy * yx)
    {
      /* broken matrix here. This can happen during transitions
       * (like when flipping an axis at the point where scale == 0)
       * and just means that nothing should be drawn.
       * But Cairo throws lots of ugly errors instead of silently
       * going on. So we silently go on.
       */
      return;
    }
  cairo_transform (cr, &ctm);

  gsk_render_node_draw_full (self->child, cr, data);
}

static gboolean
gsk_transform_node_can_diff (const GskRenderNode *node1,
                             const GskRenderNode *node2)
{
  GskTransformNode *self1 = (GskTransformNode *) node1;
  GskTransformNode *self2 = (GskTransformNode *) node2;

  if (!gsk_transform_equal (self1->transform, self2->transform))
    return FALSE;

  return gsk_render_node_can_diff (self1->child, self2->child);
}

static void
gsk_transform_node_diff (GskRenderNode *node1,
                         GskRenderNode *node2,
                         GskDiffData   *data)
{
  GskTransformNode *self1 = (GskTransformNode *) node1;
  GskTransformNode *self2 = (GskTransformNode *) node2;

  if (!gsk_transform_equal (self1->transform, self2->transform))
    {
      gsk_render_node_diff_impossible (node1, node2, data);
      return;
    }

  if (self1->child == self2->child)
    return;

  switch (gsk_transform_get_category (self1->transform))
    {
    case GSK_TRANSFORM_CATEGORY_IDENTITY:
      gsk_render_node_diff (self1->child, self2->child, data);
      break;

    case GSK_TRANSFORM_CATEGORY_2D_TRANSLATE:
      {
        float dx, dy;
        gsk_transform_to_translate (self1->transform, &dx, &dy);
        if (floorf (dx) == dx && floorf (dy) == dy)
          {
            cairo_region_translate (data->region, -dx, -dy);
            gsk_render_node_diff (self1->child, self2->child, data);
            cairo_region_translate (data->region, dx, dy);
            break;
          }
      }
      G_GNUC_FALLTHROUGH;

    case GSK_TRANSFORM_CATEGORY_2D_AFFINE:
      {
        cairo_region_t *sub;
        float scale_x, scale_y, dx, dy;
        gsk_transform_to_affine (self1->transform, &scale_x, &scale_y, &dx, &dy);
        sub = cairo_region_create ();
        if (gsk_render_node_get_copy_mode (node1) != GSK_COPY_NONE ||
            gsk_render_node_get_copy_mode (node2) != GSK_COPY_NONE)
          region_union_region_affine (sub, data->region, 1.0f / scale_x, 1.0f / scale_y, - dx / scale_x, - dy / scale_y);
        gsk_render_node_diff (self1->child, self2->child, &(GskDiffData) { sub, data->copies, data->surface });
        region_union_region_affine (data->region, sub, scale_x, scale_y, dx, dy);
        cairo_region_destroy (sub);
      }
      break;

    case GSK_TRANSFORM_CATEGORY_UNKNOWN:
    case GSK_TRANSFORM_CATEGORY_ANY:
    case GSK_TRANSFORM_CATEGORY_3D:
    case GSK_TRANSFORM_CATEGORY_2D:
    default:
      gsk_render_node_diff_impossible (node1, node2, data);
      break;
    }
}

static GskRenderNode **
gsk_transform_node_get_children (GskRenderNode *node,
                                 gsize         *n_children)
{
  GskTransformNode *self = (GskTransformNode *) node;

  *n_children = 1;
  
  return &self->child;
}

static GskRenderNode *
gsk_transform_node_replay (GskRenderNode   *node,
                           GskRenderReplay *replay)
{
  GskTransformNode *self = (GskTransformNode *) node;
  GskRenderNode *result, *child;

  child = gsk_render_replay_filter_node (replay, self->child);

  if (child == NULL)
    return NULL;

  if (child == self->child)
    result = gsk_render_node_ref (node);
  else
    result = gsk_transform_node_new (child, self->transform);

  gsk_render_node_unref (child);

  return result;
}

static void
gsk_transform_node_render_opacity (GskRenderNode  *node,
                                   GskOpacityData *data)
{
  GskTransformNode *self = (GskTransformNode *) node;

  if (gsk_transform_get_fine_category (self->transform) < GSK_FINE_TRANSFORM_CATEGORY_2D_DIHEDRAL)
    {
      /* too complex, skip child */
      if (gsk_render_node_clears_background (node) && !gsk_rect_is_empty (&data->opaque))
        {
          if (!gsk_rect_subtract (&data->opaque, &node->bounds, &data->opaque))
            data->opaque = GRAPHENE_RECT_INIT (0, 0, 0, 0);
        }
      return;
    }

  if (!gsk_rect_is_empty (&data->opaque))
    {
      GskTransform *inverse;

      inverse = gsk_transform_invert (gsk_transform_ref (self->transform));
      if (inverse == NULL)
        return;

      gsk_transform_transform_bounds (inverse, &data->opaque, &data->opaque);
      gsk_transform_unref (inverse);
    }


  gsk_render_node_render_opacity (self->child, data);

  if (!gsk_rect_is_empty (&data->opaque))
    gsk_transform_transform_bounds (self->transform, &data->opaque, &data->opaque);
}

static GskGpuRenderPass *
gsk_transform_node_occlusion (GskRenderNode   *node,
                              GskGpuOcclusion *occlusion)
{
  GskTransformNode *self = (GskTransformNode *) node;
  GskGpuTransform saved_transform;
  GskGpuRenderPass *result;

  if (!gsk_gpu_occlusion_push_transform (occlusion, self->transform, &saved_transform))
    return NULL;

  result = gsk_gpu_occlusion_try_node (occlusion, self->child, 0);

  gsk_gpu_occlusion_pop_transform (occlusion, &saved_transform);

  if (result)
    gsk_gpu_render_pass_set_transform (result, &saved_transform);

  return result;
}

static void
gsk_transform_node_class_init (gpointer g_class,
                               gpointer class_data)
{
  GskRenderNodeClass *node_class = g_class;

  node_class->node_type = GSK_TRANSFORM_NODE;

  node_class->finalize = gsk_transform_node_finalize;
  node_class->draw = gsk_transform_node_draw;
  node_class->can_diff = gsk_transform_node_can_diff;
  node_class->diff = gsk_transform_node_diff;
  node_class->get_children = gsk_transform_node_get_children;
  node_class->replay = gsk_transform_node_replay;
  node_class->render_opacity = gsk_transform_node_render_opacity;
  node_class->occlusion = gsk_transform_node_occlusion;
}

GSK_DEFINE_RENDER_NODE_TYPE (GskTransformNode, gsk_transform_node)

/**
 * gsk_transform_node_new:
 * @child: The node to transform
 * @transform: (transfer none) (nullable): The transform to apply
 *
 * Creates a `GskRenderNode` that will transform the given @child
 * with the given @transform.
 *
 * Returns: (transfer full) (type GskTransformNode): A new `GskRenderNode`
 */
GskRenderNode *
gsk_transform_node_new (GskRenderNode *child,
                        GskTransform  *transform)
{
  GskTransformNode *self;
  GskRenderNode *node;
  GskTransformCategory category;

  g_return_val_if_fail (GSK_IS_RENDER_NODE (child), NULL);

  category = gsk_transform_get_category (transform);

  self = gsk_render_node_alloc (GSK_TYPE_TRANSFORM_NODE);
  node = (GskRenderNode *) self;
  node->fully_opaque = gsk_render_node_is_fully_opaque (child) && category >= GSK_TRANSFORM_CATEGORY_2D_AFFINE;
  node->bilevel_opacity = gsk_render_node_is_bilevel_opacity (child);

  self->child = gsk_render_node_ref (child);
  self->transform = gsk_transform_ref (transform);

  gsk_transform_transform_bounds (self->transform,
                                  &child->bounds,
                                  &node->bounds);

  node->preferred_depth = gsk_render_node_get_preferred_depth (child);
  node->is_hdr = gsk_render_node_is_hdr (child);
  node->clears_background = gsk_render_node_clears_background (child);
  node->copy_mode = gsk_render_node_get_copy_mode (child) ? GSK_COPY_ANY : GSK_COPY_NONE;
  node->contains_subsurface_node = gsk_render_node_contains_subsurface_node (child);
  node->contains_paste_node = gsk_render_node_contains_paste_node (child);
  node->needs_blending = gsk_render_node_needs_blending (child);

  return node;
}

/**
 * gsk_transform_node_get_child:
 * @node: (type GskTransformNode): a `GskRenderNode` for a transform
 *
 * Gets the child node that is getting transformed by the given @node.
 *
 * Returns: (transfer none): The child that is getting transformed
 */
GskRenderNode *
gsk_transform_node_get_child (const GskRenderNode *node)
{
  const GskTransformNode *self = (const GskTransformNode *) node;

  return self->child;
}

/**
 * gsk_transform_node_get_transform:
 * @node: (type GskTransformNode): a `GskRenderNode` for a transform
 *
 * Retrieves the `GskTransform` used by the @node.
 *
 * Returns: (transfer none): a `GskTransform`
 */
GskTransform *
gsk_transform_node_get_transform (const GskRenderNode *node)
{
  const GskTransformNode *self = (const GskTransformNode *) node;

  return self->transform;
}
