package jayhorn;

import java.nio.file.Path;
import java.nio.file.Paths;

import org.apache.log4j.Level;
import org.kohsuke.args4j.CmdLineException;
import org.kohsuke.args4j.CmdLineParser;

import com.google.common.base.Stopwatch;

import jayhorn.checker.EldaricaChecker;
import jayhorn.checker.SpacerChecker;
import jayhorn.solver.ProverFactory;
import jayhorn.solver.princess.PrincessProverFactory;
import jayhorn.solver.spacer.SpacerProverFactory;
import jayhorn.utils.Stats;
import soottocfg.cfg.Program;
import soottocfg.soot.SootToCfg;
import soottocfg.soot.SootToCfg.MemModel;

public class Main {
	
    private static String parseResult(String solver, boolean result)
    {
    	if (result){
			return "SAFE";
		}else{
			return "UNSAFE";
		}
    }
    /**
     * Safety Analysis with JayHorn
     * @param options
     * @param factory
     */
    public static void safetyAnalysis(ProverFactory factory){
    	Log.info("Building CFG  ... ");
  		SootToCfg soot2cfg = new SootToCfg();
  		soottocfg.Options.v().setBuiltInSpecs(Options.v().useSpecs);
  		soottocfg.Options.v().setResolveVirtualCalls(true);
  		soottocfg.Options.v().setMemModel(MemModel.PullPush);
  		if (Options.v().getOut()!=null) {
  			Path outDir = Paths.get(Options.v().getOut());
  			String in = Options.v().getJavaInput();
  			String outName = in.substring(in.lastIndexOf('/') + 1, in.length()).replace(".java", "").replace(".class", "");
  			soottocfg.Options.v().setOutDir(outDir);
  			soottocfg.Options.v().setOutBaseName(outName);
  		}
  	
  		Stopwatch sootTocfgTimer = Stopwatch.createStarted();
  		soot2cfg.run(Options.v().getJavaInput(), Options.v().getClasspath());	
  	
  		Program program = soot2cfg.getProgram();
  	    Stats.stats().add("SootToCFG", String.valueOf(sootTocfgTimer.stop()));
  		
  		Log.info("Safety Verification ... ");

  		boolean result;
  		if ("spacer".equals(Options.v().getSolver())){
  			SpacerChecker spacer = new SpacerChecker(factory);
  			result = spacer.checkProgram(program);
  		} else{
  			EldaricaChecker eldarica = new EldaricaChecker(factory);
  			result = eldarica.checkProgram(program);
  		}
  	
  		String prettyResult = parseResult(Options.v().getSolver(), result);
  		
  		Stats.stats().add("FinalResult", prettyResult);
  		
  		Log.info("Safety Result ... " + prettyResult);
		if (Options.v().stats){ Stats.stats().printStats(); }
      }
    
	public static void main(String[] args) {
		
		Options options = Options.v();
		CmdLineParser parser = new CmdLineParser(options);
		try {
			// parse command-line arguments
			parser.parseArgument(args);
			options.updateSootToCfgOptions();
			ProverFactory factory = null;
			if ("spacer".equals(Options.v().getSolver())) {
				factory = new SpacerProverFactory();
			} else if ("eldarica".equals(Options.v().getSolver())) {
				factory = new PrincessProverFactory();
			} else {
				throw new RuntimeException("Don't know solver " + Options.v().getSolver() + ". Using Eldarica instead.");
			}

			if (Options.v().verbose) Log.v().setLevel(Level.INFO);
			
			Log.info("\t\t ---   JAYHORN : Static Analayzer for Java Programs ---- ");
			Log.info("\t Verification : " + Options.v().getChecker());
			Log.info("\t Solver : " + Options.v().getSolver());
			
			if ("safety".equals(Options.v().getChecker())) {			
				safetyAnalysis(factory);
			} else {
				Log.error(String.format("Checker %s is unknown", Options.v().getChecker()) );
			}
			
		} catch (CmdLineException e) {
			Log.error(e.toString());
			Log.error("java -jar jayhorn.jar [options...] -j [JAR, DIR]");
			parser.printUsage(System.err);
		
		} catch (Throwable t) {
			Log.error(t.toString());
			Stats.stats().add("Result", "UNKNOWN");
			if (Options.v().stats){ Stats.stats().printStats(); }
			throw t;	
		} finally {
			Options.resetInstance();
			soot.G.reset();
		}
	}
	
}
