////////////////////////////////////////////////////////////////////////////////
//
// 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.factory
{
	import flash.display.Shape;
	import flash.display.Sprite;
	import flash.geom.Rectangle;
	import flash.text.engine.TextBlock;
	import flash.text.engine.TextLine;
	import flash.text.engine.TextLineValidity;
	
	import flashx.textLayout.compose.IFlowComposer;
	import flashx.textLayout.compose.ISWFContext;
	import flashx.textLayout.compose.SimpleCompose;
	import flashx.textLayout.compose.StandardFlowComposer;
	import flashx.textLayout.container.ContainerController;
	import flashx.textLayout.container.ScrollPolicy;
	import flashx.textLayout.debug.Debugging;
	import flashx.textLayout.debug.assert;
	import flashx.textLayout.elements.OverflowPolicy;
	import flashx.textLayout.elements.TextFlow;
	import flashx.textLayout.formats.BlockProgression;
	import flashx.textLayout.tlf_internal;
	
	use namespace tlf_internal;

	[Exclude(name="containerController",kind="property")]	
	[Exclude(name="setContentBounds",kind="method")]
	[Exclude(name="callbackWithTextLines",kind="method")]
	[Exclude(name="doesComposedTextFit",kind="method")]
	[Exclude(name="getNextTruncationPosition",kind="method")]


/**
 * The TextLineFactoryBase class serves as the base class for the Text Layout Framework text line factories.
 * 
 * <p><b>Note:</b> Application code does not typically need to create or use a TextLineFactoryBase object directly.
 * Use one of the derived text factory classes instead.</p>
 *  
 * @playerversion Flash 10
 * @playerversion AIR 1.5
 * @langversion 3.0
 *
 * @see flashx.textLayout.elements.TextFlow
*/
	public class TextLineFactoryBase
	{
		/** Requested logical bounds to wrap to */
		private var _compositionBounds:Rectangle;

		/** Bounds of composition results - where the text landed */
		private var _contentBounds:Rectangle;
		
		/** @private */
		protected var _isTruncated:Boolean = false;
		
		private var _horizontalScrollPolicy:String;
		private var _verticalScrollPolicy:String;
		private var _truncationOptions:TruncationOptions;
		private var _containerController:ContainerController;
		static private var _tc:Sprite = new Sprite();
		
		private var _swfContext:ISWFContext;
		
		/** @private */
		static private      var _savedFactoryComposer:SimpleCompose;
		/** @private */
		static tlf_internal var _factoryComposer:SimpleCompose;

		/** @private */		
		static protected var _truncationLineIndex:int; 	// used during truncation
		/** @private */		
		static protected var _pass0Lines:Array; 		// used during truncation
		
		/** @private return the next factory composer that will be used */
		static tlf_internal function peekFactoryCompose():SimpleCompose
		{
			if (_savedFactoryComposer == null)
				_savedFactoryComposer = new SimpleCompose();
			return _savedFactoryComposer;
		}
		
		/** @private support recursive calls into the factory */
		static tlf_internal function beginFactoryCompose():SimpleCompose
		{
			var rslt:SimpleCompose = _factoryComposer;
			_factoryComposer = peekFactoryCompose();
			_savedFactoryComposer = null;
			return rslt;
		}
		
		/** @private support recursive calls into the factory */
		static tlf_internal function endFactoryCompose(prevComposer:SimpleCompose):void
		{
			_savedFactoryComposer = _factoryComposer;
			_factoryComposer = prevComposer;
		}
		
		/** 
		 * Base-class constructor for text line factories.
		 *  
 		 * <p><b>Note:</b> Application code does not typically need to create or use a TextLineFactoryBase object directly.
		 * Use one of the derived text factory classes instead.</p>
		 * 
		 * @playerversion Flash 10
		 * @playerversion AIR 1.5
		 * @langversion 3.0
		 */
		public function TextLineFactoryBase()
		{
			_containerController = new ContainerController(_tc);
			_horizontalScrollPolicy = _verticalScrollPolicy = String(ScrollPolicy.scrollPolicyPropertyDefinition.defaultValue);
		}
		
		/**
		 * The rectangle within which text lines are created.
		 * 
		 * @playerversion Flash 10
		 * @playerversion AIR 1.5
		 * @langversion 3.0
		 */
		public function get compositionBounds():Rectangle
		{
			return _compositionBounds;
		}
		
		public function set compositionBounds(value:Rectangle):void
		{
			_compositionBounds = value;
		}
		
		/**
		 * The smallest rectangle in which the layed-out content fits.
		 * 
		 * <p><b>Note:</b> Truncated lines are not included in the size calculation.</p>
		 * 
		 * @playerversion Flash 10
		 * @playerversion AIR 1.5
		 * @langversion 3.0
		 */
		public function getContentBounds():Rectangle
		{
			return _contentBounds;
		}
		
		/** @private */
		protected function setContentBounds(controllerBounds:Rectangle):void
		{
			_contentBounds = controllerBounds;
			_contentBounds.offset(compositionBounds.left, compositionBounds.top);
		}
		
		/** 
		* The ISWFContext instance used to make FTE calls as needed. 
		*
		* <p>By default, the ISWFContext implementation is this FlowComposerBase object.
		* Applications can provide a custom implementation to use fonts
		* embedded in a different SWF file or to cache and reuse text lines.</p>
		* 
		* @see flashx.textLayout.compose.ISWFContext
		* 
		* @playerversion Flash 10
		* @playerversion AIR 1.5
	 	* @langversion 3.0
	 	*/
 	
		public function get swfContext():ISWFContext
		{
			return _swfContext;
		}
		public function set swfContext(value:ISWFContext):void
		{
			_swfContext = value;
		}
		
		/** 
		 * Specifies the options for truncating the text if it doesn't fit in the composition bounds.
		 *  
		 * @playerversion Flash 10
		 * @playerversion AIR 1.5
		 * @langversion 3.0
		 */
		public function get truncationOptions():TruncationOptions
		{
			return _truncationOptions;
		}
		public function set truncationOptions(value:TruncationOptions):void
		{
			_truncationOptions = value;
		}
		
		/** 
		 * Indicates whether text was truncated when lines were last created.
		 *  
		 * @playerversion Flash 10
		 * @playerversion AIR 1.5
		 * @langversion 3.0
		 */
		public function get isTruncated():Boolean
		{
			return _isTruncated;
		}

		/** 
		 * Specifies how lines are created when the composition bounds are not large enough.
		 *  
		 * <p>If set to <code>ScrollPolicy.ON</code> or <code>ScrollPolicy.AUTO</code>, all lines
		 * are created. It is the your responsibility to scroll lines in the viewable area (and to
		 * mask lines outside this area, if necessary). If set to <code>ScrollPolicy.OFF</code>, then 
		 * only lines that fit within the composition bounds are created.</p>
		 * 
		 * <p>If the <code>truncationOptions</code> property is set, the scroll policy is ignored 
		 * (and treated as <code>ScrollPolicy.OFF</code>).</p> 
		 * 
		 * @playerversion Flash 10
		 * @playerversion AIR 1.5
		 * @langversion 3.0
		 * 
		 * @see flashx.textLayout.compose.StandardFlowComposer
		 * @see flashx.textLayout.container.ScrollPolicy
		 * @see #truncationOptions
	 	 */
	 	 
	 	public function get horizontalScrollPolicy():String
		{
			return _horizontalScrollPolicy;
		}
		public function set horizontalScrollPolicy(scrollPolicy:String):void
		{
			_horizontalScrollPolicy =  scrollPolicy;
		}
		
		/** 
		 * Specifies how lines are created when the composition bounds are not large enough.
		 *  
		 * <p>If set to <code>ScrollPolicy.ON</code> or <code>ScrollPolicy.AUTO</code>, all lines
		 * are created. It is the your responsibility to scroll lines in the viewable area (and to
		 * mask lines outside this area, if necessary). If set to <code>ScrollPolicy.OFF</code>, then 
		 * only lines that fit within the composition bounds are created.</p>
		 * 
		 * <p>If the <code>truncationOptions</code> property is set, the scroll policy is ignored 
		 * (and treated as <code>ScrollPolicy.OFF</code>).</p> 
		 * 
		 * @playerversion Flash 10
		 * @playerversion AIR 1.5
		 * @langversion 3.0
		 * 
		 * @see flashx.textLayout.compose.StandardFlowComposer
		 * @see flashx.textLayout.container.ScrollPolicy
		 * @see #truncationOptions
	 	 */
	 	 
		public function get verticalScrollPolicy():String
		{
			return _verticalScrollPolicy;
		}
		public function set verticalScrollPolicy(scrollPolicy:String):void
		{
			_verticalScrollPolicy =  scrollPolicy;
		}		
		
		/** @private */
		tlf_internal static function getDefaultFlowComposerClass():Class
		{
			return FactoryDisplayComposer;
		}
		
		/** @private */
		protected function get containerController():ContainerController
		{
			return _containerController;
		}
		
		/** 
		 * Sends the created TextLine objects to the client using the supplied callback function.
		 * 
		 * <p>This method sets the <code>x</code> and <code>y</code> properties of the line.</p>
		 * 
		 * @param callback the callback function supplied by the factory user
		 * @param delx the horizontal offset
		 * @param dely the vertical offset
		 * 
		 * @playerversion Flash 10
		 * @playerversion AIR 1.5
		 * @langversion 3.0
		 */
		protected function callbackWithTextLines(callback:Function,delx:Number,dely:Number):void
		{
			for each (var textLine:TextLine in _factoryComposer._lines)
			{
				var textBlock:TextBlock = textLine.textBlock;
				if (textBlock)
				{
					CONFIG::debug { Debugging.traceFTECall(null,textBlock,"releaseLines",textBlock.firstLine, textBlock.lastLine); }	
					textBlock.releaseLines(textBlock.firstLine,textBlock.lastLine);
				}
				textLine.userData = null;
				textLine.x += delx;
				textLine.y += dely;
				textLine.validity = TextLineValidity.STATIC;
				CONFIG::debug { Debugging.traceFTEAssign(textLine,"validity",TextLineValidity.STATIC); }
				callback(textLine);
			}
		}
		
		/**
		 * Indicates whether the composed text fits in the line count limit and includes all text
		 *  
		 * @playerversion Flash 10
		 * @playerversion AIR 1.5
		 * @langversion 3.0
		 */	
		protected function doesComposedTextFit (lineCountLimit:int, textLength:uint, blockProgression:String):Boolean
		{
			if (lineCountLimit != TruncationOptions.NO_LINE_COUNT_LIMIT && _factoryComposer._lines.length > lineCountLimit)
				return false; // Line count limit exceded
			
			var lines:Array = _factoryComposer._lines;
		
			if (!lines.length)
				return textLength ? false /* something to compose, but no line could fit */ : true /* nothing to compose */; 
				
			// This code is only called when scrolling if OFF, so only lines that fit in bounds are generated
			// Just check if the last line reaches the end of flow
			var lastLine:TextLine = lines[lines.length - 1] as TextLine;
			return lastLine.userData + lastLine.rawTextLength == textLength;
		}
		
		/** 
		 * Gets the next truncation position by shedding an atom's worth of characters.
		 * 
		 * @param truncateAtCharPosition the current truncation candidate position.
		 * @param multiPara <code>true</code> if text has more than one paragraph.
		 * 
		 * @returns the next candidate truncation position.
		 * @playerversion Flash 10
		 * @playerversion AIR 1.5
		 * @langversion 3.0
		 */
		protected function getNextTruncationPosition(truncateAtCharPosition:int, multiPara:Boolean=false):int
		{
			// 1. Get the position of the last character of the preceding atom
			truncateAtCharPosition--; // truncateAtCharPosition-1, because truncateAtCharPosition is an atom boundary
			
			// Note: The current set of lines may not contain the next truncation position because the truncation indicator
			// could combine with original content to form a word that does not afford a suitable break opportunity. 
			// The combined word would then move to the next line, which may not have been composed if the bounds were exceeded.
			// Therefore, this function needs to use the original lines (from before truncation is attempted). 
			CONFIG::debug 
			{ 
				assert(_pass0Lines != null, "getNextTruncationPosition called before saving off lines from the first pass at composition"); 
				assert(_truncationLineIndex < _pass0Lines.length, "index out of range in getNextTruncationPosition");
			}
			
			// 2. Find the new target line (i.e., the line that has the new truncation position) 
			// If the last truncation position was at the beginning of the target line, the new position may have moved to a previous line
			// In any case, the new truncation position lies in the vicinity of the previous target line, so a linear search suffices
			var line:TextLine = _pass0Lines[_truncationLineIndex] as TextLine;
			do
			{
				if (truncateAtCharPosition >= line.userData && truncateAtCharPosition < line.userData + line.rawTextLength)
					break;
				if (truncateAtCharPosition < line.userData)
					line = _pass0Lines[--_truncationLineIndex] as TextLine;
				else
				{
					CONFIG::debug {	assert(false, "truncation position should decrease monotonically");	}
				}	
			}
			while (true);

			var paraStart:int = multiPara ?  line.userData - line.textBlockBeginIndex : 0;
			
			// 3. Get the line atom index at this position			
			var atomIndex:int = line.getAtomIndexAtCharIndex(truncateAtCharPosition - paraStart);
			
			// 4. Get the char index for this atom index
			var nextTruncationPosition:int = line.getAtomTextBlockBeginIndex(atomIndex) + paraStart;
			
			line.flushAtomData();
			
			return nextTruncationPosition;
		} 
		/** @private */
		tlf_internal function createFlowComposer():IFlowComposer
		{
			return new FactoryDisplayComposer();
		}			
		
		/** @private
		 * Calculates the last line that fits in the line count limit
		 * The result is stored in  _truncationLineIndex
		 * 
		 * Note: This code is only called when scrolling is OFF, so only lines that fit in bounds are generated
		 */
		tlf_internal function computeLastAllowedLineIndex (lineCountLimit:int):void
		{			
			_truncationLineIndex = _factoryComposer._lines.length - 1;

			// if line count limit is smaller, use that
			if (lineCountLimit != TruncationOptions.NO_LINE_COUNT_LIMIT && lineCountLimit <= _truncationLineIndex)
				_truncationLineIndex = lineCountLimit - 1;
		}
		
		
		/** @private helper to process the background colors.  default implementation creates a shape and passes it to the callback */
		tlf_internal function processBackgroundColors(textFlow:TextFlow,callback:Function,x:Number,y:Number,constrainWidth:Number,constrainHeight:Number):*
		{
			CONFIG::debug { assert(textFlow.backgroundManager != null,"Bad call to processBackgroundColors"); }
			var bgShape:Shape = new Shape();
			textFlow.backgroundManager.drawAllRects(textFlow,bgShape,constrainWidth,constrainHeight);
			bgShape.x = x;
			bgShape.y = y;
			callback(bgShape);
			textFlow.clearBackgroundManager();
		}
	}

} // end package







