/* GStreamer
 * Copyright (C) <1999> Erik Walthinsen <omega@cse.ogi.edu>
 *
 * 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 <vorbis/codec.h>
#include <vorbis/vorbisfile.h>
#include <gst/bytestream/bytestream.h>

GST_DEBUG_CATEGORY_STATIC (vorbisfile_debug);
#define GST_CAT_DEFAULT vorbisfile_debug

#define GST_TYPE_VORBISFILE \
  (vorbisfile_get_type())
#define GST_VORBISFILE(obj) \
  (G_TYPE_CHECK_INSTANCE_CAST((obj),GST_TYPE_VORBISFILE,VorbisFile))
#define GST_VORBISFILE_CLASS(klass) \
  (G_TYPE_CHECK_CLASS_CAST((klass),GST_TYPE_VORBISFILE,VorbisFileClass))
#define GST_IS_VORBISFILE(obj) \
  (G_TYPE_CHECK_INSTANCE_TYPE((obj),GST_TYPE_VORBISFILE))
#define GST_IS_VORBISFILE_CLASS(obj) \
  (G_TYPE_CHECK_CLASS_TYPE((klass),GST_TYPE_VORBISFILE))

typedef struct _VorbisFile VorbisFile;
typedef struct _VorbisFileClass VorbisFileClass;

struct _VorbisFile {
  GstElement 	 element;

  GstPad 	*sinkpad,
  		*srcpad;
  GstByteStream *bs;

  OggVorbis_File vf;
  gint 		 current_link;

  gboolean 	 restart;
  gboolean 	 need_discont;
  gboolean 	 eos;
  gboolean 	 seek_pending;
  gint64 	 seek_value;
  GstFormat 	 seek_format;
  gboolean 	 seek_accurate;

  gboolean 	 may_eos;
  guint64 	 total_bytes;
  guint64 	 offset;
  gulong	 blocksize;

  GstCaps 	*metadata;
  GstCaps 	*streaminfo;
};

struct _VorbisFileClass {
  GstElementClass parent_class;

};

GType vorbisfile_get_type (void);

GstPadTemplate *gst_vorbisdec_src_template, *gst_vorbisdec_sink_template;

/* elementfactory information */
GstElementDetails vorbisfile_details =
{
  "Ogg Vorbis decoder",
  "Codec/Decoder/Audio",
  "Decodes OGG Vorbis audio using the vorbisfile API",
  "Monty <monty@xiph.org>, "
  "Wim Taymans <wim.taymans@chello.be>",
};

/* VorbisFile signals and args */
enum
{
  LAST_SIGNAL
};

#define DEFAULT_BLOCKSIZE 4096

enum
{
  ARG_0,
  ARG_BLOCKSIZE,
  ARG_METADATA,
  ARG_STREAMINFO
};

static void     gst_vorbisfile_base_init        (gpointer g_class);
static void
		gst_vorbisfile_class_init 	(VorbisFileClass *klass);
static void 	gst_vorbisfile_init 		(VorbisFile *vorbisfile);

static GstElementStateReturn
		gst_vorbisfile_change_state 	(GstElement *element);

static const 
GstFormat* 	gst_vorbisfile_get_formats 	(GstPad *pad);
static gboolean gst_vorbisfile_src_convert 	(GstPad *pad, 
		                                 GstFormat src_format, 
						 gint64 src_value,
		           			 GstFormat *dest_format, 
						 gint64 *dest_value);
static gboolean gst_vorbisfile_sink_convert 	(GstPad *pad, 
		            			 GstFormat src_format, 
						 gint64 src_value,
		            			 GstFormat *dest_format, 
						 gint64 *dest_value);
static const GstQueryType*
		gst_vorbisfile_get_query_types 	(GstPad *pad);

static gboolean gst_vorbisfile_src_query 	(GstPad *pad, 
		                                 GstQueryType type,
		        	 		 GstFormat *format, 
						 gint64 *value);
static const 
GstEventMask*	gst_vorbisfile_get_event_masks 	(GstPad *pad);
static gboolean gst_vorbisfile_src_event 	(GstPad *pad, GstEvent *event);

static void 	gst_vorbisfile_get_property 	(GObject *object, 
		            			 guint prop_id, 
						 GValue *value, 
						 GParamSpec *pspec);
static void 	gst_vorbisfile_set_property 	(GObject *object, 
		            			 guint prop_id, 
						 const GValue *value, 
						 GParamSpec *pspec);

static void 	gst_vorbisfile_loop 		(GstElement *element);

static GstElementClass *parent_class = NULL;
//static guint gst_vorbisfile_signals[LAST_SIGNAL] = { 0 };

static GstFormat logical_stream_format;

GType
vorbisfile_get_type (void)
{
  static GType vorbisfile_type = 0;

  if (!vorbisfile_type) {
    static const GTypeInfo vorbisfile_info = {
      sizeof (VorbisFileClass),
      gst_vorbisfile_base_init,
      NULL,
      (GClassInitFunc) gst_vorbisfile_class_init, NULL, NULL,
      sizeof (VorbisFile), 0,
      (GInstanceInitFunc) gst_vorbisfile_init,
    };

    vorbisfile_type = g_type_register_static (GST_TYPE_ELEMENT, "VorbisFile", 
		                              &vorbisfile_info, 0);

    logical_stream_format = gst_format_register ("logical_stream", "The logical stream");

    GST_DEBUG_CATEGORY_INIT (vorbisfile_debug, "vorbisfile", 0, "vorbis in ogg decoding element");
  }
  return vorbisfile_type;
}

static GstCaps*
vorbis_caps_factory (void)
{
  return
   gst_caps_new (
  	"vorbis_vorbis",
  	"application/ogg",
  	NULL);
}

static GstCaps*
raw_caps_factory (void)
{
  return
   gst_caps_new (
  	"vorbis_raw",
  	"audio/x-raw-int",
	gst_props_new (
    	    "endianness", 	GST_PROPS_INT (G_BYTE_ORDER),
    	    "signed", 		GST_PROPS_BOOLEAN (TRUE),
    	    "width", 		GST_PROPS_INT (16),
    	    "depth",    	GST_PROPS_INT (16),
    	    "rate",     	GST_PROPS_INT_RANGE (11025, 48000),
    	    "channels", 	GST_PROPS_INT_RANGE (1, 2),
	    NULL));
}

static GstCaps*
raw_caps2_factory (void)
{
  return
   gst_caps_new (
  	"vorbis_raw_float",
  	"audio/x-raw-float",
	gst_props_new (
	    "width",		GST_PROPS_INT (32),
	    "endianness",	GST_PROPS_INT (G_BYTE_ORDER),
    	    "rate",     	GST_PROPS_INT_RANGE (11025, 48000),
    	    "channels", 	GST_PROPS_INT_RANGE (1, 2),
            "buffer-frames",    GST_PROPS_INT_RANGE (1, G_MAXINT),
	    NULL));
}

static void
gst_vorbisfile_base_init (gpointer g_class)
{
  GstElementClass *element_class = GST_ELEMENT_CLASS (g_class);
  GstCaps *raw_caps, *vorbis_caps, *raw_caps2;

  raw_caps = raw_caps_factory ();
  raw_caps2 = raw_caps2_factory ();
  vorbis_caps = vorbis_caps_factory ();

  gst_vorbisdec_sink_template = gst_pad_template_new ("sink", GST_PAD_SINK, 
		                                      GST_PAD_ALWAYS, 
					              vorbis_caps, NULL);
  raw_caps = gst_caps_prepend (raw_caps, raw_caps2);
  gst_vorbisdec_src_template = gst_pad_template_new ("src", GST_PAD_SRC, 
		                                     GST_PAD_ALWAYS, 
					             raw_caps, NULL);
  gst_element_class_add_pad_template (element_class, gst_vorbisdec_sink_template);
  gst_element_class_add_pad_template (element_class, gst_vorbisdec_src_template);
  gst_element_class_set_details (element_class, &vorbisfile_details);
}

static void
gst_vorbisfile_class_init (VorbisFileClass * klass)
{
  GObjectClass *gobject_class;
  GstElementClass *gstelement_class;

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

  parent_class = g_type_class_ref (GST_TYPE_ELEMENT);

  g_object_class_install_property (G_OBJECT_CLASS (klass), ARG_BLOCKSIZE,
    g_param_spec_ulong ("blocksize", "Block size", "Size in bytes to read per buffer",
                        1, G_MAXULONG, DEFAULT_BLOCKSIZE, G_PARAM_READWRITE));
  g_object_class_install_property (gobject_class, ARG_METADATA,
    g_param_spec_boxed ("metadata", "Metadata", "(logical) Stream metadata",
                         GST_TYPE_CAPS, G_PARAM_READABLE));
  g_object_class_install_property (gobject_class, ARG_STREAMINFO,
    g_param_spec_boxed ("streaminfo", "stream", "(logical) Stream information",
                         GST_TYPE_CAPS, G_PARAM_READABLE));

  gobject_class->get_property = gst_vorbisfile_get_property;
  gobject_class->set_property = gst_vorbisfile_set_property;

  gstelement_class->change_state = gst_vorbisfile_change_state;
}

static void
gst_vorbisfile_init (VorbisFile * vorbisfile)
{
  vorbisfile->sinkpad = gst_pad_new_from_template (gst_vorbisdec_sink_template,
		                                   "sink");
  gst_element_add_pad (GST_ELEMENT (vorbisfile), vorbisfile->sinkpad);
  gst_pad_set_formats_function (vorbisfile->sinkpad, gst_vorbisfile_get_formats);
  gst_pad_set_convert_function (vorbisfile->sinkpad, gst_vorbisfile_sink_convert);

  gst_element_set_loop_function (GST_ELEMENT (vorbisfile), gst_vorbisfile_loop);
  vorbisfile->srcpad = gst_pad_new_from_template (gst_vorbisdec_src_template, 
		                                  "src");
  gst_element_add_pad (GST_ELEMENT (vorbisfile), vorbisfile->srcpad);
  gst_pad_set_formats_function (vorbisfile->srcpad, gst_vorbisfile_get_formats);
  gst_pad_set_query_type_function (vorbisfile->srcpad, 
		                   gst_vorbisfile_get_query_types);
  gst_pad_set_query_function (vorbisfile->srcpad, gst_vorbisfile_src_query);
  gst_pad_set_event_mask_function (vorbisfile->srcpad, 
		                   gst_vorbisfile_get_event_masks);
  gst_pad_set_event_function (vorbisfile->srcpad, gst_vorbisfile_src_event);
  gst_pad_set_convert_function (vorbisfile->srcpad, gst_vorbisfile_src_convert);

  vorbisfile->total_bytes = 0;
  vorbisfile->offset = 0;
  vorbisfile->seek_pending = 0;
  vorbisfile->need_discont = FALSE;
  vorbisfile->metadata = NULL;
  vorbisfile->streaminfo = NULL;
  vorbisfile->current_link = -1;
  vorbisfile->blocksize = DEFAULT_BLOCKSIZE;

  GST_FLAG_SET (vorbisfile, GST_ELEMENT_EVENT_AWARE);
}

/* the next four functions are the ov callbacks we provide to vorbisfile
 * which interface between GStreamer's handling of the data flow and
 * vorbis's needs */
static size_t
gst_vorbisfile_read (void *ptr, size_t size, size_t nmemb, void *datasource)
{
  guint32 got_bytes;
  guint8 *data;
  guint32 read_size = size * nmemb;

  VorbisFile *vorbisfile = GST_VORBISFILE (datasource);

  GST_DEBUG ("read %d", read_size);

  /* make sure we don't go to EOS */
  if (!vorbisfile->may_eos && vorbisfile->total_bytes && 
       vorbisfile->offset + read_size > vorbisfile->total_bytes) 
  {
    read_size = vorbisfile->total_bytes - vorbisfile->offset;
  }

  if (read_size == 0 || vorbisfile->eos)
    return 0;
  
  do {
    got_bytes = gst_bytestream_peek_bytes (vorbisfile->bs, &data, read_size);

    GST_DEBUG ("peek returned  %d", got_bytes);

    if (got_bytes == 0) {
      GstEvent *event;
      guint32 avail;
    
      gst_bytestream_get_status (vorbisfile->bs, &avail, &event); 

      switch (GST_EVENT_TYPE (event)) {
	case GST_EVENT_EOS:
	  GST_DEBUG ("eos");
          vorbisfile->eos = TRUE;
          gst_event_unref (event);
          goto done;
	case GST_EVENT_DISCONTINUOUS:
	  GST_DEBUG ("discont");
	  vorbisfile->need_discont = TRUE;
	default:
	  GST_DEBUG ("unknown event %d", GST_EVENT_TYPE (event));
          break;
      }
      gst_event_unref (event);
    }
  } while (got_bytes == 0);

  GST_DEBUG ("read %d got %d bytes", read_size, got_bytes);

  memcpy (ptr, data, got_bytes);
  gst_bytestream_flush_fast (vorbisfile->bs, got_bytes);

  vorbisfile->offset += got_bytes;

done:
  return got_bytes / size;
}

static int
gst_vorbisfile_seek (void *datasource, int64_t offset, int whence)
{
  VorbisFile *vorbisfile = GST_VORBISFILE (datasource);
  GstSeekType method;
  guint64 pending_offset = vorbisfile->offset;
  gboolean need_total = FALSE;

  if (!vorbisfile->vf.seekable) {
    return -1;
  }
  
  GST_DEBUG ("seek %" G_GINT64_FORMAT " %d", offset, whence);

  if (whence == SEEK_SET) {
    method = GST_SEEK_METHOD_SET;
    pending_offset = offset;
  }
  else if (whence == SEEK_CUR) {
    method = GST_SEEK_METHOD_CUR;
    pending_offset += offset;
  }
  else if (whence == SEEK_END) {
    method = GST_SEEK_METHOD_END;
    need_total = TRUE;
    pending_offset = vorbisfile->total_bytes - offset;
  }
  else 
    return -1;
  
  if (!gst_bytestream_seek (vorbisfile->bs, offset, method))
    return -1;

  vorbisfile->eos = FALSE;
  vorbisfile->offset = pending_offset;
  if (need_total)
    vorbisfile->total_bytes = gst_bytestream_tell (vorbisfile->bs) + offset;

  return 0;
}

static int
gst_vorbisfile_close (void *datasource)
{
  GST_DEBUG ("close");
  return 0;
}

static long
gst_vorbisfile_tell (void *datasource)
{
  VorbisFile *vorbisfile = GST_VORBISFILE (datasource);
  long result;

  result = gst_bytestream_tell (vorbisfile->bs);

  GST_DEBUG ("tell %ld", result);

  return result;
}

ov_callbacks vorbisfile_ov_callbacks = 
{
  gst_vorbisfile_read,
  gst_vorbisfile_seek,
  gst_vorbisfile_close,
  gst_vorbisfile_tell,
};

/* retrieve the comment field (or tags) and put in metadata GstCaps
 * returns TRUE if caps could be set,
 * FALSE if they couldn't be read somehow */
static gboolean
gst_vorbisfile_update_metadata (VorbisFile *vorbisfile, gint link)
{
  OggVorbis_File *vf = &vorbisfile->vf;
  gchar **ptr;
  vorbis_comment *vc;
  GstProps *props = NULL;
  GstPropsEntry *entry;
  gchar *name, *value;

  /* clear old one */
  if (vorbisfile->metadata) {
    gst_caps_unref (vorbisfile->metadata);
    vorbisfile->metadata = NULL;
  }

  /* create props to hold the key/value pairs */
  props = gst_props_empty_new ();

  vc = ov_comment (vf, link);
  ptr = vc->user_comments;
  while (*ptr) {
    value = strstr (*ptr, "=");
    if (value) {
      name = g_strndup (*ptr, value-*ptr);
      entry = gst_props_entry_new (name, GST_PROPS_STRING_TYPE, value+1);
      gst_props_add_entry (props, (GstPropsEntry *) entry);
    }
    ptr++;
  }
  vorbisfile->metadata = gst_caps_new ("vorbisfile_metadata",
		                       "application/x-gst-metadata",
		                       props);

  g_object_notify (G_OBJECT (vorbisfile), "metadata");

  return TRUE;
}

/* retrieve logical stream properties and put them in streaminfo GstCaps
 * returns TRUE if caps could be set,
 * FALSE if they couldn't be read somehow */
static gboolean
gst_vorbisfile_update_streaminfo (VorbisFile *vorbisfile, gint link)
{
  OggVorbis_File *vf = &vorbisfile->vf;
  vorbis_info *vi;
  GstProps *props = NULL;
  GstPropsEntry *entry;

  /* clear old one */
  if (vorbisfile->streaminfo) {
    gst_caps_unref (vorbisfile->streaminfo);
    vorbisfile->streaminfo = NULL;
  }

  /* create props to hold the key/value pairs */
  props = gst_props_empty_new ();

  vi = ov_info (vf, link);
  entry = gst_props_entry_new ("version", GST_PROPS_INT_TYPE, vi->version);
  gst_props_add_entry (props, (GstPropsEntry *) entry);
  entry = gst_props_entry_new ("bitrate_upper", GST_PROPS_INT_TYPE, 
		               vi->bitrate_upper);
  gst_props_add_entry (props, (GstPropsEntry *) entry);
  entry = gst_props_entry_new ("bitrate_nominal", GST_PROPS_INT_TYPE, 
		               vi->bitrate_nominal);
  gst_props_add_entry (props, (GstPropsEntry *) entry);
  entry = gst_props_entry_new ("bitrate_lower", GST_PROPS_INT_TYPE, 
		               vi->bitrate_lower);
  gst_props_add_entry (props, (GstPropsEntry *) entry);
  entry = gst_props_entry_new ("serial", GST_PROPS_INT_TYPE, 
		               ov_serialnumber (vf, link));
  gst_props_add_entry (props, (GstPropsEntry *) entry);
  entry = gst_props_entry_new ("bitrate", GST_PROPS_INT_TYPE, 
		               ov_bitrate (vf, link));
  gst_props_add_entry (props, (GstPropsEntry *) entry);

  vorbisfile->streaminfo = gst_caps_new ("vorbisfile_streaminfo",
		                         "application/x-gst-streaminfo",
		                         props);

  g_object_notify (G_OBJECT (vorbisfile), "streaminfo");

  return TRUE;
}

static gboolean
gst_vorbisfile_new_link (VorbisFile *vorbisfile, gint link)
{
  vorbis_info *vi = ov_info (&vorbisfile->vf, link);
  GstCaps *caps;
  gboolean res = TRUE;

  /* new logical bitstream */
  vorbisfile->current_link = link;

  gst_vorbisfile_update_metadata (vorbisfile, link);
  gst_vorbisfile_update_streaminfo (vorbisfile, link);
      
  caps = GST_CAPS_NEW ("vorbisdec_src",
                       "audio/x-raw-int",    
                         "endianness", GST_PROPS_INT (G_BYTE_ORDER),
                         "signed",     GST_PROPS_BOOLEAN (TRUE),
                         "width",      GST_PROPS_INT (16),
                         "depth",      GST_PROPS_INT (16),
                         "rate",       GST_PROPS_INT (vi->rate),
                         "channels",   GST_PROPS_INT (vi->channels));
  gst_caps_ref (caps);

  if (gst_pad_try_set_caps (vorbisfile->srcpad, caps) <= 0) {
    if (!gst_pad_recover_caps_error (vorbisfile->srcpad, caps))
      res = FALSE;
  }

  gst_caps_unref (caps);

  return res;
}

static void
gst_vorbisfile_loop (GstElement *element)
{
  VorbisFile *vorbisfile = GST_VORBISFILE (element);
  GstBuffer *outbuf;
  long ret;
  gint link;

  /* this function needs to go first since you don't want to be messing
   * with an unset vf ;) */
  if (vorbisfile->restart) {
    vorbisfile->offset = 0;
    vorbisfile->total_bytes = 0;
    vorbisfile->may_eos = FALSE;
    vorbisfile->vf.seekable = gst_bytestream_seek (vorbisfile->bs, 0, 
		                                   GST_SEEK_METHOD_SET);
    GST_DEBUG ("vorbisfile: seekable: %s",
	       vorbisfile->vf.seekable ? "yes" : "no");

    /* open our custom vorbisfile data object with the callbacks we provide */
    if (ov_open_callbacks (vorbisfile, &vorbisfile->vf, NULL, 0, 
			   vorbisfile_ov_callbacks) < 0) {
      gst_element_error (element, "this is not a vorbis file");
      return;
    }
    vorbisfile->need_discont = TRUE;
    vorbisfile->restart = FALSE;
    vorbisfile->current_link = -1;
  }

  if (vorbisfile->seek_pending) {
    /* get time to seek to in seconds */
    switch (vorbisfile->seek_format) {
      case GST_FORMAT_TIME:
      {
        gdouble seek_to = (gdouble) vorbisfile->seek_value / GST_SECOND;

	if (vorbisfile->seek_accurate) {
          if (ov_time_seek (&vorbisfile->vf, seek_to) == 0) {
            vorbisfile->need_discont = TRUE;
          }
        }
	else {
          if (ov_time_seek_page (&vorbisfile->vf, seek_to) == 0) {
            vorbisfile->need_discont = TRUE;
          }
	}
	break;
      }
      case GST_FORMAT_DEFAULT:
	if (vorbisfile->seek_accurate) {
          if (ov_pcm_seek (&vorbisfile->vf, vorbisfile->seek_value) == 0) {
            vorbisfile->need_discont = TRUE;
          }
        }
	else {
          if (ov_pcm_seek_page (&vorbisfile->vf, vorbisfile->seek_value) == 0) {
            vorbisfile->need_discont = TRUE;
          }
	}
	break;
      default:
	if (vorbisfile->seek_format == logical_stream_format) {
          gint64 seek_to;
	  
	  seek_to = vorbisfile->vf.offsets[vorbisfile->seek_value];

          if (ov_raw_seek (&vorbisfile->vf, seek_to) == 0) {
            vorbisfile->need_discont = TRUE;
            vorbisfile->current_link = -1;
          }
	  else {
	    g_warning ("raw seek failed");
	  }
	}
	else
	  g_warning ("unknown seek method, implement me !");
	break;
    }
    vorbisfile->seek_pending = FALSE;
  }

  /* we update the caps for each logical stream */
  if (vorbisfile->vf.current_link != vorbisfile->current_link) {
    if (!gst_vorbisfile_new_link (vorbisfile, vorbisfile->vf.current_link)) {
      return;
    }
    return;
  }

  outbuf = gst_buffer_new_and_alloc (vorbisfile->blocksize);

  ret = ov_read (&vorbisfile->vf, 
		 GST_BUFFER_DATA (outbuf), GST_BUFFER_SIZE (outbuf), 
		 (G_BYTE_ORDER == G_LITTLE_ENDIAN ? 0 : 1), 
		 sizeof (gint16), 1, &link);

  if (ret == 0) {
    GST_DEBUG ("eos");
    /* send EOS event */
    /*ov_clear (&vorbisfile->vf);*/
    vorbisfile->restart = TRUE;
    gst_buffer_unref (outbuf);
    /* if the pad is not usable, don't push it out */
    if (GST_PAD_IS_USABLE (vorbisfile->srcpad)) {
      gst_pad_push (vorbisfile->srcpad, 
		    GST_DATA (gst_event_new (GST_EVENT_EOS)));
    }
    gst_element_set_eos (element);
    return;
  }
  else if (ret < 0) {
    g_warning ("vorbisfile: decoding error");
    gst_buffer_unref (outbuf);
    return;
  }
  else {
    GstClockTime time;
    gint64 samples;
    
   /* get stream stats */
    samples = (gint64) (ov_pcm_tell (&vorbisfile->vf));
    time = (GstClockTime) (ov_time_tell (&vorbisfile->vf) * GST_SECOND);

    if (vorbisfile->need_discont) {
      GstEvent *discont;

      vorbisfile->need_discont = FALSE;

      /* if the pad is not usable, don't push it out */
      if (GST_PAD_IS_USABLE (vorbisfile->srcpad)) {

        discont = gst_event_new_discontinuous (FALSE, GST_FORMAT_TIME, time, 
		    			     GST_FORMAT_DEFAULT, samples, NULL); 

        gst_pad_push (vorbisfile->srcpad, GST_DATA (discont));
      }
    }

    GST_BUFFER_SIZE (outbuf) = ret;
    GST_BUFFER_TIMESTAMP (outbuf) = time;
    GST_BUFFER_OFFSET (outbuf) = samples;
    {
      GstClockTime duration;
      GstFormat format;

      format = GST_FORMAT_TIME;
      gst_pad_convert (vorbisfile->srcpad,
			     GST_FORMAT_BYTES, ret,
			     &format, &duration);
      GST_BUFFER_DURATION (outbuf) = duration;
    }

    vorbisfile->may_eos = TRUE;

    if (!vorbisfile->vf.seekable) {
      vorbisfile->total_bytes += ret;
    }
  
    if (GST_PAD_IS_USABLE (vorbisfile->srcpad)) 
      gst_pad_push (vorbisfile->srcpad, GST_DATA (outbuf));
    else
      gst_buffer_unref (outbuf);
  }
}

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

  src_formats[3] = logical_stream_format;
  sink_formats[2] = logical_stream_format;
  
  return (GST_PAD_IS_SRC (pad) ? src_formats : sink_formats);
}

static gboolean
gst_vorbisfile_src_convert (GstPad *pad, 
		            GstFormat src_format, gint64 src_value,
		            GstFormat *dest_format, gint64 *dest_value)
{
  gboolean res = TRUE;
  guint scale = 1;
  gint bytes_per_sample;
  VorbisFile *vorbisfile; 
  vorbis_info *vi;
  
  vorbisfile = GST_VORBISFILE (gst_pad_get_parent (pad));

  vi = ov_info (&vorbisfile->vf, -1);
  bytes_per_sample = vi->channels * 2;

  switch (src_format) {
    case GST_FORMAT_BYTES:
      switch (*dest_format) {
        case GST_FORMAT_DEFAULT:
          *dest_value = src_value / (vi->channels * 2);
          break;
        case GST_FORMAT_TIME:
        {
          gint byterate = bytes_per_sample * vi->rate;

          if (byterate == 0)
            return FALSE;
          *dest_value = src_value * GST_SECOND / byterate;
          break;
        }
        default:
          res = FALSE;
      }
    case GST_FORMAT_DEFAULT:
      switch (*dest_format) {
        case GST_FORMAT_BYTES:
	  *dest_value = src_value * bytes_per_sample;
          break;
        case GST_FORMAT_TIME:
	  if (vi->rate == 0)
	    return FALSE;
	  *dest_value = src_value * GST_SECOND / vi->rate;
          break;
        default:
          res = FALSE;
      }
      break;
    case GST_FORMAT_TIME:
      switch (*dest_format) {
        case GST_FORMAT_BYTES:
	  scale = bytes_per_sample;
        case GST_FORMAT_DEFAULT:
	  *dest_value = src_value * scale * vi->rate / GST_SECOND;
          break;
        default:
          res = FALSE;
      }
      break;
    default:
      if (src_format == logical_stream_format) {
	/* because we need to convert relative from 0, we have to add
	 * all pcm totals */
	gint i;
	gint64 count = 0;

        switch (*dest_format) {
          case GST_FORMAT_BYTES:
            res = FALSE;
            break;
          case GST_FORMAT_DEFAULT:
	    if (src_value > vorbisfile->vf.links) {
	      src_value = vorbisfile->vf.links;
	    }
	    for (i = 0; i < src_value; i++) {
	      vi = ov_info (&vorbisfile->vf, i);

	      count += ov_pcm_total (&vorbisfile->vf, i);
	    }
	    *dest_value = count;
            break;
          case GST_FORMAT_TIME:
	  {
	    if (src_value > vorbisfile->vf.links) {
	      src_value = vorbisfile->vf.links;
	    }
	    for (i = 0; i < src_value; i++) {
	      vi = ov_info (&vorbisfile->vf, i);
	      if (vi->rate) 
	        count += ov_pcm_total (&vorbisfile->vf, i) * GST_SECOND / vi->rate;
	      else
	        count += ov_time_total (&vorbisfile->vf, i) * GST_SECOND;
	    }
	    /* we use the pcm totals to get the total time, it's more accurate */
	    *dest_value = count;
            break;
	  }
          default:
            res = FALSE;
	}
      }
      else
        res = FALSE;
      break;
  }
  return res;
}

static gboolean
gst_vorbisfile_sink_convert (GstPad *pad, 
		             GstFormat src_format, gint64 src_value,
		             GstFormat *dest_format, gint64 *dest_value)
{
  gboolean res = TRUE;
  VorbisFile *vorbisfile; 
  
  vorbisfile = GST_VORBISFILE (gst_pad_get_parent (pad));

  switch (src_format) {
    case GST_FORMAT_BYTES:
      switch (*dest_format) {
        case GST_FORMAT_TIME:
          break;
        default:
          if (*dest_format == logical_stream_format) {
          }
	  else
            res = FALSE;
      }
    case GST_FORMAT_TIME:
      switch (*dest_format) {
        case GST_FORMAT_BYTES:
          break;
        default:
          if (*dest_format == logical_stream_format) {
          }
	  else
            res = FALSE;
      }
    default:
      if (src_format == logical_stream_format) {
        switch (*dest_format) {
          case GST_FORMAT_TIME:
            break;
          case GST_FORMAT_BYTES:
            break;
          default:
            res = FALSE;
        }
      }
      else
        res = FALSE;
      break;
  }

  return res;
}

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

/* handles queries for location in the stream in the requested format */
static gboolean
gst_vorbisfile_src_query (GstPad *pad, GstQueryType type,
		          GstFormat *format, gint64 *value)
{
  gboolean res = TRUE;
  VorbisFile *vorbisfile; 
  vorbis_info *vi;
  
  vorbisfile = GST_VORBISFILE (gst_pad_get_parent (pad));

  vi = ov_info (&vorbisfile->vf, -1);

  switch (type) {
    case GST_QUERY_TOTAL:
    {
      switch (*format) {
        case GST_FORMAT_DEFAULT:
          if (vorbisfile->vf.seekable)
	    *value = ov_pcm_total (&vorbisfile->vf, -1);
	  else
	    return FALSE;
	  break;
        case GST_FORMAT_BYTES:
          if (vorbisfile->vf.seekable)
	    *value = ov_pcm_total (&vorbisfile->vf, -1) * vi->channels * 2;
	  else
	    return FALSE;
	  break;
        case GST_FORMAT_TIME:
          if (vorbisfile->vf.seekable)
	    *value = (gint64) (ov_time_total (&vorbisfile->vf, -1) * GST_SECOND);
	  else
	    return FALSE;
	  break;
	default:
	  if (*format == logical_stream_format) {
            if (vorbisfile->vf.seekable)
	      *value = vorbisfile->vf.links;
	    else
	     return FALSE;
	  }
	  else
            res = FALSE;
          break;
      }
      break;
    }
    case GST_QUERY_POSITION:
      switch (*format) {
        case GST_FORMAT_TIME:
          if (vorbisfile->vf.seekable)
	    *value = (gint64) (ov_time_tell (&vorbisfile->vf) * GST_SECOND);
	  else
            *value = vorbisfile->total_bytes * GST_SECOND 
		                             / (vi->rate * vi->channels * 2);
	  break;
        case GST_FORMAT_BYTES:
          if (vorbisfile->vf.seekable)
	    *value = ov_pcm_tell (&vorbisfile->vf) * vi->channels * 2;
	  else
            *value = vorbisfile->total_bytes;
	  break;
        case GST_FORMAT_DEFAULT:
          if (vorbisfile->vf.seekable)
	    *value = ov_pcm_tell (&vorbisfile->vf);
	  else
            *value = vorbisfile->total_bytes / (vi->channels * 2);
	  break;
        default:
	  if (*format == logical_stream_format) {
            if (vorbisfile->vf.seekable)
	      *value = vorbisfile->current_link;
	    else
	     return FALSE;
	  }
	  else
            res = FALSE;
          break;
      }
      break;
    default:
      res = FALSE;
      break;
  }

  return res;
}

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

/* handle events on src pad */
static gboolean
gst_vorbisfile_src_event (GstPad *pad, GstEvent *event)
{
  gboolean res = TRUE;
  VorbisFile *vorbisfile; 
		  
  vorbisfile = GST_VORBISFILE (gst_pad_get_parent (pad));

  switch (GST_EVENT_TYPE (event)) {
    case GST_EVENT_SEEK:
    {
      gint64 offset;
      vorbis_info *vi;
      GstFormat format;
  
      GST_DEBUG ("vorbisfile: handling seek event on pad %s:%s",
		 GST_DEBUG_PAD_NAME (pad));
      if (!vorbisfile->vf.seekable) {
	gst_event_unref (event);
	GST_DEBUG ("vorbis stream is not seekable");
        return FALSE;
      }

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

      switch (format) {
	case GST_FORMAT_TIME:
	  vorbisfile->seek_pending = TRUE;
	  vorbisfile->seek_value = offset;
	  vorbisfile->seek_format = format;
	  vorbisfile->seek_accurate = GST_EVENT_SEEK_FLAGS (event) 
		                    & GST_SEEK_FLAG_ACCURATE;
	  break;
	case GST_FORMAT_BYTES:
          vi = ov_info (&vorbisfile->vf, -1);
	  if (vi->channels == 0) {
	    GST_DEBUG ("vorbis stream has 0 channels ?");
	    res = FALSE;
	    goto done; 
	  }
          offset /= vi->channels * 2;
	  /* fallthrough */
	case GST_FORMAT_DEFAULT:
	  vorbisfile->seek_pending = TRUE;
	  vorbisfile->seek_value = offset;
	  vorbisfile->seek_format = format;
	  vorbisfile->seek_accurate = GST_EVENT_SEEK_FLAGS (event) 
		                    & GST_SEEK_FLAG_ACCURATE;
	  break;
	default:
	  if (format == logical_stream_format) {
	    vorbisfile->seek_pending = TRUE;
	    vorbisfile->seek_value = offset;
	    vorbisfile->seek_format = format;
	    vorbisfile->seek_accurate = GST_EVENT_SEEK_FLAGS (event) 
		                      & GST_SEEK_FLAG_ACCURATE;
	  }
	  else
	  {
	    GST_DEBUG ("unhandled seek format");
	    res = FALSE;
	  }
	  break;
      }
      break;
    }
    default:
      res = FALSE;
      break;
  }

done:
  gst_event_unref (event);
  return res;
}

static GstElementStateReturn
gst_vorbisfile_change_state (GstElement *element)
{
  VorbisFile *vorbisfile = GST_VORBISFILE (element);
  
  switch (GST_STATE_TRANSITION (element)) {
    case GST_STATE_NULL_TO_READY:
    case GST_STATE_READY_TO_PAUSED:
      vorbisfile->restart = TRUE;
      vorbisfile->bs = gst_bytestream_new (vorbisfile->sinkpad);
      break;
    case GST_STATE_PAUSED_TO_PLAYING:
      vorbisfile->eos = FALSE;
    case GST_STATE_PLAYING_TO_PAUSED:
      break;
    case GST_STATE_PAUSED_TO_READY:
      ov_clear (&vorbisfile->vf);
      gst_bytestream_destroy (vorbisfile->bs);
      break;
    case GST_STATE_READY_TO_NULL:
    default:
      break;
  }

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

static void
gst_vorbisfile_set_property (GObject *object, guint prop_id, 
		             const GValue *value, GParamSpec *pspec)
{
  VorbisFile *vorbisfile;
	      
  g_return_if_fail (GST_IS_VORBISFILE (object));

  vorbisfile = GST_VORBISFILE (object);

  switch (prop_id) {
    case ARG_BLOCKSIZE:
      vorbisfile->blocksize = g_value_get_ulong (value);
      break;
    default:
      g_warning ("Unknown property id\n");
  }
}

static void 
gst_vorbisfile_get_property (GObject *object, guint prop_id, 
		             GValue *value, GParamSpec *pspec)
{
  VorbisFile *vorbisfile;
	      
  g_return_if_fail (GST_IS_VORBISFILE (object));

  vorbisfile = GST_VORBISFILE (object);

  switch (prop_id) {
    case ARG_BLOCKSIZE:
      g_value_set_ulong (value, vorbisfile->blocksize);
      break;
    case ARG_METADATA:
      g_value_set_boxed (value, vorbisfile->metadata);
      break;
    case ARG_STREAMINFO:
      g_value_set_boxed (value, vorbisfile->streaminfo);
      break;
    default:
      g_warning ("Unknown property id\n");
  }
}