/* 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.jogg;

// DECODING PRIMITIVES: packet streaming layer

// This has two layers to place more of the multi-serialno and paging
// control in the application's hands.  First, we expose a data buffer
// using ogg_decode_buffer().  The app either copies into the
// buffer, or passes it directly to read(), etc.  We then call
// ogg_decode_wrote() to tell how many bytes we just added.
//
// Pages are returned (pointers into the buffer in ogg_sync_state)
// by ogg_decode_stream().  The page is then submitted to
// ogg_decode_page() along with the appropriate
// ogg_stream_state* (ie, matching serialno).  We then get raw
// packets out calling ogg_stream_packet() with a
// ogg_stream_state.  See the 'frame-prog.txt' docs for details and
// example code.

public class SyncState{

  public byte[] data;
  int storage;
  int fill;
  int returned;

  int unsynced;
  int headerbytes;
  int bodybytes;

  public int clear(){
    data=null;
    return(0);
  }

// !!!!!!!!!!!!
//  byte[] buffer(int size){
  public int buffer(int size){
    // first, clear out any space that has been previously returned
    if(returned!=0){
      fill-=returned;
      if(fill>0){
	System.arraycopy(data, returned, data, 0, fill);
      }
      returned=0;
    }

    if(size>storage-fill){
      // We need to extend the internal buffer
      int newsize=size+fill+4096; // an extra page to be nice
      if(data!=null){
	byte[] foo=new byte[newsize];
	System.arraycopy(data, 0, foo, 0, data.length);
	data=foo;
      }
      else{
	data=new byte[newsize];
      }
      storage=newsize;
    }

    // expose a segment at least as large as requested at the fill mark
//    return((char *)oy->data+oy->fill);
//    return(data);
    return(fill);
  }

  public int wrote(int bytes){
    if(fill+bytes>storage)return(-1);
    fill+=bytes;
    return(0);
  }

// sync the stream.  This is meant to be useful for finding page
// boundaries.
//
// return values for this:
// -n) skipped n bytes
//  0) page not ready; more data (no bytes skipped)
//  n) page synced at current location; page length n bytes
  private Page pageseek=new Page();
  private  byte[] chksum=new byte[4];
  public int pageseek(Page og){
    int page=returned;
    int next;
    int bytes=fill-returned;
  
    if(headerbytes==0){
      int _headerbytes,i;
      if(bytes<27)return(0); // not enough for a header
    
    /* verify capture pattern */
//!!!!!!!!!!!
      if(data[page]!='O' ||
	 data[page+1]!='g' ||
	 data[page+2]!='g' ||
	 data[page+3]!='S'){
        headerbytes=0;
        bodybytes=0;
  
        // search for possible capture
        next=0;
        for(int ii=0; ii<bytes-1; ii++){
          if(data[page+1+ii]=='O'){next=page+1+ii; break;}
        }
    //next=memchr(page+1,'O',bytes-1);
        if(next==0) next=fill;

        returned=next;
        return(-(next-page));
      }
      _headerbytes=(data[page+26]&0xff)+27;
      if(bytes<_headerbytes)return(0); // not enough for header + seg table
    
      // count up body length in the segment table
    
      for(i=0;i<(data[page+26]&0xff);i++){
        bodybytes+=(data[page+27+i]&0xff);
      }
      headerbytes=_headerbytes;
    }
  
    if(bodybytes+headerbytes>bytes)return(0);
  
    // The whole test page is buffered.  Verify the checksum
    synchronized(chksum){
      // Grab the checksum bytes, set the header field to zero
    
      System.arraycopy(data, page+22, chksum, 0, 4);
      data[page+22]=0;
      data[page+23]=0;
      data[page+24]=0;
      data[page+25]=0;
    
      // set up a temp page struct and recompute the checksum
      Page log=pageseek;
      log.header_base=data;
      log.header=page;
      log.header_len=headerbytes;

      log.body_base=data;
      log.body=page+headerbytes;
      log.body_len=bodybytes;
      log.checksum();

      // Compare
      if(chksum[0]!=data[page+22] ||
         chksum[1]!=data[page+23] ||
         chksum[2]!=data[page+24] ||
         chksum[3]!=data[page+25]){
        // D'oh.  Mismatch! Corrupt page (or miscapture and not a page at all)
        // replace the computed checksum with the one actually read in
        System.arraycopy(chksum, 0, data, page+22, 4);
        // Bad checksum. Lose sync */

        headerbytes=0;
        bodybytes=0;
        // search for possible capture
        next=0;
        for(int ii=0; ii<bytes-1; ii++){
          if(data[page+1+ii]=='O'){next=page+1+ii; break;}
        }
        //next=memchr(page+1,'O',bytes-1);
        if(next==0) next=fill;
        returned=next;
        return(-(next-page));
      }
    }
  
    // yes, have a whole page all ready to go
    {
      page=returned;

      if(og!=null){
        og.header_base=data;
        og.header=page;
        og.header_len=headerbytes;
	og.body_base=data;
        og.body=page+headerbytes;
        og.body_len=bodybytes;
      }

      unsynced=0;
      returned+=(bytes=headerbytes+bodybytes);
      headerbytes=0;
      bodybytes=0;
      return(bytes);
    }
//  headerbytes=0;
//  bodybytes=0;
//  next=0;
//  for(int ii=0; ii<bytes-1; ii++){
//    if(data[page+1+ii]=='O'){next=page+1+ii;}
//  }
//  //next=memchr(page+1,'O',bytes-1);
//  if(next==0) next=fill;
//  returned=next;
//  return(-(next-page));
  }


// sync the stream and get a page.  Keep trying until we find a page.
// Supress 'sync errors' after reporting the first.
//
// return values:
//  -1) recapture (hole in data)
//   0) need more data
//   1) page returned
//
// Returns pointers into buffered data; invalidated by next call to
// _stream, _clear, _init, or _buffer

  public int pageout(Page og){
    // all we need to do is verify a page at the head of the stream
    // buffer.  If it doesn't verify, we look for the next potential
    // frame

    while(true){
      int ret=pageseek(og);
      if(ret>0){
        // have a page
        return(1);
      }
      if(ret==0){
        // need more data
        return(0);
      }
    
      // head did not start a synced page... skipped some bytes
      if(unsynced==0){
        unsynced=1;
        return(-1);
      }
      // loop. keep looking
    }
  }

// clear things to an initial state.  Good to call, eg, before seeking
  public int reset(){
    fill=0;
    returned=0;
    unsynced=0;
    headerbytes=0;
    bodybytes=0;
    return(0);
  }
  public void init(){}

  public int getDataOffset(){ return returned; }    
  public int getBufferOffset(){ return fill;  }
}
