package com.openMap1.mapper.converters;

import java.util.Iterator;

import org.eclipse.emf.common.util.EList;
import org.w3c.dom.Attr;
import org.w3c.dom.Document;
import org.w3c.dom.Element;

import com.openMap1.mapper.ElementDef;
import com.openMap1.mapper.MappedStructure;
import com.openMap1.mapper.core.MapperException;
import com.openMap1.mapper.structures.MapperWrapper;
import com.openMap1.mapper.util.XMLUtil;
import com.openMap1.mapper.util.XSLOutputFile;
import com.openMap1.mapper.writer.TemplateFilter;

public class FACEWrapper extends AbstractMapperWrapper implements MapperWrapper{
	
	public static String FACE_PREFIX = "face";
	
	public static String FACE_URI = "http://schemas.facecode.com/webservices/2010/01/";

	//----------------------------------------------------------------------------------------
	//                     Constructor and initialisation from the Ecore model
	//----------------------------------------------------------------------------------------
	
	public FACEWrapper(MappedStructure ms, Object spare) throws MapperException
	{
		super(ms,spare);
	}

	/**
	 * @return the file extension of the outer document, with initial '*.'
	 */
	public String fileExtension() {return ("*.xml");}
	
	/**
	 * @return the type of document transformed to and from;
	 * see static constants in class AbstractMapperWrapper.
	 */
	public int transformType() {return AbstractMapperWrapper.XML_TYPE;}


	//----------------------------------------------------------------------------------------
	//                             In-wrapper transform
	//----------------------------------------------------------------------------------------

	@Override
	public Document transformIn(Object incoming) throws MapperException 
	{
		if (!(incoming instanceof Element)) throw new MapperException("Document root is not an Element");
		Element mappingRoot = (Element)incoming;
		
		String mappingRootPath = "/GetIndicativeBudget";
		inResultDoc = XMLUtil.makeOutDoc();
				
		Element inRoot = scanDocument(mappingRoot, mappingRootPath, AbstractMapperWrapper.IN_TRANSFORM);
		inResultDoc.appendChild(inRoot);
		return inResultDoc;		
	}
	/**
	 * default behaviour is a shallow copy - copying the element name, attributes,
	 * and text content only if the element has no child elements.
	 * to be overridden for specific paths in implementing classes
	 */
	protected Element inTransformNode(Element el, String path)  throws MapperException
	{
		// copy the element with namespaces, prefixed tag name, attributes but no text or child Elements
		Element copy = (Element)inResultDoc.importNode(el, false);
		
		// convert <FaceCompletedItem> elements to specific types of item
		if (XMLUtil.getLocalName(el).equals("FaceCompletedItem"))
		{
			String questionCode = getPathValue(el,"QuestionId");
			String newName = "FaceCompletedItem_" + questionCode;
			copy = renameElement(el, newName, true);
		}

		// if the source element has no child elements but has text, copy the text
		String text = textOnly(el);
		if (!text.equals("")) copy.appendChild(inResultDoc.createTextNode(text));
		
		return copy;
	}

	//----------------------------------------------------------------------------------------
	//                             Out-wrapper transform
	//----------------------------------------------------------------------------------------

	@Override
	public Object transformOut(Element outgoing) throws MapperException 
	{
		String mappingRootPath = "/Envelope";
		outResultDoc = XMLUtil.makeOutDoc();

		Element outRoot = scanDocument(outgoing, mappingRootPath, AbstractMapperWrapper.OUT_TRANSFORM);
		outResultDoc.appendChild(outRoot);
		return outResultDoc;
	}

	/**
	 * default behaviour is a shallow copy - copying the element name, attributes,
	 * and text content only if the element has no child elements.
	 * to be overridden for specific paths in implementing classes
	 */
	protected Element outTransformNode(Element el, String path)  throws MapperException
	{
		// copy the element with namespaces, prefixed tag name, attributes but no text or child Elements
		Element copy = (Element)outResultDoc.importNode(el, false);
		
		// convert specific types of <FaceCompletedItem_XX> back to plain <FACECompletedItem>
		if (XMLUtil.getLocalName(el).startsWith("FaceCompletedItem"))
		{
			copy = renameElement(el,"FaceCompletedItem",false);			
		}

		// if the source element has no child elements but has text, copy the text
		String text = textOnly(el);
		if (!text.equals("")) copy.appendChild(outResultDoc.createTextNode(text));
		
		return copy;
	}
	/**
	 * copy an element and all its attributes to the new document, renaming it
	 * and putting it in no namespace.
	 * @param el
	 * @param newName
	 * @param isIn true for the in-transform, false for the out-transform
	 * @return
	 * @throws MapperException
	 */
	protected Element renameElement(Element el, String newName, boolean isIn) throws MapperException
	{
		Element newEl = null;
		if (isIn) newEl = inResultDoc.createElementNS(FACE_URI, newName);
		else if (!isIn) newEl = outResultDoc.createElementNS(FACE_URI, newName);
		
		// set all attributes of the constrained element, including namespace attributes
		for (int a = 0; a < el.getAttributes().getLength();a++)
		{
			Attr at = (Attr)el.getAttributes().item(a);
			newEl.setAttribute(at.getName(), at.getValue());
		}
		return newEl;
	}
	
	//--------------------------------------------------------------------------------------------------------------
	//                                   XSLT Wrapper Transforms
	//--------------------------------------------------------------------------------------------------------------
	
	
	/**
	 * @param xout XSLT output being made
	 * @param templateFilter a filter on the templates, implemented by XSLGeneratorImpl
	 * append the templates and variables to be included in the XSL
	 * to do the full transformation, to apply the wrapper transform in the 'in' direction.
	 * Templates must have mode = "inWrapper"
	 */
	public void addWrapperInTemplates(XSLOutputFile xout, TemplateFilter templateFilter)  throws MapperException
	{
		// see class AbstractMapperWrapper - adds a plain identity template
		super.addWrapperInTemplates(xout, templateFilter);
		
		// add the FACE namespace
		xout.topOut().setAttribute("xmlns:" + FACE_PREFIX, FACE_URI);
		
		for (Iterator<ElementDef> it = findFACEItemsElementDefs(ms()).iterator();it.hasNext();)
		{
			ElementDef FACEItem = it.next();
			String tagName = FACEItem.getName();
			if (tagName.startsWith("FaceCompletedItem_"))
			{
				String questionId = tagName.substring("FaceCompletedItem_".length());
				addInTemplate(xout,tagName,questionId);
			}
		}
	}
	
	
	/**
	 * @param xout XSLT output being made
	 * @param templateFilter a filter on the templates to be included, implemented by XSLGeneratorImpl
	 * append the templates and variables to be included in the XSL
	 * to do the full transformation, to apply the wrapper transform in the 'out' direction.
	 * Templates must have mode = "outWrapper"
	 * @throws MapperException
	 */
	public void addWrapperOutTemplates(XSLOutputFile xout, TemplateFilter templateFilter)  throws MapperException
	{
		// see class AbstractMapperWrapper - adds a plain identity template
		super.addWrapperOutTemplates(xout, templateFilter);	

		// add the FACE namespace
		xout.topOut().setAttribute("xmlns:" + FACE_PREFIX, FACE_URI);
		
		for (Iterator<ElementDef> it = findFACEItemsElementDefs(ms()).iterator();it.hasNext();)
		{
			ElementDef FACEItem = it.next();
			String tagName = FACEItem.getName();
			if (tagName.startsWith("FaceCompletedItem_"))
			{
				String questionId = tagName.substring("FaceCompletedItem_".length());
				addOutTemplate(xout,tagName,questionId);
			}
		}
	}
	
	/**
	 * add an in-wrapper template of the form 
	
	<xsl:template match="face:FaceCompletedItem[face:QuestionId='F14_14_46_11_15_33T61_38']" mode="inWrapper">
		<face:FaceCompletedItem_F14_14_46_11_15_33T61_38>
			<xsl:copy-of select="@*"/>
			<xsl:apply-templates mode="inWrapper"/>
		</face:FaceCompletedItem_F14_14_46_11_15_33T61_38>
	</xsl:template>

	 * @param xout
	 * @param tagName
	 * @param questionId
	 */
	private void addInTemplate(XSLOutputFile xout,String tagName,String questionId)  throws MapperException
	{
		Element tempEl = xout.XSLElement("template");
		tempEl.setAttribute("match", FACE_PREFIX + ":FaceCompletedItem[" + FACE_PREFIX + ":QuestionId='" + questionId + "']");
		tempEl.setAttribute("mode", "inWrapper");
		
	    Element FACEEl = xout.NSElement(FACE_PREFIX, tagName, FACE_URI);
	    tempEl.appendChild(FACEEl);
	    
	    addApplyChildren(xout,FACEEl,"inWrapper");
	    
	    xout.topOut().appendChild(tempEl);
	}

	
	/**
	 * add an out-wrapper template of the form 
	
	<xsl:template match="face:FaceCompletedItem_F14_14_46_11_15_33T61_38" mode="outWrapper">
		<face:FaceCompletedItem>
			<xsl:copy-of select="@*"/>
			<xsl:apply-templates mode="outWrapper"/>
		</face:FaceCompletedItem>
	</xsl:template>

	 * @param xout
	 * @param tagName
	 * @param questionId
	 */
	private void addOutTemplate(XSLOutputFile xout,String tagName,String questionId)  throws MapperException
	{
		Element tempEl = xout.XSLElement("template");
		tempEl.setAttribute("match", FACE_PREFIX + ":" + tagName);
		tempEl.setAttribute("mode", "outWrapper");
		
	    Element FACEEl = xout.NSElement(FACE_PREFIX, "FaceCompletedItem", FACE_URI);
	    tempEl.appendChild(FACEEl);
	    
	    addApplyChildren(xout,FACEEl,"outWrapper");
	    
	    xout.topOut().appendChild(tempEl);
	}
	
    /**
     * add two child nodes to a template to carry on copying down the tree
     * @param xout
     * @param FACEEl
     * @param mode
     * @throws MapperException
     */
	private void addApplyChildren(XSLOutputFile xout,Element FACEEl, String mode) throws MapperException
    {
    	Element copyOfEl = xout.XSLElement("copy-of");
    	copyOfEl.setAttribute("select", "@*");
    	FACEEl.appendChild(copyOfEl);

    	Element applyEl = xout.XSLElement("apply-templates");
    	applyEl.setAttribute("mode", mode);
    	FACEEl.appendChild(applyEl);
}

	
	/**
	 * 
	 * @param mappedStructure
	 * @return a lit of nodes in the mapping set which are children of the 'Items' node
	 * @throws MapperException
	 */
	public static EList<ElementDef> findFACEItemsElementDefs(MappedStructure mappedStructure) throws MapperException
	{
		ElementDef msRoot = mappedStructure.getRootElement();
		if (msRoot == null) throw new MapperException("No root element in mapping set");
		if (!msRoot.getName().equals("GetIndicativeBudget")) 
			throw new MapperException("Root Element of mapping set must be called 'GetIndicativeBudget'");

		// there must be a chain of child ElementDefs with the names below; throw an exception if not
		ElementDef payload = findChildElementDef(msRoot,"payload");
		ElementDef items = findChildElementDef(payload,"Items");
				
		return items.getChildElements();		
	}
	
	/**
	 * 
	 * @param parent
	 * @param childName
	 * @return the child ElementDef with given name
	 * @throws MapperException if it does not exist
	 */
	private static ElementDef findChildElementDef(ElementDef parent, String childName) throws MapperException
	{
		ElementDef child = parent.getNamedChildElement(childName);
		if (child == null) throw new MapperException("Mapping set node '" + parent.getName() + "' has no child '" + childName + "'");
		return child;
	}
	


}
