/* JOrbis
 * Copyright (C) 2000 ymnk, JCraft,Inc.
 *  
 * Written by: 2000 ymnk<ymnk@jcraft.com>
 *   
 * Many thanks to 
 *   Monty <monty@xiph.org> and 
 *   The XIPHOPHORUS Company http://www.xiph.org/ .
 * JOrbis has been based on their awesome works, Vorbis codec.
 *   
 * This program 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 program 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 program; if not, write to the Free Software
 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 */

package com.jcraft.jorbis;

import com.jcraft.jogg.*;

import java.io.InputStream;
import java.io.IOException;

public class VorbisFile{
  static final int CHUNKSIZE=8500;
  static final int SEEK_SET=0;
  static final int SEEK_CUR=1;
  static final int SEEK_END=2;

  static final int OV_FALSE=-1;
  static final int OV_EOF=-2;
  static final int OV_HOLE=-3;

  static final int OV_EREAD=-128;
  static final int OV_EFAULT=-129;
  static final int OV_EIMPL=-130;
  static final int OV_EINVAL=-131;
  static final int OV_ENOTVORBIS=-132;
  static final int OV_EBADHEADER=-133;
  static final int OV_EVERSION=-134;
  static final int OV_ENOTAUDIO=-135;
  static final int OV_EBADPACKET=-136;
  static final int OV_EBADLINK=-137;
  static final int OV_ENOSEEK=-138;

  InputStream datasource;
  boolean seekable=false;
  long offset;
  long end;
  
  SyncState oy=new SyncState();

  int links;
  long[] offsets;
  long[] dataoffsets;
  int[] serialnos;
  long[] pcmlengths;
  Info[] vi;
  Comment[] vc;

  // Decoding working state local storage
  long pcm_offset;
  boolean decode_ready=false;
  int current_serialno;
  int current_link;

  float bittrack;
  float samptrack;

  StreamState os=new StreamState(); // take physical pages, weld into a logical
                                    // stream of packets
  DspState vd=new DspState(); // central working state for 
                              // the packet->PCM decoder
  Block vb=new Block(vd);     // local working space for packet->PCM decode

  //ov_callbacks callbacks;

  public VorbisFile(String file) throws JOrbisException {
    super();
    InputStream is=null;
    try{ 
      is=new SeekableInputStream(file);
      int ret=open(is, null, 0);
      if(ret==-1){
        throw new JOrbisException("VorbisFile: open return -1");
      }
    }
    catch(Exception e){
      throw new JOrbisException("VorbisFile: "+e.toString());
    }
    finally{
      if(is != null){
        try {
          is.close();
        }
        catch (IOException e) {
          e.printStackTrace();
        }
      }
    }
  }

  public VorbisFile(InputStream is, byte[] initial, int ibytes)
    throws JOrbisException {
    super();
    int ret=open(is, initial, ibytes);
    if(ret==-1){
    }
  }

  private int get_data(){
    int index=oy.buffer(CHUNKSIZE);
    byte[] buffer=oy.data;
//  int bytes=callbacks.read_func(buffer, index, 1, CHUNKSIZE, datasource);
    int bytes=0;
    try{
      bytes=datasource.read(buffer, index, CHUNKSIZE);
    }
    catch(Exception e){
      //System.err.println(e);
      return OV_EREAD;
    }
    oy.wrote(bytes);
    if(bytes==-1){
//      System.out.println("bytes="+bytes);
      bytes=0;
    }
    return bytes;
  }

  private void seek_helper(long offst){
    //callbacks.seek_func(datasource, offst, SEEK_SET);
    fseek(datasource, offst, SEEK_SET);
    this.offset=offst;
    oy.reset();
  }

  private int get_next_page(Page page, long boundary){
    if(boundary>0) boundary+=offset;
    while(true){
      int more;
      if(boundary>0 && offset>=boundary)return OV_FALSE;
      more=oy.pageseek(page);
      if(more<0){offset-=more;}
      else{
	if(more==0){
	  if(boundary==0)return OV_FALSE;
//	  if(get_data()<=0)return -1;
          int ret=get_data();
          if(ret==0) return OV_EOF;
          if(ret<0) return OV_EREAD; 
	}
	else{
	  int ret=(int)offset; //!!!
	  offset+=more;
	  return ret;
	}
      }
    }
  }

  private int get_prev_page(Page page) throws JOrbisException {
    long begin=offset; //!!!
    int ret;
    int offst=-1;
    while(offst==-1){
      begin-=CHUNKSIZE;
      if(begin<0)
        begin=0;
      seek_helper(begin);
      while(offset<begin+CHUNKSIZE){
	ret=get_next_page(page, begin+CHUNKSIZE-offset);
	if(ret==OV_EREAD){ return OV_EREAD; }
	if(ret<0){ 
          if(offst == -1)
            throw new JOrbisException();
          break; 
        }
	else{ offst=ret; }
      }
    }
    seek_helper(offst); //!!!
    ret=get_next_page(page, CHUNKSIZE);
    if(ret<0){
      //System.err.println("Missed page fencepost at end of logical bitstream Exiting");
      //System.exit(1);
      return OV_EFAULT;
    }
    return offst;
  }

  int bisect_forward_serialno(long begin, long searched, long end, int currentno, int m){
    long endsearched=end;
    long next=end;
    Page page=new Page();
    int ret;

    while(searched<endsearched){
      long bisect;
      if(endsearched-searched<CHUNKSIZE){
	bisect=searched;
      }
      else{
	bisect=(searched+endsearched)/2;
      }

      seek_helper(bisect);
      ret=get_next_page(page, -1);
      if(ret==OV_EREAD) return OV_EREAD;
      if(ret<0 || page.serialno()!=currentno){
	endsearched=bisect;
	if(ret>=0)next=ret;
      }
      else{
	searched=ret+page.header_len+page.body_len;
      }
    }
    seek_helper(next);
    ret=get_next_page(page, -1);
    if(ret==OV_EREAD) return OV_EREAD;

    if(searched>=end || ret==-1){
      links=m+1;
      offsets=new long[m+2];
      offsets[m+1]=searched;
    }
    else{
      ret=bisect_forward_serialno(next, offset, end, page.serialno(), m+1);
      if(ret==OV_EREAD)return OV_EREAD;
    }
    offsets[m]=begin;
    return 0;  
  }

  // uses the local ogg_stream storage in vf; this is important for
  // non-streaming input sources
  int fetch_headers(Info vi, Comment vc, int[] serialno, Page og_ptr){
    //System.err.println("fetch_headers");
    Page og=new Page();
    Packet op=new Packet();
    int ret;

    if(og_ptr==null){
      ret=get_next_page(og, CHUNKSIZE);
      if(ret==OV_EREAD)return OV_EREAD;
      if(ret<0) return OV_ENOTVORBIS;
      og_ptr=og;
    }
  
    if(serialno!=null)serialno[0]=og_ptr.serialno();

    os.init(og_ptr.serialno());
  
    // extract the initial header from the first page and verify that the
    // Ogg bitstream is in fact Vorbis data
  
    vi.init();
    vc.init();
  
    int i=0;
    while(i<3){
      os.pagein(og_ptr);
      while(i<3){
	int result=os.packetout(op);
	if(result==0)break;
	if(result==-1){
	  //System.err.println("Corrupt header in logical bitstream.");
	  //goto bail_header;
	  vi.clear();
	  vc.clear();
	  os.clear();
	   return -1;
	}
	if(vi.synthesis_headerin(vc, op)!=0){
	  //System.err.println("Illegal header in logical bitstream.");
	  //goto bail_header;
	  vi.clear();
	  vc.clear();
	  os.clear();
	  return -1;
	}
	i++;
      }
      if(i<3)
	if(get_next_page(og_ptr, 1)<0){
	  //System.err.println("Missing header in logical bitstream.");
	  //goto bail_header;
	  vi.clear();
	  vc.clear();
	  os.clear();
	  return -1;
	}
    }
    return 0; 

//  bail_header:
//    vorbis_info_clear(vi);
//    vorbis_comment_clear(vc);
//    ogg_stream_clear(&vf->os);
//    return -1;
  }

  // last step of the OggVorbis_File initialization; get all the
  // vorbis_info structs and PCM positions.  Only called by the seekable
  // initialization (local stream storage is hacked slightly; pay
  // attention to how that's done)
  void prefetch_all_headers(Info first_i,Comment first_c,
                           int dataoffset) throws JOrbisException {
    Page og=new Page();
    int ret;
  
    vi=new Info[links];
    vc=new Comment[links];
    dataoffsets=new long[links];
    pcmlengths=new long[links];
    serialnos=new int[links];
  
    for(int i=0;i<links;i++){
      if(first_i!=null && first_c!=null && i==0){
	// we already grabbed the initial header earlier.  This just
	// saves the waste of grabbing it again
	// !!!!!!!!!!!!!
	vi[i]=first_i;
	//memcpy(vf->vi+i,first_i,sizeof(vorbis_info));
	vc[i]=first_c;
	//memcpy(vf->vc+i,first_c,sizeof(vorbis_comment));
	dataoffsets[i]=dataoffset;
      }
      else{
	// seek to the location of the initial header
	seek_helper(offsets[i]); //!!!
	vi[i]=new Info();
	vc[i]=new Comment();
	if(fetch_headers(vi[i], vc[i], null, null)==-1){
	  //System.err.println("Error opening logical bitstream #"+(i+1)+"\n");
	  dataoffsets[i]=-1;
	}
	else{
	  dataoffsets[i]=offset;
	  os.clear();
	}
      }

      // get the serial number and PCM length of this link. To do this,
      // get the last page of the stream
      {
	long end=offsets[i+1]; //!!!
	seek_helper(end);

	while(true){
	  ret=get_prev_page(og);
	  if(ret==-1){
	    // this should not be possible
	    //System.err.println("Could not find last page of logical "+
	    //		       "bitstream #"+(i)+"\n");
	    vi[i].clear();
	    vc[i].clear();
	    break;
	  }
	  if(og.granulepos()!=-1){
	    serialnos[i]=og.serialno();
	    pcmlengths[i]=og.granulepos();
	    break;
	  }
	}
      }
    }
  }

  int make_decode_ready(){
    if(decode_ready)System.exit(1);
    vd.synthesis_init(vi[0]);
    vb.init(vd);
    decode_ready=true;
    return(0);
  }

  int open_seekable() throws JOrbisException {
    Info initial_i=new Info();
    Comment initial_c=new Comment();
    int serialno;
    long end;
    int ret;
    int dataoffset;
    Page og=new Page();
    // is this even vorbis...?
    int[] foo=new int[1];
    ret=fetch_headers(initial_i, initial_c, foo, null);
    serialno=foo[0];
    dataoffset=(int)offset; //!!
    os.clear();
    if(ret==-1)return(-1);
    // we can seek, so set out learning all about this file
    seekable=true;
    //(callbacks.seek_func)(datasource, 0, SEEK_END);
    fseek(datasource, 0, SEEK_END);
    //offset=end=(callbacks.tell_func)(datasource);
    offset=ftell(datasource);
    end=offset;
    // We get the offset for the last page of the physical bitstream.
    // Most OggVorbis files will contain a single logical bitstream
    end=get_prev_page(og);
    // moer than one logical bitstream?
    if(og.serialno()!=serialno){
      // Chained bitstream. Bisect-search each logical bitstream
      // section.  Do so based on serial number only
      if(bisect_forward_serialno(0,0,end+1,serialno,0)<0){
        clear();
        return OV_EREAD;
      }
    }
    else{
      // Only one logical bitstream
      if(bisect_forward_serialno(0,end,end+1,serialno,0)<0){
        clear();
        return OV_EREAD;
      }
    }
    prefetch_all_headers(initial_i, initial_c, dataoffset);
    return(raw_seek(0));
  }

  int open_nonseekable(){
    //System.err.println("open_nonseekable");
    // we cannot seek. Set up a 'single' (current) logical bitstream entry
    links=1;
    vi=new Info[links]; vi[0]=new Info(); // ??
    vc=new Comment[links]; vc[0]=new Comment(); // ?? bug?

    // Try to fetch the headers, maintaining all the storage
    int[]foo=new int[1];
    if(fetch_headers(vi[0], vc[0], foo, null)==-1)return(-1);
    current_serialno=foo[0];
    make_decode_ready();
    return 0;
  }

  // clear out the current logical bitstream decoder
  void decode_clear(){
    os.clear();
    vd.clear();
    vb.clear();
    decode_ready=false;
    bittrack=0.f;
    samptrack=0.f;
  }

  // fetch and process a packet.  Handles the case where we're at a
  // bitstream boundary and dumps the decoding machine.  If the decoding
  // machine is unloaded, it loads it.  It also keeps pcm_offset up to
  // date (seek and read both use this.  seek uses a special hack with
  // readp). 
  //
  // return: -1) hole in the data (lost packet) 
  //          0) need more date (only if readp==0)/eof
  //          1) got a packet 

  int process_packet(int readp){
    Page og=new Page();

    // handle one packet.  Try to fetch it from current stream state
    // extract packets from page
    while(true){
      // process a packet if we can.  If the machine isn't loaded,
      // neither is a page
      if(decode_ready){
	Packet op=new Packet();
	int result=os.packetout(op);
	long granulepos;
        // if(result==-1)return(-1); // hole in the data. For now, swallow
	                             // and go. We'll need to add a real
	                             // error code in a bit.
	if(result>0){
	  // got a packet.  process it
	  granulepos=op.granulepos;
	  if(vb.synthesis(op)==0){ // lazy check for lazy
                                   // header handling.  The
	                           // header packets aren't
	                           // audio, so if/when we
	                           // submit them,
	                           // vorbis_synthesis will
                                   // reject them
	    // suck in the synthesis data and track bitrate
	    {
	      int oldsamples=vd.synthesis_pcmout(null, null);
	      vd.synthesis_blockin(vb);
	      samptrack+=vd.synthesis_pcmout(null, null)-oldsamples;
	      bittrack+=op.bytes*8;
	    }
	  
	    // update the pcm offset.
	    if(granulepos!=-1 && op.e_o_s==0){
	      int link=(seekable?current_link:0);
	      int samples;
	      // this packet has a pcm_offset on it (the last packet
	      // completed on a page carries the offset) After processing
	      // (above), we know the pcm position of the *last* sample
	      // ready to be returned. Find the offset of the *first*
	      // 
	      // As an aside, this trick is inaccurate if we begin
	      // reading anew right at the last page; the end-of-stream
	      // granulepos declares the last frame in the stream, and the
	      // last packet of the last page may be a partial frame.
	      // So, we need a previous granulepos from an in-sequence page
	      // to have a reference point.  Thus the !op.e_o_s clause above
	    
	      samples=vd.synthesis_pcmout(null, null);
	      granulepos-=samples;
	      for(int i=0;i<link;i++){
		granulepos+=pcmlengths[i];
	      }
	      pcm_offset=granulepos;
	    }
	    return(1);
	  }
	}
      }

      if(readp==0)return(0);
      if(get_next_page(og,-1)<0)return(0); // eof. leave unitialized

      // bitrate tracking; add the header's bytes here, the body bytes
      // are done by packet above
      bittrack+=og.header_len*8;

      // has our decoding just traversed a bitstream boundary?
      if(decode_ready){
	if(current_serialno!=og.serialno()){
	  decode_clear();
	}
      }

      // Do we need to load a new machine before submitting the page?
      // This is different in the seekable and non-seekable cases.  
      // 
      // In the seekable case, we already have all the header
      // information loaded and cached; we just initialize the machine
      // with it and continue on our merry way.
      // 
      // In the non-seekable (streaming) case, we'll only be at a
      // boundary if we just left the previous logical bitstream and
      // we're now nominally at the header of the next bitstream

      if(!decode_ready){
	int i;
	if(seekable){
	  current_serialno=og.serialno();
	
	  // match the serialno to bitstream section.  We use this rather than
	  // offset positions to avoid problems near logical bitstream
	  // boundaries
	  for(i=0;i<links;i++){
	    if(serialnos[i]==current_serialno)break;
	  }
	  if(i==links)return(-1); // sign of a bogus stream.  error out,
	                          // leave machine uninitialized
	  current_link=i;

	  os.init(current_serialno);
	  os.reset(); 

	}
	else{
	  // we're streaming
	  // fetch the three header packets, build the info struct
	  int foo[]=new int[1];
	  int ret=fetch_headers(vi[0], vc[0], foo, og);
          current_serialno=foo[0];
          if(ret!=0)return ret;
	  current_link++;
	  i=0;
	}
	make_decode_ready();
      }
      os.pagein(og);
    }
  }

  //The helpers are over; it's all toplevel interface from here on out
  // clear out the OggVorbis_File struct
  int clear(){
    vb.clear();
    vd.clear();
    os.clear();
    
    if(vi!=null && links!=0){
      for(int i=0;i<links;i++){
	vi[i].clear();
	vc[i].clear();
      }
      vi=null;
      vc=null;
    }
    if(dataoffsets!=null)dataoffsets=null;
    if(pcmlengths!=null)pcmlengths=null;
    if(serialnos!=null)serialnos=null;
    if(offsets!=null)offsets=null;
    oy.clear();
    //if(datasource!=null)(vf->callbacks.close_func)(vf->datasource);
    //memset(vf,0,sizeof(OggVorbis_File));
    return(0);
  }

  static int fseek(InputStream fis,
			  //int64_t off,
			  long off,
			  int whence){
    if(fis instanceof SeekableInputStream){
      SeekableInputStream sis=(SeekableInputStream)fis;
      try{
      if(whence==SEEK_SET){
        sis.seek(off);
      }
      else if(whence==SEEK_END){
        sis.seek(sis.getLength()-off);
      }
      else{
	//System.out.println("seek: "+whence+" is not supported");
      }
      }
      catch(Exception e){
      }
      return 0;
    }
    try{
      if(whence==0){ fis.reset(); }
      fis.skip(off);
    }
    catch(Exception e){return -1;}
    return 0;
  }

  static long ftell(InputStream fis){
    try{
    if(fis instanceof SeekableInputStream){
      SeekableInputStream sis=(SeekableInputStream)fis;
      return (sis.tell());
    }
    }
    catch(Exception e){
    }
    return 0;
  }

  // inspects the OggVorbis file and finds/documents all the logical
  // bitstreams contained in it.  Tries to be tolerant of logical
  // bitstream sections that are truncated/woogie. 
  //
  // return: -1) error
  //          0) OK

  int open(InputStream is, byte[] initial, int ibytes) throws JOrbisException {
    //callbacks callbacks = {
    // (size_t (*)(void *, size_t, size_t, void *))  fread,
    // (int (*)(void *, int64_t, int))              _fseek,
    // (int (*)(void *))                             fclose,
    // (long (*)(void *))                            ftell
    // };
    return open_callbacks(is, initial, ibytes//, callbacks
			     );
  }

  int open_callbacks(InputStream is, byte[] initial, 
			int ibytes//, callbacks callbacks
			) throws JOrbisException {
    int ret;
    datasource=is;
    //callbacks = _callbacks;
    // init the framing state
    oy.init();

    // perhaps some data was previously read into a buffer for testing
    // against other stream types.  Allow initialization from this
    // previously read data (as we may be reading from a non-seekable
    // stream)
    if(initial!=null){
      int index=oy.buffer(ibytes);
      System.arraycopy(initial, 0, oy.data, index, ibytes);
      oy.wrote(ibytes);
    }
    // can we seek? Stevens suggests the seek test was portable
    if(is instanceof SeekableInputStream){ ret=open_seekable(); }
    else{ ret=open_nonseekable(); }
    if(ret!=0){
      datasource=null;
      clear();
    }
    return ret;
  }

  // How many logical bitstreams in this physical bitstream?
  public int streams(){
    return links;
  }

  // Is the FILE * associated with vf seekable?
  public boolean seekable(){
    return seekable;
  }

  // returns the bitrate for a given logical bitstream or the entire
  // physical bitstream.  If the file is open for random access, it will
  // find the *actual* average bitrate.  If the file is streaming, it
  // returns the nominal bitrate (if set) else the average of the
  // upper/lower bounds (if set) else -1 (unset).
  // 
  // If you want the actual bitrate field settings, get them from the
  // vorbis_info structs

  public int bitrate(int i){
    if(i>=links)return(-1);
    if(!seekable && i!=0)return(bitrate(0));
    if(i<0){
      long bits=0;
      for(int j=0;j<links;j++){
	bits+=(offsets[j+1]-dataoffsets[j])*8;
      }
      return((int)Math.rint(bits/time_total(-1)));
    }
    else{
      if(seekable){
	// return the actual bitrate
	return((int)Math.rint((offsets[i+1]-dataoffsets[i])*8/time_total(i)));
      }
      else{
	// return nominal if set
	if(vi[i].bitrate_nominal>0){
	  return vi[i].bitrate_nominal;
	}
	else{
	  if(vi[i].bitrate_upper>0){
	    if(vi[i].bitrate_lower>0){
	      return (vi[i].bitrate_upper+vi[i].bitrate_lower)/2;
	    }else{
	      return vi[i].bitrate_upper;
	    }
	  }
	  return(-1);
	}
      }
    }
  }

  // returns the actual bitrate since last call.  returns -1 if no
  // additional data to offer since last call (or at beginning of stream)
  public int bitrate_instant(){
    int _link=(seekable?current_link:0);
    if(samptrack==0)return(-1);
    int ret=(int)(bittrack/samptrack*vi[_link].rate+.5);
    bittrack=0.f;
    samptrack=0.f;
    return(ret);
  }

  public int serialnumber(int i){
    if(i>=links)return(-1);
    if(!seekable && i>=0)return(serialnumber(-1));
    if(i<0){
      return(current_serialno);
    }
    else{
      return(serialnos[i]);
    }
  }

  // returns: total raw (compressed) length of content if i==-1
  //          raw (compressed) length of that logical bitstream for i==0 to n
  //          -1 if the stream is not seekable (we can't know the length)

  public long raw_total(int i){
    if(!seekable || i>=links)return(-1);
    if(i<0){
      long acc=0;               // bug?
      for(int j=0;j<links;j++){
	acc+=raw_total(j);
      }
      return(acc);
    }
    else{
      return(offsets[i+1]-offsets[i]);
    }
  }

  // returns: total PCM length (samples) of content if i==-1
  //          PCM length (samples) of that logical bitstream for i==0 to n
  //          -1 if the stream is not seekable (we can't know the length)
  public long pcm_total(int i){
    if(!seekable || i>=links)return(-1);
    if(i<0){
      long acc=0;
      for(int j=0;j<links;j++){
	acc+=pcm_total(j);
      }
      return(acc);
    }
    else{
      return(pcmlengths[i]);
    }
  }

  // returns: total seconds of content if i==-1
  //          seconds in that logical bitstream for i==0 to n
  //          -1 if the stream is not seekable (we can't know the length)
  public float time_total(int i){
    if(!seekable || i>=links)return(-1);
    if(i<0){
      float acc=0;
      for(int j=0;j<links;j++){
	acc+=time_total(j);
      }
      return(acc);
    }
    else{
      return((float)(pcmlengths[i])/vi[i].rate);
    }
  }

  // seek to an offset relative to the *compressed* data. This also
  // immediately sucks in and decodes pages to update the PCM cursor. It
  // will cross a logical bitstream boundary, but only if it can't get
  // any packets out of the tail of the bitstream we seek to (so no
  // surprises). 
  // 
  // returns zero on success, nonzero on failure

  public int raw_seek(int pos){
    if(!seekable)return(-1); // don't dump machine if we can't seek
    if(pos<0 || pos>offsets[links]){
      //goto seek_error;
      pcm_offset=-1;
      decode_clear();
      return -1;
    }

    // clear out decoding machine state
    pcm_offset=-1;
    decode_clear();

    // seek
    seek_helper(pos);

    // we need to make sure the pcm_offset is set.  We use the
    // _fetch_packet helper to process one packet with readp set, then
    // call it until it returns '0' with readp not set (the last packet
    // from a page has the 'granulepos' field set, and that's how the
    // helper updates the offset

    switch(process_packet(1)){
    case 0:
      // oh, eof. There are no packets remaining.  Set the pcm offset to
      // the end of file
      pcm_offset=pcm_total(-1);
      return(0);
    case -1:
      // error! missing data or invalid bitstream structure
      //goto seek_error;
      pcm_offset=-1;
      decode_clear();
      return -1;
    default:
      // all OK
      break;
    }
    while(true){
      switch(process_packet(0)){
      case 0:
	// the offset is set.  If it's a bogus bitstream with no offset
	// information, it's not but that's not our fault.  We still run
	// gracefully, we're just missing the offset
	return(0);
      case -1:
	// error! missing data or invalid bitstream structure
	//goto seek_error;
	pcm_offset=-1;
	decode_clear();
	return -1;
      default:
	// continue processing packets
	break;
      }
    }
  
  // seek_error:
    // dump the machine so we're in a known state
    //pcm_offset=-1;
    //decode_clear();
    //return -1;
  }

  // seek to a sample offset relative to the decompressed pcm stream 
  // returns zero on success, nonzero on failure

  public int pcm_seek(long pos){
    int link=-1;
    long total=pcm_total(-1);

    if(!seekable)return(-1); // don't dump machine if we can't seek
    if(pos<0 || pos>total){
      //goto seek_error;
      pcm_offset=-1;
      decode_clear();
      return -1;
    }

    // which bitstream section does this pcm offset occur in?
    for(link=links-1;link>=0;link--){
      total-=pcmlengths[link];
      if(pos>=total)break;
    }

    // search within the logical bitstream for the page with the highest
    // pcm_pos preceeding (or equal to) pos.  There is a danger here;
    // missing pages or incorrect frame number information in the
    // bitstream could make our task impossible.  Account for that (it
    // would be an error condition)
    {
      long target=pos-total;
      long end=offsets[link+1];
      long begin=offsets[link];
      int best=(int)begin;

      Page og=new Page();
      while(begin<end){
	long bisect;
	int ret;
    
	if(end-begin<CHUNKSIZE){
	  bisect=begin;
	}
	else{
	  bisect=(end+begin)/2;
	}
    
	seek_helper(bisect);
	ret=get_next_page(og,end-bisect);
      
	if(ret==-1){
	  end=bisect;
	}
	else{
	  long granulepos=og.granulepos();
	  if(granulepos<target){
	    best=ret;  // raw offset of packet with granulepos
	    begin=offset; // raw offset of next packet
	  }
	  else{
	    end=bisect;
	  }
	}
      }
      // found our page. seek to it (call raw_seek).
      if(raw_seek(best)!=0){
	//goto seek_error;
	pcm_offset=-1;
	decode_clear();
	return -1;
      }
    }

    // verify result
    if(pcm_offset>=pos){
      //goto seek_error;
      pcm_offset=-1;
      decode_clear();
      return -1;
    }
    if(pos>pcm_total(-1)){
      //goto seek_error;
      pcm_offset=-1;
      decode_clear();
      return -1;
    }

    // discard samples until we reach the desired position. Crossing a
    // logical bitstream boundary with abandon is OK.
    while(pcm_offset<pos){
      float[][] pcm;
      int target=(int)(pos-pcm_offset);
      float[][][] _pcm=new float[1][][];
      int[] _index=new int[getInfo(-1).channels];
      int samples=vd.synthesis_pcmout(_pcm, _index);
      pcm=_pcm[0];

      if(samples>target)samples=target;
      vd.synthesis_read(samples);
      pcm_offset+=samples;
    
      if(samples<target)
	if(process_packet(1)==0){
	  pcm_offset=pcm_total(-1); // eof
	}
    }
    return 0;
  
  // seek_error:
    // dump machine so we're in a known state
    //pcm_offset=-1;
    //decode_clear();
    //return -1;
  }

  // seek to a playback time relative to the decompressed pcm stream 
  // returns zero on success, nonzero on failure
  int time_seek(float seconds){
    // translate time to PCM position and call pcm_seek

    int link=-1;
    long pcm_total=pcm_total(-1);
    float time_total=time_total(-1);

    if(!seekable)return(-1); // don't dump machine if we can't seek
    if(seconds<0 || seconds>time_total){
      //goto seek_error;
      pcm_offset=-1;
      decode_clear();
      return -1;
    }
  
    // which bitstream section does this time offset occur in?
    for(link=links-1;link>=0;link--){
      pcm_total-=pcmlengths[link];
      time_total-=time_total(link);
      if(seconds>=time_total)break;
    }

    // enough information to convert time offset to pcm offset
    {
      long target=(long)(pcm_total+(seconds-time_total)*vi[link].rate);
      return(pcm_seek(target));
    }

  //seek_error:
    // dump machine so we're in a known state
    //pcm_offset=-1;
    //decode_clear();
    //return -1;
  }

  // tell the current stream offset cursor.  Note that seek followed by
  // tell will likely not give the set offset due to caching
  public long raw_tell(){
    return(offset);
  }

  // return PCM offset (sample) of next PCM sample to be read
  public long pcm_tell(){
    return(pcm_offset);
  }

  // return time offset (seconds) of next PCM sample to be read
  public float time_tell(){
    // translate time to PCM position and call pcm_seek

    int link=-1;
    long pcm_total=0;
    float time_total=0.f;
  
    if(seekable){
      pcm_total=pcm_total(-1);
      time_total=time_total(-1);
  
      // which bitstream section does this time offset occur in?
      for(link=links-1;link>=0;link--){
	pcm_total-=pcmlengths[link];
	time_total-=time_total(link);
	if(pcm_offset>=pcm_total)break;
      }
    }

    return((float)time_total+(float)(pcm_offset-pcm_total)/vi[link].rate);
  }

  //  link:   -1) return the vorbis_info struct for the bitstream section
  //              currently being decoded
  //         0-n) to request information for a specific bitstream section
  //
  // In the case of a non-seekable bitstream, any call returns the
  // current bitstream.  NULL in the case that the machine is not
  // initialized

  public Info getInfo(int link){
    if(seekable){
      if(link<0){
	if(decode_ready){
	  return vi[current_link];
	}
	else{
	  return null;
	}
      }
      else{
	if(link>=links){
	  return null;
	}
	else{
	  return vi[link];
	}
      }
    }
    else{
      if(decode_ready){
	return vi[0];
      }
      else{
	return null;
      }
    }
  }

  public Comment getComment(int link){
    if(seekable){
      if(link<0){
	if(decode_ready){ return vc[current_link]; }
	else{ return null; }
      }
      else{
	if(link>=links){ return null;}
	else{ return vc[link]; }
      }
    }
    else{
      if(decode_ready){ return vc[0]; }
      else{ return null; }
    }
  }

  int host_is_big_endian() {
    return 1;
//    short pattern = 0xbabe;
//    unsigned char *bytewise = (unsigned char *)&pattern;
//    if (bytewise[0] == 0xba) return 1;
//    assert(bytewise[0] == 0xbe);
//    return 0;
  }

  // up to this point, everything could more or less hide the multiple
  // logical bitstream nature of chaining from the toplevel application
  // if the toplevel application didn't particularly care.  However, at
  // the point that we actually read audio back, the multiple-section
  // nature must surface: Multiple bitstream sections do not necessarily
  // have to have the same number of channels or sampling rate.
  // 
  // read returns the sequential logical bitstream number currently
  // being decoded along with the PCM data in order that the toplevel
  // application can take action on channel/sample rate changes.  This
  // number will be incremented even for streamed (non-seekable) streams
  // (for seekable streams, it represents the actual logical bitstream
  // index within the physical bitstream.  Note that the accessor
  // functions above are aware of this dichotomy).
  //
  // input values: buffer) a buffer to hold packed PCM data for return
  //               length) the byte length requested to be placed into buffer
  //               bigendianp) should the data be packed LSB first (0) or
  //                           MSB first (1)
  //               word) word size for output.  currently 1 (byte) or 
  //                     2 (16 bit short)
  // 
  // return values: -1) error/hole in data
  //                 0) EOF
  //                 n) number of bytes of PCM actually returned.  The
  //                    below works on a packet-by-packet basis, so the
  //                    return length is not related to the 'length' passed
  //                    in, just guaranteed to fit.
  // 
  // *section) set to the logical bitstream number

  int read(byte[] buffer,int length,
	   int bigendianp, int word, int sgned, int[] bitstream){
    int host_endian = host_is_big_endian();
    int index=0;

    while(true){
      if(decode_ready){
	float[][] pcm;
	float[][][] _pcm=new float[1][][];
	int[] _index=new int[getInfo(-1).channels];
	int samples=vd.synthesis_pcmout(_pcm, _index);
	pcm=_pcm[0];
	if(samples!=0){
	  // yay! proceed to pack data into the byte buffer
	  int channels=getInfo(-1).channels;
	  int bytespersample=word * channels;
	  if(samples>length/bytespersample)samples=length/bytespersample;
	
	  // a tight loop to pack each size
	  {
	    int val;
	    if(word==1){
	      int off=(sgned!=0?0:128);
	      for(int j=0;j<samples;j++){
		for(int i=0;i<channels;i++){
		  val=(int)(pcm[i][_index[i]+j]*128. + 0.5);
		  if(val>127)val=127;
		  else if(val<-128)val=-128;
		  buffer[index++]=(byte)(val+off);
		}
	      }
	    }
	    else{
	      int off=(sgned!=0?0:32768);

	      if(host_endian==bigendianp){
		if(sgned!=0){
		  for(int i=0;i<channels;i++) { // It's faster in this order
		    int src=_index[i];
		    int dest=i;
		    for(int j=0;j<samples;j++) {
		      val=(int)(pcm[i][src+j]*32768. + 0.5);
		      if(val>32767)val=32767;
		      else if(val<-32768)val=-32768;
		      buffer[dest]=(byte)(val>>>8);
		      buffer[dest+1]=(byte)(val);
		      dest+=channels*2;
		    }
		  }
		}
		else{
		  for(int i=0;i<channels;i++) {
		    float[] src=pcm[i];
		    int dest=i;
		    for(int j=0;j<samples;j++) {
		      val=(int)(src[j]*32768. + 0.5);
		      if(val>32767)val=32767;
		      else if(val<-32768)val=-32768;
		      buffer[dest]=(byte)((val+off)>>>8);
		      buffer[dest+1]=(byte)(val+off);
		      dest+=channels*2;
		    }
		  }
		}
	      }
	      else if(bigendianp!=0){
		for(int j=0;j<samples;j++){
		  for(int i=0;i<channels;i++){
		    val=(int)(pcm[i][j]*32768. + 0.5);
		    if(val>32767)val=32767;
		    else if(val<-32768)val=-32768;
		    val+=off;
		    buffer[index++]=(byte)(val>>>8);
		    buffer[index++]=(byte)val;
		  }
		}
	      }
	      else{
		//int val;
		for(int j=0;j<samples;j++){
		  for(int i=0;i<channels;i++){
		    val=(int)(pcm[i][j]*32768. + 0.5);
		    if(val>32767)val=32767;
		    else if(val<-32768)val=-32768;
		    val+=off;
		    buffer[index++]=(byte)val;
		    buffer[index++]=(byte)(val>>>8);
		  }
		}
	      }
	    }
	  }
	
	  vd.synthesis_read(samples);
	  pcm_offset+=samples;
	  if(bitstream!=null)bitstream[0]=current_link;
	  return(samples*bytespersample);
	}
      }

      // suck in another packet
      switch(process_packet(1)){
      case 0:
	return(0);
      case -1:
	return -1;
      default:
	break;
      }
    }
  }

  public Info[] getInfo(){return vi;}
  public Comment[] getComment(){return vc;}

/*
  public static void main(String[] arg){
    try{
      VorbisFile foo=new VorbisFile(arg[0]);
      int links=foo.streams();
      System.out.println("links="+links);
      Comment[] comment=foo.getComment();
      Info[] info=foo.getInfo();
      for(int i=0; i<links; i++){
        System.out.println(info[i]);
        System.out.println(comment[i]);
      }
      System.out.println("raw_total: "+foo.raw_total(-1));
      System.out.println("pcm_total: "+foo.pcm_total(-1));
      System.out.println("time_total: "+foo.time_total(-1));
    }
    catch(Exception e){
      System.err.println(e);
    }
  }
*/

  public void close() throws java.io.IOException {
    datasource.close();
  }

  class SeekableInputStream extends InputStream {
    java.io.RandomAccessFile raf=null;
    final String mode="r";
    private SeekableInputStream(){
    }
    SeekableInputStream(String file) throws java.io.IOException{
      raf=new java.io.RandomAccessFile(file, mode);
    }
    public int read() throws java.io.IOException{
      return raf.read();
    }
    public int read(byte[] buf) throws java.io.IOException{
      return raf.read(buf);
    }
    public int read(byte[] buf , int s, int len) throws java.io.IOException{
      return raf.read(buf, s, len);
    }
    public long skip(long n) throws java.io.IOException{
      return (long)(raf.skipBytes((int)n));
    }
    public long getLength() throws java.io.IOException{
      return raf.length();
    }
    public long tell() throws java.io.IOException{
      return raf.getFilePointer();
    }
    public int available() throws java.io.IOException{
      return (raf.length()==raf.getFilePointer())? 0 : 1;
    }
    public void close() throws java.io.IOException{
      raf.close();
    }
    public synchronized void mark(int m){
    }
    public synchronized void reset() throws java.io.IOException{
    }
    public boolean markSupported(){
      return false;
    }
    public void seek(long pos) throws java.io.IOException{
      raf.seek(pos);
    }
  }

}
