/*
 * Copyright © 2019 Benjamin Otte
 *
 * 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.1 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/>.
 *
 * Authors: Benjamin Otte <otte@gnome.org>
 */

#include "config.h"

#include "gtkrendererpaintableprivate.h"

#include <gtk/gtk.h>

struct _GtkRendererPaintable
{
  GObject parent_instance;

  GskRenderer *renderer;
  GdkPaintable *paintable;
};

struct _GtkRendererPaintableClass
{
  GObjectClass parent_class;
};

enum {
  PROP_0,
  PROP_PAINTABLE,
  PROP_RENDERER,
  N_PROPS
};

static GParamSpec *properties[N_PROPS] = { NULL, };

static void
gtk_renderer_paintable_paintable_snapshot (GdkPaintable *paintable,
                                           GdkSnapshot  *snapshot,
                                           double        width,
                                           double        height)
{
  GtkRendererPaintable *self = GTK_RENDERER_PAINTABLE (paintable);
  GtkSnapshot *node_snapshot;
  GskRenderNode *node;
  GdkTexture *texture;
  gsize node_width, node_height;

  if (self->paintable == NULL)
    return;

  if (self->renderer == NULL ||
      !gsk_renderer_is_realized (self->renderer))
    {
      gdk_paintable_snapshot (self->paintable, snapshot, width, height);
      return;
    }

  node_width = gdk_paintable_get_intrinsic_width (self->paintable);
  node_height = gdk_paintable_get_intrinsic_height (self->paintable);

  node_snapshot = gtk_snapshot_new ();
  gdk_paintable_snapshot (self->paintable, node_snapshot, node_width, node_height);
  node = gtk_snapshot_free_to_node (node_snapshot);
  if (node == NULL)
    return;

  texture = gsk_renderer_render_texture (self->renderer,
                                         node,
                                         NULL);

  gdk_paintable_snapshot (GDK_PAINTABLE (texture), snapshot, width, height);
  g_object_unref (texture);

  gsk_render_node_unref (node);
}

static int
gtk_renderer_paintable_paintable_get_intrinsic_width (GdkPaintable *paintable)
{
  GtkRendererPaintable *self = GTK_RENDERER_PAINTABLE (paintable);

  if (self->paintable == NULL)
    return 0;

  return gdk_paintable_get_intrinsic_width (self->paintable);
}

static int
gtk_renderer_paintable_paintable_get_intrinsic_height (GdkPaintable *paintable)
{
  GtkRendererPaintable *self = GTK_RENDERER_PAINTABLE (paintable);

  if (self->paintable == NULL)
    return 0;

  return gdk_paintable_get_intrinsic_height (self->paintable);
}

static double
gtk_renderer_paintable_paintable_get_intrinsic_aspect_ratio (GdkPaintable *paintable)
{
  GtkRendererPaintable *self = GTK_RENDERER_PAINTABLE (paintable);

  if (self->paintable == NULL)
    return 0.0;

  return gdk_paintable_get_intrinsic_aspect_ratio (self->paintable);
}

static void
gtk_renderer_paintable_paintable_init (GdkPaintableInterface *iface)
{
  iface->snapshot = gtk_renderer_paintable_paintable_snapshot;
  iface->get_intrinsic_width = gtk_renderer_paintable_paintable_get_intrinsic_width;
  iface->get_intrinsic_height = gtk_renderer_paintable_paintable_get_intrinsic_height;
  iface->get_intrinsic_aspect_ratio = gtk_renderer_paintable_paintable_get_intrinsic_aspect_ratio;
}

G_DEFINE_TYPE_EXTENDED (GtkRendererPaintable, gtk_renderer_paintable, G_TYPE_OBJECT, 0,
                        G_IMPLEMENT_INTERFACE (GDK_TYPE_PAINTABLE,
                                               gtk_renderer_paintable_paintable_init))

static void
gtk_renderer_paintable_set_property (GObject      *object,
                                     guint         prop_id,
                                     const GValue *value,
                                     GParamSpec   *pspec)

{
  GtkRendererPaintable *self = GTK_RENDERER_PAINTABLE (object);

  switch (prop_id)
    {
    case PROP_PAINTABLE:
      gtk_renderer_paintable_set_paintable (self, g_value_get_object (value));
      break;

    case PROP_RENDERER:
      gtk_renderer_paintable_set_renderer (self, g_value_get_object (value));
      break;

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

static void
gtk_renderer_paintable_get_property (GObject    *object,
                                     guint       prop_id,
                                     GValue     *value,
                                     GParamSpec *pspec)
{
  GtkRendererPaintable *self = GTK_RENDERER_PAINTABLE (object);

  switch (prop_id)
    {
    case PROP_PAINTABLE:
      g_value_set_object (value, self->paintable);
      break;

    case PROP_RENDERER:
      g_value_set_object (value, self->renderer);
      break;

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

static void
gtk_renderer_paintable_unset_paintable (GtkRendererPaintable *self)
{
  guint flags;
  
  if (self->paintable == NULL)
    return;

  flags = gdk_paintable_get_flags (self->paintable);

  if ((flags & GDK_PAINTABLE_STATIC_CONTENTS) == 0)
    g_signal_handlers_disconnect_by_func (self->paintable,
                                          gdk_paintable_invalidate_contents,
                                          self);

  if ((flags & GDK_PAINTABLE_STATIC_SIZE) == 0)
    g_signal_handlers_disconnect_by_func (self->paintable,
                                          gdk_paintable_invalidate_size,
                                          self);

  g_clear_object (&self->paintable);
}

static void
gtk_renderer_paintable_dispose (GObject *object)
{
  GtkRendererPaintable *self = GTK_RENDERER_PAINTABLE (object);

  g_clear_object (&self->renderer);
  gtk_renderer_paintable_unset_paintable (self);

  G_OBJECT_CLASS (gtk_renderer_paintable_parent_class)->dispose (object);
}

static void
gtk_renderer_paintable_class_init (GtkRendererPaintableClass *klass)
{
  GObjectClass *gobject_class = G_OBJECT_CLASS (klass);

  gobject_class->get_property = gtk_renderer_paintable_get_property;
  gobject_class->set_property = gtk_renderer_paintable_set_property;
  gobject_class->dispose = gtk_renderer_paintable_dispose;

  properties[PROP_PAINTABLE] =
    g_param_spec_object ("paintable",
                         "Paintable",
                         "The paintable to be shown",
                         GDK_TYPE_PAINTABLE,
                         G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | G_PARAM_EXPLICIT_NOTIFY);

  properties[PROP_RENDERER] =
    g_param_spec_object ("renderer",
                         "Renderer",
                         "Renderer used to render the paintable",
                         GSK_TYPE_RENDERER,
                         G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | G_PARAM_EXPLICIT_NOTIFY);

  g_object_class_install_properties (gobject_class, N_PROPS, properties);
}

static void
gtk_renderer_paintable_init (GtkRendererPaintable *self)
{
}

GdkPaintable *
gtk_renderer_paintable_new (GskRenderer  *renderer,
                            GdkPaintable *paintable)
{
  g_return_val_if_fail (renderer == NULL || GSK_IS_RENDERER (renderer), NULL);
  g_return_val_if_fail (paintable == NULL || GDK_IS_PAINTABLE (paintable), NULL);

  return g_object_new (GTK_TYPE_RENDERER_PAINTABLE,
                       "renderer", renderer,
                       "paintable", paintable,
                       NULL);
}

void
gtk_renderer_paintable_set_renderer (GtkRendererPaintable *self,
                                     GskRenderer          *renderer)
{
  g_return_if_fail (GTK_IS_RENDERER_PAINTABLE (self));
  g_return_if_fail (renderer == NULL || GSK_IS_RENDERER (renderer));

  if (!g_set_object (&self->renderer, renderer))
    return;

  if (self->paintable)
    gdk_paintable_invalidate_contents (GDK_PAINTABLE (self));

  g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_RENDERER]);
}

GskRenderer *
gtk_renderer_paintable_get_renderer (GtkRendererPaintable *self)
{
  g_return_val_if_fail (GTK_IS_RENDERER_PAINTABLE (self), NULL);

  return self->renderer;
}

void
gtk_renderer_paintable_set_paintable (GtkRendererPaintable *self,
                                      GdkPaintable         *paintable)
{
  g_return_if_fail (GTK_IS_RENDERER_PAINTABLE (self));
  g_return_if_fail (paintable == NULL || GDK_IS_PAINTABLE (paintable));

  if (self->paintable == paintable)
    return;

  gtk_renderer_paintable_unset_paintable (self);

  if (paintable)
    {
      const guint flags = gdk_paintable_get_flags (paintable);

      self->paintable = g_object_ref (paintable);

      if ((flags & GDK_PAINTABLE_STATIC_CONTENTS) == 0)
        g_signal_connect_swapped (paintable,
                                  "invalidate-contents",
                                  G_CALLBACK (gdk_paintable_invalidate_contents),
                                  self);
      if ((flags & GDK_PAINTABLE_STATIC_SIZE) == 0)
        g_signal_connect_swapped (paintable,
                                  "invalidate-size",
                                  G_CALLBACK (gdk_paintable_invalidate_size),
                                  self);
    }

  gdk_paintable_invalidate_size (GDK_PAINTABLE (self));
  gdk_paintable_invalidate_contents (GDK_PAINTABLE (self));

  g_object_notify_by_pspec (G_OBJECT (self), properties[PROP_PAINTABLE]);
}

GdkPaintable *
gtk_renderer_paintable_get_paintable (GtkRendererPaintable *self)
{
  g_return_val_if_fail (GTK_IS_RENDERER_PAINTABLE (self), NULL);

  return self->paintable;
}

