/*
 * Farstream - Farstream MSN Conference Implementation
 *
 * Copyright 2008 Richard Spiers <richard.spiers@gmail.com>
 * Copyright 2007 Nokia Corp.
 * Copyright 2007-2009 Collabora Ltd.
 *  @author: Olivier Crete <olivier.crete@collabora.co.uk>
 *
 * fs-msn-conference.c - MSN implementation for Farstream Conference Gstreamer
 *                       Elements
 *
 * 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.1 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, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301 USA
 */

/**
 * SECTION:fs-msn-conference
 * @short_description: Farstream MSN Conference Gstreamer Elements Base class
 *
 * This element implements the unidirection webcam feature found in various
 * version of MSN Messenger (tm) and Windows Live Messenger (tm).
 */


#ifdef HAVE_CONFIG_H
#include "config.h"
#endif

#include <stdio.h>

#include "fs-msn-conference.h"

#include "fs-msn-session.h"
#include "fs-msn-stream.h"
#include "fs-msn-participant.h"

#include "fs-msn-cam-send-conference.h"
#include "fs-msn-cam-recv-conference.h"

GST_DEBUG_CATEGORY (fsmsnconference_debug);
#define GST_CAT_DEFAULT fsmsnconference_debug

/* Signals */
enum
{
  LAST_SIGNAL
};

/* Properties */
enum
{
  PROP_0
};


static GstStaticPadTemplate fs_msn_conference_sink_template =
  GST_STATIC_PAD_TEMPLATE ("sink_%d",
      GST_PAD_SINK,
      GST_PAD_SOMETIMES,
      GST_STATIC_CAPS_ANY);

static GstStaticPadTemplate fs_msn_conference_src_template =
  GST_STATIC_PAD_TEMPLATE ("src_%d_%d_%d",
      GST_PAD_SRC,
      GST_PAD_SOMETIMES,
      GST_STATIC_CAPS_ANY);

#define FS_MSN_CONFERENCE_GET_PRIVATE(obj) \
  (G_TYPE_INSTANCE_GET_PRIVATE ((obj), FS_TYPE_MSN_CONFERENCE,  \
      FsMsnConferencePrivate))

struct _FsMsnConferencePrivate
{
  gboolean disposed;
  /* Protected by GST_OBJECT_LOCK */
  FsMsnParticipant *participant;
  FsMsnSession *session;
};

G_DEFINE_ABSTRACT_TYPE (FsMsnConference, fs_msn_conference,
    FS_TYPE_CONFERENCE);

static FsSession *fs_msn_conference_new_session (FsConference *conf,
    FsMediaType media_type,
    GError **error);

static FsParticipant *fs_msn_conference_new_participant (FsConference *conf,
    GError **error);

static void _remove_session (gpointer user_data,
    GObject *where_the_object_was);
static void _remove_participant (gpointer user_data,
    GObject *where_the_object_was);

static void
fs_msn_conference_dispose (GObject * object)
{
  FsMsnConference *self = FS_MSN_CONFERENCE (object);

  if (self->priv->disposed)
    return;

  GST_OBJECT_LOCK (object);
  if (self->priv->session)
    g_object_weak_unref (G_OBJECT (self->priv->session), _remove_session, self);
  self->priv->session = NULL;

  if (self->priv->participant)
    g_object_weak_unref (G_OBJECT (self->priv->participant),
        _remove_participant, self);
  self->priv->participant = NULL;
  GST_OBJECT_UNLOCK (object);

  self->priv->disposed = TRUE;

  g_clear_error (&self->missing_element_error);

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

static void
fs_msn_conference_class_init (FsMsnConferenceClass * klass)
{
  GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
  GstElementClass *gstelement_class = GST_ELEMENT_CLASS (klass);
  FsConferenceClass *baseconf_class = FS_CONFERENCE_CLASS (klass);

  g_type_class_add_private (klass, sizeof (FsMsnConferencePrivate));

  GST_DEBUG_CATEGORY_INIT (fsmsnconference_debug, "fsmsnconference", 0,
                           "Farstream MSN Conference Element");

  gst_element_class_add_pad_template (gstelement_class,
      gst_static_pad_template_get (&fs_msn_conference_sink_template));
  gst_element_class_add_pad_template (gstelement_class,
      gst_static_pad_template_get (&fs_msn_conference_src_template));

  baseconf_class->new_session =
    GST_DEBUG_FUNCPTR (fs_msn_conference_new_session);
  baseconf_class->new_participant =
    GST_DEBUG_FUNCPTR (fs_msn_conference_new_participant);

  gobject_class->dispose = GST_DEBUG_FUNCPTR (fs_msn_conference_dispose);
}


static void
fs_msn_conference_init (FsMsnConference *conf)
{
  GST_DEBUG_OBJECT (conf, "fs_msn_conference_init");

  conf->priv = FS_MSN_CONFERENCE_GET_PRIVATE (conf);
}

static void
_remove_session (gpointer user_data,
                 GObject *where_the_object_was)
{
  FsMsnConference *self = FS_MSN_CONFERENCE (user_data);

  GST_OBJECT_LOCK (self);
  if (self->priv->session == (FsMsnSession *) where_the_object_was)
    self->priv->session = NULL;
  GST_OBJECT_UNLOCK (self);
}

static void
_remove_participant (gpointer user_data,
                     GObject *where_the_object_was)
{
  FsMsnConference *self = FS_MSN_CONFERENCE (user_data);

  GST_OBJECT_LOCK (self);
  if (self->priv->participant == (FsMsnParticipant *) where_the_object_was)
    self->priv->participant = NULL;
  GST_OBJECT_UNLOCK (self);
}


static FsSession *
fs_msn_conference_new_session (FsConference *conf,
                               FsMediaType media_type,
                               GError **error)
{
  FsMsnConference *self = FS_MSN_CONFERENCE (conf);
  FsMsnSession *new_session = NULL;

  if (self->missing_element_error)
  {
    if (error)
      *error = g_error_copy (self->missing_element_error);
    return NULL;
  }

  if (media_type != FS_MEDIA_TYPE_VIDEO)
  {
    g_set_error (error, FS_ERROR, FS_ERROR_INVALID_ARGUMENTS,
        "Only video supported for msn webcam");
    return NULL;
  }

  GST_OBJECT_LOCK (self);
  if (self->priv->session)
  {
    GST_OBJECT_UNLOCK (self);
    g_set_error (error, FS_ERROR, FS_ERROR_ALREADY_EXISTS,
        "There already is a session");
    return NULL;
  }

  GST_OBJECT_UNLOCK (self);

  new_session = fs_msn_session_new (media_type, self, error);

  if (new_session)
  {
    GST_OBJECT_LOCK (self);
    self->priv->session = new_session;
    g_object_weak_ref (G_OBJECT (new_session), _remove_session, self);
    GST_OBJECT_UNLOCK (self);
  }

  return FS_SESSION (new_session);
}


static FsParticipant *
fs_msn_conference_new_participant (FsConference *conf,
                                   GError **error)
{
  FsMsnConference *self = FS_MSN_CONFERENCE (conf);
  FsMsnParticipant *new_participant = NULL;

  if (self->missing_element_error)
  {
    if (error)
      *error = g_error_copy (self->missing_element_error);
    return NULL;
  }

  GST_OBJECT_LOCK (self);
  if (self->priv->participant)
  {
    GST_OBJECT_UNLOCK (self);
    g_set_error (error, FS_ERROR, FS_ERROR_ALREADY_EXISTS,
        "There already is a participant");
    return NULL;
  }

  GST_OBJECT_UNLOCK (self);

  new_participant = fs_msn_participant_new ();

  if (new_participant)
  {
    GST_OBJECT_LOCK (self);
    self->priv->participant = new_participant;
    g_object_weak_ref (G_OBJECT (new_participant), _remove_participant, self);
    GST_OBJECT_UNLOCK (self);
  }

  return FS_PARTICIPANT (new_participant);

}


static gboolean plugin_init (GstPlugin * plugin)
{
  return gst_element_register (plugin, "fsmsncamsendconference",
      GST_RANK_NONE, FS_TYPE_MSN_CAM_SEND_CONFERENCE) &&
    gst_element_register (plugin, "fsmsncamrecvconference",
        GST_RANK_NONE, FS_TYPE_MSN_CAM_RECV_CONFERENCE);
}



#ifdef BUILD_GTK_DOC
void
fs_msn_plugin_init_real (void)
{
  gst_plugin_register_static (
#else
GST_PLUGIN_DEFINE (
#endif
  GST_VERSION_MAJOR,
  GST_VERSION_MINOR,
  fsmsnconference,
  "Farstream MSN Conference plugin",
  plugin_init,
  VERSION,
  "LGPL",
  "Farstream",
  "http://www.freedesktop.org/wiki/Software/Farstream"
#ifdef BUILD_GTK_DOC
  );
}
#else
)
#endif

