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

package org.exolab.castor.xml.schema;

import java.util.Enumeration;

import org.exolab.castor.xml.ValidationException;

/**
 * An XML Schema Group
 * 
 * @author <a href="mailto:kvisco@intalio.com">Keith Visco</a>
 * @version $Revision$ $Date: 2006-04-14 04:14:43 -0600 (Fri, 14 Apr 2006) $
 **/
public class Group extends Particle implements ContentModelGroup, Referable {
  /** SerialVersionUID */
  private static final long serialVersionUID = 3133443973681261845L;

  /**
   * the implementation of ContentModelGroup.
   **/
  private ContentModelGroup _contentModel = null;

  /**
   * The name of this Group.
   **/
  private String _name = null;

  /**
   * The Compositor for the Group.
   **/
  private Order _order = Order.sequence;

  /**
   * ID of this Group (if present at all).
   */
  private String _id = null;

  /**
   * True if was created for a group tag, false otherwise (all, choice, sequence).
   */
  private boolean _isModelGroupDefinition = false;

  /**
   * The parent for this Group (either another Group or a ComplexType).
   **/
  private Structure _parent = null;

  /**
   * Creates a new {@link Group}, with no name.
   **/
  public Group() {
    this(null);
  }

  /**
   * Creates a new {@link Group} with the given name.
   * 
   * @param name of the {@link Group}
   **/
  public Group(final String name) {
    super();
    this._name = name;
    _contentModel = new ContentModelGroupImpl();
  }

  /**
   * {@inheritDoc}
   * 
   * @see org.exolab.castor.xml.schema.ContentModelGroup#addWildcard(org.exolab.castor.xml.schema.Wildcard)
   */
  public void addWildcard(final Wildcard wildcard) throws SchemaException {
    if (wildcard.isAttributeWildcard()) {
      throw new SchemaException("only <any> should be add in a group.");
    }
    _contentModel.addWildcard(wildcard);
  }

  /**
   * {@inheritDoc}
   * 
   * @see org.exolab.castor.xml.schema.ContentModelGroup#removeWildcard(org.exolab.castor.xml.schema.Wildcard)
   */
  public boolean removeWildcard(final Wildcard wildcard) {
    if (wildcard == null) {
      return false;
    }
    return _contentModel.removeWildcard(wildcard);
  }

  /**
   * Returns the {@link ContentModelGroup} for this group. Only used for a <group/> element
   * 
   * @return the ContentModelGroup for this group
   */
  public ContentModelGroup getContentModelGroup() {
    return _contentModel;
  }

  /**
   * Returns the ID for this {@link Group}.
   * 
   * @return the ID for this {@link Group}, or null if no ID is present
   **/
  public String getId() {
    return _id;
  }

  /**
   * Returns the name of this {@link Group}, or null if no name was defined.
   * 
   * @return the name of this {@link Group}, or null if no name was defined
   **/
  public String getName() {
    return _name;
  }

  /**
   * Returns the compositor for this {@link Group}.
   * 
   * @return the compositor for this {@link Group}
   **/
  public Order getOrder() {

    // -- Return proper compositor...
    // -- according to XML Schema spec 20000407 section 4.3.5

    // -- Note: it's important not to simply call
    // -- #getParticleCount or #getParticle because those
    // -- methods also perform some trickery
    if (_contentModel.getParticleCount() == 1) {
      Particle particle = _contentModel.getParticle(0);
      if (particle.getStructureType() == Structure.GROUP) {
        if ((getMinOccurs() == 1) && (getMaxOccurs() == 1)) {
          return ((Group) particle).getOrder();
        }
      }
    }
    return this._order;
  }


  /**
   * Returns the parent of this Group, this value may be null if no parent has been set.
   *
   * @return the parent Structure of this Group.
   **/
  public Structure getParent() {
    return _parent;
  }

  /**
   * Sets if the group is a model group definition.
   * 
   * @deprecated Since Castor 0.9.2, to handle properly the <group/> element the class ModelGroup
   *             has been created
   * @see ModelGroup
   */
  public void setIsModelGroupDefinition(final boolean isModelGroupDefinition) {
    _isModelGroupDefinition = isModelGroupDefinition;
  }

  /**
   * Tells if the group is a model group definition.
   * 
   * @return true if the group is a model group definition (<group/> tag), false otherwise
   *         {@literal <all/>}, <choice/>, or <sequence/> tags.
   * @deprecated Since Castor 0.9.2, to handle properly the <group/> element the class
   *             {@link ModelGroup} has been created
   * @see ModelGroup
   * 
   */
  public boolean isModelGroupDefinition() {
    return _isModelGroupDefinition;
  }


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

  /**
   * Sets the name of this {@link Group}.
   * 
   * @param name the new name for this {@link Group}
   **/
  public void setName(final String name) {
    this._name = name;
  }

  /**
   * Sets the ID for this {@link Group}.
   * 
   * @param id the ID for this {@link Group}
   **/
  public void setId(final String id) {
    _id = id;
  }

  /**
   * Sets the {@link Order} for this {@link Group}.
   * 
   * @param order the type of {@link Order} that this {@link Group} is restricted to
   **/
  public void setOrder(final Order order) {
    if (order == null) {
      this._order = Order.all;
    } else {
      this._order = order;
    }
  }

  // ---------------------------------------/
  // - Implementation of ContentModelGroup -/
  // ---------------------------------------/

  /**
   * {@inheritDoc}
   * 
   * @see org.exolab.castor.xml.schema.ContentModelGroup#addElementDecl(org.exolab.castor.xml.schema.ElementDecl)
   */
  public void addElementDecl(final ElementDecl elementDecl) throws SchemaException {
    _contentModel.addElementDecl(elementDecl);
    // --set the parent
    elementDecl.setParent(this);
  }

  /**
   * {@inheritDoc}
   * 
   * @see org.exolab.castor.xml.schema.ContentModelGroup#removeElementDecl(org.exolab.castor.xml.schema.ElementDecl)
   */
  public boolean removeElementDecl(final ElementDecl element) {
    return _contentModel.removeElementDecl(element);
  }

  /**
   * {@inheritDoc}
   * 
   * @see org.exolab.castor.xml.schema.ContentModelGroup#addGroup(org.exolab.castor.xml.schema.Group)
   */
  public void addGroup(final Group group) throws SchemaException {
    _contentModel.addGroup(group);

    // -- set reference to parent
    group.setParent(this);

  }

  /**
   * {@inheritDoc}
   * 
   * @see org.exolab.castor.xml.schema.ContentModelGroup#removeGroup(org.exolab.castor.xml.schema.Group)
   */
  public boolean removeGroup(final Group group) {
    boolean result = _contentModel.removeGroup(group);
    group.setParent(null);
    return result;
  }

  /**
   * {@inheritDoc}
   * 
   * @see org.exolab.castor.xml.schema.ContentModelGroup#addGroup(org.exolab.castor.xml.schema.ModelGroup)
   */
  public void addGroup(final ModelGroup group) throws SchemaException {
    _contentModel.addGroup(group);

    // -- set reference to parent
    group.setParent(this);
  }

  /**
   * {@inheritDoc}
   * 
   * @see org.exolab.castor.xml.schema.ContentModelGroup#removeGroup(org.exolab.castor.xml.schema.ModelGroup)
   */
  public boolean removeGroup(final ModelGroup group) {
    boolean result = _contentModel.removeGroup(group);
    group.setParent(null);
    return result;
  }

  /**
   * {@inheritDoc}
   * 
   * @see org.exolab.castor.xml.schema.ContentModelGroup#enumerate()
   */
  public Enumeration<Particle> enumerate() {
    // -- Some trickery to properly handle
    // -- XML Schema spec 20000407 section 4.3.5

    if (_contentModel.getParticleCount() == 1) {
      Particle particle = _contentModel.getParticle(0);
      if (particle.getStructureType() == Structure.GROUP) {
        Group temp = (Group) particle;
        if (((getMinOccurs() == 1) && (getMaxOccurs() == 1))
            && ((temp.getMinOccurs() == 1) && (temp.getMaxOccurs() == 1))) {
          return temp.enumerate();
        }
      }
    }
    return _contentModel.enumerate();
  } // -- enumerate


  /**
   * {@inheritDoc}
   * 
   * @see org.exolab.castor.xml.schema.ContentModelGroup#getElementDecl(java.lang.String)
   */
  public ElementDecl getElementDecl(final String name) {
    return _contentModel.getElementDecl(name);
  }

  /**
   * {@inheritDoc}
   * 
   * @see org.exolab.castor.xml.schema.ContentModelGroup#getParticle(int)
   */
  public Particle getParticle(final int index) {
    // -- Some trickery to properly handle
    // -- XML Schema spec 20000407 section 4.3.5
    if (_contentModel.getParticleCount() == 1) {
      Particle particle = _contentModel.getParticle(0);
      if (particle.getStructureType() == Structure.GROUP) {
        if ((getMinOccurs() == 1) && (getMaxOccurs() == 1)) {
          return ((Group) particle).getParticle(index);
        }
      }
    }
    return _contentModel.getParticle(index);
  }

  /**
   * {@inheritDoc}
   * 
   * @see org.exolab.castor.xml.schema.ContentModelGroup#getParticleCount()
   */
  public int getParticleCount() {
    // -- Some trickery to properly handle
    // -- XML Schema spec 20000407 section 4.3.5
    if (_contentModel.getParticleCount() == 1) {
      Particle particle = _contentModel.getParticle(0);
      if (particle.getStructureType() == Structure.GROUP) {
        if ((getMinOccurs() == 1) && (getMaxOccurs() == 1)) {
          return ((Group) particle).getParticleCount();
        }
      }
    }
    return _contentModel.getParticleCount();
  }

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

  /**
   * {@inheritDoc}
   * 
   * @see org.exolab.castor.xml.schema.Structure#getStructureType()
   */
  public short getStructureType() {
    return Structure.GROUP;
  }

  /**
   * A helper method that returns true if this group contains an {@literal <any>} element.
   * 
   * @return method that returns true if this group contains an {@literal <any>} element.
   */
  public boolean hasAny() {
    boolean result = false;
    Enumeration<Particle> enumeration = _contentModel.enumerate();
    while (enumeration.hasMoreElements() && !result) {
      Structure struct = enumeration.nextElement();
      switch (struct.getStructureType()) {
        case Structure.ELEMENT:
          break;
        case Structure.GROUP:
        case Structure.MODELGROUP:
          result = ((Group) struct).hasAny();
          break;
        case Structure.WILDCARD:
          result = true;
          break;
        default:
          break;
      }
    }
    return result;
  }

  /**
   * Checks the validity of this {@link Group} defintion.
   *
   * @throws ValidationException when this {@link Group} definition is invalid.
   **/
  public void validate() throws ValidationException {
    if (_order == Order.all) {
      if (getMaxOccurs() != 1) {
        String err = "Wrong maxOccurs value for a <all>:" + getMaxOccurs();
        err += "\n1 is the only possible value.";
        throw new ValidationException(err);
      }
      if (getMinOccurs() > 1) {
        String err = "Wrong minOccurs value for a <all>:" + getMinOccurs();
        err += "\n0 or 1 are the only possible values.";
        throw new ValidationException(err);
      }
    }
    Enumeration<Particle> enumeration = _contentModel.enumerate();
    while (enumeration.hasMoreElements()) {
      enumeration.nextElement().validate();
    }
  }

  /**
   * Sets the parent for this {@link Group}.
   *
   * @param parent the parent {@link Structure} for this {@link Group}
   **/
  protected void setParent(final Structure parent) {
    if (parent != null) {
      switch (parent.getStructureType()) {
        case Structure.COMPLEX_TYPE:
        case Structure.GROUP:
        case Structure.MODELGROUP:
        case Structure.SCHEMA:
          break;
        default:
          String error = "Invalid parent for group";
          throw new IllegalArgumentException(error);
      }
    }
    _parent = parent;
  }

  /**
   * Indicates whether this {@link Particle} is 'emptiable'
   * 
   * @return true if this Particle is 'emptiable'
   */
  public boolean isEmptiable() {
    if (getMinOccurs() == 0) {
      return true;
    }

    boolean result = false;
    switch (this.getOrder()) {
      case choice: {
        result = false;
        Enumeration<Particle> enumerate = this.enumerate();
        while (enumerate.hasMoreElements()) {
          Particle p = enumerate.nextElement();
          if (p.isEmptiable()) {
            result = true;
            break;
          }
        }
      }
        break;

      case all:
      case sequence: {
        result = true;
        Enumeration<Particle> enumerate = this.enumerate();
        while (enumerate.hasMoreElements()) {
          Particle p = enumerate.nextElement();
          if (!p.isEmptiable()) {
            result = false;
            break;
          }
        }
      }
        break;
    }
    return result;

  }

}
