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

public class StreamState{
  byte[] body_data;    /* bytes from packet bodies */
  int body_storage;    /* storage elements allocated */
  int body_fill;       /* elements stored; fill mark */
private  int body_returned;   /* elements of fill returned */


  int[] lacing_vals;    /* The values that will go to the segment table */
  long[] granule_vals;  /* pcm_pos values for headers. Not compact
			   this way, but it is simple coupled to the
			   lacing fifo */
  int lacing_storage;
  int lacing_fill;
  int lacing_packet;
  int lacing_returned;

  byte[] header=new byte[282];      /* working space for header encode */
  int header_fill;

  public int e_o_s;   /* set when we have buffered the last packet in the
			 logical bitstream */
  int b_o_s;          /* set after we've written the initial page
			 of a logical bitstream */
  int serialno;
  int pageno;
  long packetno;      /* sequence number for decode; the framing
                         knows where there's a hole in the data,
                         but we need coupling so that the codec
                         (which is in a seperate abstraction
                         layer) also knows about the gap */
  long granulepos;

  public StreamState(){
    init();
  }

  StreamState(int serialno){
    this();
    init(serialno);
  }
  void init(){
    body_storage=16*1024;
    body_data=new byte[body_storage];
    lacing_storage=1024;
    lacing_vals=new int[lacing_storage];
    granule_vals=new long[lacing_storage];
  }
  public void init(int serialno){
    if(body_data==null){ init(); }
    else{
      for(int i=0; i<body_data.length; i++) body_data[i]=0;
      for(int i=0; i<lacing_vals.length; i++) lacing_vals[i]=0;
      for(int i=0; i<granule_vals.length; i++) granule_vals[i]=0;
    }
    this.serialno=serialno;
  }
  public void clear(){
    body_data=null;
    lacing_vals=null;
    granule_vals=null;
    //memset(os,0,sizeof(ogg_stream_state));    
  }
  void destroy(){
    clear();
  }
  void body_expand(int needed){
    if(body_storage<=body_fill+needed){
      body_storage+=(needed+1024);
      byte[] foo=new byte[body_storage];
      System.arraycopy(body_data, 0, foo, 0, body_data.length);
      body_data=foo;
//System.out.println("expand: body_fill="+body_fill+", body_storage="+body_data.length);
    }
  }
  void lacing_expand(int needed){
    if(lacing_storage<=lacing_fill+needed){
      lacing_storage+=(needed+32);
      int[] foo=new int[lacing_storage];
      System.arraycopy(lacing_vals, 0, foo, 0, lacing_vals.length);
      lacing_vals=foo;

      long[] bar=new long[lacing_storage];
      System.arraycopy(granule_vals, 0, bar, 0, granule_vals.length);
      granule_vals=bar;
    }
  }

  /* submit data to the internal buffer of the framing engine */
  public int packetin(Packet op){
    int lacing_val=op.bytes/255+1;

    if(body_returned!=0){
      /* advance packet data according to the body_returned pointer. We
         had to keep it around to return a pointer into the buffer last
         call */
    
      body_fill-=body_returned;
      if(body_fill!=0){
//        memmove(os->body_data,os->body_data+os->body_returned,
//  	        os->body_fill*sizeof(char));
        System.arraycopy(body_data, body_returned, body_data, 0, body_fill);
      }
      body_returned=0;
    }

    /* make sure we have the buffer storage */
    body_expand(op.bytes);
    lacing_expand(lacing_val);

  /* Copy in the submitted packet.  Yes, the copy is a waste; this is
     the liability of overly clean abstraction for the time being.  It
     will actually be fairly easy to eliminate the extra copy in the
     future */

    System.arraycopy(op.packet_base, op.packet, body_data, body_fill, op.bytes);
    body_fill+=op.bytes;
//System.out.println("add: "+body_fill);

  /* Store lacing vals for this packet */
    int j;
    for(j=0;j<lacing_val-1;j++){
      lacing_vals[lacing_fill+j]=255;
      granule_vals[lacing_fill+j]=granulepos;
    }
    lacing_vals[lacing_fill+j]=(op.bytes)%255;
    granulepos=granule_vals[lacing_fill+j]=op.granulepos;

  /* flag the first segment as the beginning of the packet */
    lacing_vals[lacing_fill]|= 0x100;

    lacing_fill+=lacing_val;

  /* for the sake of completeness */
    packetno++;

    if(op.e_o_s!=0)e_o_s=1;
    return(0);
  }

  public int packetout(Packet op){

  /* The last part of decode. We have the stream broken into packet
     segments.  Now we need to group them into packets (or return the
     out of sync markers) */

    int ptr=lacing_returned;

    if(lacing_packet<=ptr){
      return(0);
    }

    if((lacing_vals[ptr]&0x400)!=0){
    /* We lost sync here; let the app know */
      lacing_returned++;

    /* we need to tell the codec there's a gap; it might need to
       handle previous packet dependencies. */
      packetno++;
      return(-1);
    }

  /* Gather the whole packet. We'll have no holes or a partial packet */
    {
      int size=lacing_vals[ptr]&0xff;
      int bytes=0;

      op.packet_base=body_data;
      op.packet=body_returned;
      op.e_o_s=lacing_vals[ptr]&0x200; /* last packet of the stream? */
      op.b_o_s=lacing_vals[ptr]&0x100; /* first packet of the stream? */
      bytes+=size;

      while(size==255){
	int val=lacing_vals[++ptr];
	size=val&0xff;
	if((val&0x200)!=0)op.e_o_s=0x200;
	bytes+=size;
      }

      op.packetno=packetno;
      op.granulepos=granule_vals[ptr];
      op.bytes=bytes;

//System.out.println(this+" # body_returned="+body_returned);
      body_returned+=bytes;
//System.out.println(this+"## body_returned="+body_returned);

      lacing_returned=ptr+1;
    }
    packetno++;
    return(1);
  }


  // add the incoming page to the stream state; we decompose the page
  // into packet segments here as well.

  public int pagein(Page og){
    byte[] header_base=og.header_base;
    int header=og.header;
    byte[] body_base=og.body_base;
    int body=og.body;
    int bodysize=og.body_len;
    int segptr=0;

    int version=og.version();
    int continued=og.continued();
    int bos=og.bos();
    int eos=og.eos();
    long granulepos=og.granulepos();
    int _serialno=og.serialno();
    int _pageno=og.pageno();
    int segments=header_base[header+26]&0xff;

    // clean up 'returned data'
    {
      int lr=lacing_returned;
      int br=body_returned;

      // body data

//System.out.println("br="+br+", body_fill="+body_fill);

      if(br!=0){
        body_fill-=br;
        if(body_fill!=0){
	  System.arraycopy(body_data, br, body_data, 0, body_fill);
	}
	body_returned=0;
      }

//System.out.println("?? br="+br+", body_fill="+body_fill+" body_returned="+body_returned);

      if(lr!=0){
        // segment table
	if((lacing_fill-lr)!=0){
	  System.arraycopy(lacing_vals, lr, lacing_vals, 0, lacing_fill-lr);
	  System.arraycopy(granule_vals, lr, granule_vals, 0, lacing_fill-lr);
	}
	lacing_fill-=lr;
	lacing_packet-=lr;
	lacing_returned=0;
      }
    }

    // check the serial number
    if(_serialno!=serialno)return(-1);
    if(version>0)return(-1);

    lacing_expand(segments+1);

    // are we in sequence?
    if(_pageno!=pageno){
      int i;

      // unroll previous partial packet (if any)
      for(i=lacing_packet;i<lacing_fill;i++){
	body_fill-=lacing_vals[i]&0xff;
//System.out.println("??");
      }
      lacing_fill=lacing_packet;

      // make a note of dropped data in segment table
      if(pageno!=-1){
	lacing_vals[lacing_fill++]=0x400;
	lacing_packet++;
      }

      // are we a 'continued packet' page?  If so, we'll need to skip
      // some segments
      if(continued!=0){
	bos=0;
	for(;segptr<segments;segptr++){
	  int val=(header_base[header+27+segptr]&0xff);
	  body+=val;
	  bodysize-=val;
	  if(val<255){
	    segptr++;
	    break;
	  }
	}
      }
    }

//System.out.println("bodysize="+bodysize);

    if(bodysize!=0){
      body_expand(bodysize);
      System.arraycopy(body_base, body, body_data, body_fill, bodysize);
      body_fill+=bodysize;
    }

//System.out.println("bodyfill="+body_fill);

    {
      int saved=-1;
      while(segptr<segments){
	int val=(header_base[header+27+segptr]&0xff);
	lacing_vals[lacing_fill]=val;
	granule_vals[lacing_fill]=-1;
      
	if(bos!=0){
	  lacing_vals[lacing_fill]|=0x100;
	  bos=0;
	}
      
	if(val<255)saved=lacing_fill;
      
	lacing_fill++;
	segptr++;
      
	if(val<255)lacing_packet=lacing_fill;
      }
  
    /* set the granulepos on the last pcmval of the last full packet */
      if(saved!=-1){
	granule_vals[saved]=granulepos;
      }
    }

    if(eos!=0){
      e_o_s=1;
      if(lacing_fill>0)
	lacing_vals[lacing_fill-1]|=0x200;
    }

    pageno=_pageno+1;
    return(0);
  }


/* This will flush remaining packets into a page (returning nonzero),
   even if there is not enough data to trigger a flush normally
   (undersized page). If there are no packets or partial packets to
   flush, ogg_stream_flush returns 0.  Note that ogg_stream_flush will
   try to flush a normal sized page like ogg_stream_pageout; a call to
   ogg_stream_flush does not gurantee that all packets have flushed.
   Only a return value of 0 from ogg_stream_flush indicates all packet
   data is flushed into pages.

   ogg_stream_page will flush the last page in a stream even if it's
   undersized; you almost certainly want to use ogg_stream_pageout
   (and *not* ogg_stream_flush) unless you need to flush an undersized
   page in the middle of a stream for some reason. */

  public int flush(Page og){

//System.out.println(this+" ---body_returned: "+body_returned);

    int i;
    int vals=0;
    int maxvals=(lacing_fill>255?255:lacing_fill);
    int bytes=0;
    int acc=0;
    long granule_pos=granule_vals[0];

    if(maxvals==0)return(0);
  
    /* construct a page */
    /* decide how many segments to include */
  
    /* If this is the initial header case, the first page must only include
       the initial header packet */
    if(b_o_s==0){  /* 'initial header page' case */
      granule_pos=0;
      for(vals=0;vals<maxvals;vals++){
        if((lacing_vals[vals]&0x0ff)<255){
	  vals++;
	  break;
        }
      }
    }
    else{
      for(vals=0;vals<maxvals;vals++){
        if(acc>4096)break;
        acc+=(lacing_vals[vals]&0x0ff);
        granule_pos=granule_vals[vals];
      }
    }

    /* construct the header in temp storage */
    System.arraycopy("OggS".getBytes(), 0, header, 0, 4);
  
    /* stream structure version */
    header[4]=0x00;

    /* continued packet flag? */
    header[5]=0x00;
    if((lacing_vals[0]&0x100)==0)header[5]|=0x01;
    /* first page flag? */
    if(b_o_s==0) header[5]|=0x02;
    /* last page flag? */
    if(e_o_s!=0 && lacing_fill==vals) header[5]|=0x04;
    b_o_s=1;

    /* 64 bits of PCM position */
    for(i=6;i<14;i++){
      header[i]=(byte)granule_pos;
      granule_pos>>>=8;
    }

    /* 32 bits of stream serial number */
    {
      int _serialno=serialno;
      for(i=14;i<18;i++){
        header[i]=(byte)_serialno;
        _serialno>>>=8;
      }
    }

    /* 32 bits of page counter (we have both counter and page header
       because this val can roll over) */
    if(pageno==-1)pageno=0;       /* because someone called
				     stream_reset; this would be a
				     strange thing to do in an
				     encode stream, but it has
				     plausible uses */
    {
      int _pageno=pageno++;
      for(i=18;i<22;i++){
        header[i]=(byte)_pageno;
        _pageno>>>=8;
      }
    }
  
    /* zero for computation; filled in later */
    header[22]=0;
    header[23]=0;
    header[24]=0;
    header[25]=0;
  
    /* segment table */
    header[26]=(byte)vals;
    for(i=0;i<vals;i++){
      header[i+27]=(byte)lacing_vals[i];
      bytes+=(header[i+27]&0xff);
    }
  
    /* set pointers in the ogg_page struct */
    og.header_base=header;
    og.header=0;
    og.header_len=header_fill=vals+27;
    og.body_base=body_data;
    og.body=body_returned;
    og.body_len=bytes;

    /* advance the lacing data and set the body_returned pointer */
  
//System.out.println("###body_returned: "+body_returned);

    lacing_fill-=vals;
    System.arraycopy(lacing_vals, vals, lacing_vals, 0, lacing_fill*4);
    System.arraycopy(granule_vals, vals, granule_vals, 0, lacing_fill*8);
    body_returned+=bytes;

//System.out.println("####body_returned: "+body_returned);
  
    /* calculate the checksum */
  
    og.checksum();

    /* done */
    return(1);
  }


/* This constructs pages from buffered packet segments.  The pointers
returned are to static buffers; do not free. The returned buffers are
good only until the next call (using the same ogg_stream_state) */
  public int pageout(Page og){
//    if(body_returned!=0){
//    /* advance packet data according to the body_returned pointer. We
//       had to keep it around to return a pointer into the buffer last
//       call */
//
//      body_fill-=body_returned;
//      if(body_fill!=0){ // overlap?
//	System.arraycopy(body_data, body_returned, body_data, 0, body_fill);
//      }
//      body_returned=0;
//    }
//
//System.out.println("pageout: e_o_s="+e_o_s+" lacing_fill="+lacing_fill+" body_fill="+body_fill+", lacing_fill="+lacing_fill+" b_o_s="+b_o_s);
//
//    if((e_o_s!=0&&lacing_fill!=0) ||  /* 'were done, now flush' case */
//       body_fill > 4096 ||          /* 'page nominal size' case */
//       lacing_fill>=255 ||          /* 'segment table full' case */
//       (lacing_fill!=0&&b_o_s==0)){  /* 'initial header page' case */
//      int vals=0,bytes=0;
//      int maxvals=(lacing_fill>255?255:lacing_fill);
//      long acc=0;
//      long pcm_pos=granule_vals[0];
//
//    /* construct a page */
//    /* decide how many segments to include */
//
//    /* If this is the initial header case, the first page must only include
//       the initial header packet */
//      if(b_o_s==0){  /* 'initial header page' case */
//        pcm_pos=0;
//        for(vals=0;vals<maxvals;vals++){
//  	  if((lacing_vals[vals]&0x0ff)<255){
// 	    vals++;
//	    break;
//	  }
//        }
//      }
//      else{
//        for(vals=0;vals<maxvals;vals++){
//	  if(acc>4096)break;
//	  acc+=lacing_vals[vals]&0x0ff;
//	  pcm_pos=granule_vals[vals];
//        }
//      }
//
//    /* construct the header in temp storage */
//      System.arraycopy("OggS".getBytes(), 0, header, 0, 4);
//
//    /* stream structure version */
//      header[4]=0x00;
//    
//    /* continued packet flag? */
//      header[5]=0x00;
//      if((lacing_vals[0]&0x100)==0)header[5]|=0x01;
//    /* first page flag? */
//      if(b_o_s==0)header[5]|=0x02;
//    /* last page flag? */
//      if(e_o_s!=0 && lacing_fill==vals)header[5]|=0x04;
//      b_o_s=1;
//
//    /* 64 bits of PCM position */
//      for(int i=6;i<14;i++){
//        header[i]=(byte)pcm_pos;
//        pcm_pos>>>=8;
//      }
//
//    /* 32 bits of stream serial number */
//      {
//        int serialn=serialno;
//        for(int i=14;i<18;i++){
//  	  header[i]=(byte)serialn;
//	  serialn>>>=8;
//        }
//      }
//
//
///* 32 bits of page counter (we have both counter and page header
//       because this val can roll over) */
//      if(pageno==-1)pageno=0; /* because someone called
//                                       stream_reset; this would be a
//                                       strange thing to do in an
//                                       encode stream, but it has
//                                       plausible uses */
//      {
//        int pagen=pageno++;
//        for(int i=18;i<22;i++){
//	  header[i]=(byte)pagen;
//	  pagen>>>=8;
//        }
//      }
//
//    /* zero for computation; filled in later */
//      header[22]=0;
//      header[23]=0;
//      header[24]=0;
//      header[25]=0;
//
//    /* segment table */
//      header[26]=(byte)vals;
//      for(int i=0;i<vals;i++){
//        header[i+27]=(byte)lacing_vals[i];
//        bytes+=header[i+27]&0xff;
////      bytes+=header[i+27]=(lacing_vals[i]&0xff);
//      }
//      
//    /* advance the lacing data and set the body_returned pointer */
//
//      lacing_fill-=vals;
//      System.arraycopy(lacing_vals, vals, lacing_vals, 0, lacing_fill);
//      System.arraycopy(granule_vals, vals, granule_vals, 0, lacing_fill);
//      body_returned=bytes;
//
//    /* set pointers in the ogg_page struct */
//      og.header_base=header;
//      og.header=0;
//      og.header_len=header_fill=vals+27;
//
//      og.body_base=body_data;
//      og.body=0;
//      og.body_len=bytes;
//
//    /* calculate the checksum */
//
//      og.checksum();
//      return(1);
//    }
//    /* not enough data to construct a page and not end of stream */
//    return(0);
//System.out.println("pageout: "+body_returned);
    if((e_o_s!=0&&lacing_fill!=0) ||  /* 'were done, now flush' case */
        body_fill-body_returned> 4096 ||     /* 'page nominal size' case */
        lacing_fill>=255 ||          /* 'segment table full' case */
        (lacing_fill!=0&&b_o_s==0)){  /* 'initial header page' case */
      return flush(og);
    }
    return 0;
  }

  public int eof(){
    return e_o_s;
  }

  public int reset(){
    body_fill=0;
    body_returned=0;

    lacing_fill=0;
    lacing_packet=0;
    lacing_returned=0;

    header_fill=0;

    e_o_s=0;
    b_o_s=0;
    pageno=-1;
    packetno=0;
    granulepos=0;
    return(0);
  }
}
