/*
 Copyright (C) 2008-2009 Christian Dywan <christian@twotoasts.de>

 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.

 See the file COPYING for the full license text.
*/
 
#include "katze-item.h"
#include "katze-utils.h"
#include "midori/midori-core.h"

#include <glib/gi18n.h>

/**
 * SECTION:katze-item
 * @short_description: A useful item
 * @see_also: #KatzeArray
 *
 * #KatzeItem is a particularly useful item that provides
 * several commonly needed properties.
 */

G_DEFINE_TYPE (KatzeItem, katze_item, G_TYPE_OBJECT);

enum
{
    PROP_0,

    PROP_NAME,
    PROP_TEXT,
    PROP_URI,
    PROP_ICON,
    PROP_TOKEN,
    PROP_ADDED,
    PROP_PARENT
};

enum {
    META_DATA_CHANGED,

    LAST_SIGNAL
};

static guint signals[LAST_SIGNAL];

static void
katze_item_finalize (GObject* object);

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

static void
katze_item_get_property (GObject*    object,
                         guint       prop_id,
                         GValue*     value,
                         GParamSpec* pspec);

static void
katze_item_class_init (KatzeItemClass* class)
{
    GObjectClass* gobject_class;
    GParamFlags flags;

    /**
     * KatzeItem::meta-data-changed:
     * @item: the object on which the signal is emitted
     * @key: the key that changed
     *
     * Emitted when a meta data value was changed.
     *
     * Since 0.2.2 details according to keys are supported.
     *
     * Since: 0.1.9
     */
    signals[META_DATA_CHANGED] = g_signal_new (
        "meta-data-changed",
        G_TYPE_FROM_CLASS (class),
        (GSignalFlags)(G_SIGNAL_RUN_LAST |G_SIGNAL_DETAILED),
        0,
        0,
        NULL,
        g_cclosure_marshal_VOID__STRING,
        G_TYPE_NONE, 1,
        G_TYPE_STRING);

    gobject_class = G_OBJECT_CLASS (class);
    gobject_class->finalize = katze_item_finalize;
    gobject_class->set_property = katze_item_set_property;
    gobject_class->get_property = katze_item_get_property;

    flags = G_PARAM_READWRITE | G_PARAM_CONSTRUCT;

    g_object_class_install_property (gobject_class,
                                     PROP_NAME,
                                     g_param_spec_string (
                                     "name",
                                     "Name",
                                     "The name of the item",
                                     NULL,
                                     flags));

    g_object_class_install_property (gobject_class,
                                     PROP_TEXT,
                                     g_param_spec_string (
                                     "text",
                                     "Text",
                                     "The descriptive text of the item",
                                     NULL,
                                     flags));

    g_object_class_install_property (gobject_class,
                                     PROP_URI,
                                     g_param_spec_string (
                                     "uri",
                                     "URI",
                                     "The URI of the item",
                                     NULL,
                                     flags));

    g_object_class_install_property (gobject_class,
                                     PROP_ICON,
                                     g_param_spec_string (
                                     "icon",
                                     "Icon",
                                     "The icon of the item",
                                     NULL,
                                     flags));

    g_object_class_install_property (gobject_class,
                                     PROP_TOKEN,
                                     g_param_spec_string (
                                     "token",
                                     "Token",
                                     "The token of the item",
                                     NULL,
                                     flags));

    g_object_class_install_property (gobject_class,
                                     PROP_ADDED,
                                     g_param_spec_int64 (
                                     "added",
                                     "Added",
                                     "When the item was added",
                                     G_MININT64,
                                     G_MAXINT64,
                                     0,
                                     flags));

    /**
    * KatzeItem:parent:
    *
    * The parent of the item.
    *
    * Since: 0.1.2
    */
    g_object_class_install_property (gobject_class,
                                     PROP_PARENT,
                                     g_param_spec_object (
                                     "parent",
                                     "Parent",
                                     "The parent of the item",
                                     G_TYPE_OBJECT,
                                     flags));

    class->copy = NULL;
}



static void
katze_item_init (KatzeItem* item)
{
    item->metadata = g_hash_table_new_full (g_str_hash, g_str_equal,
                                            g_free, g_free);
}

static void
katze_item_finalize (GObject* object)
{
    KatzeItem* item = KATZE_ITEM (object);

    g_free (item->name);
    g_free (item->text);
    g_free (item->uri);
    g_free (item->token);

    g_hash_table_unref (item->metadata);

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

static void
katze_item_set_property (GObject*      object,
                         guint         prop_id,
                         const GValue* value,
                         GParamSpec*   pspec)
{
    KatzeItem* item = KATZE_ITEM (object);

    switch (prop_id)
    {
    case PROP_NAME:
        katze_assign (item->name, g_value_dup_string (value));
        break;
    case PROP_TEXT:
        katze_assign (item->text, g_value_dup_string (value));
        break;
    case PROP_URI:
        katze_assign (item->uri, g_value_dup_string (value));
        break;
    case PROP_ICON:
        katze_item_set_icon (item, g_value_get_string (value));
        break;
    case PROP_TOKEN:
        katze_assign (item->token, g_value_dup_string (value));
        break;
    case PROP_ADDED:
        item->added = g_value_get_int64 (value);
        break;
    case PROP_PARENT:
        katze_item_set_parent (item, g_value_get_object (value));
        break;
    default:
        G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
        break;
    }
}

static void
katze_item_get_property (GObject*    object,
                         guint       prop_id,
                         GValue*     value,
                         GParamSpec* pspec)
{
    KatzeItem* item = KATZE_ITEM (object);

    switch (prop_id)
    {
    case PROP_NAME:
        g_value_set_string (value, item->name);
        break;
    case PROP_TEXT:
        g_value_set_string (value, item->text);
        break;
    case PROP_URI:
        g_value_set_string (value, item->uri);
        break;
    case PROP_ICON:
        g_value_set_string (value, katze_item_get_icon (item));
        break;
    case PROP_TOKEN:
        g_value_set_string (value, item->token);
        break;
    case PROP_ADDED:
        g_value_set_int64 (value, item->added);
        break;
    case PROP_PARENT:
        g_value_set_object (value, item->parent);
        break;
    default:
        G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
        break;
    }
}

/**
 * katze_item_new:
 *
 * Creates a new #KatzeItem.
 *
 * Return value: (transfer full): a new #KatzeItem
 **/
KatzeItem*
katze_item_new (void)
{
    KatzeItem* item = g_object_new (KATZE_TYPE_ITEM, NULL);

    return item;
}

/**
 * katze_item_get_name:
 * @item: a #KatzeItem
 *
 * Retrieves the name of @item.
 *
 * Return value: the name of the item
 **/
const gchar*
katze_item_get_name (KatzeItem* item)
{
    g_return_val_if_fail (KATZE_IS_ITEM (item), NULL);

    return item->name;
}

/**
 * katze_item_set_name:
 * @item: a #KatzeItem
 * @name: a string
 *
 * Sets the name of @item.
 **/
void
katze_item_set_name (KatzeItem*   item,
                     const gchar* name)
{
    g_return_if_fail (KATZE_IS_ITEM (item));

    if (!g_strcmp0 (item->name, name))
        return;

    katze_assign (item->name, g_strdup (name));
    if (item->parent)
        katze_array_update ((KatzeArray*)item->parent);
    g_object_notify (G_OBJECT (item), "name");
}

/**
 * katze_item_get_text:
 * @item: a #KatzeItem
 *
 * Retrieves the descriptive text of @item.
 *
 * Return value: the text of the item
 **/
const gchar*
katze_item_get_text (KatzeItem* item)
{
    g_return_val_if_fail (KATZE_IS_ITEM (item), NULL);

    return item->text;
}

/**
 * katze_item_set_text:
 * @item: a #KatzeItem
 * @text: a string
 *
 * Sets the descriptive text of @item.
 **/
void
katze_item_set_text (KatzeItem*   item,
                     const gchar* text)
{
    g_return_if_fail (KATZE_IS_ITEM (item));

    katze_assign (item->text, g_strdup (text));
    g_object_notify (G_OBJECT (item), "text");
}

/**
 * katze_item_get_uri:
 * @item: a #KatzeItem
 *
 * Retrieves the URI of @item.
 *
 * Return value: the URI of the item
 **/
const gchar*
katze_item_get_uri (KatzeItem* item)
{
    g_return_val_if_fail (KATZE_IS_ITEM (item), NULL);

    return item->uri;
}

/**
 * katze_item_set_uri:
 * @item: a #KatzeItem
 * @uri: a string
 *
 * Sets the URI of @item.
 **/
void
katze_item_set_uri (KatzeItem*   item,
                    const gchar* uri)
{
    g_return_if_fail (KATZE_IS_ITEM (item));

    if (!g_strcmp0 (item->uri, uri))
        return;

    katze_assign (item->uri, g_strdup (uri));
    g_object_notify (G_OBJECT (item), "uri");
}

/**
 * katze_item_get_icon:
 * @item: a #KatzeItem
 *
 * Retrieves the icon of @item.
 *
 * Return value: the icon of the item
 **/
const gchar*
katze_item_get_icon (KatzeItem* item)
{
    g_return_val_if_fail (KATZE_IS_ITEM (item), NULL);

    return katze_item_get_meta_string (item, "icon");
}

/**
 * katze_item_set_icon:
 * @item: a #KatzeItem
 * @icon: a string
 *
 * Sets the icon of @item.
 **/
void
katze_item_set_icon (KatzeItem*   item,
                     const gchar* icon)
{
    g_return_if_fail (KATZE_IS_ITEM (item));

    if (!g_strcmp0 (katze_item_get_meta_string (item, "icon"), icon))
        return;

    katze_item_set_meta_string (item, "icon", icon);
    if (item->parent)
        katze_array_update ((KatzeArray*)item->parent);
    g_object_notify (G_OBJECT (item), "icon");
}

/**
 * katze_item_get_pixbuf:
 * @item: a #KatzeItem
 * @widget: a #GtkWidget, or %NULL
 *
 * Creates a #GdkPixbuf fit to display @item.
 *
 * Return value: (transfer full): the icon of the item, or %NULL
 *
 * Since: 0.4.6
 **/
GdkPixbuf*
katze_item_get_pixbuf (KatzeItem* item,
                       GtkWidget*  widget)
{
    GdkPixbuf* pixbuf;

    g_return_val_if_fail (KATZE_IS_ITEM (item), NULL);

    if (widget && KATZE_ITEM_IS_FOLDER (item))
        return gtk_widget_render_icon (widget, GTK_STOCK_DIRECTORY, GTK_ICON_SIZE_MENU, NULL);
    if ((pixbuf = midori_paths_get_icon (item->uri, widget)))
        return pixbuf;
    return NULL;
}

static void
katze_item_image_destroyed_cb (GtkWidget* image,
                               KatzeItem* item);
#ifndef HAVE_WEBKIT2
static void
katze_item_icon_loaded_cb (WebKitFaviconDatabase* database,
                           const gchar*           frame_uri,
                           GtkWidget*             image)
{
    KatzeItem* item = g_object_get_data (G_OBJECT (image), "KatzeItem");
    GdkPixbuf* pixbuf;
    if (!g_strcmp0 (frame_uri, item->uri)
      && (pixbuf = midori_paths_get_icon (frame_uri, image)))
    {
        gtk_image_set_from_pixbuf (GTK_IMAGE (image), pixbuf);
        g_object_unref (pixbuf);
        /* This signal fires extremely often (WebKit bug?)
           we must throttle it (disconnect) once we have an icon */
        katze_item_image_destroyed_cb (image, g_object_ref (item));
    }
}
#endif

static void
katze_item_image_destroyed_cb (GtkWidget* image,
                               KatzeItem* item)
{
#ifndef HAVE_WEBKIT2
    g_signal_handlers_disconnect_by_func (webkit_get_favicon_database (),
        katze_item_icon_loaded_cb, image);
#endif
    g_object_unref (item);
}

/**
 * katze_item_get_image:
 * @item: a #KatzeItem
 * @widget: a #GtkWidget, or %NULL
 *
 * Creates a #GtkImage fit to display @item.
 *
 * Return value: (transfer floating): the icon of the item
 *
 * Since: 0.4.4
 * Since 0.4.8 a @widget was added and the image is set visible.
 **/
GtkWidget*
katze_item_get_image (KatzeItem* item,
                      GtkWidget* widget)
{
    GtkWidget* image;
    GdkPixbuf* pixbuf;

    g_return_val_if_fail (KATZE_IS_ITEM (item), NULL);

    pixbuf = katze_item_get_pixbuf (item, widget);
    image = gtk_image_new_from_pixbuf (pixbuf);
    gtk_widget_show (image);
    if (pixbuf != NULL)
        g_object_unref (pixbuf);
    if (KATZE_ITEM_IS_FOLDER (item))
        return image;
    g_object_set_data (G_OBJECT (image), "KatzeItem", g_object_ref (item));
    g_signal_connect (image, "destroy",
        G_CALLBACK (katze_item_image_destroyed_cb), item);
#ifndef HAVE_WEBKIT2
    g_signal_connect (webkit_get_favicon_database (), "icon-loaded",
        G_CALLBACK (katze_item_icon_loaded_cb), image);
#endif
    return image;
}

/**
 * katze_item_get_token:
 * @item: a #KatzeItem
 *
 * Retrieves the token of @item.
 *
 * Return value: the token of the item
 **/
const gchar*
katze_item_get_token (KatzeItem* item)
{
    g_return_val_if_fail (KATZE_IS_ITEM (item), NULL);

    return item->token;
}

/**
 * katze_item_set_token:
 * @item: a #KatzeItem
 * @token: a string
 *
 * Sets the token of @item.
 **/
void
katze_item_set_token (KatzeItem*   item,
                      const gchar* token)
{
    g_return_if_fail (KATZE_IS_ITEM (item));

    katze_assign (item->token, g_strdup (token));
    g_object_notify (G_OBJECT (item), "token");
}

/**
 * katze_item_get_added:
 * @item: a #KatzeItem
 *
 * Determines when @item was added.
 *
 * Return value: a timestamp
 **/
gint64
katze_item_get_added (KatzeItem* item)
{
    g_return_val_if_fail (KATZE_IS_ITEM (item), 0);

    return item->added;
}

/**
 * katze_item_set_added:
 * @item: a #KatzeItem
 * @added: a timestamp
 *
 * Sets when @item was added.
 **/
void
katze_item_set_added (KatzeItem* item,
                      gint64     added)
{
    g_return_if_fail (KATZE_IS_ITEM (item));

    item->added = added;
    g_object_notify (G_OBJECT (item), "added");
}

/**
 * katze_item_get_meta_keys:
 * @item: a #KatzeItem
 *
 * Retrieves a list of all meta keys.
 *
 * Return value: (element-type utf8) (transfer container): a newly allocated #GList of constant strings
 *
 * Since: 0.1.8
 **/
GList*
katze_item_get_meta_keys (KatzeItem* item)
{
    g_return_val_if_fail (KATZE_IS_ITEM (item), NULL);

    return g_hash_table_get_keys (item->metadata);
}

static void
katze_item_set_meta_data_value (KatzeItem*   item,
                                const gchar* key,
                                gchar*       value)
{
    /* FIXME: Make the default namespace configurable */
    if (g_str_has_prefix (key, "midori:"))
        g_hash_table_insert (item->metadata, g_strdup (&key[7]), value);
    else
        g_hash_table_insert (item->metadata, g_strdup (key), value);
    g_signal_emit (item, signals[META_DATA_CHANGED], g_quark_from_string (key), key);
}

/**
 * katze_item_get_meta_string:
 * @item: a #KatzeItem
 * @key: the name of an integer value
 *
 * Retrieves a string value by the specified key from the
 * meta data of the item.
 *
 * Specify "namespace:key" or "key" to use the default namespace.
 *
 * Return value: a string, or %NULL
 *
 * Since: 0.1.8
 *
 * Since 0.4.4 "" is treated like %NULL.
 **/
const gchar*
katze_item_get_meta_string (KatzeItem*   item,
                            const gchar* key)
{
    const gchar* value;

    g_return_val_if_fail (KATZE_IS_ITEM (item), NULL);
    g_return_val_if_fail (key != NULL, NULL);

    if (g_str_has_prefix (key, "midori:"))
        key = &key[7];
    value = g_hash_table_lookup (item->metadata, key);
    return value && *value ? value : NULL;
}

/**
 * katze_item_set_meta_string:
 * @item: a #KatzeItem
 * @key: the name of a string value
 * @value: the value as a string
 *
 * Saves the specified string value in the meta data of
 * the item under the specified key.
 *
 * Specify "namespace:key" or "key" to use the default namespace.
 *
 * Since: 0.1.8
 **/
void
katze_item_set_meta_string (KatzeItem*   item,
                            const gchar* key,
                            const gchar* value)
{
    g_return_if_fail (KATZE_IS_ITEM (item));
    g_return_if_fail (key != NULL);

    katze_item_set_meta_data_value (item, key, g_strdup (value));
}

/**
 * katze_item_get_meta_integer:
 * @item: a #KatzeItem
 * @key: the name of an integer value
 *
 * Retrieves an integer value by the specified key from the
 * meta data of the item.
 *
 * If the key is present but not representable as an
 * integer, -1 is returned.
 *
 * Return value: an integer value, or -1
 *
 * Since: 0.1.8
 **/
gint64
katze_item_get_meta_integer (KatzeItem*   item,
                             const gchar* key)
{
    gpointer value;

    g_return_val_if_fail (KATZE_IS_ITEM (item), -1);
    g_return_val_if_fail (key != NULL, -1);

    if (g_str_has_prefix (key, "midori:"))
        key = &key[7];
    if (g_hash_table_lookup_extended (item->metadata, key, NULL, &value))
        return value ? g_ascii_strtoll (value, NULL, 0) : -1;
    return -1;
}

/**
 * katze_item_get_meta_boolean:
 * @item: a #KatzeItem
 * @key: the name of a boolean value
 *
 * The Value should be set with katze_item_set_meta_integer().
 * If the value is set and not 0, %TRUE will be returned.
 *
 * Since: 0.2.7
 **/
gboolean
katze_item_get_meta_boolean  (KatzeItem*   item,
                              const gchar* key)
{
    const gchar* value;

    g_return_val_if_fail (KATZE_IS_ITEM (item), FALSE);
    g_return_val_if_fail (key != NULL, FALSE);

    value = katze_item_get_meta_string (item, key);
    if (value == NULL || value[0] == '0')
        return FALSE;
    else
        return TRUE;
}

/**
 * katze_item_set_meta_integer:
 * @item: a #KatzeItem
 * @key: the name of an integer value
 *
 * Saves the specified integer value in the meta data of
 * the item under the specified key.
 *
 * A value of -1 is intepreted as unset.
 *
 * Since: 0.1.8
 **/
void
katze_item_set_meta_integer (KatzeItem*   item,
                             const gchar* key,
                             gint64       value)
{
    g_return_if_fail (KATZE_IS_ITEM (item));
    g_return_if_fail (key != NULL);

    if (value == -1)
        katze_item_set_meta_data_value (item, key, NULL);
    else
    {
        katze_item_set_meta_data_value (item, key,
        #ifdef G_GINT64_FORMAT
            g_strdup_printf ("%" G_GINT64_FORMAT, value));
        #else
            g_strdup_printf ("%li", value));
        #endif
    }
}

/**
 * katze_item_get_parent:
 * @item: a #KatzeItem
 *
 * Determines the parent of @item.
 *
 * Since 0.1.2 you can monitor the "parent" property.
 *
 * Return value: (type GObject) (transfer none): the parent of the item
 **/
gpointer
katze_item_get_parent (KatzeItem* item)
{
    g_return_val_if_fail (KATZE_IS_ITEM (item), NULL);

    return item->parent;
}

/**
 * katze_item_set_parent:
 * @item: a #KatzeItem
 * @parent: the new parent
 *
 * Sets the parent of @item.
 *
 * This is intended for item container implementations. Notably
 * the new parent will not be notified of the change.
 *
 * Since 0.1.2 you can monitor the "parent" property, so unsetting
 * the parent is actually safe if the parent supports it.
 **/
void
katze_item_set_parent (KatzeItem* item,
                       gpointer   parent)
{
    g_return_if_fail (KATZE_IS_ITEM (item));
    g_return_if_fail (!parent || G_IS_OBJECT (parent));

    if (parent)
        g_object_ref (parent);
    katze_object_assign (item->parent, parent);
    g_object_notify (G_OBJECT (item), "parent");
}

/**
 * katze_item_copy:
 * @item: a #KatzeItem
 *
 * Creates an exact copy of @item.
 *
 * Note that subclass specific features will only
 * be preserved if the class implements it.
 *
 * Since 0.4.3 meta data is copied.
 *
 * Return value: (transfer full): a new #KatzeItem
 *
 * Since: 0.1.3
 **/
KatzeItem*
katze_item_copy (KatzeItem* item)
{
    KatzeItem* copy;
    GHashTableIter iter;
    const gchar* key;
    const gchar* value;
    KatzeItemClass* class;

    g_return_val_if_fail (KATZE_IS_ITEM (item), NULL);

    copy = g_object_new (G_OBJECT_TYPE (item),
        "name", item->name,
        "text", item->text,
        "uri", item->uri,
        "token", item->token,
        "added", item->added,
        "parent", item->parent,
        NULL);

    g_hash_table_iter_init (&iter, item->metadata);
    while (g_hash_table_iter_next (&iter, (void*)&key, (void*)&value))
    {
        if (g_str_has_prefix (key, "midori:"))
            key = &key[7];
        g_hash_table_insert (copy->metadata, g_strdup (key), g_strdup (value));
    }

    class = KATZE_ITEM_GET_CLASS (item);
    return class->copy ? class->copy (copy) : copy;
}
