/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
/*
 * Copyright (C) 2014 Pranav Kant
 *
 * 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 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
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General
 * Public License along with this library; if not, see <http://www.gnu.org/licenses/>. *
 */

#include "config.h"
#include <glib/gi18n-lib.h>

#include "goadleynaservermanager.h"
#include "goadleynaservermediadevice.h"
#include "goadlnaservermanager.h"

struct _GoaDlnaServerManagerPrivate
{
  DleynaServerManager *proxy;
  GHashTable *servers;
};

enum
{
  SERVER_FOUND,
  SERVER_LOST,
  LAST_SIGNAL
};

static guint signals[LAST_SIGNAL] = { 0 };

G_DEFINE_TYPE_WITH_PRIVATE (GoaDlnaServerManager, goa_dlna_server_manager, G_TYPE_OBJECT);

static GObject *goa_dlna_server_manager_singleton = NULL;

static void
goa_dlna_server_manager_server_new_cb (GObject      *source_object,
                                       GAsyncResult *res,
                                       gpointer      user_data)
{
  GoaDlnaServerManager *self = GOA_DLNA_SERVER_MANAGER (user_data);
  GoaDlnaServerManagerPrivate *priv = self->priv;
  DleynaServerMediaDevice *server;
  GError *error = NULL;
  const gchar *object_path;

  server = dleyna_server_media_device_proxy_new_for_bus_finish (res, &error);
  if (error != NULL)
    {
      g_warning ("Unable to load server object: %s", error->message);
      g_error_free (error);
      goto out;
    }

  object_path = g_dbus_proxy_get_object_path (G_DBUS_PROXY (server));

  g_debug ("%s '%s' %s %s",
           G_STRFUNC,
           dleyna_server_media_device_get_friendly_name (server),
           dleyna_server_media_device_get_udn (server),
           object_path);
  g_hash_table_insert (priv->servers, (gpointer) object_path, server);
  g_signal_emit (self, signals[SERVER_FOUND], 0, server);

 out:
  g_object_unref (self);
}

static void
goa_dlna_server_manager_server_found_cb (GoaDlnaServerManager *self,
                                         const gchar *object_path,
                                         gpointer *data)
{
  dleyna_server_media_device_proxy_new_for_bus (G_BUS_TYPE_SESSION,
                                                G_DBUS_PROXY_FLAGS_NONE,
                                                "com.intel.dleyna-server",
                                                object_path,
                                                NULL, /* GCancellable */
                                                goa_dlna_server_manager_server_new_cb,
                                                g_object_ref (self));
}

static void
goa_dlna_server_manager_server_lost_cb (GoaDlnaServerManager *self,
                                        const gchar *object_path,
                                        gpointer *data)
{
  GoaDlnaServerManagerPrivate *priv = self->priv;
  DleynaServerMediaDevice *server;

  server = DLEYNA_SERVER_MEDIA_DEVICE (g_hash_table_lookup (priv->servers, object_path));
  g_return_if_fail (server != NULL);

  g_hash_table_steal (priv->servers, object_path);
  g_debug ("%s '%s' %s %s",
           G_STRFUNC,
           dleyna_server_media_device_get_friendly_name (server),
           dleyna_server_media_device_get_udn (server),
           object_path);
  g_signal_emit (self, signals[SERVER_LOST], 0, server);
  g_object_unref (server);
}

static void
goa_dlna_server_manager_proxy_get_servers_cb (GObject      *source_object,
                                              GAsyncResult *res,
                                              gpointer      user_data)
{
  GoaDlnaServerManager *self = GOA_DLNA_SERVER_MANAGER (user_data);
  GoaDlnaServerManagerPrivate *priv = self->priv;
  GError *error = NULL;
  gchar **object_paths = NULL;
  guint i;

  dleyna_server_manager_call_get_servers_finish (priv->proxy, &object_paths, res, &error);
  if (error != NULL)
    {
      g_warning ("Unable to fetch the list of available servers: %s", error->message);
      g_error_free (error);
      goto out;
    }

  for (i = 0; object_paths[i] != NULL; i++)
    goa_dlna_server_manager_server_found_cb (self, object_paths[i], NULL);

  g_signal_connect_swapped (priv->proxy, "found-server", G_CALLBACK (goa_dlna_server_manager_server_found_cb), self);
  g_signal_connect_swapped (priv->proxy, "lost-server", G_CALLBACK (goa_dlna_server_manager_server_lost_cb), self);

 out:
  g_strfreev (object_paths);
  g_object_unref (self);
}

static void
goa_dlna_server_manager_proxy_new_cb (GObject *source_object,
                                      GAsyncResult *res,
                                      gpointer user_data)
{
  GoaDlnaServerManager *self = GOA_DLNA_SERVER_MANAGER (user_data);
  GoaDlnaServerManagerPrivate *priv = self->priv;
  GError *error = NULL;

  priv->proxy = dleyna_server_manager_proxy_new_for_bus_finish (res, &error);
  if (error != NULL)
    {
      g_warning ("Unable to connect to the dLeynaServer.Manager DBus object: %s",
                 error->message);
      g_error_free (error);
      goto out;
    }

  g_debug ("%s DLNA server manager initialized", G_STRFUNC);

  dleyna_server_manager_call_get_servers (priv->proxy,
                                          NULL,
                                          goa_dlna_server_manager_proxy_get_servers_cb,
                                          g_object_ref (self));

 out:
  g_object_unref (self);
}

static void
goa_dlna_server_manager_dispose (GObject *object)
{
  GoaDlnaServerManager *self = GOA_DLNA_SERVER_MANAGER (object);
  GoaDlnaServerManagerPrivate *priv = self->priv;

  g_clear_pointer (&priv->servers, (GDestroyNotify) g_hash_table_unref);
  g_clear_object (&priv->proxy);

  G_OBJECT_CLASS (goa_dlna_server_manager_parent_class)->dispose (object);
}

static GObject *
goa_dlna_server_manager_constructor (GType type,
                                     guint n_construct_params,
                                     GObjectConstructParam *construct_params)
{
  if (goa_dlna_server_manager_singleton == NULL)
    {
      goa_dlna_server_manager_singleton =
        G_OBJECT_CLASS (goa_dlna_server_manager_parent_class)->constructor (type,
                                                                            n_construct_params,
                                                                            construct_params);
      g_object_add_weak_pointer (goa_dlna_server_manager_singleton,
                                 (gpointer) &goa_dlna_server_manager_singleton);
      return goa_dlna_server_manager_singleton;
    }

  return g_object_ref (goa_dlna_server_manager_singleton);
}

static void
goa_dlna_server_manager_init (GoaDlnaServerManager *self)
{
  GoaDlnaServerManagerPrivate *priv;

  self->priv = priv = goa_dlna_server_manager_get_instance_private (self);
  dleyna_server_manager_proxy_new_for_bus (G_BUS_TYPE_SESSION,
                                           G_DBUS_PROXY_FLAGS_NONE,
                                           "com.intel.dleyna-server",
                                           "/com/intel/dLeynaServer",
                                           NULL, /* GCancellable */
                                           goa_dlna_server_manager_proxy_new_cb,
                                           g_object_ref (self));
  priv->servers = g_hash_table_new_full (g_str_hash, g_str_equal, NULL, g_object_unref);
}

static void
goa_dlna_server_manager_class_init (GoaDlnaServerManagerClass *class)
{
  GObjectClass *object_class = G_OBJECT_CLASS (class);

  object_class->constructor = goa_dlna_server_manager_constructor;
  object_class->dispose = goa_dlna_server_manager_dispose;

  signals[SERVER_FOUND] = g_signal_new ("server-found",
                                        G_TYPE_FROM_CLASS (class),
                                        G_SIGNAL_RUN_LAST,
                                        0,
                                        NULL,
                                        NULL,
                                        g_cclosure_marshal_VOID__OBJECT,
                                        G_TYPE_NONE,
                                        1,
                                        DLEYNA_SERVER_TYPE_MEDIA_DEVICE);

  signals[SERVER_LOST] = g_signal_new ("server-lost",
                                       G_TYPE_FROM_CLASS (class),
                                       G_SIGNAL_RUN_LAST,
                                       0,
                                       NULL,
                                       NULL,
                                       g_cclosure_marshal_VOID__OBJECT,
                                       G_TYPE_NONE,
                                       1,
                                       DLEYNA_SERVER_TYPE_MEDIA_DEVICE);
}

GoaDlnaServerManager *
goa_dlna_server_manager_dup_singleton (void)
{
  return g_object_new (GOA_TYPE_DLNA_SERVER_MANAGER, NULL);
}

GList *
goa_dlna_server_manager_dup_servers (GoaDlnaServerManager *self)
{
  GoaDlnaServerManagerPrivate *priv = self->priv;
  GList *servers;

  servers = g_hash_table_get_values (priv->servers);
  g_list_foreach (servers, (GFunc) g_object_ref, NULL);

  return servers;
}
