/*
 * This file is part of telepathy-idle
 *
 * Copyright (C) 2009 Collabora Limited
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public License
 * version 2.1 as published by the Free Software Foundation.
 *
 * 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, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 *
 * Authors:
 *  Jonathon Jongsma <jonathon.jongsma@collabora.co.uk>
 */

#include "config.h"
#include "idle-roomlist-manager.h"

#include <telepathy-glib/telepathy-glib.h>

#define IDLE_DEBUG_FLAG IDLE_DEBUG_ROOMLIST
#include "idle-connection.h"
#include "idle-debug.h"
#include "idle-roomlist-channel.h"
#include "idle-parser.h"

static void _roomlist_manager_iface_init (gpointer g_iface, gpointer iface_data);
static GObject * _roomlist_manager_constructor (GType type, guint n_props, GObjectConstructParam *props);
static void _roomlist_manager_dispose (GObject *object);

G_DEFINE_TYPE_WITH_CODE(IdleRoomlistManager, idle_roomlist_manager, G_TYPE_OBJECT,
    G_IMPLEMENT_INTERFACE(TP_TYPE_CHANNEL_MANAGER, _roomlist_manager_iface_init));

/* properties */
enum {
    PROP_CONNECTION = 1,
    LAST_PROPERTY_ENUM
};

static const gchar * const roomlist_channel_fixed_properties[] = {
    TP_IFACE_CHANNEL ".ChannelType",
    TP_IFACE_CHANNEL ".TargetHandleType",
    NULL
};

static const gchar * const roomlist_channel_allowed_properties[] = {
    NULL
};

struct _IdleRoomlistManagerPrivate
{
  IdleConnection *conn;
  IdleRoomlistChannel *channel;
  int status_changed_id;
  gboolean dispose_has_run;
};

static void _roomlist_manager_close_all (IdleRoomlistManager *self);
static void connection_status_changed_cb (IdleConnection* conn, guint status, guint reason, IdleRoomlistManager *self);

static void _roomlist_manager_foreach (TpChannelManager *self, TpExportableChannelFunc func, gpointer user_data);
static void _roomlist_manager_foreach_class (TpChannelManager *self, TpChannelManagerChannelClassFunc func, gpointer user_data);

static gboolean _roomlist_manager_create_channel (TpChannelManager *self, gpointer request_token, GHashTable *request_properties);
static gboolean _roomlist_manager_request_channel (TpChannelManager *self, gpointer request_token, GHashTable *request_properties);
static gboolean _roomlist_manager_ensure_channel (TpChannelManager *self, gpointer request_token, GHashTable *request_properties);
static gboolean _roomlist_manager_requestotron (IdleRoomlistManager *self, gpointer request_token, GHashTable *request_properties, gboolean require_new);
static IdleRoomlistChannel *_roomlist_manager_new_channel (IdleRoomlistManager *self, gpointer request);

static void _roomlist_channel_closed_cb (IdleRoomlistChannel *chan, gpointer user_data);

static void
idle_roomlist_manager_init (IdleRoomlistManager *self)
{
    IdleRoomlistManagerPrivate *priv = G_TYPE_INSTANCE_GET_PRIVATE(self,
        IDLE_TYPE_ROOMLIST_MANAGER, IdleRoomlistManagerPrivate);

    self->priv = priv;
    priv->channel = NULL;
    priv->status_changed_id = 0;
    priv->dispose_has_run = FALSE;
}


static void
idle_roomlist_manager_get_property (GObject *object,
                                    guint property_id,
                                    GValue *value,
                                    GParamSpec *pspec)
{
    IdleRoomlistManager *self = IDLE_ROOMLIST_MANAGER (object);
    IdleRoomlistManagerPrivate *priv = self->priv;

    switch (property_id) {
      case PROP_CONNECTION:
        g_value_set_object (value, priv->conn);
        break;

      default:
        G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
        break;
    }
}


static void
idle_roomlist_manager_set_property (GObject *object,
                                    guint property_id,
                                    const GValue *value,
                                    GParamSpec *pspec)
{
    IdleRoomlistManager *self = IDLE_ROOMLIST_MANAGER (object);
    IdleRoomlistManagerPrivate *priv = self->priv;

    switch (property_id) {
      case PROP_CONNECTION:
        priv->conn = g_value_get_object (value);
        break;

      default:
        G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
        break;
    }
}


static void
idle_roomlist_manager_class_init (IdleRoomlistManagerClass *klass)
{
    GObjectClass *object_class = G_OBJECT_CLASS (klass);
    GParamSpec *param_spec;

    g_type_class_add_private (klass, sizeof (IdleRoomlistManagerPrivate));

    object_class->constructor = _roomlist_manager_constructor;
    object_class->dispose = _roomlist_manager_dispose;
    object_class->get_property = idle_roomlist_manager_get_property;
    object_class->set_property = idle_roomlist_manager_set_property;

    param_spec = g_param_spec_object ("connection", "IdleConnection object",
        "The IdleConnection object that owns this Roomlist channel manager object.",
        IDLE_TYPE_CONNECTION, G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE |
        G_PARAM_STATIC_NICK | G_PARAM_STATIC_BLURB);
    g_object_class_install_property (object_class, PROP_CONNECTION, param_spec);
}


static GObject *
_roomlist_manager_constructor (GType type,
                               guint n_props,
                               GObjectConstructParam *props)
{
  GObject *obj;
  IdleRoomlistManager *self;
  IdleRoomlistManagerPrivate *priv;

  obj = G_OBJECT_CLASS (idle_roomlist_manager_parent_class)->constructor
    (type, n_props, props);

  self = IDLE_ROOMLIST_MANAGER (obj);
  priv = self->priv;

  g_return_val_if_fail (priv->conn, obj);

  priv->status_changed_id = g_signal_connect (priv->conn,
      "status-changed", (GCallback)
      connection_status_changed_cb,
      self);

  return obj;
}


static void
_roomlist_manager_close_all (IdleRoomlistManager *self)
{
    IdleRoomlistManagerPrivate *priv = self->priv;
    IdleRoomlistChannel *tmp;

    if (priv->channel != NULL)
      {
        /* use a temporary variable and set priv->channel to NULL first
         * because unreffing this channel will cause the
         * _roomlist_channel_closed_cb to fire, which will try to unref it
         * again if priv->channel is not NULL */
        tmp = priv->channel;
        priv->channel = NULL;
        g_object_unref(tmp);
      }
    if (priv->status_changed_id != 0)
      {
        g_signal_handler_disconnect (priv->conn, priv->status_changed_id);
        priv->status_changed_id = 0;
      }
}


static void
connection_status_changed_cb (IdleConnection* conn,
                              guint status,
                              guint reason,
                              IdleRoomlistManager *self)
{
  if (status == TP_CONNECTION_STATUS_DISCONNECTED)
    {
      _roomlist_manager_close_all (self);
    }
}


static void
_roomlist_manager_foreach (TpChannelManager *manager,
                           TpExportableChannelFunc func,
                           gpointer user_data)
{
    IdleRoomlistManager *self = IDLE_ROOMLIST_MANAGER (manager);
    IdleRoomlistManagerPrivate *priv = self->priv;

    if (!priv->channel)
      {
        IDLE_DEBUG ("Channel missing, ignoring...");
        return;
      }

    func (TP_EXPORTABLE_CHANNEL (priv->channel), user_data);
}


static void
_roomlist_manager_foreach_class (TpChannelManager *self,
                                 TpChannelManagerChannelClassFunc func,
                                 gpointer user_data)
{
  GHashTable *table = tp_asv_new (
      roomlist_channel_fixed_properties[0], G_TYPE_STRING,
          TP_IFACE_CHANNEL_TYPE_ROOM_LIST,
      roomlist_channel_fixed_properties[1], G_TYPE_UINT,
          TP_HANDLE_TYPE_NONE,
      NULL);

  func (self, table, roomlist_channel_allowed_properties, user_data);

  g_hash_table_destroy (table);
}


static gboolean
_roomlist_manager_create_channel (TpChannelManager *self,
                                  gpointer request_token,
                                  GHashTable *request_properties)
{
  IdleRoomlistManager *mgr = IDLE_ROOMLIST_MANAGER (self);

  return _roomlist_manager_requestotron (mgr, request_token, request_properties,
      TRUE);
}


static gboolean
_roomlist_manager_request_channel (TpChannelManager *self,
                                   gpointer request_token,
                                   GHashTable *request_properties)
{
  IdleRoomlistManager *mgr = IDLE_ROOMLIST_MANAGER (self);

  return _roomlist_manager_requestotron (mgr, request_token,
      request_properties, FALSE);
}


static gboolean
_roomlist_manager_ensure_channel (TpChannelManager *self,
                                  gpointer request_token,
                                  GHashTable *request_properties)
{
  IdleRoomlistManager *mgr = IDLE_ROOMLIST_MANAGER (self);

  return _roomlist_manager_requestotron (mgr, request_token,
      request_properties, FALSE);
}


static gboolean
_roomlist_manager_requestotron (IdleRoomlistManager *self,
                                gpointer request_token,
                                GHashTable *request_properties,
                                gboolean require_new)
{
  IdleRoomlistManagerPrivate *priv = self->priv;
  GError *error = NULL;

  IDLE_DEBUG("requesting new room list channel");

  if (tp_strdiff (tp_asv_get_string (request_properties,
          TP_IFACE_CHANNEL ".ChannelType"), TP_IFACE_CHANNEL_TYPE_ROOM_LIST))
    return FALSE;

  if (tp_asv_get_uint32 (request_properties,
        TP_IFACE_CHANNEL ".TargetHandleType", NULL) != 0)
    return FALSE;

  /* Check if there are any other properties that we don't understand */
  if (tp_channel_manager_asv_has_unknown_properties (request_properties,
        roomlist_channel_fixed_properties,
        roomlist_channel_allowed_properties,
        &error))
    {
      goto error;
    }

  if (priv->channel == NULL)
    {
      _roomlist_manager_new_channel (self, request_token);
      return TRUE;
    }

  if (require_new)
    {
      g_set_error (&error, TP_ERROR, TP_ERROR_NOT_AVAILABLE,
          "Only one room list channel can be created");
      goto error;
    }

  tp_channel_manager_emit_request_already_satisfied (self, request_token,
      TP_EXPORTABLE_CHANNEL (priv->channel));
  return TRUE;

error:
  tp_channel_manager_emit_request_failed (self, request_token,
      error->domain, error->code, error->message);
  g_error_free (error);
  return TRUE;
}


static void
_roomlist_channel_closed_cb (IdleRoomlistChannel *chan,
                             gpointer user_data)
{
  IdleRoomlistManager *self = IDLE_ROOMLIST_MANAGER (user_data);
  IdleRoomlistManagerPrivate *priv = self->priv;

  tp_channel_manager_emit_channel_closed_for_object (self,
      TP_EXPORTABLE_CHANNEL (chan));

  if (priv->channel)
    {
      g_assert (priv->channel == chan);
      g_object_unref (priv->channel);
      priv->channel = NULL;
    }
}


static IdleRoomlistChannel *
_roomlist_manager_new_channel (IdleRoomlistManager *self,
                               gpointer request)
{
  IdleRoomlistManagerPrivate *priv = self->priv;
  IdleRoomlistChannel *chan;
  GSList *requests = NULL;

  g_assert (priv->channel == NULL);

  IDLE_DEBUG ("Requested room list channel");

  chan = g_object_new (IDLE_TYPE_ROOMLIST_CHANNEL,
                       "connection", priv->conn,
                       NULL);

  if (request != NULL)
    requests = g_slist_prepend (requests, request);

  tp_channel_manager_emit_new_channel (self, TP_EXPORTABLE_CHANNEL (chan),
      requests);

  g_slist_free (requests);

  g_signal_connect (chan, "closed", G_CALLBACK (_roomlist_channel_closed_cb), self);
  priv->channel = chan;

  return chan;
}


static void
_roomlist_manager_iface_init (gpointer g_iface,
                              gpointer iface_data)
{
    TpChannelManagerIface *iface = g_iface;

    iface->foreach_channel = _roomlist_manager_foreach;
    iface->foreach_channel_class = _roomlist_manager_foreach_class;
    iface->request_channel = _roomlist_manager_request_channel;
    iface->create_channel = _roomlist_manager_create_channel;
    iface->ensure_channel = _roomlist_manager_ensure_channel;
}


static void
_roomlist_manager_dispose (GObject *object)
{
  IdleRoomlistManager *self = IDLE_ROOMLIST_MANAGER (object);
  IdleRoomlistManagerPrivate *priv = self->priv;

  if (priv->dispose_has_run)
    return;

  priv->dispose_has_run = TRUE;

  _roomlist_manager_close_all (self);

  if (G_OBJECT_CLASS (idle_roomlist_manager_parent_class)->dispose)
    G_OBJECT_CLASS (idle_roomlist_manager_parent_class)->dispose (object);
}

