/*
 * JBoss, Home of Professional Open Source.
 * See the COPYRIGHT.txt file distributed with this work for information
 * regarding copyright ownership.  Some portions may be licensed
 * to Red Hat, Inc. under one or more contributor license agreements.
 * 
 * This library 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.1 of the License, or (at your option) any later version.
 * 
 * This library 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 library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
 * 02110-1301 USA.
 */

package org.teiid.query.sql.lang;

import java.util.Collection;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.TreeSet;

import org.teiid.core.util.EquivalenceUtil;
import org.teiid.core.util.HashCodeUtil;
import org.teiid.query.sql.LanguageObject;
import org.teiid.query.sql.LanguageVisitor;
import org.teiid.query.sql.symbol.Expression;


/**
 * A criteria which is true is the expression's value is a member in a list 
 * of values.  This criteria can be represented as "<expression> IN (<expr>, ...)".  
 */
public class SetCriteria extends AbstractSetCriteria {

	/** The set of value expressions */
    private Collection values;
    private boolean allConstants;

    /**
     * Constructs a default instance of this class.
     */
    public SetCriteria() {}

    /**
     * Constructs an instance of this class with the membership expression and value expressions
     * @param expression The membership expression 
     * @param values   The set of value {@link org.teiid.query.sql.symbol.Expression}s
     */
    public SetCriteria( Expression expression, Collection values ) {
        set(expression,values);
    }

    /**
     * Returns the number of values in the set.
     * @return Number of values in set
     */
    public int getNumberOfValues() {
        return (this.values != null) ? this.values.size() : 0;
    }

    /**
     * Returns the set of values.  Returns an empty collection if there are
     * currently no values.
     * @return The collection of Expression values
     */
    public Collection getValues() {
        return this.values;
    }

    /**
     * Sets the values in the set.
     * @param values The set of value Expressions
     */
    public void setValues( Collection values ) {
        this.values = values;
    }
    
    /**
     * Sets the membership expression and the set of value expressions
     * @param expression The membership expression
     * @param values   The set of value expressions
     */
    public void set( Expression expression, Collection values ) {
        setExpression(expression);
        setValues(values);
    }

    public void acceptVisitor(LanguageVisitor visitor) {
        visitor.visit(this);
    }

	/**
	 * Get hash code.  WARNING: The hash code is based on data in the criteria.
	 * If data values are changed, the hash code will change - don't hash this
	 * object and change values.
	 * @return Hash code
	 */
	public int hashCode() {
		int hc = 0;
		hc = HashCodeUtil.hashCode(hc, getExpression());

		// The expHashCode method walks the set of values, combining
		// the hash code at every power of 2: 1,2,4,8,...  This is
		// much quicker than calculating hash codes for ALL values
		
		hc = HashCodeUtil.expHashCode(hc, getValues());
		return hc;
	}
	
    /**
     * Override equals() method.
     * @param obj Other object
     * @return True if equal
     */
    public boolean equals(Object obj) {
    	// Use super.equals() to check obvious stuff and variable
    	if(obj == this) {
			return true;
		}
		
		if(! (obj instanceof SetCriteria)) {
    		return false;
		}

        SetCriteria sc = (SetCriteria)obj;

        if (isNegated() ^ sc.isNegated()) {
            return false;
        }
                
        return getValues().size() == sc.getValues().size() && 
        	getValues().containsAll(sc.getValues()) &&
            EquivalenceUtil.areEqual(getExpression(), sc.getExpression());
	}
	
	/**
	 * Deep copy of object
	 * @return Deep copy of object
	 */
	public Object clone() {
	    Expression copy = null;
	    if(getExpression() != null) { 
	        copy = (Expression) getExpression().clone();
	    }	
	    
	    Collection copyValues = null;
	    if (isAllConstants()) {
	    	if (values instanceof HashSet) {
	    		copyValues = new LinkedHashSet();
	    	} else {
	    		//assume that it's non-hashable
	    		copyValues = new TreeSet();
	    	}
	    	for (Expression ex : (Collection<Expression>)values) {
	    		copyValues.add(ex.clone());
	    	}
	    } else {
	    	copyValues = LanguageObject.Util.deepClone(values, Expression.class);
	    }
	    
        SetCriteria criteriaCopy = new SetCriteria(copy, copyValues);
        criteriaCopy.setNegated(isNegated());
        criteriaCopy.allConstants = allConstants;
        return criteriaCopy;
	}
	
	public boolean isAllConstants() {
		return allConstants;
	}
	
	public void setAllConstants(boolean allConstants) {
		this.allConstants = allConstants;
	}

}  // END CLASS
