/**
 * 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 1999 - 2004 (C) Intalio, Inc. All Rights Reserved.
 *
 * $Id$
 */

package org.exolab.castor.xml.schema;

import org.exolab.castor.xml.*;

import java.util.Vector;
import java.util.Enumeration;

/**
 * An XML Schema ModelGroup : <xsd:group>
 * 
 * @author <a href="mailto:kvisco@intalio.com">Keith Visco</a>
 * @version $Revision$ $Date: 2006-04-25 15:08:23 -0600 (Tue, 25 Apr 2006) $
 **/
public class ModelGroup extends Group {
  /** SerialVersionUID */
  private static final long serialVersionUID = -2057934322265672413L;

  /**
   * the name of the ModelGroup referenced
   */
  private String _groupRef = null;

  /**
   * An ordered list of all ModelGroup definitions
   **/
  private final Vector<ModelGroup> _modelDefs = new Vector<>();

  /**
   * An attribute that indicates if this Group is a redefinition
   */
  private boolean _redefinition = false;


  /**
   * the schema that contains this model group
   */
  private Schema _schema = null;

  /**
   * Creates a new ModelGroup, with no name
   **/
  public ModelGroup() {
    this(null);
  } // -- ModelGroup

  /**
   * Creates a new ModelGroup definition
   * 
   * @param schema the XML Schema to which this ModelGroup belongs
   */
  public ModelGroup(Schema schema) {
    this(null, schema);
  }

  /**
   * Creates a new ModelGroup with the given name
   * 
   * @param name of the ModelGroup
   **/
  public ModelGroup(String name, Schema schema) {
    super(name);
    _schema = schema;
  } // -- ModelGroup


  /**
   * Adds the given ModelGroup to this ModelGroup
   * 
   * @param modelGroup the ModelGroup to add to this ModelGroup
   **/
  public void addModelGroup(ModelGroup modelGroup) {
    if (!_modelDefs.contains(modelGroup)) {
      _modelDefs.add(modelGroup);
    }
  } // -- addModelGroup

  /**
   * Returns an enumeration of all the Particles of this ContentModelGroup
   *
   * @return an enumeration of the Particles contained within this ContentModelGroup
   **/
  public Enumeration<Particle> enumerate() {
    return this.getContentModelGroup().enumerate();
  } // -- enumerate

  /**
   * Returns an ordered Enumeration of all the ContentModelType definitions (element, group,
   * modelGroupRef)+
   **/
  public Enumeration<ModelGroup> getDeclarations() {
    return _modelDefs.elements();
  } // -- getDeclarations

  /**
   * Returns the ContentModelGroup for this group Only used for a <group> element
   * 
   * @return the ContentModelGroup for this group
   */
  public ContentModelGroup getContentModelGroup() {
    if (_groupRef != null)
      return getReference().getContentModelGroup();
    return super.getContentModelGroup();
  }

  /**
   * Returns the name of this ModelGroup.
   * 
   * @return the name of this ModelGroup.
   **/
  public String getName() {
    return getName(false);
  } // -- getName

  /**
   * Returns the name of this Model Group definition
   *
   * @param ignoreRef If True the name of the referenced model group (if specified) is returned
   * @return the name of this element declaration
   **/
  public String getName(boolean ignoreRef) {

    if (ignoreRef == false && _groupRef != null) {
      String localName = _groupRef;
      // -- check for namespace prefix
      int idx = localName.indexOf(':');
      if (idx > 0) {
        localName = localName.substring(idx + 1);
      }
      return localName;
    }
    return super.getName();
  } // -- getName

  /**
   * Returns true if this model group definition simply references another model group Definition
   * 
   * @return true if this model group definition is a reference
   */
  public boolean isReference() {
    return (_groupRef != null);
  } // -- isReference

  /**
   * Sets this Group has redefined.
   */
  public void setRedefined() {
    _redefinition = true;
  }

  /**
   * Returns true if this group is a redefinition.
   * 
   * @return true if this group is a redefinition.
   */
  public boolean isRedefined() {
    return _redefinition;
  }

  /**
   * Sets the reference for this ModelGroup definition
   * 
   * @param reference the name of the ModelGroup that this definition references
   **/
  public void setReference(String reference) {
    this._groupRef = reference;
  } // -- setReference

  // -------------------------------/
  // - Implementation of Structure -/
  // -------------------------------/

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

  /**
   * Returns the Id used to Refer to this Object
   * 
   * @return the Id used to Refer to this Object
   * @see Referable
   **/
  public String getReferenceId() {
    if (this.getName() != null)
      return "group:" + this.getName();
    return null;
  } // -- getReferenceId

  /**
   * Returns the reference if any
   * 
   * @return the reference if any
   */
  public ModelGroup getReference() {
    ModelGroup result = null;
    if (_groupRef != null) {
      result = _schema.getModelGroup(_groupRef);

      // --check for redefinition
      if (_schema.getMasterSchema() != null) {
        ModelGroup temp = _schema.getMasterSchema().getModelGroup(_groupRef);
        if (temp != null)
          result = temp;
      }

      if (result == null) {
        String err = "Unable to find group referenced :\" ";
        err += getName();
        err += "\"";
        throw new IllegalStateException(err);
      }
    }
    return result;
  } // -- getReference


  /**
   * Returns true if this ModelGroup is referencing another one
   * 
   * @return true if this ModelGroup is referencing another one
   */
  public boolean hasReference() {
    return (_groupRef != null) ? (_groupRef.length() != 0) : false;
  }

  /**
   * Checks the validity of this Schema defintion.
   * 
   * @exception ValidationException when this Schema definition is invalid.
   **/
  public void validate() throws ValidationException {
    // -- check name
    if (getParent() != null && getParent().getStructureType() != Structure.SCHEMA) {
      if (getName(true) != null) {
        String err = "Only top-level model group definition (<group>) can be named.";
        err += getName() + "is not a valid model group definition.";
        throw new ValidationException(err);
      }
    }

    if (getContentModelGroup() == null) {
      String err = "<group> should contains :\" ";
      err += " 'all' or 'sequence' or 'choice'";
      err += "\"";
      throw new ValidationException(err);
    }

    // -- Check for circular references
    // -- Validation related to section 3.8.6 : Constraints on Model Group Schema Components
    // -- Schema Component Constraint: Model Group Correct
    // -- from the W3C XML Schema Recommendation
    for (int i = 0; i < getParticleCount(); i++) {
      Structure temp = getParticle(i);
      switch (temp.getStructureType()) {
        case Structure.MODELGROUP:
          ModelGroup tempGroup = (ModelGroup) temp;
          String name = null;
          if (tempGroup.getReference() != null)
            name = tempGroup.getReference().getName();

          if (name != null && name.equals(this.getName())) {
            if (isRedefined()) {
              if (getMaxOccurs() != 1 || getMinOccurs() != 1) {
                String err = "in the redefined <group> named:" + this.getName();
                err +=
                    "\nThe particle information (minOccurs, maxOccurs) of a circular group must be set to 1.\n";
                throw new ValidationException(err);
              }

            } else {
              String err = "in <group> named:" + this.getName();
              err += "\nCircular groups are disallowed.\n";
              err +=
                  "That is, within the {particles} of a group there must not be at any depth a particle whose {term} is the group itself.\n";
              throw new ValidationException(err);
            }
          }
          // check cross-reference
          int j = 0;
          tempGroup = tempGroup.getReference();
          while (j < tempGroup.getParticleCount()) {
            if (tempGroup.getParticle(j).getStructureType() == Structure.MODELGROUP) {
              ModelGroup referencedGroup = ((ModelGroup) tempGroup.getParticle(j)).getReference();
              if ((referencedGroup != null) && (referencedGroup.equals(this))) {
                if (isRedefined()) {
                  if (getMaxOccurs() != 1 || getMinOccurs() != 1) {
                    String err = "in the redefined <group> named:" + this.getName();
                    err +=
                        "\nThe particle information (minOccurs, maxOccurs) of a circular group must be set to 1.\n";
                    throw new ValidationException(err);
                  }
                } else {
                  String err = "Cross reference between <group>:" + this.getName() + " and <group>:"
                      + tempGroup.getName();
                  err += "\nCircular groups are disallowed.\n";
                  err +=
                      "That is, within the {particles} of a group there must not be at any depth a particle whose {term} is the group itself.\n";
                  throw new ValidationException(err);
                }
              }

            }
            j++;

          }
          break;
        default:
          break;

      }
    }
  } // -- validate

  /**
   * Returns the schema that contains this modelGroup definition
   */
  public Schema getSchema() {
    return _schema;
  }

  /**
   * Sets the Schema that contains this group.
   *
   * @param schema the Schema that contains this group.
   */
  public void setSchema(Schema schema) {
    _schema = schema;
  }



} // -- Group
