/* application object */

/*
 * Copyright (C) 2001 Havoc Pennington
 * Copyright (C) 2003 Red Hat, Inc.
 *
 * 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.
 */

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

#include "netk-application.h"
#include "netk-private.h"

#define FALLBACK_NAME "??"

#define NETK_APPLICATION_GET_PRIVATE(o)    (G_TYPE_INSTANCE_GET_PRIVATE ((o), NETK_TYPE_APPLICATION, NetkApplicationPrivate))

static GHashTable *app_hash = NULL;

struct _NetkApplicationPrivate
{
    Window xwindow;             /* group leader */
    NetkScreen *screen;
    GList *windows;
    int pid;
    char *name;

    NetkWindow *name_window;    /* window we are using name of */

    GdkPixbuf *icon;
    GdkPixbuf *mini_icon;

    NetkIconCache *icon_cache;

    NetkWindow *icon_window;

    guint name_from_leader:1;   /* name is from group leader */
    guint icon_from_leader:1;

    guint need_emit_icon_changed:1;
};

enum
{
    NAME_CHANGED,
    ICON_CHANGED,
    LAST_SIGNAL
};

static void emit_name_changed (NetkApplication * app);
static void emit_icon_changed (NetkApplication * app);

static void reset_name (NetkApplication * app);
static void update_name (NetkApplication * app);

static void netk_application_init (NetkApplication * application);
static void netk_application_class_init (NetkApplicationClass * klass);
static void netk_application_finalize (GObject * object);


static GObjectClass *parent_class;
static guint signals[LAST_SIGNAL] = { 0 };

GType
netk_application_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,
                                             "NetkApplication",
                                             sizeof (NetkApplicationClass),
                                             netk_application_class_init,
                                             sizeof (NetkApplication),
                                             netk_application_init,
                                             0);                                    
    }

    return type;
}

static void
netk_application_init (NetkApplication * application)
{
    application->priv = NETK_APPLICATION_GET_PRIVATE (application);
    
    application->priv->icon_cache = p_netk_icon_cache_new ();
    p_netk_icon_cache_set_want_fallback (application->priv->icon_cache, FALSE);
}

static void
netk_application_class_init (NetkApplicationClass * klass)
{
    GObjectClass *object_class = G_OBJECT_CLASS (klass);
    
    g_type_class_add_private (klass, sizeof (NetkApplicationPrivate));

    parent_class = g_type_class_peek_parent (klass);

    object_class->finalize = netk_application_finalize;

    signals[NAME_CHANGED] =
        g_signal_new (I_("name_changed"), G_OBJECT_CLASS_TYPE (object_class),
                      G_SIGNAL_RUN_LAST,
                      G_STRUCT_OFFSET (NetkApplicationClass, 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 (object_class),
                      G_SIGNAL_RUN_LAST,
                      G_STRUCT_OFFSET (NetkApplicationClass, icon_changed),
                      NULL, NULL, g_cclosure_marshal_VOID__VOID, G_TYPE_NONE,
                      0);
}

static void
netk_application_finalize (GObject * object)
{
    NetkApplication *application;

    application = NETK_APPLICATION (object);

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

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

    p_netk_icon_cache_free (application->priv->icon_cache);

    g_free (application->priv->name);

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

NetkApplication *
netk_application_get (gulong xwindow)
{
    if (app_hash == NULL)
        return NULL;
    else
        return g_hash_table_lookup (app_hash, &xwindow);
}

/**
 * netk_application_get_xid:
 * @app: a #NetkApplication
 * 
 * Gets the X id of the group leader window for the app.
 * 
 * Return value: X id for the app
 **/
gulong
netk_application_get_xid (NetkApplication * app)
{
    g_return_val_if_fail (NETK_IS_APPLICATION (app), 0);

    return app->priv->xwindow;
}

/**
 * netk_application_get_windows:
 * @app: a #NetkApplication
 * 
 * Gets a list of all windows belonging to @app. The list
 * is returned by reference and should not be freed.
 * 
 * Return value: list of #NetkWindow in this app
 **/
GList *
netk_application_get_windows (NetkApplication * app)
{
    g_return_val_if_fail (NETK_IS_APPLICATION (app), NULL);

    return app->priv->windows;
}

int
netk_application_get_n_windows (NetkApplication * app)
{
    g_return_val_if_fail (NETK_IS_APPLICATION (app), 0);

    return g_list_length (app->priv->windows);
}

/**
 * netk_application_get_name:
 * @app: a #NetkApplication
 * 
 * Gets the name of an application, employing various
 * suboptimal heuristics to try to figure it out.
 * Probably GTK should have a function to allow apps to
 * set _NET_WM_NAME on the group leader as the app name,
 * and the WM spec should say that's where the app name
 * goes.
 * 
 * Return value: name of the application
 **/
const char *
netk_application_get_name (NetkApplication * app)
{
    g_return_val_if_fail (NETK_IS_APPLICATION (app), NULL);

    if (app->priv->name)
        return app->priv->name;
    else
        return FALLBACK_NAME;
}

/**
 * netk_application_get_icon_name:
 * @app: a #NetkApplication
 * 
 * Gets the icon name of an application, employing various
 * suboptimal heuristics to try to figure it out.
 * 
 * Return value: name of the application when minimized
 **/
const char *
netk_application_get_icon_name (NetkApplication * app)
{
    g_return_val_if_fail (NETK_IS_APPLICATION (app), NULL);

    if (app->priv->name)
        return app->priv->name;
    else
        return FALLBACK_NAME;
}

/**
 * netk_application_get_pid:
 * @app: a #NetkApplication
 * 
 * Gets the process ID of an application, or 0 if none
 * is available.
 * 
 * Return value: process ID or 0
 **/
int
netk_application_get_pid (NetkApplication * app)
{
    g_return_val_if_fail (NETK_IS_APPLICATION (app), 0);

    return app->priv->pid;
}

static void
get_icons (NetkApplication * app)
{
    GdkPixbuf *icon;
    GdkPixbuf *mini_icon;

    g_return_if_fail (app != NULL);
    g_return_if_fail (NETK_IS_APPLICATION (app));
    
    icon = NULL;
    mini_icon = NULL;

    if (p_netk_read_icons (app->priv->xwindow, app->priv->icon_cache, 
                           &icon, 
                           DEFAULT_ICON_WIDTH, DEFAULT_ICON_HEIGHT, 
                           &mini_icon, 
                           DEFAULT_MINI_ICON_WIDTH, DEFAULT_MINI_ICON_HEIGHT))
    {
        app->priv->need_emit_icon_changed = TRUE;
        app->priv->icon_from_leader = TRUE;

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

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

        app->priv->icon = icon;
        app->priv->mini_icon = mini_icon;
    }

    /* FIXME we should really fall back to using the icon
     * for one of the windows. But then we need to be more
     * complicated about icon_changed and when the icon
     * needs updating and all that.
     */

    g_assert ((app->priv->icon && app->priv->mini_icon)
              || !(app->priv->icon || app->priv->mini_icon));
}

/* Prefer to get group icon from a window of type "normal" */
static NetkWindow *
find_icon_window (NetkApplication * app)
{
    GList *tmp;

    tmp = app->priv->windows;
    while (tmp != NULL)
    {
        NetkWindow *w = tmp->data;

        if (netk_window_get_window_type (w) == NETK_WINDOW_NORMAL)
            return w;

        tmp = tmp->next;
    }

    if (app->priv->windows)
        return app->priv->windows->data;
    else
        return NULL;
}

GdkPixbuf *
netk_application_get_icon (NetkApplication * app)
{
    g_return_val_if_fail (app != NULL, NULL);
    g_return_val_if_fail (NETK_IS_APPLICATION (app), NULL);

    get_icons (app);
    if (app->priv->need_emit_icon_changed)
        emit_icon_changed (app);

    if (app->priv->icon)
        return app->priv->icon;
    else
    {
        NetkWindow *w = find_icon_window (app);
        if (w)
            return netk_window_get_icon (w);
        else
            return NULL;
    }
}

GdkPixbuf *
netk_application_get_mini_icon (NetkApplication * app)
{
    g_return_val_if_fail (app != NULL, NULL);
    g_return_val_if_fail (NETK_IS_APPLICATION (app), NULL);

    get_icons (app);
    if (app->priv->need_emit_icon_changed)
        emit_icon_changed (app);

    if (app->priv->mini_icon)
        return app->priv->mini_icon;
    else
    {
        NetkWindow *w = find_icon_window (app);
        if (w)
            return netk_window_get_mini_icon (w);
        else
            return NULL;
    }
}

/**
 * netk_application_get_icon_is_fallback:
 * @application: a #NetkApplication
 *
 * Checks if we are using a default fallback icon because
 * none was set on the application.
 * 
 * Return value: %TRUE if icon is a fallback
 **/
gboolean
netk_application_get_icon_is_fallback (NetkApplication * app)
{
    g_return_val_if_fail (NETK_IS_APPLICATION (app), FALSE);

    return p_netk_icon_cache_get_is_fallback (app->priv->icon_cache);
}

/* xwindow is a group leader */
NetkApplication *
p_netk_application_create (Window xwindow, NetkScreen * screen)
{
    NetkApplication *application;

    if (app_hash == NULL)
        app_hash = g_hash_table_new (p_netk_xid_hash, p_netk_xid_equal);

    g_return_val_if_fail (g_hash_table_lookup (app_hash, &xwindow) == NULL,
                          NULL);

    application = g_object_new (NETK_TYPE_APPLICATION, NULL);
    application->priv->xwindow = xwindow;
    application->priv->screen = screen;

    application->priv->name = p_netk_get_name (xwindow);

    if (application->priv->name == NULL)
        application->priv->name = p_netk_get_res_class_utf8 (xwindow);

    if (application->priv->name)
        application->priv->name_from_leader = TRUE;

    application->priv->pid = p_netk_get_pid (application->priv->xwindow);

    g_hash_table_insert (app_hash, &application->priv->xwindow, application);

    /* Hash now owns one ref, caller gets none */

    /* Note that xwindow may correspond to a NetkWindow's xwindow,
     * so we select events needed by either
     */
    p_netk_select_input (application->priv->xwindow,
                         NETK_APP_WINDOW_EVENT_MASK);

    return application;
}

void
p_netk_application_destroy (NetkApplication * application)
{
    g_return_if_fail (application != NULL);
    g_return_if_fail (NETK_IS_APPLICATION (application));
    g_return_if_fail (netk_application_get (application->priv->xwindow) ==
                      application);

    g_hash_table_remove (app_hash, &application->priv->xwindow);

    g_return_if_fail (netk_application_get (application->priv->xwindow) ==
                      NULL);

    application->priv->xwindow = None;

    /* remove hash's ref on the application */
    g_object_unref (G_OBJECT (application));
}

static void
window_name_changed (NetkWindow * window, NetkApplication * app)
{
    if (window == app->priv->name_window)
    {
        reset_name (app);
        update_name (app);
    }
}

void
p_netk_application_add_window (NetkApplication * app, NetkWindow * window)
{
    g_return_if_fail (NETK_IS_APPLICATION (app));
    g_return_if_fail (NETK_IS_WINDOW (window));
    g_return_if_fail (netk_window_get_application (window) == NULL);

    app->priv->windows = g_list_prepend (app->priv->windows, window);
    p_netk_window_set_application (window, app);

    g_signal_connect (G_OBJECT (window), "name_changed",
                      G_CALLBACK (window_name_changed), app);

    /* emits signals, so do it last */
    reset_name (app);
    update_name (app);

    /* see if we're using icon from a window */
    if (app->priv->icon == NULL || app->priv->mini_icon == NULL)
        emit_icon_changed (app);
}

void
p_netk_application_remove_window (NetkApplication * app, NetkWindow * window)
{
    g_return_if_fail (NETK_IS_APPLICATION (app));
    g_return_if_fail (NETK_IS_WINDOW (window));
    g_return_if_fail (netk_window_get_application (window) == app);

    app->priv->windows = g_list_remove (app->priv->windows, window);
    p_netk_window_set_application (window, NULL);

    g_signal_handlers_disconnect_by_func (G_OBJECT (window),
                                          window_name_changed, app);

    /* emits signals, so do it last */
    reset_name (app);
    update_name (app);

    /* see if we're using icon from a window */
    if (app->priv->icon == NULL || app->priv->mini_icon == NULL)
        emit_icon_changed (app);
}

void
p_netk_application_process_property_notify (NetkApplication * app,
                                            XEvent * xevent)
{
    g_return_if_fail (NETK_IS_APPLICATION (app));
    /* This prop notify is on the leader window */

    if (xevent->xproperty.atom == XA_WM_NAME
        || xevent->xproperty.atom == p_netk_atom_get ("_NET_WM_NAME")
        || xevent->xproperty.atom == p_netk_atom_get ("_NET_WM_VISIBLE_NAME"))
    {
        /* FIXME should change the name */
    }
    else if (xevent->xproperty.atom == XA_WM_ICON_NAME
             || xevent->xproperty.atom == p_netk_atom_get ("_NET_WM_ICON_NAME")
             || xevent->xproperty.atom == p_netk_atom_get ("_NET_WM_VISIBLE_ICON_NAME"))
    {
        /* FIXME */
    }
    else if (xevent->xproperty.atom == p_netk_atom_get ("_NET_WM_ICON")
             || xevent->xproperty.atom == p_netk_atom_get ("KWM_WIN_ICON")
             || xevent->xproperty.atom == p_netk_atom_get ("WM_NORMAL_HINTS"))
    {
        p_netk_icon_cache_property_changed (app->priv->icon_cache,
                                            xevent->xproperty.atom);
        emit_icon_changed (app);
    }
}

static void
emit_name_changed (NetkApplication * app)
{
    g_signal_emit (G_OBJECT (app), signals[NAME_CHANGED], 0);
}

static void
emit_icon_changed (NetkApplication * app)
{
    app->priv->need_emit_icon_changed = FALSE;
    g_signal_emit (G_OBJECT (app), signals[ICON_CHANGED], 0);
}

static void
reset_name (NetkApplication * app)
{
    if (!app->priv->name_from_leader)
    {
        if (app->priv->name)
            g_free (app->priv->name);
        app->priv->name = NULL;
        app->priv->name_window = NULL;
    }
}

static void
update_name (NetkApplication * app)
{
    if (app->priv->name == NULL)
    {
        /* if only one window, get name from there. If more than one and
         * they all have the same res_class, use that. Else we want to
         * use the fallback name, since using the title of one of the
         * windows would look wrong.
         */
        if (app->priv->windows && app->priv->windows->next == NULL)
        {
            app->priv->name =
                g_strdup (netk_window_get_name (app->priv->windows->data));
            app->priv->name_window = app->priv->windows->data;
            emit_name_changed (app);
        }
        else if (app->priv->windows)
        {
            /* more than one */
            app->priv->name =
                p_netk_get_res_class_utf8 (netk_window_get_xid
                                           (app->priv->windows->data));
            if (app->priv->name)
            {
                app->priv->name_window = app->priv->windows->data;
                emit_name_changed (app);
            }
        }
    }
}
