/*****************************************************
*  
*  Copyright 2009 Adobe Systems Incorporated.  All Rights Reserved.
*  
*****************************************************
*  The contents of this file are subject to the Mozilla Public License
*  Version 1.1 (the "License"); you may not use this file except in
*  compliance with the License. You may obtain a copy of the License at
*  http://www.mozilla.org/MPL/
*   
*  Software distributed under the License is distributed on an "AS IS"
*  basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the
*  License for the specific language governing rights and limitations
*  under the License.
*   
*  
*  The Initial Developer of the Original Code is Adobe Systems Incorporated.
*  Portions created by Adobe Systems Incorporated are Copyright (C) 2009 Adobe Systems 
*  Incorporated. All Rights Reserved. 
*  
*****************************************************/
package org.osmf.net.httpstreaming.f4f
{
	import flash.utils.ByteArray;
	import flash.utils.IDataInput;
	
	import org.osmf.events.HTTPStreamingFileHandlerEvent;
	import org.osmf.net.httpstreaming.HTTPStreamingFileHandlerBase;

	[ExcludeClass]

	/**
	 * @private
	 * 
	 * This is the actual implementation of HTTPStreamingFileHandlerBase. It handles 
	 * individual fragments of an F4V file.
	 */	
	public class HTTPStreamingF4FFileHandler extends HTTPStreamingFileHandlerBase
	{
		/**
		 * Constructor.
		 *  
		 *  @langversion 3.0
		 *  @playerversion Flash 10
		 *  @playerversion AIR 1.5
		 *  @productversion OSMF 1.0
		 */
		public function HTTPStreamingF4FFileHandler()
		{
			super();
			
		}
		
		/** 
		 * @private
		 */
		override public function beginProcessFile(seek:Boolean, seekTime:Number):void
		{
			_processRequestWasSeek = seek;
			_seekToTime = seek ? seekTime : 0;
			_bytesNeeded = F4FConstants.FIELD_SIZE_LENGTH + F4FConstants.FIELD_TYPE_LENGTH + F4FConstants.FIELD_LARGE_SIZE_LENGTH + F4FConstants.FIELD_EXTENDED_TYPE_LENGTH;
			_bytesReadSinceAfraStart = 0;
			_countingReadBytes = false;
			_boxInfoPending = true;
			_nextBox = null;
		}
		
		/**
		 * @private
		 */	
		override public function get inputBytesNeeded():Number
		{
			return _bytesNeeded;
		}
		
		/**
		 * @private
		 */		
		override public function processFileSegment(input:IDataInput):ByteArray
		{
			if (input.bytesAvailable < _bytesNeeded)
			{
				return null;
			}

			var returnByteArray:ByteArray = null;

			var bytesRead:Number = F4FConstants.FIELD_SIZE_LENGTH + F4FConstants.FIELD_TYPE_LENGTH;
						
			if (_boxInfoPending)
			{
				_ba = new ByteArray();
				
				input.readBytes(_ba, 0, bytesRead);
				if (_countingReadBytes)
				{
					_bytesReadSinceAfraStart += bytesRead;
				}
				
				_parser.init(_ba);
				_nextBox = _parser.getNextBoxInfo();
				if (_nextBox.size == F4FConstants.FLAG_USE_LARGE_SIZE)
				{
					bytesRead += F4FConstants.FIELD_LARGE_SIZE_LENGTH;
					
					_ba.position = 0;
					input.readBytes(_ba, 0, F4FConstants.FIELD_LARGE_SIZE_LENGTH);
					if (_countingReadBytes)
					{
						_bytesReadSinceAfraStart += F4FConstants.FIELD_LARGE_SIZE_LENGTH;
					}
					_nextBox.size = _parser.readLongUIntToNumber();
				}
				
				// TODO: Check for extended type too.
				
				_boxInfoPending = false;
				if (_nextBox.type == F4FConstants.BOX_TYPE_MDAT)
				{
					_bytesNeeded = 0;
					_mdatBytesPending = _nextBox.size - bytesRead;
				}
				else
				{
					_bytesNeeded = _nextBox.size - bytesRead;
					_mdatBytesPending = 0;
					if (_nextBox.type == F4FConstants.BOX_TYPE_AFRA)
					{
						_bytesReadSinceAfraStart = bytesRead;
						_countingReadBytes = true;
					}
				}
			}
			// otherwise, we are not waiting for BoxInfo but the actual box contents
			else if (_bytesNeeded > 0)
			{
				var pos:uint = _ba.position;
				input.readBytes(_ba, _ba.length, _nextBox.size - bytesRead);
				if (_countingReadBytes)
				{
					_bytesReadSinceAfraStart += (_nextBox.size - bytesRead);
				}
				_ba.position = pos;
				
				if (_nextBox.type == F4FConstants.BOX_TYPE_ABST)
				{
					var abst:AdobeBootstrapBox = _parser.readAdobeBootstrapBox(_nextBox);
					if (abst != null)
					{
						dispatchEvent(
							new HTTPStreamingFileHandlerEvent(
								HTTPStreamingFileHandlerEvent.NOTIFY_BOOTSTRAP_BOX, 
								false, 
								false, 
								0, 
								null, 
								false, 
								false, 
								abst));
					}
				} 
				else if (_nextBox.type == F4FConstants.BOX_TYPE_AFRA)
				{
					_afra = _parser.readFragmentRandomAccessBox(_nextBox);
					processSeekToTime();
				}
				else if (_nextBox.type == F4FConstants.BOX_TYPE_MOOF)
				{
					// Don't need to do anything with a MOOF box, so skip and move on.
				}

				_bytesNeeded = F4FConstants.FIELD_SIZE_LENGTH + F4FConstants.FIELD_TYPE_LENGTH + F4FConstants.FIELD_LARGE_SIZE_LENGTH + F4FConstants.FIELD_EXTENDED_TYPE_LENGTH;
				_boxInfoPending = true;
				_nextBox = null;
			}
			else
			{
				returnByteArray = getMDATBytes(input, false);
			}
			
			return returnByteArray;
		}
		
		/**
		 * @private
		 */	
		override public function endProcessFile(input:IDataInput):ByteArray
		{
			if (this._bytesNeeded > 0)
			{
				dispatchEvent(
					new HTTPStreamingFileHandlerEvent(
						HTTPStreamingFileHandlerEvent.NOTIFY_ERROR, 
						false, 
						false, 
						0, 
						null, 
						false, 
						false, 
						null, 
						true));
			}
			
			return getMDATBytes(input, true);
		}	

		/**
		 * @private
		 */	
		override public function flushFileSegment(input:IDataInput):ByteArray
		{
			return null;
		}
		
		// Internal
		//
		
		private function getMDATBytes(input:IDataInput, endOfFile:Boolean):ByteArray
		{
			if (input == null)
			{
				return null;
			}
			
			skipSeekBytes(input);
			
			var ba:ByteArray;
			if (_mdatBytesPending > 0)
			{
				var bytesToRead:uint = _mdatBytesPending < input.bytesAvailable? _mdatBytesPending : input.bytesAvailable;
				if (!endOfFile && bytesToRead > MAX_BYTES_PER_MDAT_READ)
				{
					bytesToRead = MAX_BYTES_PER_MDAT_READ;
				}
				ba = new ByteArray();
				_mdatBytesPending -= bytesToRead;
				input.readBytes(ba, 0, bytesToRead);
			}
			
			return ba;
		}
		
		private function skipSeekBytes(input:IDataInput):void
		{
			if (_bytesReadSinceAfraStart < _mdatBytesOffset)
			{
				var skip:uint = _mdatBytesOffset - _bytesReadSinceAfraStart;
				if (input.bytesAvailable < skip)
				{
					skip = input.bytesAvailable;
				}
				
				var ba:ByteArray = new ByteArray();
				input.readBytes(ba, 0, skip);
				_bytesReadSinceAfraStart += skip;
				_mdatBytesPending -= skip;
			}
		}
		
		/**
		 * File handler notifies HTTPNetStream of segment duration (fragment duration in
		 * f4f terminology) and time bias if it is actually a seek.
		 * 
		 * The segment duration can be known by the file index handler if there is no
		 * seek. Otherwise, file handler needs to consult the afra box to figure that out.
		 * This means that NOTIFY_SEGMENT_DURATION can be done from two places, which is 
		 * desirable. Therefore, we have only file handler report the segment duration.
		 * 
		 * For the seek case, segment duration is the duration of the whole segment minus
		 * the portion to be skipped.
		 *  
		 *  @langversion 3.0
		 *  @playerversion Flash 10
		 *  @playerversion AIR 1.5
		 *  @productversion OSMF 1.0
		 */
		private function processSeekToTime():void
		{
			var timeBias:Number = 0;
			var entry:LocalRandomAccessEntry = null;
			
			if (_seekToTime <= 0)
			{
				_mdatBytesOffset = 0;
			}
			else
			{
				entry = getMDATBytesOffset(_seekToTime);
				if (entry != null)
				{
					_mdatBytesOffset = entry.offset;
					timeBias = entry.time;
				} 
				else
				{
					_mdatBytesOffset = 0;
				}
			}
		}
		
		private function getMDATBytesOffset(seekToTime:Number):LocalRandomAccessEntry
		{
			return (!isNaN(seekToTime))? _afra.findNearestKeyFrameOffset(seekToTime * _afra.timeScale) : null;
		}

		private var _afra:AdobeFragmentRandomAccessBox;
		private var _ba:ByteArray;
		private var _boxInfoPending:Boolean;
		private var _bytesNeeded:uint;
		private var _bytesReadSinceAfraStart:uint;
		private var _countingReadBytes:Boolean;
		private var _mdatBytesPending:uint;
		private var _nextBox:BoxInfo;
		private var _parser:BoxParser = new BoxParser();
		private var _seekToTime:Number;
		private var _mdatBytesOffset:Number;
		private var _processRequestWasSeek:Boolean = false;
		
		private static const MAX_BYTES_PER_MDAT_READ:uint = 5*1024;
	}
}