////////////////////////////////////////////////////////////////////////////////
//
// 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
{

/**
 * The ElementRange class represents the range of objects selected within a text flow.
 * 
 * <p>The beginning elements 
 * (such as <code>firstLeaf</code>) are always less than or equal to the end elements (in this case, <code>lastLeaf</code>)
 * for each pair of values in an element range.</p>
 * 
 * @see flashx.textLayout.elements.TextFlow
 * 
 * @playerversion Flash 10
 * @playerversion AIR 1.5
 * @langversion 3.0
 */	
public class ElementRange
{
	import flashx.textLayout.compose.IFlowComposer;
	import flashx.textLayout.container.ContainerController;
	import flashx.textLayout.debug.assert;
	import flashx.textLayout.elements.ContainerFormattedElement;
	import flashx.textLayout.elements.FlowLeafElement;
	import flashx.textLayout.elements.ParagraphElement;
	import flashx.textLayout.elements.SubParagraphGroupElementBase;
	import flashx.textLayout.elements.TextFlow;
	import flashx.textLayout.formats.Category;
	import flashx.textLayout.formats.ITextLayoutFormat;
	import flashx.textLayout.formats.TextLayoutFormat;
	import flashx.textLayout.property.Property;
	import flashx.textLayout.tlf_internal;
	
	use namespace tlf_internal;
	
	private var _absoluteStart:int;
	private var _absoluteEnd:int;
	private var _firstLeaf:FlowLeafElement;
	private var _lastLeaf:FlowLeafElement;
	private var _firstParagraph:ParagraphElement;
	private var _lastParagraph:ParagraphElement;
	private var _textFlow:TextFlow;	
	
	/** 
	 * The absolute text position of the FlowLeafElement object that contains the start of the range.
	 *  
	 * @playerversion Flash 10
	 * @playerversion AIR 1.5
 	 * @langversion 3.0
	 */
	public function get absoluteStart():int
	{
		return _absoluteStart;
	}
	public function set absoluteStart(value:int):void
	{
		_absoluteStart = value;
	}
	
	/** 
	 * The absolute text position of the FlowLeafElement object that contains the end of the range. 
	 * 
	 * @playerversion Flash 10
	 * @playerversion AIR 1.5
 	 * @langversion 3.0
 	 */
	public function get absoluteEnd():int
	{
		return _absoluteEnd;
	}
	public function set absoluteEnd(value:int):void
	{
		_absoluteEnd = value;
	}

	/** 
	 * The FlowLeafElement object that contains the start of the range. 
	 * 
	 * @playerversion Flash 10
	 * @playerversion AIR 1.5
 	 * @langversion 3.0
	 */
	public function get firstLeaf():FlowLeafElement
	{
		return _firstLeaf;
	}
	public function set firstLeaf(value:FlowLeafElement):void
	{
		_firstLeaf = value;
	}

	/** 
	 * The FlowLeafElement object that contains the end of the range. 
	 * 
	 * @playerversion Flash 10
	 * @playerversion AIR 1.5
 	 * @langversion 3.0
	*/
	public function get lastLeaf():FlowLeafElement
	{
		return _lastLeaf;
	}
	public function set lastLeaf(value:FlowLeafElement):void
	{
		_lastLeaf = value;
	}

	/** 
	 * The ParagraphElement object that contains the start of the range. 
	 * 
	 * @playerversion Flash 10
	 * @playerversion AIR 1.5
 	 * @langversion 3.0
	 */
	public function get firstParagraph():ParagraphElement
	{
		return _firstParagraph;
	}
	public function set firstParagraph(value:ParagraphElement):void
	{
		_firstParagraph = value;
	}
	
	/** 
	 * The ParagraphElement object that contains the end of the range. 
	 * 
	 * @playerversion Flash 10
	 * @playerversion AIR 1.5
 	 * @langversion 3.0
	*/
	public function get lastParagraph():ParagraphElement
	{
		return _lastParagraph;
	}
	public function set lastParagraph(value:ParagraphElement):void
	{
		_lastParagraph = value;
	}
	
	/** 
	 * The TextFlow object that contains the range. 
	 * 
	 * @playerversion Flash 10
	 * @playerversion AIR 1.5
 	 * @langversion 3.0
	 */
	public function get textFlow():TextFlow
	{
		return _textFlow;
	}	
	public function set textFlow(value:TextFlow):void
	{
		_textFlow = value;
	}	
	
	// This constructor function is here just to silence a compile warning in Eclipse. There
	// appears to be no way to turn the warning off selectively.
	/** @private */
	CONFIG::debug public function ElementRange()
	{
		super();
	}
	
 	// NOTE: We've been back and forth on this - should a range selection show null attributes or beginning of range attributes?
	// After looking at this for a while I want it to show beginning of range attributes.  Two main reasons
	// 1. If the range contains different objects but homogoneous settings we should show the attributes.
	// 2. If we show null attributes on a range selection there's no way to, for example, turn BOLD off.
	// Try this at home - restore the old code. Select the entire text.  Turn Bold on.  Can't turn bold off.
	// Please don't revert this without further discussion.
	// Ideally we would have a way of figuring out which attributes are homogoneous over the selection range
	// and which were not and showing, for example, a "half-checked" bold item.  We'd have to work this out for all the properties.
	
	// OLD CODE that shows null attribute settings on a range selection
	// var charAttr:ICharacterFormat = selRange.begElem == selRange.endElem ? selRange.begElem.computedCharacterFormat : new CharacterFormat();
	// var paraAttr:IParagraphFormat = selRange.begPara == selRange.endPara ? selRange.begPara.computedParagraphFormat : new ParagraphFormat();


	/** 
	 * The format attributes of the container displaying the range. 
	 * 
	 * <p>If the range spans more than one container, the format of the first container is returned.</p>
	 *  
	 * @playerversion Flash 10
	 * @playerversion AIR 1.5
 	 * @langversion 3.0
	 */
	public function get containerFormat():ITextLayoutFormat
	{
		// see NOTE above before changing!!
		var container:ContainerController;
		var flowComposer:IFlowComposer = _textFlow.flowComposer;
		if (flowComposer)
		{
			var idx:int = flowComposer.findControllerIndexAtPosition(absoluteStart);
			if (idx != -1)
				container = flowComposer.getControllerAt(idx);
		}
		return container ? container.computedFormat : _textFlow.computedFormat;
	}
		
	/** 
	 * The format attributes of the paragraph containing the range. 
	 * 
	 * <p>If the range spans more than one paragraph, the format of the first paragraph is returned.</p>
	 * 
	 * @playerversion Flash 10
	 * @playerversion AIR 1.5
 	 * @langversion 3.0
	 */
	public function get paragraphFormat():ITextLayoutFormat
	{
		// see NOTE above before changing!!
  		return firstParagraph.computedFormat;
 	}
		
	/** 
	 * The format attributes of the characters in the range. 
	 * 
	 * <p>If the range spans more than one FlowElement object, which means that more than one
	 * character format may exist within the range, the format of the first FlowElement object is returned.</p>
	 * 
	 * @playerversion Flash 10
	 * @playerversion AIR 1.5
 	 * @langversion 3.0
	 */
	public function get characterFormat():ITextLayoutFormat
	{
		// see NOTE above before changing!!
 		return firstLeaf.computedFormat;
	}
	
	/**
	 * Gets the character format attributes that are common to all characters in the text range or current selection.
	 * 
	 * <p>Format attributes that do not have the same value for all characters in the element range are set to 
	 * <code>null</code> in the returned TextLayoutFormat instance.</p>
	 * 
	 * @return The common character style settings
	 * 
	 * @playerversion Flash 10
	 * @playerversion AIR 1.5
	 * @langversion 3.0
	 */
	public function getCommonCharacterFormat():TextLayoutFormat
	{		
		var leaf:FlowLeafElement = firstLeaf;
		var attr:TextLayoutFormat = new TextLayoutFormat(leaf.computedFormat);
		
		for (;;)
		{
			if (leaf == lastLeaf)
				break;
			leaf = leaf.getNextLeaf();
			attr.removeClashing(leaf.computedFormat);
		}

		return Property.extractInCategory(TextLayoutFormat, TextLayoutFormat.description, attr, Category.CHARACTER, false) as TextLayoutFormat;
	}
	
	/**
	 * Gets the paragraph format attributes that are common to all paragraphs in the element range.
	 * 
	 * <p>Format attributes that do not have the same value for all paragraphs in the element range are set to 
	 * <code>null</code> in the returned TextLayoutFormat instance.</p>
	 * 	 
	 * @return The common paragraph style settings
	 * 
	 * @see flashx.textLayout.edit.ISelectionManager#getCommonParagraphFormat
	 * 
	 * @playerversion Flash 10
	 * @playerversion AIR 1.5
	 * @langversion 3.0
	 */
	public function getCommonParagraphFormat():TextLayoutFormat
	{
		var para:ParagraphElement = firstParagraph;
		var attr:TextLayoutFormat = new TextLayoutFormat(para.computedFormat);
		for (;;)
		{
			if (para == lastParagraph)
				break;
			para = _textFlow.findAbsoluteParagraph(para.getAbsoluteStart()+para.textLength);
			attr.removeClashing(para.computedFormat);
		}
		return Property.extractInCategory(TextLayoutFormat,TextLayoutFormat.description,attr,Category.PARAGRAPH, false) as TextLayoutFormat;
	}
	
	/**
		 * Gets the container format attributes that are common to all containers in the element range.
	 * 
	 * <p>Format attributes that do not have the same value for all containers in the element range are set to 
	 * <code>null</code> in the returned TextLayoutFormat instance.</p>
	 * 	 
	 * @return The common paragraph style settings
	 * 
	 * @see flashx.textLayout.edit.ISelectionManager#getCommonParagraphFormat	 * 
		* @playerversion Flash 10
	 * @playerversion AIR 1.5
	 * @langversion 3.0
	 */
	public function getCommonContainerFormat():TextLayoutFormat
	{	
		var flowComposer:IFlowComposer = _textFlow.flowComposer;
		if (!flowComposer)
			return null;

		var index:int = flowComposer.findControllerIndexAtPosition(this.absoluteStart);
		if (index == -1)
			return null;
		var controller:ContainerController = flowComposer.getControllerAt(index);
		var attr:TextLayoutFormat = new TextLayoutFormat(controller.computedFormat);
		while (controller.absoluteStart+controller.textLength < absoluteEnd)
		{
			index++;
			if (index == flowComposer.numControllers)
				break;
			controller = flowComposer.getControllerAt(index);
			attr.removeClashing(controller.computedFormat);
		}
		
		return Property.extractInCategory(TextLayoutFormat,TextLayoutFormat.description,attr,Category.CONTAINER, false) as TextLayoutFormat;
	}
	
	/** 
	 * Creates an ElementRange object.
	 * 
	 * @param textFlow	the text flow
	 * @param beginIndex absolute text position of the first character in the text range
	 * @param endIndex one beyond the absolute text position of the last character in the text range
	 * 
	 * @playerversion Flash 10
	 * @playerversion AIR 1.5
 	 * @langversion 3.0
	 */
	static public function createElementRange(textFlow:TextFlow, absoluteStart:int, absoluteEnd:int):ElementRange
	{
		var rslt:ElementRange = new ElementRange();
		if (absoluteStart == absoluteEnd)
		{
			rslt.absoluteStart = rslt.absoluteEnd = absoluteStart;
			rslt.firstLeaf = textFlow.findLeaf(rslt.absoluteStart);
			rslt.firstParagraph = rslt.firstLeaf.getParagraph();
	//		rslt.begContainer = rslt.endContainer = selState.textFlow.findAbsoluteContainer(rslt.begElemIdx);
			adjustForLeanLeft(rslt);
			rslt.lastLeaf = rslt.firstLeaf;
			rslt.lastParagraph = rslt.firstParagraph;
		}
		else
		{
			// order the selection points
			if (absoluteStart < absoluteEnd)
			{
				rslt.absoluteStart  = absoluteStart;
				rslt.absoluteEnd = absoluteEnd;
			}
			else
			{
				rslt.absoluteStart  = absoluteEnd;
				rslt.absoluteEnd = absoluteStart;
			}
			rslt.firstLeaf = textFlow.findLeaf(rslt.absoluteStart);
			rslt.lastLeaf = textFlow.findLeaf(rslt.absoluteEnd);
			// back up one element if the end of the selection is the start of an element
			// otherwise a block selection of a span looks like it includes discreet selection ranges
			if (((rslt.lastLeaf == null) && (rslt.absoluteEnd == textFlow.textLength)) || (rslt.absoluteEnd == rslt.lastLeaf.getAbsoluteStart()))
				rslt.lastLeaf = textFlow.findLeaf(rslt.absoluteEnd-1);
				
			rslt.firstParagraph = rslt.firstLeaf.getParagraph();
			rslt.lastParagraph = rslt.lastLeaf.getParagraph();
			
	//		rslt.begContainer = selState.textFlow.findAbsoluteContainer(rslt.begElemIdx);
	//		rslt.endContainer = selState.textFlow.findAbsoluteContainer(rslt.endElemIdx);
	//		if (rslt.endElemIdx == rslt.endContainer.relativeStart)
	//			rslt.endContainer = rslt.endContainer.preventextContainer;
				
			// if the end of the range includes the next to last character in a paragraph 
			// expand it to include the paragraph teriminate character
			if (rslt.absoluteEnd == rslt.lastParagraph.getAbsoluteStart() + rslt.lastParagraph.textLength - 1)
			{
				rslt.absoluteEnd++
				rslt.lastLeaf = rslt.lastParagraph.getLastLeaf();
			}

		}
		rslt.textFlow = textFlow;
			
		return rslt;
	}
	
	static private function adjustForLeanLeft(rslt:ElementRange):void
	{		
		// If we're at the start of a leaf element, look to the previous leaf element and see if it shares the same
		// parent. If so, we're going to move the selection to the end of the previous element so it takes on
		// the formatting of the character to the left. We don't want to do this if the previous element is in
		// a different character, across link or tcy boundaries, etc.
		if (rslt.firstLeaf.getAbsoluteStart() == rslt.absoluteStart)
		{
			var previousNode:FlowLeafElement = rslt.firstLeaf.getPreviousLeaf(rslt.firstParagraph);
			if (previousNode && previousNode.getParagraph() == rslt.firstLeaf.getParagraph())
			{
				if((!(previousNode.parent is SubParagraphGroupElementBase) || (previousNode.parent as SubParagraphGroupElementBase).acceptTextAfter())
					&& (!(rslt.firstLeaf.parent is SubParagraphGroupElementBase) || previousNode.parent === rslt.firstLeaf.parent))
					rslt.firstLeaf = previousNode;
			}
				
		}
	}
}
}