/* GStreamer
 * Copyright (C) 2003 Julien Moutte <julien@moutte.net>
 *
 * 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

/* Object header */
#include "gstswitch.h"

enum {
  ARG_0,
  ARG_NB_SOURCES,
  ARG_ACTIVE_SOURCE
};

/* ElementFactory information */
static GstElementDetails gst_switch_details = GST_ELEMENT_DETAILS (
  "Switch",
  "Generic",
  "N-to-1 input switching",
  "Julien Moutte <julien@moutte.net>"
);

static GstStaticPadTemplate gst_switch_sink_factory =
GST_STATIC_PAD_TEMPLATE (
  "sink%d",
  GST_PAD_SINK,
  GST_PAD_REQUEST,
  GST_STATIC_CAPS_ANY
);

static GstElementClass *parent_class = NULL;

/* ============================================================= */
/*                                                               */
/*                       Private Methods                         */
/*                                                               */
/* ============================================================= */

static GstPad*
gst_switch_request_new_pad (GstElement *element,
                            GstPadTemplate *templ,
                            const gchar *unused) 
{
  char *name = NULL;
  GstPad *sinkpad = NULL;
  GstSwitch *gstswitch = NULL;
  GstSwitchPad *switchpad = NULL;
  
  g_return_val_if_fail (GST_IS_SWITCH (element), NULL);
  
  if (templ->direction != GST_PAD_SINK) {
    g_warning ("gstswitch: requested a non sink pad\n");
    return NULL;
  }
  
  gstswitch = GST_SWITCH (element);
  
  name = g_strdup_printf ("sink%d", gstswitch->nb_sinkpads);
  
  sinkpad = gst_pad_new_from_template (templ, name);
  
  if (name)
    g_free (name);
  
  gst_element_add_pad (GST_ELEMENT (gstswitch), sinkpad);
  
  switchpad = g_new0 (GstSwitchPad, 1);
  if (!switchpad)
    return NULL;
  
  switchpad->sinkpad = sinkpad;
  switchpad->data = NULL;
  switchpad->forwarded = FALSE;
  
  gstswitch->sinkpads = g_list_insert (gstswitch->sinkpads, switchpad,
                                       gstswitch->nb_sinkpads);
  gstswitch->nb_sinkpads++;
  
  return sinkpad;
}

static gboolean
gst_switch_poll_sinkpads (GstSwitch *gstswitch)
{
  GList *pads;
  
  g_return_val_if_fail (gstswitch != NULL, FALSE);
  g_return_val_if_fail (GST_IS_SWITCH (gstswitch), FALSE);
  
  pads = gstswitch->sinkpads;
  
  while (pads) {
    GstSwitchPad *switchpad = pads->data;
    GstData *data = gst_pad_pull (switchpad->sinkpad);
    if (GST_IS_EVENT (data) &&
        (GST_EVENT_TYPE (GST_EVENT (data)) == GST_EVENT_EOS)) {
      /* If that data was not forwarded we unref it */
      if (!switchpad->forwarded && switchpad->data) {
        gst_data_unref (switchpad->data);
        switchpad->data = NULL;
      }
      gst_event_unref (GST_EVENT (data));
    }
    else  {
      /* If that data was not forwarded we unref it */
      if (!switchpad->forwarded && switchpad->data) {
        gst_data_unref (switchpad->data);
        switchpad->data = NULL;
      }
      switchpad->data = data;
      switchpad->forwarded = FALSE;
    }
    pads = g_list_next (pads);
  }
    
  return TRUE;
}

static void 
gst_switch_loop (GstElement *element) 
{
  GstSwitch *gstswitch = NULL;
  GstSwitchPad *switchpad = NULL;
  
  g_return_if_fail (element != NULL);
  g_return_if_fail (GST_IS_SWITCH (element));
  
  gstswitch = GST_SWITCH (element);
  
  /* We poll all our sinkpads */
  gst_switch_poll_sinkpads (gstswitch);
  
  /* We get the active sinkpad */
  switchpad = g_list_nth_data (gstswitch->sinkpads, gstswitch->active_sinkpad);
  
  if (switchpad) {
    /* Pushing active sinkpad data to srcpad */
    gst_pad_push (gstswitch->srcpad, switchpad->data);
    /* Mark this data as forwarded so that it won't get unrefed on next poll */
    switchpad->forwarded = TRUE;
  }
}

/* =========================================== */
/*                                             */
/*                 Properties                  */
/*                                             */
/* =========================================== */

static void
gst_switch_set_property (GObject *object, guint prop_id,
                         const GValue *value, GParamSpec *pspec)
{
  GstSwitch *gstswitch = NULL;
  
  g_return_if_fail (GST_IS_SWITCH (object));
  
  gstswitch = GST_SWITCH (object);
  
  switch (prop_id) {
    case ARG_ACTIVE_SOURCE:
      gstswitch->active_sinkpad = g_value_get_int (value);
      break;
    default:
      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
      break;
  }
}

static void
gst_switch_get_property (GObject *object, guint prop_id,
                         GValue *value, GParamSpec *pspec)
{
  GstSwitch *gstswitch = NULL;
  
  g_return_if_fail (GST_IS_SWITCH (object));
  
  gstswitch = GST_SWITCH (object);
  
  switch (prop_id) {
    case ARG_ACTIVE_SOURCE:
      g_value_set_int (value, gstswitch->active_sinkpad);
      break;
    case ARG_NB_SOURCES:
      g_value_set_int (value, gstswitch->nb_sinkpads);
      break;
    default:
      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
      break;
  }
}

/* =========================================== */
/*                                             */
/*              Init & Class init              */
/*                                             */
/* =========================================== */

static void
gst_switch_dispose (GObject *object)
{
  GstSwitch *gstswitch = NULL;
  
  gstswitch = GST_SWITCH (object);

  if (gstswitch->sinkpads) {  
    g_list_free (gstswitch->sinkpads);
    gstswitch->sinkpads = NULL;
  }
  
  G_OBJECT_CLASS (parent_class)->dispose (object);
}

static void
gst_switch_init (GstSwitch *gstswitch)
{
  gstswitch->srcpad = gst_pad_new ("src", GST_PAD_SRC);
  gst_element_add_pad (GST_ELEMENT (gstswitch), gstswitch->srcpad);
  
  gst_element_set_loop_function (GST_ELEMENT (gstswitch), gst_switch_loop);
  
  gstswitch->sinkpads = NULL;
  gstswitch->active_sinkpad = 0;
  gstswitch->nb_sinkpads = 0;
}

static void
gst_switch_base_init (gpointer g_class)
{
  GstElementClass *element_class = GST_ELEMENT_CLASS (g_class);
  
  gst_element_class_set_details (element_class, &gst_switch_details);

  gst_element_class_add_pad_template (element_class, 
    gst_static_pad_template_get (&gst_switch_sink_factory));
}

static void
gst_switch_class_init (GstSwitchClass *klass)
{
  GObjectClass *gobject_class;
  GstElementClass *gstelement_class;

  gobject_class = (GObjectClass *) klass;
  gstelement_class = (GstElementClass *) klass;

  parent_class = g_type_class_ref (GST_TYPE_ELEMENT);
  
  g_object_class_install_property (gobject_class,
                                   ARG_NB_SOURCES,
                                   g_param_spec_int ("nb_sources",
                                                     "number of sources",
                                                     "number of sources",
                                                     G_MININT, G_MAXINT, 0,
                                                     G_PARAM_READABLE));
  g_object_class_install_property (gobject_class,
                                   ARG_ACTIVE_SOURCE,
                                   g_param_spec_int ("active_source",
                                                     "active source",
                                                     "active source",
                                                     G_MININT, G_MAXINT, 0,
                                                     G_PARAM_READWRITE));
                                                     
  gobject_class->dispose = gst_switch_dispose;
  gobject_class->set_property = gst_switch_set_property;
  gobject_class->get_property = gst_switch_get_property;
  
  gstelement_class->request_new_pad = gst_switch_request_new_pad;
}

/* ============================================================= */
/*                                                               */
/*                       Public Methods                          */
/*                                                               */
/* ============================================================= */

GType
gst_switch_get_type (void)
{
  static GType switch_type = 0;

  if (!switch_type) {
      static const GTypeInfo switch_info = {
        sizeof(GstSwitchClass),
        gst_switch_base_init,
        NULL,
        (GClassInitFunc) gst_switch_class_init,
        NULL,
        NULL,
        sizeof(GstSwitch),
        0,
        (GInstanceInitFunc) gst_switch_init,
      };
      
      switch_type = g_type_register_static (GST_TYPE_ELEMENT,
                                            "GstSwitch", &switch_info, 0);
  }
    
  return switch_type;
}

static gboolean
plugin_init (GstPlugin *plugin)
{
  return gst_element_register (plugin, "switch", GST_RANK_NONE,
                               GST_TYPE_SWITCH);
}

GST_PLUGIN_DEFINE (
  GST_VERSION_MAJOR,
  GST_VERSION_MINOR,
  "switch",
  "N-to-1 input switching",
  plugin_init,
  VERSION,
  GST_LICENSE,
  GST_PACKAGE,
  GST_ORIGIN
)