/* 
 * Kodkod -- Copyright (c) 2005-present, 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.fol2sat;

import static kodkod.engine.fol2sat.FormulaFlattener.flatten;
import static kodkod.engine.fol2sat.Skolemizer.skolemize;
import static kodkod.util.nodes.AnnotatedNode.annotate;
import static kodkod.util.nodes.AnnotatedNode.annotateRoots;
import static kodkod.util.collections.Containers.setDifference;

import java.util.Collections;
import java.util.IdentityHashMap;
import java.util.LinkedHashSet;
import java.util.Map;
import java.util.Set;

import kodkod.ast.Expression;
import kodkod.ast.Formula;
import kodkod.ast.IntExpression;
import kodkod.ast.Node;
import kodkod.ast.Relation;
import kodkod.ast.RelationPredicate;
import kodkod.ast.visitor.AbstractReplacer;
import kodkod.engine.bool.BooleanAccumulator;
import kodkod.engine.bool.BooleanConstant;
import kodkod.engine.bool.BooleanFactory;
import kodkod.engine.bool.BooleanFormula;
import kodkod.engine.bool.BooleanMatrix;
import kodkod.engine.bool.BooleanValue;
import kodkod.engine.bool.Int;
import kodkod.engine.bool.Operator;
import kodkod.engine.config.Options;
import kodkod.engine.satlab.SATSolver;
import kodkod.instance.Bounds;
import kodkod.instance.Instance;
import kodkod.instance.TupleSet;
import kodkod.util.ints.IndexedEntry;
import kodkod.util.ints.IntSet;
import kodkod.util.nodes.AnnotatedNode;

/** 
 * Translates, evaluates, and approximates {@link Node nodes} with
 * respect to given {@link Bounds bounds} (or {@link Instance instances}) and {@link Options}.
 * 
 * @author Emina Torlak 
 */
public final class Translator {
	
	/*---------------------- public methods ----------------------*/
	/**
	 * Overapproximates the value of the given expression using the provided bounds and options.
	 * @return a BooleanMatrix whose TRUE entries represent the tuples contained in a sound overapproximation
	 * of the expression.
	 * @throws expression = null || instance = null || options = null
	 * @throws UnboundLeafException  the expression refers to an undeclared variable or a relation not mapped by the instance
	 * @throws HigherOrderDeclException  the expression contains a higher order declaration
	 */
	@SuppressWarnings("unchecked")
	public static BooleanMatrix approximate(Expression expression, Bounds bounds, Options options) {
		return FOL2BoolTranslator.approximate(annotate(expression), LeafInterpreter.overapproximating(bounds, options), Environment.EMPTY);
	}
	
	/**
	 * Evaluates the given formula to a BooleanConstant using the provided instance and options.  
	 * 
	 * @return a BooleanConstant that represents the value of the formula.
	 * @throws NullPointerException  formula = null || instance = null || options = null
	 * @throws UnboundLeafException  the formula refers to an undeclared variable or a relation not mapped by the instance
	 * @throws HigherOrderDeclException  the formula contains a higher order declaration
	 */
	public static BooleanConstant evaluate(Formula formula, Instance instance, Options options) {
		return (BooleanConstant) FOL2BoolTranslator.translate(annotate(formula), LeafInterpreter.exact(instance, options));
	}
	
	/**
	 * Evaluates the given expression to a BooleanMatrix using the provided instance and options.
	 * 
	 * @return a BooleanMatrix whose TRUE entries represent the tuples contained by the expression.
	 * @throws NullPointerException  expression = null || instance = null || options = null
	 * @throws UnboundLeafException  the expression refers to an undeclared variable or a relation not mapped by the instance
	 * @throws HigherOrderDeclException  the expression contains a higher order declaration
	 */
	public static BooleanMatrix evaluate(Expression expression,Instance instance, Options options) {
		return (BooleanMatrix) FOL2BoolTranslator.translate(annotate(expression), LeafInterpreter.exact(instance, options));
	}

	/**
	 * Evaluates the given intexpression to an {@link kodkod.engine.bool.Int} using the provided instance and options.
	 * @return an {@link kodkod.engine.bool.Int} representing the value of the intExpr with respect
	 * to the specified instance and options.
	 * @throws NullPointerException  formula = null || instance = null || options = null
	 * @throws UnboundLeafException  the expression refers to an undeclared variable or a relation not mapped by the instance
	 * @throws HigherOrderDeclException  the expression contains a higher order declaration
	 */
	public static Int evaluate(IntExpression intExpr, Instance instance, Options options) {
		return (Int) FOL2BoolTranslator.translate(annotate(intExpr), LeafInterpreter.exact(instance,options));
	}
	
	/**
	 * Translates the given formula using the specified bounds and options.
	 * The CNF representation of the given formula and bounds  is generated so that the magnitude 
	 * of the literal representing the truth value of a given circuit is strictly larger than the magnitudes of 
	 * the literals representing the truth values of the circuit's descendants.   
	 * @return some t: Translation.Whole |  t.originalFormula = formula && t.originalBounds = bounds && t.options = options
	 * @throws NullPointerException  any of the arguments are null
	 * @throws UnboundLeafException  the formula refers to an undeclared variable or a relation not mapped by the given bounds.
	 * @throws HigherOrderDeclException  the formula contains a higher order declaration that cannot
	 * be skolemized, or it can be skolemized but options.skolemize is false.
	 */
	public static Translation.Whole translate(Formula formula, Bounds bounds, Options options)  {
		return (Translation.Whole) (new Translator(formula,bounds,options)).translate();
	}
	
	/**
	 * Translates the given formula using the specified bounds and options in such a way 
	 * that the resulting translation can be extended with additional formulas and bounds, subject to 
	 * the same options.  We require that the options specify  an incremental SAT solver, and no translation logging.
	 * The CNF representation of the given formula and bounds  is generated so that the magnitude 
	 * of the literal representing the truth value of a given circuit is strictly larger than the magnitudes of 
	 * the literals representing the truth values of the circuit's descendants.  
	 * @requires options.solver.incremental() && options.logTranslation = 0  
	 * @return some t: Translation.Incremental |  t.originalFormula = formula && t.originalBounds = bounds && t.options = options
	 * @throws NullPointerException  any of the arguments are null
	 * @throws UnboundLeafException  the formula refers to an undeclared variable or a relation not mapped by the given bounds
	 * @throws HigherOrderDeclException  the formula contains a higher order declaration
	 * @throws IllegalArgumentException any of the preconditions on options are violated
	 */
	public static Translation.Incremental translateIncremental(Formula formula, Bounds bounds, Options options)  {
		checkIncrementalOptions(options);	
		return (Translation.Incremental) (new Translator(formula, bounds, options, true)).translate();
	}
	
	/**
	 * Updates the given translation with {@code CNF(formula, translation.originalBounds + bounds, translation.options)}.  The 
	 * result of the update is either a new translation instance or the given {@code translation}, modified in place.  We assume
	 * that client did not modify any translation state between invocations to {@code translateIncremental(...)}.
	 * 
	 * <p>
	 * We require {@code bounds} and {@code translation} to be consistent in the following sense:
	 * <ol>
	 * <li>{@code bounds} and {@code translation.bounds} share the same universe;</li>
	 * <li>{@code bounds} must not specify any integer bounds;</li> 
	 * <li>{@code bounds.relations} must not contain any members of {@code translation.bounds.relations} 
	 * (which may be a superset of {@code translation.originalBounds.relations} that also includes Skolem constants); and,</li>
	 * <li>{@code bounds} must induce a coarser set of equivalence classes on the shared universe than {@code translation.originalBounds}.</li>
	 * </ol>
	 * </p>
	 * 
	 * <p>
	 * The behavior of this method is unspecified if a prior call to {@code translation.cnf.solve()} returned false, or 
	 * if a prior call to this method resulted in an exception.
	 * </p>
	 * 
	 * @requires translation.cnf.solve()
	 * @requires formula.*components & Relation in (translation.bounds + bounds).relations
	 * @requires translation.bounds.universe = bounds.universe && no bounds.intBound && no (translation.bounds.relations & bounds.relations)
	 * @requires all s: translation.symmetries | 
	 *            some p: {@link SymmetryDetector#partition(Bounds) partition}(bounds) |
	 *             s.ints in p.ints       
	 * @return some t: Translation | 
	 *          t.originalFormula = translation.originalFormula.and(formula) && 
	 * 	        t.originalBounds.relations = translation.originalBounds.relations + bounds.relations &&
	 *          t.originalBounds.upperBound = translation.originalBounds.upperBound + bounds.upperBound &&
	 *          t.originalBounds.lowerBound = translation.originalBounds.lowerBound + bounds.lowerBound &&
	 *          t.originalBounds.intBound = translation.originalBounds.intBound 
	 * @throws NullPointerException  any of the arguments are null
	 * @throws UnboundLeafException  the formula refers to an undeclared variable or a relation not mapped by translation.bounds + bounds
	 * @throws HigherOrderDeclException  the formula contains a higher order declaration
	 * @throws IllegalArgumentException any of the other preconditions on the arguments are violated
	 */
	public static Translation.Incremental translateIncremental(Formula formula, Bounds bounds, Translation.Incremental translation)  {
		checkIncrementalOptions(translation.options());
		checkIncrementalBounds(bounds, translation);		
		if (translation.trivial())  { 
			return translateIncrementalTrivial(formula, bounds, translation);
		} else {
			return translateIncrementalNonTrivial(formula, bounds, translation);
		}	
	}

	/** 
	 * @requires checkIncrementalBounds(bounds, transl)
	 * @requires checkIncrementalOptions(transl.options) 
	 * @requires transl.trivial()
	 * @requires transl.cnf.solve()
	 * @return see {@link #translateIncremental(Formula, Bounds, Options)}
	 **/
	private static Translation.Incremental translateIncrementalTrivial(Formula formula, Bounds bounds, Translation.Incremental transl) {
		if (!transl.cnf().solve()) 
			throw new IllegalArgumentException("Expected a satisfiable translation, given " + transl);
		
		transl.cnf().free(); // release the old empty solver since we are going to re-translate
		
		final Options tOptions = transl.options();
		final Bounds tBounds = transl.bounds();
		
		// add new relation bindings to the translation bounds.  since the given bounds induce
		// a coarser set of symmetries on the universe than transl.symmetries, adding their (disjoint) bindings to tBounds 
		// will not change the symmetries of tBounds.  note that the symmetries of tBounds refine transl.symmetries, and they
		// may be strictly finer if some of the symmetries in transl.symmetries were broken via SymmetryBreaker.breakMatrixSymmetries(...) 
		// during the generation of transl.  in particular, any symmetries absent from tBounds are precisely those that were broken based
		// on the total ordering and acyclic predicates in transl.originalFormula.
		for(Relation r : bounds.relations()) {
			tBounds.bound(r, bounds.lowerBound(r), bounds.upperBound(r));
		}
		
		// re-translate the given formula with respect to tBounds.  note that we don't have to re-translate 
		// the conjunction of transl.formula and formula since transl.formula is guaranteed to evaluate to 
		// TRUE with respect to tBounds (since no bindings that were originally in tBounds were changed by the above loop).
		final Translation.Incremental updated = translateIncremental(formula, tBounds, tOptions);
		
		// we can't return the updated translation as is, since we have to make sure that updated.symmetries is set to
		// transl.symmetries rather than the potentially finer set of symmetries induced by tBounds. note that 
		// the updated translation currently has updated.originalBounds = tBounds, while updated.bounds is a copy of 
		// tBounds with possibly additional skolem relations, as well as new bounds for some relations in formula.*components 
		// due to symmetry breaking.
		return new Translation.Incremental(updated.bounds(), tOptions, transl.symmetries(), updated.interpreter(), updated.incrementer());
	}
	
	/** 
	 * @requires checkIncrementalBounds(bounds, transl)
	 * @requires checkIncrementalOptions(transl.options) 
	 * @requires !transl.trivial()
	 * @return see {@link #translateIncremental(Formula, Bounds, Options)}
	 **/
	private static Translation.Incremental translateIncrementalNonTrivial(Formula formula, Bounds bounds, Translation.Incremental transl) {
		
		final Options tOptions = transl.options();
		final Bounds tBounds = transl.bounds();
		
		// save the set of relations bound in the pre-state
		final Set<Relation> oldRelations = new LinkedHashSet<Relation>(tBounds.relations());
		
		// add new relation bindings to the translation bounds.  note that skolemization (below) may also cause extra relations to be added.
		for(Relation r : bounds.relations()) {
			tBounds.bound(r, bounds.lowerBound(r), bounds.upperBound(r));
		}
		final AnnotatedNode<Formula> annotated = 
			(transl.options().skolemDepth() < 0) ? annotate(formula) : skolemize(annotate(formula), tBounds, tOptions);
		
		// extend the interpreter with variable allocations for new relations, either from given bounds
		// or those introduced by skolemization
		final LeafInterpreter interpreter = transl.interpreter();
		interpreter.extend(setDifference(tBounds.relations(), oldRelations), tBounds.lowerBounds(), tBounds.upperBounds());
		
		final BooleanValue circuit = FOL2BoolTranslator.translate(annotated, interpreter); 
	
		if (circuit==BooleanConstant.FALSE) {
			// release the old solver and state, and return a fresh trivially false incremental translation.
			transl.incrementer().solver().free();
			return new Translation.Incremental(tBounds, tOptions, transl.symmetries(), 
					LeafInterpreter.empty(tBounds.universe(), tOptions), 
					Bool2CNFTranslator.translateIncremental(BooleanConstant.FALSE, tOptions.solver()));			
		} else if (circuit==BooleanConstant.TRUE) {
			// must add any newly allocated primary variables to the solver for interpretation to work correctly 
			final int maxVar = interpreter.factory().maxVariable();
			final int cnfVar = transl.cnf().numberOfVariables();
			if (maxVar > cnfVar) {
				transl.cnf().addVariables(maxVar-cnfVar);
			}
		} else {
			// circuit is a formula; add its CNF representation to transl.incrementer.solver()			
			Bool2CNFTranslator.translateIncremental((BooleanFormula) circuit, interpreter.factory().maxVariable(), transl.incrementer());			
		}  
		
		return transl;
	}
	
	/**
	 * Checks that the given options are suitable for incremental translation.
	 * @requires options.solver.incremental() && options.logTranslation = 0  
	 * @throws IllegalArgumentException any of the preconditions are violated
	 */
	public static void checkIncrementalOptions(Options options) {
		if (!options.solver().incremental())
			throw new IllegalArgumentException("An incremental solver is required for incremental translation: " + options);
		if (options.logTranslation() != 0)
			throw new IllegalArgumentException("Translation logging must be disabled for incremental translation: " + options);
	}
	
	/**
	 * Checks that the given {@code inc} bounds are incremental with respect to the given {@code translation}.
	 * @requires translation.bounds.universe = inc.universe && no inc.intBound && no (translation.bounds.relations & inc.relations)
	 * @requires all s: translation.symmetries |  
	 * 				some p: {@link SymmetryDetector#partition(Bounds) partition}(inc) | 
	 * 				   s.elements in p.elements
	 * @throws IllegalArgumentException any of the preconditions are violated
	 */
	public static void checkIncrementalBounds(Bounds inc, Translation.Incremental translation) {
		final Bounds base = translation.bounds();
		if (!base.universe().equals(inc.universe()))
			incBoundErr(inc.universe(), "universe", "equal to", base.universe());
		if (!inc.intBounds().isEmpty()) 
			incBoundErr(inc.intBounds(), "intBound", "empty, with integer bounds fully specified by", base.intBounds());
		if (inc.relations().isEmpty()) return;
		final Set<Relation> baseRels = base.relations();
		for(Relation r : inc.relations()) { 
			if (baseRels.contains(r)) {
				incBoundErr(inc.relations(), "relations", "disjoint from", baseRels);
			}  
 		}
		final Set<IntSet> symmetries = translation.symmetries();
		final Set<IntSet> incSymmetries = SymmetryDetector.partition(inc);
		EQUIV_CHECK : for(IntSet part : symmetries) {
			for(IntSet incPart : incSymmetries) {
				if (incPart.containsAll(part))
					continue EQUIV_CHECK;
			}
			incBoundErr(incSymmetries, "partition", "coarser than", symmetries);
		}
	}
	
	/**
	 * Throws an {@link IllegalArgumentException} with an error message that describes why given bounds 
	 * cannot be used for incremental translation.  
	 */
	private static void incBoundErr(Object newObj, String desc, String relatedTo, Object translObj) {
		final String newDesc = "bounds." + desc, oldDesc = "translation.originalBounds." + desc;
		throw new IllegalArgumentException("Expected " + newDesc + " to be " + relatedTo + " " + oldDesc + 
				" for incremental translation; given "+ newDesc + " = " + newObj + ", " + oldDesc + " = " + translObj);
	}
	
	
	/*---------------------- private translation state and methods ----------------------*/
	/**
	 * @specfield originalFormula: Formula
	 * @specfield originalBounds: Bounds
	 * @specfield bounds: Bounds
	 * @specfield options: Options
	 * @specfield incremental: boolean
	 */
	private final Formula originalFormula;
	private final Bounds originalBounds;
	private final Bounds bounds;
	private final Options options;
	private final boolean logging;
	private final boolean incremental;
	
	/**
	 * Constructs a Translator for the given formula, bounds, options and incremental flag.
	 * If the flag is true, then the translator produces an initial {@linkplain Translation.Incremental incremental translation}.
	 * Otherwise, the translator produces a {@linkplain Translation.Whole basic translation}.
	 * @ensures this.originalFormula' = formula and 
	 * 	this.options' = options and 
	 *  this.originalBounds' = bounds and 
	 * 	this.bounds' = bounds.clone() and
	 *  this.incremental' = incremental 
	 */
	private Translator(Formula formula, Bounds bounds, Options options, boolean incremental) {
		this.originalFormula = formula;
		this.originalBounds = bounds;
		this.bounds = bounds.clone();
		this.options = options;
		this.logging = options.logTranslation()>0;
		this.incremental = incremental;
	}
	
	/**
	 * Constructs a non-incremental Translator for the given formula, bounds and options.
	 * @ensures this(formula, bounds, options, false)
	 */
	private Translator(Formula formula, Bounds bounds, Options options) {
		this(formula, bounds, options, false);
	}
	
	/**
	 * Translates this.originalFormula with respect to this.bounds and this.options. If this.incremental is true, 
	 * then the returned translation is {@linkplain Translation.Incremental incremental}; otherwise the output is
	 * a {@linkplain Translation.Whole basic} translation.
	 * @return a {@linkplain Translation} whose solver is a SATSolver instance initialized with the 
	 * CNF representation of the given formula, with respect to the given bounds.  The CNF
	 * is generated in such a way that the magnitude of a literal representing the truth
	 * value of a given formula is strictly larger than the magnitudes of the literals representing
	 * the truth values of the formula's descendants.  
	 * @throws UnboundLeafException  this.originalFormula refers to an undeclared variable or a relation not mapped by this.bounds.
	 * @throws HigherOrderDeclException  this.originalFormula contains a higher order declaration that cannot
	 * be skolemized, or it can be skolemized but this.options.skolemDepth < 0
	 */
	private Translation translate()   {
		final AnnotatedNode<Formula> annotated = logging ? annotateRoots(originalFormula) : annotate(originalFormula);
		// Remove bindings for unused relations/ints if this is not an incremental translation.  If it is
		// an incremental translation, we have to keep all bindings since they may be used later on.
		if (!incremental) {
			bounds.relations().retainAll(annotated.relations());
			if (!annotated.usesInts()) bounds.ints().clear();
		}
		// Detect symmetries.
		final SymmetryBreaker breaker = new SymmetryBreaker(bounds, options.reporter());
		// Optimize formula and bounds by using symmetry information to tighten bounds and 
		// eliminate top-level predicates, and also by skolemizing.  Then translate the optimize
		// formula and bounds to a circuit, augment the circuit with a symmetry breaking predicate 
		// that eliminates any remaining symmetries, and translate everything to CNF.
		return toBoolean(optimizeFormulaAndBounds(annotated, breaker), breaker);
	}
	
	/**
	 * <p>When logging is disabled, optimizes annotated.node by first breaking matrix symmetries on its top-level predicates,
	 * replacing them with the simpler formulas generated by 
	 * {@linkplain SymmetryBreaker#breakMatrixSymmetries(Map, boolean) breaker.breakMatrixSymmetries(...)}, 
	 * and skolemizing the result, if applicable.</p>
	 * 
	 * <p> When logging is enabled, optimizes annotated.node by first flattening it into a set of conjuncts, 
	 * assuming that core granularity is 1.  This involves pushing negations through quantifier-free formulas.
	 * We then skolemize, followed by an additional layer of flattening (if this.options.coreGranularity > 1), 
	 * possibly through quantifiers (if this.options.coreGranuarity is 3).  Predicate inlining and breaking of
	 * matrix symmetries is performed last to  prevent any quantified formulas generated by predicate inlining 
	 * from also being flattened (as this wouldn't be meaningful at the level of the original formula).</p> 
	 * 
	 * @requires SAT(annotated.node, this.bounds, this.options) iff SAT(this.originalFormula, this.originalBounds, this.options)
	 * @requires annotated.node.*components & Relation = this.originalFormula.*components & Relation
	 * @requires breaker.bounds = this.bounds
	 * @ensures this.bounds.relations in this.bounds.relations' 
	 * @ensures this.options.reporter().optimizingBoundsAndFormula()
	 * @return some f: AnnotatedNode<Formula> | meaning(f.node, this.bounds, this.options) = meaning(this.originalFormula, this.originalBounds, this.options)
	 */
	private AnnotatedNode<Formula> optimizeFormulaAndBounds(AnnotatedNode<Formula> annotated, SymmetryBreaker breaker) {	
		options.reporter().optimizingBoundsAndFormula();

		if (logging) {  
			final int coreGranularity = options.coreGranularity();
			if (coreGranularity==1) { 
				annotated = flatten(annotated, false);
			}
			if (options.skolemDepth()>=0) {
				annotated = skolemize(annotated, bounds, options);
			}
			if (coreGranularity>1) { 
				annotated = flatten(annotated, options.coreGranularity()==3);
			}
			return inlinePredicates(annotated, breaker.breakMatrixSymmetries(annotated.predicates(), false));		
		} else {  			
			annotated = inlinePredicates(annotated, breaker.breakMatrixSymmetries(annotated.predicates(), true).keySet());
			return options.skolemDepth()>=0 ? Skolemizer.skolemize(annotated, bounds, options) : annotated;
		}
	}

	/**
	 * Returns an annotated formula f such that f.node is equivalent to annotated.node
	 * with its <tt>truePreds</tt> replaced with the constant formula TRUE and the remaining
	 * predicates replaced with equivalent constraints.
	 * @requires truePreds in annotated.predicates()[RelationnPredicate.NAME]
	 * @requires truePreds are trivially true with respect to this.bounds
	 * @return an annotated formula f such that f.node is equivalent to annotated.node
	 * with its <tt>truePreds</tt> replaced with the constant formula TRUE and the remaining
	 * predicates replaced with equivalent constraints.
	 */
	private AnnotatedNode<Formula> inlinePredicates(final AnnotatedNode<Formula> annotated, final Set<RelationPredicate> truePreds) {
		final AbstractReplacer inliner = new AbstractReplacer(annotated.sharedNodes()) {
			public Formula visit(RelationPredicate pred) {
				Formula ret = lookup(pred);
				if (ret!=null) return ret;
				return truePreds.contains(pred) ? cache(pred, Formula.TRUE) : cache(pred, pred.toConstraints());
			}
		};
		return annotate(annotated.node().accept(inliner));	
	}
	
	/**
	 * Returns an annotated formula f such that f.node is equivalent to annotated.node
	 * with its <tt>simplified</tt> predicates replaced with their corresponding Formulas and the remaining
	 * predicates replaced with equivalent constraints.  The annotated formula f will contain transitive source 
	 * information for each of the subformulas of f.node.  Specifically, let t be a subformula of f.node, and
	 * s be a descdendent of annotated.node from which t was derived.  Then, f.source[t] = annotated.source[s]. </p>
	 * @requires simplified.keySet() in annotated.predicates()[RelationPredicate.NAME]
	 * @requires no disj p, p': simplified.keySet() | simplified.get(p) = simplified.get(p') // this must hold in order
	 * to maintain the invariant that each subformula of the returned formula has exactly one source
	 * @requires for each p in simplified.keySet(), the formulas "p and [[this.bounds]]" and
	 * "simplified.get(p) and [[this.bounds]]" are equisatisfiable
	 * @return an annotated formula f such that f.node is equivalent to annotated.node
	 * with its <tt>simplified</tt> predicates replaced with their corresponding Formulas and the remaining
	 * predicates replaced with equivalent constraints.
	 */
	private AnnotatedNode<Formula> inlinePredicates(final AnnotatedNode<Formula> annotated, final Map<RelationPredicate,Formula> simplified) {
		final Map<Node,Node> sources = new IdentityHashMap<Node,Node>();
		final AbstractReplacer inliner = new AbstractReplacer(annotated.sharedNodes()) {
			private RelationPredicate source =  null;			
			protected <N extends Node> N cache(N node, N replacement) {
				if (replacement instanceof Formula) {
					if (source==null) {
						final Node nsource = annotated.sourceOf(node);
						if (replacement!=nsource) 
							sources.put(replacement, nsource);
					} else {
						sources.put(replacement, source);
					}
				}
				return super.cache(node, replacement);
			}
			public Formula visit(RelationPredicate pred) {
				Formula ret = lookup(pred);
				if (ret!=null) return ret;
				source = pred;
				if (simplified.containsKey(pred)) {
					ret = simplified.get(pred).accept(this);
				} else {
					ret = pred.toConstraints().accept(this);
				}
				source = null;
				return cache(pred, ret);
			}
		};

		return annotate(annotated.node().accept(inliner), sources);
	}
	
	/**
	 * Translates the given annotated formula to a circuit, conjoins the circuit with an 
	 * SBP generated by the given symmetry breaker, and returns its {@linkplain Translation} to CNF.
	 * The SBP breaks any symmetries that could not be broken during the 
	 * {@linkplain #optimizeFormulaAndBounds(AnnotatedNode, SymmetryBreaker) formula and bounds optimization} step.
	 * @requires SAT(annotated.node, this.bounds, this.options) iff SAT(this.originalFormula, this.originalBounds, this.options)
	 * @requires breaker.bounds = this.bounds
	 * @ensures this.options.logTranslation => some this.log'
	 * @ensures this.options.reporter().translatingToBoolean(annotated.node(), this.bounds)
	 * @ensures this.options.reporter().generatingSBP()
	 * @return the translation of annotated.node with respect to this.bounds
	 */
	private Translation toBoolean(AnnotatedNode<Formula> annotated, SymmetryBreaker breaker) {
		
		options.reporter().translatingToBoolean(annotated.node(), bounds);
		
		final LeafInterpreter interpreter = LeafInterpreter.exact(bounds, options, incremental);
		final BooleanFactory factory = interpreter.factory();
		
		if (logging) {
			assert !incremental;
			final TranslationLogger logger = options.logTranslation()==1 ? new MemoryLogger(annotated, bounds) : new FileLogger(annotated, bounds);
			final BooleanAccumulator circuit = FOL2BoolTranslator.translate(annotated, interpreter, logger);
			final TranslationLog log = logger.log();
			if (circuit.isShortCircuited()) {
				return trivial(circuit.op().shortCircuit(), log);
			} else if (circuit.size()==0) { 
				return trivial(circuit.op().identity(), log);
			}
			circuit.add(breaker.generateSBP(interpreter, options));
			return toCNF((BooleanFormula)factory.accumulate(circuit), interpreter, log);
		} else {
			final BooleanValue circuit = (BooleanValue)FOL2BoolTranslator.translate(annotated, interpreter);
			if (circuit.op()==Operator.CONST) {
				return trivial((BooleanConstant)circuit, null);
			} 
			return toCNF((BooleanFormula)factory.and(circuit, breaker.generateSBP(interpreter, options)), interpreter, null);
		}
	}
	
	/**
	 * Translates the given circuit to CNF, adds the clauses to a SATSolver returned
	 * by options.solver(), and returns a Translation object constructed from the solver
	 * and the provided arguments.
	 * @requires SAT(circuit) iff SAT(this.originalFormula, this.originalBounds, this.options)
	 * @requires circuit.factory = interpreter.factory
	 * @requires interpreter.universe = this.bounds.universe && interpreter.relations = this.bounds.relations() && 
	 *           interpreter.ints = this.bounds.ints() && interpreter.lbounds = this.bounds.lowerBound && 
	 *           this.interpreter.ubounds = bounds.upperBound && interpreter.ibounds = bounds.intBound 
	 * @requires log.originalFormula = this.originalFormula && log.bounds = this.bounds
	 * @ensures {@link #completeBounds()}
	 * @ensures this.options.reporter.translatingToCNF(circuit)
	 * @return some t: Translation | 
	 *           t.bounds = completeBounds() && t.originalBounds = this.originalBounds &&
	 *           t.vars = interpreter.vars &&
	 *           t.vars[Relation].int in t.solver.variables && 
	 *           t.solver.solve() iff SAT(this.formula, this.bounds, this.options)
	 */
	private Translation toCNF(BooleanFormula circuit, LeafInterpreter interpreter, TranslationLog log) {	
		options.reporter().translatingToCNF(circuit);
		final int maxPrimaryVar = interpreter.factory().maxVariable();
		if (incremental) {
			final Bool2CNFTranslator incrementer = Bool2CNFTranslator.translateIncremental(circuit, maxPrimaryVar, options.solver());
			return new Translation.Incremental(completeBounds(), options, SymmetryDetector.partition(originalBounds), interpreter, incrementer);
		} else {
			final Map<Relation, IntSet> varUsage = interpreter.vars();
			interpreter = null; // enable gc
			final SATSolver cnf = Bool2CNFTranslator.translate((BooleanFormula)circuit, maxPrimaryVar, options.solver());
			return new Translation.Whole(completeBounds(), options, cnf, varUsage, maxPrimaryVar, log);
		}
	}
	
	/**
	 * Returns a whole or incremental translation, depending on the value of {@code this.incremental}, 
	 * using the given trivial outcome, {@linkplain #completeBounds() completeBounds()}, {@code this.options}, 
	 * and the given log.
	 * @ensures {@link #completeBounds()}
	 * @return some t: Translation | 
	 *           t.bounds = completeBounds() && t.originalBounds = this.originalBounds &&
	 *           no t.solver.variables && 
	 *           no t.vars &&
	 *           (outcome.booleanValue() => no t.solver.clauses else (one t.solver.clauses && no t.solver.clauses.literals))      
	 **/
	@SuppressWarnings("unchecked")
	private Translation trivial(BooleanConstant outcome, TranslationLog log) {
		if (incremental) {
			return new Translation.Incremental(completeBounds(), options, 
					SymmetryDetector.partition(originalBounds), 
					LeafInterpreter.empty(bounds.universe(), options), // empty interpreter
					Bool2CNFTranslator.translateIncremental(outcome, options.solver()));
		} else {
			return new Translation.Whole(completeBounds(), options, 
					Bool2CNFTranslator.translate(outcome, options.solver()), 
					(Map<Relation,IntSet>)Collections.EMPTY_MAP, 0, log);
		}
	}
	
	/**
	 * Completes {@code this.bounds} using the bindings from {@code this.originalBounds} so that 
	 * the result satisfies the {@linkplain Translation} invariants. This involves updating 
	 * {@code this.bounds} with bindings from {@code this.originalBounds}, if any, that had been discarded 
	 * in the {@link #translate() first step} of the translation.  The first step of a non-incremental 
	 * translation is to discard bounds for relations that are not constrained by {@code this.originalFormula}, 
	 * and to discard all integer bounds if {@code this.originalFormula} contains no integer expressions.  
	 * This is sound since any instance of {@code this.originalFormula} with respect to {@code this.originalBounds} only needs to 
	 * satisfy the lower bound constraint on each discarded relation/integer.   By updating {@code this.bounds} with 
	 * the bindings for discarded relations/integers for which no variables were allocated, we ensure that any instance returned by 
	 * {@linkplain Translation#interpret()} will bind those relations/integers to their lower bound, therefore satisfying 
	 * the original problem {@code (this.originalFormula, this.originalBounds, this.options)}.
	 * 
	 * @requires no this.bounds.intBound or this.bounds.intBound = this.originalBounds.intBound
	 * @ensures  this.bounds.relations' = this.bounds.relations + this.originalBounds.relations &&
	 *           this.bounds.intBound' = this.originalBounds.intBound &&
	 *           this.bounds.lowerBound' = this.bounds.lowerBound + (this.originalBounds.relations - this.bounds.relations)<:(this.originalBounds.lowerBound) &&
	 *           this.bounds.upperBound' = bounds.upperBound + (this.originalBounds.relations - this.bounds.relations)<:(this.originalBounds.upperBound)
	 * @return this.bounds
	 */
	private Bounds completeBounds() {
		final Bounds optimized = this.bounds; 
		final Bounds original = this.originalBounds;
		if (optimized.ints().isEmpty()) {
			for(IndexedEntry<TupleSet> entry : original.intBounds()) { 
				optimized.boundExactly(entry.index(), entry.value());
			}
		} else {
			assert optimized.intBounds().equals(original.intBounds());
		}
		final Set<Relation> rels = optimized.relations();
		for(Relation r : original.relations()) {
			if (!rels.contains(r)) {
				optimized.bound(r, original.lowerBound(r), original.upperBound(r));
			}
		}
		return optimized;
	}
}