/*
* Copyright (c) 2009 Nokia Corporation and/or its subsidiary(-ies).
* All rights reserved.
* This component and the accompanying materials are made available
* under the terms of the License "Eclipse Public License v1.0"
* which accompanies this distribution, and is available
* at the URL "http://www.eclipse.org/legal/epl-v10.html".
*
* Initial Contributors:
* Nokia Corporation - initial contribution.
*
* Contributors:
*
* Description: 
*
*/
package com.nokia.sdt.emf.dm.xml;

import com.nokia.sdt.emf.dm.*;
import com.nokia.sdt.emf.dm.impl.*;
import com.nokia.cpp.internal.api.utils.core.*;

import org.eclipse.emf.common.util.EMap;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.emf.ecore.EStructuralFeature;
import org.eclipse.emf.ecore.xmi.XMLHelper;
import org.eclipse.emf.ecore.xmi.XMLResource;
import org.eclipse.emf.ecore.xmi.impl.SAXXMLHandler;

import java.util.Map;

/**
 * This override of SAXXMLHandler is used to inform
 * EMF's parser about how to map our custom XML to
 * EMF structural features, and to hook up
 * property values as they are parsed from our custom XML
 *
 */
public class XMLHandlerNXD extends SAXXMLHandler {
	
		// attributes that don't map to structural features, need
		// to be tracked manually
	
			// value from StringValue type constants
	int pendingStringValueType = StringValue.LITERAL;
			// distinguish null from empty property value
	boolean pendingStringIsDefaultAttr;
	
	public XMLHandlerNXD(XMLResource xmiResource, XMLHelper helper, Map options) {
		super(xmiResource, helper, options);
	}
	
	private boolean isPropertyElementTag(String tag) {
		return Names.PROPERTY.equals(tag) ||
			 Names.COMPOUND_PROPERTY.equals(tag) ||
			 Names.SEQUENCE_PROPERTY.equals(tag);
	}
	
	private void establishPropertyContainer() {
		// In node elements property elements are at the same level as child
		// components. Therefore, the "top object" of the object stack is not
		// known to be a property container. So whenever a property element is
		// seen we have to establish the appropriate container on the stack.
		IPropertyContainer container = null;
		EObject peekObject = (EObject) objects.peek();
		if (peekObject instanceof INode) {
			container = ((INode)peekObject).getProperties();
		}
		else if (peekObject instanceof EStringToIPropertyValueMapEntryImpl) {
			// child property of a compound property. 
			EStringToIPropertyValueMapEntryImpl entry = (EStringToIPropertyValueMapEntryImpl) peekObject;
			IPropertyValue pv = entry.getTypedValue();
			Check.checkState(pv != null && pv.hasCompoundValue());
			container = pv.getCompoundValue();
		}
		else if (peekObject instanceof IPropertyValue) {
			IPropertyValue pv = (IPropertyValue) peekObject;
			container = pv.getCompoundValue();
		}
		else if (peekObject instanceof IDesignerData) {
			container = ((IDesignerData)peekObject).getProperties();
		}
		if (container != null)
			objects.push(container);
		// else, perhaps invalid XML document, and allow default EMF handling
	}
	
	private boolean preHandleFeature(String prefix, String name) {
		boolean handled = false;
		if (isPropertyElementTag(name)) {
			establishPropertyContainer();
		}
		else if (Names.ELEMENT.equals(name) || Names.COMPOUND_ELEMENT.equals(name)) {
			EObject peekObject = (EObject) objects.peek();
			if (peekObject instanceof EStringToIPropertyValueMapEntryImpl) {
				EStringToIPropertyValueMapEntryImpl entry = (EStringToIPropertyValueMapEntryImpl) peekObject;
				IPropertyValueImpl pv = (IPropertyValueImpl) entry.getTypedValue();
				Check.checkState(pv != null && pv.hasSequenceValue());
				objects.push(pv);
			}
		}		
		return handled;
	}
	
	private void postHandleFeature(String prefix, String name) {
		// post process certain elements to prepare to handle child elements
		EObject peekObject = (EObject) objects.peek();
		if (Names.COMPOUND_PROPERTY.equals(name)) {
			if (peekObject instanceof EStringToIPropertyValueMapEntryImpl) {
				EStringToIPropertyValueMapEntryImpl entry = (EStringToIPropertyValueMapEntryImpl) peekObject;
				IPropertyContainer parentContainer = peekPropertyContainer();
				if (parentContainer != null) {
					IPropertyValue pv = parentContainer.createPropertyContainerForProperty(entry.getKey());
					entry.setTypedValue(pv);
				}
			}
		}
		else if (Names.SEQUENCE_PROPERTY.equals(name)) {
			if (peekObject instanceof EStringToIPropertyValueMapEntryImpl) {
				EStringToIPropertyValueMapEntryImpl entry = (EStringToIPropertyValueMapEntryImpl) peekObject;
				IPropertyContainer parentContainer = peekPropertyContainer();
				if (parentContainer != null) {
					IPropertyValue pv = parentContainer.createSequenceForProperty(entry.getKey());
					entry.setTypedValue(pv);
				}
			}
		}
	}
	
	protected EObject createObjectFromFeatureType(EObject peekObject, EStructuralFeature feature) {
		EObject result = null;
		// For a compound element of a sequence property we need to set up the 
		// property value with the property container on creation.
		if (peekObject instanceof IPropertyValue &&
				feature.getFeatureID() == DmPackage.IPROPERTY_VALUE__SEQUENCE_VALUE &&
			Names.COMPOUND_ELEMENT.equals(elements.peek())) {
			IPropertyContainer parentContainer = peekPropertyContainer();
			if (parentContainer != null) {
				result = parentContainer.createPropertyContainerForProperty(null);
			}
			setFeatureValue(peekObject, feature, result);
		    processObject(result);
		}
		
		if (result == null)
			result = super.createObjectFromFeatureType(peekObject, feature);
		return result;
	}


	protected void handleFeature(String prefix, String name)
	{
		boolean handled = preHandleFeature(prefix, name);
		if (!handled)
			super.handleFeature(prefix, name);

		postHandleFeature(prefix, name);
	}
	
	private boolean decodeStringTypeAttr(String value) {
		boolean result = false;
		if (Names.SIMPLE_PROPERTY_TYPE_LITERAL.equals(value)) {
			pendingStringValueType = StringValue.LITERAL;
			result = true;
		}
		else if (Names.SIMPLE_PROPERTY_TYPE_I18N.equals(value)) {
			pendingStringValueType = StringValue.LOCALIZED;
			result = true;
		}
		else if (Names.SIMPLE_PROPERTY_TYPE_MACRO.equals(value)) {
			pendingStringValueType = StringValue.MACRO;
			result = true;
		}
		else if (Names.SIMPLE_PROPERTY_TYPE_REFERENCE.equals(value)) {
			pendingStringValueType = StringValue.REFERENCE;
			result = true;
		}
		return result;
	}
	
	protected void setAttribValue(EObject object, String name, String value) {
		boolean handled = false;
		if (object instanceof EStringToIPropertyValueMapEntryImpl) {
			if (Names.SIMPLE_PROPERTY_TYPE.equals(name)) {
				handled = decodeStringTypeAttr(value);
			}
		}
		else if (object instanceof EStringToStringMapEntryImpl) {
			if (Names.DEFAULT.equals(name)) {
				pendingStringIsDefaultAttr = Boolean.valueOf(value).booleanValue();
				handled = true;
			}
		}
		else if (object instanceof IPropertyValue) {
			if (Names.SIMPLE_PROPERTY_TYPE.equals(name)) {
				handled = decodeStringTypeAttr(value);
			}
		}
		
		if (!handled)
			super.setAttribValue(object, name, value);
	}

	String peekElement(int pos) {
		String result = null;
		if (elements.size() >= pos) 
			result = (String) elements.get(elements.size()-pos);
		return result;
	}
	
	Object peekObject(int pos) {
		Object result = null;
		if (objects.size() >= pos)
			result = objects.get(objects.size()-pos);
		return result;
	}
	
	IPropertyContainer peekPropertyContainer() {
		IPropertyContainer result = null;
		int i = objects.size();
		while (result == null && --i >= 0) {
			if (objects.get(i) instanceof IPropertyContainer)
				result = (IPropertyContainer) objects.get(i);
		}
		return result;
	}
	
	ILocalizedStringTable peekLocalizedStringTable() {
		ILocalizedStringTable result = null;
		int i = objects.size();
		while (result == null && --i >= 0) {
			if (objects.get(i) instanceof ILocalizedStringTable)
				result = (ILocalizedStringTable) objects.get(i);
		}
		return result;
	}
	
	IMacroStringTable peekMacroTable() {
		IMacroStringTable result = null;
		int i = objects.size();
		while (result == null && --i >= 0) {
			if (objects.get(i) instanceof IMacroStringTable)
				result = (IMacroStringTable) objects.get(i);
		}
		return result;
	}

	public void endElement(String uri, String localName, String name) {
		super.endElement(uri, localName, name);
		if (isPropertyElementTag(name)) {
			Object popped = objects.pop();
			Check.checkState(popped instanceof IPropertyContainer);
		}
		else if (Names.ELEMENT.equals(name) || Names.COMPOUND_ELEMENT.equals(name)) {
			Object popped = objects.pop();
			Check.checkState(popped instanceof IPropertyValue);
		}
		pendingStringValueType = StringValue.LITERAL;
		pendingStringIsDefaultAttr = false;
	}

	/*
	 * This method maps incoming XML elements to the EMF structural feature
	 * The object parameter is the EMF object that will contain the feature. The 
	 * name parameter is the element or attribute names
	 * @see org.eclipse.emf.ecore.xmi.impl.XMLHandler#getFeature(org.eclipse.emf.ecore.EObject, java.lang.String, java.lang.String, boolean)
	 */
	protected EStructuralFeature getFeature(EObject object, String prefix, String name, boolean isElement) {
		EStructuralFeature result = null;
		if (isElement && Names.COMPONENT.equals(name)) {
			if (object instanceof IDesignerData) {
		//		result = DmPackage.eINSTANCE.getIDesignerData_RootNode();
			}
			else if (object instanceof INode) {
				result = DmPackage.eINSTANCE.getINode_Children();
			}
		}
		else if (isElement && isPropertyElementTag(name)) {
			if (object instanceof IPropertyContainer) {
				result = DmPackage.eINSTANCE.getIPropertyContainer_Properties();
			}
		}
		else if (!isElement && Names.ID.equals(name)) {
			if (object instanceof EStringToIPropertyValueMapEntryImpl) {
				result = DmPackage.eINSTANCE.getEStringToIPropertyValueMapEntry_Key();
			}
		}
		else if (isElement && 
				(Names.ELEMENT.equals(name) || Names.COMPOUND_ELEMENT.equals(name))) {
			if (object instanceof IPropertyValue) {
				result = DmPackage.eINSTANCE.getIPropertyValue_SequenceValue();
			}
		}
		
		if (result == null)
			result = super.getFeature(object, prefix, name, isElement);
		return result;
	}
	
	/*
	 * We override EMF's default behavior to emit simple property values as
	 * text content, i.e. within <property>...</property> tags, rather than
	 * as an attribute value. This override tells EMF to gather text content 
	 * and put it in the property value feature.
	 * We need to check that the current tag is not a <compoundProperty>, since
	 * they of course do not have mixed content.
	 */
	protected EStructuralFeature getContentFeature(EObject object) {
		EStructuralFeature result = null;
		if (object instanceof EStringToIPropertyValueMapEntryImpl && 
			Names.PROPERTY.equals(elements.peek())) {
			result = DmPackage.eINSTANCE.getEStringToIPropertyValueMapEntry_Value();
		}
		else if (object instanceof IPropertyValue &&
			Names.ELEMENT.equals(elements.peek())) {
			result = DmPackage.eINSTANCE.getIPropertyValue_StringValue();
		}
		else if (object instanceof EStringToStringMapEntryImpl &&
				 Names.STRING_ENTRY.equals(elements.peek())) {
			result = DmPackage.eINSTANCE.getEStringToStringMapEntry_Value();
		}
		else if (object instanceof IResourceMapping) {
			result = DmPackage.eINSTANCE.getIResourceMapping_Value();
		}
		else if (object instanceof IEnumMapping) {
			result = DmPackage.eINSTANCE.getIEnumMapping_Value();
		}
		else if (object instanceof IElementMapping) {
			result = DmPackage.eINSTANCE.getIElementMapping_Value();
		}
		
		if (result == null)
			result = super.getContentFeature(object);
		return result;
	}

	/*
	 * This method initializes and stores property values. Since we've simpified the XML output
	 * there isn't enough there for EMF to do it without some help.
	 * All other cases are passed through to EMF for default handling.
	 */
	protected void setFeatureValue(EObject object, EStructuralFeature feature, Object value, int position) {
		boolean handled = false;
		if (object instanceof INode) {
			if (value instanceof IPropertyContainer) {
				INode node = (INode) object;
				node.getProperties().setFromPropertyContainer((IPropertyContainer) value);
				handled = true;
			}
		}
		else if (object instanceof IPropertyContainer) {
			IPropertyContainer container = (IPropertyContainer) object;
			if (value instanceof EStringToIPropertyValueMapEntryImpl) {
				// Set a property in a property container. There are two cases
				// to worry about duplicate properties. One is the name property, were
				// a default has been set, and another is if the XML has duplicates.
				EStringToIPropertyValueMapEntryImpl entry = (EStringToIPropertyValueMapEntryImpl) value;
				EMap map = container.getProperties();
				map.removeKey(entry.getKey());
				container.getProperties().add(value);
				handled = true;
			}
			else if (feature == DmPackage.eINSTANCE.getEStringToIPropertyValueMapEntry_Key() &&
					 value instanceof String) {
				handled = true;
			}
		}
		else if (object instanceof EStringToIPropertyValueMapEntryImpl) {
			EStringToIPropertyValueMapEntryImpl objEntry = (EStringToIPropertyValueMapEntryImpl) object;
			if (value instanceof String) {
				if (feature.getName().equals("value")) {
					IPropertyValue pv = DmFactory.eINSTANCE.createIPropertyValue();
					StringValue sv = new StringValue(pendingStringValueType, value.toString());
					pendingStringValueType = -1;
					pv.setStringValue(sv);
					objEntry.setValue(pv);
					handled = true;
				}
			}
		}
		else if (object instanceof IPropertyValue) {
			if (value instanceof String) {
				StringValue sv = new StringValue(pendingStringValueType, value.toString());
				pendingStringValueType = -1;
				IPropertyValue pv = (IPropertyValue) object;
				pv.setStringValue(sv);
				handled = true;
			}
			else if (value instanceof IPropertyValue) {
				IPropertyValue objectPV = (IPropertyValue) object;
				if (feature.getFeatureID() == DmPackage.IPROPERTY_VALUE__SEQUENCE_VALUE && 
						objectPV.hasSequenceValue()) {
					objectPV.getSequenceValue().add(value);
					handled = true;
				}
			}
		}
		else if (object instanceof EStringToStringMapEntryImpl) {
			// this could be an entry for an ILocalizedStringTable or IMacroStringTable,
			// we handle them the same
			EStringToStringMapEntryImpl entry = (EStringToStringMapEntryImpl) object;
			if (value instanceof String) {
				if (feature.getName().equals("value")) {
					if (!pendingStringIsDefaultAttr) {
						String strValue = TextUtils.unescape(value.toString());
						entry.setTypedValue(strValue);
					}
					handled = true;					
				}
			}
		}
		
		if (!handled)
			super.setFeatureValue(object, feature, value, position);
	}
	
	

}
