package ca.uwaterloo.cs858.analysis

import com.ibm.wala.cast.ir.cfg.AstInducedCFG
import com.ibm.wala.cfg.ShrikeCFG
import com.ibm.wala.classLoader.CallSiteReference
import com.ibm.wala.classLoader.IClass
import com.ibm.wala.classLoader.IMethod
import com.ibm.wala.classLoader.Module
import com.ibm.wala.dalvik.classLoader.DexFileModule
import com.ibm.wala.dalvik.classLoader.DexIMethod
import com.ibm.wala.dalvik.classLoader.DexIRFactory
import com.ibm.wala.dalvik.ipa.callgraph.impl.DexEntryPoint
import com.ibm.wala.dalvik.util.AndroidAnalysisScope
import com.ibm.wala.ipa.callgraph.*
import com.ibm.wala.ipa.callgraph.impl.Util
import com.ibm.wala.ipa.callgraph.propagation.InstanceKey
import com.ibm.wala.ipa.cha.ClassHierarchy
import com.ibm.wala.ipa.slicer.*
import com.ibm.wala.ssa.ISSABasicBlock
import com.ibm.wala.ssa.SSACFG
import com.ibm.wala.ssa.SSAPhiInstruction
import com.ibm.wala.ssa.SSAPiInstruction
import com.ibm.wala.types.ClassLoaderReference
import com.ibm.wala.types.MethodReference
import com.ibm.wala.types.TypeReference
import com.ibm.wala.util.collections.HashSetFactory
import com.ibm.wala.util.config.AnalysisScopeReader
import com.ibm.wala.util.graph.traverse.BFSPathFinder
import com.ibm.wala.viz.DotUtil
import edu.purdue.cs.toydroid.utils.SimpleConfig
import org.apache.commons.io.FileUtils
import org.apache.logging.log4j.LogManager
import java.io.File
import java.io.FileNotFoundException
import java.io.IOException
import java.util.*
import java.util.concurrent.*
import kotlin.collections.ArrayList

class Main {
    companion object {
        private val logger = LogManager.getLogger(Main::class.java)
        private var taskTimeout = false
        private val timeout: Long = 20
        //private var outputDir = "C:\\Users\\mrafu\\IdeaProjects\\Analysis\\results"
        private var outputDir = "/Users/thomashumphries/IdeaProjects/CS858-Analysis/results"
        private var startTask: Long = 0
        private val ApkFiles: Array<String> = arrayOf(
                "C:\\Users\\mrafu\\IdeaProjects\\Analysis\\apks\\app-host-shamir-debug\\classes.dex",
                "C:\\Users\\mrafu\\IdeaProjects\\Analysis\\apks\\app-host-shamir-debug\\classes2.dex",
                "C:\\Users\\mrafu\\IdeaProjects\\Analysis\\apks\\app-client-shamir-debug\\classes.dex",
                "C:\\Users\\mrafu\\IdeaProjects\\Analysis\\apks\\app-client-shamir-debug\\classes2.dex"
        )

        @Throws(Throwable::class)
        fun doAnalysis() {
            logger.info("Start Analysis...")
            val analysis = Analysis(ApkFiles, outputDir)
            val future = Executors.newSingleThreadExecutor().submit(analysis)

            try {
                future[timeout, TimeUnit.MINUTES]
            } catch (ire: InterruptedException) {
                logger.error("InterruptedException: {}", ire.message)
            } catch (exe: ExecutionException) {
                logger.error("ExecutionException: {}", exe.message)
                exe.printStackTrace()
            } catch (toe: TimeoutException) {
                taskTimeout = true
                logger.warn("Analysis Timeout After {} {}!", timeout, TimeUnit.MINUTES)
            }

            val analysisEnd = System.currentTimeMillis()
            val time = String.format("%d.%03d",
                    (analysisEnd - startTask) / 1000,
                    (analysisEnd - startTask) % 1000)
            logger.info("Total Analysis Time: {} seconds.", time)
        }

        @JvmStatic
        fun main(args: Array<String>) {
            try {
                logger.info("APK Files: {}", ApkFiles)

                val files = ApkFiles.map { file -> File(file) }
                for (file in files) {
                    if (!(file.isFile && file.canRead())) {
                        throw Exception("Invalid APK file: " + file.absolutePath)
                    }
                }

                logger.info("Set TIMEOUT as {} minutes.", timeout)
                var fName = files[0].parentFile.name
                val idxDot = fName.lastIndexOf('.')
                if (idxDot >= 0) {
                    fName = fName.substring(0, idxDot)
                }

                outputDir = outputDir + File.separator + fName
                logger.info("Writing to: $outputDir")
                val file = File(outputDir)
                if (!file.exists()) {
                    file.mkdirs() // cascade mkdir
                } else if (!file.isDirectory) {
                    throw Exception("Non-directory path exists as OUTPUT dir: $outputDir")
                } else {
                    FileUtils.cleanDirectory(file)
                }
            } catch (pex: Exception) {
                logger.error("Error parsing command line arguments: {}", pex.message)
            }

            startTask = System.currentTimeMillis()
            try {
                doAnalysis()
            } catch (e: Throwable) {
                logger.error("Crashed: {}", e.message)
            }

            val endTask = System.currentTimeMillis()
            val time = String.format("%d.%03d", (endTask - startTask) / 1000, (endTask - startTask) % 1000)
            logger.info("Total Time: {} seconds.", time)
            System.exit(0)
        }
    }
}