import java.io.{File, PrintWriter}
import java.time.{Duration, Instant, LocalTime}
import java.util

import analysis.{Analyzer, PreAnalyzer}
import ch.qos.logback.classic.{Level, Logger}
import com.microsoft.z3._
import com.typesafe.scalalogging.StrictLogging
import constraints.Z3Constraint
import domain._
import org.slf4j.LoggerFactory
import parser.Parser

object Main extends App with StrictLogging {

  val help = """Usage:
    -apk=FILE     Apk file to analyze
    -print-rules  Print the textual description of the constraints
    -print-goal   Print the Z3 goal
    """

  // Check parameters
  if (args.length == 0) {
    println(help)
    System.exit(1)
  }
  val apkArg = args.find(a => a.startsWith("-apk"))
  if (apkArg.isEmpty) {
    println("No apk given")
    System.exit(1)
  }
  val apkLocation: String = apkArg.get.split("=")(1)
  val printRules: Boolean = args.exists(a => a.startsWith("-print-rules"))
  val printGoal: Boolean = args.exists(a => a.startsWith("-print-goal"))

  val parser = new Parser(apkLocation)
  var start = Instant.now()
  logger.info(s"Start: ${LocalTime.now()}")

  // Context
  val cfg = new util.HashMap[String, String]
  cfg.put("model", "true")
  val ctx = new Context(cfg)
  // Goal
  val g = ctx.mkGoal(true, false, false)

  val domainsCollection =
    AbstractDomainsCollection(
      new TopDomain(ctx),
      new ReferenceDomain(ctx),
      new TaintDomain(ctx)
    )
  val app = parser.dexApp
  val preAnalysisResult = new PreAnalyzer(app.classes).analyze
  val analyzer =
    new Analyzer(ctx, domainsCollection, app, preAnalysisResult)
  val constraints: Seq[Z3Constraint] = analyzer.analyze
  g.add(
    ctx.mkAnd(
      constraints.map(c => c.boolExpr): _*
    )
  )
  if (printRules) {
    println("Rules:")
    println(
      constraints
        .map(c => s"${c.pp.m.shortName}-${c.pp.pc} ${c.description}")
        .mkString("\n")
    )
  }
  if (printGoal) {
    println("Goal:")
    println(g)
  }
  var end = Instant.now()
  logger.info(
    s"Time to generate goal: ${Duration.between(start, end).getSeconds} seconds")
  start = Instant.now()
  val solver = ctx.mkSolver
  g.getFormulas.foreach(solver.add(_))
  val status = solver.check()
  end = Instant.now()
  logger.info(
    s"Time to check goal: ${Duration.between(start, end).getSeconds} seconds")
  status match {
    case Status.SATISFIABLE   => println("OK")
    case Status.UNSATISFIABLE => println("LEAK")
    case Status.UNKNOWN       => println(s"UNKNOWN: ${solver.getReasonUnknown}")
  }

}
