////////////////////////////////////////////////////////////////////////////////
//
// 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.display.DisplayObject;
	import flash.events.Event;
	import flash.geom.Rectangle;
	import flash.text.engine.TextBlock;
	import flash.text.engine.TextElement;
	import flash.text.engine.TextLine;
	import flash.text.engine.TextLineCreationResult;
	import flash.text.engine.TextLineValidity;
	
	import flashx.textLayout.container.ContainerController;
	import flashx.textLayout.debug.Debugging;
	import flashx.textLayout.debug.assert;
	import flashx.textLayout.elements.ContainerFormattedElement;
	import flashx.textLayout.elements.FlowElement;
	import flashx.textLayout.elements.FlowGroupElement;
	import flashx.textLayout.elements.FlowLeafElement;
	import flashx.textLayout.elements.InlineGraphicElement;
	import flashx.textLayout.elements.OverflowPolicy;
	import flashx.textLayout.elements.ParagraphElement;
	import flashx.textLayout.elements.TextFlow;
	import flashx.textLayout.formats.BaselineOffset;
	import flashx.textLayout.formats.BlockProgression;
	import flashx.textLayout.formats.Direction;
	import flashx.textLayout.formats.Float;
	import flashx.textLayout.formats.ITextLayoutFormat;
	import flashx.textLayout.formats.ListStylePosition;
	import flashx.textLayout.formats.TextAlign;
	import flashx.textLayout.formats.VerticalAlign;
	import flashx.textLayout.tlf_internal;
	import flashx.textLayout.utils.Twips;
	
	use namespace tlf_internal;

	[ExcludeClass]
	/** Keeps track of internal state during composition. 
	 * 
	 * This is the simpler version, used when there are no floats, no wraps, no columns.
	 * @private
	 */
	public class ComposeState extends BaseCompose 
	{
		/** Index of current line */
		protected var _curLineIndex:int;	
		
		// for figuring out when to do VJ
		protected var vjBeginLineIndex:int;
		protected var vjDisableThisParcel:Boolean;
		
		protected var _useExistingLine:Boolean;
		
		// a single ComposeState that is checked out and checked in
		static private var _sharedComposeState:ComposeState;

		/** @private */
		static tlf_internal function getComposeState():ComposeState
		{
			var rslt:ComposeState = _sharedComposeState;
			if (rslt)
			{
				_sharedComposeState = null;
				return rslt;
			}
			return new ComposeState();
		}
		
		/** @private */
		static tlf_internal function releaseComposeState(state:ComposeState):void
		{
			state.releaseAnyReferences();
			_sharedComposeState = state;
		}				

		/** @private */
		protected override function createParcelList():ParcelList
		{
			return ParcelList.getParcelList();
		}
		/** @private */
		protected override function releaseParcelList(list:ParcelList):void
		{
			ParcelList.releaseParcelList(list);
		}
		
		/** @private */
		public override function composeTextFlow(textFlow:TextFlow, composeToPosition:int, controllerEndIndex:int):int
		{
			CONFIG::debug { assert(textFlow.flowComposer is StandardFlowComposer,"ComposeState.composeTextFlow requires textFlow with a StandardFlowComposer"); }
			_curLineIndex    = -1;		// unitialized: this will get set in composeParagraphElement
			_curLine = null;
					
			vjBeginLineIndex = 0;
			vjDisableThisParcel = false;
			
			return super.composeTextFlow(textFlow, composeToPosition, controllerEndIndex);
		}
		
		protected override function initializeForComposer(composer:IFlowComposer,composeToPosition:int,controllerStartIndex:int, controllerEndIndex:int):void
		{
			           
			// start composing from the first damaged position. Update internal composition state as if we'd composed to here already.
			_startComposePosition = composer.damageAbsoluteStart;
			if (controllerStartIndex == -1)
			{
				var controllerIndex:int = composer.findControllerIndexAtPosition(_startComposePosition);
				if (controllerIndex == -1)
				{
					controllerIndex = composer.numControllers-1;
					// if off the end in the overflow - find the last non-zero controller
					while (controllerIndex != 0 && composer.getControllerAt(controllerIndex).textLength == 0)
						controllerIndex--;
				}
			}
			
			// if damage is in overflow after last controller we could get smart about that
			_startController = composer.getControllerAt(controllerIndex);
			CONFIG::debug { assert(_startController != null,"Bad start start controller"); }

			// Disable partial container composition if we have to vertically align the lines.
			if (_startController.computedFormat.verticalAlign != VerticalAlign.TOP)
				_startComposePosition = _startController.absoluteStart; 

			// Comment this line in to disable composing from the middle of a container.
		//	_startComposePosition = _startController.absoluteStart; 

			super.initializeForComposer(composer,composeToPosition, controllerIndex, controllerEndIndex);
		}
		
		/** @private */
		protected override function composeInternal(composeRoot:FlowGroupElement,absStart:int):void
		{
			super.composeInternal(composeRoot,absStart);
			
			// mark all overflow lines as not being in any container or column
			if (_curElement)
			{
				var lineIndex:int = _curLineIndex;
				CONFIG::debug { assert(_curLineIndex == _flowComposer.findLineIndexAtPosition(_curElementStart + _curElementOffset),"bad _curLineIndex"); }
				while (lineIndex < _flowComposer.numLines)
					_flowComposer.getLineAt(lineIndex++).setController(null,-1);
			}
		}

 		override protected function doVerticalAlignment(canVerticalAlign:Boolean,nextParcel:Parcel):void
		{
			if (canVerticalAlign && _curParcel && vjBeginLineIndex != _curLineIndex &&  !vjDisableThisParcel)
			{
				var controller:ContainerController = _curParcel.controller;
				var vjtype:String = controller.computedFormat.verticalAlign;
				// Don't allow vertical justification if the line contains floats, since we don't support recomposing when lines are moved.
				if (vjtype == VerticalAlign.JUSTIFY)
				{
					for  (var i:int = controller.numFloats - 1; i >= 0 && canVerticalAlign; --i)
					{
						var floatInfo:FloatCompositionData = controller.getFloatAt(i);
						if (floatInfo.floatType != Float.NONE)
							canVerticalAlign = false;
							
					}
				}
				if (canVerticalAlign && vjtype != VerticalAlign.TOP)
				{	
					// Exclude overset lines
					var end:int = _flowComposer.findLineIndexAtPosition(_curElementStart + _curElementOffset)

					if (vjBeginLineIndex < end)
					{
						var beginFloatIndex:int = 0;
						var endFloatIndex:int = 0;
						var lines:Array = (_flowComposer as StandardFlowComposer).lines;
						if (controller.numFloats > 0)
						{
							beginFloatIndex = controller.findFloatIndexAtOrAfter(lines[vjBeginLineIndex].absoluteStart);
							endFloatIndex = controller.findFloatIndexAfter(_curElementStart + _curElementOffset);
						}
						applyVerticalAlignmentToColumn(controller,vjtype,lines,vjBeginLineIndex,end-vjBeginLineIndex, beginFloatIndex, endFloatIndex);
					}
				}
			}
			
			// always reset these variables
			vjDisableThisParcel = false;
			vjBeginLineIndex = _curLineIndex;	
		}
		
		
		/** apply vj and adjust the parcel bounds */
		override protected function applyVerticalAlignmentToColumn(controller:ContainerController,vjType:String,lines:Array,beginIndex:int,numLines:int,beginFloatIndex:int,endFloatIndex:int):void
		{
			super.applyVerticalAlignmentToColumn(controller, vjType, lines, beginIndex, numLines, beginFloatIndex, endFloatIndex);
			
			// Reposition lines in case they got aligned after being added to _linesInView
			for each (var textLine:TextLine in controller.composedLines)
			{
				var line:TextFlowLine = textLine.userData as TextFlowLine;
				CONFIG::debug
				{
					for (var i:int = 0; i < _textFlow.flowComposer.numLines; ++i)
					{
						var tfl:TextFlowLine = _textFlow.flowComposer.getLineAt(i);
						if (tfl == line)
							break;
					}
					assert(i < _textFlow.flowComposer.numLines, "Creating shape for unknown (old) line");
				}
				line.createShape(_blockProgression, textLine);
			}
		}
		
		/** Final adjustment on the content bounds. */
 		override protected function finalParcelAdjustment(controller:ContainerController):void
 		{
			CONFIG::debug { assert(_flowComposer.getLineAt(_flowComposer.findLineIndexAtPosition(_curParcelStart)).absoluteStart == _curParcelStart,"finalParcelAdjustment: bad _curParcelStart"); }

 			var minX:Number = TextLine.MAX_LINE_WIDTH;
 			var minY:Number = TextLine.MAX_LINE_WIDTH;
 			var maxX:Number = -TextLine.MAX_LINE_WIDTH;
 			
 			var verticalText:Boolean = _blockProgression == BlockProgression.RL;
			
			if (!isNaN(_parcelLogicalTop))
			{
				if (verticalText)
					maxX = _parcelLogicalTop;
				else
					minY = _parcelLogicalTop;
			}
			
			if (!_measuring)
			{
				if (verticalText)
					minY = _accumulatedMinimumStart;
				else
					minX = _accumulatedMinimumStart;			
			}
			else
			{
				var edgeAdjust:Number;
				var curPara:ParagraphElement;
				var curParaFormat:ITextLayoutFormat;
				var curParaDirection:String;
				
				for (var lineIndex:int = _flowComposer.findLineIndexAtPosition(_curParcelStart); lineIndex < _curLineIndex; lineIndex++)
	            {
					var line:TextFlowLine = _flowComposer.getLineAt(lineIndex);
					
					// Now check the logical horizontal dimension
					CONFIG::debug { assert(line != null && line.paragraph != null, "found it");}
					
					if (line.paragraph != curPara)
					{
						curPara = line.paragraph;
						curParaFormat = curPara.computedFormat;
						curParaDirection = curParaFormat.direction;
						if (curParaDirection != Direction.LTR)
							edgeAdjust = curParaFormat.paragraphEndIndent;
					}
					
					if (curParaDirection == Direction.LTR)
						edgeAdjust = Math.max(line.lineOffset, 0);
					
					edgeAdjust = verticalText ? line.y - edgeAdjust : line.x - edgeAdjust;
					
					var numberLine:TextLine = TextFlowLine.findNumberLine(line.getTextLine(true));
					
					if (numberLine)
					{
						var numberLineStart:Number = verticalText ? numberLine.y+line.y : numberLine.x+line.x;
						edgeAdjust = Math.min(edgeAdjust,numberLineStart);
					}
					
	             	if (verticalText)
	           			minY = Math.min(edgeAdjust, minY);
	             	else 
	           			minX = Math.min(edgeAdjust, minX);
	            }
			}
            
            // Don't make adjustments for tiny fractional values.
            if (minX != TextLine.MAX_LINE_WIDTH && Math.abs(minX-_parcelLeft) >= 1)
         		_parcelLeft = minX;
            if (maxX != -TextLine.MAX_LINE_WIDTH && Math.abs(maxX-_parcelRight) >= 1)
         		_parcelRight = maxX;
         	if (minY != TextLine.MAX_LINE_WIDTH && Math.abs(minY-_parcelTop) >= 1)
           		_parcelTop = minY;
 		}
 				
		/** Called when we are finished composing a line. Handler for derived classes to override default behavior.  */
		override protected function endLine(textLine:TextLine):void
		{
			super.endLine(textLine);

			if ( !_useExistingLine ) 		
				(_flowComposer as StandardFlowComposer).addLine(_curLine,_curLineIndex);
			
			commitLastLineState (_curLine);
			_curLineIndex++;
		}		
		
		protected override function composeParagraphElement(elem:ParagraphElement, absStart:int):Boolean
		{				
			if (_curLineIndex < 0)
				_curLineIndex = _flowComposer.findLineIndexAtPosition(_curElementStart + _curElementOffset);

			return super.composeParagraphElement(elem, absStart);
		}
		
		/** @private */
		protected override function composeNextLine():TextLine
		{
			CONFIG::debug { assert(_curLineIndex == _flowComposer.findLineIndexAtPosition(_curElementStart + _curElementOffset),"bad _curLineIndex"); }

			// Find out how long a zero-logical height line can be in the parcel at the current position (depth). 
			// See if there is an existing line that is composed up-to-date matching the parcel width. If there is,
			// we try to reuse it, otherwise we create a new line. Once we have placed a line, we will recheck the
			// width in fitLineToParcel to make sure it fits at the (possibly changed) line height.
			var startCompose:int = _curElementStart + _curElementOffset - _curParaStart;
			var line:TextFlowLine = _curLineIndex < _flowComposer.numLines ? (_flowComposer as StandardFlowComposer).lines[_curLineIndex] : null;
			
			var useExistingLine:Boolean = line && (!line.isDamaged() || line.validity == FlowDamageType.GEOMETRY);
			var numberLine:TextLine;
			
			// create numberLine if in a listElement
			if (_listItemElement && _listItemElement.getAbsoluteStart() == _curElementStart+_curElementOffset)
			{
				var peekLine:TextLine;
				if (useExistingLine && (peekLine = line.peekTextLine()) != null)
				{
					// may be null
					numberLine = TextFlowLine.findNumberLine(peekLine);
				}
				else
				{
					var isRTL:Boolean = _curParaElement.computedFormat.direction == Direction.RTL;
					numberLine = TextFlowLine.createNumberLine(_listItemElement, _curParaElement, _flowComposer.swfContext, isRTL ? _parcelList.rightMargin : _parcelList.leftMargin);
				}
				
				pushInsideListItemMargins(numberLine);
			}
			
			_parcelList.getLineSlug(_lineSlug, 0, 1, _textIndent, _curParaFormat.direction == Direction.LTR);
			
			if (useExistingLine && Twips.to(_lineSlug.width) != line.outerTargetWidthTW)
				useExistingLine = false;

			_curLine = useExistingLine ? line : null;
			var textLine:TextLine;
			
			for (;;) 
			{
				while (!_curLine)
				{	
					useExistingLine = false;
					// generate new line
					CONFIG::debug { assert(!_parcelList.atEnd(), "failing to stop"); }
					CONFIG::debug { assert(_curElement is FlowLeafElement, "element must be leaf before calling composeLine"); }					
					CONFIG::debug { validateLineStart(_previousLine, startCompose, _curParaElement); }
					
					textLine = createTextLine(_lineSlug.width, !_lineSlug.wrapsKnockOut /* don't allow emergency breaks next to floats or padded elements */);
					if (textLine)
						break;

					// force advance within the parcel to the next wider slug, or (if there are no more) to the next parcel
					var newDepth:Number = _curParcel.findNextTransition(_lineSlug.depth);
					if (newDepth < Number.MAX_VALUE)
					{
						_parcelList.addTotalDepth(newDepth - _lineSlug.depth);
						if (!_parcelList.getLineSlug(_lineSlug, 0, 1, _textIndent, _curParaFormat.direction == Direction.LTR))
							return null;
					}
					else 
					{
						advanceToNextParcel();
						if (!_parcelList.atEnd())
						{
							if (_parcelList.getLineSlug(_lineSlug, 0, 1, _textIndent, _curParaFormat.direction == Direction.LTR))
								continue;
						}
						popInsideListItemMargins(numberLine);
						return null;
					}						
				}
				
				// updates _lineSlug
				CONFIG::debug { assert(textLine || useExistingLine, "expected non-null textLine because we had to regenerate the line"); }
				if (!textLine)
					textLine = _curLine.getTextLine(true);
				if (fitLineToParcel(textLine, !useExistingLine, numberLine))
					break;	// we have a good line
				_curLine = null;	// keep looking
				if (_parcelList.atEnd())
				{
					popInsideListItemMargins(numberLine);
					return null;
				}
			}
			
			// Clear up user_invalid
			if (_curLine.validity == FlowDamageType.GEOMETRY)
				_curLine.clearDamage(); 
								
			_useExistingLine = useExistingLine;
			
			popInsideListItemMargins(numberLine);

			CONFIG::debug { assert(textLine != null, "textLine != null"); }			
			return textLine;
		}
		
		/** @private */
		override protected function createTextLine(
			targetWidth:Number,	// target width we're composing into
			allowEmergencyBreaks:Boolean	// true to allow words to break in the middle of narrow columns, false to force overset
			):TextLine
        {		
			_curLine = new TextFlowLine(null, null);		// it will be initialized in BaseCompose.createTextLine
			var textLine:TextLine = super.createTextLine(targetWidth, allowEmergencyBreaks);
			
			if (textLine)
	 			textLine.doubleClickEnabled = true;		// allow line to be the target oif a double click event
			else
				_curLine = null;
 			
 			return textLine;
        }
        
        /** @private */
		CONFIG::debug private static function validateLineStart(previousLine:TextLine, lineStart:int, paraNode:ParagraphElement):void
		{
			// If the lines have been released, don't validate
			if (lineStart != 0 && paraNode.getTextBlock().firstLine == null)
				return;
				
	       	var testStart:int = 0;
	    	var testLine:TextLine = previousLine;
	    	while (testLine)
	    	{
				CONFIG::debug { assert(testLine.validity == TextLineValidity.VALID, "previous line in paragraph is not valid!"); }
	    		testStart += testLine.rawTextLength;
	    		testLine = testLine.previousLine;
	    	}
	    	assert(testStart == lineStart, "Bad lines");
			
      		assert(paraNode is ParagraphElement,"composeLine: paraNode must be a para"); 
			var prevLine:TextFlowLine = previousLine ? previousLine.userData as TextFlowLine : null;
			assert(!previousLine || prevLine != null, "prevLine null");
      		assert(!previousLine || !(prevLine.location & TextFlowLineLocation.LAST),"prevLine may not be from a different para"); 
		} 
	}
}
