/* dzl-box-theatric.c
 *
 * Copyright (C) 2014 Christian Hergert <christian@hergert.me>
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program 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 General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

#define G_LOG_DOMAIN "dzl-box-theatric"

#include <glib/gi18n.h>

#include "animation/dzl-box-theatric.h"
#include "util/dzl-cairo.h"

struct _DzlBoxTheatric
{
  GObject          parent_instance;

  GtkWidget       *target;
  GtkWidget       *toplevel;

  GIcon           *icon;
  cairo_surface_t *icon_surface;
  guint            icon_surface_size;

  GdkRectangle     area;
  GdkRectangle     last_area;
  GdkRGBA          background_rgba;
  gdouble          alpha;

  guint            draw_handler;

  guint            background_set : 1;
  guint            pixbuf_failed : 1;
};

enum {
  PROP_0,
  PROP_ALPHA,
  PROP_BACKGROUND,
  PROP_HEIGHT,
  PROP_ICON,
  PROP_TARGET,
  PROP_WIDTH,
  PROP_X,
  PROP_Y,
  PROP_SURFACE,
  LAST_PROP
};

G_DEFINE_TYPE (DzlBoxTheatric, dzl_box_theatric, G_TYPE_OBJECT)

static GParamSpec *properties[LAST_PROP];

static void
get_toplevel_rect (DzlBoxTheatric *theatric,
                   GdkRectangle  *area)
{
  gtk_widget_translate_coordinates (theatric->target, theatric->toplevel,
                                    theatric->area.x, theatric->area.y,
                                    &area->x, &area->y);

  area->width = theatric->area.width;
  area->height = theatric->area.height;
}

static gboolean
on_toplevel_draw (GtkWidget      *widget,
                  cairo_t        *cr,
                  DzlBoxTheatric *self)
{
  GdkRectangle area;

  g_assert (DZL_IS_BOX_THEATRIC (self));

  get_toplevel_rect (self, &area);

#if 0
  g_print ("Drawing on %s at %d,%d %dx%d\n",
           g_type_name (G_TYPE_FROM_INSTANCE (widget)),
           area.x, area.y, area.width, area.height);
#endif

  if (self->background_set)
    {
      GdkRGBA bg;

      bg = self->background_rgba;
      bg.alpha = self->alpha;

      dzl_cairo_rounded_rectangle (cr, &area, 3, 3);
      gdk_cairo_set_source_rgba (cr, &bg);
      cairo_fill (cr);
    }

  /* Load an icon if necessary and cache the surface */
  if (self->icon != NULL && self->icon_surface == NULL && !self->pixbuf_failed)
    {
      g_autoptr(GtkIconInfo) icon_info = NULL;
      GtkIconTheme *icon_theme;
      gint width;

      width = area.width * 4;
      icon_theme = gtk_icon_theme_get_default ();
      icon_info = gtk_icon_theme_lookup_by_gicon (icon_theme,
                                                  self->icon,
                                                  width,
                                                  GTK_ICON_LOOKUP_FORCE_SIZE);

      if (icon_info != NULL)
        {
          GdkWindow *window = gtk_widget_get_window (widget);
          g_autoptr(GdkPixbuf) pixbuf = NULL;
          GtkStyleContext *context;

          context = gtk_widget_get_style_context (self->target);
          pixbuf = gtk_icon_info_load_symbolic_for_context (icon_info, context, NULL, NULL);

          if (pixbuf != NULL)
            {
              self->icon_surface = gdk_cairo_surface_create_from_pixbuf (pixbuf, 0, window);
              self->icon_surface_size = width;
              self->pixbuf_failed = FALSE;
            }
          else
            {
              self->pixbuf_failed = TRUE;
            }
        }
    }

  if (self->icon_surface != NULL)
    {
      cairo_translate (cr, area.x, area.y);
      cairo_rectangle (cr, 0, 0, area.width, area.height);
      if (self->icon_surface_size != 0)
        cairo_scale (cr,
                     area.width / (gdouble)self->icon_surface_size,
                     area.height / (gdouble)self->icon_surface_size);
      cairo_set_source_surface (cr, self->icon_surface, 0, 0);
      cairo_paint_with_alpha (cr, self->alpha);
    }

  self->last_area = area;

  /* The invalidation rectangle does not appear to be inclusive.
   * This means that occcasionly we seem to have a stragler line.
   * But setting this to one seems to make that go away for the
   * current tests I have.
   */
  self->last_area.width += 1;
  self->last_area.height += 1;

  return FALSE;
}

static void
dzl_box_theatric_notify (GObject    *object,
                        GParamSpec *pspec)
{
  DzlBoxTheatric *self = DZL_BOX_THEATRIC (object);

  if (G_OBJECT_CLASS (dzl_box_theatric_parent_class)->notify)
    G_OBJECT_CLASS (dzl_box_theatric_parent_class)->notify (object, pspec);

  if (self->target && self->toplevel)
    {
      GdkWindow *window;
      GdkRectangle area;

      get_toplevel_rect (DZL_BOX_THEATRIC (object), &area);

#if 0
      g_print ("  Invalidate : %d,%d %dx%d\n",
               area.x, area.y, area.width, area.height);
#endif

      window = gtk_widget_get_window (self->toplevel);

      if (window != NULL)
        {
          gdk_window_invalidate_rect (window, &self->last_area, TRUE);
          gdk_window_invalidate_rect (window, &area, TRUE);
        }
    }
}

static void
dzl_box_theatric_dispose (GObject *object)
{
  DzlBoxTheatric *self =  DZL_BOX_THEATRIC (object);

  if (self->target)
    {
      if (self->draw_handler && self->toplevel)
        {
          g_signal_handler_disconnect (self->toplevel, self->draw_handler);
          self->draw_handler = 0;
        }
      g_object_remove_weak_pointer (G_OBJECT (self->target),
                                    (gpointer *) &self->target);
      self->target = NULL;
    }

  g_clear_pointer (&self->icon_surface, cairo_surface_destroy);
  g_clear_object (&self->icon);

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

static void
dzl_box_theatric_get_property (GObject    *object,
                               guint       prop_id,
                               GValue     *value,
                               GParamSpec *pspec)
{
  DzlBoxTheatric *theatric = DZL_BOX_THEATRIC (object);

  switch (prop_id)
    {
    case PROP_ALPHA:
      g_value_set_double (value, theatric->alpha);
      break;

    case PROP_BACKGROUND:
      g_value_take_string (value,
                           gdk_rgba_to_string (&theatric->background_rgba));
      break;

    case PROP_HEIGHT:
      g_value_set_int (value, theatric->area.height);
      break;

    case PROP_ICON:
      g_value_set_object (value, theatric->icon);
      break;

    case PROP_TARGET:
      g_value_set_object (value, theatric->target);
      break;

    case PROP_WIDTH:
      g_value_set_int (value, theatric->area.width);
      break;

    case PROP_X:
      g_value_set_int (value, theatric->area.x);
      break;

    case PROP_Y:
      g_value_set_int (value, theatric->area.y);
      break;

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

static void
dzl_box_theatric_set_property (GObject      *object,
                              guint         prop_id,
                              const GValue *value,
                              GParamSpec   *pspec)
{
  DzlBoxTheatric *theatric = DZL_BOX_THEATRIC (object);

  switch (prop_id)
    {
    case PROP_ALPHA:
      theatric->alpha = g_value_get_double (value);
      break;

    case PROP_BACKGROUND:
      {
        const gchar *str = g_value_get_string (value);

        if (str == NULL)
          {
            gdk_rgba_parse (&theatric->background_rgba, "#000000");
            theatric->background_rgba.alpha = 0;
            theatric->background_set = FALSE;
          }
        else
          {
            gdk_rgba_parse (&theatric->background_rgba, str);
            theatric->background_set = TRUE;
          }
      }
      break;

    case PROP_HEIGHT:
      theatric->area.height = g_value_get_int (value);
      break;

    case PROP_ICON:
      g_clear_pointer (&theatric->icon_surface, cairo_surface_destroy);
      g_clear_object (&theatric->icon);
      theatric->icon = g_value_dup_object (value);
      theatric->pixbuf_failed = FALSE;
      break;

    case PROP_SURFACE:
      g_clear_pointer (&theatric->icon_surface, cairo_surface_destroy);
      theatric->icon_surface = g_value_get_pointer (value);
      if (theatric->icon_surface != NULL)
        cairo_surface_reference (theatric->icon_surface);
      break;

    case PROP_TARGET:
      theatric->target = g_value_get_object (value);
      theatric->toplevel = gtk_widget_get_toplevel (theatric->target);
      g_object_add_weak_pointer (G_OBJECT (theatric->target),
                                 (gpointer *) &theatric->target);
      theatric->draw_handler =
        g_signal_connect_after (theatric->toplevel,
                                "draw",
                                G_CALLBACK (on_toplevel_draw),
                                theatric);
      break;

    case PROP_WIDTH:
      theatric->area.width = g_value_get_int (value);
      break;

    case PROP_X:
      theatric->area.x = g_value_get_int (value);
      break;

    case PROP_Y:
      theatric->area.y = g_value_get_int (value);
      break;

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

  g_object_notify_by_pspec (object, pspec);
}

static void
dzl_box_theatric_class_init (DzlBoxTheatricClass *klass)
{
  GObjectClass *object_class;

  object_class = G_OBJECT_CLASS (klass);
  object_class->dispose = dzl_box_theatric_dispose;
  object_class->notify = dzl_box_theatric_notify;
  object_class->get_property = dzl_box_theatric_get_property;
  object_class->set_property = dzl_box_theatric_set_property;

  properties[PROP_ALPHA] =
    g_param_spec_double ("alpha",
                         "Alpha",
                         "Alpha",
                         0.0,
                         1.0,
                         1.0,
                         (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));

  properties[PROP_BACKGROUND] =
    g_param_spec_string ("background",
                         "background",
                         "background",
                         "#000000",
                         (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));

  properties[PROP_HEIGHT] =
    g_param_spec_int ("height",
                      "height",
                      "height",
                      0,
                      G_MAXINT,
                      0,
                      (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));

  properties [PROP_ICON] =
    g_param_spec_object ("icon",
                         "Icon",
                         "The GIcon to render over the background",
                         G_TYPE_ICON,
                         (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));

  properties[PROP_TARGET] =
    g_param_spec_object ("target",
                         "Target",
                         "Target",
                         GTK_TYPE_WIDGET,
                         (G_PARAM_READWRITE |
                          G_PARAM_CONSTRUCT_ONLY |
                          G_PARAM_STATIC_STRINGS));

  properties[PROP_WIDTH] =
    g_param_spec_int ("width",
                      "width",
                      "width",
                      0,
                      G_MAXINT,
                      0,
                      (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));

  properties[PROP_X] =
    g_param_spec_int ("x",
                      "x",
                      "x",
                      G_MININT,
                      G_MAXINT,
                      0,
                      (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));

  properties[PROP_Y] =
    g_param_spec_int ("y",
                      "y",
                      "y",
                      G_MININT,
                      G_MAXINT,
                      0,
                      (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));

  properties[PROP_SURFACE] =
    g_param_spec_pointer ("surface",
                          "Surface",
                          "Surface",
                          (G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));

  g_object_class_install_properties (object_class, LAST_PROP, properties);
}

static void
dzl_box_theatric_init (DzlBoxTheatric *theatric)
{
  theatric->alpha = 1.0;
}
