/* -*- Mode: c; indent-tabs-mode: nil; c-basic-offset: 2; tab-width: 2 -*- */
/*
 * Copyright 2009 Canonical Ltd.
 *
 * This program is free software: you can redistribute it and/or modify it
 * under the terms of either or both of the following licenses:
 *
 * 1) the GNU Lesser General Public License version 3, as published by the
 * Free Software Foundation; and/or
 * 2) the GNU Lesser General Public License version 2.1, as published by
 * the Free Software Foundation.
 *
 * This program is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranties of
 * MERCHANTABILITY, SATISFACTORY QUALITY or FITNESS FOR A PARTICULAR
 * PURPOSE.  See the applicable version of the GNU Lesser General Public
 * License for more details.
 *
 * You should have received a copy of both the GNU Lesser General Public
 * License version 3 and version 2.1 along with this program.  If not, see
 * <http://www.gnu.org/licenses/>
 *
 * Authored by: Neil Jagdish Patel <neil.patel@canonical.com>
 *
 */
/**
 * SECTION:ctk-image
 * @short_description: A Widget that presents graphical texture data
 * @include: ctk-button.h
 *
 * #CtkImage is designed to present graphical images whist being able to load
 * from intelligent sources such as stock icons and icon names
 */

#if HAVE_CONFIG_H
#include <config.h>
#endif

#include <math.h>

#include "ctk-image.h"

G_DEFINE_TYPE (CtkImage, ctk_image, CTK_TYPE_ACTOR);

#define CTK_IMAGE_GET_PRIVATE(obj) (G_TYPE_INSTANCE_GET_PRIVATE ((obj),\
  CTK_TYPE_IMAGE, \
  CtkImagePrivate))

struct _CtkImagePrivate
{
  ClutterActor *texture;

  GtkIconTheme *icon_theme;
  CtkImageType type;
  gint         size;
  gint         real_width;
  gint         real_height;

  GdkPixbuf *pixbuf;
  gchar     *stock_id;
  gchar     *icon_name;
  GIcon     *gicon;
  gchar     *filename;
};

enum
{
  PROP_0,

  PROP_SIZE,
  PROP_PIXBUF,
  PROP_STOCK,
  PROP_ICON_NAME,
  PROP_GICON,
  PROP_FILENAME
};

/* Globals */

/* Forwards */

/* GObject stuff */
static void
finalize (GObject *object)
{
  CtkImagePrivate *priv = CTK_IMAGE (object)->priv;

  if (priv->texture)
    {
      clutter_actor_unparent (priv->texture);
      priv->texture = NULL;
    }

  G_OBJECT_CLASS (ctk_image_parent_class)->finalize (object);
}

static void
set_property (GObject      *object,
              guint         prop_id,
              const GValue *value,
              GParamSpec   *pspec)
{
  CtkImage *image = CTK_IMAGE (object);

  switch (prop_id)
    {
    case PROP_SIZE:
      ctk_image_set_size (image, g_value_get_int (value));
      break;

    case PROP_PIXBUF:
      ctk_image_set_from_pixbuf (image, g_value_get_object (value));
      break;

    case PROP_STOCK:
      ctk_image_set_from_stock (image, g_value_get_string (value));
      break;

    case PROP_ICON_NAME:
      ctk_image_set_from_icon_name (image, g_value_get_string (value));
      break;

    case PROP_GICON:
      ctk_image_set_from_gicon (image, g_value_get_object (value));
      break;

    case PROP_FILENAME:
      ctk_image_set_from_filename (image, g_value_get_string (value));
      break;

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

static void
get_property (GObject      *object,
              guint         prop_id,
              GValue       *value,
              GParamSpec   *pspec)
{
  CtkImage *image = CTK_IMAGE (object);

  switch (prop_id)
    {
    case PROP_SIZE:
      g_value_set_int (value, ctk_image_get_size (image));
      break;

    case PROP_PIXBUF:
      g_value_set_object (value, ctk_image_get_pixbuf (image));
      break;

    case PROP_STOCK:
      g_value_set_string (value, ctk_image_get_stock (image));
      break;

    case PROP_ICON_NAME:
      g_value_set_string (value, ctk_image_get_icon_name (image));
      break;

    case PROP_GICON:
      g_value_set_object (value, ctk_image_get_gicon (image));
      break;

    case PROP_FILENAME:
      g_value_set_string (value, ctk_image_get_filename (image));
      break;

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

static void
get_preferred_width (ClutterActor *actor,
                     gfloat   for_height,
                     gfloat  *min_width,
                     gfloat  *nat_width)
{
  CtkImagePrivate *priv = CTK_IMAGE (actor)->priv;

  if (min_width)
    *min_width = (gfloat) (priv->size);
  if (nat_width)
    *nat_width = (gfloat) (priv->size);
}

static void
get_preferred_height (ClutterActor *actor,
                      gfloat   for_width,
                      gfloat  *min_height,
                      gfloat  *nat_height)
{
  CtkImagePrivate *priv = CTK_IMAGE (actor)->priv;

  if (min_height)
    *min_height = (gfloat) (priv->size);
  if (nat_height)
    *nat_height = (gfloat) (priv->size);
}

static void
allocate (ClutterActor          *actor,
          const ClutterActorBox *box,
          ClutterAllocationFlags flags)
{
  ClutterActorBox child_box;
  CtkPadding padding;
  gint       img_width = CTK_IMAGE (actor)->priv->real_width;
  gint       img_height = CTK_IMAGE (actor)->priv->real_height;
  gfloat     width;
  gfloat     height;

  CLUTTER_ACTOR_CLASS (ctk_image_parent_class)->allocate (actor,
                                                          box,
                                                          flags);

  ctk_actor_get_padding (CTK_ACTOR (actor), &padding);
  width = box->x2 - box->x1 - padding.left - padding.right;
  height = box->y2 - box->y1 - padding.top - padding.bottom;

  child_box.x1 = floor (padding.left + ((width/2) - (img_width/2)));
  child_box.x2 = floor (child_box.x1 + img_width);
  child_box.y1 = floor (padding.top + ((height/2) - (img_height/2)));
  child_box.y2 = floor (child_box.y1 + img_height);

  clutter_actor_allocate (CTK_IMAGE (actor)->priv->texture,
                          &child_box, flags);
}

static void
paint (ClutterActor *actor)
{
  CtkActor *ctk_actor = CTK_ACTOR (actor);
  GSList   *effects   = NULL;

  if (!ctk_actor_get_effects_painting (ctk_actor)
      && (effects = ctk_actor_get_effects (ctk_actor)))
    {
      GSList *e;

      ctk_actor_set_effects_painting (ctk_actor, TRUE);

      for (e = effects; e; e = e->next)
        {
          gboolean last_effect = e->next ? FALSE : TRUE;
          ctk_effect_paint (e->data, paint, last_effect);
        }

      ctk_actor_set_effects_painting (ctk_actor, FALSE);
    }
  else
    {
      CLUTTER_ACTOR_CLASS (ctk_image_parent_class)->paint (actor);

      clutter_actor_paint (CTK_IMAGE (actor)->priv->texture);
    }
  ctk_actor_set_damaged (ctk_actor, FALSE);
}

static void
pick (ClutterActor *actor, const ClutterColor *color)
{
  CLUTTER_ACTOR_CLASS (ctk_image_parent_class)->pick (actor, color);

  ctk_actor_set_effects_painting (CTK_ACTOR (actor), TRUE);
  clutter_actor_paint (CTK_IMAGE (actor)->priv->texture);
  ctk_actor_set_effects_painting (CTK_ACTOR (actor), FALSE);
}

static void
map (ClutterActor *actor)
{
  CLUTTER_ACTOR_CLASS (ctk_image_parent_class)->map (actor);

  clutter_actor_map (CTK_IMAGE (actor)->priv->texture);
}

static void
unmap (ClutterActor *actor)
{
  CLUTTER_ACTOR_CLASS (ctk_image_parent_class)->unmap (actor);

  clutter_actor_unmap (CTK_IMAGE (actor)->priv->texture);
}

static void
ctk_image_class_init (CtkImageClass *klass)
{
  GObjectClass      *obj_class = G_OBJECT_CLASS (klass);
  ClutterActorClass *act_class = CLUTTER_ACTOR_CLASS (klass);
  GParamSpec        *pspec;

  obj_class->finalize             = finalize;
  obj_class->get_property         = get_property;
  obj_class->set_property         = set_property;

  act_class->get_preferred_width  = get_preferred_width;
  act_class->get_preferred_height = get_preferred_height;
  act_class->allocate             = allocate;
  act_class->paint                = paint;
  act_class->pick                 = pick;
  act_class->map                  = map;
  act_class->unmap                = unmap;

  pspec = g_param_spec_int ("size", "size", "Size of image",
                            0, G_MAXINT, 48, G_PARAM_READWRITE);
  g_object_class_install_property (obj_class, PROP_SIZE, pspec);

  pspec = g_param_spec_object ("pixbuf", "pixbuf", "Pixbuf to display",
                               GDK_TYPE_PIXBUF, G_PARAM_READWRITE);
  g_object_class_install_property (obj_class, PROP_PIXBUF, pspec);

  pspec = g_param_spec_string ("stock-id", "stock-id",
                               "Stock ID of icon to display", NULL,
                               G_PARAM_READWRITE);
  g_object_class_install_property (obj_class, PROP_STOCK, pspec);

  pspec = g_param_spec_string ("icon-name", "icon-name",
                               "Icon name of icon to display", NULL,
                               G_PARAM_READWRITE);
  g_object_class_install_property (obj_class, PROP_ICON_NAME, pspec);

  pspec = g_param_spec_object ("gicon", "gicon", "GIcon to display",
                               G_TYPE_ICON, G_PARAM_READWRITE);
  g_object_class_install_property (obj_class, PROP_GICON, pspec);

  pspec = g_param_spec_string ("filename", "filename",
                               "Filename to display image of", NULL,
                               G_PARAM_READWRITE);
  g_object_class_install_property (obj_class, PROP_FILENAME, pspec);

  g_type_class_add_private (obj_class, sizeof (CtkImagePrivate));
}

static void
ctk_image_init (CtkImage *image)
{
  CtkImagePrivate *priv;

  priv = image->priv = CTK_IMAGE_GET_PRIVATE (image);

  priv->texture = clutter_texture_new ();
  clutter_actor_set_parent (priv->texture, CLUTTER_ACTOR (image));
  clutter_actor_show (priv->texture);

  priv->type = CTK_IMAGE_EMPTY;

  priv->icon_theme = gtk_icon_theme_get_default ();
}

/**
 * ctk_image_new:
 * @size: the size of the resulting image
 *
 * Creates a new Ctk of the given @size
 *
 * returns: a CtkImage
 */
ClutterActor *
ctk_image_new (guint size)
{
  return g_object_new (CTK_TYPE_IMAGE,
                       "size", size,
                       NULL);
}

/*
 * Private methods
 */
static void
free_resources (CtkImage *image)
{
  CtkImagePrivate *priv = image->priv;

  switch (image->priv->type)
    {
    case CTK_IMAGE_EMPTY:
      break;

    case CTK_IMAGE_PIXBUF:
      g_object_unref (priv->pixbuf);
      priv->pixbuf = NULL;
      break;

    case CTK_IMAGE_STOCK:
      g_free (priv->stock_id);
      priv->stock_id = NULL;
      break;

    case CTK_IMAGE_ICON_NAME:
      g_free (priv->icon_name);
      priv->icon_name = NULL;
      break;

    case CTK_IMAGE_GICON:
      g_object_unref (priv->gicon);
      priv->gicon = NULL;
      break;

    case CTK_IMAGE_FILENAME:
      g_free (priv->filename);
      priv->filename = NULL;
      break;

    default:
      g_critical ("free_resources not implemented for CtkImageType: %d",
                  image->priv->type);
      break;
    }
}

static void
refresh_icon (CtkImage *image)
{
  CtkImagePrivate *priv = image->priv;

  if (priv->type == CTK_IMAGE_EMPTY)
    {
      ;
    }
  else if (priv->type == CTK_IMAGE_PIXBUF)
    {
      gtk_clutter_texture_set_from_pixbuf (CLUTTER_TEXTURE (priv->texture),
                                           priv->pixbuf,
                                           NULL);
      priv->real_width = gdk_pixbuf_get_width (priv->pixbuf);
      priv->real_height = gdk_pixbuf_get_height (priv->pixbuf);

      if (priv->real_width > priv->size || priv->real_height > priv->size)
        {
          gfloat max_edge = MAX (priv->real_width, priv->real_height) *1.0;
          priv->real_width = priv->size * (priv->real_width/max_edge);
          priv->real_height = priv->size * (priv->real_height/max_edge);
        }
    }
  else if (priv->type == CTK_IMAGE_STOCK)
    {
      GdkPixbuf *pixbuf;
      GError    *error = NULL;

      pixbuf = gtk_icon_theme_load_icon (priv->icon_theme,
                                         priv->stock_id,
                                         priv->size,
                                         GTK_ICON_LOOKUP_FORCE_SIZE,
                                         &error);
      if (error)
        {
          g_warning ("Unable to show icon %s at size %d: %s",
                     priv->stock_id,
                     priv->size,
                     error->message);
          g_error_free (error);
        }

      if (pixbuf)
        {
          gtk_clutter_texture_set_from_pixbuf (CLUTTER_TEXTURE (priv->texture),
                                               pixbuf,
                                               NULL);
          priv->real_width = gdk_pixbuf_get_width (pixbuf);
          priv->real_height = gdk_pixbuf_get_height (pixbuf);

          g_object_unref (pixbuf);
        }
    }
  else if (priv->type == CTK_IMAGE_ICON_NAME)
    {
      g_debug ("CtkImageType == CTK_IMAGE_ICON_NAME not implemented");
    }
  else if (priv->type == CTK_IMAGE_GICON)
    {
      g_debug ("CtkImageType == CTK_IMAGE_GICON not implemented");
    }
  else if (priv->type == CTK_IMAGE_FILENAME)
    {
      GdkPixbuf *pixbuf;
      GError    *error = NULL;

      pixbuf = gdk_pixbuf_new_from_file_at_size (priv->filename,
						 priv->size,
						 priv->size,
						 &error);

      if (error)
	{
	  g_warning ("Unable to load icon %s: %s",
		     priv->filename,
		     error->message);
	  g_error_free (error);
	}

      if (pixbuf)
	{
	  gtk_clutter_texture_set_from_pixbuf (CLUTTER_TEXTURE (priv->texture),
					       pixbuf,
					       NULL);

	  priv->real_width = gdk_pixbuf_get_width (pixbuf);
	  priv->real_height = gdk_pixbuf_get_height (pixbuf);

	  g_object_unref (pixbuf);
	}
    }
  else
    {
      g_critical ("Refresh function not implemented for CtkImageType: %d",
                  priv->type);
    }

  priv->real_width = MIN (priv->real_width, priv->size);
  priv->real_height = MIN (priv->real_height, priv->size);

  clutter_actor_queue_redraw (CLUTTER_ACTOR (image));
}

/*
 * Public methods
 */

/**
 * ctk_image_new_from_pixbuf:
 * @size: the size of the image in pixels
 * @pixbuf: a #GdkPixbuf object
 *
 * Creates a new #CtkImage object based on the data in @pixbuf
 *
 * Returns: a new #CtkImage
 */
ClutterActor *
ctk_image_new_from_pixbuf (guint        size,
                           GdkPixbuf   *pixbuf)
{
  ClutterActor *image;

  image = ctk_image_new (size);

  g_object_set (image, "pixbuf", pixbuf, NULL);

  return image;
}

/**
 * ctk_image_new_from_stock:
 * @size: the size of the image in pixels
 * @stock_id: a string containing the stock id name
 *
 * Creates a new #CtkImage based on the @stock_id provided, see #GtkStockItem
 *
 * Returns: a new #CtkImage
 */
ClutterActor *
ctk_image_new_from_stock (guint        size,
                          const gchar *stock_id)
{
  ClutterActor *image;

  image = ctk_image_new (size);

  g_object_set (image, "stock-id", stock_id, NULL);

  return image;
}

/**
 * ctk_image_new_from_icon_name:
 * @size: the size of the image in pixels
 * @icon_name: a string containing the icon name
 *
 * Uses a "named icon" to create a new CtkImage object
 *
 * Returns: a new #ClutterActor
 */
ClutterActor *
ctk_image_new_from_icon_name (guint        size,
                              const gchar *icon_name)
{
  ClutterActor *image;

  image = ctk_image_new (size);

  g_object_set (image, "icon-name", icon_name, NULL);

  return image;
}

/**
 * ctk_image_new_from_gicon:
 * @size: the size of the image in pixels
 * @icon: a #GIcon object containing the icon
 *
 * Creates a new #CtkImage object based on the data in the provided #GIcon @icon
 *
 * Returns: a new #CtkImage
 */
ClutterActor *
ctk_image_new_from_gicon (guint        size,
                          GIcon       *icon)
{
  ClutterActor *image;

  image = ctk_image_new (size);

  g_object_set (image, "gicon", icon, NULL);

  return image;
}

/**
 * ctk_image_new_from_filename:
 * @size: the size of the image in pixels
 * @filename: the filename of the image
 *
 * Creates a new #CtkImage from the image containined at @filename
 *
 * Returns: a new #CtkImage
 */
ClutterActor *
ctk_image_new_from_filename (guint        size,
                             const gchar * filename)
{
  ClutterActor *image;

  image = ctk_image_new (size);

  g_object_set (image, "filename", filename, NULL);

  return image;
}

/**
 * ctk_image_get_image_storage_type:
 * @image: A CtkImage
 *
 * Retrives the storage type used to create/set the CtkImage object
 *
 * Returns: a #CtkImageType object
 */
CtkImageType
ctk_image_get_image_storage_type (CtkImage *image)
{
  g_return_val_if_fail (CTK_IS_IMAGE (image), CTK_IMAGE_EMPTY);

  return image->priv->type;
}

/**
 * ctk_image_set_size:
 * @image: A #CtkImage
 * @size: size in pixels of the image
 *
 * Sets the size of @image to the given @size
 */
void
ctk_image_set_size (CtkImage *image,
                    guint          size)
{
  g_return_if_fail (CTK_IS_IMAGE (image));

  image->priv->size = size;

  refresh_icon (image);

  clutter_actor_queue_relayout (CLUTTER_ACTOR (image));
}

/**
 * ctk_image_get_size:
 * @image: A #CtkImage object
 *
 * Retrives the current size in pixels of @image
 *
 * Returns: a uint, 0 if @image is invalid
 */
guint
ctk_image_get_size (CtkImage *image)
{
  g_return_val_if_fail (CTK_IS_IMAGE (image), 0);

  return image->priv->size;
}

/**
 * ctk_image_set_from_pixbuf:
 * @image: A #CtkImage object
 * @pixbuf: A #GdkPixbuf object
 *
 * sets @image to use @pixbuf for its image data
 * see ctk_image_new_from_pixbuf()
 */
void
ctk_image_set_from_pixbuf (CtkImage  *image,
                           GdkPixbuf *pixbuf)
{
  g_return_if_fail (CTK_IS_IMAGE (image));

  free_resources (image);

  image->priv->type = CTK_IMAGE_PIXBUF;
  if (pixbuf)
    image->priv->pixbuf = g_object_ref (pixbuf);

  refresh_icon (image);
}

/**
 * ctk_image_set_from_stock:
 * @image: a CtkImage
 * @stock_id: a gchar referencing the stock image id
 *
 * sets a CtkImage to display a stock icon
 * see ctk_image_new_from_stock()
 */
void
ctk_image_set_from_stock (CtkImage    *image,
                          const gchar *stock_id)
{
  g_return_if_fail (CTK_IS_IMAGE (image));
  g_return_if_fail (stock_id);

  free_resources (image);

  image->priv->type = CTK_IMAGE_STOCK;
  image->priv->stock_id = g_strdup (stock_id);

  refresh_icon (image);
}

/**
 * ctk_image_set_from_icon_name:
 * @image: A #CtkImage
 * @icon_name: a string representation of the icon name
 *
 * Sets @image to use the image data supplied with the given @icon_name
 * see: ctk_image_new_from_icon_name();
 */
void
ctk_image_set_from_icon_name (CtkImage    *image,
                              const gchar *icon_name)
{
  g_return_if_fail (CTK_IS_IMAGE (image));
  g_return_if_fail (icon_name);

  free_resources (image);

  image->priv->type = CTK_IMAGE_ICON_NAME;
  image->priv->icon_name = g_strdup (icon_name);

  refresh_icon (image);
}

/**
 * ctk_image_set_from_gicon:
 * @image: a #CtkImage
 * @icon: A #GIcon object
 *
 * Sets @image to use @icon as its image data
 * see: ctk_image_new_from_gicon()
 */
void
ctk_image_set_from_gicon (CtkImage    *image,
                          GIcon       *icon)
{
  g_return_if_fail (CTK_IS_IMAGE (image));
  g_return_if_fail (G_IS_ICON (icon));

  free_resources (image);

  image->priv->type = CTK_IMAGE_GICON;
  image->priv->gicon = g_object_ref (icon);

  refresh_icon (image);
}

/**
 * ctk_image_set_from_filename:
 * @image: a #CtkImage
 * @filename: a string representation of the filename
 *
 * Sets @image to use @filename as its iamge data
 */
void
ctk_image_set_from_filename (CtkImage    *image,
                             const gchar *filename)
{
  g_return_if_fail (CTK_IS_IMAGE (image));
  g_return_if_fail (filename);

  free_resources (image);

  image->priv->type = CTK_IMAGE_FILENAME;
  image->priv->filename = g_strdup (filename);

  refresh_icon (image);
}

/**
 *
 */
GdkPixbuf *
ctk_image_get_pixbuf (CtkImage    *image)
{
  g_return_val_if_fail (CTK_IS_IMAGE (image), NULL);

  return image->priv->pixbuf;
}

/**
 *
 */
const gchar *
ctk_image_get_stock (CtkImage    *image)
{
  g_return_val_if_fail (CTK_IS_IMAGE (image), NULL);

  return image->priv->stock_id;
}

/**
 *
 */
const gchar *
ctk_image_get_icon_name (CtkImage    *image)
{
  g_return_val_if_fail (CTK_IS_IMAGE (image), NULL);

  return image->priv->icon_name;
}

/**
 *
 */
GIcon *
ctk_image_get_gicon (CtkImage    *image)
{
  g_return_val_if_fail (CTK_IS_IMAGE (image), NULL);

  return image->priv->gicon;
}

/**
 *
 */
const gchar *
ctk_image_get_filename (CtkImage    *image)
{
  g_return_val_if_fail (CTK_IS_IMAGE (image), NULL);

  return image->priv->filename;
}


