/* 
 * Kodkod -- Copyright (c) 2005-2011, Emina Torlak
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */
package kodkod.engine.bool;

import java.util.Iterator;
import java.util.Set;

import kodkod.util.collections.Containers;

/**
 * Represents a multi gate with more than two inputs.
 * @invariant #this.inputs > 2
 * @invariant digest = sum(inputs.digest(this.op))
 */
final class NaryGate extends MultiGate {
	private final BooleanFormula[] inputs;
	
	/**
	 * Constructs a new n-ary gate with the given label, from the given mutable multi gate.
	 * @requires g != null && #g.inputs > 2
	 * @ensures this.op' = g.op && this.inputs' = g.inputs && this.label' = label
	 */
	NaryGate(BooleanAccumulator g, int label, int hashcode) {
		super(g.op, label, hashcode);
		this.inputs = new BooleanFormula[g.size()];
		int index = 0;
		for(Iterator<BooleanValue> i = g.iterator(); i.hasNext(); ) {
			inputs[index] = (BooleanFormula) i.next();
			index++;
		}
	}
	
	/**
	 * Returns the number of inputs to this gate.
	 * @return #this.inputs
	 */
	@Override
	public int size() {
		return inputs.length;
	}
	
	/**
	 * Returns an iterator over the inputs to this gate, in the ascending
	 * label order.
	 * @return an iterator over this.inputs
	 */
	@Override
	public Iterator<BooleanFormula> iterator() {
		return Containers.iterate(inputs);
	}
	
	 /** 
	 * Returns an integer k' such that 0 < |k'| < k and |k'| is the number of flattening
	 * steps that need to be taken to determine that this circuit has (or does not have)
	 * an input with the given label.
	 * A positive k' indicates that f is found to be an input to this circuit in k' steps.
	 * A negative k' indicates that f is not an input to this circuit, when it is flattened
	 * using at most k steps.  
	 * @requires k > 0
	 * @return the number of flattening
	 * steps that need to be taken to determine that f is (not) an input to this circuit
	 */
	@Override
	int contains(Operator op, int f, int k) {
		assert k > 0;
		if (f==label()) return 1;
		else if (this.op != op || f>label() || -f>label()) return -1;
		else {
			int low = 0, high = inputs.length-1, step = 1;
			while (low <= high && step <= k) {
				int mid = (low + high) >>> 1;
				int midVal = inputs[mid].label();
				
				if (midVal < f)
					low = mid + 1;
				else if (midVal > f)
					high = mid - 1;
				else
					return step; // key found in the given number of steps
				step++;
			}
			return 1-step;  // key not found.
		}
	}
	
	/**
	 * Flattens this circuit with respect to the given operator into 
	 * the provided set.  
	 * Specifically, the method modifies the set so that it contains
	 * the elements f_0, ..., f_k' where k' <= k elements and 
	 * [[this]] = op(f_0, ..., f_k').
	 * The default implementation simply adds this to the set.
	 * @requires k > 0
	 * @ensures 1 <= k' <= k && some f_0,..., f_k' : flat.elts' | 
	 * [[this]] = op([[f_0]], ..., [[f_k']])
	 */
	@Override
	void flatten(Operator op, Set<BooleanFormula> flat, int k) {
		assert k > 0;
		if (this.op == op && k >= inputs.length) {
			int diff = k - inputs.length;
			for(BooleanFormula f: inputs) {
				int oldsize = flat.size();
				f.flatten(op, flat, StrictMath.max(1, diff));
				diff -= (flat.size() - oldsize);
			}
		} else {
			flat.add(this);
		}
	}
	
	/**
	 * Returns true if the given iterator and this.iterator 
	 * return the same elements, in the same order.
	 * @return true if values and this.iterator return the same elements,
	 * in the same order.
	 */
	boolean sameInputs(Iterator<? extends BooleanValue> values) {
		for(BooleanFormula f : inputs) {
			if (!(values.hasNext() && f == values.next()))
				return false;
		}
		return !values.hasNext();
	}
	
	/**
	 * Returns the ith input to this gate.
	 * @return this.inputs[i]
	 * @requires 0 <= i < size
	 * @throws IndexOutOfBoundsException  i < 0 || i >= #this.inputs
	 */
	@Override
	public BooleanFormula input(int i) {
		if (i < 0 || i > inputs.length)
			throw new IndexOutOfBoundsException();
		return inputs[i];
	}
}