/**
 * Copyright (c) 2002-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 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.impl;


import java.io.IOException;
import java.io.InputStream;
import org.w3c.dom.Element;

import org.eclipse.emf.common.notify.Notification;
import org.eclipse.emf.common.util.URI;
import org.eclipse.emf.ecore.EAttribute;
import org.eclipse.emf.ecore.EClass;
import org.eclipse.emf.ecore.impl.ENotificationImpl;
import org.eclipse.emf.ecore.resource.Resource;
import org.eclipse.emf.ecore.resource.ResourceSet;
import org.eclipse.emf.ecore.util.EcoreUtil;

import org.eclipse.xsd.XSDConcreteComponent;
import org.eclipse.xsd.XSDFactory;
import org.eclipse.xsd.XSDPackage;
import org.eclipse.xsd.XSDPlugin;
import org.eclipse.xsd.XSDSchema;
import org.eclipse.xsd.XSDSchemaDirective;
import org.eclipse.xsd.util.XSDConstants;
import org.eclipse.xsd.util.XSDResourceImpl;
import org.eclipse.xsd.util.XSDSchemaLocationResolver;
import org.eclipse.xsd.util.XSDSchemaLocator;


/**
 * <!-- begin-user-doc -->
 * An implementation of the model object '<em><b>Schema Directive</b></em>'.
 * <!-- end-user-doc -->
 * <p>
 * The following features are implemented:
 * <ul>
 *   <li>{@link org.eclipse.xsd.impl.XSDSchemaDirectiveImpl#getSchemaLocation <em>Schema Location</em>}</li>
 *   <li>{@link org.eclipse.xsd.impl.XSDSchemaDirectiveImpl#getResolvedSchema <em>Resolved Schema</em>}</li>
 * </ul>
 * </p>
 *
 * @generated
 */
public abstract class XSDSchemaDirectiveImpl 
  extends XSDSchemaContentImpl 
  implements XSDSchemaDirective
{
  /**
   * The default value of the '{@link #getSchemaLocation() <em>Schema Location</em>}' attribute.
   * <!-- begin-user-doc -->
   * <!-- end-user-doc -->
   * @see #getSchemaLocation()
   * @generated
   * @ordered
   */
  protected static final String SCHEMA_LOCATION_EDEFAULT = null;

  /**
   * The cached value of the '{@link #getSchemaLocation() <em>Schema Location</em>}' attribute.
   * <!-- begin-user-doc -->
   * <!-- end-user-doc -->
   * @see #getSchemaLocation()
   * @generated
   * @ordered
   */
  protected String schemaLocation = SCHEMA_LOCATION_EDEFAULT;

  /**
   * The cached value of the '{@link #getResolvedSchema() <em>Resolved Schema</em>}' reference.
   * <!-- begin-user-doc -->
   * <!-- end-user-doc -->
   * @see #getResolvedSchema()
   * @generated
   * @ordered
   */
  protected XSDSchema resolvedSchema;

  /**
   * <!-- begin-user-doc -->
   * <!-- end-user-doc -->
   * @generated
   */
  protected XSDSchemaDirectiveImpl()
  {
    super();
  }

  @Override
  public void reset()
  {
    super.reset();
    resolved = false;
    setResolvedSchema(null);
  }

  /**
   * <!-- begin-user-doc -->
   * <!-- end-user-doc -->
   * @generated
   */
  @Override
  protected EClass eStaticClass()
  {
    return XSDPackage.Literals.XSD_SCHEMA_DIRECTIVE;
  }

  /**
   * <!-- begin-user-doc -->
   * <!-- end-user-doc -->
   * @generated
   */
  public String getSchemaLocation()
  {
    return schemaLocation;
  }

  /**
   * <!-- begin-user-doc -->
   * <!-- end-user-doc -->
   * @generated
   */
  public void setSchemaLocation(String newSchemaLocation)
  {
    String oldSchemaLocation = schemaLocation;
    schemaLocation = newSchemaLocation;
    if (eNotificationRequired())
      eNotify(new ENotificationImpl(this, Notification.SET, XSDPackage.XSD_SCHEMA_DIRECTIVE__SCHEMA_LOCATION, oldSchemaLocation, schemaLocation));
  }

  /**
   * <!-- begin-user-doc -->
   * <!-- end-user-doc -->
   * @generated
   */
  public XSDSchema getResolvedSchema()
  {
    return resolvedSchema;
  }

  /**
   * <!-- begin-user-doc -->
   * <!-- end-user-doc -->
   * @generated
   */
  public void setResolvedSchema(XSDSchema newResolvedSchema)
  {
    XSDSchema oldResolvedSchema = resolvedSchema;
    resolvedSchema = newResolvedSchema;
    if (eNotificationRequired())
      eNotify(new ENotificationImpl(this, Notification.SET, XSDPackage.XSD_SCHEMA_DIRECTIVE__RESOLVED_SCHEMA, oldResolvedSchema, resolvedSchema));
  }

  /**
   * <!-- begin-user-doc -->
   * <!-- end-user-doc -->
   * @generated
   */
  @Override
  public Object eGet(int featureID, boolean resolve, boolean coreType)
  {
    switch (featureID)
    {
      case XSDPackage.XSD_SCHEMA_DIRECTIVE__SCHEMA_LOCATION:
        return getSchemaLocation();
      case XSDPackage.XSD_SCHEMA_DIRECTIVE__RESOLVED_SCHEMA:
        return getResolvedSchema();
    }
    return super.eGet(featureID, resolve, coreType);
  }

  /**
   * <!-- begin-user-doc -->
   * <!-- end-user-doc -->
   * @generated
   */
  @Override
  public void eSet(int featureID, Object newValue)
  {
    switch (featureID)
    {
      case XSDPackage.XSD_SCHEMA_DIRECTIVE__SCHEMA_LOCATION:
        setSchemaLocation((String)newValue);
        return;
      case XSDPackage.XSD_SCHEMA_DIRECTIVE__RESOLVED_SCHEMA:
        setResolvedSchema((XSDSchema)newValue);
        return;
    }
    super.eSet(featureID, newValue);
  }

  /**
   * <!-- begin-user-doc -->
   * <!-- end-user-doc -->
   * @generated
   */
  @Override
  public void eUnset(int featureID)
  {
    switch (featureID)
    {
      case XSDPackage.XSD_SCHEMA_DIRECTIVE__SCHEMA_LOCATION:
        setSchemaLocation(SCHEMA_LOCATION_EDEFAULT);
        return;
      case XSDPackage.XSD_SCHEMA_DIRECTIVE__RESOLVED_SCHEMA:
        setResolvedSchema((XSDSchema)null);
        return;
    }
    super.eUnset(featureID);
  }

  /**
   * <!-- begin-user-doc -->
   * <!-- end-user-doc -->
   * @generated
   */
  @Override
  public boolean eIsSet(int featureID)
  {
    switch (featureID)
    {
      case XSDPackage.XSD_SCHEMA_DIRECTIVE__SCHEMA_LOCATION:
        return SCHEMA_LOCATION_EDEFAULT == null ? schemaLocation != null : !SCHEMA_LOCATION_EDEFAULT.equals(schemaLocation);
      case XSDPackage.XSD_SCHEMA_DIRECTIVE__RESOLVED_SCHEMA:
        return resolvedSchema != null;
    }
    return super.eIsSet(featureID);
  }

  /**
   * <!-- begin-user-doc -->
   * <!-- end-user-doc -->
   * @generated
   */
  @Override
  public String toString()
  {
    if (eIsProxy()) return super.toString();

    StringBuffer result = new StringBuffer(super.toString());
    result.append(" (schemaLocation: ");
    result.append(schemaLocation);
    result.append(')');
    return result.toString();
  }

  @Override
  protected void reconcileAttributes(Element changedElement)
  {
    super.reconcileAttributes(changedElement);

    String newSchemaLocation = null;
    if (changedElement.hasAttributeNS(null, XSDConstants.SCHEMALOCATION_ATTRIBUTE))
    {
      newSchemaLocation = changedElement.getAttributeNS(null, XSDConstants.SCHEMALOCATION_ATTRIBUTE);
    }

    if (newSchemaLocation == null ? getSchemaLocation() != null : !newSchemaLocation.equals(getSchemaLocation()))
    {
      setSchemaLocation(newSchemaLocation);
    }
  }

  @Override
  protected void changeAttribute(EAttribute eAttribute)
  {
    // Try to resolve again if the schema location changes and we previously failed.
    //
    if (resolved && getResolvedSchema() == null && eAttribute == XSDPackage.Literals.XSD_SCHEMA_DIRECTIVE__SCHEMA_LOCATION)
    {
      resolved = false;
    }

    if (!isReconciling)
    {
      super.changeAttribute(eAttribute);
      if (eAttribute == null || eAttribute == XSDPackage.Literals.XSD_SCHEMA_DIRECTIVE__SCHEMA_LOCATION)
      {
        Element theElement = getElement();
        if (theElement != null)
        {
          niceSetAttribute(theElement, XSDConstants.SCHEMALOCATION_ATTRIBUTE, getSchemaLocation());
        }
      }
    }

    if (eAttribute == XSDPackage.Literals.XSD_SCHEMA_DIRECTIVE__SCHEMA_LOCATION && getResolvedSchema() != null && getSchema().isIncrementalUpdate())
    {
      getSchema().reset();
    }
  }
  
  @Override
  protected void orphanBy(XSDSchema xsdSchema)
  {
    super.orphanBy(xsdSchema);
    if (resolved && xsdSchema.isIncrementalUpdate())
    {
      xsdSchema.reset();
    }
  }

  protected boolean resolved;
  protected void resolve(String namespace, String schemaLocation)
  {
    if (!resolved)
    {
      XSDSchema xsdSchema = getSchema();
      if (xsdSchema != null && 
            (xsdSchema.getSchemaLocation() != null ||
              ((XSDSchemaImpl)xsdSchema).getPendingSchemaLocation() != null))
      {
        Resource resource = xsdSchema.eResource();
        if (resource != null)
        {
          ResourceSet resourceSet = resource.getResourceSet();
          if (resourceSet != null)
          {
            if ("".equals(namespace))
            {
              namespace = xsdSchema.getTargetNamespace();
            }

            String resolvedSchemaLocation = resolveSchemaLocation(xsdSchema, namespace, schemaLocation);
            if (schemaLocation == null)
            {
              if (XSDConstants.isXMLNamespace(resolvedSchemaLocation))
              {
                resolvedSchemaLocation = XSDPlugin.INSTANCE.getBaseURL().toString() + "cache/www.w3.org/2001/xml.xsd";
              } 
              else if (XSDConstants.SCHEMA_FOR_SCHEMA_URI_2001.equals(resolvedSchemaLocation))
              {
                resolvedSchemaLocation = XSDPlugin.INSTANCE.getBaseURL().toString() + "cache/www.w3.org/2001/XMLSchema.xsd";
              }
            }

            XSDSchema resolvedSchema = locateSchema(xsdSchema, namespace, schemaLocation, resolvedSchemaLocation);
            if (resolvedSchema == null)
            {
              URI uri = URI.createURI(resolvedSchemaLocation == null ? "" : resolvedSchemaLocation);
              Resource resolvedResource = resourceSet.getResource(uri, false);
              if (resolvedResource == null)
              {
                try
                {
                  InputStream inputStream = resourceSet.getURIConverter().createInputStream(uri);
                  resolvedResource = resourceSet.createResource(URI.createURI("*.xsd"));
                  resolvedResource.setURI(uri);
                  resolvedResource.load(inputStream, resourceSet.getLoadOptions());
                }
                catch (IOException exception)
                {
                  // It is generally not an error to fail to resolve.
                  // If a resource is actually created, 
                  // which happens only when we can create an input stream,
                  // then it's an error if it's not a good schema
                }
              }
              else if (!resolvedResource.isLoaded())
              {
                try
                {
                  resolvedResource.load(resourceSet.getLoadOptions());
                }
                catch (IOException exception)
                {
                  // Ignore.
                }
              }
  
              if (resolvedResource != null)
              {
                if (resolvedResource instanceof XSDResourceImpl)
                {
                  resolvedSchema = ((XSDResourceImpl)resolvedResource).getSchema();
                }
                else
                {
                  resolvedSchema = XSDFactory.eINSTANCE.createXSDSchema();
                }
              }
            }

            resolved = true;
            handleResolvedSchema(resolvedSchema);
          }
        }
      }
    }
  }

  protected XSDSchema locateSchema(XSDSchema xsdSchema, String namespace, String rawSchemaLocation, String resolvedSchemaLocation)
  {
    XSDSchemaLocator xsdSchemaLocator = (XSDSchemaLocator)EcoreUtil.getRegisteredAdapter(xsdSchema.eResource(), XSDSchemaLocator.class);
    return 
      xsdSchemaLocator == null ? 
        null : 
        xsdSchemaLocator.locateSchema(xsdSchema, namespace, rawSchemaLocation, resolvedSchemaLocation);
  }

  protected String resolveSchemaLocation(XSDSchema xsdSchema, String namespace, String schemaLocation)
  {
    for (;;)
    {
      XSDSchemaLocationResolver xsdSchemaLocationResolver = 
        (XSDSchemaLocationResolver)EcoreUtil.getRegisteredAdapter(xsdSchema, XSDSchemaLocationResolver.class);
      if (xsdSchemaLocationResolver != null)
      {
        return xsdSchemaLocationResolver.resolveSchemaLocation(xsdSchema, namespace, schemaLocation);
      }
      
      XSDConcreteComponent container = xsdSchema.getContainer();
      if (container == null)
      {
        break;
      }
      else
      {
        XSDSchema containingXSDSchema = container.getSchema();
        if (containingXSDSchema == null)
        {
          break;
        }
        else
        {
          xsdSchema = containingXSDSchema;
        }
      }
    }

    return XSDConstants.resolveSchemaLocation(xsdSchema.getSchemaLocation(), namespace, schemaLocation);
  }

  protected void handleResolvedSchema(XSDSchema xsdSchema)
  {
    // Ignore
  }
} 
