/* GStreamer
 * Copyright (C) 2003, 2004 Benjamin Otte <otte@gnome.org>
 *
 * gstoggdemux.c: ogg stream demuxer
 *
 * 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 <gst/gst.h>
#include <gst/bytestream/filepad.h>
#include <ogg/ogg.h>
#include <string.h>

/* tweak this to improve setup times */
/* PLEASE don't just tweak it because one file is faster with tweaked numbers, 
 * but use a good benchmark with both video and audio files */
/* number of bytes we seek in front of desired point so we can resync properly */
#define SETUP_EXPECTED_PAGE_SIZE (8500) /* this is out of vorbisfile */
/* number of bytes where we don't seek to middle anymore but just walk through
 * all packets */
#define SETUP_PASSTHROUGH_SIZE (SETUP_EXPECTED_PAGE_SIZE * 20)
/* if we have to repeat a seek backwards because we didn't seek back far enough, 
 * we multiply the amount we seek by this amount */
#define SETUP_SEEK_MULTIPLIER (5)


GST_DEBUG_CATEGORY_STATIC (gst_ogg_demux_debug);
GST_DEBUG_CATEGORY_STATIC (gst_ogg_demux_setup_debug);
#define GST_CAT_DEFAULT gst_ogg_demux_debug

#define GST_TYPE_OGG_DEMUX (gst_ogg_demux_get_type())
#define GST_OGG_DEMUX(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj),GST_TYPE_OGG_DEMUX, GstOggDemux))
#define GST_OGG_DEMUX_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass),GST_TYPE_OGG_DEMUX, GstOggDemux))
#define GST_IS_OGG_DEMUX(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj),GST_TYPE_OGG_DEMUX))
#define GST_IS_OGG_DEMUX_CLASS(obj) (G_TYPE_CHECK_CLASS_TYPE((klass),GST_TYPE_OGG_DEMUX))

typedef struct _GstOggDemux GstOggDemux;
typedef struct _GstOggDemuxClass GstOggDemuxClass;

typedef enum
{
  /* just because you shouldn't make a valid enum value 0 */
  GST_OGG_STATE_INAVLID,
  /* just started, we need to decide if we should do setup */
  GST_OGG_STATE_START,
  /* setup is analyzing the stream, getting lengths and so on */
  GST_OGG_STATE_SETUP,
  /* after a seek, during resyncing */
  GST_OGG_STATE_SEEK,
  /* normal playback */
  GST_OGG_STATE_PLAY
}
GstOggState;

/* all information needed for one ogg stream */
typedef struct
{
  GstPad *pad;                  /* reference for this pad is held by element we belong to */

  gint serial;
  ogg_stream_state stream;
  guint64 offset;               /* end offset of last buffer */
  guint64 known_offset;         /* last known offset */
  gint64 packetno;              /* number of next expected packet */

  guint64 start;                /* first valid granulepos */
  guint64 length;               /* length of stream or 0 */
  glong pages;                  /* number of pages in stream or 0 */

  gint64 start_offset;          /* earliest offset in file where this stream has been found */
  gboolean start_found;         /* we have found the bos (first) page */
  gint64 end_offset;            /* last offset in file where this stream has been found */
  gboolean end_found;           /* we have fount the eos (last) page */

  guint flags;
}
GstOggPad;

typedef enum
{
  GST_OGG_PAD_NEEDS_DISCONT = (1 << 0),
  GST_OGG_PAD_NEEDS_FLUSH = (1 << 1)
}
GstOggPadFlags;

/* all information needed for one ogg chain (relevant for chained bitstreams) */
typedef struct
{
  gint64 starts_at;             /* starting offset of chain */
  gint64 ends_at;               /* end offset of stream (only valid when not last chain or not in setup) */

  GSList *pads;                 /* list of GstOggPad */
}
GstOggChain;

#define CURRENT_CHAIN(ogg) (&g_array_index ((ogg)->chains, GstOggChain, (ogg)->current_chain))
#define FOR_PAD_IN_CURRENT_CHAIN(ogg, __pad, ...) \
  FOR_PAD_IN_CHAIN(ogg, __pad, (ogg)->current_chain, __VA_ARGS__)
#define FOR_PAD_IN_CHAIN(ogg, _pad, i, ...) G_STMT_START{			\
  GSList *_walk;							      	\
  GstOggChain *_chain = &g_array_index ((ogg)->chains, GstOggChain, i);		\
  if (i != -1) {								\
    for (_walk = _chain->pads; _walk; _walk = g_slist_next (_walk)) {		\
      GstOggPad *_pad = (GstOggPad *) _walk->data;				\
      __VA_ARGS__								\
    }										\
  }										\
}G_STMT_END

typedef enum
{
  GST_OGG_FLAG_BOS = GST_ELEMENT_FLAG_LAST,
  GST_OGG_FLAG_EOS,
  GST_OGG_FLAG_WAIT_FOR_DISCONT
}
GstOggFlag;

struct _GstOggDemux
{
  GstElement element;

  /* pad */
  GstFilePad *sinkpad;

  /* state */
  GstOggState state;
  GArray *chains;               /* list of chains we know */
  gint current_chain;           /* id of chain that currently "plays" */
  gboolean bos;                 /* no-more-pads signal needs this */
  /* setup */
  GSList *unordered;            /* streams we haven't found chains for yet */
  guint setup_state;            /* seperate from global state */

  /* ogg stuff */
  ogg_sync_state sync;

  /* seeking */
  GstOggPad *seek_pad;
  gint64 seek_to;
  gint64 seek_skipped;
  guint64 seek_offset;
  GstFormat seek_format;
  gint seek_try;
};

struct _GstOggDemuxClass
{
  GstElementClass parent_class;
};

/* signals and args */
enum
{
  /* FILL ME */
  LAST_SIGNAL
};

enum
{
  ARG_0
      /* FILL ME */
};

static GstStaticPadTemplate ogg_demux_src_template_factory =
GST_STATIC_PAD_TEMPLATE ("src",
    GST_PAD_SRC,
    GST_PAD_SOMETIMES,
    GST_STATIC_CAPS_ANY);

static GstStaticPadTemplate ogg_demux_sink_template_factory =
GST_STATIC_PAD_TEMPLATE ("sink",
    GST_PAD_SINK,
    GST_PAD_ALWAYS,
    GST_STATIC_CAPS ("application/ogg")
    );

/* different setup phases */
typedef enum
{
  SETUP_INVALID,
  SETUP_READ_FIRST_BOS,
  SETUP_READ_BOS,
  SETUP_FIND_LAST_CHAIN,
  SETUP_FIND_END_OF_CHAIN,
  SETUP_FIND_END_OF_STREAMS,
  SETUP_FIND_END_OF_LAST_STREAMS
}
GstOggSetupState;

typedef struct
{
  gboolean (*init) (GstOggDemux * ogg);
  gboolean (*process) (GstOggDemux * ogg, ogg_page * page);
}
SetupStateFunc;

static gboolean _read_bos_init (GstOggDemux * ogg);
static gboolean _read_bos_process (GstOggDemux * ogg, ogg_page * page);
static gboolean _find_chain_init (GstOggDemux * ogg);
static gboolean _find_chain_process (GstOggDemux * ogg, ogg_page * page);
static gboolean _find_last_chain_init (GstOggDemux * ogg);
static gboolean _find_last_chain_process (GstOggDemux * ogg, ogg_page * page);
static gboolean _find_streams_init (GstOggDemux * ogg);
static gboolean _find_streams_process (GstOggDemux * ogg, ogg_page * page);

static SetupStateFunc setup_funcs[] = {
  {NULL, NULL},
  {_read_bos_init, _read_bos_process},
  {_read_bos_init, _read_bos_process},
  {_find_last_chain_init, _find_last_chain_process},
  {_find_chain_init, _find_chain_process},
  {_find_streams_init, _find_streams_process},
  {_find_streams_init, _find_streams_process},
  {NULL, NULL}                  /* just because */
};

static gboolean gst_ogg_demux_set_setup_state (GstOggDemux * ogg,
    GstOggSetupState state);

static void gst_ogg_demux_finalize (GObject * object);

static gboolean gst_ogg_demux_src_event (GstPad * pad, GstEvent * event);
static const GstEventMask *gst_ogg_demux_get_event_masks (GstPad * pad);
static const GstQueryType *gst_ogg_demux_get_query_types (GstPad * pad);
static const GstFormat *gst_ogg_demux_get_formats (GstPad * pad);

static gboolean gst_ogg_demux_src_query (GstPad * pad,
    GstQueryType type, GstFormat * format, gint64 * value);

static void gst_ogg_demux_iterate (GstFilePad * pad);
static gboolean gst_ogg_demux_handle_event (GstPad * pad, GstEvent * event);

static GstElementStateReturn gst_ogg_demux_change_state (GstElement * element);

static GstOggPad *gst_ogg_pad_new (GstOggDemux * ogg, int serial_no);
static void gst_ogg_pad_remove (GstOggDemux * ogg, GstOggPad * ogg_pad);
static void gst_ogg_pad_reset (GstOggDemux * ogg, GstOggPad * pad);
static void gst_ogg_demux_push (GstOggDemux * ogg, ogg_page * page);
static void gst_ogg_pad_push (GstOggDemux * ogg, GstOggPad * ogg_pad);
static void gst_ogg_chains_clear (GstOggDemux * ogg);
static void gst_ogg_add_chain (GstOggDemux * ogg);

static GstCaps *gst_ogg_type_find (ogg_packet * packet);

static void gst_ogg_print (GstOggDemux * demux);

#define GST_OGG_SET_STATE(ogg, new_state) G_STMT_START{				\
  GST_DEBUG_OBJECT (ogg, "setting state to %s", G_STRINGIFY (new_state));	\
  ogg->state = new_state;							\
  ogg->setup_state = (new_state == GST_OGG_STATE_SETUP) ?			\
      SETUP_READ_FIRST_BOS : SETUP_INVALID;	        		\
}G_STMT_END

GST_BOILERPLATE (GstOggDemux, gst_ogg_demux, GstElement, GST_TYPE_ELEMENT)

     static void gst_ogg_demux_base_init (gpointer g_class)
{
  GstElementClass *element_class = GST_ELEMENT_CLASS (g_class);
  static GstElementDetails gst_ogg_demux_details =
      GST_ELEMENT_DETAILS ("ogg demuxer",
      "Codec/Demuxer",
      "demux ogg streams (info about ogg: http://xiph.org)",
      "Benjamin Otte <otte@gnome.org>");

  gst_element_class_set_details (element_class, &gst_ogg_demux_details);

  gst_element_class_add_pad_template (element_class,
      gst_static_pad_template_get (&ogg_demux_sink_template_factory));
  gst_element_class_add_pad_template (element_class,
      gst_static_pad_template_get (&ogg_demux_src_template_factory));
}
static void
gst_ogg_demux_class_init (GstOggDemuxClass * klass)
{
  GstElementClass *gstelement_class = GST_ELEMENT_CLASS (klass);
  GObjectClass *gobject_class = G_OBJECT_CLASS (klass);

  gstelement_class->change_state = gst_ogg_demux_change_state;

  gobject_class->finalize = gst_ogg_demux_finalize;
}

static void
gst_ogg_demux_init (GstOggDemux * ogg)
{
  GST_FLAG_SET (ogg, GST_ELEMENT_EVENT_AWARE);

  /* create the sink pad */
  ogg->sinkpad =
      GST_FILE_PAD (gst_file_pad_new (gst_static_pad_template_get
          (&ogg_demux_sink_template_factory), "sink"));
  gst_file_pad_set_iterate_function (ogg->sinkpad, gst_ogg_demux_iterate);
  gst_file_pad_set_event_function (ogg->sinkpad, gst_ogg_demux_handle_event);
  gst_pad_set_formats_function (GST_PAD (ogg->sinkpad),
      gst_ogg_demux_get_formats);
  gst_element_add_pad (GST_ELEMENT (ogg), GST_PAD (ogg->sinkpad));

  /* initalize variables */
  GST_OGG_SET_STATE (ogg, GST_OGG_STATE_START);
  ogg->chains = g_array_new (TRUE, TRUE, sizeof (GstOggChain));
  ogg->current_chain = -1;
}

static void
gst_ogg_demux_finalize (GObject * object)
{
  GstOggDemux *ogg;

  ogg = GST_OGG_DEMUX (object);

  ogg_sync_clear (&ogg->sync);

  /* chains are removed when going to READY */
  g_assert (ogg->current_chain == -1);
  g_assert (ogg->chains->len == 0);
  g_array_free (ogg->chains, TRUE);

  if (G_OBJECT_CLASS (parent_class)->finalize)
    G_OBJECT_CLASS (parent_class)->finalize (object);
}

static const GstFormat *
gst_ogg_demux_get_formats (GstPad * pad)
{
  static GstFormat src_formats[] = {
    GST_FORMAT_BYTES,
    GST_FORMAT_DEFAULT,         /* granulepos */
    GST_FORMAT_TIME,
    0
  };
  static GstFormat sink_formats[] = {
    GST_FORMAT_BYTES,
    GST_FORMAT_DEFAULT,         /* bytes */
    0
  };

  return (GST_PAD_IS_SRC (pad) ? src_formats : sink_formats);
}

static const GstEventMask *
gst_ogg_demux_get_event_masks (GstPad * pad)
{
  static const GstEventMask gst_ogg_demux_src_event_masks[] = {
    {GST_EVENT_SEEK, GST_SEEK_METHOD_SET | GST_SEEK_FLAG_FLUSH},
    {0,}
  };

  return gst_ogg_demux_src_event_masks;
}
static const GstQueryType *
gst_ogg_demux_get_query_types (GstPad * pad)
{
  static const GstQueryType gst_ogg_demux_src_query_types[] = {
    GST_QUERY_TOTAL,
    GST_QUERY_POSITION,
    0
  };

  return gst_ogg_demux_src_query_types;
}

static GstOggPad *
gst_ogg_get_pad_by_pad (GstOggDemux * ogg, GstPad * pad)
{
  GSList *walk;
  GstOggPad *cur;

  if (ogg->current_chain == -1) {
    GST_DEBUG_OBJECT (ogg, "no active chain, returning NULL");
    return NULL;
  }
  for (walk = CURRENT_CHAIN (ogg)->pads; walk; walk = g_slist_next (walk)) {
    cur = (GstOggPad *) walk->data;
    if (cur->pad == pad)
      return cur;
  }
  return NULL;
}

/* will subtract the base from a given granulepos in a stream
 * (lineairly) and return the relative granulepos from the first
 * packet in the stream, or some approximation thereof. Input is
 * in granulepos units, output is either granulepos or time.
 * Uses time internally. Returns -1 on error.
 */
static gint64
get_relative (GstOggDemux * ogg, GstOggPad * cur, gint64 granpos, GstFormat out)
{
  gint64 time, start = -1, tmp;
  GstFormat fmt;

  /* we're gonna ask our peer */
  if (!GST_PAD_PEER (cur->pad))
    return -1;

  /* lineair unit (time) */
  fmt = GST_FORMAT_TIME;
  if (!gst_pad_convert (GST_PAD_PEER (cur->pad),
          GST_FORMAT_DEFAULT, granpos, &fmt, &time))
    return -1;

  /* get base for this chain */
  FOR_PAD_IN_CURRENT_CHAIN (ogg, pad,
      if (pad->start != -1 &&
          GST_PAD_PEER (pad->pad) &&
          gst_pad_convert (GST_PAD_PEER (pad->pad),
              GST_FORMAT_DEFAULT, pad->start,
              &fmt, &tmp) && (start == -1 || tmp < start))
      start = tmp;);
  if (start == -1)
    return -1;

  /* base is *end of first page*, so subtract $random amount to make
   * us think it's the start of the page (= 1 second) */
  if (start > GST_SECOND)
    start -= GST_SECOND;
  else
    start = 0;

  /* subtract */
  if (time > start)
    time -= start;
  else
    time = 0;

  /* convert back to $outputformat */
  if (!gst_pad_convert (GST_PAD_PEER (cur->pad),
          GST_FORMAT_TIME, time, &out, &tmp))
    return -1;

  return tmp;
}

/* the query function on the src pad only knows about granulepos
 * values but we can use the peer plugins to convert the granulepos
 * (which is supposed to be the default format) to any other format 
 */
static gboolean
gst_ogg_demux_src_query (GstPad * pad, GstQueryType type,
    GstFormat * format, gint64 * value)
{
  gboolean res = FALSE;
  GstOggDemux *ogg;
  GstOggPad *cur;
  guint64 granulepos = 0;

  ogg = GST_OGG_DEMUX (gst_pad_get_parent (pad));

  cur = gst_ogg_get_pad_by_pad (ogg, pad);
  if (!cur)
    return FALSE;

  switch (type) {
    case GST_QUERY_TOTAL:{
      if (cur->length != 0 && cur->length > cur->start) {
        granulepos = cur->length;
        res = TRUE;
      }
      break;
    }
    case GST_QUERY_POSITION:
      if (cur->length != 0 && cur->length > cur->start) {
        granulepos = cur->known_offset;
        res = TRUE;
      }
      break;
    default:
      break;
  }

  if (res) {
    gint64 time;

    time = get_relative (ogg, cur, granulepos, GST_FORMAT_TIME);
    if (time == -1)
      return FALSE;

    /* still ok, got a granulepos then */
    switch (*format) {
      case GST_FORMAT_TIME:
        /* fine, result should be granulepos */
        *value = time;
        break;
      default:
        /* something we have to ask our peer */
        if (GST_PAD_PEER (pad)) {
          res = gst_pad_convert (GST_PAD_PEER (pad),
              GST_FORMAT_TIME, time, format, value);
        } else {
          res = FALSE;
        }
        break;
    }
  }
  return res;
}

/* The current seeking implementation is the most simple I could come up with:
 * - when seeking forwards, just discard data until desired position is reached
 * - when seeking backwards, seek to beginning and seek forward from there
 * Anyone is free to improve this algorithm as it is quite stupid and probably
 * really slow.
 *
 * The seeking position can be specified as the granulepos in case a decoder
 * plugin can give us a correct granulepos, or in timestamps.
 * In the case of a time seek, we repeadedly ask the peer element to 
 * convert the granulepos in the page to a timestamp. We go back to playing
 * when the timestamp is the requested one (or close enough to it).
 */
static gboolean
gst_ogg_demux_src_event (GstPad * pad, GstEvent * event)
{
  GstOggDemux *ogg;
  GstOggPad *cur;

  ogg = GST_OGG_DEMUX (gst_pad_get_parent (pad));
  cur = gst_ogg_get_pad_by_pad (ogg, pad);

  /* FIXME: optimize this so events from inactive chains work? 
   * in theory there shouldn't be an exisiting pad for inactive chains */
  if (cur == NULL)
    goto error;

  switch (GST_EVENT_TYPE (event)) {
    case GST_EVENT_SEEK:
    {
      gint64 offset, position, total, seek_offset;
      GstFormat format, my_format;
      gboolean res;

      format = GST_EVENT_SEEK_FORMAT (event);
      offset = GST_EVENT_SEEK_OFFSET (event);

      my_format = format;

      /* get position, we'll need it later to decide what direction
       * we need to seek in */
      res = gst_ogg_demux_src_query (pad,
          GST_QUERY_POSITION, &my_format, &position);
      if (!res)
        goto error;

      switch (GST_EVENT_SEEK_METHOD (event)) {
        case GST_SEEK_METHOD_END:
        {
          gint64 value;

          /* invalid offset */
          if (offset > 0)
            goto error;

          /* calculate total length first */
          res = gst_ogg_demux_src_query (pad,
              GST_QUERY_TOTAL, &my_format, &value);
          if (!res)
            goto error;

          /* requested position is end + offset */
          offset = value + offset;
          break;
        }
        case GST_SEEK_METHOD_CUR:
        {
          /* add current position to offset */
          offset = position + offset;
          break;
        }
        case GST_SEEK_METHOD_SET:
          /* offset and format are fine here */
          break;
        default:
          g_warning ("invalid seek method in seek event");
          goto error;
      }

      my_format = GST_FORMAT_TIME;
      if (format != GST_FORMAT_TIME) {
        if (!GST_PAD_PEER (pad) ||
            !gst_pad_convert (GST_PAD_PEER (pad), format,
                offset, &my_format, &position))
          goto error;
      } else {
        position = offset;
      }
      if (!gst_ogg_demux_src_query (pad, GST_QUERY_TOTAL, &my_format, &total))
        goto error;
      if (position < 0)
        position = 0;
      else if (position > total)
        position = total;
      seek_offset = gst_file_pad_get_length (ogg->sinkpad) *
          ((gdouble) position) / ((gdouble) total);
      if (gst_file_pad_seek (ogg->sinkpad, seek_offset,
              GST_SEEK_METHOD_SET) != 0)
        goto error;
      ogg->seek_try = 1;
      ogg_sync_clear (&ogg->sync);

      GST_OGG_SET_STATE (ogg, GST_OGG_STATE_SEEK);
      FOR_PAD_IN_CURRENT_CHAIN (ogg, pad,
          pad->flags |= GST_OGG_PAD_NEEDS_DISCONT;
          );
      if (GST_EVENT_SEEK_FLAGS (event) & GST_SEEK_FLAG_FLUSH) {
        FOR_PAD_IN_CURRENT_CHAIN (ogg, pad,
            pad->flags |= GST_OGG_PAD_NEEDS_FLUSH;
            );
      }
      GST_DEBUG_OBJECT (ogg,
          "initiating seeking to format %d, offset %" G_GUINT64_FORMAT, format,
          offset);

      /* store format and position we seek to */
      ogg->seek_pad = cur;
      ogg->seek_to = position;
      ogg->seek_format = GST_FORMAT_TIME;
      ogg->seek_offset = seek_offset;

      gst_event_unref (event);
      return TRUE;
    }
    default:
      return gst_pad_event_default (pad, event);
  }

  g_assert_not_reached ();

error:
  gst_event_unref (event);
  return FALSE;
}

static gboolean
gst_ogg_demux_src_convert (GstPad * pad,
    GstFormat src_format, gint64 src_value,
    GstFormat * dest_format, gint64 * dest_value)
{
  gboolean res = FALSE;
  GstOggDemux *ogg;
  GstOggPad *cur;

  ogg = GST_OGG_DEMUX (gst_pad_get_parent (pad));
  cur = gst_ogg_get_pad_by_pad (ogg, pad);

  /* fill me, not sure with what... */

  return res;
}

static void
gst_ogg_start_playing (GstOggDemux * ogg)
{
  GST_DEBUG_OBJECT (ogg, "done with setup, changing to playback now");
  if (gst_file_pad_seek (ogg->sinkpad, 0, GST_SEEK_METHOD_SET) != 0) {
    GST_ELEMENT_ERROR (ogg, CORE, SEEK, (NULL),
        ("cannot seek to start after EOS"));
  }
  ogg_sync_clear (&ogg->sync);
  if (ogg->current_chain >= 0) {
    ogg->current_chain = 0;
  } else {
    gst_ogg_add_chain (ogg);
  }
  GST_FLAG_UNSET (ogg, GST_OGG_FLAG_EOS);
  GST_FLAG_SET (ogg, GST_OGG_FLAG_WAIT_FOR_DISCONT);
  GST_OGG_SET_STATE (ogg, GST_OGG_STATE_PLAY);
  gst_ogg_print (ogg);
}

static gboolean
gst_ogg_demux_handle_event (GstPad * pad, GstEvent * event)
{
  GstOggDemux *ogg = GST_OGG_DEMUX (gst_pad_get_parent (pad));

  switch (GST_EVENT_TYPE (event)) {
    case GST_EVENT_DISCONTINUOUS:
      GST_DEBUG_OBJECT (ogg, "got a discont event");
      ogg_sync_reset (&ogg->sync);
      gst_event_unref (event);
      GST_FLAG_UNSET (ogg, GST_OGG_FLAG_WAIT_FOR_DISCONT);
      FOR_PAD_IN_CURRENT_CHAIN (ogg, pad,
          pad->flags |= GST_OGG_PAD_NEEDS_DISCONT;
          );
      break;
    default:
      gst_pad_event_default (pad, event);
      break;
  }
  return TRUE;
}

static void
gst_ogg_demux_eos (GstOggDemux * ogg)
{
  guint i;
  GSList *walk;
  GstEvent *event;

  GST_DEBUG_OBJECT (ogg, "got EOS");
  ogg->current_chain = -1;
  if (ogg->state == GST_OGG_STATE_SETUP) {
    gst_ogg_start_playing (ogg);
    return;
  }
  event = gst_event_new (GST_EVENT_EOS);
  for (i = 0; i < ogg->chains->len; i++) {
    GstOggChain *chain = &g_array_index (ogg->chains, GstOggChain, i);

    for (walk = chain->pads; walk; walk = g_slist_next (walk)) {
      GstOggPad *pad = (GstOggPad *) walk->data;

      if (pad->pad && GST_PAD_IS_USABLE (pad->pad)) {
        gst_data_ref (GST_DATA (event));
        gst_pad_push (pad->pad, GST_DATA (event));
      }
    }
  }
  gst_element_set_eos (GST_ELEMENT (ogg));
  gst_event_unref (event);
}

static GstOggPad *
gst_ogg_pad_get_in_chain (GstOggDemux * ogg, guint chain, int serial)
{
  FOR_PAD_IN_CHAIN (ogg, pad, chain, if (pad->serial == serial)
      return pad;);
  return NULL;
}

/* get the pad with the given serial in the current stream or NULL if none */
static GstOggPad *
gst_ogg_pad_get_in_current_chain (GstOggDemux * ogg, int serial)
{
  if (ogg->current_chain == -1)
    return NULL;
  g_return_val_if_fail (ogg->current_chain < ogg->chains->len, NULL);
  return gst_ogg_pad_get_in_chain (ogg, ogg->current_chain, serial);
}

/* FIXME: HACK - i dunno if this is supported ogg API */
static guint
gst_ogg_page_get_length (ogg_page * page)
{
  return page->header_len + page->body_len;
}

static gint64
gst_ogg_demux_position (GstOggDemux * ogg)
{
  gint64 pos = gst_file_pad_tell (ogg->sinkpad);

  if (pos < 0)
    return pos;

  return pos - ogg->sync.fill + ogg->sync.returned;
}

/* END HACK */

/* fill in values from this page */
#include <signal.h>
static void
gst_ogg_pad_populate (GstOggDemux * ogg, GstOggPad * pad, ogg_page * page)
{
  gint64 start, end;

  if (pad->start > ogg_page_granulepos (page) && ogg_page_granulepos (page) > 0) {
    pad->start = ogg_page_granulepos (page);
  }
  if (pad->length < ogg_page_granulepos (page))
    pad->length = ogg_page_granulepos (page);
  if (pad->pages < ogg_page_pageno (page))
    pad->pages = ogg_page_pageno (page);
  end = gst_ogg_demux_position (ogg);
  if (end >= 0) {
    /* we need to know the offsets into the stream for the current page */
    start = end - gst_ogg_page_get_length (page);
    //g_print ("really setting start from %lld to %lld\n", pad->start_offset, start);
    //g_print ("really setting end from %lld to %lld\n", pad->end_offset, end);
    if (start < pad->start_offset || pad->start_offset < 0)
      pad->start_offset = start;
    if (ogg_page_bos (page))
      pad->start_found = TRUE;
    if (end > pad->end_offset)
      pad->end_offset = end;
    if (ogg_page_eos (page))
      pad->end_found = TRUE;
  }
}

/* get the ogg pad with the given serial in the unordered list or create and add it */
static GstOggPad *
gst_ogg_pad_get_unordered (GstOggDemux * ogg, ogg_page * page)
{
  GSList *walk;
  GstOggPad *pad;
  int serial = ogg_page_serialno (page);

  for (walk = ogg->unordered; walk; walk = g_slist_next (walk)) {
    pad = (GstOggPad *) walk->data;

    if (pad->serial == serial)
      goto out;
  }
  pad = gst_ogg_pad_new (ogg, serial);
  ogg->unordered = g_slist_prepend (ogg->unordered, pad);

out:
  /* update start and end pointer if applicable */
  gst_ogg_pad_populate (ogg, pad, page);

  return pad;
}

static GstOggPad *
gst_ogg_pad_get (GstOggDemux * ogg, ogg_page * page)
{
  GstOggPad *pad =
      gst_ogg_pad_get_in_current_chain (ogg, ogg_page_serialno (page));
  if (pad) {
    gst_ogg_pad_populate (ogg, pad, page);
  } else {
    pad = gst_ogg_pad_get_unordered (ogg, page);
  }
  return pad;
}

static void
gst_ogg_add_chain (GstOggDemux * ogg)
{
  GST_LOG_OBJECT (ogg, "adding chain %u", ogg->chains->len);
  ogg->current_chain = ogg->chains->len;
  g_array_set_size (ogg->chains, ogg->chains->len + 1);
}

/* abort setup phase and just start playing */
static void
abort_setup (GstOggDemux * ogg)
{
  gst_ogg_print (ogg);
  gst_ogg_chains_clear (ogg);
  gst_ogg_start_playing (ogg);
}

#undef GST_CAT_DEFAULT
#define GST_CAT_DEFAULT gst_ogg_demux_setup_debug
static gboolean
gst_ogg_demux_set_setup_state (GstOggDemux * ogg, GstOggSetupState state)
{
  g_assert (ogg->state == GST_OGG_STATE_SETUP);
  g_assert (state > 0);
  g_assert (state < G_N_ELEMENTS (setup_funcs));
  g_assert (state != ogg->setup_state);

  GST_DEBUG_OBJECT (ogg, "setting setup state from %d to %d", ogg->setup_state,
      state);
  ogg->setup_state = state;
  if (!setup_funcs[state].init (ogg)) {
    abort_setup (ogg);
    return FALSE;
  }

  return TRUE;
}

/* seeks to the given position if TRUE is returned. Seeks a bit before this
 * offset for syncing. You can call this function multiple times, if sync 
 * failed, it will then seek further back. It will never seek further back as
 * min_offset though.
 */
static gboolean
gst_ogg_demux_seek_before (GstOggDemux * ogg, gint64 offset, gint64 min_offset)
{
  gint64 before;
  GstOggChain *chain;
  gint streams;

  /* figure out how many streams are in this chain */
  chain = CURRENT_CHAIN (ogg);
  if (chain) {
    streams = g_slist_length (chain->pads);
  } else {
    streams = 1;
  }

  /* need to multiply the expected page size with the numer of streams we
   * detected to have a good chance of finding all pages */
  before = ogg->seek_skipped ? ogg->seek_skipped * SETUP_SEEK_MULTIPLIER :
      SETUP_EXPECTED_PAGE_SIZE * streams;

  GST_DEBUG_OBJECT (ogg,
      "seeking to %" G_GINT64_FORMAT " bytes before %" G_GINT64_FORMAT,
      before, offset);
  /* tried to seek to start once, don't try again */
  if (min_offset + ogg->seek_skipped > offset)
    return FALSE;
  if (gst_file_pad_seek (ogg->sinkpad, MAX (min_offset, offset - before),
          GST_SEEK_METHOD_SET) != 0)
    return FALSE;
  ogg_sync_clear (&ogg->sync);
  ogg->seek_skipped = before;
  ogg->seek_to = offset;

  return TRUE;
}

static gboolean
_read_bos_init (GstOggDemux * ogg)
{
  gst_ogg_add_chain (ogg);

  return TRUE;
}

static gboolean
_read_bos_process (GstOggDemux * ogg, ogg_page * page)
{
  /* here we're reading in the bos pages of the current chain */
  if (ogg_page_bos (page)) {
    GstOggPad *pad;

    GST_LOG_OBJECT (ogg,
        "SETUP_READ_BOS: bos found with serial %d, adding to current chain",
        ogg_page_serialno (page));
    pad = gst_ogg_pad_get_unordered (ogg, page);
    ogg->unordered = g_slist_remove (ogg->unordered, pad);
    g_assert (CURRENT_CHAIN (ogg));
    CURRENT_CHAIN (ogg)->pads =
        g_slist_prepend (CURRENT_CHAIN (ogg)->pads, pad);
  } else {
    gboolean have_all_first_pages = TRUE;

    if (CURRENT_CHAIN (ogg)->pads == NULL) {
      GST_ERROR_OBJECT (ogg, "broken ogg stream, chain has no BOS pages");
      return FALSE;
    }

    FOR_PAD_IN_CURRENT_CHAIN (ogg, pad, if (pad->start == (guint64) - 1)
        have_all_first_pages = FALSE;);

    if (have_all_first_pages) {
      GST_DEBUG_OBJECT (ogg,
          "SETUP_READ_BOS: no more bos pages, going to find end of stream");
      if (ogg->setup_state == SETUP_READ_FIRST_BOS) {
        return gst_ogg_demux_set_setup_state (ogg, SETUP_FIND_LAST_CHAIN);
      } else if (ogg->unordered) {
        return gst_ogg_demux_set_setup_state (ogg,
            SETUP_FIND_END_OF_LAST_STREAMS);
      } else {
        return gst_ogg_demux_set_setup_state (ogg, SETUP_FIND_END_OF_STREAMS);
      }
    } else {
      GstOggPad *pad =
          gst_ogg_pad_get_in_current_chain (ogg, ogg_page_serialno (page));

      gst_ogg_pad_populate (ogg, pad, page);
    }
  }
  return TRUE;
}

static gboolean
_find_chain_get_unknown_part (GstOggDemux * ogg, gint64 * start, gint64 * end)
{
  *start = 0;
  *end = G_MAXINT64;

  g_assert (ogg->current_chain >= 0);
  FOR_PAD_IN_CURRENT_CHAIN (ogg, pad, *start = MAX (*start, pad->end_offset);
      );

  if (ogg->setup_state == SETUP_FIND_LAST_CHAIN) {
    *end = gst_file_pad_get_length (ogg->sinkpad);
    if (*end < 0)
      return FALSE;
  } else {
    GSList *walk;

    g_assert (ogg->unordered != NULL);
    for (walk = ogg->unordered; walk; walk = g_slist_next (walk)) {
      GstOggPad *temp = walk->data;

      *end = MIN (*end, temp->start_offset);
    }
  }
  GST_DEBUG_OBJECT (ogg, "we're looking for a new chain in the range [%"
      G_GINT64_FORMAT ", %" G_GINT64_FORMAT "]", *start, *end);

  /* overlapping chains?! */
  if (*end < *start) {
    GST_ERROR_OBJECT (ogg, "chained streams overlap, bailing out");
    return FALSE;
  }

  return TRUE;
}

static gboolean
_find_last_chain_init (GstOggDemux * ogg)
{
  gint64 end = gst_file_pad_get_length (ogg->sinkpad);

  ogg->seek_skipped = 0;
  if (end < 0)
    return FALSE;
  if (!gst_ogg_demux_seek_before (ogg, end, 0))
    return FALSE;
  return TRUE;
}

static gboolean
_find_last_chain_process (GstOggDemux * ogg, ogg_page * page)
{
  GstOggPad *pad = gst_ogg_pad_get (ogg, page);

  /* optimization: set eos as found - we're investigating last pages here anyway */
  pad->end_found = TRUE;
  /* set to 0 to indicate we found a page */
  ogg->seek_skipped = 0;
  return TRUE;
}

static gboolean
_find_chain_seek (GstOggDemux * ogg, gint64 start, gint64 end)
{
  if (end - start < SETUP_PASSTHROUGH_SIZE) {
    GST_LOG_OBJECT (ogg,
        "iterating through remaining window, because it's smaller than %u bytes",
        SETUP_PASSTHROUGH_SIZE);
    if (ogg->seek_to >= start) {
      ogg->seek_skipped = 0;
      if (!gst_ogg_demux_seek_before (ogg, start, start))
        return FALSE;
    }
  } else {
    if (!gst_ogg_demux_seek_before (ogg, (start + end) / 2, start))
      return FALSE;
  }
  return TRUE;
}

static gboolean
_find_chain_init (GstOggDemux * ogg)
{
  gint64 start, end;

  ogg->seek_skipped = 0;
  ogg->seek_to = -1;
  if (!_find_chain_get_unknown_part (ogg, &start, &end))
    return FALSE;
  if (!_find_chain_seek (ogg, start, end))
    return FALSE;
  return TRUE;
}

static gboolean
_find_chain_process (GstOggDemux * ogg, ogg_page * page)
{
  gint64 start, end;

  if (!_find_chain_get_unknown_part (ogg, &start, &end))
    return FALSE;

  if (ogg->seek_to <= start && gst_ogg_demux_position (ogg) > end) {
    /* we now should have the first bos page, because
     * - we seeked to a point in the known chain
     * - we're now in a part that belongs to the unordered streams
     */
    g_assert (g_slist_find (ogg->unordered, gst_ogg_pad_get (ogg, page)));
    if (!ogg_page_bos (page)) {
      /* broken stream */
      return FALSE;
    }
    if (!gst_ogg_demux_set_setup_state (ogg, SETUP_READ_BOS))
      return FALSE;
    return _read_bos_process (ogg, page);
  } else {
    if (!_find_chain_seek (ogg, start, end))
      return FALSE;
  }

  return TRUE;
}

static gboolean
_find_streams_check (GstOggDemux * ogg)
{
  gint chain_nr = ogg->setup_state == SETUP_FIND_END_OF_LAST_STREAMS ?
      ogg->chains->len - 1 : ogg->chains->len - 2;
  gint64 endpos;

  /* figure out positions */
  if (ogg->setup_state == SETUP_FIND_END_OF_LAST_STREAMS) {
    if ((endpos = gst_file_pad_get_length (ogg->sinkpad)) < 0)
      return FALSE;
  } else {
    endpos = G_MAXINT64;
    FOR_PAD_IN_CHAIN (ogg, pad, ogg->chains->len - 1,
        endpos = MIN (endpos, pad->start_offset);
        );
  }
  if (!ogg->seek_skipped || gst_ogg_demux_position (ogg) >= endpos) {
    /* have we found the endposition for all streams yet? */
    FOR_PAD_IN_CHAIN (ogg, pad, chain_nr, if (!pad->end_offset)
        goto go_on;);
    /* get out, we're done */
    ogg->seek_skipped = 0;
    ogg->seek_to = -1;
    if (ogg->unordered) {
      ogg->setup_state = SETUP_FIND_END_OF_CHAIN;
    } else {
      gst_ogg_start_playing (ogg);
    }
    return TRUE;
  go_on:
    if (!gst_ogg_demux_seek_before (ogg, endpos, 0))
      return FALSE;
  }

  return TRUE;
}

static gboolean
_find_streams_init (GstOggDemux * ogg)
{
  ogg->seek_skipped = 0;
  ogg->seek_to = -1;
  return _find_streams_check (ogg);
}

static gboolean
_find_streams_process (GstOggDemux * ogg, ogg_page * page)
{
  gint chain_nr = ogg->setup_state == SETUP_FIND_END_OF_LAST_STREAMS ?
      ogg->chains->len - 1 : ogg->chains->len - 2;

  g_assert (ogg->setup_state == SETUP_FIND_END_OF_LAST_STREAMS ||
      ogg->setup_state == SETUP_FIND_END_OF_STREAMS);
  g_assert (chain_nr >= 0);
  /* mark current pad as having an endframe */
  if (ogg->seek_skipped) {
    GstOggPad *pad =
        gst_ogg_pad_get_in_chain (ogg, chain_nr, ogg_page_serialno (page));
    if (pad) {
      pad->end_offset = TRUE;
      g_print ("marking pad %d as having an end\n", pad->serial);
    }
  }
  return _find_streams_check (ogg);
}

#undef GST_CAT_DEFAULT
#define GST_CAT_DEFAULT gst_ogg_demux_debug

static void
gst_ogg_demux_iterate (GstFilePad * pad)
{
  GstOggDemux *ogg;
  guint8 *data;
  guint available;
  int pageout_ret = 1;
  gint64 offset_end;

  ogg = GST_OGG_DEMUX (gst_pad_get_parent (GST_PAD (pad)));

  available = gst_file_pad_available (ogg->sinkpad);
  if (available == 0) {
    if (gst_file_pad_eof (ogg->sinkpad)) {
      gst_ogg_demux_eos (ogg);
    } else {
      GST_DEBUG_OBJECT (ogg, "no data available, doing nothing");
    }
    if (ogg->state != GST_OGG_STATE_SETUP)
      return;
  }
  GST_LOG_OBJECT (ogg, "queueing next %u bytes of data", available);
  data = (guint8 *) ogg_sync_buffer (&ogg->sync, available);
  if ((available = gst_file_pad_read (ogg->sinkpad, data, available)) < 0) {
    GST_ERROR_OBJECT (ogg, "error %u reading data from pad",
        gst_file_pad_error (ogg->sinkpad));
    return;
  }
  if (ogg_sync_wrote (&ogg->sync, available) != 0) {
    GST_ELEMENT_ERROR (ogg, LIBRARY, TOO_LAZY, (NULL),
        ("ogg_sync_wrote failed"));
    return;
  }
  offset_end = gst_file_pad_tell (ogg->sinkpad);
  g_assert (offset_end >= 0);   /* FIXME: do sth reasonable if no length available */
  while (pageout_ret != 0) {
    ogg_page page;

    pageout_ret = ogg_sync_pageout (&ogg->sync, &page);
    switch (pageout_ret) {
      case -1:
        /* FIXME: need some kind of discont here, we don't know any values to send though,
         * we only have the END_OFFSET */
        break;
      case 0:
        if (ogg->state == GST_OGG_STATE_SETUP) {
          if (gst_file_pad_get_length (ogg->sinkpad) <= offset_end) {
            if (ogg->seek_skipped) {
              if (!gst_ogg_demux_seek_before (ogg,
                      gst_file_pad_get_length (ogg->sinkpad), 0))
                abort_setup (ogg);
            } else if (ogg->setup_state == SETUP_FIND_LAST_CHAIN) {
              if (ogg->unordered) {
                if (!gst_ogg_demux_seek_before (ogg, offset_end / 2, 0))
                  abort_setup (ogg);
                if (!gst_ogg_demux_set_setup_state (ogg,
                        SETUP_FIND_END_OF_CHAIN))
                  goto out;
              } else {
                if (!gst_ogg_demux_set_setup_state (ogg,
                        SETUP_FIND_END_OF_LAST_STREAMS))
                  goto out;
              }
            } else {
              abort_setup (ogg);
            }
            goto out;
          }
        }
        break;
      case 1:
        GST_LOG_OBJECT (ogg,
            "processing ogg page (serial %d, packet %ld, granule pos %llu, state: %d, bos %d)",
            ogg_page_serialno (&page), ogg_page_pageno (&page),
            ogg_page_granulepos (&page), ogg->state, ogg_page_bos (&page));
        switch (ogg->state) {
          case GST_OGG_STATE_SETUP:
            if (!setup_funcs[ogg->setup_state].process (ogg, &page)) {
              abort_setup (ogg);
              goto out;
            }
            break;
          case GST_OGG_STATE_START:
            if (gst_file_pad_seek (ogg->sinkpad, 0, GST_SEEK_METHOD_END) == 0) {
              GST_OGG_SET_STATE (ogg, GST_OGG_STATE_SETUP);
              GST_DEBUG_OBJECT (ogg, "stream can seek, try setup now");
              if (gst_file_pad_seek (ogg->sinkpad, 0, GST_SEEK_METHOD_SET) != 0) {
                GST_ELEMENT_ERROR (ogg, CORE, SEEK, (NULL),
                    ("stream can seek to end, but not to start. Can't handle that."));
              }
              ogg_sync_clear (&ogg->sync);
              gst_ogg_add_chain (ogg);
              GST_FLAG_SET (ogg, GST_OGG_FLAG_WAIT_FOR_DISCONT);
              goto out;
            } else {
              GST_DEBUG_OBJECT (ogg, "stream can not seek");
              gst_ogg_add_chain (ogg);
              GST_OGG_SET_STATE (ogg, GST_OGG_STATE_PLAY);
            }
            /* fall through */
          case GST_OGG_STATE_SEEK:
          case GST_OGG_STATE_PLAY:
            gst_ogg_demux_push (ogg, &page);
            break;
          default:
            g_assert_not_reached ();
            break;
        }
        break;
      default:
        GST_WARNING_OBJECT (ogg,
            "unknown return value %d from ogg_sync_pageout", pageout_ret);
        pageout_ret = 0;
        break;
    }
  }
out:
  return;
}
static GstOggPad *
gst_ogg_pad_new (GstOggDemux * ogg, int serial)
{
  GstOggPad *ret = g_new0 (GstOggPad, 1);
  GstTagList *list = gst_tag_list_new ();

  ret->serial = serial;
  if (ogg_stream_init (&ret->stream, serial) != 0) {
    GST_ERROR_OBJECT (ogg,
        "Could not initialize ogg_stream struct for serial %d.", serial);
    g_free (ret);
    return NULL;
  }
  gst_tag_list_add (list, GST_TAG_MERGE_REPLACE, GST_TAG_SERIAL, serial, NULL);
  gst_element_found_tags (GST_ELEMENT (ogg), list);
  gst_tag_list_free (list);

  GST_LOG_OBJECT (ogg, "created new ogg src %p for stream with serial %d", ret,
      serial);
  ret->start_offset = ret->end_offset = -1;
  ret->start = -1;
  ret->start_found = ret->end_found = FALSE;

  return ret;
}
static void
gst_ogg_pad_remove (GstOggDemux * ogg, GstOggPad * pad)
{
  if (pad->pad) {
    /* FIXME:
     * we do it in the EOS signal already - EOS handling needs to be better thought out.
     * Correct way would be pushing EOS on eos page, but scheduler doesn't like that
     */
    if (GST_PAD_IS_USABLE (pad->pad))
      gst_pad_push (pad->pad, GST_DATA (gst_event_new (GST_EVENT_EOS)));

    gst_element_remove_pad (GST_ELEMENT (ogg), pad->pad);
    pad->pad = NULL;
  }
  if (ogg_stream_clear (&pad->stream) != 0)
    GST_ERROR_OBJECT (ogg,
        "ogg_stream_clear (serial %d) did not return 0, ignoring this error",
        pad->serial);
  GST_LOG_OBJECT (ogg, "free ogg src %p for stream with serial %d", pad,
      pad->serial);
  g_free (pad);
}
static void
gst_ogg_demux_push (GstOggDemux * ogg, ogg_page * page)
{
  GstOggPad *cur =
      gst_ogg_pad_get_in_current_chain (ogg, ogg_page_serialno (page));

  /* now we either have a stream (cur) or not */
  if (ogg_page_bos (page)) {
    if (cur) {
      GST_DEBUG_OBJECT (ogg,
          "ogg page declared as BOS while stream %d already existed."
          "Possibly a seek happened.", cur->serial);
    } else if (cur) {
      GST_DEBUG_OBJECT (ogg, "reactivating deactivated stream %d.",
          cur->serial);
    } else {
      /* FIXME: monitor if we are still in creation stage? */
      cur = gst_ogg_pad_new (ogg, ogg_page_serialno (page));
      if (!cur) {
        GST_ELEMENT_ERROR (ogg, LIBRARY, TOO_LAZY, (NULL),
            ("Creating ogg_stream struct failed."));
        return;
      }
      if (ogg->current_chain == -1) {
        /* add new one at the end */
        gst_ogg_add_chain (ogg);
      }
      CURRENT_CHAIN (ogg)->pads =
          g_slist_prepend (CURRENT_CHAIN (ogg)->pads, cur);
    }
    ogg->bos = TRUE;
  } else if (ogg->bos) {
    gst_element_no_more_pads (GST_ELEMENT (ogg));
    ogg->bos = FALSE;
  }
  if (cur == NULL) {
    GST_ELEMENT_ERROR (ogg, STREAM, DECODE, (NULL),
        ("invalid ogg stream serial no"));
    return;
  }
  if (ogg_stream_pagein (&cur->stream, page) != 0) {
    GST_WARNING_OBJECT (ogg,
        "ogg stream choked on page (serial %d), resetting stream", cur->serial);
    gst_ogg_pad_reset (ogg, cur);
    return;
  }
  switch (ogg->state) {
    case GST_OGG_STATE_SEEK:
      GST_LOG_OBJECT (ogg,
          "in seek - offset now: %" G_GUINT64_FORMAT
          " (pad %d) - desired offset %" G_GUINT64_FORMAT " (pad %d)",
          cur->known_offset, cur->serial, ogg->seek_to, ogg->seek_pad->serial);

      if (cur != ogg->seek_pad) {
        break;
      } else {
        gint64 position, diff;
        gdouble ratio;

        /* see if we reached the destination position when seeking */
        position = get_relative (ogg, cur, ogg_page_granulepos (page),
            GST_FORMAT_TIME);
        /* Note: ogg->seek_to is already in GST_FORMAT_TIME... */
        if (position == -1) {
          /* let's just stop then */
          goto play;
        }

        /* fairly random treshold. */
        if (ogg->seek_to > position)
          diff = ogg->seek_to - position;
        else
          diff = position - ogg->seek_to;
        if (diff < GST_SECOND) {
          GST_DEBUG ("Close enough (%" GST_TIME_FORMAT " seconds off)",
              GST_TIME_ARGS (diff));
          ogg->seek_to = position;
          goto play;
        }

        /* not too long */
        if (ogg->seek_try > 5) {
          GST_DEBUG ("Seeking took too long, continuing with current page");
          ogg->seek_to = position;
          goto play;
        }

        /* seek again! yay */
        ratio = (gdouble) ogg->seek_to / position;
        ogg->seek_offset = ogg->seek_offset * ratio;
        if (gst_file_pad_seek (ogg->sinkpad, ogg->seek_offset,
                GST_SEEK_METHOD_SET) != 0) {
          goto play;
        }
        ogg->seek_try++;
        ogg_sync_clear (&ogg->sync);
        return;

      play:
        GST_OGG_SET_STATE (ogg, GST_OGG_STATE_PLAY);
        GST_DEBUG_OBJECT (ogg,
            "ended seek at offset %" G_GUINT64_FORMAT " (requested  %"
            G_GUINT64_FORMAT, cur->known_offset, ogg->seek_to);
        ogg->seek_pad = NULL;
        ogg->seek_offset = 0;
        ogg->seek_try = 0;
      }
      /* fallthrough */
    case GST_OGG_STATE_PLAY:
      cur->known_offset = ogg_page_granulepos (page);
      gst_ogg_pad_push (ogg, cur);
      break;
    default:
      g_assert_not_reached ();
      break;
  }
  if (ogg_page_eos (page)) {
    GST_DEBUG_OBJECT (ogg, "got EOS for stream with serial %d, sending EOS now",
        cur->serial);

    /* send an EOS before removing this pad */
    if (GST_PAD_IS_USABLE (cur->pad))
      gst_pad_push (cur->pad, GST_DATA (gst_event_new (GST_EVENT_EOS)));

    gst_element_remove_pad (GST_ELEMENT (ogg), cur->pad);
    cur->pad = NULL;
  }
}
static void
gst_ogg_pad_push (GstOggDemux * ogg, GstOggPad * pad)
{
  ogg_packet packet;
  int ret;
  GstBuffer *buf;

  /* Hack. If someone connects to the push (or any related) signal and
   * goes to READY, the pad will be destroyed and the next line will
   * segfault. This does not work on PLAY -> READY -> PLAY. */
  while (GST_STATE (ogg) >= GST_STATE_PAUSED) {
    ret = ogg_stream_packetout (&pad->stream, &packet);
    GST_LOG_OBJECT (ogg, "packetout gave %d", ret);
    switch (ret) {
      case 0:
        return;
      case -1:
        /* out of sync, could call gst_ogg_pad_reset() here but ogg can decode
         * the packet just fine. We should probably send a DISCONT though. */
        break;
      case 1:{
        gint64 pos = -1;

        /* only push data when playing, not during seek or similar */
        if (ogg->state != GST_OGG_STATE_PLAY)
          continue;
        if (!pad->pad) {
          GstCaps *caps = gst_ogg_type_find (&packet);
          gchar *name = g_strdup_printf ("serial_%d", pad->serial);

          if (caps == NULL) {
            GST_WARNING_OBJECT (ogg,
                "couldn't find caps for stream with serial %d", pad->serial);
            caps = gst_caps_new_simple ("application/octet-stream", NULL);
          }
          pad->pad =
              gst_pad_new_from_template (gst_static_pad_template_get
              (&ogg_demux_src_template_factory), name);
          g_free (name);
          gst_pad_set_event_function (pad->pad,
              GST_DEBUG_FUNCPTR (gst_ogg_demux_src_event));
          gst_pad_set_event_mask_function (pad->pad,
              GST_DEBUG_FUNCPTR (gst_ogg_demux_get_event_masks));
          gst_pad_set_query_function (pad->pad,
              GST_DEBUG_FUNCPTR (gst_ogg_demux_src_query));
          gst_pad_set_query_type_function (pad->pad,
              GST_DEBUG_FUNCPTR (gst_ogg_demux_get_query_types));
          gst_pad_set_formats_function (pad->pad,
              GST_DEBUG_FUNCPTR (gst_ogg_demux_get_formats));
          gst_pad_set_convert_function (pad->pad,
              GST_DEBUG_FUNCPTR (gst_ogg_demux_src_convert));

          gst_pad_use_explicit_caps (pad->pad);
          gst_pad_set_explicit_caps (pad->pad, caps);
          gst_caps_free (caps);
          gst_pad_set_active (pad->pad, TRUE);
          gst_element_add_pad (GST_ELEMENT (ogg), pad->pad);
        }
        /* check for discont */
        if (packet.packetno != pad->packetno++) {
          pad->flags |= GST_OGG_PAD_NEEDS_DISCONT;
          pad->packetno = packet.packetno + 1;
        }

        if (pad->known_offset != -1) {
          pos = get_relative (ogg, pad, pad->known_offset, GST_FORMAT_DEFAULT);
        }

        if ((pad->flags & GST_OGG_PAD_NEEDS_FLUSH)
            && GST_PAD_IS_USABLE (pad->pad)) {
          gst_pad_push (pad->pad, GST_DATA (gst_event_new (GST_EVENT_FLUSH)));
          pad->flags &= (~GST_OGG_PAD_NEEDS_FLUSH);
        }

        /* send discont if needed */
        if ((pad->flags & GST_OGG_PAD_NEEDS_DISCONT)
            && GST_PAD_IS_USABLE (pad->pad)) {
          /* so in order to synchronized the variety of streams, we will
           * not use the granpos but the last seekpos for the discont. */
          GstFormat fmt;
          GstEvent *event;
          gint64 discont;

          if (pos != -1) {
            fmt = GST_FORMAT_DEFAULT;
            if (!GST_PAD_PEER (pad->pad) ||
                !gst_pad_convert (GST_PAD_PEER (pad->pad),
                    ogg->seek_format, ogg->seek_to, &fmt, &discont)) {
              event = gst_event_new_discontinuous (FALSE,
                  ogg->seek_format, ogg->seek_to, GST_FORMAT_UNDEFINED);
            } else {
              event = gst_event_new_discontinuous (FALSE,
                  GST_FORMAT_DEFAULT, discont, GST_FORMAT_UNDEFINED);
            }
          } else {
            event = gst_event_new_discontinuous (FALSE,
                GST_FORMAT_DEFAULT, 0, GST_FORMAT_UNDEFINED);
          }

          /* FIXME: this might be wrong because we can only use the last
           * known offset */

          gst_pad_push (pad->pad, GST_DATA (event));
          pad->flags &= (~GST_OGG_PAD_NEEDS_DISCONT);
        }

        /* optimization: use a bufferpool containing the ogg packet? */
        buf =
            gst_pad_alloc_buffer (pad->pad, GST_BUFFER_OFFSET_NONE,
            packet.bytes);
        memcpy (buf->data, packet.packet, packet.bytes);
        if (pad->offset != -1)
          GST_BUFFER_OFFSET (buf) = pad->offset;
        if (packet.granulepos != -1 && pos != -1)
          GST_BUFFER_OFFSET_END (buf) = pos;
        pad->offset = packet.granulepos;
        if (GST_PAD_IS_USABLE (pad->pad))
          gst_pad_push (pad->pad, GST_DATA (buf));
        break;
      }
      default:
        GST_ERROR_OBJECT (ogg,
            "invalid return value %d for ogg_stream_packetout, resetting stream",
            ret);
        gst_ogg_pad_reset (ogg, pad);
        break;
    }
  }
}
static void
gst_ogg_pad_reset (GstOggDemux * ogg, GstOggPad * pad)
{
  ogg_stream_reset (&pad->stream);
  pad->offset = GST_BUFFER_OFFSET_NONE;
  /* FIXME: need a discont here */
}

static void
gst_ogg_chains_clear (GstOggDemux * ogg)
{
  gint i;
  GSList *walk;

  for (i = ogg->chains->len - 1; i >= 0; i--) {
    GstOggChain *cur = &g_array_index (ogg->chains, GstOggChain, i);

    for (walk = cur->pads; walk; walk = g_slist_next (walk)) {
      gst_ogg_pad_remove (ogg, (GstOggPad *) walk->data);
    }
    g_slist_free (cur->pads);
    cur->pads = NULL;
    g_array_remove_index (ogg->chains, i);
  }
  ogg->current_chain = -1;
}

static GstElementStateReturn
gst_ogg_demux_change_state (GstElement * element)
{
  GstOggDemux *ogg;

  ogg = GST_OGG_DEMUX (element);

  switch (GST_STATE_TRANSITION (element)) {
    case GST_STATE_NULL_TO_READY:
      ogg_sync_init (&ogg->sync);
      break;
    case GST_STATE_READY_TO_PAUSED:
      ogg_sync_reset (&ogg->sync);
      break;
    case GST_STATE_PAUSED_TO_PLAYING:
      break;
    case GST_STATE_PLAYING_TO_PAUSED:
      break;
    case GST_STATE_PAUSED_TO_READY:
      gst_ogg_chains_clear (ogg);
      GST_OGG_SET_STATE (ogg, GST_OGG_STATE_START);
      ogg->seek_pad = NULL;
      ogg->seek_to = 0;
      break;
    case GST_STATE_READY_TO_NULL:
      ogg_sync_clear (&ogg->sync);
      break;
    default:
      break;
  }

  return parent_class->change_state (element);
}

/*** typefinding **************************************************************/
/* ogg supports its own typefinding because the ogg spec defines that the first
 * packet of an ogg stream must identify the stream. Therefore ogg can use a
 * simplified approach at typefinding.
 */
typedef struct
{
  ogg_packet *packet;
  guint best_probability;
  GstCaps *caps;
}
OggTypeFind;
static guint8 *
ogg_find_peek (gpointer data, gint64 offset, guint size)
{
  OggTypeFind *find = (OggTypeFind *) data;

  if (offset + size <= find->packet->bytes) {
    return ((guint8 *) find->packet->packet) + offset;
  } else {
    return NULL;
  }
}
static void
ogg_find_suggest (gpointer data, guint probability, const GstCaps * caps)
{
  OggTypeFind *find = (OggTypeFind *) data;

  if (probability > find->best_probability) {
    gst_caps_replace (&find->caps, gst_caps_copy (caps));
    find->best_probability = probability;
  }
}
static GstCaps *
gst_ogg_type_find (ogg_packet * packet)
{
  GstTypeFind gst_find;
  OggTypeFind find;
  GList *walk, *type_list = NULL;

  walk = type_list = gst_type_find_factory_get_list ();

  find.packet = packet;
  find.best_probability = 0;
  find.caps = NULL;
  gst_find.data = &find;
  gst_find.peek = ogg_find_peek;
  gst_find.suggest = ogg_find_suggest;

  while (walk) {
    GstTypeFindFactory *factory = GST_TYPE_FIND_FACTORY (walk->data);

    gst_type_find_factory_call_function (factory, &gst_find);
    if (find.best_probability >= GST_TYPE_FIND_MAXIMUM)
      break;
    walk = g_list_next (walk);
  }

  if (find.best_probability > 0)
    return find.caps;

  return NULL;
}

gboolean
gst_ogg_demux_plugin_init (GstPlugin * plugin)
{
  GST_DEBUG_CATEGORY_INIT (gst_ogg_demux_debug, "oggdemux", 0, "ogg demuxer");
  GST_DEBUG_CATEGORY_INIT (gst_ogg_demux_setup_debug, "oggdemux_setup", 0,
      "ogg demuxer setup stage when parsing pipeline");

  return gst_element_register (plugin, "oggdemux", GST_RANK_PRIMARY,
      GST_TYPE_OGG_DEMUX);
}

/* prints all info about the element */
#undef GST_CAT_DEFAULT
#define GST_CAT_DEFAULT gst_ogg_demux_setup_debug

#ifdef GST_DISABLE_GST_DEBUG

static void
gst_ogg_print (GstOggDemux * ogg)
{
  /* NOP */
}

#else /* !GST_DISABLE_GST_DEBUG */

#define gst_ogg_print_pad(ogg, _pad) \
G_STMT_START{\
  GstOggPad *pad = (_pad); \
  GST_INFO_OBJECT (ogg, "  stream %d:", pad->serial); \
  GST_INFO_OBJECT (ogg, "    length %" G_GUINT64_FORMAT, pad->length); \
  GST_INFO_OBJECT (ogg, "    pages %ld", pad->pages); \
  GST_INFO_OBJECT (ogg, "    offset: %"G_GINT64_FORMAT"%s - %"G_GINT64_FORMAT"%s", \
      pad->start_offset, pad->start_found ? "" : " (?)", \
      pad->end_offset, pad->end_found ? "" : " (?)"); \
}G_STMT_END
static void
gst_ogg_print (GstOggDemux * ogg)
{
  guint i;
  GSList *walk;

  for (i = 0; i < ogg->chains->len; i++) {
    GstOggChain *chain = &g_array_index (ogg->chains, GstOggChain, i);

    GST_INFO_OBJECT (ogg, "chain %d (%u streams):", i,
        g_slist_length (chain->pads));
    for (walk = chain->pads; walk; walk = g_slist_next (walk)) {
      gst_ogg_print_pad (ogg, walk->data);
    }
  }
  if (ogg->unordered) {
    GST_INFO_OBJECT (ogg, "unordered (%u streams):", i,
        g_slist_length (ogg->unordered));
    for (walk = ogg->unordered; walk; walk = g_slist_next (walk)) {
      gst_ogg_print_pad (ogg, walk->data);
    }
  }

}
#endif /* GST_DISABLE_GST_DEBUG */