/**
 * Copyright (c) 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.io.IOException;
import java.io.InputStream;
import java.util.Map;
import java.util.regex.Pattern;

import org.eclipse.emf.common.util.EList;
import org.eclipse.emf.common.util.URI;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.emf.ecore.EReference;
import org.eclipse.emf.ecore.resource.ContentHandler;
import org.eclipse.emf.ecore.resource.impl.ContentHandlerImpl;
import org.eclipse.emf.ecore.util.ExtendedMetaData;
import org.eclipse.emf.ecore.xmi.XMLResource;
import org.eclipse.emf.ecore.xml.type.XMLTypeDocumentRoot;
import org.eclipse.emf.ecore.xml.type.XMLTypePackage;

/**
 * A specialized {@link ContentHandler} implementation that matches root element names and namespaces.
 */
public class RootXMLContentHandlerImpl extends XMLContentHandlerImpl
{
  /**
   * The content type identifier for content that matches this handler.
   */
  protected String contentTypeID;

  /**
   * The file extensions for which this handler applies.
   */
  protected String [] extensions;

  /**
   * The kind of content that's expected; {@link #XMI_KIND XMI content} has special handling.
   */
  protected String kind;

  /**
   * The namespace of the root element or the first non-XMI element.
   */
  protected String namespace;

  /**
   * The namespace pattern of the root element or the first non-XMI element.
   */
  protected Pattern namespacePattern;

  /**
   * The expected name of the root element or the first non-XMI element.
   */
  protected String [] elementNames;

  /**
   * The key in the {@link #RootXMLContentHandlerImpl(Map) constructor}'s parameter map representing the content type identifier String.
   * @see #contentTypeID
   */
  public static final String CONTENT_TYPE_ID = "contentTypeID";

  /**
   * The key in the {@link #RootXMLContentHandlerImpl(Map) constructor}'s parameter map representing the extensions
   * which are encoded as a space separate list of suffixes.
   * @see #extensions
   */
  public static final String EXTENSIONS = "extensions";

  /**
   * The key in the {@link #RootXMLContentHandlerImpl(Map) constructor}'s parameter map representing the kind of resource being processed;
   * only the value <code>xmi<code> is recognized.
   * @see #kind
   */
  public static final String KIND = "kind";
  
  /**
   * A {@link #kind} value indicating XMI content.
   */
  public static final String XMI_KIND = "xmi";

  /**
   * The key in the {@link #RootXMLContentHandlerImpl(Map) constructor}'s parameter map representing the namespace.
   * @see #namespace
   */
  public static final String NAMESPACE = "namespace";

  /**
   * The key in the {@link #RootXMLContentHandlerImpl(Map) constructor}'s parameter map representing the namespace pattern.
   * @see #namespacePattern
   */
  public static final String NAMESPACE_PATTERN = "namespacePattern";

  /**
   * The key in the {@link #RootXMLContentHandlerImpl(Map) constructor}'s parameter map representing the root element names
   * which are encoded as a space separated list of names.
   * @see #elementNames
   */
  public static final String ELEMENT_NAMES = "elementNames";
  

  /**
   * Creates an instance corresponding to the given parameters.
   * @param parameters the map of key value pairs.
   * @see #CONTENT_TYPE_ID
   * @see #EXTENSIONS
   * @see #KIND
   * @see #NAMESPACE
   * @see #ELEMENT_NAMES
   */
  public RootXMLContentHandlerImpl(Map<String, String> parameters)
  {
    this
      (parameters.get(CONTENT_TYPE_ID),
       parameters.containsKey(EXTENSIONS) ? parameters.get(EXTENSIONS).split(" ") : new String [0],
       parameters.get(KIND),
       parameters.get(NAMESPACE),
       parameters.get(NAMESPACE_PATTERN) != null ? Pattern.compile(parameters.get(NAMESPACE_PATTERN)) : null,
       parameters.containsKey(ELEMENT_NAMES) ? parameters.get(ELEMENT_NAMES).split(" ") : new String [0]);
  }

  /**
   * Creates an instance initialized with the given arguments.
   * @param contentTypeID the content type identifier for content recognized this this handler.
   * @param extensions the {@link URI#fileExtension() extensions} handled by this instance;
   * <code>null</code> or an empty array specifies that this handler applies for all URIs.
   * @param kind the kind of content that's expected; {@link #XMI_KIND} supports special handling.
   * @param namespace the expected namespace of the root element or first non-XMI element.
   * @param elementNames the expected element names of the root element or the first non-XMI element;
   * <code>null</code> or an empty array specifies that any root element name is allowed.
   */
  public RootXMLContentHandlerImpl
    (String contentTypeID,
     String [] extensions,
     String kind,
     String namespace,
     String [] elementNames)
  {
    this(contentTypeID, extensions, kind, namespace, null, elementNames);
  }

  /**
   * Creates an instance initialized with the given arguments.
   * @param contentTypeID the content type identifier for content recognized this this handler.
   * @param extensions the {@link URI#fileExtension() extensions} handled by this instance;
   * <code>null</code> or an empty array specifies that this handler applies for all URIs.
   * @param kind the kind of content that's expected; {@link #XMI_KIND} supports special handling.
   * @param namespacePattern a pattern that will match the expected namespace of the root element or first non-XMI element.
   * @param elementNames the expected element names of the root element or the first non-XMI element;
   * <code>null</code> or an empty array specifies that any root element name is allowed.
   */
  public RootXMLContentHandlerImpl
    (String contentTypeID,
     String [] extensions,
     String kind,
     Pattern namespacePattern,
     String [] elementNames)
  {
    this(contentTypeID, extensions, kind, null, namespacePattern, elementNames);
  }

  private RootXMLContentHandlerImpl
    (String contentTypeID,
     String [] extensions,
     String kind,
     String namespace,
     Pattern namespacePattern,
     String [] elementNames)
  {
    assert namespacePattern == null || namespace == null;

    this.contentTypeID = contentTypeID;
    this.extensions = extensions;
    this.kind = kind;
    this.namespace = namespace;
    this.namespacePattern = namespacePattern;
    this.elementNames = elementNames;
  }

  /**
   * Returns true if the {@link #extensions} are null or empty, of if the URI's {@link URI#fileExtension() file extension} matches one of the extension's values.
   */
  @Override
  public boolean canHandle(URI uri)
  {
    if (extensions == null || extensions.length == 0)
    {
      return true;
    }
    else
    {
      String fileExtension = uri.fileExtension();
      if (fileExtension != null)
      {
        for (String extension : extensions)
        {
          if (fileExtension.equals(extension))
          {
            return true;
          }
        }
      }
      return false;
    }
  }

  /**
   * Returns a valid content description if the XML content of the input stream has an element that matches the element names and namespace.
   */
  @Override
  public Map<String, Object> contentDescription(URI uri, InputStream inputStream, Map<?, ?> options, Map<Object, Object> context) throws IOException
  {
    Map<String, Object> result = super.contentDescription(uri, inputStream, options, context);

    XMLResource xmlResource = load(uri, inputStream, options, context);
    EList<EObject> contents = xmlResource.getContents();
    if (!contents.isEmpty())
    {
      EObject eObject = contents.get(0);
      if (eObject instanceof XMLTypeDocumentRoot)
      {
        XMLTypeDocumentRoot documentRoot = (XMLTypeDocumentRoot)eObject;
        EList<EObject> rootContents = documentRoot.eContents();
        String rootElementName = null;
        String rootElementNamespace = null;
        for (EObject root : rootContents)
        {
          EReference eContainmentFeature = root.eContainmentFeature();
          if (eContainmentFeature.getEContainingClass() != XMLTypePackage.Literals.XML_TYPE_DOCUMENT_ROOT)
          {
            rootElementName = eContainmentFeature.getName();
            rootElementNamespace = ExtendedMetaData.INSTANCE.getNamespace(eContainmentFeature);
            if (XMI_KIND.equals(kind) && isXMINameAndNamespace(rootElementName, rootElementNamespace))
            {
              // Look for the first non-XMI element.
              //
              for (EObject candidate : root.eContents())
              {
                eContainmentFeature = candidate.eContainmentFeature();
                if (eContainmentFeature.getEContainingClass() != XMLTypePackage.Literals.XML_TYPE_DOCUMENT_ROOT)
                {
                  rootElementNamespace = ExtendedMetaData.INSTANCE.getNamespace(eContainmentFeature);
                  if (!isXMINamespace(rootElementNamespace))
                  {
                    rootElementName = eContainmentFeature.getName();
                    break;
                  }
                }
              }
            }

            break;
          }
        }

        if (rootElementName != null && isMatchingNamespace(rootElementNamespace))
        {
          boolean elementNameMatched = false;
          if (elementNames == null || elementNames.length == 0)
          {
            elementNameMatched = true;
          }
          else
          {
            for (String elementName : elementNames)
            {
              if (rootElementName.equals(elementName))
              {
                elementNameMatched = true;
                break;
              }
            }
          }
          if (elementNameMatched)
          {
            result.put(VALIDITY_PROPERTY, ContentHandler.Validity.VALID);
          }
        }

        result.put(CONTENT_TYPE_PROPERTY, contentTypeID == null ? rootElementNamespace == null ? "" : rootElementNamespace : contentTypeID);
        return result;
      }
    }
    result.put(CONTENT_TYPE_PROPERTY, contentTypeID == null ? "" : contentTypeID);
    return result;
  }

  /**
   * Returns whether the given root element's namespace matches the namespace expected for the content type.
   */
  protected boolean isMatchingNamespace(String rootElementNamespace)
  {
    if (namespacePattern != null)
    {
      return namespacePattern.matcher(rootElementNamespace == null ? "" : rootElementNamespace).matches();
    }
    else
    {
      return namespace == null ? rootElementNamespace == null : namespace.equals(rootElementNamespace);
    }
  }

  /**
   * A describer that {@link #createContentHandler(Map) creates} a {@link RootXMLContentHandlerImpl} instance.
   */
  public static class Describer extends ContentHandlerImpl.Describer
  {
    /**
     * Creates a {@link RootXMLContentHandlerImpl} instance.
     */
    @Override
    protected ContentHandler createContentHandler(Map<String, String> parameters)
    {
      return new RootXMLContentHandlerImpl(parameters);
    }
  }
}
