package com.automatastudios.data.riff {
	import flash.errors.EOFError;
	import com.automatastudios.audio.audiodecoder.events.AudioDecoderEvent;
	import com.automatastudios.audio.audiodecoder.events.AudioDecoderEvent;
	import flash.events.EventDispatcher;
	import flash.events.Event;
	import flash.events.ProgressEvent;
	import flash.errors.EOFError;
	import flash.net.URLStream;
	import flash.utils.ByteArray;
	import flash.utils.Endian;
	import flash.utils.IDataInput;	
		
	public class RIFFParser {
		private var _data:IDataInput;
		private var _currentSize:uint;
		private var _chunkSize:uint;
		private var _currentPosition:uint;
		private var _startPosition:uint;
		private var _isDataComplete:Boolean;
		private var _dataSize:uint;
		
		private var _positionStack:Array;
		private var _sizeStack:Array;
		
		public function RIFFParser(data:IDataInput = null) {
			_data = data;
			_sizeStack = new Array();
			_positionStack = new Array();
			_currentPosition = 0;
			_currentSize = 0;
			_startPosition = 0;
			_isDataComplete = false;			
			
			if (_data is ByteArray) {
				_dataSize = (_data as ByteArray).length;
			} else {	
				(_data as EventDispatcher).addEventListener(ProgressEvent.PROGRESS, onDataProgress);
			}
		}
		
		public function get isDataComplete():Boolean {
			return _isDataComplete;
		}
		
		public function parse(data:IDataInput):void {
			_data = data;
			_sizeStack = new Array();
			_positionStack = new Array();
			_currentPosition = 0;
			_currentSize = 0;
			_startPosition = 0;
			
			if (_data is ByteArray) {
				_isDataComplete = true;
			} else {
				_isDataComplete = false;
			}
		}
		
		public function readChunk():RIFFChunkInfo {
			var chunkInfo:RIFFChunkInfo;
			
			_data.endian = Endian.BIG_ENDIAN;
			
			if (_data.bytesAvailable >= 8) {

				// move on to next chunk
				if (_currentSize > 0) {
					_data.readBytes(new ByteArray(), 0, _currentSize - (_currentPosition - _startPosition));
				}
				
				// check to see if we need to pop up the stack
				while(_sizeStack.length > 0 && _sizeStack[_sizeStack.length - 1] - _currentPosition - _positionStack[_positionStack.length - 1] < 1) {
					_sizeStack.pop();
					_positionStack.pop();
				}
				
				// if on an odd byte remove the padding byte
				if (_currentPosition % 2 == 1) {
					_data.readUnsignedByte();
					++_currentPosition;
				}
	
				chunkInfo = new RIFFChunkInfo();
				chunkInfo.chunkId = String.fromCharCode(_data.readUnsignedByte(), 
												_data.readUnsignedByte(), 
												_data.readUnsignedByte(), 
												_data.readUnsignedByte());
				
				_data.endian = Endian.LITTLE_ENDIAN;				
				chunkInfo.size = 
				_chunkSize = 
				_currentSize = _data.readUnsignedInt();
				
				_currentPosition += 8;
				_startPosition = _currentPosition;
												
				return chunkInfo;
			} else {
				throw new EOFError("Not enough data available to read chunk");
			}
		}
		
		public function readSubChunk():String {
			var subChunkId:String = "";
			
			if (_data.bytesAvailable >= 4) {
		
				_sizeStack.push(_currentSize);
				_positionStack.push(_startPosition);
				
				_currentSize = 0;
				_startPosition = 0;
				
				_data.endian = Endian.BIG_ENDIAN;
	
				subChunkId = String.fromCharCode(_data.readUnsignedByte(), 
												_data.readUnsignedByte(), 
												_data.readUnsignedByte(), 
												_data.readUnsignedByte());
								
				
				_currentPosition += 4;
	
				return subChunkId;
			} else {
				throw new EOFError("Not enough data avilable to read sub chunk");
			}
		}
		
		public function readChunkData():ByteArray {
			var chunkData:ByteArray;
			
			if (_data.bytesAvailable >= _currentSize) {
				chunkData = new ByteArray();
				_data.readBytes(chunkData, 0, _currentSize);
				_currentPosition += _currentSize;
				_currentSize = 0;

				return chunkData;
			} else {
				throw new EOFError("Not enough data avilable to read chunk data");
			}
		}
		
		public function streamChunkData(bufferSize:uint, streamData:ByteArray):uint {
			var readSize:int = Math.min(bufferSize, _currentSize);
			var maxPosition:uint = Math.min(_chunkSize, _currentPosition + _data.bytesAvailable);
			var movedPosition:uint;
					
			readSize = Math.max(readSize, 0);

			if (_data.bytesAvailable >= readSize) {
				if (readSize > 0) {
					_data.readBytes(streamData, streamData.length, readSize);
					_currentPosition += readSize;
					_currentSize -= readSize;
				}
				
				if (_currentSize == 0 && _dataSize > 0) {
					_isDataComplete = true;
				}
				
				return readSize;
			} else {
				throw new EOFError("Not enough data avilable to read " + readSize + " bytes from chunk data");
			}	
		}
		
		private function onDataProgress(event:ProgressEvent):void {
			_dataSize = event.bytesTotal;
		}
	}
}