/*
 *  eXist Open Source Native XML Database
 *  Copyright (C) 2001-06 Wolfgang M. Meier
 *  wolfgang@exist-db.org
 *  http://exist.sourceforge.net
 *  
 *  This program is free software; you can redistribute it and/or
 *  modify it under the terms of the GNU Lesser General Public License
 *  as published by the Free Software Foundation; either version 2
 *  of the License, or (at your option) any later version.
 *  
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU Lesser General Public License for more details.
 *  
 *  You should have received a copy of the GNU Lesser General Public License
 *  along with this program; if not, write to the Free Software
 *  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 *  
 *  $Id: SequenceType.java 4163 2006-08-24 14:23:13Z wolfgang_m $
 */
package org.exist.xquery.value;

import org.exist.dom.QName;
import org.exist.xquery.Cardinality;
import org.exist.xquery.XPathException;
import org.w3c.dom.Node;

/**
 * Represents an XQuery SequenceType and provides methods to check
 * sequences and items against this type.
 * 
 * @author wolf
 */
public class SequenceType {
 
	private int primaryType = Type.ITEM;
	private int cardinality = Cardinality.EXACTLY_ONE;
	private QName nodeName = null;
	
	public SequenceType() {
	}

    /**
     * Construct a new SequenceType using the specified
     * primary type and cardinality constants.
     * 
     * @param primaryType
     * @param cardinality
     */
	public SequenceType(int primaryType, int cardinality) {
		this.primaryType = primaryType;
		this.cardinality = cardinality;
	}

    /**
     * Returns the primary type as one of the
     * constants defined in {@link Type}.
     */
	public int getPrimaryType() {
		return primaryType;
	}

	public void setPrimaryType(int type) {
		this.primaryType = type;
	}

    /**
     * Returns the expected cardinality. See the constants 
     * defined in {@link Cardinality}.
     * 
     */
	public int getCardinality() {
		return cardinality;
	}

	public void setCardinality(int cardinality) {
		this.cardinality = cardinality;
	}

	public QName getNodeName() {
		return nodeName;
	}
	
	public void setNodeName(QName qname) {
		this.nodeName = qname;
	}

    /**
     * Check the specified sequence against this SequenceType.
     *  
     * @param seq
     * @throws XPathException 
     * @throws XPathException 
     */
    public boolean checkType(Sequence seq) throws XPathException {
        if (nodeName != null) {
            Item next;
            for (SequenceIterator i = seq.iterate(); i.hasNext(); ) {
                next = i.nextItem();
                if (!checkType(next))
                    return false;
            }
            return true;
        } else {
            return Type.subTypeOf(seq.getItemType(), primaryType);
        }
    }
    
    /**
     * Check a single item against this SequenceType.
     * 
     * @param item
     */
	public boolean checkType(Item item) {
		if(!Type.subTypeOf(item.getType(), primaryType))
			return false;
		if(nodeName != null) {
			//TODO : how to improve performance ?
			Node n = ((NodeValue)item).getNode();
			if (nodeName.getNamespaceURI() != null) {
				if (!nodeName.getNamespaceURI().equals(n.getNamespaceURI()))
					return false;
			}
			if (nodeName.getLocalName() != null) {
				return nodeName.getLocalName().equals(n.getLocalName());
			}
		}
		return true;
	}
	
    /**
     * Check the given type against the primary type
     * declared in this SequenceType.
     * 
     * @param type
     * @throws XPathException
     */
	public void checkType(int type) throws XPathException {
		if (type == Type.EMPTY || type == Type.ITEM)
			return;
        
		if (!Type.subTypeOf(type, primaryType))
			throw new XPathException(
				"Type error: expected type: "
					+ Type.getTypeName(primaryType)
					+ "; got: "
					+ Type.getTypeName(type));
	}

    /**
     * Check if the given sequence has the cardinality required
     * by this sequence type.
     * 
     * @param seq
     * @throws XPathException
     */
	public void checkCardinality(Sequence seq) throws XPathException {
		if (!seq.isEmpty() && cardinality == Cardinality.EMPTY)
			throw new XPathException("Empty sequence expected; got " + seq.getLength());
		if (seq.isEmpty() && (cardinality & Cardinality.ZERO) == 0)
			throw new XPathException("Empty sequence is not allowed here");
		else if (seq.hasMany() && (cardinality & Cardinality.MANY) == 0)
			throw new XPathException("Sequence with more than one item is not allowed here");
	}

	public String toString() {
		if (cardinality == Cardinality.EMPTY)
			return Cardinality.toString(cardinality);
		return Type.getTypeName(primaryType) + Cardinality.toString(cardinality);
	}

}
