/**
 * Redistribution and use of this software and associated documentation ("Software"), with or
 * without modification, are permitted provided that the following conditions are met:
 *
 * 1. Redistributions of source code must retain copyright statements and notices. Redistributions
 * must also contain a copy of this document.
 *
 * 2. Redistributions in binary form must reproduce the above copyright notice, this list of
 * conditions and the following disclaimer in the documentation and/or other materials provided with
 * the distribution.
 *
 * 3. The name "Exolab" must not be used to endorse or promote products derived from this Software
 * without prior written permission of Intalio, Inc. For written permission, please contact
 * info@exolab.org.
 *
 * 4. Products derived from this Software may not be called "Exolab" nor may "Exolab" appear in
 * their names without prior written permission of Intalio, Inc. Exolab is a registered trademark of
 * Intalio, Inc.
 *
 * 5. Due credit should be given to the Exolab Project (http://www.exolab.org/).
 *
 * THIS SOFTWARE IS PROVIDED BY INTALIO, INC. AND CONTRIBUTORS ``AS IS'' AND ANY EXPRESSED OR
 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
 * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL INTALIO, INC. OR ITS
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY
 * WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 * Copyright 2004 (C) Intalio Inc. All Rights Reserved.
 *
 * $Id$
 */
package org.exolab.castor.xml.schema;

import java.util.Collection;
import java.util.Collections;
import java.util.Hashtable;
import java.util.Map;

import org.exolab.castor.xml.ValidationException;

/**
 * <p>
 * This class is a wrapper used to save meta information concerning redefined structures from an XML
 * schema.
 * </p>
 * 
 * <p>
 * This wrapper is identified by:
 * <ul>
 * <li>a reference to the schema redefined</li>
 * <li>the schema location of the redefined schema</li>
 * <li>A vector containing the names of the structures redefined</li>
 * </ul>
 * 
 * @author <a href="mailto:blandin@intalio.com">Arnaud Blandin</a>
 * @version $Revision$ $Date: 2006-04-13 06:47:36 -0600 (Thu, 13 Apr 2006) $
 **/
public class RedefineSchema extends Annotated {
  /** SerialVersionUID */
  private static final long serialVersionUID = -7095458840388436859L;

  /**
   * The original schema that is imported in the parent schema
   */
  private Schema _originalSchema;

  /**
   * The parent schema in which this redefined XML Schema is used.
   */
  private Schema _parentSchema;

  /**
   * The schema location of the redefined schema
   */
  private String _schemaLocation = null;

  /**
   * A vector with the names of the redefined complexTypes
   */
  private Map<String, ComplexType> _complexTypes;

  /**
   * A vector with the names of the redefined simpleTypes
   */
  private Map<String, SimpleType> _simpleTypes;

  /**
   * A vector with the names of the redefined groups
   */
  private Map<String, ModelGroup> _groups;

  /**
   * A vector with the names of the redefined attributeGroups
   */
  private Map<String, AttributeGroup> _attributeGroups;

  /**
   * Default constructor to create a RedefineSchema that contains only Annotations
   * 
   */
  public RedefineSchema(Schema parentSchema) {
    super();
    _parentSchema = parentSchema;
    _schemaLocation = "";
  }

  /**
   * Constructs a new RedefineSchema structure
   */
  public RedefineSchema(Schema parentSchema, Schema originalSchema) {
    super();
    _schemaLocation = originalSchema.getSchemaLocation();
    _parentSchema = parentSchema;
    _originalSchema = originalSchema;
    _complexTypes = new Hashtable<String, ComplexType>();
    _simpleTypes = new Hashtable<String, SimpleType>();
    _attributeGroups = new Hashtable<String, AttributeGroup>();
    _groups = new Hashtable<String, ModelGroup>();
  }

  /**
   * Adds the given complexType in the list of redefined complexTypes.
   * 
   * @param complexType the ComplexType to add.
   */
  public void addComplexType(ComplexType complexType) throws SchemaException {
    if (_schemaLocation.length() == 0)
      throw new IllegalStateException(
          "A RedefineSchema with no schemaLocation must contain only annotations");
    String name = complexType.getName();

    if (name == null) {
      String err = "a global ComplexType must contain a name.";
      throw new SchemaException(err);
    }

    if (complexType.getSchema() != _parentSchema) {
      String err = "invalid attempt to add an ComplexType which ";
      err += "belongs to a different Schema; type name: " + name;
      throw new SchemaException(err);
    }

    if (getComplexType(name) != null) {
      String err = "In the RedefineSchema:" + _schemaLocation
          + "a ComplexType already exists with the given name: ";
      throw new SchemaException(err + name);
    }

    // --forces the redefine character
    complexType.setRedefined();

    _complexTypes.put(name, complexType);
    complexType.setParent(_parentSchema);
  }

  /**
   * Enumerates the complexType names.
   * 
   * @return an enumeration of the names of the redefined ComplexTypes
   */
  public Collection<ComplexType> enumerateComplexTypes() {
    if (_schemaLocation.length() == 0) {
      return Collections.emptyList();
    }
    return _complexTypes.values();
  }

  /**
   * Returns the ComplexType corresponding to the given name.
   * 
   * @param name the name of the ComplexType to look for.
   * @return the ComplexType corresponding to the gven name.
   */
  public ComplexType getComplexType(String name) {
    if (_schemaLocation.length() == 0) {
      return null;
    }

    return _complexTypes.get(name);
  }

  /**
   * Returns true if this redefinition contains a redefinition for a complexType with the given
   * name.
   * 
   * @param name the canonical name of the complexType.
   */
  public boolean hasComplexTypeRedefinition(String name) {
    if (_schemaLocation.length() == 0)
      return false;

    return (_complexTypes.containsKey(name));
  }

  /**
   * Removes the complexType with the given name from the redefine structure.
   * 
   * @param complexType The complexType to be removed from this Redefined Structure.
   */
  public boolean removeComplexType(ComplexType complexType) {
    if (_schemaLocation.length() == 0)
      return false;

    boolean result = false;
    if (_complexTypes.containsValue(complexType)) {
      _complexTypes.remove(complexType);
      result = true;
    }
    return result;
  }

  /**
   * Adds a simpleType in the list of redefined simpleTypes.
   * 
   * @param simpleType the SimpleType to add.
   */
  public void addSimpleType(SimpleType simpleType) throws SchemaException {

    String name = simpleType.getName();
    if (name == null)
      throw new IllegalArgumentException("A redefined simpleType must have a name");
    if (_schemaLocation.length() == 0)
      throw new IllegalStateException(
          "A RedefineSchema with no schemaLocation must contain only annotations");

    if (simpleType.getSchema() != _parentSchema) {
      String err = "invalid attempt to add a SimpleType which ";
      err += "belongs to a different Schema; type name: " + name;
      throw new SchemaException(err);
    }

    if (getSimpleType(name) != null) {
      String err = "In the RedefineSchema:" + _schemaLocation
          + "a SimpleType already exists with the given name: ";
      throw new SchemaException(err + name);
    }
    // --forces the redefine character
    simpleType.setRedefined();

    simpleType.setParent(this);
    _simpleTypes.put(name, simpleType);
  }

  /**
   * Enumerates the simpleType names.
   * 
   * @return an enumeration of the names of the redefined SimpleTypes.
   */
  public Collection<SimpleType> enumerateSimpleTypes() {
    if (_schemaLocation.length() == 0) {
      return Collections.emptyList();
    }

    return _simpleTypes.values();
  }

  /**
   * Returns the SimpleType corresponding to the given name.
   * 
   * @param name the name of the SimpleType to look for.
   * @return the SimpleType corresponding to the gven name.
   */
  public SimpleType getSimpleType(String name) {
    if (_schemaLocation.length() == 0) {
      return null;
    }

    return _simpleTypes.get(name);
  }

  /**
   * Returns true if this redefinition contains a redefinition for a simpleType with the given name.
   * 
   * @param name the canonical name of the simpleType.
   */
  public boolean hasSimpleTypeRedefinition(String name) {
    if (_schemaLocation.length() == 0) {
      return false;
    }

    return (_simpleTypes.containsKey(name));
  }

  /**
   * Removes the given simpleType from the redefine structure.
   * 
   * @param simpleType the simpleType to be removed from this Redefined Structure.
   */
  public boolean removeSimpleType(SimpleType simpleType) {
    if (_schemaLocation.length() == 0)
      return false;

    boolean result = false;
    if (_simpleTypes.containsValue(simpleType)) {
      _simpleTypes.remove(simpleType);
      result = true;
    }
    return result;
  }

  /**
   * Adds a group name in the list of redefined groups.
   */
  public void addGroup(ModelGroup group) throws SchemaException {
    if (_schemaLocation.length() == 0) {
      throw new IllegalStateException(
          "A RedefineSchema with no schemaLocation must contain only annotations");
    }

    String name = group.getName();

    if (name == null) {
      String err = "a group declaration must contain a name.";
      throw new SchemaException(err);
    }

    if (getModelGroup(name) != null) {
      String err = "In the RedefineSchema:" + _schemaLocation
          + " a group declaration already exists with the given name: ";
      throw new SchemaException(err + name);
    }
    // --forces the redefine character
    group.setRedefined();
    group.setParent(_parentSchema);

    _groups.put(name, group);
  }

  /**
   * Returns the Model Group of the given name that is contained in this RedefineSchema.
   * 
   * @param name the name of the ModelGroup to retrieve.
   * @return the ModelGroup of the given name contained in this RedefineSchema.
   */
  public ModelGroup getModelGroup(String name) {
    if (_schemaLocation.length() == 0) {
      return null;
    }

    return _groups.get(name);
  }

  /**
   * Enumerates the group names.
   * 
   * @return an enumeration of the names of the redefined groups.
   */
  public Collection<ModelGroup> enumerateGroups() {
    if (_schemaLocation.length() == 0) {
      return Collections.emptyList();
    }

    return _groups.values();
  }

  /**
   * Returns true if this redefinition contains a redefinition for a group with the given name.
   * 
   * @param name the canonical name of the complexType.
   */
  public boolean hasGroupRedefinition(String name) {
    if (_schemaLocation.length() == 0)
      return false;

    return (_groups.containsKey(name));
  }

  /**
   * Removes the given ModelGroup from the redefine structure.
   * 
   * @param group the ModelGroup to be removed from this Redefined Structure.
   */
  public boolean removeGroup(ModelGroup group) {
    if (_schemaLocation.length() == 0)
      return false;

    boolean result = false;
    if (_groups.containsValue(group)) {
      _groups.remove(group);
      result = true;
    }
    return result;
  }

  /**
   * Adds a AttributeGroup name in the list of redefined attributeGroups.
   */
  public void addAttributeGroup(AttributeGroupDecl attrGroup) throws SchemaException {
    if (_schemaLocation.length() == 0)
      throw new IllegalStateException(
          "A RedefineSchema with no schemaLocation must contain only annotations");

    if (attrGroup == null)
      return;

    String name = attrGroup.getName();

    // -- handle namespace prefix, if necessary
    int idx = name.indexOf(':');
    if (idx >= 0) {
      String nsPrefix = name.substring(0, idx);
      name = name.substring(idx + 1);
      String ns = _parentSchema.getNamespace(nsPrefix);
      if (ns == null) {
        String err = "addAttributeGroup: ";
        err += "Namespace prefix not recognized '" + nsPrefix + "'";
        throw new IllegalArgumentException(err);
      }
      if (!ns.equals(_parentSchema.getTargetNamespace())) {
        String err = "AttributeGroup has different namespace " + "than this Schema definition.";
        throw new IllegalArgumentException(err);
      }
    }

    if (attrGroup.getSchema() != _parentSchema) {
      String err = "invalid attempt to add an AttributeGroup which ";
      err += "belongs to a different Schema; " + name;
      throw new SchemaException(err);
    }

    attrGroup.setRedefined();

    _attributeGroups.put(name, attrGroup);
  }

  /**
   * Returns the AttributeGroup corresponding to the given canonical name (unqualified name).
   * 
   * @return the AttributeGroup corresponding to the given canonical name (unqualified name).
   */
  public AttributeGroupDecl getAttributeGroup(String name) {
    if (_schemaLocation.length() == 0)
      return null;

    return (AttributeGroupDecl) _attributeGroups.get(name);
  }

  /**
   * Enumerates the attributeGroup names.
   * 
   * @return an enumeration of the names of the redefined AttributeGroups.
   */
  public Collection<AttributeGroup> enumerateAttributeGroups() {
    if (_schemaLocation.length() == 0) {
      return Collections.emptyList();
    }

    return _attributeGroups.values();
  }

  /**
   * Returns true if this redefinition contains a redefinition for an AttributeGroup with the given
   * name.
   * 
   * @param name the canonical name of the complexType.
   */
  public boolean hasAttributeGroupRedefinition(String name) {
    if (_schemaLocation.length() == 0)
      return false;

    return (_attributeGroups.containsKey(name));
  }

  /**
   * Removes the attributeGroup with the given name from the redefine structure.
   * 
   * @param attrGroup the attributeGroup to be removed from this Redefined Structure.
   */
  public boolean removeAttributeGroup(AttributeGroupDecl attrGroup) {
    if (_schemaLocation.length() == 0)
      return false;

    boolean result = false;
    if (_attributeGroups.containsValue(attrGroup)) {
      _attributeGroups.remove(attrGroup);
      result = true;
    }
    return result;
  }

  /**
   * Returns true if at least one structure other than an annotation is present.
   * 
   * @return true if at least one structure other than an annotation is present.
   */
  public boolean hasRedefinition() {
    if (_schemaLocation.length() == 0)
      return false;

    return (!_complexTypes.isEmpty()) || (!_simpleTypes.isEmpty()) || (!_groups.isEmpty())
        || (!_attributeGroups.isEmpty());
  }

  /**
   * Returns the URI of the imported schema.
   * 
   * @return the URI of the imported schema.
   */
  public String getSchemaLocation() {
    return _schemaLocation;
  }

  /**
   * Returns the schema imported used for the redefinitions.
   * 
   * @return the original schema imported.
   */
  public Schema getOriginalSchema() {
    return _originalSchema;
  }

  /**
   * Returns the parent schema in which this RedefineSchema is used.
   * 
   * @return the parent schema in which this Redefined Schema is used.
   */
  public Schema getParentSchema() {
    return _parentSchema;
  }

  /**
   * Returns the type of this Redefine Structure
   * 
   * @return the type of this Redefin Structure
   **/
  public short getStructureType() {
    return Structure.REDEFINE;
  } // -- getStructureType


  public void validate() throws ValidationException {
    // -- no validation performed on the structure since
    // -- it is simply a place holder for names and not for real structures.
  }

}
