package jayhorn.hornify;

import java.nio.charset.Charset;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.LinkedHashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;

import jayhorn.Log;
import jayhorn.hornify.encoder.MethodEncoder;
import jayhorn.solver.Prover;
import jayhorn.solver.ProverFactory;
import jayhorn.solver.ProverFun;
import jayhorn.solver.ProverHornClause;
import soottocfg.cfg.Program;
import soottocfg.cfg.method.Method;

/**
 * Class to hornify Java program
 * 
 * @author teme
 *
 */

public class Hornify {

	private final ProverFactory factory;

	private Prover prover;

	public final List<ProverHornClause> clauses = new LinkedList<ProverHornClause>();

	public Hornify(ProverFactory fac) {
		this.factory = fac;	
	}

	/**
	 * Main method to encode into Horn
	 * @param program
	 */
	public HornEncoderContext toHorn(Program program){
		prover = factory.spawn();
		prover.setHornLogic(true);
		
		HornEncoderContext hornContext = new HornEncoderContext(prover, program);

		Log.info("Transform Program Methods into Horn Clauses ... ");

		for (Method method : program.getMethods()) {
			final MethodEncoder encoder = new MethodEncoder(prover, method, hornContext);
			clauses.addAll(encoder.encode());		
		}
		
		return hornContext;
	}

	/**
	 * Return the current prover object
	 * @return prover
	 */
	public Prover getProver() {
		return prover;
	}


	/**
	 * Write clauses
	 * @return
	 */
	public String writeHorn() {
		StringBuilder st = new StringBuilder();
		for (ProverHornClause clause : clauses)
			st.append("\t\t" + clause + "\n");
		st.append("\t\t-------------\n");
		return st.toString();
	}

	/**
	 * Write Horn clauses to file
	 */
	public static void hornToFile(List<ProverHornClause> clauses,
			int num) {
		// write Horn clauses to file
		String out = jayhorn.Options.v().getOutDir();
		if (out != null) {
			String basename = jayhorn.Options.v().getOutBasename();
			Path file = Paths.get(out + basename + "_" + num + ".horn");

			LinkedList<String> it = new LinkedList<String>();
			for (ProverHornClause clause : clauses)
				it.add("\t\t" + clause);

			writeToFile(file, it);
		}
	}

	/**
	 * Write Horn clauses to an SMT-LIB file
	 */
	public static void hornToSMTLIBFile(List<ProverHornClause> clauses,
			int num,
			Prover prover) {
		String out = jayhorn.Options.v().getOutDir();
		if (out != null) {
			String basename = jayhorn.Options.v().getOutBasename();
			Path file = Paths.get(out + basename + "_" + num + ".smt2");

			Log.info("Writing Horn clauses to " + file);

			LinkedList<String> it = new LinkedList<String>();

			it.add("(set-info :origin \"Horn clauses generated by JayHorn\")");
			it.add("(set-logic HORN)");

			it.add("");

			Set<ProverFun> predicates = new LinkedHashSet<ProverFun>();
			for (ProverHornClause clause : clauses) {
				// null indicates that the head of the clause is "false"
				if (clause.getHeadFun() != null)
					predicates.add(clause.getHeadFun());
				for (int i = 0; i < clause.getArity(); ++i)
					predicates.add(clause.getBodyFun(i));
			}

			for (ProverFun fun : predicates)
				it.add(prover.toSMTLIBDeclaration(fun));

			it.add("");

			for (ProverHornClause clause : clauses)
				it.add("(assert " + prover.toSMTLIBFormula(clause) + ")");

			it.add("");
			it.add("(check-sat)");

			writeToFile(file, it);
		}
	}

	private static void writeToFile(Path file, List<String> it) {
		try {					
			Path parent = file.getParent();
			if (parent != null)
				Files.createDirectories(parent);
			Files.write(file, it, Charset.forName("UTF-8"));
		} catch (Exception e) {
			System.err.println("Error writing file " + file);
		}
	}


}
