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

import java.util.Collection;
import java.util.Iterator;
import java.util.List;

import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.OperationCanceledException;
import org.eclipse.core.runtime.Platform;

import org.eclipse.emf.codegen.ecore.genmodel.GenAnnotation;
import org.eclipse.emf.codegen.ecore.genmodel.GenModel;
import org.eclipse.emf.codegen.ecore.genmodel.GenPackage;
import org.eclipse.emf.codegen.ecore.genmodel.GenResourceKind;
import org.eclipse.emf.common.util.BasicDiagnostic;
import org.eclipse.emf.common.util.Diagnostic;
import org.eclipse.emf.common.util.DiagnosticException;
import org.eclipse.emf.common.util.EList;
import org.eclipse.emf.common.util.Monitor;
import org.eclipse.emf.common.util.URI;
import org.eclipse.emf.converter.ConverterPlugin;
import org.eclipse.emf.converter.util.ConverterUtil;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.emf.ecore.EPackage;
import org.eclipse.emf.ecore.resource.Resource;
import org.eclipse.emf.ecore.resource.ResourceSet;
import org.eclipse.emf.ecore.util.EcoreUtil;
import org.eclipse.emf.importer.ModelImporter;

import org.eclipse.xsd.XSDEnumerationFacet;
import org.eclipse.xsd.XSDFeature;
import org.eclipse.xsd.XSDSchema;
import org.eclipse.xsd.XSDTypeDefinition;
import org.eclipse.xsd.ecore.XSDEcoreBuilder;


public class XSDImporter extends ModelImporter
{
  /**
   * @since 2.9
   */
  protected static final String SORT_ATTRIBUTES_KEY = "sortAttributes";

  /**
   * @since 2.9
   */
  protected static final String CREATE_MAP_KEY = "createMap";

  public static class MapHelper
  {
    /**
     * @since 2.9
     */
    protected Monitor monitor;

    public MapHelper()
    {
    }

    /**
     * @since 2.9
     */
    public MapHelper(Monitor monitor)
    {
      this.monitor = monitor;
    }

    public void setNewMapper(XSDEcoreBuilder ecoreBuilder)
    {
      try
      {
        // The builder provides access to the resource set used to load the root schemas...
        //
        final Builder builder =  ecoreBuilder instanceof Builder ? (Builder)ecoreBuilder : null;
        org.eclipse.emf.mapping.xsd2ecore.XSD2EcoreMapper mapper =
          new org.eclipse.emf.mapping.xsd2ecore.XSD2EcoreMapper()
          {
            boolean initialized;
            int mappings;
            int count;

            @Override
            public void map(Collection<? extends EObject> inputs, Collection<? extends EObject> outputs)
            {
              if (monitor != null)
              {
                // The first time we create a mapping...
                //
                if (!initialized)
                {
                  initialized = true;
  
                  // If there isn't a builder, we don't know how many mappings we'll create.
                  // But this should never be the case.
                  //
                  if (builder == null)
                  {
                    mappings = 1;
                    
                  }
                  else
                  {
                    // Count the number of objects for which we expect to create mappings.
                    //
                    for (Iterator<?> i = builder.resourceSet.getAllContents(); i.hasNext(); )
                    {
                      Object object = i.next();
                      if (object instanceof XSDSchema ||
                            object instanceof XSDTypeDefinition ||
                            object instanceof XSDFeature ||
                            object instanceof XSDEnumerationFacet)
                      {
                        ++mappings;
                      }
                    }
                  }
                  
                  // Use this as the estimated number of work items in the task.
                  //
                  monitor.beginTask("", mappings);
                }
                monitor.worked(1);
                if (count++ % 100 == 0)
                {
                  monitor.subTask(XSDImporterPlugin.INSTANCE.getString("_UI_Mapping_message", new Object [] { EcoreUtil.getURI(inputs.iterator().next()).trimFragment() }));
                  if (monitor.isCanceled())
                  {
                    throw new OperationCanceledException();
                  }
                }
              }
              super.map(inputs, outputs);
            }
          };
        ecoreBuilder.setMapper(mapper);
      }
      catch (Exception e)
      {
        XSDImporterPlugin.INSTANCE.log(e);
      }
    }
  }

  protected boolean createEcoreMap;

  protected EObject mappingRoot;

  /**
   * @since 2.9
   */
  protected boolean sortAttributes = true;

  @Override
  public void dispose()
  {
    mappingRoot = null;
    super.dispose();
  }

  @Override
  public String getID()
  {
    return "org.eclipse.xsd.ecore.importer";
  }

  public boolean canCreateEcoreMap()
  {
    return Platform.getBundle("org.eclipse.emf.mapping.xsd2ecore") != null;
  }

  public void setCreateEcoreMap(boolean createEcoreMap)
  {
    this.createEcoreMap = createEcoreMap;
  }

  public boolean createEcoreMap()
  {
    return createEcoreMap && canCreateEcoreMap();
  }

  /**
   * @since 2.9
   */
  public void setSortAttributes(boolean sortAttributes)
  {
    this.sortAttributes = sortAttributes;
  }

  /**
   * @since 2.9
   */
  public boolean sortAttributes()
  {
    return sortAttributes;
  }

  public void setMappingRoot(EObject mappingRoot)
  {
    this.mappingRoot = mappingRoot;
  }

  public EObject getMappingRoot()
  {
    return mappingRoot;
  }

  /**
   * An XSDEcoreBuilder that respects the {@link XSDImporter#sortAttributes} setting.
   * @since 2.9
   */
  protected class Builder extends XSDEcoreBuilder
  {
    public ResourceSet resourceSet;

    @Override
    protected ResourceSet createResourceSet()
    {
      return resourceSet = super.createResourceSet();
    }

    @Override
    protected boolean useSortedAttributes()
    {
      return sortAttributes;
    }
  }

  @Override
  protected Diagnostic doComputeEPackages(Monitor monitor) throws Exception
  {
    BasicDiagnostic basicDiagnostic = null;

    List<URI> locationURIs = getModelLocationURIs();
    if (locationURIs.isEmpty())
    {
      basicDiagnostic = new BasicDiagnostic(
        Diagnostic.ERROR,
        ConverterPlugin.ID,
        ConverterUtil.ACTION_DIALOG_NONE | ConverterUtil.ACTION_MESSAGE_SET_ERROR,
        XSDImporterPlugin.INSTANCE.getString("_UI_SpecifyAValidXMLSchema_message"),
        null);
    }
    else
    {
      setMappingRoot(null);

      XSDEcoreBuilder ecoreBuilder = new Builder();

      if (createEcoreMap())
      {
        new MapHelper(monitor).setNewMapper(ecoreBuilder);
      }
      else
      {
        // If we're creating mappings, defer task size to the mapper's estimation of the number of mappings that need to be created.
        //
        monitor.beginTask("", 2);
        monitor.subTask(XSDImporterPlugin.INSTANCE.getString("_UI_Loading_message", new Object [] { locationURIs }));
      }

      @SuppressWarnings("unchecked")
      List<Object> result = (List<Object>)(List<?>)(Collection<?>)ecoreBuilder.generate(locationURIs);

      Object lastElement = removeNonEPackageFromTheEnd(result);
      if (lastElement instanceof List<?>)
      {
        @SuppressWarnings("unchecked")
        List<List<?>> diagnostics = (List<List<?>>)(List<?>)lastElement;
        if (!diagnostics.isEmpty())
        {
          BasicDiagnostic diagnostic = new BasicDiagnostic(
            ConverterPlugin.ID,
            ConverterUtil.ACTION_MESSAGE_NONE,
            XSDImporterPlugin.INSTANCE.getString("_UI_ErrorsWereDetectedXMLSchema_message"),
            null);

          for (List<?> information : diagnostics)
          {
            diagnostic.add(new BasicDiagnostic(
              "fatal".equals(information.get(0)) || "error".equals(information.get(0)) ? Diagnostic.ERROR : "warning".equals(information.get(0)) ? Diagnostic.WARNING : Diagnostic.INFO,
              XSDImporterPlugin.getPlugin().getBundle().getSymbolicName(),
              0,
              (String)information.get(1),
              null));
          }
          basicDiagnostic = diagnostic;
        }

        lastElement = removeNonEPackageFromTheEnd(result);
      }

      if (lastElement instanceof EObject)
      {
        setMappingRoot((EObject)lastElement);
      }

      @SuppressWarnings("unchecked")
      List<EPackage> ePackages = (List<EPackage>)(List<?>)result;
      getEPackages().addAll(ePackages);
    }

    if (basicDiagnostic == null)
    {
      return Diagnostic.OK_INSTANCE;
    }
    else
    {
      return basicDiagnostic;
    }
  }

  protected Object removeNonEPackageFromTheEnd(List<Object> list)
  {
    int lastIndex = list.size() - 1;
    if (lastIndex >= 0 && !(list.get(lastIndex) instanceof EPackage))
    {
      return list.remove(lastIndex);
    }
    else
    {
      return null;
    }
  }

  @Override
  protected void adjustGenPackageDuringTraverse(GenPackage genPackage)
  {
    genPackage.setResource(GenResourceKind.XML_LITERAL);
  }

  @Override
  protected void adjustGenModel(Monitor monitor)
  {
    super.adjustGenModel(monitor);

    IPath genModelFileFullPath = getGenModelPath();
    URI genModelURI = createFileURI(genModelFileFullPath.toString());

    GenModel genModel = getGenModel();
    EList<String> foreignModel = genModel.getForeignModel();
    for (URI uri : getModelLocationURIs())
    {
      foreignModel.add(makeRelative(uri, genModelURI).toString());
    }

    if (getMappingRoot() != null)
    {
      IPath mappingPath = genModelFileFullPath.removeFileExtension().addFileExtension("xsd2ecore");
      URI mappingModelURI = createFileURI(mappingPath.toString());
      Resource mappingModelResource = getGenModelResourceSet().createResource(mappingModelURI);
      mappingModelResource.getContents().add(getMappingRoot());
    }
  }

  @Override
  public void prepareGenModelAndEPackages(Monitor monitor)
  {
    super.prepareGenModelAndEPackages(monitor);

    GenModel genModel = getGenModel();

    GenAnnotation annotation = genModel.getGenAnnotation(getConverterGenAnnotationSource());
    if (!sortAttributes())
    {
      if (annotation == null)
      {
        annotation = genModel.createGenAnnotation();
        annotation.setSource(getConverterGenAnnotationSource());
      }
      annotation.getDetails().put(SORT_ATTRIBUTES_KEY, "false");
      genModel.getGenAnnotations().add(annotation);
    }
    else if (annotation != null)
    {
      annotation.getDetails().remove(SORT_ATTRIBUTES_KEY);
    }

    if (getMappingRoot() != null)
    {
      if (annotation == null)
      {
        annotation = genModel.createGenAnnotation();
        annotation.setSource(getConverterGenAnnotationSource());
        genModel.getGenAnnotations().add(annotation);
      }
      annotation.getDetails().put(CREATE_MAP_KEY, "true");
    }
    else if (annotation != null)
    {
      annotation.getDetails().remove(CREATE_MAP_KEY);
    }

    if (annotation != null && annotation.getDetails().isEmpty())
    {
      genModel.getGenAnnotations().remove(annotation);
    }
  }

  @Override
  protected List<Resource> computeResourcesToBeSaved()
  {
    List<Resource> resources = super.computeResourcesToBeSaved();
    if (getMappingRoot() != null)
    {
      resources.add(getMappingRoot().eResource());
    }
    return resources;
  }

  @Override
  protected void handleOriginalGenModel() throws DiagnosticException
  {
    GenModel originalGenModel = getOriginalGenModel();
    URI genModelURI = originalGenModel.eResource().getURI();
    StringBuffer text = new StringBuffer();
    for (String value : originalGenModel.getForeignModel())
    {
      if (value.endsWith(".xsd") || value.endsWith(".wsdl"))
      {
        text.append(makeAbsolute(URI.createURI(value), genModelURI).toString());
        text.append(" ");
      }
    }
    GenAnnotation annotation = originalGenModel.getGenAnnotation(getConverterGenAnnotationSource());
    if (annotation != null)
    {
      setSortAttributes(!"false".equals(annotation.getDetails().get(SORT_ATTRIBUTES_KEY)));
      setCreateEcoreMap("true".equals(annotation.getDetails().get(CREATE_MAP_KEY)));
    }
    setModelLocation(text.toString().trim());
  }
}