/* GStreamer APEv1/2 tag reader
 * Copyright (C) 2004 Ronald Bultje <rbultje@ronald.bitfreak.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

#include <string.h>
#include <gst/gst.h>
#include <gst/bytestream/bytestream.h>

#include "apedemux.h"

GST_DEBUG_CATEGORY_STATIC (apedemux_debug);
#define GST_CAT_DEFAULT apedemux_debug

static GstStaticPadTemplate sink_templ = GST_STATIC_PAD_TEMPLATE ("sink",
    GST_PAD_SINK,
    GST_PAD_ALWAYS,
    GST_STATIC_CAPS ("application/x-apetag")
    );

static GstStaticPadTemplate src_templ = GST_STATIC_PAD_TEMPLATE ("src",
    GST_PAD_SRC,
    GST_PAD_SOMETIMES,          /* spider/decodebin hack */
    GST_STATIC_CAPS_ANY);

static void gst_ape_demux_base_init (GstApeDemuxClass * klass);
static void gst_ape_demux_class_init (GstApeDemuxClass * klass);
static void gst_ape_demux_init (GstApeDemux * ape);

static void gst_ape_demux_loop (GstElement * element);

static const GstEventMask *gst_ape_demux_get_event_mask (GstPad * pad);
static gboolean gst_ape_demux_handle_src_event (GstPad * pad, GstEvent * event);
static const GstFormat *gst_ape_demux_get_src_formats (GstPad * pad);
static const GstQueryType *gst_ape_demux_get_src_query_types (GstPad * pad);
static gboolean gst_ape_demux_handle_src_query (GstPad * pad,
    GstQueryType type, GstFormat * format, gint64 * value);

static GstElementStateReturn gst_ape_demux_change_state (GstElement * element);

static GstElementClass *parent_class = NULL;

GType
gst_ape_demux_get_type (void)
{
  static GType ape_demux_type = 0;

  if (!ape_demux_type) {
    static const GTypeInfo ape_demux_info = {
      sizeof (GstApeDemuxClass),
      (GBaseInitFunc) gst_ape_demux_base_init,
      NULL,
      (GClassInitFunc) gst_ape_demux_class_init,
      NULL,
      NULL,
      sizeof (GstApeDemux),
      0,
      (GInstanceInitFunc) gst_ape_demux_init,
    };

    ape_demux_type =
        g_type_register_static (GST_TYPE_ELEMENT,
        "GstApeDemux", &ape_demux_info, 0);
  }

  return ape_demux_type;
}

static void
gst_ape_demux_base_init (GstApeDemuxClass * klass)
{
  static GstElementDetails gst_ape_demux_details =
      GST_ELEMENT_DETAILS ("Ape tag reader",
      "Codec/Demuxer/Audio",
      "Reads APEv1/2 tags",
      "Ronald Bultje <rbultje@ronald.bitfreak.net>");
  GstElementClass *element_class = GST_ELEMENT_CLASS (klass);

  gst_element_class_add_pad_template (element_class,
      gst_static_pad_template_get (&sink_templ));
  gst_element_class_add_pad_template (element_class,
      gst_static_pad_template_get (&src_templ));
  gst_element_class_set_details (element_class, &gst_ape_demux_details);
}

static void
gst_ape_demux_class_init (GstApeDemuxClass * klass)
{
  GstElementClass *gstelement_class = GST_ELEMENT_CLASS (klass);

  GST_DEBUG_CATEGORY_INIT (apedemux_debug, "apedemux",
      0, "Demuxer for APE tag reader");

  parent_class = g_type_class_ref (GST_TYPE_ELEMENT);

  gstelement_class->change_state = gst_ape_demux_change_state;
}

static void
gst_ape_demux_init (GstApeDemux * ape)
{
  GST_FLAG_SET (ape, GST_ELEMENT_EVENT_AWARE);

  ape->sinkpad =
      gst_pad_new_from_template (gst_static_pad_template_get (&sink_templ),
      "sink");
  gst_element_add_pad (GST_ELEMENT (ape), ape->sinkpad);

#if 0
  ape->srcpad =
      gst_pad_new_from_template (gst_static_pad_template_get (&src_templ),
      "src");
  gst_pad_set_formats_function (ape->srcpad, gst_ape_demux_get_src_formats);
  gst_pad_set_event_mask_function (ape->srcpad, gst_ape_demux_get_event_mask);
  gst_pad_set_event_function (ape->srcpad, gst_ape_demux_handle_src_event);
  gst_pad_set_query_type_function (ape->srcpad,
      gst_ape_demux_get_src_query_types);
  gst_pad_set_query_function (ape->srcpad, gst_ape_demux_handle_src_query);
  gst_pad_use_explicit_caps (ape->srcpad);
  gst_element_add_pad (GST_ELEMENT (ape), ape->srcpad);
#endif
  ape->srcpad = NULL;

  gst_element_set_loop_function (GST_ELEMENT (ape), gst_ape_demux_loop);

  ape->state = GST_APE_DEMUX_TAGREAD;
  ape->start_off = ape->end_off = 0;
}

static const GstFormat *
gst_ape_demux_get_src_formats (GstPad * pad)
{
  static const GstFormat formats[] = {
    GST_FORMAT_BYTES,
    0
  };

  return formats;
}

static const GstQueryType *
gst_ape_demux_get_src_query_types (GstPad * pad)
{
  static const GstQueryType types[] = {
    GST_QUERY_TOTAL,
    GST_QUERY_POSITION,
    0
  };

  return types;
}

static gboolean
gst_ape_demux_handle_src_query (GstPad * pad,
    GstQueryType type, GstFormat * format, gint64 * value)
{
  GstApeDemux *ape = GST_APE_DEMUX (gst_pad_get_parent (pad));
  gboolean res;

  res = gst_pad_query (GST_PAD_PEER (ape->sinkpad), type, format, value);
  if (!res)
    return FALSE;

  switch (type) {
    case GST_QUERY_TOTAL:
      switch (*format) {
        case GST_FORMAT_BYTES:
          *value -= (ape->start_off + ape->end_off);
          break;
        default:
          break;
      }
      break;
    case GST_QUERY_POSITION:
      switch (*format) {
        case GST_FORMAT_BYTES:
          *value -= ape->start_off;
          break;
        default:
          break;
      }
      break;
    default:
      break;
  }

  return res;
}

static const GstEventMask *
gst_ape_demux_get_event_mask (GstPad * pad)
{
  static const GstEventMask masks[] = {
    {GST_EVENT_SEEK, GST_SEEK_METHOD_SET | GST_SEEK_FLAG_KEY_UNIT},
    {0,}
  };

  return masks;
}

static gboolean
gst_ape_demux_handle_src_event (GstPad * pad, GstEvent * event)
{
  GstApeDemux *ape = GST_APE_DEMUX (gst_pad_get_parent (pad));

  if (ape->state != GST_APE_DEMUX_IDENTITY)
    return FALSE;

  switch (GST_EVENT_TYPE (event)) {
    case GST_EVENT_SEEK:
      switch (GST_EVENT_SEEK_FORMAT (event)) {
        case GST_FORMAT_BYTES:{
          GstEvent *new;
          gint64 new_off;

          new_off = GST_EVENT_SEEK_OFFSET (event);
          new_off += ape->start_off;
          new = gst_event_new_seek (GST_EVENT_SEEK_TYPE (event), new_off);
          gst_event_unref (event);
          event = new;
          break;
        }
        default:
          break;
      }
      break;
    default:
      break;
  }

  return gst_pad_send_event (GST_PAD_PEER (ape->sinkpad), event);
}

/*
 * Handle an event during 'open' stage.
 */

static gboolean
gst_ape_demux_handle_event (GstApeDemux * ape, GstByteStream * bs)
{
  GstEvent *event;
  guint32 remaining;
  gboolean res = FALSE;

  gst_bytestream_get_status (bs, &remaining, &event);
  if (!event) {
    GST_ELEMENT_ERROR (ape, RESOURCE, READ, (NULL), (NULL));
    return FALSE;
  }

  switch (GST_EVENT_TYPE (event)) {
      /* this shouldn't happen. We definately can't deal with it. */
    case GST_EVENT_EOS:
    case GST_EVENT_INTERRUPT:
      GST_ELEMENT_ERROR (ape, RESOURCE, READ, (NULL),
          ("Cannot deal with EOS/interrupt events during init stage"));
      break;
    case GST_EVENT_DISCONTINUOUS:
    case GST_EVENT_FLUSH:
      /* we disregard those during init stage */
      res = TRUE;
      break;
    default:
      gst_pad_event_default (ape->sinkpad, event);
      return TRUE;
  }

  gst_event_unref (event);

  return res;
}

/*
 * Find media type. Simple for now.
 */

typedef struct _GstApeDemuxTypeFind
{
  GstApeDemux *ape;
  GstByteStream *bs;
  gboolean seekable;
  guint64 len;
  GstCaps *caps;
  guint probability;
  gboolean stop;
} GstApeDemuxTypeFind;

static guint8 *
gst_ape_demux_typefind_peek (gpointer ptr, gint64 offset, guint size)
{
  GstApeDemuxTypeFind *apetf = ptr;
  guint8 *data;

  /* non-seekable first - easy */
  if (!apetf->seekable || offset == 0) {
    /* don't seek outside reach */
    if (offset != 0 || size > apetf->len)
      return NULL;

    /* try to get data, fatal event *is* fatal for typefinding */
    while (gst_bytestream_peek_bytes (apetf->bs, &data, size) != size) {
      if (!gst_ape_demux_handle_event (apetf->ape, apetf->bs)) {
        apetf->stop = TRUE;
        return NULL;
      }
    }

    return data;
  }

  /* FIXME: theoretically we could embed mp3 and we'd like to seek
   * beyond just the beginnings then. */
  return NULL;
}

static guint64
gst_ape_demux_typefind_get_length (gpointer ptr)
{
  GstApeDemuxTypeFind *apetf = ptr;

  return apetf->len;
}

static void
gst_ape_demux_typefind_suggest (gpointer ptr,
    guint probability, const GstCaps * caps)
{
  GstApeDemuxTypeFind *apetf = ptr;

  GST_LOG ("Found type of mime %s, probability %u",
      gst_structure_get_name (gst_caps_get_structure (caps, 0)), probability);

  if (probability > apetf->probability) {
    if (apetf->caps)
      gst_caps_free (apetf->caps);
    apetf->caps = gst_caps_copy (caps);
    apetf->probability = probability;
  }
}

static gboolean
gst_ape_demux_typefind (GstApeDemux * ape,
    GstByteStream * bs, gboolean seekable)
{
  GstApeDemuxTypeFind apetf;
  GstTypeFind tf;
  GList *factories;

  GST_LOG ("Doing typefinding now");

  /* prepare */
  memset (&apetf, 0, sizeof (apetf));
  memset (&tf, 0, sizeof (tf));
  tf.peek = gst_ape_demux_typefind_peek;
  tf.suggest = gst_ape_demux_typefind_suggest;
  tf.data = &apetf;
  apetf.bs = bs;
  apetf.ape = ape;
  apetf.len = gst_bytestream_length (bs);
  if (apetf.len != (guint64) - 1) {
    apetf.len -= ape->start_off + ape->end_off;
    tf.get_length = gst_ape_demux_typefind_get_length;
  }
  apetf.seekable = seekable;

  /* run */
  for (factories = gst_type_find_factory_get_list ();
      factories != NULL && !apetf.stop &&
      apetf.probability < GST_TYPE_FIND_MAXIMUM; factories = factories->next) {
    gst_type_find_factory_call_function (factories->data, &tf);
  }

  /* fatal error */
  if (apetf.stop)
    return FALSE;

  /* type found? */
  if (!apetf.caps || apetf.probability < GST_TYPE_FIND_MINIMUM) {
    GST_ELEMENT_ERROR (ape, STREAM, TYPE_NOT_FOUND, (NULL), (NULL));
    return FALSE;
  }

  GST_LOG ("Done typefinding, found mime %s",
      gst_structure_get_name (gst_caps_get_structure (apetf.caps, 0)));

  ape->srcpad =
      gst_pad_new_from_template (gst_static_pad_template_get (&src_templ),
      "src");
  gst_pad_set_formats_function (ape->srcpad, gst_ape_demux_get_src_formats);
  gst_pad_set_event_mask_function (ape->srcpad, gst_ape_demux_get_event_mask);
  gst_pad_set_event_function (ape->srcpad, gst_ape_demux_handle_src_event);
  gst_pad_set_query_type_function (ape->srcpad,
      gst_ape_demux_get_src_query_types);
  gst_pad_set_query_function (ape->srcpad, gst_ape_demux_handle_src_query);
  gst_pad_use_explicit_caps (ape->srcpad);
  gst_pad_set_explicit_caps (ape->srcpad, apetf.caps);
  gst_element_add_pad (GST_ELEMENT (ape), ape->srcpad);

  return TRUE;
}

/*
 * Parse tags from a buffer.
 */

static GstTagList *
gst_ape_demux_parse_tags (GstApeDemux * ape, guint8 * data, gint size)
{
  GstTagList *taglist = gst_tag_list_new ();
  gboolean have_tag = FALSE;

  GST_LOG ("Reading tags from chunk of size %u bytes", size);

  /* get rid of header/footer */
  if (!memcmp (data, "APETAGEX", 8)) {
    data += 32;
    size -= 32;
  }
  if (!memcmp (data + size - 32, "APETAGEX", 8)) {
    size -= 32;
  }

  /* read actual tags - at least 10 bytes for tag header */
  while (size >= 10) {
    guint len, n = 8;
    gchar *tag, *val;
    const gchar *type = NULL;
    gboolean i = FALSE;

    /* find tag type and size */
    len = GST_READ_UINT32_LE (data);
    while (n < size && data[n] != 0x0)
      n++;
    if (n == size)
      break;
    g_assert (data[n] == 0x0);
    n++;
    if (size - n < len)
      break;

    /* read */
    tag = g_strndup ((char *) &data[8], n - 9);
    val = g_strndup ((char *) &data[n], len);
    if (!strcasecmp (tag, "title")) {
      type = GST_TAG_TITLE;
    } else if (!strcasecmp (tag, "artist")) {
      type = GST_TAG_ARTIST;
    } else if (!strcasecmp (tag, "album")) {
      type = GST_TAG_ALBUM;
    } else if (!strcasecmp (tag, "comment")) {
      type = GST_TAG_COMMENT;
    } else if (!strcasecmp (tag, "copyright")) {
      type = GST_TAG_COPYRIGHT;
    } else if (!strcasecmp (tag, "genre")) {
      type = GST_TAG_GENRE;
    } else if (!strcasecmp (tag, "isrc")) {
      type = GST_TAG_ISRC;
    } else if (!strcasecmp (tag, "track")) {
      type = GST_TAG_TRACK_NUMBER;
      i = TRUE;
    } else if (!strcasecmp (tag, "replaygain_track_gain")) {
      type = GST_TAG_TRACK_GAIN;
      i = TRUE;
    } else if (!strcasecmp (tag, "replaygain_track_peak")) {
      type = GST_TAG_TRACK_PEAK;
      i = TRUE;
    } else if (!strcasecmp (tag, "replaygain_album_gain")) {
      type = GST_TAG_ALBUM_GAIN;
      i = TRUE;
    } else if (!strcasecmp (tag, "replaygain_album_peak")) {
      type = GST_TAG_ALBUM_PEAK;
      i = TRUE;
    }
    if (type) {
      GValue v = { 0 };

      if (i) {
        g_value_init (&v, G_TYPE_INT);
        g_value_set_int (&v, atoi (val));
      } else {
        g_value_init (&v, G_TYPE_STRING);
        g_value_set_string (&v, val);
      }
      gst_tag_list_add_values (taglist, GST_TAG_MERGE_APPEND, type, &v, NULL);
      g_value_unset (&v);
      have_tag = TRUE;
    }
    GST_DEBUG ("Read tag %s: %s", tag, val);
    g_free (tag);
    g_free (val);

    /* move data pointer */
    size -= len + n;
    data += len + n;
  }

  /* let people know */
  if (have_tag) {
    gst_element_found_tags (GST_ELEMENT (ape), taglist);
    /* we'll push it over the srcpad later */
  } else {
    gst_tag_list_free (taglist);
    taglist = NULL;
  }

  return taglist;
}

/*
 * "Open" a APEv1/2 file.
 */

static gboolean
gst_ape_demux_stream_init (GstApeDemux * ape)
{
  GstByteStream *bs;
  gboolean seekable = TRUE, res = TRUE;
  guint8 *data;
  guint32 size = 0;
  GstTagList *taglist1 = NULL, *taglist2 = NULL, *taglist = NULL;

  GST_LOG ("Initializing stream, stripping tags");

  /* start off, we'll want byte-reading here */
  bs = gst_bytestream_new (ape->sinkpad);

  /* peek one byte to not confuse the typefinder */
  while (gst_bytestream_peek_bytes (bs, &data, 1) != 1) {
    if (!gst_ape_demux_handle_event (ape, bs)) {
      res = FALSE;
      goto the_city;
    }
  }

  /* can we seek? */
  if (!gst_bytestream_seek (bs, 0, GST_SEEK_METHOD_END)) {
    seekable = FALSE;
  } else {
    if (!gst_bytestream_seek (bs, 0, GST_SEEK_METHOD_SET)) {
      GST_ELEMENT_ERROR (ape, RESOURCE, SEEK, (NULL),
          ("Couldn't seek back to start - cannot handle that"));
      res = FALSE;
      goto the_city;
    }
  }

  /* ape tags at start? */
  while (gst_bytestream_peek_bytes (bs, &data, 32) != 32) {
    if (!gst_ape_demux_handle_event (ape, bs)) {
      res = FALSE;
      goto the_city;
    }
  }
  if (!memcmp (data, "APETAGEX", 8)) {
    GST_LOG ("Found tags at start");

    /* APEv2 at start of file - note that the flags are useless because
     * I have yet to see the first writer that writes correct HAS_HEADER
     * and HAS_FOOTER flags... So we detect it ourselves. */
    size = GST_READ_UINT32_LE (data + 12);

    /* Size is without the header and with the footer. So add 32 because
     * we're still at position 0 here (peek != read). */
    size += 32;
    while (gst_bytestream_peek_bytes (bs, &data, size) != size) {
      if (!gst_ape_demux_handle_event (ape, bs)) {
        res = FALSE;
        goto the_city;
      }
    }
    taglist1 = gst_ape_demux_parse_tags (ape, data, size);
    ape->start_off = size;
  }

  /* if we're not seekable, then this is it already. Flush the tags,
   * and forward the rest of the data to the next element. */
  if (!seekable) {
    if (size != 0)
      gst_bytestream_flush_fast (bs, size);

    if (!gst_ape_demux_typefind (ape, bs, FALSE)) {
      res = FALSE;
      goto the_city;
    }

    gst_bytestream_get_status (bs, &size, NULL);
    if (size) {
      GstBuffer *buf = NULL;

      gst_bytestream_read (bs, &buf, size);
      g_assert (buf);
      gst_pad_push (ape->srcpad, GST_DATA (buf));
    }

    goto the_city;
  }

  /* now look for tags at the end */
  if (!gst_bytestream_seek (bs, -32, GST_SEEK_METHOD_END)) {
    GST_ELEMENT_ERROR (ape, RESOURCE, SEEK, (NULL), (NULL));
    res = FALSE;
    goto the_city;
  }
  while (gst_bytestream_peek_bytes (bs, &data, 32) != 32) {
    if (!gst_ape_demux_handle_event (ape, bs)) {
      res = FALSE;
      goto the_city;
    }
  }

  if (!memcmp (data, "APETAGEX", 8)) {
    GST_LOG ("Found tags at end");

    /* APEv1/2 at start of file - note that the flags are useless because
     * I have yet to see the first writer that writes correct HAS_HEADER
     * and HAS_FOOTER flags... So we detect it ourselves. */
    size = GST_READ_UINT32_LE (data + 12);

    /* size is without header, so add 32 to detect that. */
    size += 32;
    if (!gst_bytestream_seek (bs, -(gint64) size, GST_SEEK_METHOD_END)) {
      GST_ELEMENT_ERROR (ape, RESOURCE, SEEK, (NULL), (NULL));
      res = FALSE;
      goto the_city;
    }
    while (gst_bytestream_peek_bytes (bs, &data, size) != size) {
      if (!gst_ape_demux_handle_event (ape, bs)) {
        res = FALSE;
        goto the_city;
      }
    }
    if (memcmp (data, "APETAGEX", 8) != 0) {
      data += 32;
      size -= 32;
    }
    taglist2 = gst_ape_demux_parse_tags (ape, data, size);
    ape->end_off = size;
  }

  /* seek back to beginning */
  if (!gst_bytestream_seek (bs, ape->start_off, GST_SEEK_METHOD_SET)) {
    GST_ELEMENT_ERROR (ape, RESOURCE, SEEK, (NULL), (NULL));
    res = FALSE;
    goto the_city;
  }

  /* get any events */
  while (gst_bytestream_peek_bytes (bs, &data, 1) != 1) {
    if (!gst_ape_demux_handle_event (ape, bs)) {
      res = FALSE;
      goto the_city;
    }
  }

  /* typefind */
  if (!gst_ape_demux_typefind (ape, bs, TRUE)) {
    res = FALSE;
    goto the_city;
  }

  /* push any leftover data */
  gst_bytestream_get_status (bs, &size, NULL);
  if (size) {
    GstBuffer *buf = NULL;

    gst_bytestream_read (bs, &buf, size);
    g_assert (buf);
    gst_pad_push (ape->srcpad, GST_DATA (buf));
  }

the_city:
  /* become rich & famous */
  gst_bytestream_destroy (bs);
  if (taglist1 || taglist2) {
    if (res) {
      /* merge */
      if (taglist1 && taglist2) {
        taglist = gst_tag_list_merge (taglist1, taglist2,
            GST_TAG_MERGE_REPLACE);
        gst_tag_list_free (taglist1);
        gst_tag_list_free (taglist2);
      } else {
        taglist = taglist1 ? taglist1 : taglist2;
      }
      gst_pad_push (ape->srcpad, GST_DATA (gst_event_new_tag (taglist)));
    } else {
      if (taglist1)
        gst_tag_list_free (taglist1);
      if (taglist2)
        gst_tag_list_free (taglist2);
    }
  }

  return res;
}

/*
 * Forward one buffer (we're an identity here).
 */

static void
gst_ape_demux_stream_data (GstApeDemux * ape)
{
  GstData *data;

  data = gst_pad_pull (ape->sinkpad);

  if (GST_IS_EVENT (data)) {
    GstEvent *event = GST_EVENT (data);

    switch (GST_EVENT_TYPE (event)) {
      case GST_EVENT_DISCONTINUOUS:{
        GstEvent *new;
        gint64 new_off = ape->start_off;

        gst_event_discont_get_value (event, GST_FORMAT_BYTES, &new_off);
        new_off -= ape->start_off;
        new = gst_event_new_discontinuous (GST_EVENT_DISCONT_NEW_MEDIA (event),
            GST_FORMAT_BYTES, new_off, GST_FORMAT_UNDEFINED);
        gst_event_unref (event);
        event = new;
        data = GST_DATA (new);
        break;
      }
      default:
        break;
    }

    gst_pad_event_default (ape->sinkpad, event);

    return;
  } else {
    GstBuffer *buf = GST_BUFFER (data), *kid;
    gint64 pos, len;
    GstFormat fmt = GST_FORMAT_BYTES;

    kid = gst_buffer_create_sub (buf, 0, GST_BUFFER_SIZE (buf));
    GST_BUFFER_OFFSET (kid) -= ape->start_off;
    gst_buffer_unref (buf);
    data = GST_DATA (kid);

    /* if the plugin allows us to, see if we're close to eos */
    if (gst_pad_query (GST_PAD_PEER (ape->sinkpad),
            GST_QUERY_POSITION, &fmt, &pos) &&
        gst_pad_query (GST_PAD_PEER (ape->sinkpad),
            GST_QUERY_TOTAL, &fmt, &len)) {
      if (pos > len - ape->end_off) {
        if (pos - GST_BUFFER_SIZE (buf) >= len - ape->end_off) {
          gst_buffer_unref (kid);
          data = NULL;
        } else {
          GST_BUFFER_SIZE (kid) -= ape->end_off - (len - pos);
        }
      }
    }
  }

  if (data)
    gst_pad_push (ape->srcpad, data);
}

static void
gst_ape_demux_loop (GstElement * element)
{
  GstApeDemux *ape = GST_APE_DEMUX (element);

  switch (ape->state) {
    case GST_APE_DEMUX_TAGREAD:
      if (!gst_ape_demux_stream_init (ape))
        return;
      GST_LOG ("From now on, we're in identity mode");
      ape->state = GST_APE_DEMUX_IDENTITY;
      break;

    case GST_APE_DEMUX_IDENTITY:
      gst_ape_demux_stream_data (ape);
      break;

    default:
      g_assert (0);
  }
}

static GstElementStateReturn
gst_ape_demux_change_state (GstElement * element)
{
  GstApeDemux *ape = GST_APE_DEMUX (element);

  switch (GST_STATE_TRANSITION (element)) {
    case GST_STATE_PAUSED_TO_READY:
      if (ape->srcpad) {
        gst_element_remove_pad (element, ape->srcpad);
        ape->srcpad = NULL;
      }
      ape->state = GST_APE_DEMUX_TAGREAD;
      ape->start_off = ape->end_off = 0;
      break;
    default:
      break;
  }

  if (GST_ELEMENT_CLASS (parent_class)->change_state)
    return GST_ELEMENT_CLASS (parent_class)->change_state (element);

  return GST_STATE_SUCCESS;
}