////////////////////////////////////////////////////////////////////////////////
//
// 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.conversion 
{	
	import flash.display.Shape;
	import flash.text.engine.TextRotation;
	import flash.utils.Dictionary;
	
	import flashx.textLayout.TextLayoutVersion;
	import flashx.textLayout.container.ContainerController;
	import flashx.textLayout.debug.assert;
	import flashx.textLayout.elements.BreakElement;
	import flashx.textLayout.elements.DivElement;
	import flashx.textLayout.elements.FlowElement;
	import flashx.textLayout.elements.FlowGroupElement;
	import flashx.textLayout.elements.GlobalSettings;
	import flashx.textLayout.elements.IConfiguration;
	import flashx.textLayout.elements.InlineGraphicElement;
	import flashx.textLayout.elements.LinkElement;
	import flashx.textLayout.elements.ListElement;
	import flashx.textLayout.elements.ListItemElement;
	import flashx.textLayout.elements.ParagraphElement;
	import flashx.textLayout.elements.SpanElement;
	import flashx.textLayout.elements.SubParagraphGroupElement;
	import flashx.textLayout.elements.TCYElement;
	import flashx.textLayout.elements.TabElement;
	import flashx.textLayout.elements.TextFlow;
	import flashx.textLayout.formats.ITextLayoutFormat;
	import flashx.textLayout.formats.ListMarkerFormat;
	import flashx.textLayout.formats.TextLayoutFormat;
	import flashx.textLayout.property.Property;
	import flashx.textLayout.tlf_internal;
	
	use namespace tlf_internal;

	[ExcludeClass]
	/** 
	 * @private
	 * TextLayoutImporter converts from XML to TextLayout data structures and back.
	 */ 	
	public class TextLayoutImporter extends BaseTextLayoutImporter implements ITextLayoutImporter
	{
		private static var _defaultConfiguration:ImportExportConfiguration;
		
		/** Default ImportExportConfiguration to use when none specified 
		* @playerversion Flash 10
		* @playerversion AIR 1.5
	 	* @langversion 3.0 
		*/
		public static function get defaultConfiguration():ImportExportConfiguration
		{
			// The first call will force the import/export to include the standard components
			if (!_defaultConfiguration)
			{
				_defaultConfiguration = new ImportExportConfiguration();
				// elements
	 			_defaultConfiguration.addIEInfo("TextFlow", TextFlow,        BaseTextLayoutImporter.parseTextFlow,	BaseTextLayoutExporter.exportTextFlow);
				_defaultConfiguration.addIEInfo("br", BreakElement,          BaseTextLayoutImporter.parseBreak,		BaseTextLayoutExporter.exportFlowElement);
				_defaultConfiguration.addIEInfo("p", ParagraphElement,       BaseTextLayoutImporter.parsePara,		BaseTextLayoutExporter.exportParagraphFormattedElement);
				_defaultConfiguration.addIEInfo("span", SpanElement,         BaseTextLayoutImporter.parseSpan,		BaseTextLayoutExporter.exportSpan);
				_defaultConfiguration.addIEInfo("tab", TabElement,           BaseTextLayoutImporter.parseTab,		BaseTextLayoutExporter.exportFlowElement);
				_defaultConfiguration.addIEInfo("list", ListElement,  		 BaseTextLayoutImporter.parseList,		BaseTextLayoutExporter.exportList);
				_defaultConfiguration.addIEInfo("li", ListItemElement,       BaseTextLayoutImporter.parseListItem,	BaseTextLayoutExporter.exportListItem);
				_defaultConfiguration.addIEInfo("g", SubParagraphGroupElement, TextLayoutImporter.parseSPGE, 		TextLayoutExporter.exportSPGE);
				_defaultConfiguration.addIEInfo("tcy", TCYElement,           TextLayoutImporter.parseTCY, 			TextLayoutExporter.exportTCY);
				_defaultConfiguration.addIEInfo("a", LinkElement,            TextLayoutImporter.parseLink, 			TextLayoutExporter.exportLink);
	 			_defaultConfiguration.addIEInfo("div", DivElement,           TextLayoutImporter.parseDivElement, 	TextLayoutExporter.exportDiv);
				_defaultConfiguration.addIEInfo("img", InlineGraphicElement, TextLayoutImporter.parseInlineGraphic, TextLayoutExporter.exportImage);	
				
				// validate the defaultTypeName values.  They are to match the TLF format export xml names
				CONFIG::debug 
				{
					for (var name:String in _defaultConfiguration.flowElementInfoList)
					{
						var info:FlowElementInfo = _defaultConfiguration.flowElementInfoList[name];
						assert(name == (new info.flowClass).defaultTypeName,"Bad defaultTypeName in "+info.flowClass);
					}
				}
				// customized link formats
				_defaultConfiguration.addIEInfo(LinkElement.LINK_NORMAL_FORMAT_NAME,null,TextLayoutImporter.parseLinkNormalFormat,null);
				_defaultConfiguration.addIEInfo(LinkElement.LINK_ACTIVE_FORMAT_NAME,null,TextLayoutImporter.parseLinkActiveFormat,null);
				_defaultConfiguration.addIEInfo(LinkElement.LINK_HOVER_FORMAT_NAME, null,TextLayoutImporter.parseLinkHoverFormat, null);
				// list marker format
				_defaultConfiguration.addIEInfo(ListElement.LIST_MARKER_FORMAT_NAME,null,TextLayoutImporter.parseListMarkerFormat,null);
			}
			
			return _defaultConfiguration;
		}
		
		/** Set the default configuration back to its original value 
		* @playerversion Flash 10
		* @playerversion AIR 1.5
	 	* @langversion 3.0
	 	*/
		public static function restoreDefaults():void
		{
			_defaultConfiguration = null;
		}
				
		static private const _formatImporter:TLFormatImporter = new TLFormatImporter(TextLayoutFormat,TextLayoutFormat.description);
		static private const _idImporter:SingletonAttributeImporter = new SingletonAttributeImporter("id");
		static private const _typeNameImporter:SingletonAttributeImporter = new SingletonAttributeImporter("typeName");
		static private const _customFormatImporter:CustomFormatImporter = new CustomFormatImporter();
		
		static private const _flowElementFormatImporters:Array = [ _formatImporter,_idImporter,_typeNameImporter,_customFormatImporter ];
		
		private var _imageSourceResolveFunction:Function;

		/** Constructor */
		public function TextLayoutImporter()
		{
			super(new Namespace("flow", "http://ns.adobe.com/textLayout/2008"), defaultConfiguration);
		}
		
		/** @copy ITextLayoutImporter#imageSourceResolveFunction
		 * 
		 * @playerversion Flash 10.0
		 * @playerversion AIR 2.0
		 * @langversion 3.0
		 */
		public function get imageSourceResolveFunction():Function
		{ return _imageSourceResolveFunction; }
		public function set imageSourceResolveFunction(resolver:Function):void
		{ _imageSourceResolveFunction = resolver; }
		
		
		/**  @private */
		override protected function parseContent(rootStory:XML):TextFlow
		{
			// Capture all the top-level tags of interest that can be "bound"
			// We have to do this because the attributes are applied at the point
			// of calling something like:
			// span.charAttrs = characterAttrs;
			// At one time, we just set the variable to the parameter (in the setter),
			// but now we're copying the data into a new object. This change does
			// not allow for us to parse the bindings in any order. Hence, we
			// will process the potential bindings objects first, then the
			// TextFlow objects.
			//
			// Also note the use of "..*" below. We are using this to traverse the
			// XML structure looking for particular tags and at the same time allow for
			// any namespace. So, you might see something like <flow:TextContainer> or
			// <TextContainer> and this code will capture both cases.
			
			var rootName:String = rootStory.name().localName;
			var textFlowElement:XML = rootName == "TextFlow" ? rootStory : rootStory..*::TextFlow[0];
			if (!textFlowElement)
			{
				reportError(GlobalSettings.resourceStringFunction("missingTextFlow")); 
				return null;
			}
			if (!checkNamespace(textFlowElement))
				return null;
	
			return parseTextFlow(this, textFlowElement);
		}
		
		private function parseStandardFlowElementAttributes(flowElem:FlowElement,xmlToParse:XML,importers:Array = null):void
		{
			if (importers == null)
				importers = _flowElementFormatImporters;
			// all the standard ones have to be in importers - some check needed
			parseAttributes(xmlToParse,importers);
			
			var textFormat:TextLayoutFormat = extractTextFormatAttributesHelper(flowElem.format,_formatImporter) as TextLayoutFormat;
			if (textFormat)
			{
				CONFIG::debug { assert(textFormat.getStyles() != null,"Bad TextFormat in parseStandardFlowElementAttributes"); }
				flowElem.format = textFormat;
			}

			if (_idImporter.result)
				flowElem.id = _idImporter.result as String;
			if (_typeNameImporter.result)
				flowElem.typeName = _typeNameImporter.result as String;
			if (_customFormatImporter.result)
			{
				for (var styleName:String in _customFormatImporter.result)
					flowElem.setStyle(styleName,_customFormatImporter.result[styleName]);
			}
		}
		

		override public function createTextFlowFromXML(xmlToParse:XML, textFlow:TextFlow = null):TextFlow
		{
			// allocate the TextFlow and set the TextContainer's rootElement to it.
			var newFlow:TextFlow = null;

			if (!checkNamespace(xmlToParse))
				return newFlow;

			if (xmlToParse.hasOwnProperty("@version"))
			{
				var version:String = xmlToParse.@["version"];
				if (version == "2.0.0")
					_importVersion = TextLayoutVersion.VERSION_2_0;
				else if (version == "1.1.0" || version == "1.0.0")
					_importVersion = TextLayoutVersion.VERSION_1_0;
				else
				{
					reportError(GlobalSettings.resourceStringFunction("unsupportedVersion",[ xmlToParse.@["version"] ]));
					_importVersion = TextLayoutVersion.CURRENT_VERSION;
				}
			}
			else		// we must be the first version
				_importVersion = TextLayoutVersion.VERSION_1_0;
				
			// allocate the TextFlow and initialize the container attributes
			if (!newFlow)
				newFlow = new TextFlow(_textFlowConfiguration);
	
			// parse formatting
			parseStandardFlowElementAttributes(newFlow,xmlToParse);
			
			// descend into children
			parseFlowGroupElementChildren(xmlToParse, newFlow);
			
			CONFIG::debug { newFlow.debugCheckNormalizeAll() ; }
			newFlow.normalize();
			
			newFlow.applyWhiteSpaceCollapse(null);
			
			return newFlow;
		}
		
		public function createDivFromXML(xmlToParse:XML):DivElement
		{
			// add the div element to the parent
			var divElem:DivElement = new DivElement();
			
			parseStandardFlowElementAttributes(divElem,xmlToParse);

			return divElem;
		}
		
		public override function createParagraphFromXML(xmlToParse:XML):ParagraphElement
		{
			var paraElem:ParagraphElement = new ParagraphElement();
			parseStandardFlowElementAttributes(paraElem,xmlToParse);
			return paraElem;
		}
		
		public function createSubParagraphGroupFromXML(xmlToParse:XML):SubParagraphGroupElement
		{
			var elem:SubParagraphGroupElement = new SubParagraphGroupElement();
			parseStandardFlowElementAttributes(elem,xmlToParse);
			return elem;
		}
		
		public function createTCYFromXML(xmlToParse:XML):TCYElement
		{
			var tcyElem:TCYElement = new TCYElement();
			parseStandardFlowElementAttributes(tcyElem,xmlToParse);
			return tcyElem;
		}
		
			
		static internal const _linkDescription:Object = {
			href : Property.NewStringProperty("href",null, false, null),
			target : Property.NewStringProperty("target",null, false, null)
		}
		static private const _linkFormatImporter:TLFormatImporter = new TLFormatImporter(Dictionary,_linkDescription);
		static private const _linkElementFormatImporters:Array = [ _linkFormatImporter, _formatImporter,_idImporter,_typeNameImporter,_customFormatImporter ];

		/** Parse a LinkElement Block.
		 * 
		 * @param - importFilter:BaseTextLayoutImporter - parser object
		 * @param - xmlToParse:XML - the xml describing the Link
		 * @param - parent:FlowBlockElement - the parent of the new Link
		 * @return LinkElement - a new LinkElement and its children
		 */
		public function createLinkFromXML(xmlToParse:XML):LinkElement
		{
			var linkElem:LinkElement = new LinkElement();
			parseStandardFlowElementAttributes(linkElem,xmlToParse,_linkElementFormatImporters);
			if (_linkFormatImporter.result)
			{
				linkElem.href = _linkFormatImporter.result["href"] as String;
				linkElem.target = _linkFormatImporter.result["target"] as String;
			}

			return linkElem;
		}
		
		public override function createSpanFromXML(xmlToParse:XML):SpanElement
		{
			var spanElem:SpanElement = new SpanElement();
			
			parseStandardFlowElementAttributes(spanElem,xmlToParse);

			return spanElem;
		}
		
		static private const _imageDescription:Object = {
			height:InlineGraphicElement.heightPropertyDefinition,
			width:InlineGraphicElement.widthPropertyDefinition,
			source: Property.NewStringProperty("source", null, false, null),
			float: Property.NewStringProperty("float", null, false, null),
			rotation: InlineGraphicElement.rotationPropertyDefinition }
		
		static private const _ilgFormatImporter:TLFormatImporter = new TLFormatImporter(Dictionary,_imageDescription);
		static private const _ilgElementFormatImporters:Array = [ _ilgFormatImporter, _formatImporter, _idImporter, _typeNameImporter, _customFormatImporter ];

		public function createInlineGraphicFromXML(xmlToParse:XML):InlineGraphicElement
		{				
			var imgElem:InlineGraphicElement = new InlineGraphicElement();
			
			parseStandardFlowElementAttributes(imgElem,xmlToParse,_ilgElementFormatImporters);
			
			if (_ilgFormatImporter.result)
			{
				var source:String = _ilgFormatImporter.result["source"];
				imgElem.source = _imageSourceResolveFunction != null ? _imageSourceResolveFunction(source) : source;
				
				// if not defined then let InlineGraphic set its own default
				imgElem.height = _ilgFormatImporter.result["height"];
				imgElem.width  = _ilgFormatImporter.result["width"];
				/*	We don't support rotation yet because of bugs in the player. */		
				// imgElem.rotation  = InlineGraphicElement.heightPropertyDefinition.setHelper(imgElem.rotation,_ilgFormatImporter.result["rotation"]);
				imgElem.float = _ilgFormatImporter.result["float"];
			}
			
			return imgElem;
		}
	
		public override function createListFromXML(xmlToParse:XML):ListElement
		{
			var rslt:ListElement = new ListElement;
			parseStandardFlowElementAttributes(rslt,xmlToParse);
			return rslt;
		}

		public override function createListItemFromXML(xmlToParse:XML):ListItemElement
		{
			var rslt:ListItemElement = new ListItemElement;
			parseStandardFlowElementAttributes(rslt,xmlToParse);
			return rslt;
		}
		
		public function extractTextFormatAttributesHelper(curAttrs:Object, importer:TLFormatImporter):Object
		{
			return extractAttributesHelper(curAttrs,importer);
		}
		
		/** Parse an SPGE element
		 * 
		 * @param - importFilter:BaseTextLayoutImporter - parser object
		 * @param - xmlToParse:XML - the xml describing the TCY Block
		 * @param - parent:FlowBlockElement - the parent of the new TCY Block
		 * @return SubParagraphGroupElement - a new TCYBlockElement and its children
		 */
		static public function parseSPGE(importFilter:BaseTextLayoutImporter, xmlToParse:XML, parent:FlowGroupElement):void
		{
			var elem:SubParagraphGroupElement = TextLayoutImporter(importFilter).createSubParagraphGroupFromXML(xmlToParse);
			if (importFilter.addChild(parent, elem))
			{
				importFilter.parseFlowGroupElementChildren(xmlToParse, elem);
				//if parsing an empty tcy, create a Span for it.
				if (elem.numChildren == 0)
					elem.addChild(new SpanElement());
			}
		}

		/** Parse a TCY Block.
		 * 
		 * @param - importFilter:BaseTextLayoutImporter - parser object
		 * @param - xmlToParse:XML - the xml describing the TCY Block
		 * @param - parent:FlowBlockElement - the parent of the new TCY Block
		 * @return TCYBlockElement - a new TCYBlockElement and its children
		 */
		static public function parseTCY(importFilter:BaseTextLayoutImporter, xmlToParse:XML, parent:FlowGroupElement):void
		{
			var tcyElem:TCYElement = TextLayoutImporter(importFilter).createTCYFromXML(xmlToParse);
			if (importFilter.addChild(parent, tcyElem))
			{
				importFilter.parseFlowGroupElementChildren(xmlToParse, tcyElem);
				//if parsing an empty tcy, create a Span for it.
				if (tcyElem.numChildren == 0)
					tcyElem.addChild(new SpanElement());
			}
		}
		
				
		/** Parse a LinkElement Block.
		 * 
		 * @param - importFilter:BaseTextLayoutImporter - parser object
		 * @param - xmlToParse:XML - the xml describing the Link
		 * @param - parent:FlowBlockElement - the parent of the new Link
		 * @return LinkElement - a new LinkElement and its children
		 */
		static public function parseLink(importFilter:BaseTextLayoutImporter, xmlToParse:XML, parent:FlowGroupElement):void
		{
			var linkElem:LinkElement = TextLayoutImporter(importFilter).createLinkFromXML(xmlToParse);
			if (importFilter.addChild(parent, linkElem))
			{
				importFilter.parseFlowGroupElementChildren(xmlToParse, linkElem);
				//if parsing an empty link, create a Span for it.
				if (linkElem.numChildren == 0)
					linkElem.addChild(new SpanElement());
			}
		}
		
		public function createDictionaryFromXML(xmlToParse:XML):Dictionary
		{
			var formatImporters:Array = [ _customFormatImporter ];

			// parse the TextLayoutFormat child object		
			var formatList:XMLList = xmlToParse..*::TextLayoutFormat;
			if (formatList.length() != 1)
				reportError(GlobalSettings.resourceStringFunction("expectedExactlyOneTextLayoutFormat",[ xmlToParse.name() ]));
			
			var parseThis:XML = formatList.length() > 0 ? formatList[0] : xmlToParse;
			parseAttributes(parseThis,formatImporters);
			return _customFormatImporter.result as Dictionary;
		}

		static public function parseLinkNormalFormat(importFilter:BaseTextLayoutImporter, xmlToParse:XML, parent:FlowGroupElement):void
		{ parent.linkNormalFormat = TextLayoutImporter(importFilter).createDictionaryFromXML(xmlToParse); }
		static public function parseLinkActiveFormat(importFilter:BaseTextLayoutImporter, xmlToParse:XML, parent:FlowGroupElement):void
		{ parent.linkActiveFormat = TextLayoutImporter(importFilter).createDictionaryFromXML(xmlToParse); }
		static public function parseLinkHoverFormat(importFilter:BaseTextLayoutImporter, xmlToParse:XML, parent:FlowGroupElement):void
		{ parent.linkHoverFormat = TextLayoutImporter(importFilter).createDictionaryFromXML(xmlToParse); }
		
		public function createListMarkerFormatDictionaryFromXML(xmlToParse:XML):Dictionary
		{
			var formatImporters:Array = [ _customFormatImporter ];
			
			// parse the TextLayoutFormat child object		
			var formatList:XMLList = xmlToParse..*::ListMarkerFormat;
			if (formatList.length() != 1)
				reportError(GlobalSettings.resourceStringFunction("expectedExactlyOneListMarkerFormat",[ xmlToParse.name() ]));
			
			var parseThis:XML = formatList.length() > 0 ? formatList[0] : xmlToParse;
			parseAttributes(parseThis,formatImporters);
			return _customFormatImporter.result as Dictionary;
		}
		
		static public function parseListMarkerFormat(importFilter:BaseTextLayoutImporter, xmlToParse:XML, parent:FlowGroupElement):void
		{ parent.listMarkerFormat = TextLayoutImporter(importFilter).createListMarkerFormatDictionaryFromXML(xmlToParse); }

		/** Parse the <div ...> tag and all its children
		 * 
		 * @param - importFilter:BaseTextLayoutImportFilter - parser object
		 * @param - xmlToParse:XML - the xml describing the Div
		 * @param - parent:FlowBlockElement - the parent of the new Div
		 */
		static public function parseDivElement(importFilter:BaseTextLayoutImporter, xmlToParse:XML, parent:FlowGroupElement):void
		{
			var divElem:DivElement = TextLayoutImporter(importFilter).createDivFromXML(xmlToParse);
			if (importFilter.addChild(parent, divElem))
			{
				importFilter.parseFlowGroupElementChildren(xmlToParse, divElem);
				// we can't have a <div> tag w/no children... so, add an empty paragraph
				if (divElem.numChildren == 0)
					divElem.addChild(new ParagraphElement());
			}
		}

		/** Parse a leaf element, the <img ...>  tag.
		 * 
		 * @param - importFilter:BaseTextLayoutImporter - parser object
		 * @param - xmlToParse:XML - the xml describing the InlineGraphic FlowElement
		 * @param - parent:FlowBlockElement - the parent of the new image FlowElement
		 */
		static public function parseInlineGraphic(importFilter:BaseTextLayoutImporter, xmlToParse:XML, parent:FlowGroupElement):void
		{
			var ilg:InlineGraphicElement = TextLayoutImporter(importFilter).createInlineGraphicFromXML(xmlToParse);
			importFilter.addChild(parent, ilg);
		}
	}
}

