////////////////////////////////////////////////////////////////////////////////
//
// ADOBE SYSTEMS INCORPORATED
// Copyright 2007-2010 Adobe Systems Incorporated
// All Rights Reserved.
//
// NOTICE:  Adobe permits you to use, modify, and distribute this file 
// in accordance with the terms of the license agreement accompanying it.
//
////////////////////////////////////////////////////////////////////////////////
package flashx.textLayout.compose
{
	import flash.geom.Rectangle;
	import flash.text.engine.TextLine;
	
	import flashx.textLayout.container.ColumnState;
	import flashx.textLayout.container.ContainerController;
	import flashx.textLayout.container.ScrollPolicy;
	import flashx.textLayout.debug.assert;
	import flashx.textLayout.formats.BlockProgression;
	import flashx.textLayout.formats.ITextLayoutFormat;
	import flashx.textLayout.formats.LineBreak;
	import flashx.textLayout.formats.VerticalAlign;
	import flashx.textLayout.tlf_internal;
	import flashx.textLayout.utils.Twips;
	
	use namespace tlf_internal;
			
	[ExcludeClass]
	/** @private
	 * Used for composing text containers, keeps track of the areas that text in the 
	 * flow is composed into.
	 * 
	 * ParcelList will always have one parcel, which corresponds to the container's
	 * bounding box. 
	 */
	public class ParcelList
	{
		protected var _flowComposer:IFlowComposer;
		
		/** Current vertical position in the parcel. */
		protected var _totalDepth:Number;
		
		/** whether the current parcel has any content */
		protected var _hasContent:Boolean;
		
		/** The list of parcels that are available for text layout.
			They are appear in the array in reading order: the first text goes in the
			first parcel, when it gets filled later text is flowed into the second 
			parcel, and so on.  */
		protected var _parcelArray:Array;	/* of Parcel */
		protected var _numParcels:int;
		protected var _singleParcel:Parcel;
		
		/** Index of the "current" parcel. These next two variables must be kept in sync. */
		protected var _currentParcelIndex:int;
		protected var _currentParcel:Parcel;
		
		protected var _insideListItemMargin:Number;
		
		protected var _leftMargin:Number;
		protected var _rightMargin:Number; 
		
		protected var _explicitLineBreaks:Boolean;
			
		/** True if text is vertical (as for some Japanese & Chinese, false otherwise */
		protected var _verticalText:Boolean;
		
		private static const MAX_HEIGHT:Number = 900000000;		// vertical scroll max - capped to prevent loss of precision - what should it be?
		private static const MAX_WIDTH:Number =  900000000;		// horizontal scroll max - capped to prevent loss of precision - what should it be?
			
		// a single parcellist that is checked out and checked in
		static private var _sharedParcelList:ParcelList;

		/** @private */
		static tlf_internal function getParcelList():ParcelList
		{
			var rslt:ParcelList = _sharedParcelList ? _sharedParcelList : new ParcelList();
			_sharedParcelList = null;
			return rslt;
		}
		
		/** @private */
		static tlf_internal function releaseParcelList(list:ParcelList):void
		{
			if (_sharedParcelList == null)
			{
				_sharedParcelList = list as ParcelList;
				if (_sharedParcelList)
					_sharedParcelList.releaseAnyReferences();
			}
		}

		/** Constructor. */
		public function ParcelList()
		{ _numParcels = 0;	}
		
		/** prevent any leaks. @private */
		tlf_internal function releaseAnyReferences():void
		{
			this._flowComposer = null;
			
			_numParcels = 0;
			_parcelArray = null;
			
			if (_singleParcel)
				_singleParcel.releaseAnyReferences();
		}
		
		CONFIG::debug public function getBounds():Array
		{
			var boundsArray:Array = [];
			for (var i:int = 0; i < _numParcels; ++i)
				boundsArray.push(getParcelAt(i));
			return boundsArray;
		}
		
		public function getParcelAt(idx:int):Parcel
		{ return _numParcels <= 1 ? _singleParcel : _parcelArray[idx]; }
				
		public function get currentParcelIndex():int
		{ return _currentParcelIndex; }
		
		public function get explicitLineBreaks():Boolean
		{ 
			return _explicitLineBreaks;
		}
		
		private function get measureLogicalWidth():Boolean
		{
			if (_explicitLineBreaks)
				return true;
			if (!_currentParcel)
				return false;
			var controller:ContainerController = _currentParcel.controller;
			return _verticalText ? controller.measureHeight : controller.measureWidth;
		}

		private function get measureLogicalHeight():Boolean
		{
			if (!_currentParcel)
				return false;
			var controller:ContainerController = _currentParcel.controller;
			return _verticalText ? controller.measureWidth : controller.measureHeight;
		}
		
		public function get totalDepth():Number
		{
			return _totalDepth;
		}
		
		public function addTotalDepth(value:Number):Number
		{
			_totalDepth += value;	
		//	trace("addTotalDepth", value, "newDepth", totalDepth);
			return _totalDepth;
		}
		
		protected function reset():void
		{
			// Composition starts with an initial invalid parcel. It will start by calling next(), which will 
			// advance to the first parcel.
			_totalDepth = 0;
			_hasContent = false;

			_currentParcelIndex = -1;
			_currentParcel = null;
			
			_leftMargin = 0;
			_rightMargin = 0;
			_insideListItemMargin = 0;
		}
		
		private function addParcel(column:Rectangle, controller:ContainerController, columnIndex:int):void
		{
			var newParcel:Parcel = _numParcels == 0 && _singleParcel 
				? _singleParcel.initialize(_verticalText, column.x,column.y,column.width,column.height,controller,columnIndex) 
				: new Parcel(_verticalText, column.x, column.y, column.width, column.height, controller, columnIndex)
			if (_numParcels == 0)
				_singleParcel = newParcel;
			else if (_numParcels == 1)
				_parcelArray = [  _singleParcel, newParcel ];
			else
				_parcelArray.push(newParcel);
			_numParcels++;
		}
		
		protected function addOneControllerToParcelList(controllerToInitialize:ContainerController):void
		{
			// Initialize new parcels for columns
			var columnState:ColumnState = controllerToInitialize.columnState;
			for (var columnIndex:int = 0; columnIndex < columnState.columnCount; columnIndex++)
			{
				var column:Rectangle = columnState.getColumnAt(columnIndex);
				if (!column.isEmpty())
					addParcel(column, controllerToInitialize, columnIndex);
			}
		}
		
		public function beginCompose(composer:IFlowComposer, controllerStartIndex:int, controllerEndIndex:int, composeToPosition:Boolean):void
		{
			_flowComposer = composer;
			
			var rootFormat:ITextLayoutFormat = composer.rootElement.computedFormat;
			_explicitLineBreaks = rootFormat.lineBreak == LineBreak.EXPLICIT;
			_verticalText   = (rootFormat.blockProgression == BlockProgression.RL);
			
			if (composer.numControllers != 0)
			{
				// if controllerEndIndex is not specified then assume we are composing to position and add all controllers
				if (controllerEndIndex < 0)
					controllerEndIndex = composer.numControllers-1;
				else
					controllerEndIndex = Math.min(controllerEndIndex,composer.numControllers-1);
				var idx:int = controllerStartIndex;
				do
				{
					addOneControllerToParcelList(ContainerController(composer.getControllerAt(idx)));
				} while (idx++ != controllerEndIndex)
				// adjust the last container for scrolling
				if (controllerEndIndex == composer.numControllers-1)
					adjustForScroll(composer.getControllerAt(composer.numControllers-1), composeToPosition);
			}
			reset();
		}
		
		/** Adjust the size of the parcel corresponding to the last column of the containter, in 
		 * order to account for scrolling.
		 */
		private function adjustForScroll(containerToInitialize:ContainerController, composeToPosition:Boolean):void
		{			
			// Expand the last parcel if scrolling could be enabled. Expand to twice what would fit in available space. 
			// We will start composing from the top, so if we've scrolled down there will be more to compose.
			// We turn on fitAny, so that lines will be included in the container even if only a tiny portion of the line
			// fits. This makes lines that are only partially scrolling in appear. We turn on composeToPosition if we're
			// forcing composition to go through a given position -- this will make all lines fit, and composition will
			// continue until it is past the supplied position.
			if (_verticalText)
			{
				if (containerToInitialize.horizontalScrollPolicy != ScrollPolicy.OFF)
				{
					p = getParcelAt(_numParcels-1);
					if (p)
					{
						var horizontalPaddingAmount:Number = containerToInitialize.getTotalPaddingRight() + containerToInitialize.getTotalPaddingLeft();
						var right:Number = p.right;
						p.x = containerToInitialize.horizontalScrollPosition - p.width - horizontalPaddingAmount;
						p.width = right - p.x;
						p.fitAny = true;
						p.composeToPosition = composeToPosition;
					}
				}
			}
			else
			{
				if (containerToInitialize.verticalScrollPolicy != ScrollPolicy.OFF)
				{
					var p:Parcel = getParcelAt(_numParcels-1);
					if (p)
					{
						var verticalPaddingAmount:Number = containerToInitialize.getTotalPaddingBottom() + containerToInitialize.getTotalPaddingTop();
						p.height = (containerToInitialize.verticalScrollPosition + p.height + verticalPaddingAmount) - p.y;
						p.fitAny = true;
						p.composeToPosition = composeToPosition;
					}
				}
			}
		}

		public function get leftMargin():Number
		{
			return _leftMargin;
		}
		
		public function pushLeftMargin(leftMargin:Number):void
		{
			_leftMargin += leftMargin;	
		}
		
		public function popLeftMargin(leftMargin:Number):void
		{
			_leftMargin -= leftMargin;	
		}
					
		public function get rightMargin():Number
		{
			return _rightMargin;
		}
		
		public function pushRightMargin(rightMargin:Number):void
		{
			_rightMargin += rightMargin;	
		}
		
		public function popRightMargin(rightMargin:Number):void
		{
			_rightMargin -= rightMargin;	
		}
		
		public function pushInsideListItemMargin(margin:Number):void
		{ _insideListItemMargin += margin; }
		public function popInsideListItemMargin(margin:Number):void
		{ _insideListItemMargin -= margin; }	
		public function get insideListItemMargin():Number
		{ return _insideListItemMargin; }
		
		public		function getComposeXCoord(o:Rectangle):Number
		{ 
			// trace("LPL: getComposeXCoord");
			return _verticalText ? o.right : o.left;
		}
		public		function getComposeYCoord(o:Rectangle):Number
		{ 
			// trace("LPL: getComposeYCoord");
			return o.top;
		}

		public function getComposeWidth(o:Rectangle):Number
		{ 
			// trace("LPL: getComposeWidth");
			if (measureLogicalWidth)
				return TextLine.MAX_LINE_WIDTH;
			return _verticalText ? o.height : o.width; 
		}

		public function getComposeHeight(o:Rectangle):Number
		{ 
			// trace("LPL: getComposeHeight");
			if (measureLogicalHeight)
				return TextLine.MAX_LINE_WIDTH;
			return _verticalText ? o.width : o.height; 
		}		

		/** Returns true if the current parcel is the last.
		*/
		public function atLast():Boolean
		{
			return _numParcels == 0 || _currentParcelIndex == _numParcels -1;
		}
		
		public function atEnd():Boolean
		{
			return _numParcels == 0 || _currentParcelIndex >= _numParcels;
		}
		
		public function next():Boolean
		{
			CONFIG::debug { assert(_currentParcelIndex >= -1 && _currentParcelIndex < _numParcels, "invalid _currentParcelIndex in ParcelList"); }			
			var nextParcelIsValid:Boolean = (_currentParcelIndex + 1) < _numParcels;

			_currentParcelIndex += 1;
			_totalDepth = 0;

			if (nextParcelIsValid)
			{
				_currentParcel = getParcelAt(_currentParcelIndex);
				var nextController:ContainerController = _currentParcel.controller;
			}
			else
				_currentParcel = null;
	
			return nextParcelIsValid;
		}
		
		public function get currentParcel():Parcel
		{ return _currentParcel; }

		/**Return the slug rectangle for a line that goes at the current vertical location,
		 * and could extend down for at least height pixels. Note that this function
		 * can change the current parcel, and the location within the parcel.
		 * @param slugRect result rectangle where line was fit
		 * @param height	amount of contiguous vertical space that must be available
		 * @param minWidth	amount of contiguous horizontal space that must be available 
		 * @return true if a line slug was fit horizontal space actually available
		 */
		public function getLineSlug(slug:Slug, height:Number, minWidth:Number, textIndent:Number, directionLTR:Boolean):Boolean
		{
			if (currentParcel.getLineSlug(slug, _totalDepth, height, minWidth, currentParcel.fitAny ? 1 : int(height), _leftMargin, _rightMargin, textIndent+_insideListItemMargin, directionLTR,  _explicitLineBreaks))
			{
				if (totalDepth != slug.depth)
					_totalDepth = slug.depth;
				return true;
			}
			return false;
		}
	
		// Attempts to fit a float of the specified width and height in the current parcel. Float is considered to fit if it
		// is alone on the line but exceeds the parcel width and fits within the logical height.
		// Returns success or failure.
		public function fitFloat(slug:Slug, totalDepth:Number, width:Number, height:Number):Boolean
		{
			return currentParcel.getLineSlug(slug, totalDepth, height, width, currentParcel.fitAny ? 1 : int(height), _leftMargin, _rightMargin, 0, true, _explicitLineBreaks);
		}
				
	}	//end class
} //end package
