////////////////////////////////////////////////////////////////////////////////
//
// 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.utils.getQualifiedClassName;
	
	import flashx.textLayout.conversion.ConverterBase;
	import flashx.textLayout.debug.assert;
	import flashx.textLayout.elements.ContainerFormattedElement;
	import flashx.textLayout.elements.DivElement;
	import flashx.textLayout.elements.FlowElement;
	import flashx.textLayout.elements.FlowGroupElement;
	import flashx.textLayout.elements.FlowLeafElement;
	import flashx.textLayout.elements.LinkElement;
	import flashx.textLayout.elements.ListItemElement;
	import flashx.textLayout.elements.ParagraphElement;
	import flashx.textLayout.elements.SpanElement;
	import flashx.textLayout.elements.SubParagraphGroupElementBase;
	import flashx.textLayout.elements.TCYElement;
	import flashx.textLayout.elements.TextFlow;
	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 TextFlowEdit
	{
		tlf_internal static function deleteRange(textFlow:TextFlow, startPos:int, endPos:int):ParagraphElement
		{
			var mergePara:ParagraphElement;

			// If the range to be deleted contains the paragraph end, we may have to merge up the paragraphs when we're done.
			if (endPos > startPos)
			{
				var firstLeafInRange:FlowLeafElement = textFlow.findLeaf(startPos);
				var lastLeafInRange:FlowLeafElement = textFlow.findLeaf(endPos - 1);
				var firstParagraphInRange:ParagraphElement = firstLeafInRange.getParagraph();
				var lastParagraphInRange:ParagraphElement = lastLeafInRange.getParagraph();
				var firstParaStart:int = firstParagraphInRange.getAbsoluteStart();
				var lastParaEnd:int = lastParagraphInRange.getAbsoluteStart() + lastParagraphInRange.textLength;
				// If the selection is inside a single paragraph, merge only if the terminator is included and the start of the paragraph is not.
				// If the two paragraphs are different, merge unless the start and end match exactly
				// Don't merge if the paragraph is an empty paragraph in a list item that has other content (it will just come back again in normalize)
				var doMerge:Boolean = false;
				if (firstParagraphInRange == lastParagraphInRange)
					doMerge = (endPos == lastParaEnd && startPos != firstParaStart);
				else
					doMerge = (startPos != firstParaStart);
				if (doMerge)
				{
					var followingLeaf:FlowLeafElement = textFlow.findLeaf(endPos);
					if (followingLeaf)
					{
						mergePara = followingLeaf.getParagraph();
						if (mergePara.textLength == 1 && mergePara.parent is ListItemElement && mergePara.parent.numChildren > 1)
							mergePara = null;
					}
				}
			}

			deleteRangeInternal(textFlow, startPos, endPos - startPos);
	
			if (mergePara)
			{
				var previousLeaf:FlowLeafElement = mergePara.getFirstLeaf().getPreviousLeaf();
				mergePara = previousLeaf ? previousLeaf.getParagraph() : null;
			}
			
			return mergePara;
		}
		
		private static function deleteRangeInternal(element:FlowGroupElement, relativeStart:int, numToDelete:int):void
		{
			var pendingDeleteStart:int = -1;
			var pendingDeleteCount:int = 0;
			
			var childIndex:int = element.findChildIndexAtPosition(relativeStart);
			while (numToDelete > 0 && childIndex < element.numChildren)
			{
				var child:FlowElement = element.getChildAt(childIndex);
				if (relativeStart <= child.parentRelativeStart && numToDelete >= child.textLength)	// remove the entire child
				{
					if (pendingDeleteStart < 0)
						pendingDeleteStart = childIndex;
					pendingDeleteCount++;
					numToDelete -= child.textLength;
				}
				else // deleting part of the child
				{
					if (pendingDeleteStart >= 0)
					{
						element.replaceChildren(pendingDeleteStart, pendingDeleteStart + pendingDeleteCount);
						childIndex -= pendingDeleteCount;
						pendingDeleteStart = -1;
						pendingDeleteCount = 0;
					}
					var childStart:int = child.parentRelativeStart;
					var childRelativeStart:int = Math.max(relativeStart - childStart, 0);
					var childNumToDelete:int = Math.min(child.textLength - childRelativeStart, numToDelete);
					if (child is SpanElement)
					{
						var span:SpanElement = child as SpanElement;
						span.replaceText(childRelativeStart, childRelativeStart + childNumToDelete, "");
						numToDelete -= childNumToDelete;
					}
					else
					{
						CONFIG::debug { assert (child is FlowGroupElement, "Expected FlowGroupElement"); }
						deleteRangeInternal(child as FlowGroupElement, childRelativeStart, childNumToDelete);
						numToDelete -= childNumToDelete;
					}
				}
				childIndex++
			}
			if (pendingDeleteStart >= 0)
				element.replaceChildren(pendingDeleteStart, pendingDeleteStart + pendingDeleteCount);
		}
				
		// Find the lowest possible FlowElement ancestor of element that can accept prospectiveChild as a child element.
		private static function findLowestPossibleParent(element:FlowGroupElement, prospectiveChild:FlowElement):FlowGroupElement
		{
			while (element && !element.canOwnFlowElement(prospectiveChild))
				element = element.parent;
			return element;
		}
		
		private static function removePasteAttributes(element:FlowElement):void
		{
			if (!element)
				return;
			
			if (element is FlowGroupElement && element.format)
			{
				var flowGroupElement:FlowGroupElement = FlowGroupElement(element);
				if (element.format.getStyle(ConverterBase.MERGE_TO_NEXT_ON_PASTE) !== undefined)
					removePasteAttributes(flowGroupElement.getChildAt(flowGroupElement.numChildren - 1));
			}
			element.setStyle(ConverterBase.MERGE_TO_NEXT_ON_PASTE, undefined);		
		}
		
		// Apply the formatting attributes from the (soon to be) previous element to the insertThis element(s). Used when we're about to 
		// insert the element(s) and we want it to adopt the formatting of its context.
		private static function applyFormatToElement(destinationElement:FlowGroupElement, childIndex:int, insertThis:Object):void
		{
			var formatSourceSibling:FlowElement;
			
			// find the previous sibling and use its formats for the new siblings
			if (childIndex > 0)
				formatSourceSibling = destinationElement.getChildAt(childIndex - 1);
			else
				formatSourceSibling = destinationElement.getChildAt(0);
			if (formatSourceSibling)
			{ 
				var spanFormat:ITextLayoutFormat;
				if (formatSourceSibling is FlowGroupElement)	// take all levels from the sibling down to the root into account
				{
					var element:FlowElement = FlowGroupElement(formatSourceSibling).getLastLeaf();
					var concatFormat:TextLayoutFormat;
					while (element != formatSourceSibling.parent)
					{
						if (element.format)
						{
							if (!concatFormat)
								concatFormat = new TextLayoutFormat(element.format);
							else 
								concatFormat.concatInheritOnly(element.format);
						}
						element = element.parent;
					}
					spanFormat = concatFormat;
				}
				else 
					spanFormat = formatSourceSibling.format;
					
				if (insertThis is Array)
				{
					for each (var scrapElement:FlowElement in insertThis)
						if (scrapElement is FlowLeafElement)
							scrapElement.format = spanFormat;
						else
							scrapElement.format = formatSourceSibling.format;
				}
				else if (insertThis is FlowLeafElement)
					insertThis.format = spanFormat;
				else
					insertThis.format = formatSourceSibling.format;
			}
		}
		/**
		 * Replaces the range of text positions that the <code>startPos</code> and
		 * <code>endPos</code> parameters specify with the <code>textScrap</code> parameter in
		 * <code>theFlow</code>.
		 * <p>To delete elements, pass <code>null</code> for <code>newTextFlow</code>.</p>
		 * <p>To insert an element, pass the same value for <code>startPos</code> and <code>endPos</code>.
		 * <p>The new element will be inserted before the specified index.</p>
		 * <p>To append the TextFlow, pass <code>theFlow.length</code> for <code>startPos</code> and <code>endPos</code>.</p>
		 * 
		 * @param textFlow The TextFlow that is being inserted into.
		 * @param absoluteStart The index value of the first position of the replacement range in the TextFlow.
		 * @param textScrap The TextScrap to be pasted into theFlow.
		 */				
		public static function insertTextScrap(textFlow:TextFlow, absoluteStart:int, textScrap:TextScrap, applyFormat:Boolean):int
		{
			if (!textScrap)
				return absoluteStart;
			
			var scrapFlow:TextFlow = textScrap.textFlow.deepCopy() as TextFlow;
			var scrapLeaf:FlowLeafElement = scrapFlow.getFirstLeaf();

			var destinationLeaf:FlowLeafElement = textFlow.findLeaf(absoluteStart);		
			var insertPosition:int = absoluteStart;
			
			var firstParagraph:Boolean = true;
			var doSplit:Boolean = false;
			
			while (scrapLeaf)
			{
				removePasteAttributes(scrapLeaf);
				var scrapElement:FlowElement = scrapLeaf;		// highest level complete element in the scrap

				// On the first paragraph, it always merges in to the destination paragraph if the destination paragraph has content
				var destinationParagraph:ParagraphElement = destinationLeaf.getParagraph();
				if (firstParagraph && (destinationParagraph.textLength > 1 || applyFormat))
				{
					var scrapParagraph:ParagraphElement = scrapLeaf.getParagraph();
					if (!scrapParagraph.format || scrapParagraph.format.getStyle(ConverterBase.MERGE_TO_NEXT_ON_PASTE) === undefined)
						doSplit = true;
					scrapElement = scrapParagraph.getChildAt(0);
				}
				else
				{
					if (applyFormat && firstParagraph)
					{
						destinationElement = findLowestPossibleParent(destinationLeaf.parent, scrapElement);
						var currentIndex:int = destinationElement.findChildIndexAtPosition(insertPosition - destinationElement.getAbsoluteStart());
						applyFormatToElement(destinationElement, currentIndex, scrapElement);
					}
					// Normally the root element of the scrap is marked as partial, but if not, just assume that its partial (we never paste the TextFlow element)
					while (scrapElement && scrapElement.parent && (!scrapElement.parent.format || scrapElement.parent.format.getStyle(ConverterBase.MERGE_TO_NEXT_ON_PASTE) === undefined) && !(scrapElement.parent is TextFlow))
						scrapElement = scrapElement.parent;
				}
				

				// Find the lowest level parent in the TextFlow that can accept the scrapElement as a child. 
				// If necessary, copy higher up the scrapElement hierarchy to find a match.
				var destinationElement:FlowGroupElement = findLowestPossibleParent(destinationLeaf.parent, scrapElement);
				while (!destinationElement)
				{
					// Nothing in the TextFlow element hierarchy can accept the incoming scrap element.
					// Go up the scrapElement's hierarchy of partial nodes until we find one that can be inserted.
					scrapElement = scrapElement.parent;
					CONFIG::debug { assert(scrapElement != null, "Couldn't find scrapElement that could be pasted"); }
					destinationElement = findLowestPossibleParent(destinationLeaf.parent, scrapElement);
				}
				CONFIG::debug { assert(destinationElement != null, "insertTextScrap failed to find a FlowElement that can take the scrap element"); }
				
				removePasteAttributes(scrapElement);

				var destinationStart:int = destinationElement.getAbsoluteStart();
				if (firstParagraph && doSplit)
				{
					// Split the paragraph, and merge the scrap paragraph to the end of the first paragraph of the destination
					CONFIG::debug { assert(destinationElement is ParagraphElement, "We should be splitting a paragraph"); }
					ModelEdit.splitElement(textFlow, destinationElement, insertPosition - destinationStart);
					var scrapParent:FlowGroupElement = scrapElement.parent;
					var scrapChildren:Array = scrapParent.mxmlChildren;
					scrapParent.replaceChildren(0, scrapParent.numChildren);
					if (scrapParent.parent)
						scrapParent.parent.removeChild(scrapParent);
					if (applyFormat)
						applyFormatToElement(destinationElement, destinationElement.numChildren, scrapChildren);
					destinationElement.replaceChildren(destinationElement.numChildren, destinationElement.numChildren, scrapChildren);
					scrapElement = destinationElement.getChildAt(destinationElement.numChildren - 1);		// last span pasted, so we'll paste next after this
					firstParagraph = false;
				}
				else
				{
					// We're going to add scrapElement as a child of destinationElement at the insertPosition.
					// Split the children of destinationElement if necessary.
					var childIndex:int = destinationElement.findChildIndexAtPosition(insertPosition - destinationElement.getAbsoluteStart());
					var child:FlowElement = destinationElement.getChildAt(childIndex);
					var childStart:int = child.getAbsoluteStart();
					if (insertPosition == childStart + child.textLength)
						++childIndex;
					else if (insertPosition > childStart)
					{
						if (child is FlowLeafElement)
							child.splitAtPosition(insertPosition - childStart);
						else
							ModelEdit.splitElement(textFlow, child as FlowGroupElement, insertPosition - childStart);
						++childIndex;
					}
					if (applyFormat)
						applyFormatToElement(destinationElement, childIndex, scrapElement);
					destinationElement.replaceChildren(childIndex, childIndex, scrapElement);
				}

				
				// Advance to the next destination leaf
				destinationLeaf = (scrapElement is FlowLeafElement) ? FlowLeafElement(scrapElement).getNextLeaf() : FlowGroupElement(scrapElement).getLastLeaf().getNextLeaf();
				insertPosition = destinationLeaf ? destinationLeaf.getAbsoluteStart() : textFlow.textLength - 1;
				
				scrapLeaf = scrapFlow.getFirstLeaf();
			}
			
			return insertPosition;
		}

		/**
		 * Creates a TCY run out of the selected positions.
		 * @param theFlow The TextFlow of interest.
		 * @param startPos The index value of the first position of the TextFlow to be turned into a TCY run.
		 * @param endPos The index value following the end position of the TextFlow to be turned into a TCY run.
		 */	
		public static function makeTCY(theFlow:TextFlow, startPos:int, endPos:int):Boolean
		{
			var madeTCY:Boolean = true;
			var curPara:ParagraphElement = theFlow.findAbsoluteParagraph(startPos);
			if(!curPara)
				return false;
			while(curPara)
			{
				var paraEnd:int = curPara.getAbsoluteStart() + curPara.textLength;
				var curEndPos:int = Math.min(paraEnd, endPos);
				
				//we have an entire para selected and the para only contains a kParaTerminator char, which cannot be
				//made into TCY. 
				if(canInsertSPBlock(theFlow, startPos, curEndPos, TCYElement) && curPara.textLength > 1)
				{
					var new_tcyElem:TCYElement = new TCYElement();
					
					//don't hide an error!
					madeTCY = madeTCY && insertNewSPBlock(theFlow, startPos, curEndPos, new_tcyElem, TCYElement);
				}
				else
					madeTCY = false;
				
				if(paraEnd < endPos)
				{
					curPara = theFlow.findAbsoluteParagraph(curEndPos);	
					startPos = curEndPos;
				}
				else
					curPara = null;
			}
			
			return madeTCY;
		}
		
		/**
		 * Creates one or more LinkElements out of the selected positions. It will go through
		 * every paragraph within the selected position and make links.
		 * @param theFlow The TextFlow of interest.
		 * @param startPos The index value of the first position of the TextFlow to be turned into a link.
		 * @param endPos The index value following the end position of the TextFlow to be turned into a link.
		 * @param urlString The url string to be associated with the link.
		 */			
		public static function makeLink(theFlow:TextFlow, startPos:int, endPos:int, urlString:String, target:String):Boolean
		{
			var madeLink:Boolean = true;
			var curPara:ParagraphElement = theFlow.findAbsoluteParagraph(startPos);
			if(!curPara)
				return false;
				
			while(curPara)
			{
				var paraEnd:int = curPara.getAbsoluteStart() + curPara.textLength;
				var curEndPos:int = Math.min(paraEnd, endPos);
				var linkEndPos:int = (curEndPos == paraEnd) ? (curEndPos - 1) : curEndPos;
				if (linkEndPos > startPos)
				{
					//if the end of the paragraph is < endPos, we are going across bounds
					if(!canInsertSPBlock(theFlow, startPos, linkEndPos, LinkElement))
					{
						return false;
					}
				
					var newLinkElement:LinkElement = new LinkElement();
					newLinkElement.href = urlString;
					newLinkElement.target = target;
				
					//don't hide an error!
					madeLink = madeLink && insertNewSPBlock(theFlow, startPos, linkEndPos, newLinkElement, LinkElement);
				}	
				if(paraEnd < endPos)
				{
					curPara = theFlow.findAbsoluteParagraph(curEndPos);	
					startPos = curEndPos;
				}
				else
					curPara = null;
			}
			
			return madeLink;
		}
		
		
		/**
		 * Removes the TCY block at the selected positions. 
		 * @param theFlow The TextFlow of interest.
		 * @param startPos The index value of the first position of the TextFlow.
		 * @param endPos The index value following the end position of the TextFlow.
		 */			
		public static function removeTCY(theFlow:TextFlow, startPos:int, endPos:int):Boolean
		{
			if (endPos <= startPos)
			{
				return false;
			}
			
			return findAndRemoveFlowGroupElement(theFlow, startPos, endPos, TCYElement);
		}
		
		/**
		 * Removes all LinkElements under the selected positions. It will go through
		 * every paragraph within the selected position and remove any link.
		 * @param theFlow The TextFlow of interest.
		 * @param startPos The index value of the first position of the TextFlow.
		 * @param endPos The index value following the end position of the TextFlow.
		 */			
		public static function removeLink(theFlow:TextFlow, startPos:int, endPos:int):Boolean
		{
			if (endPos <= startPos)
			{
				return false;
			}
			
			return findAndRemoveFlowGroupElement(theFlow, startPos, endPos, LinkElement);
		}
		
		/**
		 * @private
		 * insertNewSPBlock - add a SubParagraphGroupElementBase (spg) to <code>theFlow</code> at the indicies specified by <code>startPos</code> and
		 * <code>endPos</code>.  The <code>newSPB</code> will take ownership of any FlowElements within the range and will split them
		 * as needed.  If the parent of the FlowGroupElement indicated by <code>startPos</code> is the same as <code>spgClass</code> then
		 * the method fails and returns false because a spg cannot own children of the same class as itself.  Any spg of type <code>spgClass</code>
		 * found within the indicies, however, is subsumed into <code>newSPB</code>, effectively replacing it.
		 *
		 * @param theFlow:TextFlow - The TextFlow that is the destination for the newSPB
		 * @param startPos:int - The absolute index value of the first position of the range in the TextFlow to perform the insertion.
		 * @param endPos:int - The index value following the end position of the range in the TextFlow to perform the insertion.
		 * @param newSPB:SubParagraphGroupElementBase - The new SubParagraphElement which is to be added into theFlow.
		 * @param spgClass:Class - the class of the fbe we intend to add.
		 * 
		 * Examples: Simple and complex where insertion is of <code>spgClass</code> b.  Selection is l~o
		 *		1) <a><span>ghijklmnop</span></a>
		 * 		2) <a><span>ghij</span><b><span>klm</span></b><span>nop</span></a>
		 * 		3) <a><span>ghijk</span><c><span>lmn</span></c><span>op</span></a>
		 * 
		 */
		tlf_internal static function insertNewSPBlock(theFlow:TextFlow, startPos:int, endPos:int, newSPB:SubParagraphGroupElementBase, spgClass:Class):Boolean
		{
			var curPos:int = startPos;
			var curFBE:FlowGroupElement = theFlow.findAbsoluteFlowGroupElement(curPos);
			var elementIdx:int = 0;
			
			CONFIG::debug{ assert(curFBE != null, "No FBE at location curPos(" + curPos + ")!");}
			
			//if we are at the last "real" glyph of the paragraph, include the terminator.
			var paraEl:ParagraphElement = curFBE.getParagraph();
			if(endPos == (paraEl.getAbsoluteStart() + paraEl.textLength - 1))
				++endPos;
			
			//before processing this any further, we need to make sure that we are not
			//splitting a spg which is contained within the same type of spg as the curFBE's parent.
			//for example, if we had a tcyElement inside a linkElement, then we cannot allow a link element
			//to be made within the tcyElement as the link would not function.  As a rule, a SubParagraphElement
			//cannot own a child of the same class.
			//
			//However, if the curFBE is parented by a spg and the objects have the same start and end, then we are doing
			//a replace and we're not splitting the parent. Check if the bounds are the same and if so, don't skip it...
			var parentStart:int = curFBE.parent.getAbsoluteStart();
			var curFBEStart:int = curFBE.getAbsoluteStart();
			if(curFBE.parent && curFBE.parent is spgClass && 
				!(parentStart == curFBEStart && parentStart + curFBE.parent.textLength == curFBEStart + curFBE.textLength))
			{
				return false;
			}
			
			//entire FBE is selected and is not a paragraph, get its parent.
			if(!(curFBE is ParagraphElement) && curPos == curFBEStart && curPos + curFBE.textLength <= endPos)
			{
				elementIdx = curFBE.parent.getChildIndex(curFBE);
				curFBE = curFBE.parent;
			}
			//first, if the curFBE is of the same class as the newSPB, then we need to split it to allow for insertion
			//of the new one IF the start position is > the start of the curFBE
			//
			//running example after this block:
			//	1) <a><span>ghijk</span><span>lmnop</span></a>
			//	2) <a><span>ghij</span><b><span>k</span></b><b><span>lm</span></b><span>nop</span></a>
			//	3) <a><span>ghijk</span><c><span>lmn</span></c><span>op</span></a> - no change
			if(curPos >= curFBEStart)
			{
				if(!(curFBE is spgClass))
					elementIdx = findAndSplitElement(curFBE, elementIdx, curPos, true);
				else
				{
					elementIdx = findAndSplitElement(curFBE.parent, curFBE.parent.getChildIndex(curFBE), curPos, false);
					curFBE = curFBE.parent;
				}
			}
			
			//now that the curFBE has been split, we want to insert the newSPB into the flow and then start absorbing the
			//contents...
			//running example after this block:
			//	1) <a><span>ghijk</span><b></b><span>lmnop</span></a>
			//	2) <a><span>ghij</span><b><span>k</span></b><b></b><b><span>lm</span></b><span>nop</span></a>
			//	3) <a><span>ghijk</span><b></b><c><span>lmn</span></c><span>op</span></a> - no change
			//
		//	we need another use case here where selection is entire sbp and selection runs from the head of a spg to
		//	part way through it - so that a) does into parent and b) goes into spg
			
			// if this is case 2, then the new element must go into the parent...
			if(curFBE is spgClass)
			{
				curFBEStart = curFBE.getAbsoluteStart();
				elementIdx = curFBE.parent.getChildIndex(curFBE);
				if(curPos > curFBEStart)//we're splitting the element, not replacing it...
					elementIdx += 1;
				
				//if the spg, curFBE is entirely selected then we want to use the parent, not the item itself.
				while(endPos >= curFBEStart + curFBE.textLength)
				{
					//we need access to the parent, which contains both the start and end not the FBE we just split
					curFBE = curFBE.parent;
				}
				curFBE.replaceChildren(elementIdx, elementIdx, newSPB);
			}
			else
			{
				//we're inserting into the curFBE
				curFBE.replaceChildren(elementIdx, elementIdx, newSPB);
			}
			
			//see subsumeElementsToSPBlock to see effects on running example
			subsumeElementsToSPBlock(curFBE, elementIdx + 1, curPos, endPos, newSPB, spgClass);
			
			return true;
		}
		
		
		/**
		 * @private
		 * splitElement - split <code>elem</code> at the relative index of <code>splitPos</code>.  If <code>splitSubBlockContents</code>
		 * is true, split the contents of <code>elem</code> if it is a SubParagraphGroupElementBase, otherwise just split <code>elem</code>
		 * 
		 * @param elem:FlowElement - the FlowElement to split
		 * @param splitPos:int - The elem relative index indicating where to split
		 * @param splitSubBlockContents:Boolean - boolean indicating whether a SubParagraphGroupElementBase is to be split OR that it's contents
		 * should be split.  For example, are we splitting a link or are we splitting the child of the link
		 * 
		 * <spg><span>ABCDEF</span></spg>
		 * 
		 * if <code>splitPos</code> indicated index between C and D, then if <code>splitSubBlockContents</code> equals true,
		 * result is:
		 * 
		 * <spg><span>ABC</span><span>DEF</span></spg>
		 * 
		 * if <code>splitSubBlockContents</code> equals false, result is:
		 * 
		 * <spg><span>ABC</span></spg><spg><span>DEF</span></spg>
		 */
		tlf_internal static function splitElement(elem:FlowElement, splitPos:int, splitSubBlockContents:Boolean):void
		{
			CONFIG::debug{ assert(splitPos < elem.textLength, "trying to splic FlowElement at illegal index!"); }
			if (elem is SpanElement)
			{
				SpanElement(elem).splitAtPosition(splitPos);
			}
			else if(elem is SubParagraphGroupElementBase && splitSubBlockContents)
			{
				var subBlock:SubParagraphGroupElementBase = SubParagraphGroupElementBase(elem);
				// Split the SpanElement of the block at splitPos.  If the item at the splitPos is not a SpanElement, no action occurs.
				var tempElem:SpanElement = subBlock.findLeaf(splitPos) as SpanElement;
				if (tempElem)
					tempElem.splitAtPosition(splitPos - tempElem.getElementRelativeStart(subBlock));
			}
			else if (elem is FlowGroupElement)
			{
				FlowGroupElement(elem).splitAtPosition(splitPos);
			}
			else
			{
				CONFIG::debug { assert(false, "Trying to split on an illegal FlowElement"); }				
			}
		}
		
		/**
		 * @private
		 * findAndSplitElement - starting at the child <code>elementIdx</code> of <code>fbe</code>, iterate
		 * through the elements untill we find the one located at the aboslute index of <code>startIdx</code>. Upon
		 * locating the child, split either the element itself OR its children based on the value of <code>splitSubBlockContents</code>
		 * 
		 * @param fbe:FlowGroupElement - the FBE into which the newSPB is being inserted.
		 * @param elementIdx:int - The index into the <code>fbe's</code> child list to start
		 * @param startIdx:int - The absolute index value into the TextFlow.
		 * @param splitSubBlockContents:Boolean - boolean indicating whether a subElement is to be split OR that it's contents
		 * should be split.  For example, are we splitting a link or are we splitting the child of the link
		 * 
		 * <p>ZYX<link>ABCDEF</link>123</p>
		 * 
		 * if we are inserting a TCY into the link, splitSubBlockContents should be false. We want to split the span ABCDEF such that result is:
		 * <p>ZYX<link>AB<tcy>CD</tcy>EF</link>123</p>
		 * 
		 * if we are creating a new link from X to B, then we want the link to split and splitSubBlockContents should be false:
		 * 
		 * <p>ZY<link>XAB</link><link>CDEF</link>123</p>
		 * 
		 * @return int - the index of the last child of <code>fbe</code> processed.
		 */
		tlf_internal static function findAndSplitElement(fbe:FlowGroupElement, elementIdx:int, startIdx:int, splitSubBlockContents:Boolean):int
		{
			var curFlowEl:FlowElement = null;
			var curIndexInPar:int = startIdx - fbe.getAbsoluteStart();
			
			while(elementIdx < fbe.numChildren)
			{
				curFlowEl = fbe.getChildAt(elementIdx);
				if (curIndexInPar == curFlowEl.parentRelativeStart)
					return elementIdx;
				if ((curIndexInPar > curFlowEl.parentRelativeStart) && (curIndexInPar < curFlowEl.parentRelativeEnd))
				{
					splitElement(curFlowEl, curIndexInPar - curFlowEl.parentRelativeStart, splitSubBlockContents); 
				}
				++elementIdx;
			}
			return elementIdx;
		}
		
		/**
		 * @private
		 * subsumeElementsToSPBlock - incorporates all elements of <code>parentFBE</code> into
		 * the <code>newSPB</code> between the <code>curPos</code> and <code>endPos</code>.  If a child of
		 * <code>parentFBE</code> is of type <code>spgClass</code> then the child's contents are removed from the child,
		 * added to the <code>newSPB</code>, the child is then removed from the <code>parentFBE</code>
		 * 
		 * @param parentFBE:FlowGroupElement - the FBE into which the newSPB is being inserted.
		 * @param startPos:int - The index value of the first position of the replacement range in the TextFlow.
		 * @param endPos:int - The index value following the end position of the replacement range in the TextFlow.
		 * @param newSPB:SubParagraphGroupElementBase - the new SubParagraphGroupElementBase we intend to insert.
		 * @param spgClass:Class - the class of the fbe we intend to insert.
		 * 
		 * @return int - the aboslute index in the text flow after insertion.
		 * 
		 *  Examples: Simple and complex where insertion is of <code>spgClass</class> b.  Selection is l~o
		 *		1) <a><span>ghijk</span><b></b><span>lmnop</span></a>
		 *		2) <a><span>ghij</span><b><span>k</span></b><b></b><b><span>lm</span></b><span>nop</span></a>
		 * 
		 * 	parentFBE = <a>
		 *  elementIdx = 1) 2, 2) 3
		 *  curPos = 5
		 *  endPos = 9
		 *  newSPB is of type <b>
		 */
		tlf_internal static function subsumeElementsToSPBlock(parentFBE:FlowGroupElement, elementIdx:int, curPos:int, endPos:int, newSPB:SubParagraphGroupElementBase, spgClass:Class):int
		{
			var curFlowEl:FlowElement = null;
			
			//if we have an invalid index, then skip out.  elementIdx will always point one
			//element beyond the one we are inserting....
			if(elementIdx >= parentFBE.numChildren)
				return curPos;
			
			while (curPos < endPos)
			{
				//running example: curFlowEl is the element immediately after the inserted newSPB:
				//	1) <a><span>ghijk</span><b></b><span>lmnop</span></a>
				//		points to span-lmnop
				//	2) <a><span>ghij</span><b><span>k</span></b><b></b><b><span>lm</span></b><span>nop</span></a>
				//		points to b-lm
				curFlowEl = parentFBE.getChildAt(elementIdx);
				
				//if the curFlowEl is of the Class we are adding (spgClass), and the entire thing is selected,
				//then we are adding the entire block, but not spliting it - perform the split on the next block
				
				//I think this can be safely removed from here as ownership of contents is handled below.
				//leaving in commented out code in case we need to revert - gak 05.01.08
			/*	if(curFlowEl is spgClass && curPos == curFlowEl.getAbsoluteStart() && curFlowEl.getAbsoluteStart() + curFlowEl.textLength <= endPos)
				{
					curPos = parentFBE.getAbsoluteStart() + parentFBE.textLength;
					continue;
				}*/
				
				//if the endPos is less than the length of the curFlowEl, then we need to split it.
				//if the curFlowEl is NOT of class type spgClass, then we need to break it
				//
				//Use case: splitting a link in two (or three as will be the result with head and tail sharing
				//attributes...
				//running example 1 hits this, but 2 does not. Using variation of 2:
				//
				// example: 1) <a><span>ghijk</span><b></b><span>lmnop</span></a>
				// 			2a) <a><span>fo</span><b></b><b><span>obar</span></b></a> - selection: from o~a
				//
				// after this code:
				//			1) <a><span>ghijk</span><b></b><span>lmno</span><span>p</span></a>
				//			2a) <a><span>fo</span><b></b><b><span>oba</span></b><b><span>or</span></b></a>
				if ((curPos + curFlowEl.textLength) > endPos)
				{
					splitElement(curFlowEl, endPos - curFlowEl.getAbsoluteStart(), !(curFlowEl is spgClass));  //changed to curFlowEl from newSPB as newSPB should be of type spgClass
				}
				
				//add the length before replacing the elements
				curPos += curFlowEl.textLength;
				
				//running example: after parentFBE.replaceChildren
				//
				//	1) curFlowEl = <span>lmno</span>
				//		<a><span>ghijk</span><b></b>{curFlowEl}<span>p</span></a>
				//
				//	2) curFlowEl = <b><span>lm</span></b>
				//		<a><span>ghij</span><b><span>k</span></b><b></b>{curFlowEl}<span>nop</span></a>
				parentFBE.replaceChildren(elementIdx, elementIdx + 1);
				
				//if the curFlowEl is of type spgClass, then we need to take its children and
				//add them to the newSPB because a spg cannot contain a child of the same class
				//as itself
				//
				// exmaple: 2) curFlowEl = <b><span>lm</span></b>
				if (curFlowEl is spgClass)
				{
					var subBlock:SubParagraphGroupElementBase = curFlowEl as SubParagraphGroupElementBase;
					//elementCount == 1 - <span>lm</span>
					while (subBlock.numChildren > 0)
					{
						//fe[0] = <span>lm</span>
						var fe:FlowElement = subBlock.getChildAt(0);
						//<span></span>
						subBlock.replaceChildren(0, 1);
						//<b><span>lm</span></b>
						newSPB.replaceChildren(newSPB.numChildren, newSPB.numChildren, fe);
					}
					//when compelete, example 2 is:
					//2) <a><span>ghij</span><b><span>k</span></b><b><span>lm</span></b><span>nop</span></a>
				}
				else 
				{
					//example 1, curFlowEl is <span>lmno</span>, so this is not hit
					//
					// extending element <a> from foo~other
					// <a>foo</a><b>bar<a>other</a><b>
					// curFlowEl = <b>bar<a>other</a><b>
					//
					// since <b> is a spg, we need to walk it's contents and remove any <a> elements
					if(curFlowEl is SubParagraphGroupElementBase)
					{
						//we need to dive into this spgClass and remove any fbes of type spgClass
						//pass in the curFlowEl as the newSPB, remove any spgs of type spgClass, then 
						//perform the replace on the newSPB passed in here
						//
						//ignore the return value of the recursive call as the length has already been
						//accounted for above
						flushSPBlock(curFlowEl as SubParagraphGroupElementBase, spgClass);
					}
					newSPB.replaceChildren(newSPB.numChildren, newSPB.numChildren, curFlowEl);
					
					if(newSPB.numChildren == 1 && curFlowEl is SubParagraphGroupElementBase)
					{
						var childSPGE:SubParagraphGroupElementBase = curFlowEl as SubParagraphGroupElementBase;
						//running example:
						//a.precedence = 800, tcy.precedence = kMinSPGEPrecedence
						//this = <tcy><a><span>fooBar</span></a><tcy>
						//childSPGE = <a><span>fooBar</span></a>
						if(childSPGE.textLength == newSPB.textLength && (curPos >= endPos))
						{
							CONFIG::debug { assert(childSPGE.precedence != newSPB.precedence, "normalizeRange found two equal SPGEs"); }
	
							//if the child's precedence is higher than mine, I need to swap
							if(childSPGE.precedence > newSPB.precedence)
							{
								//first, remove the child
								//this = <tcy></tcy>
								newSPB.replaceChildren(0,1);
								
								//we need to flop this object for the child
								while(childSPGE.numChildren > 0)
								{
									//tempFE = <span>fooBar</span>
									var tempFE:FlowElement = childSPGE.getChildAt(0);
									//child = <a></a>
									childSPGE.replaceChildren(0,1);
									//this = <tcy><span>fooBar</span></tcy>
									newSPB.replaceChildren(newSPB.numChildren, newSPB.numChildren, tempFE);
								}
								
								var myIdx:int = newSPB.parent.getChildIndex(newSPB);
								CONFIG::debug{ assert(myIdx >= 0, "Invalid index!  How can a SubParagraphGroupElementBase normalizing not have a parent!"); }
								
								//add childSPGE in my place
								newSPB.parent.replaceChildren(myIdx, myIdx + 1, childSPGE)
								
								//childSPGE = <tcy><a><span>fooBar</span></a></tcy>
								childSPGE.replaceChildren(0,0,newSPB);
							}
						}
					}
				}
				
			}
	
			return curPos;
		}
		
		/**
		 * @private
		 * findAndRemoveFlowGroupElement 
		 *
		 * @param theFlow The TextFlow that is containing the elements to remove.
		 * @param startPos The index value of the first position of the range in the TextFlow where we want to perform removal.
		 * @param endPos The index value following the end position of the range in the TextFlow where we want to perform removal.
		 * @param fbeClass Class the class of the fbe we intend to remove.
		 * 
		 * Walks through the elements of <code>theFlow</code> looking for any FlowGroupElement of type <code>fbeClass</class>
		 * On finding one, it removes the FBE's contents and adds them back into the FBE's parent.  If the class of object is
		 * embedded within another spg and this removal would break the parent spg, then the method does nothing.
		 * 
		 * Example:
		 * 	<link>ABC<tcy>DEF</tcy>GHI</link>
		 * 	Selection is on E and removal of link is attempted.
		 * 	Because E is a child of a spg (tcy), and removing the link from E would split the parent spg (link),
		 *  the action is disallowed.
		 * 
		 * Running example:
		 * 	1) <link><tcy><span>foo</span></tcy><span>bar</span></link>
		 * @return Boolean - true if items are removed or none are found.  false if operation is illegal.
		 */ 
		tlf_internal static function findAndRemoveFlowGroupElement(theFlow:TextFlow, startPos:int, endPos:int, fbeClass:Class):Boolean
		{
			var curPos:int = startPos;
			var curEl:FlowElement;
			
			//walk through the elements
			while (curPos < endPos)
			{
				var containerFBE:FlowGroupElement = theFlow.findAbsoluteFlowGroupElement(curPos);
				
				//if the start of the parent is the same as the start of the current containerFBE, then
				//we potentially have the wrong object.  We need to walk up the parents until we get to
				//the one which starts at our start AND is the topmost object at that index.
				//example: <a><b>foo</b> bar</a> - getting the object at "f" will yield the <b> element, not <a>
				while(containerFBE.parent && containerFBE.parent.getAbsoluteStart() == containerFBE.getAbsoluteStart() && 
					!(containerFBE.parent is ParagraphElement) && !(containerFBE is ParagraphElement)) //don't go beyond paragraph
				{
					containerFBE = containerFBE.parent;
				}
				
				//if the absoluteFBE is the item we are trying to remove, we need to work with its parent, so
				//reassign containerFBE.  For example, if an entire link were selected, we'd need to get it's parent to
				//perform the removal
				if(containerFBE is fbeClass)
					containerFBE = containerFBE.parent;
					
				//before processing this any further, we need to make sure that we are not
				//splitting a spg which is contained within the same type of spg as the curFBE's parent.
				//for example, if we had a tcyElement inside a linkElement, then we cannot allow a link element
				//to be broken within the tcyElement as the link would have to split the TCY.
				var ancestorOfFBE:FlowGroupElement = containerFBE.parent;
				while(ancestorOfFBE != null && !(ancestorOfFBE is fbeClass))
				{
					if(ancestorOfFBE.parent is fbeClass)
					{
						return false;
					}
					ancestorOfFBE = ancestorOfFBE.parent;
				}
				
				//if this is a sbe block contained in another sbe, and it is entire within the 
				//selection bounds, we need to use the parent sbe's container.  If it is splitting
				//the child sbe, we don't allow this and it is handled later...
				var containerFBEStart:int = containerFBE.getAbsoluteStart();
				if(ancestorOfFBE is fbeClass && (containerFBEStart >= curPos && containerFBEStart + containerFBE.textLength <= endPos))
					containerFBE = ancestorOfFBE.parent;
					
				var childIdx:int = containerFBE.findChildIndexAtPosition(curPos - containerFBEStart);
				curEl = containerFBE.getChildAt(childIdx);
				if(curEl is fbeClass)
				{
					CONFIG::debug{ assert(curEl is SubParagraphGroupElementBase, "Wrong FBE type!  Trying to remove illeage FBE!"); }
					var curFBE:FlowGroupElement = curEl as FlowGroupElement;
					
					//get it's parent and the index of the curFBE
					var parentBlock:FlowGroupElement = curFBE.parent;
					var idxInParent:int = parentBlock.getChildIndex(curFBE);
					
					//if the curPos is not at the head of the SPB, then we need to split it here
					//curFBE will point to the FBE starting at curPos
					if(curPos > curFBE.getAbsoluteStart())
					{
						splitElement(curFBE, curPos - curFBE.getAbsoluteStart(), false);
						curPos = curFBE.getAbsoluteStart() + curFBE.textLength;
						continue;
					}
					
					//if curFBE goes beyond the endPos, then we need to split off the tail.
					if (curFBE.getAbsoluteStart() + curFBE.textLength > endPos)
					{
						splitElement(curFBE, endPos - curFBE.getAbsoluteStart(), false);
					}
				
					//apply the length of the curFBE to the curPos tracker.  Do this before 
					//removing the contents or it will be 0!
					curPos = curFBE.getAbsoluteStart() + curFBE.textLength;
					
					//walk all the contents of the FBE into it's parent container
					while (curFBE.numChildren > 0)
					{
						var childFE:FlowElement = curFBE.getChildAt(0);
						curFBE.replaceChildren(0, 1);
						parentBlock.replaceChildren(idxInParent, idxInParent, childFE);
						idxInParent++; 
					}
					
					//remove the curFBE
					parentBlock.replaceChildren(idxInParent, idxInParent + 1);
				}
				else if(curEl is SubParagraphGroupElementBase) //check all the parents...
				{
					var curSPB:SubParagraphGroupElementBase = SubParagraphGroupElementBase(curEl);
					if(curSPB.numChildren == 1)
						curPos = curSPB.getAbsoluteStart() + curSPB.textLength;
					else
					{
						curEl = curSPB.getChildAt(curSPB.findChildIndexAtPosition(curPos - curSPB.getAbsoluteStart()));
						curPos = curEl.getAbsoluteStart() + curEl.textLength;
					}
				}
				else
				{
					//the current block isn't the type we're looking for, so just go to the end of the
					//FlowElement and continue
					curPos = curEl.getAbsoluteStart() + curEl.textLength;
				}
				
			}
			
			return true;
		}
		
		/**
		 * @private
		 * canInsertSPBlock 
		 * 
		 * validate that we a valid selection to allow for insertion of a subBlock.  The rules are as
		 * follows:
		 * 	endPos > start
		 * 	the new block will not span multiple paragraphs
		 *  if the block is going into a SubParagraphGroupElementBase, it must not split the block:
		 * 		example:  Text 		- ABCDEFG with a link on CDE
		 * 		legal new Block		- D, CD, CDE, [n-chars]CDE[n1-chars]
		 * 		illegal new Block 	- [1 + n-chars]C[D], [D]E[1 + n-chars]
		 * 			exception - if the newBlock is the same class as the one we are trying to split
		 * 			then we can truncate the original and add its contents to the new one, or extend it
		 * 			as appropriate
		 * 
		 * @param theFlow The TextFlow that is containing the elements to validate.
		 * @param startPos The index value of the first position of the range in the TextFlow to test.
		 * @param endPos The index value following the end position of the range in the TextFlow to test.
		 * @param blockClass Class the class of the fbe we intend to insert.
		 */
		tlf_internal static function canInsertSPBlock(theFlow:TextFlow, startPos:int, endPos:int, blockClass:Class):Boolean
		{
			if(endPos <= startPos)
				return false;
				
			var anchorFBE:FlowGroupElement = theFlow.findAbsoluteFlowGroupElement(startPos);
			if(anchorFBE.getParentByType(blockClass))
				anchorFBE = anchorFBE.getParentByType(blockClass) as FlowGroupElement;
				
			var tailFBE:FlowGroupElement = theFlow.findAbsoluteFlowGroupElement(endPos - 1);
			if(tailFBE.getParentByType(blockClass))
				tailFBE = tailFBE.getParentByType(blockClass) as FlowGroupElement;
			
			//if these are the same FBEs then we are safe to insert a SubParagraphGroupElementBase
			if(anchorFBE == tailFBE)
				return true;
			//make sure that the two FBEs belong to the same paragraph!
			else if(anchorFBE.getParagraph() != tailFBE.getParagraph())
				return false;
			else if(anchorFBE is blockClass && tailFBE is blockClass)//they're the same class, OK to merge, split, etc...
				return true;
			else if(anchorFBE is SubParagraphGroupElementBase && !(anchorFBE is blockClass))
			{
				var anchorStart:int = anchorFBE.getAbsoluteStart();
				if(startPos > anchorStart && endPos > anchorStart + anchorFBE.textLength)
					return false;
			}
			else if((anchorFBE.parent is SubParagraphGroupElementBase || tailFBE.parent is SubParagraphGroupElementBase)
				&& anchorFBE.parent != tailFBE.parent)
			{
				//if either FBE parent is a SPGE and they are not the same, prevent the split.  
				return false;
			}	
			
			//if we got here, then the anchorFBE is OK, check the tail.  If endPos is pointing to the
			//0th character of a FlowGroupElement, we don't need to worry about the tail.
			if(tailFBE is SubParagraphGroupElementBase && !(tailFBE is blockClass) && endPos > tailFBE.getAbsoluteStart())
			{
				var tailStart:int = tailFBE.getAbsoluteStart();
				if(startPos < tailStart && endPos < tailStart + tailFBE.textLength)
					return false;
			}	
			return true;
		}
		
		/**
		 * @private flushSPBlock recursively walk a spg looking for elements of type spgClass.  On finding one,
		 * remove it's children and then remove the object itself.  Since spg's cannot hold children of the same type
		 * as themselves, recursion is only needed for spg's of a class other than that of spgClass.
		 * 
		 * example: subPB = <b>bar<a>other</a><b> extending an <a> element to include all of "other"
		 */ 
		tlf_internal static function flushSPBlock(subPB:SubParagraphGroupElementBase, spgClass:Class):void
		{
			var subParaIter:int = 0;
	
			//example, subPB has 2 elements, <span>bar</span> and <a><span>other</span></a>
			while(subParaIter < subPB.numChildren)
			{
				//subParaIter == 0, subFE = <span>bar</span> skip the FE and move to next
				//subParaIter == 1, subFE = <a><span>other</span></a> - is a spgClass
				var subFE:FlowElement = subPB.getChildAt(subParaIter);
				if(subFE is spgClass)
				{
					//subParaIter == 1, subFE = <a><span>other</span></a>
					var subChildFBE:FlowGroupElement = subFE as FlowGroupElement;
					while(subChildFBE.numChildren > 0)
					{
						//subFEChild = <span>other</span>
						var subFEChild:FlowElement = subChildFBE.getChildAt(0);
						//subFEChild = <a></a>
						subChildFBE.replaceChildren(0, 1);
						//subPB = <b>barother<a></a><b>
						subPB.replaceChildren(subParaIter, subParaIter, subFEChild);
					}
					
					//increment so that subParaIter points to the element we just emptied
					++subParaIter;
					//remove the empty child
					//subPB = <b>barother<b>
					subPB.replaceChildren(subParaIter, subParaIter + 1);
				}
				else if(subFE is SubParagraphGroupElementBase)
				{
					flushSPBlock(subFE as SubParagraphGroupElementBase, spgClass);
					++subParaIter;
				}
				else
					++subParaIter;//go to next child
			}
		}
		
		/** returns next paragraph in reading order after para. Used for merging paragraphs after delete.  */
		tlf_internal static function findNextParagraph(para:ParagraphElement):ParagraphElement
		{
			if (para)
			{
				var leaf:FlowLeafElement = para.getLastLeaf();
				leaf = leaf.getNextLeaf();
				if (leaf)
					return leaf.getParagraph();
			}
			return null;
		/*	var sibParagraph:ParagraphElement;
			if (para && para.parent)
			{
				var child:FlowGroupElement = para;
				var parent:FlowGroupElement = para.parent;
				
				var myidx:int = parent.getChildIndex(child);
				
				// go up the chain till not on last child
				while(myidx == parent.numChildren-1)
				{
					child = parent;
					parent = parent.parent;
					myidx = parent.getChildIndex(child);
				}
				if (myidx != parent.numChildren-1)
				{
					// go down the first child descendents till reach a paragraph
					var sibElement:FlowGroupElement = parent.getChildAt(myidx+1) as FlowGroupElement;
					while(sibElement && !(sibElement is ParagraphElement))
					{
						sibElement = sibElement.getChildAt(0) as FlowGroupElement;	
					}
					sibParagraph = sibElement as ParagraphElement;
				}
			}
			return sibParagraph; */
		}
		
		/** if parent is a singleton element, deletes it, then repeats deletion of singletons up the parent chain.  Used after paragraph merge. */
		tlf_internal static function removeEmptyParentChain(parent:FlowGroupElement):IMemento
		{
			var mementoList:MementoList = new MementoList(parent.getTextFlow());
			while(parent && (parent.numChildren == 0))
			{
				var grandParent:FlowGroupElement = parent.parent;
				if(grandParent)
				{
					var parentIdx:int = grandParent.getChildIndex(parent);
					mementoList.push(ModelEdit.removeElements(grandParent.getTextFlow(), grandParent, parentIdx, 1));
					//grandParent.replaceChildren(parentIdx, parentIdx+1);
				}
				parent = grandParent;
			}
			return mementoList;
		}
		
		/** Joins this paragraph's next sibling to this if it is a paragraph */
		static public function joinNextParagraph(para:ParagraphElement, inSameParent:Boolean):IMemento
		{		
			var nextPara:ParagraphElement = findNextParagraph(para);
			if (nextPara && (!inSameParent || para.parent == nextPara.parent))
				return joinToElement(para, nextPara);
			return null;
		}

		/** Joins this paragraph's next sibling to this if it is a paragraph */
		static public function joinToNextParagraph(para:ParagraphElement, inSameParent:Boolean):MementoList
		{		
			var sibParagraph:ParagraphElement = findNextParagraph(para);
			if (sibParagraph && (!inSameParent || para.parent == sibParagraph.parent))
				return joinToNextElement(para, sibParagraph);
			return null;
		}

		/** Joins this element2 to element1 -- all children of element2 added to end of element1 */
		static public function joinToElement(element1:FlowGroupElement, element2:FlowGroupElement):IMemento
		{	
			var list:MementoList;
			
			if (element1 && element2)
			{
		/*		list = new MementoList(element1.getTextFlow());
				
				var elementList:Array = element2.mxmlChildren;
				
				list.push(ModelEdit.removeElements(element2.getTextFlow(), element2, 0, element2.numChildren)); // remove children of the second element

				for(var i:int=0; i<elementList.length; ++i) // add them to the first element
				{
					list.push(ModelEdit.addElement(element1.getTextFlow(), elementList[i], element1, element1.numChildren));
				}
				// remove (empty) element2 and chain of any empty parents
				list.push(removeEmptyParentChain(element2));
				return list;
				*/
				return ModelEdit.joinElement(element2.getTextFlow(), element1, element2);
			}
			return list;
		}
		
		/** Joins this element1 to element2 -- all children of element1 added to front of element2 */
		static public function joinToNextElement(element1:FlowGroupElement, element2:FlowGroupElement):MementoList
		{		
			var list:MementoList;

			if (element1 && element2)
			{
				list = new MementoList(element1.getTextFlow());

				var elementList:Array = element1.mxmlChildren;
				list.push(ModelEdit.removeElements(element1.getTextFlow(), element1, 0, element1.numChildren)); // remove children of the first element
				for(var i:int=elementList.length - 1; i>=0; --i) // add them to the second element
				{
					list.push(ModelEdit.addElement(element2.getTextFlow(), elementList[i], element2, 0));
				}
				// remove (empty) element1 and chain of any empty parents
				list.push(removeEmptyParentChain(element1));
				return list;
			}
			return list;
		}
		
								
	}
}
