/* class group object */

/*
 * Copyright (C) 2003 Ximian, Inc.
 * Authors: Federico Mena-Quintero <federico@ximian.com>
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Library General Public
 * License as published by the Free Software Foundation; either
 * version 2 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
 * Library General Public License for more details.
 *
 * You should have received a copy of the GNU Library General Public
 * License along with this library; if not, write to the
 * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
 * Boston, MA 02111-1307, USA.
 */

#include "netk-class-group.h"
#include "netk-window.h"
#include "netk-private.h"

#define NETK_CLASS_GROUP_GET_PRIVATE(o)    (G_TYPE_INSTANCE_GET_PRIVATE ((o), NETK_TYPE_CLASS_GROUP, NetkClassGroupPrivate))

/* Private part of the NetkClassGroup structure */
struct _NetkClassGroupPrivate
{
    char *res_class;
    char *name;
    GList *windows;

    GdkPixbuf *icon;
    GdkPixbuf *mini_icon;
};

#define ICON_SIZE 32
#define MINI_ICON_SIZE 16

/* Hash table that maps res_class strings -> NetkClassGroup instances */
static GHashTable *class_group_hash = NULL;



static void netk_class_group_class_init (NetkClassGroupClass * class);
static void netk_class_group_init (NetkClassGroup * class_group);
static void netk_class_group_finalize (GObject * object);

enum
{
    NAME_CHANGED,
    ICON_CHANGED,
    LAST_SIGNAL
};

static guint signals[LAST_SIGNAL] = { 0 };

static GObjectClass *parent_class;


GType
netk_class_group_get_type (void)
{
    static GType type = G_TYPE_INVALID;

    if (G_UNLIKELY (type == G_TYPE_INVALID))
    {
        type = _netk_g_type_register_simple (G_TYPE_OBJECT,
                                             "NetkClassGroup",
                                             sizeof (NetkClassGroupClass),
                                             netk_class_group_class_init,
                                             sizeof (NetkClassGroup),
                                             netk_class_group_init,
                                             0);
    }

    return type;
}

static void
netk_class_group_class_init (NetkClassGroupClass * klass)
{
    GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
    
    g_type_class_add_private (klass, sizeof (NetkClassGroupPrivate));

    parent_class = g_type_class_peek_parent (klass);

    gobject_class->finalize = netk_class_group_finalize;

    signals[NAME_CHANGED] = 
        g_signal_new (I_("name_changed"),
                      G_OBJECT_CLASS_TYPE (gobject_class),
                      G_SIGNAL_RUN_LAST,
                      G_STRUCT_OFFSET (NetkClassGroupClass, name_changed),
                      NULL, NULL, g_cclosure_marshal_VOID__VOID, G_TYPE_NONE, 0);
                      
    signals[ICON_CHANGED] = 
        g_signal_new (I_("icon_changed"),
                      G_OBJECT_CLASS_TYPE (gobject_class),
                      G_SIGNAL_RUN_LAST,
                      G_STRUCT_OFFSET (NetkClassGroupClass, icon_changed),
                      NULL, NULL, g_cclosure_marshal_VOID__VOID, G_TYPE_NONE, 0);
}

static void
netk_class_group_init (NetkClassGroup * class_group)
{
    class_group->priv = NETK_CLASS_GROUP_GET_PRIVATE (class_group);
    
    class_group->priv->res_class = NULL;
    class_group->priv->name = NULL;
    class_group->priv->windows = NULL;

    class_group->priv->icon = NULL;
    class_group->priv->mini_icon = NULL;
}

static void
netk_class_group_finalize (GObject * object)
{
    NetkClassGroup *class_group;

    class_group = NETK_CLASS_GROUP (object);

    if (class_group->priv->res_class)
        g_free (class_group->priv->res_class);

    if (class_group->priv->name)
        g_free (class_group->priv->name);

    g_list_free (class_group->priv->windows);

    if (class_group->priv->icon)
        g_object_unref (G_OBJECT (class_group->priv->icon));

    if (class_group->priv->mini_icon)
        g_object_unref (G_OBJECT (class_group->priv->mini_icon));

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

/**
 * netk_class_group_get:
 * @res_class: Name of the sought resource class.
 *
 * Gets an existing class group based on its resource class name.
 *
 * Return value: An existing #NetkClassGroup, or NULL if there is no groups with
 * the specified @res_class.
 **/
NetkClassGroup *
netk_class_group_get (const char *res_class)
{
    if (!class_group_hash)
        return NULL;
    else
        return g_hash_table_lookup (class_group_hash,
            res_class ? res_class : "");
}

/**
 * p_netk_class_group_create:
 * @res_class: Name of the resource class for the group.
 *
 * Creates a new NetkClassGroup with the specified resource class name.  If
 * @res_class is #NULL, then windows without a resource class name will get
 * grouped under this class group.
 *
 * Return value: A newly-created #NetkClassGroup, or an existing one that
 * matches the @res_class.
 **/
NetkClassGroup *
p_netk_class_group_create (const char *res_class)
{
    NetkClassGroup *class_group;

    if (class_group_hash == NULL)
        class_group_hash = g_hash_table_new (g_str_hash, g_str_equal);

    g_return_val_if_fail (g_hash_table_lookup
        (class_group_hash, res_class ? res_class : "") == NULL, NULL);

    class_group = g_object_new (NETK_TYPE_CLASS_GROUP, NULL);

    class_group->priv->res_class = g_strdup (res_class ? res_class : "");

    g_hash_table_insert (class_group_hash, class_group->priv->res_class, class_group);
    /* Hash now owns one ref, caller gets none */

    return class_group;
}

/**
 * p_netk_class_group_destroy:
 * @class_group: A window class group.
 *
 * Destroys the specified @class_group.
 **/
void
p_netk_class_group_destroy (NetkClassGroup * class_group)
{
    g_return_if_fail (NETK_IS_CLASS_GROUP (class_group));

    g_hash_table_remove (class_group_hash, class_group->priv->res_class);

    g_free (class_group->priv->res_class);
    class_group->priv->res_class = NULL;

    /* remove hash's ref on the class group */
    g_object_unref (G_OBJECT (class_group));
}

static const char *
get_name_from_applications (NetkClassGroup * class_group)
{
    const char *first_name;
    GList *l;

    /* Try to get the name from the group leaders.  If all have the same name, we
     * can use that.
     */

    first_name = NULL;

    for (l = class_group->priv->windows; l; l = l->next)
    {
        NetkWindow *w;
        NetkApplication *app;

        w = NETK_WINDOW (l->data);
        app = netk_window_get_application (w);

        if (!first_name)
        {
            if (app)
                first_name = netk_application_get_name (app);
        }
        else
        {
            if (!app
                || g_utf8_collate (first_name,
                    netk_application_get_name (app)) != 0)
                break;
        }
    }

    if (!l)
    {
        /* All names are the same, so use one of them */
        return first_name;
    }
    else
        return NULL;
}

static const char *
get_name_from_windows (NetkClassGroup * class_group)
{
    const char *first_name;
    GList *l;

    /* Try to get the name from windows, following the same rationale as
     * get_name_from_applications()
     */

    first_name = NULL;

    for (l = class_group->priv->windows; l; l = l->next)
    {
        NetkWindow *window;

        window = NETK_WINDOW (l->data);

        if (!first_name)
            first_name = netk_window_get_name (window);
        else if (g_utf8_collate (first_name,
                netk_window_get_name (window)) != 0)
            break;
    }

    if (!l)
    {
        /* All names are the same, so use one of them */
        return first_name;
    }
    else
        return NULL;
}


/* Gets a sensible name for the class group from the application group leaders
 * or from individual windows.
 */
static void
set_name (NetkClassGroup * class_group)
{
    const char *new_name;

    if (class_group->priv->name)
    {
        g_free (class_group->priv->name);
        class_group->priv->name = NULL;
    }

    new_name = get_name_from_applications (class_group);

    if (!new_name)
    {
        new_name = get_name_from_windows (class_group);

        if (!new_name)
            new_name = class_group->priv->res_class;
    }

    g_assert (new_name != NULL);

    if (!class_group->priv->name || g_utf8_collate (class_group->priv->name, new_name) != 0)
    {
        g_free (class_group->priv->name);
        class_group->priv->name = g_strdup (new_name);

        g_signal_emit (G_OBJECT (class_group), signals[NAME_CHANGED], 0);
    }
}

/* Walks the list of applications, trying to get an icon from them */
static void
get_icons_from_applications (NetkClassGroup * class_group, GdkPixbuf ** icon,
    GdkPixbuf ** mini_icon)
{
    GList *l;

    *icon = NULL;
    *mini_icon = NULL;

    for (l = class_group->priv->windows; l; l = l->next)
    {
        NetkWindow *window;
        NetkApplication *app;

        window = NETK_WINDOW (l->data);
        app = netk_window_get_application (window);
        if (app)
        {
            *icon = netk_application_get_icon (app);
            *mini_icon = netk_application_get_mini_icon (app);

            if (*icon && *mini_icon)
                return;
            else
            {
                *icon = NULL;
                *mini_icon = NULL;
            }
        }
    }
}

/* Walks the list of windows, trying to get an icon from them */
static void
get_icons_from_windows (NetkClassGroup * class_group, GdkPixbuf ** icon,
    GdkPixbuf ** mini_icon)
{
    GList *l;

    *icon = NULL;
    *mini_icon = NULL;

    for (l = class_group->priv->windows; l; l = l->next)
    {
        NetkWindow *window;

        window = NETK_WINDOW (l->data);

        *icon = netk_window_get_icon (window);
        *mini_icon = netk_window_get_mini_icon (window);

        if (*icon && *mini_icon)
            return;
        else
        {
            *icon = NULL;
            *mini_icon = NULL;
        }
    }
}

/* Gets a sensible icon and mini_icon for the class group from the application
 * group leaders or from individual windows.
 */
static void
set_icon (NetkClassGroup * class_group)
{
    GdkPixbuf *icon, *mini_icon;

    get_icons_from_applications (class_group, &icon, &mini_icon);

    if (!icon || !mini_icon)
        get_icons_from_windows (class_group, &icon, &mini_icon);

    if (!icon || !mini_icon)
        p_netk_get_fallback_icons (&icon, ICON_SIZE, ICON_SIZE,
            &mini_icon, MINI_ICON_SIZE, MINI_ICON_SIZE);

    g_assert (icon && mini_icon);

    if (class_group->priv->icon)
        g_object_unref (G_OBJECT (class_group->priv->icon));

    if (class_group->priv->mini_icon)
        g_object_unref (G_OBJECT (class_group->priv->mini_icon));

    class_group->priv->icon = g_object_ref (G_OBJECT (icon));
    class_group->priv->mini_icon = g_object_ref (G_OBJECT (mini_icon));

    g_signal_emit (G_OBJECT (class_group), signals[ICON_CHANGED], 0);
}

/**
 * p_netk_class_group_add_window:
 * @class_group: A window class group.
 * @window: A window.
 *
 * Adds a window to a class group.  You should only do this if the resource
 * class of the window matches the @class_group<!-- -->'s.
 **/
void
p_netk_class_group_add_window (NetkClassGroup * class_group,
    NetkWindow * window)
{
    g_return_if_fail (NETK_IS_CLASS_GROUP (class_group));
    g_return_if_fail (NETK_IS_WINDOW (window));
    g_return_if_fail (netk_window_get_class_group (window) == NULL);

    class_group->priv->windows = g_list_prepend (class_group->priv->windows, window);
    p_netk_window_set_class_group (window, class_group);

    set_name (class_group);
    set_icon (class_group);

    /* FIXME: should we monitor class group changes on the window?  The ICCCM says
     * that clients should never change WM_CLASS unless the window is withdrawn.
     */
}

/**
 * p_netk_class_group_remove_window:
 * @class_group: A window class group.
 * @window: A window.
 * 
 * Removes a window from the list of windows that are grouped under the
 * specified @class_group.
 **/
void
p_netk_class_group_remove_window (NetkClassGroup * class_group,
    NetkWindow * window)
{
    g_return_if_fail (NETK_IS_CLASS_GROUP (class_group));
    g_return_if_fail (NETK_IS_WINDOW (window));
    g_return_if_fail (netk_window_get_class_group (window) == class_group);

    class_group->priv->windows = g_list_remove (class_group->priv->windows, window);
    p_netk_window_set_class_group (window, NULL);
}

/**
 * netk_class_group_get_windows:
 * @class_group: A window class group.
 * 
 * Gets the list of windows that are grouped in a @class_group.
 * 
 * Return value: A list of windows, or NULL if the group contains no windows.
 * The list should not be freed, as it belongs to the @class_group.
 **/
GList *
netk_class_group_get_windows (NetkClassGroup * class_group)
{
    g_return_val_if_fail (class_group != NULL, NULL);

    return class_group->priv->windows;
}

/**
 * netk_class_group_get_res_class:
 * @class_group: A window class group.
 * 
 * Queries the resource class name for a class group.
 * 
 * Return value: The resource class name of the specified @class_group, or the
 * empty string if the group has no name.  The string should not be freed.
 **/
const char *
netk_class_group_get_res_class (NetkClassGroup * class_group)
{
    g_return_val_if_fail (class_group != NULL, NULL);

    return class_group->priv->res_class;
}

/**
 * netk_class_group_get_name:
 * @class_group: A window class group.
 * 
 * Queries the human-readable name for a class group.
 * 
 * Return value: Name of the class group.
 **/
const char *
netk_class_group_get_name (NetkClassGroup * class_group)
{
    g_return_val_if_fail (class_group != NULL, NULL);

    return class_group->priv->name;
}

/**
 * netk_class_group_get_icon:
 * @class_group: A window class group.
 * 
 * Queries the icon to be used for a class group.
 * 
 * Return value: The icon to use.
 **/
GdkPixbuf *
netk_class_group_get_icon (NetkClassGroup * class_group)
{
    g_return_val_if_fail (class_group != NULL, NULL);

    return class_group->priv->icon;
}

/**
 * netk_class_group_get_mini_icon:
 * @class_group: A window class group.
 * 
 * Queries the mini-icon to be used for a class group.
 * 
 * Return value: The mini-icon to use.
 **/
GdkPixbuf *
netk_class_group_get_mini_icon (NetkClassGroup * class_group)
{
    g_return_val_if_fail (class_group != NULL, NULL);

    return class_group->priv->mini_icon;
}
