/* GStreamer
 *
 * Copyright (C) 2005 Lutz Mueller <lutz@topfrose.de>
 * 
 * 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,
 * tarut 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.
 */

#include "gsttardemux.h"

#include <gst/base/gsttypefindhelper.h>

#include <string.h>

GST_DEBUG_CATEGORY_STATIC (tar_demux_debug);
#define GST_CAT_DEFAULT tar_demux_debug

/******************************************************************************
 * Source pad
 */

#define GST_TYPE_TAR_DEMUX_PAD_SRC         (gst_tar_demux_pad_src_get_type())
#define GST_TAR_DEMUX_PAD_SRC(obj)         (G_TYPE_CHECK_INSTANCE_CAST((obj),GST_TYPE_TAR_DEMUX_PAD_SRC,GstTarDemuxPadSrc))
#define GST_TAR_DEMUX_PAD_SRC_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass),GST_TYPE_TAR_DEMUX_PAD_SRC,GstTarDemuxPadSrcClass))

typedef struct {
  GstPad parent;

  guint64 offset;
  guint64 duration;
} GstTarDemuxPadSrc;

typedef struct {
  GstPadClass parent_class;
} GstTarDemuxPadSrcClass;

enum {
  SRC_PROP_0,
  SRC_PROP_OFFSET,
  SRC_PROP_DURATION
};

static GType gst_tar_demux_pad_src_get_type (void);

static const GstQueryType *
gst_tar_demux_pad_src_query_type (GstPad *pad)
{
  static const GstQueryType query_types[] = {
    GST_QUERY_DURATION,
    GST_QUERY_POSITION,
    GST_QUERY_FORMATS,
    0
  };

  return query_types;
}

static gboolean
gst_tar_demux_pad_src_query (GstPad *pad, GstQuery *query)
{
  GstTarDemuxPadSrc *src = GST_TAR_DEMUX_PAD_SRC (pad);
  GstFormat format;
  gboolean ret;
  gint64 pos;

  switch (GST_QUERY_TYPE (query)) {
    case GST_QUERY_DURATION:
      gst_query_parse_duration (query, &format, NULL);
      switch (format) {
        case GST_FORMAT_BYTES:
          gst_query_set_duration (query, format, src->duration);
          return TRUE;
        default:
          return gst_pad_query_default (pad, query);
      }
    case GST_QUERY_POSITION:
      gst_query_parse_position (query, &format, NULL);
      if (!(ret = gst_pad_query_default (pad, query)))
        return ret;
      switch (format) {
        case GST_FORMAT_BYTES:
          gst_query_parse_position (query, NULL, &pos);
          gst_query_set_position (query, format, pos - src->offset);
          return TRUE;
        default:
          return TRUE;
      }
    case GST_QUERY_FORMATS:
      gst_query_set_formats (query, 1, GST_FORMAT_BYTES);
      return TRUE;
    default:
      return gst_pad_query_default (pad, query);
  }
}

static GstFlowReturn
gst_tar_demux_pad_src_getrange (GstPad * pad, guint64 offset, guint length,
    GstBuffer ** buf)
{
  GstTarDemuxPadSrc *src = GST_TAR_DEMUX_PAD_SRC (pad);

  return gst_pad_get_range (
      GST_PAD_PEER (gst_element_get_pad (GST_PAD_PARENT (pad), "sink")),
      offset + src->offset, MIN (length, src->duration - offset), buf);
}

static gboolean
gst_tar_demux_pad_src_activate (GstPad * pad)
{
  return gst_pad_activate_pull (pad, TRUE);
}

static gboolean
gst_tar_demux_pad_src_checkgetrange (GstPad *pad)
{
  return gst_pad_check_pull_range (
      gst_element_get_pad (GST_PAD_PARENT (pad), "sink"));
}

static void
gst_tar_demux_pad_src_init (GTypeInstance *instance, gpointer g_class)
{
  GstPad *pad = GST_PAD (instance);

  GST_PAD_DIRECTION (pad) = GST_PAD_SRC;
  GST_PAD_CAPS (pad) = gst_caps_new_any ();
  gst_pad_set_query_function (pad, gst_tar_demux_pad_src_query);
  gst_pad_set_query_type_function (pad, gst_tar_demux_pad_src_query_type);
  gst_pad_set_checkgetrange_function (pad, gst_tar_demux_pad_src_checkgetrange);
  gst_pad_set_getrange_function (pad, gst_tar_demux_pad_src_getrange);
  gst_pad_set_activate_function (pad, gst_tar_demux_pad_src_activate);
}

static void
gst_tar_demux_pad_src_get_property (GObject * object, guint prop_id,
    GValue * value, GParamSpec * pspec)
{
  GstTarDemuxPadSrc *pad = GST_TAR_DEMUX_PAD_SRC (object);

  switch (prop_id) {
    case SRC_PROP_OFFSET:
      g_value_set_int64 (value, pad->offset);
      break;
    case SRC_PROP_DURATION:
      g_value_set_int64 (value, pad->duration);
      break;
    default:
      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
  }
}

static void
gst_tar_demux_pad_src_set_property (GObject * object, guint prop_id,
    const GValue * value, GParamSpec * pspec)
{
  GstTarDemuxPadSrc *pad = GST_TAR_DEMUX_PAD_SRC (object);

  switch (prop_id) {
    case SRC_PROP_OFFSET:
      pad->offset = g_value_get_int64 (value);
      break;
    case SRC_PROP_DURATION:
      pad->duration = g_value_get_int64 (value);
      break;
    default:
      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
  }
}

static void
gst_tar_demux_pad_src_class_init (gpointer g_class, gpointer data)
{
  GObjectClass *gobject_class = G_OBJECT_CLASS (g_class);

  gobject_class->set_property = gst_tar_demux_pad_src_set_property;
  gobject_class->get_property = gst_tar_demux_pad_src_get_property;

  g_object_class_install_property (gobject_class,
      SRC_PROP_OFFSET, g_param_spec_int64 ("offset",
        "Offset", "Offset", 0, G_MAXINT64, 0, G_PARAM_READWRITE));
  g_object_class_install_property (gobject_class,
      SRC_PROP_DURATION, g_param_spec_int64 ("duration",
        "Duration", "Duration", 0, G_MAXINT64, 0, G_PARAM_READWRITE));
}

static GType
gst_tar_demux_pad_src_get_type (void)
{
  static GType object_type = 0;

  if (G_UNLIKELY (!object_type)) {
    static const GTypeInfo object_info = {
      sizeof (GstTarDemuxPadSrcClass), NULL, NULL,
      gst_tar_demux_pad_src_class_init, NULL, NULL,
      sizeof (GstTarDemuxPadSrc), 0,
      gst_tar_demux_pad_src_init
    };

    object_type = g_type_register_static (GST_TYPE_PAD, "GstTarDemuxPadSrc",
        &object_info, (GTypeFlags) 0);
  }
  return object_type;
}

/******************************************************************************
 * Sink pad
 */

#define GST_TYPE_TAR_DEMUX_PAD_SINK         (gst_tar_demux_pad_sink_get_type())
#define GST_TAR_DEMUX_PAD_SINK(obj)         (G_TYPE_CHECK_INSTANCE_CAST((obj),GST_TYPE_TAR_DEMUX_PAD_SINK,GstTarDemuxPadSink))
#define GST_TAR_DEMUX_PAD_SINK_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass),GST_TYPE_TAR_DEMUX_PAD_SINK,GstTarDemuxPadSinkClass))

typedef struct {
  GstPad parent;

  guint h_len;
  gchar h[512];

  gint64 to_write;
  gint64 to_skip;

  gint64 offset;

  GPtrArray *sources;
  gint source;
  gboolean sources_known;

} GstTarDemuxPadSink;

typedef struct {
  GstPadClass parent_class;
} GstTarDemuxPadSinkClass;

static GstPadClass *pad_sink_parent_class = NULL;

enum {
  SINK_PROP_0,
  SINK_PROP_SRC
};

static GType gst_tar_demux_pad_sink_get_type (void);

static void
gst_tar_demux_pad_sink_send_eos (GstTarDemuxPadSink * pad)
{
    guint s;

    GST_DEBUG_OBJECT (pad, "Sending EOS to all source pads.");
    if (!pad->sources_known) {
      pad->sources_known = TRUE;
      gst_element_no_more_pads (GST_PAD_PARENT (pad));
    }
    for (s = 0; s < pad->sources->len; s++) {
      GstPad *src = g_ptr_array_index (pad->sources, s);

      gst_pad_push_event (src, gst_event_new_eos ());
    }
}

static gboolean
gst_tar_demux_pad_sink_event (GstPad * pad, GstEvent * event)
{
  switch (GST_EVENT_TYPE (event)) {
    case GST_EVENT_EOS:
      gst_tar_demux_pad_sink_send_eos (GST_TAR_DEMUX_PAD_SINK (pad));
      gst_event_unref (event);
      return TRUE;
    default:
      return gst_pad_event_default (pad, event);
  }
}

static GstPad *
gst_tar_demux_pad_sink_decode_header (GstTarDemuxPadSink * pad, gint64 offset)
{
  GstTagList *list;
  guint i, c1, c2;
  GDate d;
  GTime t;
  GstPad *src;

  /* If header is empty, just skip it. */
  for (i = 0; (i < sizeof (pad->h)) && (pad->h[i] == '\0'); i++);
  if (i == sizeof (pad->h)) {
    pad->to_write = pad->to_skip = pad->h_len = 0;
    return NULL;
  }

  /* Check the checksum */
  for (c1 = i = 0; i < 512; i++)
    c1 += ((i < 148 || i >= 148 + 8) ? (guchar) pad->h[i] : 32);
  for (c2 = 0, i = 148; (i < 148 + 8) && pad->h[i]; i++)
    c2 = (8 * c2 + (guchar) pad->h[i] - 48);
  if (c1 != c2) {
    GST_DEBUG_OBJECT (pad, "Checksum mismatch.");
    pad->to_write = pad->to_skip = pad->h_len = 0;
    return NULL;
  }

  for (pad->to_write = 0, i = 124; pad->h[i] != '\0'; i++)
    pad->to_write = pad->to_write * 8 + pad->h[i] - 48;
  if (pad->to_write % 512)
    pad->to_skip = 512 - pad->to_write % 512;

  /* Create the pad if necessary */
  pad->source++;
  for (i = pad->sources->len; i < pad->source; i++) {
    gchar *name;

    name = g_strdup_printf ("src_%d", pad->source - 1);
    src = g_object_new (GST_TYPE_TAR_DEMUX_PAD_SRC,
        "direction", GST_PAD_SRC, "name", name, NULL);
    g_free (name);
    g_ptr_array_add (pad->sources, src);
    gst_pad_set_active (src, TRUE);
    gst_element_add_pad (GST_PAD_PARENT (pad), src);
  }
  src = g_ptr_array_index (pad->sources, pad->source - 1);
  g_object_set (G_OBJECT (src), "offset", offset,
      "duration", pad->to_write, NULL);
  g_object_notify (G_OBJECT (pad), "source");

  /* Post information */
  g_date_clear (&d, 1);
  for (t = 0, i = 136; i < 136 + 12; i++)
    t = t * 10 + pad->h[i] - 48;
  g_date_set_time (&d, t);
  list = gst_tag_list_new ();
  gst_tag_list_add (list, GST_TAG_MERGE_REPLACE,
      GST_TAG_TITLE, pad->h,
      GST_TAG_DATE, &d, NULL);
  gst_pad_push_event (src, gst_event_new_tag (list));

  return src;
}

static GstFlowReturn
gst_tar_demux_pad_sink_chain_recursive (GstTarDemuxPadSink * pad,
    GstBuffer * in)
{
  GstBuffer *out;
  GstFlowReturn r = GST_FLOW_OK;
  guint remaining = GST_BUFFER_SIZE (in);
  guint next;
  guint position = 0;
  GstCaps *caps;

  /* Scan header */
  if (pad->h_len < sizeof (pad->h)) {
    next = MIN (remaining, sizeof (pad->h) - pad->h_len);
    memcpy (pad->h + pad->h_len, GST_BUFFER_DATA (in), next);
    pad->h_len += next;
    if (pad->h_len < sizeof (pad->h))
      goto clean_up;
    gst_tar_demux_pad_sink_decode_header (pad, pad->offset - remaining);
    remaining -= next;
    if (!remaining)
      goto clean_up;
    position += next;
  }

  /* Write data */
  if (pad->to_write) {
    GstPad *src;
    gint64 offset;

    src = g_ptr_array_index (pad->sources, pad->source - 1);
    g_object_get (G_OBJECT (src), "offset", &offset, NULL);

    next = MIN (remaining, pad->to_write);
    out = gst_buffer_create_sub (in, position, next);
    GST_BUFFER_OFFSET (out) = pad->offset - remaining - (offset + 512);

    /* Configure source pad (if necessary) */
    if (gst_caps_is_any (GST_PAD_CAPS (src))) {
      caps = gst_type_find_helper_for_buffer (GST_OBJECT (pad), out, NULL);
      if (caps) {
        gst_pad_set_caps (src, caps);
        gst_caps_unref (caps);
      } else
        GST_DEBUG_OBJECT (pad, "Could not determine caps for buffer.");
    }

    gst_buffer_set_caps (out, GST_PAD_CAPS (src));
    if ((r = gst_pad_push (src, out)) != GST_FLOW_OK)
      goto clean_up;

    pad->to_write -= next;
    if (!pad->to_write) {
      pad->h_len = 0;
      gst_pad_push_event (src, gst_event_new_eos ());
    }

    remaining -= next;
    if (!remaining)
      goto clean_up;
    position += next;
  }

  /* Skip data */
  if (pad->to_skip) {
    next = MIN (remaining, pad->to_skip);
    pad->to_skip -= next;
    remaining -= next;
    if (!remaining)
      goto clean_up;
    position += next;
  }

  /* Scan next header */
  out = gst_buffer_create_sub (in, position, remaining);
  if ((r = gst_tar_demux_pad_sink_chain_recursive (pad, out)) != GST_FLOW_OK)
    goto clean_up;

clean_up:
  gst_buffer_unref (in);
  return r;
}

static GstFlowReturn
gst_tar_demux_pad_sink_chain (GstPad * pad, GstBuffer * buf)
{
  GST_TAR_DEMUX_PAD_SINK (pad)->offset += GST_BUFFER_SIZE (buf);
  return gst_tar_demux_pad_sink_chain_recursive (GST_TAR_DEMUX_PAD_SINK (pad), buf);
}

static void
gst_tar_demux_pad_sink_loop (void *data)
{
  GstTarDemuxPadSink *snk = GST_TAR_DEMUX_PAD_SINK (data);
  GstFlowReturn ret;
  GstFormat format = GST_FORMAT_BYTES;
  gint64 offset, duration;

  /* Don't loop if we already did and didn't reset thereafter. */
  if (snk->sources_known) {
    GST_DEBUG_OBJECT (snk, "Pausing task as already finished stream.");
    gst_pad_pause_task (GST_PAD (snk));
    return;
  }

  /* We can only scan the stream if we know the duration */
  ret = gst_pad_query_peer_duration (GST_PAD (snk), &format, &duration);
  switch (ret) {
    case GST_FLOW_OK:
    case GST_FLOW_RESEND:
      break;
    default:
      GST_DEBUG_OBJECT (snk, "Pausing task: %s", ret);
      gst_pad_pause_task (GST_PAD (snk));
      return;
  }

  /*
   * We are able to fetch always 2 blocks after a header (i.e. 
   * 3 blocks including header) because TAR files do have
   * at least 2 empty blocks at the end.
   */
  GST_DEBUG_OBJECT (snk, "Scanning stream of duration %"
      G_GINT64_FORMAT "...", duration);
  for (offset = 0; offset < duration - 3 * 512; ) {
    GstCaps *caps;
    GstPad *src;
    GstBuffer *buf, *sub;
    gint64 i;

    /* Get header. */
    ret = gst_pad_pull_range (GST_PAD (snk), offset, 3 * 512, &buf);
    if (ret != GST_FLOW_OK) {
      GST_DEBUG_OBJECT (snk, "Failed to scan stream: %s",
          gst_flow_get_name (ret));
      gst_pad_pause_task (GST_PAD (snk));
      return;
    }
    if (GST_BUFFER_SIZE (buf) < 512) {
      GST_DEBUG_OBJECT (snk, "Failed to scan stream: "
          "Could not fetch enough data.");
      gst_buffer_unref (buf);
      gst_pad_pause_task (GST_PAD (snk));
      return;
    }
    memcpy (snk->h, GST_BUFFER_DATA (buf), sizeof (snk->h));
    offset += 512;
    snk->h_len = sizeof (snk->h);
    src = gst_tar_demux_pad_sink_decode_header (snk, offset);
    if (!src || !snk->to_write) {
      gst_buffer_unref (buf);
      continue;
    }

    /* Identify caps. */
    sub = gst_buffer_create_sub (buf, 512, GST_BUFFER_SIZE (buf) - 512);
    gst_buffer_unref (buf);
    GST_BUFFER_OFFSET (sub) = 0;
    caps = gst_type_find_helper_for_buffer (GST_OBJECT (snk), sub, NULL);
    gst_buffer_unref (sub);
    if (caps) {
      gst_pad_set_caps (src, caps);
      gst_caps_unref (caps);
    }

    /* Write the data to the source pad. */
    GST_DEBUG_OBJECT (src, "Writing %li bytes...", snk->to_write);
    for (i = 0; i < snk->to_write; i += 1024) {
      ret = gst_pad_pull_range (GST_PAD (snk), offset + i,
        MIN (1024, snk->to_write - i), &buf);
      if (ret != GST_FLOW_OK) {
        GST_DEBUG_OBJECT (snk, "Failed to fetch data: %s",
          gst_flow_get_name (ret));
        gst_pad_pause_task (GST_PAD (snk));
        return;
      }
      gst_pad_push (src, buf);
    }
    offset += snk->to_write + snk->to_skip;
  }

  snk->sources_known = TRUE;
  gst_element_no_more_pads (GST_PAD_PARENT (snk));
  snk->h_len = 0;
  snk->source = 0;
  gst_pad_pause_task (GST_PAD (snk));
  gst_tar_demux_pad_sink_send_eos (snk);
}

static gboolean
gst_tar_demux_pad_sink_activate_pull (GstPad * snk, gboolean active)
{
  if (active)
    return gst_pad_start_task (snk, gst_tar_demux_pad_sink_loop, snk);
  else
    return gst_pad_stop_task (snk);
}

static gboolean
gst_tar_demux_pad_sink_activate (GstPad * pad)
{
  if (gst_pad_check_pull_range (pad)) {
    return gst_pad_activate_pull (pad, TRUE);
  } else
    return gst_pad_activate_push (pad, TRUE);
}

static void
gst_tar_demux_pad_sink_init (GTypeInstance *instance, gpointer g_class)
{
  GstPad *pad = GST_PAD (instance);
  GstCaps *caps;

  GST_TAR_DEMUX_PAD_SINK (pad)->sources = g_ptr_array_new ();

  GST_PAD_DIRECTION (pad) = GST_PAD_SINK;
  caps = gst_caps_new_simple ("application/x-tar", NULL);
  gst_pad_set_caps (pad, caps);
  gst_caps_unref (caps);

  gst_pad_set_chain_function (pad, gst_tar_demux_pad_sink_chain);
  gst_pad_set_event_function (pad, gst_tar_demux_pad_sink_event);
  gst_pad_set_activate_function (pad, gst_tar_demux_pad_sink_activate);
  gst_pad_set_activatepull_function (pad, gst_tar_demux_pad_sink_activate_pull);
}

static void
gst_tar_demux_pad_sink_finalize (GObject * object)
{
  GstTarDemuxPadSink *pad = GST_TAR_DEMUX_PAD_SINK (object);

  if (pad->sources) {
    g_ptr_array_free (pad->sources, TRUE);
    pad->sources = NULL;
  }

  G_OBJECT_CLASS (pad_sink_parent_class)->finalize (object);
}

static void
gst_tar_demux_pad_sink_get_property (GObject * object, guint prop_id,
    GValue * value, GParamSpec * pspec)
{
  GstTarDemuxPadSink *pad = GST_TAR_DEMUX_PAD_SINK (object);

  switch (prop_id) {
    case SINK_PROP_SRC:
      g_value_set_object (value,
          g_ptr_array_index (pad->sources, pad->source));
      break;
    default:
      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
  }
}

static void
gst_tar_demux_pad_sink_set_property (GObject * object, guint prop_id,
    const GValue * value, GParamSpec * pspec)
{
  GstTarDemuxPadSink *pad = GST_TAR_DEMUX_PAD_SINK (object);

  switch (prop_id) {
    case  SINK_PROP_SRC:

      /* This property gets only set during construction. */
      g_ptr_array_add (pad->sources, g_value_get_object (value));

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

static void
gst_tar_demux_pad_sink_class_init (gpointer g_class, gpointer data)
{
  GObjectClass *gobject_class = G_OBJECT_CLASS (g_class);

  gobject_class->finalize     = gst_tar_demux_pad_sink_finalize;
  gobject_class->get_property = gst_tar_demux_pad_sink_get_property;
  gobject_class->set_property = gst_tar_demux_pad_sink_set_property;

  g_object_class_install_property (gobject_class,
      SINK_PROP_SRC, g_param_spec_object ("source",
        "Source", "Source", GST_TYPE_PAD,
        G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));

  pad_sink_parent_class = GST_PAD_CLASS (g_type_class_peek_parent (g_class));
}

static GType
gst_tar_demux_pad_sink_get_type (void)
{
  static GType object_type = 0;

  if (G_UNLIKELY (!object_type)) {
    static const GTypeInfo object_info = {
      sizeof (GstTarDemuxPadSinkClass), NULL, NULL,
      gst_tar_demux_pad_sink_class_init, NULL, NULL,
      sizeof (GstTarDemuxPadSink), 0,
      gst_tar_demux_pad_sink_init
    };

    object_type = g_type_register_static (GST_TYPE_PAD, "GstTarDemuxPadSink",
        &object_info, (GTypeFlags) 0);
  }
  return object_type;
}

static void
gst_tar_demux_pad_sink_reset (GstTarDemuxPadSink * pad)
{
  pad->h_len = 0;
  pad->offset = 0;
  pad->source = 0;
  pad->to_write = pad->to_skip = 0;
}

/******************************************************************************
 * Element
 */

static GstElementDetails tar_demux_details = GST_ELEMENT_DETAILS ("TAR demuxer",
    "Codec/Demuxer", "Demuxer for TAR streams",
    "Lutz Mueller <lutz@topfrose.de>");

static GstStaticPadTemplate snk_t =
GST_STATIC_PAD_TEMPLATE ("sink", GST_PAD_SINK, GST_PAD_ALWAYS,
    GST_STATIC_CAPS ("application/x-tar"));
static GstStaticPadTemplate src_t =
GST_STATIC_PAD_TEMPLATE ("src_0", GST_PAD_SRC, GST_PAD_ALWAYS,
    GST_STATIC_CAPS_ANY);

struct _GstTarDemux
{
  GstElement parent;

  GstPad *snk;
};

struct _GstTarDemuxClass
{
  GstElementClass parent_class;
};

GST_BOILERPLATE (GstTarDemux, gst_tar_demux, GstElement, GST_TYPE_ELEMENT);

static void
gst_tar_demux_init (GstTarDemux * tar, GstTarDemuxClass * klass)
{
  GstPad *src;

  /* The first source */
  src = g_object_new (GST_TYPE_TAR_DEMUX_PAD_SRC,
      "direction", GST_PAD_SRC, "name", "src_0", NULL);
  gst_element_add_pad (GST_ELEMENT (tar), src);

  /* The sink */
  tar->snk = g_object_new (GST_TYPE_TAR_DEMUX_PAD_SINK,
      "source", src, "direction", GST_PAD_SINK, "name", "sink", NULL);
  gst_element_add_pad (GST_ELEMENT (tar), tar->snk);
}

static void
gst_tar_demux_base_init (gpointer g_class)
{
  GstElementClass *ec = GST_ELEMENT_CLASS (g_class);

  gst_element_class_add_pad_template (ec, gst_static_pad_template_get (&snk_t));
  gst_element_class_add_pad_template (ec, gst_static_pad_template_get (&src_t));
  gst_element_class_set_details (ec, &tar_demux_details);
}

static GstStateChangeReturn
gst_tar_demux_change_state (GstElement * element, GstStateChange transition)
{
  GstTarDemux *tar = GST_TAR_DEMUX (element);
  GstStateChangeReturn ret;

  ret = GST_ELEMENT_CLASS (parent_class)->change_state (element, transition);
  switch (transition) {
    case GST_STATE_CHANGE_PAUSED_TO_READY:
      gst_tar_demux_pad_sink_reset (GST_TAR_DEMUX_PAD_SINK (tar->snk));
      break;
    default:
      break;
  }
  return ret;
}

static void
gst_tar_demux_class_init (GstTarDemuxClass * klass)
{
  GstElementClass *element_class = GST_ELEMENT_CLASS (klass);
  
  element_class->change_state = gst_tar_demux_change_state;

  GST_DEBUG_CATEGORY_INIT (tar_demux_debug, "tardemux", 0, "TAR demuxer");
}
