/**
 * Copyright (c) 2003-2007 IBM Corporation and others.
 * All rights reserved.   This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v2.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v20.html
 *
 * Contributors:
 *   IBM - Initial API and implementation
 */
package org.eclipse.emf.ecore.xmi.impl;

import java.util.Iterator;
import java.util.List;
import java.util.Map;

import org.eclipse.emf.common.util.EList;
import org.eclipse.emf.ecore.EAnnotation;
import org.eclipse.emf.ecore.EClass;
import org.eclipse.emf.ecore.EClassifier;
import org.eclipse.emf.ecore.EDataType;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.emf.ecore.EReference;
import org.eclipse.emf.ecore.EStructuralFeature;
import org.eclipse.emf.ecore.EcoreFactory;
import org.eclipse.emf.ecore.EcorePackage;
import org.eclipse.emf.ecore.InternalEObject;
import org.eclipse.emf.ecore.util.InternalEList;
import org.eclipse.emf.ecore.xmi.XMLHelper;
import org.eclipse.emf.ecore.xmi.XMLResource;


public class EMOFSaveImpl extends XMISaveImpl 
{
  public EMOFSaveImpl(XMLHelper helper)
  {
    super(helper);
    idAttributeName = XMI_ID_NS;
  }

  private static final Integer ONE = 1;

  @Override
  protected void init(XMLResource resource, Map<?, ?> options)
  {
    super.init(resource, options);
    xmlTypeInfo = new EMOFXMLTypeInfoImpl(xmlTypeInfo);
    saveTypeInfo = true;
  }

  @Override
  protected boolean shouldSaveFeature(EObject o, EStructuralFeature f)
  {
    if (f == EcorePackage.Literals.ETYPED_ELEMENT__ETYPE)
    {
      return o.eGet(f) != null;
    }
    else if (f == EcorePackage.Literals.EOPERATION__EEXCEPTIONS || 
             f == EcorePackage.Literals.ECLASS__ESUPER_TYPES)
    {
      return !((EList<?>)o.eGet(f)).isEmpty();
    }
    else if (f == EcorePackage.Literals.ETYPED_ELEMENT__LOWER_BOUND)
    {
      return keepDefaults || !ONE.equals(o.eGet(f));
    }
    else if (f == EcorePackage.Literals.ETYPED_ELEMENT__ORDERED)
    {
      return keepDefaults || !Boolean.FALSE.equals(o.eGet(f));
    }
    else
    {
      return super.shouldSaveFeature(o, f);
    }
  }

  @Override
  protected void saveDataTypeElementSingle(EObject o, EStructuralFeature f)
  {
    if (f == EcorePackage.Literals.EPACKAGE__NS_PREFIX ||
        f == EcorePackage.Literals.ESTRUCTURAL_FEATURE__UNSETTABLE ||
        f == EcorePackage.Literals.ESTRUCTURAL_FEATURE__TRANSIENT ||
        f == EcorePackage.Literals.ESTRUCTURAL_FEATURE__VOLATILE ||
        f == EcorePackage.Literals.EENUM_LITERAL__VALUE ||
        f == EcorePackage.Literals.EREFERENCE__RESOLVE_PROXIES ||
        f == EcorePackage.Literals.ECLASSIFIER__INSTANCE_CLASS_NAME ||
        f == EcorePackage.Literals.ECLASSIFIER__INSTANCE_TYPE_NAME ||
        f == EcorePackage.Literals.EDATA_TYPE__SERIALIZABLE ||
        f == EcorePackage.Literals.ECLASS__INTERFACE)
    {
      if (f == EcorePackage.Literals.ESTRUCTURAL_FEATURE__UNSETTABLE && 
          o.eIsSet(EcorePackage.Literals.ESTRUCTURAL_FEATURE__VOLATILE)) return;
      if (f == EcorePackage.Literals.ESTRUCTURAL_FEATURE__TRANSIENT && 
          (o.eIsSet(EcorePackage.Literals.ESTRUCTURAL_FEATURE__UNSETTABLE) ||
           o.eIsSet(EcorePackage.Literals.ESTRUCTURAL_FEATURE__VOLATILE))) return;
      if (f == EcorePackage.Literals.EREFERENCE__RESOLVE_PROXIES && 
          (o.eIsSet(EcorePackage.Literals.ESTRUCTURAL_FEATURE__TRANSIENT) || 
           o.eIsSet(EcorePackage.Literals.ESTRUCTURAL_FEATURE__UNSETTABLE) ||
           o.eIsSet(EcorePackage.Literals.ESTRUCTURAL_FEATURE__VOLATILE))) return;
      if (f == EcorePackage.Literals.EDATA_TYPE__SERIALIZABLE && 
          (o.eIsSet(EcorePackage.Literals.ECLASSIFIER__INSTANCE_CLASS_NAME) || o.eIsSet(EcorePackage.Literals.ECLASSIFIER__INSTANCE_TYPE_NAME))) return;
      if (f == EcorePackage.Literals.ECLASS__INTERFACE && 
          (o.eIsSet(EcorePackage.Literals.ECLASSIFIER__INSTANCE_CLASS_NAME) || o.eIsSet(EcorePackage.Literals.ECLASSIFIER__INSTANCE_TYPE_NAME))) return;
      doc.startElement(EMOFExtendedMetaData.XMI_EXTENSION_ELEMENT);
      doc.addAttribute(EMOFExtendedMetaData.XMI_EXTENDER_ATTRIBUTE, EcorePackage.eNS_URI);
      saveExtensionFeature(o, f);
      if (f == EcorePackage.Literals.ECLASSIFIER__INSTANCE_CLASS_NAME || f == EcorePackage.Literals.ECLASSIFIER__INSTANCE_TYPE_NAME)
      {
        if (o instanceof EDataType)
        {
          if (o.eIsSet(EcorePackage.Literals.EDATA_TYPE__SERIALIZABLE))
          {
            saveExtensionFeature(o, EcorePackage.Literals.EDATA_TYPE__SERIALIZABLE);
          }
        }
        else if (o instanceof EClass)
        {
          if (o.eIsSet(EcorePackage.Literals.ECLASS__INTERFACE))
          {
            saveExtensionFeature(o, EcorePackage.Literals.ECLASS__INTERFACE);
          }
        }
      }
      else if (f == EcorePackage.Literals.ESTRUCTURAL_FEATURE__TRANSIENT)
      {
        if (o instanceof EReference && o.eIsSet(EcorePackage.Literals.EREFERENCE__RESOLVE_PROXIES))
        {
          saveExtensionFeature(o, EcorePackage.Literals.EREFERENCE__RESOLVE_PROXIES);
        }
      }
      else if (f == EcorePackage.Literals.ESTRUCTURAL_FEATURE__UNSETTABLE)
      { 
        if (o.eIsSet(EcorePackage.Literals.ESTRUCTURAL_FEATURE__TRANSIENT))
        {
          saveExtensionFeature(o, EcorePackage.Literals.ESTRUCTURAL_FEATURE__TRANSIENT);
        }
        if (o instanceof EReference && o.eIsSet(EcorePackage.Literals.EREFERENCE__RESOLVE_PROXIES))
        {
          saveExtensionFeature(o, EcorePackage.Literals.EREFERENCE__RESOLVE_PROXIES);
        }
      }
      else if (f == EcorePackage.Literals.ESTRUCTURAL_FEATURE__VOLATILE)
      { 
        if (o.eIsSet(EcorePackage.Literals.ESTRUCTURAL_FEATURE__UNSETTABLE))
        {
          saveExtensionFeature(o, EcorePackage.Literals.ESTRUCTURAL_FEATURE__UNSETTABLE);
        }
        if (o.eIsSet(EcorePackage.Literals.ESTRUCTURAL_FEATURE__TRANSIENT))
        {
          saveExtensionFeature(o, EcorePackage.Literals.ESTRUCTURAL_FEATURE__TRANSIENT);
        }
        if (o instanceof EReference && o.eIsSet(EcorePackage.Literals.EREFERENCE__RESOLVE_PROXIES))
        {
          saveExtensionFeature(o, EcorePackage.Literals.EREFERENCE__RESOLVE_PROXIES);
        }
      }
      doc.endElement();
    }
    else
    {
      super.saveDataTypeElementSingle(o, f);
    }
  }

  @Override
  protected String getDatatypeValue(Object value, EStructuralFeature f, boolean isAttribute)
  {
    String result = super.getDatatypeValue(value, f, isAttribute);
    if (f == EcorePackage.Literals.ETYPED_ELEMENT__UPPER_BOUND && "-1".equals(result))
    {
      result = "*";
    } 
    return result;
  }

  protected void saveExtensionFeature(EObject o, EStructuralFeature f)
  {
    doc.startElement(f.getName());
    EDataType eDataType = (EDataType)f.getEType();
    doc.endContentElement(escape.convertText(EcoreFactory.eINSTANCE.convertToString(eDataType, o.eGet(f))));
  }

  @Override
  protected void saveContainedSingle(EObject o, EStructuralFeature f)
  {
    if (f == EcorePackage.Literals.ETYPED_ELEMENT__EGENERIC_TYPE)
    {
      doc.startElement(EMOFExtendedMetaData.XMI_EXTENSION_ELEMENT);
      doc.addAttribute(EMOFExtendedMetaData.XMI_EXTENDER_ATTRIBUTE, EcorePackage.eNS_URI);
      super.saveContainedSingle(o, f);
      doc.endElement();
    }
    else
    {
      super.saveContainedSingle(o, f);
    }
  }
  
  @Override
  protected void saveContainedMany(EObject o, EStructuralFeature f)
  {
    // EAnnotations denoting specific EMOF elements are serialized as their EMOF elements. 
    // Other EAnnotations are serialized as xmi:Extensions.
    //
    if (f == EcorePackage.Literals.EMODEL_ELEMENT__EANNOTATIONS) 
    {
      @SuppressWarnings("unchecked")
      List<EAnnotation> eAnnotations = ((InternalEList<EAnnotation>)helper.getValue(o, f)).basicList();
      boolean saveExtension = false;
      int size = eAnnotations.size();
      for (int i = 0; i < size; i++)
      {
        EAnnotation eAnnotation = eAnnotations.get(i);
        String source = eAnnotation.getSource();
        if (EMOFExtendedMetaData.EMOF_COMMENT_ANNOTATION_SOURCE.equals(source))
        {
          doc.startElement(EMOFExtendedMetaData.EMOF_OWNED_COMMENT);
          doc.addAttribute(EMOFExtendedMetaData.EMOF_COMMENT_BODY, eAnnotation.getDetails().get(EMOFExtendedMetaData.EMOF_COMMENT_BODY));
          doc.endElement();
        }
        else if (source.startsWith(EMOFExtendedMetaData.EMOF_PACKAGE_NS_URI_2_0))
        {
          doc.startElement(EMOFExtendedMetaData.EMOF_OWNED_COMMENT);
          doc.addAttribute(EMOFExtendedMetaData.EMOF_COMMENT_BODY, eAnnotation.getDetails().get(EMOFExtendedMetaData.EMOF_COMMENT_BODY));
          doc.startElement(EMOFExtendedMetaData.EMOF_OWNED_COMMENT);
          doc.addAttribute(EMOFExtendedMetaData.EMOF_COMMENT_BODY, source);
          doc.endElement();
          doc.endElement();
        }
        else
        {
          saveExtension = true;
        }
      }
      if (saveExtension) 
      {
        doc.startElement(EMOFExtendedMetaData.XMI_EXTENSION_ELEMENT);
        doc.addAttribute(EMOFExtendedMetaData.XMI_EXTENDER_ATTRIBUTE, EcorePackage.eNS_URI);
        for (int i = 0; i < size; i++)
        {
          EAnnotation eAnnotation = eAnnotations.get(i);
          String source = eAnnotation.getSource();
          if (!source.startsWith(EMOFExtendedMetaData.EMOF_PACKAGE_NS_URI_2_0))
          {
            saveElement((InternalEObject)eAnnotation, f);
          }
        }
        doc.endElement();
      }
    }
    else if (f == EcorePackage.Literals.ECLASS__EGENERIC_SUPER_TYPES ||
          f == EcorePackage.Literals.EOPERATION__EGENERIC_EXCEPTIONS ||
          f == EcorePackage.Literals.ECLASSIFIER__ETYPE_PARAMETERS ||
          f == EcorePackage.Literals.EOPERATION__ETYPE_PARAMETERS)
    {
      doc.startElement(EMOFExtendedMetaData.XMI_EXTENSION_ELEMENT);
      doc.addAttribute(EMOFExtendedMetaData.XMI_EXTENDER_ATTRIBUTE, EcorePackage.eNS_URI);
      super.saveContainedMany(o, f);
      doc.endElement();
    }
    else
    {
      super.saveContainedMany(o, f);
    }
  }

  @Override
  public Object writeTopObjects(List<? extends EObject> contents)
  {
    doc.startElement(XMI_TAG_NS);
    Object mark = doc.mark();

    for (int i = 0, size = contents.size(); i < size; i++)
    {
      EObject top = contents.get(i);
      EClass eClass = top.eClass();
      if (eClass == EcorePackage.Literals.EANNOTATION)
      {
        EAnnotation annotation = (EAnnotation)top;
        String source = annotation.getSource();
        if (!EMOFExtendedMetaData.EMOF_PACKAGE_NS_URI.equals(source) && !EMOFExtendedMetaData.EMOF_PACKAGE_NS_URI_2_0.equals(source)) continue;

        doc.startElement(EMOFExtendedMetaData.EMOF_TAG);
        doc.addAttribute(idAttributeName, helper.getID(annotation));
        doc.addAttribute(EMOFExtendedMetaData.EMOF_TAG_NAME, annotation.getDetails().get(EMOFExtendedMetaData.EMOF_TAG_NAME));
        doc.addAttribute(EMOFExtendedMetaData.EMOF_TAG_VALUE, annotation.getDetails().get(EMOFExtendedMetaData.EMOF_TAG_VALUE));

        InternalEList<? extends EObject> values = (InternalEList<? extends EObject>)annotation.getReferences();
        if (!values.isEmpty())
        {
          if (sameDocMany(annotation, EcorePackage.Literals.EANNOTATION__REFERENCES) == CROSS_DOC)
          {
            for (Iterator<? extends EObject> iter = values.basicIterator(); iter.hasNext(); )
            {
              EObject value = iter.next();
              String href = helper.getHREF(value);
              if (href != null)
              {
                doc.startElement(EMOFExtendedMetaData.EMOF_TAG_ELEMENT);
                doc.addAttribute(XMLResource.HREF, href);
                doc.endEmptyElement();
              }
            }
          }
          else
          {
            StringBuilder ids = new StringBuilder();
            boolean failed = true;
            for (Iterator<? extends EObject> iter = values.basicIterator();; )
            {
              EObject value = iter.next();
              String idref =  helper.getIDREF(value);
              if (idref == null)
              {
                failed = true;
                if (!iter.hasNext()) break;
              }
              else
              {
                ids.append(idref);
                if (!iter.hasNext()) break;
                ids.append(' ');
              }
            }
            String idsString = ids.toString();
            if (!failed || (idsString = idsString.trim()).length() != 0)
            {
              doc.addAttribute(EMOFExtendedMetaData.EMOF_TAG_ELEMENT, ids.toString());
            }
          }
        }
        doc.endElement();
      }
      else
      {
        String name = helper.getQName(eClass);
        doc.startElement(name);
        saveElementID(top);
      }
    }

    doc.endElement();
    return mark;
  }

  /**
   * Forces type information (xsi:type/xmi:type) to be serialized for references 
   * in cases where the object's type is different from the feature's type,
   * except for the case of the eStructuralFeatures of an EClass,
   * because for that case, an EMOF Property instance will be serialized 
   * and no xsi:type is needed for EMOF for that.
   */
  protected class EMOFXMLTypeInfoImpl extends XMLTypeInfoImpl
  {
    protected XMLTypeInfo xmlTypeInfo;

    public EMOFXMLTypeInfoImpl(XMLTypeInfo xmlTypeInfo)
    {
      this.xmlTypeInfo = xmlTypeInfo;
    }

    @Override
    public boolean shouldSaveType(EClass objectType, EClassifier featureType, EStructuralFeature feature)
    {
      return 
        feature != EcorePackage.Literals.ECLASS__ESTRUCTURAL_FEATURES &&
          (xmlTypeInfo == null ?  
            super.shouldSaveType(objectType, featureType, feature) : 
            xmlTypeInfo.shouldSaveType(objectType, featureType, feature));
    }

    @Override
    public boolean shouldSaveType(EClass objectType, EClass featureType, EStructuralFeature feature)
    {
      return 
        feature != EcorePackage.Literals.ECLASS__ESTRUCTURAL_FEATURES &&
          (xmlTypeInfo == null ?  
            super.shouldSaveType(objectType, featureType, feature) : 
            xmlTypeInfo.shouldSaveType(objectType, featureType, feature));
    }
  }
}
