/*
 * Copyright (C) 2009,2011 Nokia Corporation.
 * Copyright (C) 2006, 2007, 2008 OpenedHand Ltd.
 *
 * Author: Jens Georg <mail@jensge.org>
 *         Zeeshan Ali (Khattak) <zeeshanak@gnome.org>
 *         Jorn Baayen <jorn@openedhand.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., 51 Franklin Street, Fifth Floor,
 * Boston, MA 02110-1301, USA.
 */

/**
 * SECTION:gupnp-simple-context-manager
 * @short_description: Abstract implementation of basic #GUPnPContextManager.
 *
 */

#include <config.h>
#include <errno.h>
#include <gio/gio.h>
#include <libgssdp/gssdp-error.h>

#include "gupnp-simple-context-manager.h"
#include "gupnp-context.h"

G_DEFINE_ABSTRACT_TYPE (GUPnPSimpleContextManager,
                        gupnp_simple_context_manager,
                        GUPNP_TYPE_CONTEXT_MANAGER);

struct _GUPnPSimpleContextManagerPrivate {
        GList *contexts; /* List of GUPnPContext instances */

        GSource *idle_context_creation_src;
};

static GList*
gupnp_simple_context_manager_get_interfaces (GUPnPSimpleContextManager *manager)
{
        GUPnPSimpleContextManagerClass *object_class =
                              GUPNP_SIMPLE_CONTEXT_MANAGER_GET_CLASS (manager);

        return object_class->get_interfaces (manager);
}

static void
create_and_signal_context (const char                *interface,
                           GUPnPSimpleContextManager *manager)
{
        GUPnPContext *context;
        guint port;

        GError *error;

        g_object_get (manager,
                      "port", &port,
                      NULL);

        error = NULL;
        context = g_initable_new (GUPNP_TYPE_CONTEXT,
                                  NULL,
                                  &error,
                                  "interface", interface,
                                  "port", port,
                                  NULL);
        if (error != NULL) {
                if (!(error->domain == GSSDP_ERROR &&
                      error->code == GSSDP_ERROR_NO_IP_ADDRESS))
                        g_warning
                           ("Failed to create context for interface '%s': %s",
                            interface,
                            error->message);

                g_error_free (error);

                return;
        }

        g_signal_emit_by_name (manager,
                               "context-available",
                               context);

        manager->priv->contexts = g_list_append (manager->priv->contexts,
                                                 context);
}

/*
 * Create a context for all network interfaces that are up.
 */
static gboolean
create_contexts (gpointer data)
{
        GUPnPSimpleContextManager *manager = (GUPnPSimpleContextManager *) data;
        GList *ifaces;

        manager->priv->idle_context_creation_src = NULL;

        if (manager->priv->contexts != NULL)
               return FALSE;

        ifaces = gupnp_simple_context_manager_get_interfaces (manager);
        while (ifaces) {
                create_and_signal_context ((char *) ifaces->data, manager);
                g_free (ifaces->data);
                ifaces = g_list_remove_link (ifaces, ifaces);
        }

        return FALSE;
}

static void
destroy_contexts (GUPnPSimpleContextManager *manager)
{
        while (manager->priv->contexts) {
                GUPnPContext *context;

                context = GUPNP_CONTEXT (manager->priv->contexts->data);

                g_signal_emit_by_name (manager,
                                       "context-unavailable",
                                       context);
                g_object_unref (context);

                manager->priv->contexts = g_list_delete_link
                                        (manager->priv->contexts,
                                         manager->priv->contexts);
        }
}

static void
schedule_contexts_creation (GUPnPSimpleContextManager *manager)
{
        manager->priv->idle_context_creation_src = NULL;

        /* Create contexts in mainloop so that is happens after user has hooked
         * to the "context-available" signal.
         */
        manager->priv->idle_context_creation_src = g_idle_source_new ();
        g_source_attach (manager->priv->idle_context_creation_src,
                         g_main_context_get_thread_default ());
        g_source_set_callback (manager->priv->idle_context_creation_src,
                               create_contexts,
                               manager,
                               NULL);
        g_source_unref (manager->priv->idle_context_creation_src);
}

static void
gupnp_simple_context_manager_init (GUPnPSimpleContextManager *manager)
{
        manager->priv =
                G_TYPE_INSTANCE_GET_PRIVATE (manager,
                                             GUPNP_TYPE_SIMPLE_CONTEXT_MANAGER,
                                             GUPnPSimpleContextManagerPrivate);
}

static void
gupnp_simple_context_manager_constructed (GObject *object)
{
        GObjectClass *parent_class;
        GUPnPSimpleContextManager *manager;

        manager = GUPNP_SIMPLE_CONTEXT_MANAGER (object);
        schedule_contexts_creation (manager);

        /* Chain-up */
        parent_class = G_OBJECT_CLASS (gupnp_simple_context_manager_parent_class);
        if (parent_class->constructed != NULL)
                parent_class->constructed (object);
}

static void
gupnp_simple_context_manager_dispose (GObject *object)
{
        GUPnPSimpleContextManager *manager;
        GObjectClass *object_class;

        manager = GUPNP_SIMPLE_CONTEXT_MANAGER (object);

        destroy_contexts (manager);

        if (manager->priv->idle_context_creation_src) {
                g_source_destroy (manager->priv->idle_context_creation_src);
                manager->priv->idle_context_creation_src = NULL;
        }


        /* Call super */
        object_class = G_OBJECT_CLASS (gupnp_simple_context_manager_parent_class);
        object_class->dispose (object);
}

static void
gupnp_simple_context_manager_class_init (GUPnPSimpleContextManagerClass *klass)
{
        GObjectClass *object_class;

        object_class = G_OBJECT_CLASS (klass);

        object_class->constructed  = gupnp_simple_context_manager_constructed;
        object_class->dispose      = gupnp_simple_context_manager_dispose;

        g_type_class_add_private (klass,
                                  sizeof (GUPnPSimpleContextManagerPrivate));
}
