/**
 * 
 */
package kodkod.examples.tptp;

import java.util.ArrayList;
import java.util.List;

import kodkod.ast.Decls;
import kodkod.ast.Expression;
import kodkod.ast.Formula;
import kodkod.ast.Relation;
import kodkod.ast.Variable;
import kodkod.engine.Solution;
import kodkod.engine.Solver;
import kodkod.engine.config.ConsoleReporter;
import kodkod.engine.fol2sat.HigherOrderDeclException;
import kodkod.engine.fol2sat.UnboundLeafException;
import kodkod.engine.satlab.SATFactory;
import kodkod.instance.Bounds;
import kodkod.instance.TupleFactory;
import kodkod.instance.TupleSet;
import kodkod.instance.Universe;
import kodkod.util.nodes.PrettyPrinter;

/**
 * A KK encoding of GRA019+1.p through GRA026+1.p from http://www.cs.miami.edu/~tptp/
 * @author Emina Torlak
 */
public final class GRA013_026 {
	private final Relation red, green, lessThan,goal,node;
	private final int graphSize, cliqueSize;
	
	/**
	 * Constructs a new instance of GRA013_026 with the given graph and clique size.
	 * @requires 0 < cliqueSize <= graphSize
	 */
	public GRA013_026(int graphSize, int cliqueSize) {
		if (cliqueSize <= 0) throw new IllegalArgumentException("cliqueSize must be positive: " + cliqueSize);
		if (cliqueSize > graphSize) throw new IllegalArgumentException("cliqueSize must be less than or equal to graph size: " + cliqueSize + ">" + graphSize);
		node = Relation.unary("N");
		red = Relation.binary("red");
		green = Relation.binary("green");
		lessThan = Relation.binary("lessThan");
		goal = Relation.unary("goal");
		this.graphSize = graphSize;
		this.cliqueSize = cliqueSize;
	}
	
	private final Formula cliqueAxiom(Expression color) {
		final Variable[] vars = new Variable[cliqueSize];
		for(int i = 0; i < cliqueSize; i++) { 
			vars[i] = Variable.unary("V"+i);
		}
		final List<Expression> members = new ArrayList<Expression>(cliqueSize);
		for(int i = 0, max = cliqueSize-1; i < max; i++) { 
			final List<Expression> tmp = new ArrayList<Expression>();
			for(int j = i+1; j < cliqueSize; j++) { 
				tmp.add(vars[j]);
			}
			members.add(vars[i].product(Expression.union(tmp)));
		}
		Decls d = vars[0].oneOf(node);
		for(int i = 1; i < cliqueSize; i++) { 
			d = d.and(vars[i].oneOf(vars[i-1].join(lessThan)));
		}
		return Expression.union(members).in(color).implies(goalToBeProved()).forAll(d);
	}
	
	
	/**
	 * Returns the red clique axiom.
	 * @return red clique axiom.
	 */
	public final Formula redCliqueAxiom() {
		return cliqueAxiom(red);
	}
	
	/**
	 * Returns the green clique axiom.
	 * @return green clique axiom.
	 */
	public final Formula greenCliqueAxiom() {
		return cliqueAxiom(green);
	}
	
	/**
	 * Returns the partition axiom.
	 * @return partition axiom
	 */
	public final Formula partition() {
		return lessThan.in(red.union(green));
	}
	
	/**
	 * Returns the transitivity axiom.
	 * @return transitivity axiom
	 */
	public final Formula lessThanTransitive() { 
		return lessThan.join(lessThan).in(lessThan);
	}
	
	/**
	 * Returns the no overlap axiom.
	 * @return no overlap axiom.
	 */
	public final Formula noOverlap() { 
		return red.intersection(green).no();
	}
	
	/**
	 * Returns the conjunction of all axioms.
	 * @return conjunction of all axioms
	 */
	public final Formula axioms() {
		return Formula.and(redCliqueAxiom(), greenCliqueAxiom(), partition(), lessThanTransitive(), noOverlap());
	}
	
	/**
	 * Returns the goal_to_be_proved conjecture.
	 * @return goal_to_be_proved conjecture.
	 */
	public final Formula goalToBeProved() { 
		return goal.some();
	}
	
	/**
	 * Returns the conjunction of the axioms and the negation of the hypothesis.
	 * @return axioms() && !goalToBeProved()
	 */
	public final Formula checkGoalToBeProved() { 
		return axioms().and(goalToBeProved().not());
	}
	
	/**
	 * Returns the bounds.
	 * @return the bounds
	 */
	public final Bounds bounds() {
		final List<String> atoms = new ArrayList<String>(graphSize);
		for(int i = 1; i <= graphSize; i++)
			atoms.add("n"+i);
		atoms.add("goal");
		final Universe u = new Universe(atoms);
		final TupleFactory f = u.factory();
		final Bounds b = new Bounds(u);
		b.bound(goal, f.setOf("goal"));
		final TupleSet ns = f.range(f.tuple("n1"), f.tuple("n"+graphSize));
		b.boundExactly(node, ns);
		
		final TupleSet s = f.noneOf(2);
		for(int i = 1; i < graphSize; i++) {
			for(int j = i+1; j < graphSize; j++)
				s.add(f.tuple("n"+i, "n"+j));
		}
		b.boundExactly(lessThan, s);
		b.bound(red, s);
		b.bound(green, s);
		return b;
	}
	
	private static void usage() {
		System.out.println("Usage: java examples.tptp.GRA013_026 <graph size> <clique size>");
		System.exit(1);
	}
	
	/**
	 * Usage: java examples.tptp.GRA013_026 <graph size> <clique size>
	 */
	public  static void main(String[] args) {
		if (args.length < 2)
			usage();
		try {

			final GRA013_026 model = new GRA013_026(Integer.parseInt(args[0]), Integer.parseInt(args[1]));
			
			final Bounds b = model.bounds();
			final Solver solver = new Solver();
			solver.options().setSolver(SATFactory.MiniSat);
			solver.options().setReporter(new ConsoleReporter());
			
			final Formula f = model.checkGoalToBeProved();
			System.out.println(PrettyPrinter.print(f, 2));
//			System.out.println(b);
			final Solution s = solver.solve(f, b);
			System.out.println(s);
			//System.out.println((new Evaluator(s.instance())).evaluate(f));
	
		} catch (HigherOrderDeclException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (UnboundLeafException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (NumberFormatException nfe) {
			usage();
		}
	}
}