////////////////////////////////////////////////////////////////////////////////
//
// 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.edit
{
	import flash.geom.Point;
	import flash.utils.getQualifiedClassName;
	
	import flashx.textLayout.container.ContainerController;
	import flashx.textLayout.debug.assert;
	import flashx.textLayout.elements.FlowElement;
	import flashx.textLayout.elements.FlowGroupElement;
	import flashx.textLayout.elements.FlowLeafElement;
	import flashx.textLayout.elements.InlineGraphicElement;
	import flashx.textLayout.elements.ParagraphElement;
	import flashx.textLayout.elements.SpanElement;
	import flashx.textLayout.elements.SubParagraphGroupElementBase;
	import flashx.textLayout.elements.TextFlow;
	import flashx.textLayout.formats.Float;
	import flashx.textLayout.formats.ITextLayoutFormat;
	import flashx.textLayout.formats.TextLayoutFormat;
	import flashx.textLayout.tlf_internal;
	
	use namespace tlf_internal;

	[ExcludeClass]
	/**
	 * Encapsulates all methods necessary for dynamic editing of a text.  The methods are all static member functions of this class.
	 * @private - because we can't make it tlf_internal. Used by the operations package 
	 */
 	public class ParaEdit
	{
		/**
		 * Inserts text into specified paragraph
		 * @param textFlow		TextFlow to insert into
		 * @param absoluteStart	index relative to beginning of the TextFlow to insert text
		 * @param text	actual text to insert 
		 * @param createNewSpan	flag to force creation of a new span
		 */
		public static function insertText(textFlow:TextFlow, absoluteStart:int,insertText:String, createNewSpan:Boolean):SpanElement
		{
			if (insertText.length == 0)
				return null;	// error? other sanity checks needed here?
				
			var sibling:FlowElement = textFlow.findLeaf(absoluteStart);
			var siblingIndex:int;
			var paragraph:ParagraphElement = sibling.getParagraph();
			var paraStart:int = paragraph.getAbsoluteStart();
			var paraSelBegIdx:int = absoluteStart - paraStart;
			
			if (paraStart == absoluteStart)		// insert to start of paragraph
				siblingIndex = 0;
			else 
			{
				// If we're at the start a span, go to the previous span in the same paragraph, and insert at the end of it
				if (paraSelBegIdx == sibling.getElementRelativeStart(paragraph))
					sibling = FlowLeafElement(sibling).getPreviousLeaf(paragraph);	
				siblingIndex = sibling.parent.getChildIndex(sibling) + 1;
			}
			var insertParent:FlowGroupElement = sibling.parent;
			
			// If we are adding text to the start or end of a link, it doesn't allow the insertion to group with the link.
			// So in that case, we will insert to the element beside the position that is *not* part of the link.
			var curSPGElement:SubParagraphGroupElementBase = sibling.getParentByType(SubParagraphGroupElementBase) as SubParagraphGroupElementBase;
			while (curSPGElement != null)
			{
				var subParInsertionPoint:int = paraSelBegIdx - curSPGElement.getElementRelativeStart(paragraph);
				if (((subParInsertionPoint == 0) && (!curSPGElement.acceptTextBefore())) ||
					((!curSPGElement.acceptTextAfter() && (subParInsertionPoint == curSPGElement.textLength || 
					(subParInsertionPoint == curSPGElement.textLength - 1 && (sibling == paragraph.getLastLeaf()))))))
				{
					createNewSpan = true;
					sibling = insertParent;
					insertParent = insertParent.parent;
					curSPGElement = curSPGElement.getParentByType(SubParagraphGroupElementBase) as SubParagraphGroupElementBase;
					siblingIndex = insertParent.getChildIndex(sibling) + 1;
				} else {
					break;
				}
			}
				
			// adjust the flow so that we are in a span for the insertion
			var insertSpan:SpanElement = sibling as SpanElement;
			if (!insertSpan || createNewSpan)
			{
				var newSpan:SpanElement = new SpanElement();
				var insertIdx:int;
				if (siblingIndex > 0)
				{
					var relativeStart:int = paraSelBegIdx - sibling.getElementRelativeStart(paragraph);
					if (createNewSpan)
					{
						if (relativeStart == 0)
							siblingIndex--;
						else if (relativeStart != sibling.textLength)
							sibling.splitAtPosition(relativeStart);		// we'll insert between the two elements
					}
				}
				insertParent.replaceChildren(siblingIndex, siblingIndex, newSpan);
				var formatElem:FlowLeafElement = newSpan.getPreviousLeaf(paragraph);
				if (formatElem == null)
					newSpan.format = newSpan.getNextLeaf(paragraph).format;
				else
					newSpan.format = formatElem.format;
					
				insertSpan = newSpan;
			}
			
			var runInsertionPoint:int = paraSelBegIdx - insertSpan.getElementRelativeStart(paragraph);
			
				
			insertSpan.replaceText(runInsertionPoint, runInsertionPoint, insertText);
			return insertSpan;
		}
		
		private static function deleteTextInternal(para:ParagraphElement, paraSelBegIdx:int, totalToDelete:int):void
		{
			var composeNode:FlowElement;
			var curSpan:SpanElement;
			
			var curNumToDelete:int;
			var curSpanDeletePos:int = 0;
			
			while (totalToDelete > 0)
			{
				composeNode = para.findLeaf(paraSelBegIdx);
				CONFIG::debug { assert(composeNode is SpanElement,"deleteTextInternal: leaf element is not a span"); }
								
				curSpan = composeNode as SpanElement;
				var curSpanRelativeStart:int = curSpan.getElementRelativeStart(para);
				curSpanDeletePos = paraSelBegIdx - curSpanRelativeStart;
				if (paraSelBegIdx > (curSpanRelativeStart + curSpan.textLength))
				{
					curNumToDelete = curSpan.textLength;
				}
				else
				{
					curNumToDelete = (curSpanRelativeStart + curSpan.textLength) - paraSelBegIdx;
				}
				
				if (totalToDelete < curNumToDelete)
				{
					curNumToDelete = totalToDelete;
				}
				
				curSpan.replaceText(curSpanDeletePos, curSpanDeletePos + curNumToDelete, "");
				if (curSpan.textLength == 0)
				{
					var delIdx:int = curSpan.parent.getChildIndex(curSpan);
					curSpan.parent.replaceChildren(delIdx,delIdx+1,null);						
				}
				totalToDelete -= curNumToDelete;
			}			
		}
		
		public static function deleteText(para:ParagraphElement, paraSelBegIdx:int, totalToDelete:int):void
		{
			var lastParPos:int = para.textLength - 1;
			if ((paraSelBegIdx < 0) || (paraSelBegIdx > lastParPos))
			{
				//not much we can do. There's nothing to delete in this paragraph
				return;
			}
			
			if (totalToDelete <= 0)
			{
				//can't delete a negative number of characters... just return
				return;
			}
			
			var endPos:int = paraSelBegIdx + totalToDelete - 1;
			if (endPos > lastParPos)
			{
				endPos = lastParPos;
				totalToDelete = endPos - paraSelBegIdx + 1
			}
			
			deleteTextInternal(para,paraSelBegIdx,totalToDelete);
		}
		
		/**
		 * Creates image and inserts it into specified FlowGroupElement
		 * @param flowBlock	FlowGroupElement to insert image into
		 * @param flowSelBegIdx	index relative to beginning of the FlowGroupElement to insert image
		 * @param urlString	the url of image to insert
		 * @param width	the width of the image
		 * @param height the height of the image
		 * @param options none supported
		 */
		public static function createImage(flowBlock:FlowGroupElement, flowSelBegIdx:int,source:Object, width:Object, height:Object, options:Object, pointFormat:ITextLayoutFormat):InlineGraphicElement
		{
			//first, split the element that we are on
			var curComposeNode:FlowElement = flowBlock.findLeaf(flowSelBegIdx);
			var posInCurComposeNode:int = 0;
			if (curComposeNode != null)
			{
				posInCurComposeNode = flowSelBegIdx - curComposeNode.getElementRelativeStart(flowBlock); // curComposeNode.parentRelativeStart;
			}			
			
			if ((curComposeNode != null) && (posInCurComposeNode > 0) && (posInCurComposeNode < curComposeNode.textLength))
			{
				//it is a LeafElement, and not position 0. It has to be a Span 
				(curComposeNode as SpanElement).splitAtPosition(posInCurComposeNode);
			}
			
			//the FlowElement or FlowGroupElement is now split.  Insert the image now.
			var imgElem:InlineGraphicElement = new InlineGraphicElement();
			imgElem.height = height;
			imgElem.width = width;
			imgElem.float = options ? options.toString() : undefined;
			
			var src:Object = source;
			var embedStr:String = "@Embed";
			if (src is String && src.length > embedStr.length && src.substr(0, embedStr.length) == embedStr) {
				// we should be dealing with an embedded asset. They are of the form "url=@Embed(source='path-to-asset')"
				var searchStr:String = "source=";
				var index:int = src.indexOf(searchStr, embedStr.length);
				if (index > 0) {
					index += searchStr.length;
					index = src.indexOf("'", index);
					src = src.substring(index+1, src.indexOf("'", index+1));
				}
			}
			imgElem.source = src;
			
			while (curComposeNode && curComposeNode.parent != flowBlock)
			{
				curComposeNode = curComposeNode.parent;
			}

			var elementIdx:int = curComposeNode != null ? flowBlock.getChildIndex(curComposeNode) : flowBlock.numChildren;
			if (curComposeNode && posInCurComposeNode > 0)
				elementIdx++;
			flowBlock.replaceChildren(elementIdx,elementIdx,imgElem);
			
			//clone characterFormat from the left OR iff the the first element the right
			var p:ParagraphElement = imgElem.getParagraph();
			var attrElem:FlowLeafElement = imgElem.getPreviousLeaf(p);
			if (!attrElem)
				attrElem = imgElem.getNextLeaf(p);
			CONFIG::debug { assert(attrElem != null, "no element to get attributes from"); }
			
			if (attrElem.format || pointFormat)
			{
				var imageElemFormat:TextLayoutFormat = new TextLayoutFormat(attrElem.format);
				if (pointFormat)
					imageElemFormat.apply(pointFormat);
				imgElem.format = imageElemFormat;
			}
			return imgElem;			
		}		
		
		/** Merge changed attributes into this
		 */
		static private function splitForChange(span:SpanElement,begIdx:int,rangeLength:int):SpanElement
		{
			var startOffset:int = span.getAbsoluteStart();
			if (begIdx == startOffset && rangeLength == span.textLength)
				return span;
				
			// element must be split into spans
			var elemToUpdate:SpanElement;
			var origLength:int = span.textLength;
			
			var begRelativeIdx:int = begIdx - startOffset;
			if (begRelativeIdx > 0)
			{
				// We create an initial span to hold the text before the new span, then
				// a following span for the specified range.
				elemToUpdate = span.splitAtPosition(begRelativeIdx) as SpanElement;
				if (begRelativeIdx + rangeLength < origLength)
					elemToUpdate.splitAtPosition(rangeLength);
			}
			else
			{
				// The specified range falls at the start of the element, so this span is the 
				// one that's getting the new format.
				span.splitAtPosition(rangeLength);
				elemToUpdate = span;
			}

			return elemToUpdate;
		}
		
		private static function undefineDefinedFormats(target:TextLayoutFormat,undefineFormat:ITextLayoutFormat):void
		{
			if (undefineFormat)
			{
				// this is fairly rare so this operation is not optimizied
				var tlfUndefineFormat:TextLayoutFormat;
				if (undefineFormat is TextLayoutFormat)
					tlfUndefineFormat = undefineFormat as TextLayoutFormat;
				else 
					tlfUndefineFormat = new TextLayoutFormat(undefineFormat);
				for (var prop:String in tlfUndefineFormat.styles)
					target.setStyle(prop, undefined);
			}
		}

		/**
		 * Apply formatting changes to a range of text in the FlowElement
		 *
		 * @param begIdx	text index of first text in span
		 * @param rangeLength	number of characters to modify
		 * @param applyFormat		Character Format to apply to content
		 * @param undefineFormat	Character Format to undefine to content
		 * @return begIdx + number of actual actual characters modified.
		 */
		static private function applyCharacterFormat(leaf:FlowLeafElement, begIdx:int, rangeLength:int, applyFormat:ITextLayoutFormat, undefineFormat:ITextLayoutFormat):int
		{
			var newFormat:TextLayoutFormat = new TextLayoutFormat(leaf.format);
			if (applyFormat)
				newFormat.apply(applyFormat);
			undefineDefinedFormats(newFormat,undefineFormat);
			return setCharacterFormat(leaf, newFormat, begIdx, rangeLength);
		}
		
		/**
		 * Set formatting to a range of text in the FlowElement
		 *
		 * @param format	Character Format to apply to content
		 * @param begIdx	text index of first text in span
		 * @param rangeLength	number of characters to modify
		 * @return starting position of following span
		 */
		static private function setCharacterFormat(leaf:FlowLeafElement, format:ITextLayoutFormat, begIdx:int, rangeLength:int):int
		{
			var startOffset:int = leaf.getAbsoluteStart();
			if (!(format is ITextLayoutFormat) || !TextLayoutFormat.isEqual(ITextLayoutFormat(format),leaf.format))
			{				
				var para:ParagraphElement = leaf.getParagraph();
				var paraStartOffset:int = para.getAbsoluteStart();
				
				// clip rangeLength to the length of this span. Extend the rangeLength by one to include the terminator if
				// it is in the span, and the end of the range abuts the terminator. That way the terminator will stay in the 
				// last span.
				var begRelativeIdx:int = begIdx - startOffset;
				if (begRelativeIdx + rangeLength > leaf.textLength)
					rangeLength = leaf.textLength - begRelativeIdx;
				if (begRelativeIdx + rangeLength == leaf.textLength - 1 && (leaf is SpanElement) && SpanElement(leaf).hasParagraphTerminator)
					++rangeLength;
				
				var elemToUpdate:FlowLeafElement
				if (leaf is SpanElement)
					elemToUpdate = splitForChange(SpanElement(leaf),begIdx,rangeLength);
				else
				{
					CONFIG::debug { assert(rangeLength >= leaf.textLength,"unable to split non-span leaf"); }
					elemToUpdate = leaf;
				}
				
				if (format is ITextLayoutFormat)
					elemToUpdate.format = ITextLayoutFormat(format);
				else
					elemToUpdate.setStylesInternal(format);
				
				return begIdx+rangeLength;
			}
			
			rangeLength = leaf.textLength;
			return startOffset+rangeLength;
		}
		
		public static function applyTextStyleChange(flowRoot:TextFlow,begChange:int,endChange:int,applyFormat:ITextLayoutFormat,undefineFormat:ITextLayoutFormat):void
		{	
			// TODO: this code only works for span's.  Revisit when new FlowLeafElement types enabled
			var workIdx:int = begChange;
			while (workIdx < endChange)
			{
				var elem:FlowLeafElement = flowRoot.findLeaf(workIdx);
				CONFIG::debug  { assert(elem != null,"null FlowLeafElement found"); }
				workIdx = applyCharacterFormat(elem,workIdx,endChange-workIdx,applyFormat,undefineFormat); 
			}
		}
		
		// used for undo of operation
		public static function setTextStyleChange(flowRoot:TextFlow,begChange:int, endChange:int, coreStyle:ITextLayoutFormat):void
		{
			// TODO: this code only works for span's.  Revisit when new FlowLeafElement types enabled
			var workIdx:int = begChange;
			while (workIdx < endChange)
			{
				var elem:FlowElement = flowRoot.findLeaf(workIdx);
				CONFIG::debug  { assert(elem != null,"null FlowLeafElement found"); }
				workIdx = setCharacterFormat(FlowLeafElement(elem),coreStyle,workIdx,endChange-workIdx);
			}
		}
		
		public static function splitElement(elem:FlowGroupElement, splitPos:int):FlowGroupElement
		{
			CONFIG::debug { assert(splitPos >= 0 && splitPos <= elem.textLength, "Invalid call to ParaEdit.splitElement"); }
			
			var rslt:FlowGroupElement = elem.splitAtPosition(splitPos) as FlowGroupElement;
			
			// rslt always follows elem
			

			if (!(rslt is SubParagraphGroupElementBase))
			{
				
				// need to insure there is a paragraph and a span on each side
				var rsltParagraph:FlowGroupElement = rslt;
				while (!(rsltParagraph is ParagraphElement) && rsltParagraph.numChildren)
					rsltParagraph = rsltParagraph.getChildAt(0) as FlowGroupElement;
				
				var elemParagraph:FlowGroupElement = elem;
				while (!(elemParagraph is ParagraphElement) && elemParagraph.numChildren)
					elemParagraph = elemParagraph.getChildAt(elemParagraph.numChildren-1) as FlowGroupElement;
				
				CONFIG::debug { assert (elemParagraph is ParagraphElement || rsltParagraph is ParagraphElement,"ParaEdit.splitElement didn't find at least one paragraph"); }
				
				var p:ParagraphElement;
				
				if (!(elemParagraph is ParagraphElement))
				{
					// clone rlstParagraph
					p = rsltParagraph.shallowCopy() as ParagraphElement;
					elemParagraph.addChild(p);
					elemParagraph = p;
				}
				else if (!(rsltParagraph is ParagraphElement))
				{
					p = elemParagraph.shallowCopy() as ParagraphElement;
					rsltParagraph.addChild(p);
					rsltParagraph = p;						
				}
				
				// if we have an empty before or after para need to make sure the formats got copied
				if (elemParagraph.textLength <= 1)
				{
					elemParagraph.normalizeRange(0,elemParagraph.textLength);
					elemParagraph.getLastLeaf().quickCloneTextLayoutFormat(rsltParagraph.getFirstLeaf());
				}
				else if (rsltParagraph.textLength <= 1)
				{
					rsltParagraph.normalizeRange(0,rsltParagraph.textLength);
					rsltParagraph.getFirstLeaf().quickCloneTextLayoutFormat(elemParagraph.getLastLeaf());
				}
			}

			return rslt;
		}		
		// TODO: rewrite this method by moving the elements.  This is buggy.
		public static function mergeParagraphWithNext(para:ParagraphElement):Boolean
		{
			var indexOfPara:int = para.parent.getChildIndex(para);
			
			// last can't merge
			if (indexOfPara == para.parent.numChildren-1)
				return false;
				
			var nextPar:ParagraphElement  = para.parent.getChildAt(indexOfPara + 1) as ParagraphElement;
			// next is not a paragraph
			if (nextPar == null)
				return false;
				
			// remove nextPar from its parent - do this first because it will require less updating of starts and lengths
			para.parent.replaceChildren(indexOfPara+1,indexOfPara+2,null);
			if (nextPar.textLength <= 1) 
				return true;
			
			// move all the elements
			while (nextPar.numChildren)
			{
				var elem:FlowElement = nextPar.getChildAt(0);
				nextPar.replaceChildren(0,1,null);
				para.replaceChildren(para.numChildren,para.numChildren,elem);
				if ((para.numChildren >  1) && (para.getChildAt(para.numChildren - 2).textLength == 0))
				{
					//bug 1658164
					//imagine that the last element of para is only a kParaTerminator (like a single
					//span of length 1 that only contains a kParaTerminator, and you merge with the
					//next paragraph. That kParaTerminator will move, leaving an empty leaf element
					para.replaceChildren(para.numChildren - 2, para.numChildren - 1, null);
				}
			}

			return true;
		}
		
		public static function cacheParagraphStyleInformation(flowRoot:TextFlow,begSel:int,endSel:int,undoArray:Array):void
		{
			while (begSel <= endSel && begSel >= 0)
			{
				var para:ParagraphElement = flowRoot.findLeaf(begSel).getParagraph();
				
				// build an object holding the old style and format
				var obj:Object = new Object();
				obj.begIdx = para.getAbsoluteStart();
				obj.endIdx = obj.begIdx + para.textLength - 1;
				obj.attributes = new TextLayoutFormat(para.format);
				undoArray.push(obj);

				begSel = obj.begIdx + para.textLength;
			}
		}

		/**
		 * Replace the existing paragraph attributes with the incoming attributes.
		 * 
		 * @param flowRoot	text flow where paragraphs are
		 * @param format	attributes to apply
		 * @param beginIndex	text index within the first paragraph in the range
		 * @param endIndex		text index within the last paragraph in the range
		 */
		// used for undo of operation
		public static function setParagraphStyleChange(flowRoot:TextFlow,begChange:int, endChange:int, format:ITextLayoutFormat):void
		{
			var beginPara:int = begChange;
			while (beginPara <= endChange)
			{
				var para:ParagraphElement = flowRoot.findLeaf(beginPara).getParagraph();
				
				para.format = format ? new TextLayoutFormat(format) : null;
				beginPara = para.getAbsoluteStart() + para.textLength;
			}
		}
		
		/**
		 * Additively apply the paragraph formating attributes to the paragraphs in the specified range.
		 * Each non-null field in the incoming format is copied into the existing paragraph attributes.
		 * 
		 * @param flowRoot	text flow where paragraphs are
		 * @param format	attributes to apply
		 * @param beginIndex	text index within the first paragraph in the range
		 * @param endIndex		text index within the last paragraph in the range
		 */
		public static function applyParagraphStyleChange(flowRoot:TextFlow,begChange:int,endChange:int,applyFormat:ITextLayoutFormat,undefineFormat:ITextLayoutFormat):void
		{	
			var curIndex:int = begChange;
			while (curIndex <= endChange)
			{
				var leaf:FlowLeafElement = flowRoot.findLeaf(curIndex);
				if (!leaf)
					break;
				var para:ParagraphElement = leaf.getParagraph();
				
				// now, need to get the change from "format" and apply to para. We make
				// a new ParagraphFormat object instead of changing the ParagraphFormat
				// already in the paragraph so that if the object is shared, other uses
				// in other paragraphs will not be affected.
				var newFormat:TextLayoutFormat = new TextLayoutFormat(para.format);
				if (applyFormat)
					newFormat.apply(applyFormat);
				undefineDefinedFormats(newFormat,undefineFormat);
				para.format = newFormat;
				
				curIndex = para.getAbsoluteStart() + para.textLength;
			}		
		}

		public static function cacheStyleInformation(flowRoot:TextFlow,begSel:int,endSel:int,undoArray:Array):void
		{
			var elem:FlowElement = flowRoot.findLeaf(begSel);
			var elemLength:int = elem.getAbsoluteStart()+elem.textLength-begSel;
			var countRemaining:int = endSel - begSel;
			CONFIG::debug { assert(countRemaining != 0,"cacheStyleInformation called on point selection"); }
			for (;;)
			{
				// build an object holding the old style and format
				var obj:Object = new Object();

				obj.begIdx = begSel;
				var objLength:int = Math.min(countRemaining, elemLength);
				obj.endIdx = begSel + objLength;
				
				// just the styles
				obj.style = new TextLayoutFormat(elem.format);
				undoArray.push(obj);
				
				countRemaining -= Math.min(countRemaining, elemLength);
				if (countRemaining == 0)
					break;
				
				// advance
				begSel = obj.endIdx;
				
				elem = flowRoot.findLeaf(begSel);
				elemLength = elem.textLength;
			}
		}
		
		public static function cacheContainerStyleInformation(flowRoot:TextFlow,begIdx:int,endIdx:int,undoArray:Array):void
		{
			CONFIG::debug { assert(begIdx <= endIdx,"bad indexeds passed to ParaEdit.cacheContainerStyleInformation");  }
			if (flowRoot.flowComposer)
			{
				var startIdx:int = flowRoot.flowComposer.findControllerIndexAtPosition(begIdx,false);
				if (startIdx == -1)
					return;
				var endIdx:int = flowRoot.flowComposer.findControllerIndexAtPosition(endIdx,true);
				if (endIdx == -1)
					endIdx = flowRoot.flowComposer.numControllers-1;
				while (startIdx <= endIdx)
				{
					var controller:ContainerController = flowRoot.flowComposer.getControllerAt(startIdx);
					var obj:Object = new Object();
					obj.container = controller;
					// save just the styles
					obj.attributes = new TextLayoutFormat(controller.format);
					undoArray.push(obj);
					startIdx++;
				}
			}
		}

		public static function applyContainerStyleChange(flowRoot:TextFlow,begIdx:int,endIdx:int,applyFormat:ITextLayoutFormat,undefineFormat:ITextLayoutFormat):void
		{
			CONFIG::debug { assert(begIdx <= endIdx,"bad indexes passed to ParaEdit.cacheContainerStyleInformation");  }
			if (flowRoot.flowComposer)
			{
				var startIdx:int = flowRoot.flowComposer.findControllerIndexAtPosition(begIdx,false);
				if (startIdx == -1)
					return;
				var endIdx:int = flowRoot.flowComposer.findControllerIndexAtPosition(endIdx,true);
				if (endIdx == -1)
					endIdx = flowRoot.flowComposer.numControllers-1;
				var controllerIndex:int = flowRoot.flowComposer.findControllerIndexAtPosition(begIdx,false);
				while (startIdx <= endIdx)
				{
					var controller:ContainerController = flowRoot.flowComposer.getControllerAt(startIdx);
					var newFormat:TextLayoutFormat = new TextLayoutFormat(controller.format);
					if (applyFormat)
						newFormat.apply(applyFormat);
					undefineDefinedFormats(newFormat,undefineFormat);
					controller.format = newFormat;
					startIdx++;
				}
			}
		}

		/** obj is created by cacheContainerStyleInformation */
		public static function setContainerStyleChange(obj:Object):void
		{
			obj.container.format = obj.attributes as ITextLayoutFormat;
		}
	}
}
