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

import java.util.*;

import org.xiph.libogg.*;
import static org.xiph.libvorbis.vorbis_constants.integer_constants.*;

public class vorbis_dsp_state {

	int analysisp;
	
	public vorbis_info vi;

	float[][] pcm;		// float **pcm // float **pcmret

	int pcm_storage;
	public int pcm_current;
	int pcm_returned;

	int preextrapolate;
	int eofflag;

	int lW;
	int W;
	int nW;
	int centerW;

	int granulepos;
	int sequence;

	int glue_bits;
	int time_bits;
	int floor_bits;
	int res_bits;

	private_state backend_state;


	public vorbis_dsp_state() {}


	private vorbis_look_floor1 floor1_look( vorbis_info_floor1 info ) {

		int sortpointer[] = new int[ VIF_POSIT+2 ];
		vorbis_look_floor1 look = new vorbis_look_floor1();

		int i,j,n=0;

		look.vi = info;
		look.n = info.postlist[1];
 
		// we drop each position value in-between already decoded values,
		// and use linear interpolation to predict each new value past the
		// edges.  The positions are read in the order of the position
		// list... we precompute the bounding positions in the lookup.  Of
		// course, the neighbors can change (if a position is declined), but
		// this is an initial mapping

		for ( i=0; i<info.partitions; i++ ) 
			n += info.class_dim[ info.partitionclass[i] ];
		n+=2;
		look.posts=n;

		// also store a sorted position index
		for ( i=0; i<n; i++ )
			sortpointer[i] = info.postlist[i];
			// sortpointer[i] = info.postlist+i;

		// static int icomp(const void *a,const void *b) { return(**(int **)a-**(int **)b); }
		// qsort( sortpointer, n, sizeof(*sortpointer), icomp );

		Arrays.sort( sortpointer, 0, n );

		// points from sort order back to range number
		for ( i=0; i<n; i++ )
			for ( j=0; j<n; j++)
				if ( sortpointer[i] == info.postlist[j] )
					look.forward_index[i] = j;
		// look.forward_index[i] = sortpointer[i]-info.postlist;

		// points from range order to sorted position
		for ( i=0; i<n; i++ )
			look.reverse_index[ look.forward_index[i] ] = i;

		// we actually need the post values too
		for ( i=0; i<n; i++ )
			look.sorted_index[i] = info.postlist[ look.forward_index[i] ];

		switch ( info.mult ) {	// quantize values to multiplier spec

			case 1:
				look.quant_q=256;
				break;
			case 2:
				look.quant_q=128;
				break;
			case 3:
				look.quant_q=86;
				break;
			case 4:
				look.quant_q=64;
				break;
		}

		// discover our neighbors for decode where we don't use fit flags (that would push the neighbors outward)
		for ( i=0; i<n-2; i++ ) {
			int lo=0;
			int hi=1;
			int lx=0;
			int hx=look.n;
			int currentx=info.postlist[i+2];
			for ( j=0; j<i+2; j++ ) {
				int x=info.postlist[j];
				if (x>lx && x<currentx) {
					lo=j;
					lx=x;
				}
				if (x<hx && x>currentx) {
					hi=j;
					hx=x;
				}
			}
			look.loneighbor[i]=lo;
			look.hineighbor[i]=hi;
		}

		return look;
	}


	private vorbis_look_residue0 res0_look( vorbis_info_residue0 info ) {

		vorbis_look_residue0 look = new vorbis_look_residue0();
		codec_setup_info ci = vi.codec_setup;

		int j,k,acc=0;
		int dim;
		int maxstage = 0;

		look.info = info;

		look.parts = info.partitions;
		look.fullbooks = ci.fullbooks;
// PTR CITY

		// look.phrasebook = ci.fullbooks+info.groupbook;
		look.phrasebook = ci.fullbooks[ info.groupbook ];
		dim = look.phrasebook.dim;

		// look.partbooks = _ogg_calloc(look.parts,sizeof(*look.partbooks));
		look.partbooks = new codebook[ look.parts ][];
		

		for ( j=0; j<look.parts; j++ ) {

			int stages = ilog( info.secondstages[j] );
			if ( stages > 0 ) {
				if ( stages > maxstage )
					maxstage = stages;

				// look.partbooks[j]=_ogg_calloc(stages,sizeof(*look.partbooks[j]));
				look.partbooks[j] = new codebook[ stages ];

				for ( k=0; k<stages; k++)

					if ( (info.secondstages[j] & (1<<k) ) > 0 ) {

						// look.partbooks[j][k] = ci.fullbooks + info.booklist[acc++];

						look.partbooks[j][k] = ci.fullbooks[ info.booklist[acc++] ];

						// #ifdef TRAIN_RES
						// 	look.training_data[k][j]=calloc(look.partbooks[j][k].entries,sizeof(***look.training_data));
						// #endif
					}
			}
		}

		look.partvals = new Double( Math.rint( Math.pow( look.parts, dim) ) ).intValue();
		look.stages = maxstage;

		// look.decodemap=_ogg_malloc(look.partvals*sizeof(*look.decodemap));
		look.decodemap = new int[ look.partvals ][ dim ];

		for ( j=0; j<look.partvals; j++) {
			int val = j;
			int mult = look.partvals / look.parts;

			// look.decodemap[j]=_ogg_malloc(dim*sizeof(*look.decodemap[j]));

			for ( k=0; k<dim; k++ ){
				int deco = val/mult;
				val -= deco*mult;
				mult /= look.parts;
				look.decodemap[j][k] = deco;
			}
		}

		// #ifdef TRAIN_RES
		//	static int train_seq=0;
		//	look.train_seq=train_seq++;
		// #endif

  		return look;
	}


	private boolean _vds_shared_init( boolean encp ) {

		int i;
		codec_setup_info ci = vi.codec_setup;
		int hs;

		if ( ci == null )
			return false;

		hs = ci.halfrate_flag;

		backend_state = new private_state();

		backend_state.modebits = ilog2( ci.modes );

		backend_state.transform[0][0].mdct_init( ci.blocksizes[0]>>>hs );
		backend_state.transform[1][0].mdct_init( ci.blocksizes[1]>>>hs );

		// Vorbis I uses only window type 0
		backend_state.window[0] = ilog2(ci.blocksizes[0])-6;
		backend_state.window[1] = ilog2(ci.blocksizes[1])-6;

		if (encp) { // encode/decode differ here

			// analysis always needs an fft
			backend_state.fft_look[0].drft_init( ci.blocksizes[0] );
			backend_state.fft_look[1].drft_init( ci.blocksizes[1] );

			// finish the codebooks
			if ( ci.fullbooks == null ) {

				// ci.fullbooks=_ogg_calloc(ci.books,sizeof(*ci.fullbooks));
				ci.fullbooks = new codebook[ ci.books ];

				for ( i=0; i<ci.books; i++ ) {
					ci.fullbooks[i] = new codebook();
					ci.fullbooks[i].vorbis_book_init_encode( ci.book_param[i] );
				}
			}

			// backend_state.psy=_ogg_calloc(ci.psys,sizeof(*backend_state.psy));
			backend_state.psy = new vorbis_look_psy[ ci.psys ];

			for ( i=0; i<ci.psys; i++ ) {
				backend_state.psy[i] = new vorbis_look_psy();
				backend_state.psy[i]._vp_psy_init( ci.psy_param[i], ci.psy_g_param, ci.blocksizes[ci.psy_param[i].blockflag]/2, vi.rate );
			 }

			analysisp = 1;
		}
		else {
			// decode the codebooks - not yet implemented
			System.out.println( "_vds_shared_init DECODE not yet implemented");
			return false;
		}

		pcm_storage = ci.blocksizes[1];

		pcm = new float[ vi.channels ][ pcm_storage ];
		// pcmret = new float[ vi.channels ];

		lW = 0; // previous window size
		W = 0;  // current window size

		// all vector indexes
		centerW = ci.blocksizes[1]/2;

		pcm_current = centerW;

// flr - will always be floor1
// residue - look data will always fit in res0

		// initialize all the backend lookups
		// backend_state.flr = _ogg_calloc(ci.floors,sizeof(*backend_state.flr));
		// backend_state.residue = _ogg_calloc(ci.residues,sizeof(*backend_state.residue));

		backend_state.flr = new vorbis_look_floor1[ ci.floors ];
		backend_state.residue = new vorbis_look_residue0[ ci.residues ];

		// for ( i=0; i < ci.floors; i++ )
		//	backend_state.flr[i] = _floor_P[ci.floor_type[i]].look( v, ci.floor_param[i] );

		for ( i=0; i < ci.floors; i++ ) {
			backend_state.flr[i] = floor1_look( ci.floor_param[i] );
		}

		// for ( i=0; i < ci.residues; i++ )
		//	backend_state.residue[i] = _residue_P[ci.residue_type[i]].look( v, ci.residue_param[i] );
		
		for ( i=0; i < ci.residues; i++ ) {
			backend_state.residue[i] = res0_look( ci.residue_param[i] );
		}
		
		return true;
	}

	public boolean vorbis_analysis_init( vorbis_info _vi ) {

		vi = _vi;

  		if ( !_vds_shared_init( true ) )
			return false;

		backend_state.psy_g_look = new vorbis_look_psy_global( vi );

		backend_state.ve = new envelope_lookup( vi );

  		backend_state.bms = new bitrate_manager_state( vi );
  		
  		// compressed audio packets start after the headers with sequence number 3
  		sequence = 3;

  		return true;
	}

	public boolean vorbis_analysis_headerout( vorbis_comment vc, ogg_packet op, ogg_packet op_comm, ogg_packet op_code ) {

		oggpack_buffer opb;

		// first header packet **********************************************
		opb = new oggpack_buffer();
		if ( !opb._vorbis_pack_info( vi ) )
			return false;

		// build the packet
		backend_state.header = new byte[ opb.oggpack_bytes() ];
		// memcpy(b->header,opb.buffer,oggpack_bytes(&opb));
		System.arraycopy( opb.buffer, 0, backend_state.header, 0, opb.oggpack_bytes() );
		op.packet = backend_state.header;
		op.bytes = opb.oggpack_bytes();
		op.b_o_s = 1;
		op.e_o_s = 0;
		op.granulepos = 0;
		op.packetno = 0;

		// second header packet (comments) **********************************
		opb = new oggpack_buffer();
		if ( !opb._vorbis_pack_comment( vc ) )
			return false;
		
		// build the packet
		backend_state.header1 = new byte[ opb.oggpack_bytes() ];
		// memcpy(b->header1,opb.buffer,oggpack_bytes(&opb));
		System.arraycopy( opb.buffer, 0, backend_state.header1, 0, opb.oggpack_bytes() );
		op_comm.packet = backend_state.header1;
		op_comm.bytes = opb.oggpack_bytes();
		op_comm.b_o_s = 0;
		op_comm.e_o_s = 0;
		op_comm.granulepos = 0;
		op_comm.packetno = 1;

		// third header packet (modes/codebooks) ****************************
		opb = new oggpack_buffer();
		if( !opb._vorbis_pack_books( vi ) )
			return false;

		// build the packet
		backend_state.header2 = new byte[ opb.oggpack_bytes() ];
		// memcpy(b->header2,opb.buffer,oggpack_bytes(&opb));
		System.arraycopy( opb.buffer, 0, backend_state.header2, 0, opb.oggpack_bytes() );
		op_code.packet = backend_state.header2;
		op_code.bytes = opb.oggpack_bytes();
		op_code.b_o_s = 0;
		op_code.e_o_s = 0;
		op_code.granulepos = 0;
		op_code.packetno = 2;

		return true;
	}
	
	public float[][] vorbis_analysis_buffer( int vals ) {
		
		int i;
		
		// free header, header1, header2
		backend_state.header = null;
		backend_state.header1 = null;
		backend_state.header2 = null;
		
		// Do we have enough storage space for the requested buffer? If not, expand the PCM (and envelope) storage
		
		if ( pcm_current + vals >= pcm_storage  ) {

			pcm_storage = pcm_current + vals*2;

// TODO - multiple channels
// temp work around for loop - need hash, etc
// for unique array name for array reference
// this is workaround for only 2 channels

			for ( i=0; i < vi.channels; i+=2 ) {
				// pcm[i] = _ogg_realloc( v->pcm[i], v->pcm_storage*sizeof(*v->pcm[i]) );
				float[] temp = new float[ pcm_storage ];
				System.arraycopy( pcm[i], 0, temp, 0, pcm_current );
				pcm[i] = temp;
				
				float[] temp2 = new float[ pcm_storage ];
				System.arraycopy( pcm[i+1], 0, temp2, 0, pcm_current );
				pcm[i+1] = temp2;
			}
		}
		
// for now I just return pcm and offset
// by pcm_current to alter the .wav buffer

		// for ( i=0; i < vi.channels; i++ )
			// v->pcmret[i] = v->pcm[i] + v->pcm_current;

		return pcm;
	}
	
	public boolean vorbis_analysis_wrote( int vals ) {

		codec_setup_info ci = vi.codec_setup;

		if ( vals <= 0 ) {
			
			int order=32;
		    int i;
		    float[] lpc = new float[ order ];
		    
		    // if it wasn't done earlier (very short sample)
		    if( preextrapolate <= 0 )
		    	_preextrapolate_helper();
		    
		    // We're encoding the end of the stream.  Just make sure we have
		    // [at least] a few full blocks of zeroes at the end.
		    
		    // Actually, we don't want zeroes; that could drop a large
		    // amplitude off a cliff, creating spread spectrum noise that will
		    // suck to encode.  Extrapolate for the sake of cleanliness.
		    
		    vorbis_analysis_buffer( ci.blocksizes[1]*3 ); 
		    eofflag = pcm_current;
		    pcm_current += ci.blocksizes[1]*3;
		    
		    for ( i=0; i < vi.channels; i++ ) {
		    	
		    	if ( eofflag > order*2 ) {
		    		// extrapolate with LPC to fill in
		    		int n;
		    		
		    		// make a predictor filter
		    		n = eofflag;
		    		if ( n > ci.blocksizes[1] )
		    			n = ci.blocksizes[1];

		    		// vorbis_lpc_from_data(v->pcm[i]+v->eofflag-n,lpc,n,order);
		    		vorbis_lpc_from_data( pcm[i], eofflag-n, lpc, n, order );

		    		// run the predictor filter
		    		// vorbis_lpc_predict(lpc,v->pcm[i]+v->eofflag-order,order, v->pcm[i]+v->eofflag,v->pcm_current-v->eofflag);
		    		vorbis_lpc_predict( lpc, pcm[i], eofflag-order, order, eofflag, pcm_current - eofflag );
		    		
		    	} else {
		    		
		    		// not enough data to extrapolate (unlikely to happen due to
		    		// guarding the overlap, but bulletproof in case that
		    		// assumtion goes away). zeroes will do.

		    		// memset(v->pcm[i]+v->eofflag,0,(v->pcm_current-v->eofflag)*sizeof(*v->pcm[i]));
		    		
		    		// for ( int j=eofflag; j < pcm_current; j++ ) {
		    		//	pcm[i][j] = 0;
		    		// }
		    		
		    		Arrays.fill( pcm[i], eofflag, pcm_current, 0 );
		    	}
		    }
		} else {
			
			if ( pcm_current+vals > pcm_storage )
				return false;
			
			pcm_current += vals;
			
			// we may want to reverse extrapolate the beginning of a stream
			// too... in case we're beginning on a cliff!
			// clumsy, but simple.  It only runs once, so simple is good.
			
			if ( (preextrapolate <= 0) && pcm_current-centerW > ci.blocksizes[1] )
				_preextrapolate_helper();
		}
		return true;
	}
	
	public boolean vorbis_bitrate_flushpacket( ogg_packet op ) {
		
		private_state b = backend_state;
		bitrate_manager_state bm = b.bms;
		vorbis_block vb = bm.vb;
		int choice = PACKETBLOBS/2;
		
		if ( vb == null )
			return false;
		
		if ( op != null ) {
			
			vorbis_block_internal vbi = vb.internal;
			
			if ( vb.vorbis_bitrate_managed() )
				choice = bm.choice;

		    op.packet = vbi.packetblob[choice].buffer; 	// oggpack_get_buffer();
		    op.bytes = vbi.packetblob[choice].oggpack_bytes();
		    op.b_o_s = 0;
		    op.e_o_s = vb.eofflag;
		    op.granulepos = vb.granulepos;
		    op.packetno = vb.sequence; 	// for sake of completeness    
		}
		
		// bm.vb = 0;
		bm.vb = null;
		return true;	  
	}
	
	public void _preextrapolate_helper() {
		
		int i;
		int order = 32;
		float[] lpc = new float[ order ];
		float[] work = new float[ pcm_current ];
		int j;
		preextrapolate = 1;
		
		if ( pcm_current-centerW > order*2 ) { // safety
			
			for ( i=0; i< vi.channels; i++ ) {
				
				// need to run the extrapolation in reverse!
				
				for (j=0; j < pcm_current; j++)
					work[j] = pcm[i][pcm_current-j-1];
				
				// prime as above
				vorbis_lpc_from_data( work, 0, lpc, pcm_current-centerW, order );
				
				// run the predictor filter
				vorbis_lpc_predict( lpc, work, pcm_current-centerW-order, order, pcm_current-centerW, centerW );
				
				for ( j=0; j < pcm_current; j++ )
					pcm[i][pcm_current-j-1] = work[j];
			}
		}
	}
	
	public float vorbis_lpc_from_data( float[] data, int offset1, float[] lpci, int n, int m ) {
		
		// double *aut=alloca(sizeof(*aut)*(m+1));
		double[] aut = new double[ m+1 ];
		// double *lpc=alloca(sizeof(*lpc)*(m));
		double[] lpc = new double[ m ];
		double error;
		int i,j;
		
		// autocorrelation, p+1 lag coefficients
		j = m+1;
		while ( j-- > 0 ) {
			double d = 0; // double needed for accumulator depth
			for ( i=j; i < n; i++ )
				d += (double)data[offset1+i] * data[offset1+i-j];
			aut[j] = d;
		}
		
		// Generate lpc coefficients from autocorr values
		
		error = aut[0];
		
		for ( i=0; i < m; i++ ) {
			
			double r = -aut[i+1];
			
			if ( error==0 ) {
				lpci = new float[ lpci.length ];
				return 0;
			}
			
			// Sum up this iteration's reflection coefficient; note that in Vorbis
			// we don't save it.  If anyone wants to recycle this code and needs 
			// reflection coefficients, save the results of 'r' from each iteration.
			
			for ( j=0; j < i; j++)
				r -= lpc[j]*aut[i-j];
			r /= error;
			
			// Update LPC coefficients and total error
			
			lpc[i] = r;
			for ( j=0; j < i/2; j++ ) {
				
				double tmp = lpc[j];
				
				lpc[j] += r*lpc[i-1-j];
				lpc[i-1-j] += r*tmp;
			}
			if ( i%2 > 0 )
				lpc[j] += lpc[j]*r;
			
			error *= 1.f-r*r;
		}
		
		for ( j=0; j <m ; j++ )
			lpci[j] = (float)lpc[j];
		
		// we need the error value to know how big an impulse to hit the filter with later
		
		return new Double( error ).floatValue();
	}
	
	public void vorbis_lpc_predict( float[] coeff, float[] prime, int offset1, int m, int offset2, int n ) {
		
		// in: coeff[0...m-1] LPC coefficients 
		// 	   prime[0...m-1] initial values (allocated size of n+m-1)
		// out: data[0...n-1] data samples
		
		int i,j,o,p;
		float y;
		float[] work = new float[ m+n ];
		
		if ( prime == null )
			for ( i=0; i < m; i++ )
				work[i] = 0.f;
		else
			for ( i=0; i < m; i++)
				work[i] = prime[ offset1+i ];
		
		for ( i=0; i < n; i++ ) {
			y=0;
			o=i;
			p=m;
			for ( j=0; j < m; j++ )
				y -= work[o++]*coeff[--p];
		    
		    prime[ offset2+i ] = work[o] = y;
		}
	}
	
	public int _ve_envelope_search() {
		
		codec_setup_info ci = vi.codec_setup;
		vorbis_info_psy_global gi = ci.psy_g_param;
		envelope_lookup ve = backend_state.ve;
		int i, j;
		
		int first = ve.current/ve.searchstep;
		int last = pcm_current/ve.searchstep-VE_WIN;
		if ( first < 0 )
			first=0;
		
		// make sure we have enough storage to match the PCM
		if ( last+VE_WIN+VE_POST > ve.storage ) {
			ve.storage=last+VE_WIN+VE_POST; // be sure
			
			// ve.mark = _ogg_realloc(ve.mark,ve.storage*sizeof(*ve.mark));
			int[] temp = new int[ ve.storage ];
			System.arraycopy( ve.mark, 0, temp, 0, ve.mark.length );
			ve.mark = temp;
		}
		
		for ( j=first; j < last; j++ ) {
			
			int ret=0;
			
			ve.stretch++;
			if ( ve.stretch > VE_MAXSTRETCH*2 )
				ve.stretch = VE_MAXSTRETCH*2;
			
			for ( i=0; i < ve.ch; i++ ) {
				// float *pcm = pcm[i] + ve.searchstep*(j);
				// ret |= _ve_amp( ve, gi, _pcm, ve.band, ve.filter+i*VE_BANDS, j );
				ret |= ve._ve_amp( gi, pcm[i], ve.searchstep*(j), ve.band, ve.filter, i*VE_BANDS, j );
			}
			
			ve.mark[j+VE_POST] = 0;
			if ( (ret&1) > 0 ) {
				ve.mark[j] = 1;
				ve.mark[j+1] = 1;
			}
			
			if ( (ret&2) > 0 ) {
				ve.mark[j] = 1;
				if ( j > 0 )
					ve.mark[j-1] = 1;
			}
			
			if ( (ret&4) > 0 )
				ve.stretch = -1;
		}
		
//		ve.out( "", null );
		
		ve.current = last*ve.searchstep;

// OOP local variable rename
		int centerW_local = centerW;
		int testW = centerW_local + ci.blocksizes[W]/4 + ci.blocksizes[1]/2 + ci.blocksizes[0]/4;
		
		j = ve.cursor;
		
		while ( j < ve.current-(ve.searchstep) ) { // account for postecho working back one window
			
			if ( j >= testW )
				return 1;
			
			ve.cursor=j;
			
			if ( ve.mark[ j/ve.searchstep ] > 0 ) {
				
				if ( j > centerW_local ) {
					ve.curmark = j;
					if ( j >= testW )
						return 1;
					return 0;
				}
			}
			
			j += ve.searchstep;
		}
		
		return -1;
	}
		
	public boolean _ve_envelope_mark() {
		
		envelope_lookup ve = backend_state.ve;
		codec_setup_info ci = vi.codec_setup;
	
// local OOP variable rename centerW
		int centerW_local = centerW;
		int beginW = centerW_local-ci.blocksizes[W]/4;
		int endW = centerW_local+ci.blocksizes[W]/4;
		
		if ( W > 0 ) {
			beginW -= ci.blocksizes[lW]/4;
			endW += ci.blocksizes[nW]/4;
		} else {
			beginW -= ci.blocksizes[0]/4;
			endW += ci.blocksizes[0]/4;
		}
		
		if ( ve.curmark >= beginW && ve.curmark<endW)
			return true;
		  {
		    int first = beginW/ve.searchstep;
		    int last = endW/ve.searchstep;
		    int i;
		    for ( i=first; i < last; i++ )
		    	if ( ve.mark[i] > 0 )
		    		return true;
		  }
		  
		  return false;
	}
	
	public float _vp_ampmax_decay( float amp ) {
		
		codec_setup_info ci = vi.codec_setup;
		vorbis_info_psy_global gi = ci.psy_g_param;
		
		int n = ci.blocksizes[W]/2;
		float secs = (float)n/vi.rate;
		
		amp += secs*gi.ampmax_att_per_sec;
		if ( amp < -9999 )
			amp=-9999;
		
		return amp;
	}
}