/* 
 * 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;

import java.util.Collections;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Set;

import kodkod.ast.Formula;
import kodkod.ast.Node;
import kodkod.ast.Variable;
import kodkod.ast.visitor.AbstractVoidVisitor;
import kodkod.engine.fol2sat.RecordFilter;
import kodkod.engine.fol2sat.TranslationLog;
import kodkod.engine.fol2sat.TranslationRecord;
import kodkod.engine.satlab.ReductionStrategy;
import kodkod.engine.satlab.ResolutionTrace;
import kodkod.engine.satlab.SATProver;
import kodkod.engine.ucore.StrategyUtils;
import kodkod.instance.TupleSet;
import kodkod.util.collections.IdentityHashSet;
import kodkod.util.ints.IntSet;
import kodkod.util.ints.IntTreeSet;

/**
 * A proof of unsatisfiability based on a {@linkplain ResolutionTrace resolution trace} produced
 * by a {@linkplain SATProver SATProver}.
 * 
 * @author Emina Torlak
 */
final class ResolutionBasedProof extends Proof {
	private SATProver solver;
	private RecordFilter coreFilter;
	private Map<Formula,Node> coreRoots;
	
	/**
	 * Constructs a new ResolutionRefutation that will extract the 
	 * unsatisfiable core for log.formula from the given solver.  
	 * @requires solver.solve() has been called and it returned false.
	 * @requires log.formula is the formula whose translation
	 * resulted in the given SATProver
	 * @ensures this.formula' = log.formula
	 */
	ResolutionBasedProof(SATProver solver, TranslationLog log) {
		super(log);
		this.solver = solver;
		this.coreFilter = null;
		this.coreRoots = null;
	}
	
	/**
	 * Returns the connected core based on the given set of 
	 * core variables.  
	 * @requires coreVar = StrategyUtils.coreVars(solver.proof());
	 * @return let formulas = (this.log.records[int] & literal.{i: int | abs(i) in coreVars}).formula |
	 *  	   connected = {f: formulas  | some s: set coreNodes | f + this.log.formula in s and (s - this.log.formula).~components in s } 
	 */
	private Set<Formula>  connectedCore(final IntSet coreVars) {
		final Set<Formula> coreNodes = new IdentityHashSet<Formula>();
		final RecordFilter filter = new RecordFilter() {
			public boolean accept(Node node, Formula translated, int literal, Map<Variable,TupleSet> env) {
				return coreVars.contains(StrictMath.abs(literal));
			}
		};
		for(Iterator<TranslationRecord> itr = log().replay(filter); itr.hasNext(); ) {
			coreNodes.add(itr.next().translated());
		}
		final Set<Formula> connected = new IdentityHashSet<Formula>();
		final AbstractVoidVisitor traverser = new AbstractVoidVisitor() {
			final Set<Node> visited = new IdentityHashSet<Node>();
			/**
			 * Returns true if the given node has been visited before or if 
			 * it is not contained in this.nodes set.  Otherwise adds 
			 * the node to the connected set and returns false.
			 * @ensures this.visited' = this.visited + n
			 * @ensures n !in this.visited && n in coreNodes => 
			 *  connected' = connected + n else connected' = connected
			 * @return n in visited || n !in coreNodes
			 */
			protected boolean visited(Node n) {
				if (visited.add(n) && coreNodes.contains(n)) {
					connected.add((Formula)n);
					return false;
				}
				return true;
			}
		};
		for(Formula root: log().roots()) {
			root.accept(traverser);
		}
		return connected;
	}
	
	/**
	 * {@inheritDoc}
	 * @see kodkod.engine.Proof#core()
	 */
	public final Iterator<TranslationRecord> core() { 
		if (coreFilter == null) {
			coreFilter = new RecordFilter() {
				final IntSet coreVariables = StrategyUtils.coreVars(solver.proof());
				final Set<Formula> coreNodes = connectedCore(coreVariables);
				public boolean accept(Node node, Formula translated, int literal, Map<Variable,TupleSet> env) {
					return coreNodes.contains(translated) && coreVariables.contains(StrictMath.abs(literal));
				}
			};
		}
		return log().replay(coreFilter); 
	}
	
	/**
	 * {@inheritDoc}
	 * @see kodkod.engine.Proof#highLevelCore()
	 */
	public final Map<Formula, Node> highLevelCore() {
		if (coreRoots == null) { 
			final RecordFilter unitFilter = new RecordFilter() {
				final IntSet coreUnits = StrategyUtils.coreUnits(solver.proof());
				final Set<Formula> roots = log().roots();
				public boolean accept(Node node, Formula translated, int literal, Map<Variable, TupleSet> env) {
					return roots.contains(translated) && coreUnits.contains(Math.abs(literal));
				}
				
			};
			coreRoots = new LinkedHashMap<Formula, Node>();
			final IntSet seenUnits = new IntTreeSet();
			for(Iterator<TranslationRecord> itr = log().replay(unitFilter); itr.hasNext(); ) {
				// it is possible that two top-level formulas have identical meaning,
				// and are represented with the same core unit; in that case, we want only
				// one of them in the core.
				final TranslationRecord rec = itr.next();
				if (seenUnits.add(rec.literal())) {
					coreRoots.put(rec.translated(), rec.node());
				}  
			}
			coreRoots = Collections.unmodifiableMap(coreRoots);
		}
		return coreRoots;
	}
	
	/**
	 * {@inheritDoc}
	 * @see kodkod.engine.Proof#minimize(kodkod.engine.satlab.ReductionStrategy)
	 */
	public void minimize(ReductionStrategy strategy) {
		solver.reduce(strategy);
		coreFilter = null;
		coreRoots = null;
	}
}