/*
 * Copyright (C) 2002 Red Hat, Inc.
 * Copyright (C) 2003-2006 Vincent Untz
 * Copyright (C) 2007 Christian Persch
 * Copyright (C) 2017 Colomban Wendling <cwendling@hypra.fr>
 *
 * 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 2 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, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301, USA.
 */

/* Well, actuall'y is the Tray itself, the container for the items.  But
 * NaTray is already taken for the XEMBED part, so for now it's called NaBox,
 * don't make a big deal out of it. */

#include "config.h"

#include <gtk/gtk.h>

#include "na-box.h"

#include "system-tray/na-tray.h"
#include "status-notifier/sn-host-v0.h"

#define ICON_SPACING 1
#define MIN_BOX_SIZE 3

struct _NaBox
{
  GtkBox parent;

  gint icon_padding;
  gint icon_size;

  GSList *hosts;
  GSList *items;
};

enum
{
  PROP_0,
  PROP_ICON_PADDING,
  PROP_ICON_SIZE
};

G_DEFINE_TYPE (NaBox, na_box, GTK_TYPE_BOX)

static gint
compare_items (gconstpointer a,
               gconstpointer b)
{
  NaItem *item1;
  NaItem *item2;
  NaItemCategory c1;
  NaItemCategory c2;
  const gchar *id1;
  const gchar *id2;

  item1 = (NaItem *) a;
  item2 = (NaItem *) b;

  c1 = na_item_get_category (item1);
  c2 = na_item_get_category (item2);

  if (c1 < c2)
    return -1;
  else if (c1 > c2)
    return 1;

  id1 = na_item_get_id (item1);
  id2 = na_item_get_id (item2);

  return g_strcmp0 (id1, id2);
}

static void
reorder_items (GtkWidget *widget,
               gpointer   user_data)
{
  NaBox *nb;
  gint position;

  nb = NA_BOX (user_data);

  position = g_slist_index (nb->items, widget);
  gtk_box_reorder_child (GTK_BOX (nb), widget, position);
}

static void
item_added_cb (NaHost *host,
               NaItem *item,
               NaBox  *self)
{
  g_return_if_fail (NA_IS_HOST (host));
  g_return_if_fail (NA_IS_ITEM (item));
  g_return_if_fail (NA_IS_BOX (self));

  g_object_bind_property (self, "orientation",
                          item, "orientation",
                          G_BINDING_SYNC_CREATE);

  self->items = g_slist_prepend (self->items, item);
  gtk_box_pack_start (GTK_BOX (self), GTK_WIDGET (item), FALSE, FALSE, 0);

  self->items = g_slist_sort (self->items, compare_items);
  gtk_container_foreach (GTK_CONTAINER (self), reorder_items, self);
}

static void
item_removed_cb (NaHost *host,
                 NaItem *item,
                 NaBox  *self)
{
  g_return_if_fail (NA_IS_HOST (host));
  g_return_if_fail (NA_IS_ITEM (item));
  g_return_if_fail (NA_IS_BOX (self));

  gtk_container_remove (GTK_CONTAINER (self), GTK_WIDGET (item));
  self->items = g_slist_remove (self->items, item);
}

static void
update_size_and_orientation (NaBox          *self,
                             GtkOrientation  orientation)
{
  /* FIXME: do we really need that?  comes from NaTray */
  /* FIXME: if we do, do that in overridden preferred size handlers */

  /* note, you want this larger if the frame has non-NONE relief by default. */
  switch (orientation)
    {
    case GTK_ORIENTATION_VERTICAL:
      /* Give box a min size so the frame doesn't look dumb */
      gtk_widget_set_size_request (GTK_WIDGET (self), MIN_BOX_SIZE, -1);
      break;
    case GTK_ORIENTATION_HORIZONTAL:
      gtk_widget_set_size_request (GTK_WIDGET (self), -1, MIN_BOX_SIZE);
      break;
    }
}

static void
orientation_notify (GObject    *object,
                    GParamSpec *pspec,
                    gpointer    data)
{
  update_size_and_orientation (NA_BOX (object),
                               gtk_orientable_get_orientation (GTK_ORIENTABLE (object)));
}

static void
na_box_init (NaBox *self)
{
  GtkOrientation orientation;

  self->icon_padding = 0;
  self->icon_size = 0;

  self->hosts = NULL;
  self->items = NULL;

  orientation = gtk_orientable_get_orientation (GTK_ORIENTABLE (self));
  update_size_and_orientation (self, orientation);

  g_signal_connect (self, "notify::orientation", G_CALLBACK (orientation_notify), NULL);
}

static void
add_host (NaBox  *self,
          NaHost *host)
{
  self->hosts = g_slist_prepend (self->hosts, host);

  g_object_bind_property (self, "icon-padding", host, "icon-padding",
                          G_BINDING_DEFAULT | G_BINDING_SYNC_CREATE);
  g_object_bind_property (self, "icon-size", host, "icon-size",
                          G_BINDING_DEFAULT | G_BINDING_SYNC_CREATE);

  g_signal_connect_object (host, "item-added",
                           G_CALLBACK (item_added_cb), self, 0);
  g_signal_connect_object (host, "item-removed",
                           G_CALLBACK (item_removed_cb), self, 0);
}

static void
na_box_style_updated (GtkWidget *widget)
{
  NaBox           *self = NA_BOX (widget);
  GtkStyleContext *context;
  GSList          *node;

  if (GTK_WIDGET_CLASS (na_box_parent_class)->style_updated)
    GTK_WIDGET_CLASS (na_box_parent_class)->style_updated (widget);

  context = gtk_widget_get_style_context (widget);

  for (node = self->hosts; node; node = node->next)
    {
      gtk_style_context_save (context);
      na_host_style_updated (node->data, context);
      gtk_style_context_restore (context);
    }
}

/* Custom drawing because system-tray items need weird stuff. */
static gboolean
na_box_draw (GtkWidget *box,
             cairo_t   *cr)
{
  GList *child;
  GList *children = gtk_container_get_children (GTK_CONTAINER (box));

  for (child = children; child; child = child->next)
    {
      if (! NA_IS_ITEM (child->data) ||
          ! na_item_draw_on_parent (child->data, box, cr))
	{
	  if (gtk_widget_is_drawable (child->data) &&
	      gtk_cairo_should_draw_window (cr, gtk_widget_get_window (child->data)))
	    gtk_container_propagate_draw (GTK_CONTAINER (box), child->data, cr);
	}
    }

  g_list_free (children);

  return TRUE;
}

static void
na_box_realize (GtkWidget *widget)
{
  NaBox *self = NA_BOX (widget);
  GdkScreen *screen;
  GtkOrientation orientation;
  NaHost *tray_host;

  GTK_WIDGET_CLASS (na_box_parent_class)->realize (widget);

  /* Instantiate the hosts now we have a screen */
  screen = gtk_widget_get_screen (GTK_WIDGET (self));
  orientation = gtk_orientable_get_orientation (GTK_ORIENTABLE (self));
  tray_host = na_tray_new_for_screen (screen, orientation);
  g_object_bind_property (self, "orientation",
                          tray_host, "orientation",
                          G_BINDING_DEFAULT);

  add_host (self, tray_host);
  add_host (self, sn_host_v0_new ());
}

static void
na_box_unrealize (GtkWidget *widget)
{
  NaBox *self = NA_BOX (widget);

  if (self->hosts != NULL)
    {
      g_slist_free_full (self->hosts, g_object_unref);
      self->hosts = NULL;
    }

  g_clear_pointer (&self->items, g_slist_free);

  GTK_WIDGET_CLASS (na_box_parent_class)->unrealize (widget);
}

static void
na_box_get_property (GObject    *object,
                     guint       property_id,
                     GValue     *value,
                     GParamSpec *pspec)
{
  NaBox *self = NA_BOX (object);

  switch (property_id)
    {
      case PROP_ICON_PADDING:
        g_value_set_int (value, self->icon_padding);
        break;

      case PROP_ICON_SIZE:
        g_value_set_int (value, self->icon_size);
        break;

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

static void
na_box_set_property (GObject      *object,
                     guint         property_id,
                     const GValue *value,
                     GParamSpec   *pspec)
{
  NaBox *self = NA_BOX (object);

  switch (property_id)
    {
      case PROP_ICON_PADDING:
        self->icon_padding = g_value_get_int (value);
        break;

      case PROP_ICON_SIZE:
        self->icon_size = g_value_get_int (value);
        break;

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

static void
na_box_class_init (NaBoxClass *klass)
{
  GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
  GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);

  gobject_class->get_property = na_box_get_property;
  gobject_class->set_property = na_box_set_property;

  widget_class->draw = na_box_draw;
  widget_class->realize = na_box_realize;
  widget_class->unrealize = na_box_unrealize;
  widget_class->style_updated = na_box_style_updated;

  g_object_class_install_property (gobject_class, PROP_ICON_PADDING,
    g_param_spec_int ("icon-padding",
                      "Padding around icons",
                      "Padding that should be put around icons, in pixels",
                      0, G_MAXINT, 0,
                      G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));

  g_object_class_install_property (gobject_class, PROP_ICON_SIZE,
    g_param_spec_int ("icon-size",
                      "Icon size",
                      "If non-zero, hardcodes the size of the icons in pixels",
                      0, G_MAXINT, 0,
                      G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
}

GtkWidget *
na_box_new (GtkOrientation  orientation)
{
  return g_object_new (NA_TYPE_BOX,
                       "orientation", orientation,
                       "spacing", ICON_SPACING,
                       NULL);
}

void
na_box_force_redraw (NaBox *box)
{
  GSList *node;

  for (node = box->hosts; node; node = node->next)
    na_host_force_redraw (node->data);
}