/********************************************************************
 *                                                                  *
 * THIS FILE IS PART OF THE OggVorbis SOFTWARE CODEC SOURCE CODE.   *
 * USE, DISTRIBUTION AND REPRODUCTION OF THIS LIBRARY SOURCE IS     *
 * GOVERNED BY A BSD-STYLE SOURCE LICENSE INCLUDED WITH THIS SOURCE *
 * IN 'COPYING'. PLEASE READ THESE TERMS BEFORE DISTRIBUTING.       *
 *                                                                  *
 * THE OggVorbis SOURCE CODE IS (C) COPYRIGHT 1994-2002             *
 * by the Xiph.Org Foundation http://www.xiph.org/                  *
 *                                                                  *
 ********************************************************************/

package org.xiph.libogg;

public class ogg_stream_state {
	
	byte[] body_data;	// unsigned char // bytes from packet bodies
	int body_storage;	// long //storage elements allocated
	int body_fill;		// long //elements stored; fill mark
	int body_returned;	// long //elements of fill returned
	
	int[] lacing_vals;		// The values that will go to the segment table
	long[] granule_vals;	// ogg_int64_t //granulepos values for headers. Not compact this way, but it is simple coupled to the lacing fifo
	int lacing_storage;		// long
	int lacing_fill;		// long
	int lacing_packet;		// long
	int lacing_returned;	// long

	byte[] header;		// unsigned char    header[282];      //working space for header encode
	int header_fill;

	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;	// long
	int pageno;		// long
	long packetno;	// ogg_int64_t  // 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;		// ogg_int64_t   


	public ogg_stream_state () {

		header = new byte[ 282 ];
	}

	public ogg_stream_state( int _serialno ) {
		
		header = new byte[ 282 ];
		
		body_storage = 16*1024;

		// body_data = _ogg_malloc(os->body_storage*sizeof(*os->body_data));
		body_data = new byte[ body_storage ];

		lacing_storage = 1024;
		lacing_vals = new int[ lacing_storage ];
		granule_vals = new long[ lacing_storage ];

		serialno = _serialno;
	}
	
	public int getSerialNo() {
		return serialno;
	}

	// Helpers for ogg_stream_encode; this keeps the structure and what's happening fairly clear
	
	public void _os_body_expand( int needed ) {
		
		if ( body_storage <= body_fill + needed ) {
			
			body_storage += (needed+1024);
			
			// body_data = _ogg_realloc(os->body_data,os->body_storage*sizeof(*os->body_data));
			byte[] temp = new byte[ body_storage ];
    		System.arraycopy( body_data, 0, temp, 0, body_data.length );
    		body_data = temp;
		}
	}

	public void _os_lacing_expand( int needed ) {
		
		if ( lacing_storage <= lacing_fill + needed ) {
			
			lacing_storage += (needed+32);
			
			// lacing_vals = _ogg_realloc(os->lacing_vals,os->lacing_storage*sizeof(*os->lacing_vals));
			int[] temp = new int[ lacing_storage ];
    		System.arraycopy( lacing_vals, 0, temp, 0, lacing_vals.length );
    		lacing_vals = temp;
    		
			// granule_vals = _ogg_realloc(os->granule_vals,os->lacing_storage*sizeof(*os->granule_vals));
    		long[] temp2 = new long[ lacing_storage ];
    		System.arraycopy( granule_vals, 0, temp, 0, granule_vals.length );
    		granule_vals = temp2;
	  }
	}
	
	public boolean ogg_stream_packetin( ogg_packet op ) {
		
// OOP varaible lacing_vals renamed lacing_vals_local
		int lacing_vals_local = op.bytes/255+1;
		int i;
		
		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( body_data, body_data + body_returned, body_fill );
	    		System.arraycopy( body_data, body_returned, body_data, 0, body_fill );
			}
			body_returned = 0;
		}
		
		// make sure we have the buffer storage
		_os_body_expand( op.bytes );
		_os_lacing_expand( lacing_vals_local );
		
		/* 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 */
		
		// memcpy( body_data + body_fill, op.packet, op.bytes );
		System.arraycopy( op.packet, 0, body_data, body_fill, op.bytes );
		body_fill += op.bytes;
		
		// Store lacing vals for this packet
		for ( i=0; i < lacing_vals_local-1; i++ ) {
			lacing_vals[ lacing_fill+i ] = 255;
			granule_vals[ lacing_fill+i ] = granulepos;
		}
		
		lacing_vals[ lacing_fill+i ] = (op.bytes)%255;
		granulepos = granule_vals[ lacing_fill+i] = op.granulepos;
		
		// flag the first segment as the beginning of the packet
		lacing_vals[ lacing_fill ] |= 0x100;
		lacing_fill += lacing_vals_local;
		
		// for the sake of completeness
		packetno++;
		
		if ( op.e_o_s > 0 )
			e_o_s = 1;
		
		return true;
	}
	
	/* 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 guarantee that all packets have flushed.
	 * Only a return value of 0 from ogg_stream_flush indicates all packet
	 * data is flushed into pages.
	 * 
	 * since ogg_stream_flush 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 specifically need to flush 
	 * an page regardless of size in the middle of a stream. */
	
	public boolean ogg_stream_flush( ogg_page og ) {
		
		int maxvals = (lacing_fill>255?255:lacing_fill);
		if ( maxvals == 0 )
			return false;
		
		int i;
		int vals=0;
		int bytes=0;
		int acc=0;
		// ogg_int64_t granule_pos=os->granule_vals[0];
		long granule_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
			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
		// memcpy(os->header,"OggS",4);
		// header[0] = 'O';
		// header[1] = 'g';
		// header[2] = 'g';
		// header[3] = 'S';
		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
// local OOP varname serialno to serialno_local
		int serialno_local = serialno;
		for ( i=14; i <18; i++ ) {
			header[i] = (byte)(serialno_local);
			serialno_local >>>= 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 */
		
// local OOP varname serialno to serialno_local
		int pageno_local = pageno++;
		for ( i=18; i < 22; i++ ) {
			header[i] = (byte)(pageno_local);
			pageno_local >>>= 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);
		}
			
		
// this has been hacked together
// do not know if pointers (offsets) are ok
// or if actual data needs to be copied to ogg_page
		
		// set pointers in the ogg_page struct
		og.header = header;
		og.header_len = header_fill = vals+27;
		// og.body = body_data + body_returned;
		og.body = new byte[ body_data.length - body_returned ];
		System.arraycopy( body_data, body_returned, og.body, 0, body_data.length - body_returned );
		og.body_len = bytes;
		
		// advance the lacing data and set the body_returned pointer
		lacing_fill -= vals;
		
		// memmove(os->lacing_vals, os->lacing_vals+vals, os->lacing_fill*sizeof(*os->lacing_vals));
		// int[] temp = new int[ lacing_fill ];
		// System.arraycopy( lacing_vals, vals, temp, 0, lacing_fill );
		// lacing_vals = temp;
		// System.arraycopy( lacing_vals, vals, lacing_vals, 0, lacing_fill*4 );
		System.arraycopy( lacing_vals, vals, lacing_vals, 0, lacing_fill );
		
		
		// memmove(os->granule_vals, os->granule_vals+vals, os->lacing_fill*sizeof(*os->granule_vals));
		// long[] temp2 = new long[ lacing_fill ];
		// System.arraycopy( granule_vals, vals, temp2, 0, lacing_fill );
		// granule_vals= temp2;
		// System.arraycopy( granule_vals, vals, granule_vals, 0, lacing_fill*8 );
		System.arraycopy( granule_vals, vals, granule_vals, 0, lacing_fill );
		
		body_returned += bytes;
		
		// calculate the checksum
		og.ogg_page_checksum_set();
		
		return true;
	}
	
	/* 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 boolean ogg_stream_pageout( ogg_page og ) {
		
		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 ogg_stream_flush(og);
	  }
	  
	  // not enough data to construct a page and not end of stream
	  return false;
	}
	
}