/* 
 * 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.NoSuchElementException;
import java.util.Set;

/**
 * A logic gate with two inputs.
 * @invariant #this.inputs = 2
 * @invariant digest = sum(inputs.digest(this.op))
 */
final class BinaryGate extends MultiGate {
	private final BooleanFormula low, high;
	
	/**
	 * Constructs a new binary gate with the given operator, label, and inputs.
	 * @requires components.h = components.l && l.label < h.label
	 * @ensures this.op' = op && this.inputs' = l + h && this.label' = label
	 */
	BinaryGate(Operator.Nary op, int label, int hashcode, BooleanFormula l, BooleanFormula h) {
		super(op, label, hashcode);
		assert l.label() < h.label();
		this.low = l;
		this.high = h;
	}
	
	/**
	 * 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 || k < 2 || f>label() || -f>label()) return -1;
		else {
			final int l = low.contains(op, f, k-1);
			if (l > 0) return l;
			else {
				final int h = high.contains(op, f, k - l);
				return h > 0 ? h - l : h + l;
			}
		}
	}
	
	/**
	 * 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 > 1) {
			final int oldsize = flat.size();
			low.flatten(op, flat, k-1);
			high.flatten(op, flat, k - (flat.size()-oldsize));
		} else {
			flat.add(this);
		}
	}
	
	/**
	 * Returns 2.
	 * @return 2
	 */
	@Override
	public int size() { return 2; }
	
	/**
	 * Returns an iterator over the inputs to this gate, in
	 * the increasing label order.
	 * @return an iterator over this.inputs.
	 */
	@Override
	public Iterator<BooleanFormula> iterator() {
		return new Iterator<BooleanFormula>() {
			int next = 0;		
			public boolean hasNext() { 	return next < 2; }
			public BooleanFormula next() {
				if (!hasNext()) throw new NoSuchElementException();
				return (next++ == 0 ? low : high);
			}
			public void remove() {
				throw new UnsupportedOperationException();
			}
		};
	}
	
	/**
	 * 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) {
		switch(i) {
		case 0 : return low;
		case 1 : return high;
		default:
			throw new IndexOutOfBoundsException();
		}
	}
}