#!/usr/bin/env python
from __future__ import division
import argparse
import collections
from datetime import datetime
import json
import math
import os
import random
import re
import shutil
import sys
import time
import subprocess
import threading
import xml.etree.ElementTree

TMP_FILE = "/data/local/tmp/"
PRM_ACTION_OFF = "revoke"
PRM_ACTION_ON = "grant"
INSTRUMENTATION_TAG = "edu.uci.seal.cactus_instrumentation"
INSTRUMENTATION_SEPARATOR = "###"
INSTRUMENTATION_DIR = "instrumented"
ANALYSIS_DIR = "analysis"
MONKEY_DIR = "monkey"
DARE_OUTPUT = "dare"
RETARGETED_DIR = DARE_OUTPUT + "/retargeted"
TEST_DYNAMIC_ANALYSIS_RESULT = "TE.json"
FNULL = open(os.devnull, 'w')	
XPOSED_LOGGER = "InputLogger.java"
XPOSED_LOGGER_APP = "libs/xposed/XposedEntryPoint"

VERBOSE = False
INTERACTIVE = False
ANDROID_HOME = None
EMULATOR_ID = None
TEST_FRAMEWORK = None
PERFORMANCE = None

MONKEY_TEST_SIZE = 50
MONKEY_EVENTS_SIZE = 2000

# ---------------------------------- Utility functions -----------------------------------
def nCr(n,r):
    f = math.factorial
    return f(n) / f(r) / f(n-r)

def pairwise(perm_size):
    pair_set = {2:4, 3:4, 4:6, 5:6, 6:6, 7:7, 8:8, 9:8, 10:8, 11:8, 12:8, 13:9, 14:10, 15:10, 16:10}
    return pair_set.get(perm_size)

def run_test(test_pkg, test_method, coverage_file=None):
	if VERBOSE:
		print "run test: ", test_method
	adb_command = ["adb", "shell", "am", "instrument", "-w"]
	test_runner = None
	if TEST_FRAMEWORK == 'r':
		test_runner = "android.test.InstrumentationTestRunner"
	elif TEST_FRAMEWORK == 'e':
		test_runner = "android.support.test.runner.AndroidJUnitRunner"
	test_module = [test_pkg + "/" + test_runner]
	test_options = []
	if test_method:
		test_options.extend(["class", test_method])
	if coverage_file:
		test_options.extend(["emma", "true", "coverageFile", coverage_file])
	if len(test_options) > 0:
		test_options.insert(0, "-e")
	call_command(adb_command + test_options + test_module)

def conf_prms(pkg, prm_config):
	if VERBOSE:
		print "set config: ", prm_config
	for prm in prm_config:
		action = prm_config[prm]
		conf_prm(pkg, prm, action)

def generate_all_configs(perm_list):
	if not perm_list:
		return [{}]
	cur_prm = perm_list.pop(0)
	config_list = generate_all_configs(perm_list)
	result = []
	for config in config_list:
		cur_prm_off = config.copy()
		cur_prm_off[cur_prm] = PRM_ACTION_OFF
		cur_prm_on = config.copy()
		cur_prm_on[cur_prm] = PRM_ACTION_ON
		result.extend([cur_prm_off, cur_prm_on])
	return result

def conf_prm(pkg, prm, action):
	full_prm = "android.permission." + prm
	cmd_args = [action, pkg, full_prm]
	run_adb_pm_command(cmd_args)

def run_adb_push_command(src, dst):
	adb_command = ["adb", "push"]
	call_command(adb_command + [src, dst])

def run_adb_pm_command(args):
	adb_command = ["adb", "shell", "pm"]
	call_command(adb_command + args)

def sign_app(apk):
	apk_signer = ANDROID_HOME + "/build-tools/24.0.3/apksigner"
	command = [apk_signer, "sign", "--ks", "seal_key_store.jks", "--ks-pass", "pass:AS123456", apk]
	call_command(command)

def resign_app(apk):
	command = "libs/signapk.sh %s %s %s %s" % (apk, "seal_key_store.jks", "AS123456", "seal")
	call_command(command, is_shell=True)

def sync_emulator_time():
	adb_command = ["adb", "shell", "date", "`date +%m%d%H%M%G.%S`"]
	call_command(adb_command)

def clear_logcat():
	adb_command = ["adb", "logcat", "-c"]
	call_command(adb_command)

def get_test_number(test):
	match = re.search(r'.*#t(\d*)_.*', test)
	if match:
		return int(match.group(1))
	return test

def run_cactus_jar(input, android_cp, log_message, phase=None, analysis_time=None):
	skip = print_debug_info(log_message)
	res = 'libs/cactus/res'
	# TODO (assume app is already instrumented)	
	#if phase == "instrument":
	#	cactus_jar = "Cactus_instrument.jar"
	#else:
	cactus_jar = "Cactus.jar"
	command = ["java", "-jar", "libs/cactus/" + cactus_jar, "-input", input, "-android", android_cp, "-res", res]
	if phase:
		command.extend(["-phase", phase])
	start_time = time.time()
	call_command(command, skip)
	end_time = time.time()
	if analysis_time and phase:
		analysis_time[phase] = end_time - start_time

def print_debug_info(info):
	print "-" * 10, '{:^40}'.format(info), "-" * 10
	skip = False
	if INTERACTIVE:
		c = raw_input('Press s to skip this phase, or enter to continue:')
		if c.upper() == 'S': 
			skip = True
	print "-" * 10, '{:^40}'.format('Running ...'), "-" * 10
	return skip

def create_json_input(apk_address, apk_test_address, output=None):
	skip = print_debug_info("Creating json input")
	if not output:
		output = os.path.join(os.path.dirname(apk_address), os.path.splitext(os.path.basename(apk_address))[0] + ".json")
	if not skip:
		input = {'apk':apk_address}	
		if apk_test_address:
			input['test_apk'] = apk_test_address
		with open(output, 'w') as outfile:
			json.dump(input, outfile, sort_keys=True, indent=4, separators=(',', ': '))
	return os.path.abspath(output)


def start_emulator():
	emulator = ANDROID_HOME + "/tools/emulator"
	command = [emulator, "-netdelay", "none", "-netspeed", "full", "-avd", EMULATOR_ID]
	print_debug_info("Starting Emulator")
	call_command(command)

def process_logcat(error_log=False):
	process_result = []
	logcat_commands = []
	logcat_commands.append((["adb", "logcat", "-d", INSTRUMENTATION_TAG + ":*", "*:S"], r'(.*) (.*) (.*) (.*) (V|D|I|W|E|F|S) ' + INSTRUMENTATION_TAG + '.*' + INSTRUMENTATION_SEPARATOR + ' (.*)', 6))
	if error_log:
		logcat_commands.append((["adb", "logcat", "-d", "*:E"], r'(.*permission.*)', 1))
	for logcat_command in logcat_commands:
		process_result.append(process_single_logcat(logcat_command[0], logcat_command[1], logcat_command[2]))
	clear_logcat()
	return process_result

def process_single_logcat(logcat_command, logcat_pattern, logcat_group_number):
	logcat_results = set()
	p = subprocess.Popen(logcat_command, stdout=subprocess.PIPE)		
	logcat_out = p.communicate()[0]
	p.wait()
	for line in logcat_out.splitlines():
		match = re.search(logcat_pattern, line)
		if match:
			logcat_results.add(match.group(logcat_group_number))
	return logcat_results

def generateRandom(testSuiteSize, testSeeds):
    with open(testSeeds, "w") as out:
    	for test in range(testSuiteSize):
        	rand = random.randint(1, sys.maxsize)
        	out.write(str(rand)+"\n")

def call_command(command, skip=False, is_shell=False, timeout=None):
	if skip:
		if VERBOSE:		
			print 'Skipping command:', command
		return
	if not timeout:
		if VERBOSE:
			subprocess.call(command, shell=is_shell)
		else:
			subprocess.call(command, stdout=FNULL, stderr=subprocess.STDOUT, shell=is_shell)
	else:	
		command = Command(command, VERBOSE, is_shell)
		command.run(timeout)

def build_exposed_module(app):
	skip = print_debug_info("Building Xposed module")
	if skip:
		return	
	run_cactus_jar(app.apk, ANDROID_HOME + '/platforms', 'Creating xposed Java file', 'instrument_xposed')
	xposed_java = os.path.join(os.path.dirname(app.apk), XPOSED_LOGGER)
	shutil.copyfile(xposed_java, os.path.join(XPOSED_LOGGER_APP + '/app/src/main/java/edu/uci/ics/seal/xposedentrypoint', XPOSED_LOGGER))
	shutil.copyfile(xposed_java, os.path.join(app.out_dir, XPOSED_LOGGER))
	os.remove(xposed_java)
	skip = print_debug_info('Install Xposed module')
	call_command(["gradle", "clean", "installDebug", "-p", XPOSED_LOGGER_APP], skip)
	call_command(["adb", "shell", "su", "-c", "setprop", "ctl.restart zygote"], skip)
	time.sleep(5)
	print_debug_info("Waiting for device")
	call_command("libs/wait-for-device.sh", is_shell=True)

def clean_up_experiment(app):
	skip = print_debug_info("Cleaning the mess!")
	if skip:
		return	
	call_command(["adb", "uninstall", app.pkg])
	if TEST_FRAMEWORK != 'm':	
		call_command(["adb", "uninstall", app.test_pkg])
	dare_dir = os.path.join(app.out_dir,'dare')
	if os.path.exists(dare_dir):
		shutil.rmtree(dare_dir)	

# ---------------------------------- RepeatedTimer class -----------------------------------
class RepeatedTimer(object):
    def __init__(self, interval, function, *args, **kwargs):
        self._timer     = None
        self.interval   = interval
        self.function   = function
        self.args       = args
        self.kwargs     = kwargs
        self.is_running = False
        self.start()

    def _run(self):
        self.is_running = False
        self.start()
        self.function(*self.args, **self.kwargs)

    def start(self):
        if not self.is_running:
            self._timer = threading.Timer(self.interval, self._run)
            self._timer.start()
            self.is_running = True

    def stop(self):
        self._timer.cancel()
        self.is_running = False

# ---------------------------------- Command class -----------------------------------
class Command(object):
    def __init__(self, cmd, verbosity, is_shell):
        self.cmd = cmd
        self.process = None
        self.is_shell = is_shell
        self.verbose = verbosity
        self.out = None
        self.err = None

    def run(self, timeout):
        def target():
        	if self.verbose:
        		self.process = subprocess.Popen(self.cmd, shell=self.is_shell)       			
        	else:
        		self.process = subprocess.Popen(self.cmd, stdout=FNULL, stderr=subprocess.STDOUT, shell=self.is_shell)
        	self.out, self.err = self.process.communicate()

        thread = threading.Thread(target=target)
        thread.start()
        thread.join(timeout)
        if thread.is_alive():
        	print "*" * 10, '{:^40}'.format('Timeout, terminating...'), "*" * 10
        	self.process.terminate()
        	thread.join()
        if self.verbose:
        	print self.err

# ---------------------------------- Application class -----------------------------------
class Application:

	def __init__(self, json_file, instrument=False):
		self.json_file = json_file
		self.set_output_dir()
		self.set_analysis_dir()
		self.extract_app()

	def extract_app(self):
		with open(self.json_file) as data_file:
			data = json.load(data_file)
			self.pkg = data["package"]
			self.apk = data["apk"]
			self.perms = data["perms"]
			if data.get("test_apk"):
				self.test_pkg = data["test_package"]
				self.test_apk = data["test_apk"]
			self.test_maps = data.get("tests")
	
	def install_app(self, install_test=True):
		# TODO (assume app is already instrumented)
		#if (instrument):
		#	src = self.out_dir + "/" + INSTRUMENTATION_DIR + "/" + os.path.basename(self.apk)
		src = self.apk
		call_command(["adb", "uninstall", self.pkg])
		resign_app(src)
		dst = TMP_FILE + self.pkg
		run_adb_push_command(src, dst)
		run_adb_pm_command(["install", "-r", dst])
		if install_test:
			src = self.test_apk
			call_command(["adb", "uninstall", self.test_pkg])		
			resign_app(src)
			dst = TMP_FILE + self.test_pkg
			run_adb_push_command(src, dst)
			run_adb_pm_command(["install", "-r", dst])

	def get_test_methods(self):
		return sorted(self.test_maps.keys(), key=lambda s: get_test_number(s))
	
	def set_output_dir(self):
		dir_name = os.path.dirname(self.json_file)
		file_name = os.path.splitext(os.path.basename(self.json_file))[0]	
		out_dir = os.path.join(dir_name, file_name)
		if not os.path.exists(out_dir):
			os.makedirs(out_dir)
		self.out_dir = out_dir

	def set_analysis_dir(self):
		analysis_dir = os.path.join(self.out_dir, ANALYSIS_DIR)
		if not os.path.exists(analysis_dir):
			os.makedirs(analysis_dir)
		self.analysis_dir = analysis_dir

# ---------------------------------- TestRunner class -----------------------------------
class TestRunner:

	def __init__(self, application):
		self.app = application
		self.app.install_app()		
	   
	def run_test_no_config(self, analyzer=None, log_time=False):
		test_results = {}
		for test_method in self.app.get_test_methods():
			if test_method == 'commonTest':
				continue
			start_time = time.time()
			run_test(self.app.test_pkg, test_method)
			end_time = time.time()			
			if analyzer:
				test_results[test_method] = list(process_logcat()[0])
			elif log_time:
				test_results[test_method] = (end_time-start_time)
		return test_results
	
# ---------------------------------- MonkeyTestRunner class -----------------------------------
class MonkeyTestRunner:

	def __init__(self, application):
		self.app = application
		self.set_monkey_dir()	

	def set_monkey_dir(self):
		self.monkey_dir = os.path.join(self.app.out_dir, MONKEY_DIR)
		self.monkey_seeds = os.path.join(self.monkey_dir,"testSeeds.txt")
		self.monkey_logs = os.path.join(self.monkey_dir,"logs.json")
		if not os.path.exists(self.monkey_dir):
			os.makedirs(self.monkey_dir)			
		if not os.path.exists(self.monkey_seeds):
			generateRandom(MONKEY_TEST_SIZE, self.monkey_seeds)

	def run_monkey(self):
		overlay_command = ["adb", "shell", "am", "broadcast", "-a", "org.thisisafactory.simiasque.SET_OVERLAY", "--ez", "enable", "true"]
		call_command(overlay_command)		
		rt = RepeatedTimer(5, call_command, overlay_command)
		skip = print_debug_info("Running Monkey Tests")
		if skip:
			return	
		self.app.install_app(False)
		test_results = {}
		test_logs = {}
		start_time = time.time()		
		with open(self.monkey_seeds) as f:
			seeds = f.read().splitlines()
	    	count = 1
    		for seed in seeds:
    			call_command(["adb", "shell", "monkey", "-p", self.app.pkg, "-s", seed, "--pct-anyevent 0", "--pct-syskeys 0", "-v", str(MONKEY_EVENTS_SIZE)])
    			time.sleep(2)
    			call_command(["adb", "shell", "am", "force-stop", self.app.pkg])
    			logcat_out = process_logcat(True)
	    		test_results["test_" + str(count)] = list(logcat_out[0])
	    		test_logs["test_" + str(count)] = list(logcat_out[1])
    			count = count + 1
		end_time = time.time()		
		output = os.path.join(self.app.analysis_dir, TEST_DYNAMIC_ANALYSIS_RESULT)
		with open(output, 'w') as outfile:
   			json.dump(test_results, outfile, sort_keys=True, indent=4, separators=(',', ': '))
		output = self.monkey_logs
		with open(output, 'w') as outfile:
   			json.dump(test_logs, outfile, sort_keys=True, indent=4, separators=(',', ': '))
   		rt.stop()
   		return end_time - start_time
							
# ---------------------------------- Analyzer class -----------------------------------
class Analyzer:
	
	def __init__(self, application):
		self.app = application

	def run_dynamic_analysis(self):
		skip = print_debug_info("Running Dynamic Analysis")
		if skip:
			return
		output = os.path.join(self.app.analysis_dir, TEST_DYNAMIC_ANALYSIS_RESULT)
		runner = TestRunner(self.app)
		start_time = time.time()
		test_entrypoints = runner.run_test_no_config(self)
		end_time = time.time()		
		with open(output, 'w') as outfile:
			json.dump(test_entrypoints, outfile, sort_keys=True, indent=4, separators=(',', ': '))
		return end_time - start_time

	def gen_ipc(self):
		skip = print_debug_info("Generating IPC")
		apk = self.app.apk
		command = ["./libs/dare-1.1.0-macos/dare", "-d", self.app.out_dir + "/" + DARE_OUTPUT , apk]
		call_command(command, skip)
		dare_out = os.path.join(self.app.out_dir + '/' + RETARGETED_DIR, os.path.splitext(os.path.basename(apk))[0])
		command = ["java", "-Xmx8g", "-jar", "./libs/ic3-0.2.0/ic3-0.2.0-full.jar", "-apkormanifest", apk, "-input", dare_out, "-cp", "./libs/ic3-0.2.0/android.jar", "-protobuf", self.app.out_dir, "-binary"]
		call_command(command, skip, timeout=300)

# ---------------------------------- Experiment class -----------------------------------
class RuntimeCalculator:
	
	def __init__(self, application):
		self.app = application
		self.out = os.path.join(self.app.out_dir, "runtime.csv")
		self.result = {}
	
	def calc_runtime(self, calcRunTime):
		if calcRunTime:
			runner = TestRunner(self.app)
			self.test_results = runner.run_test_no_config(log_time=True)
			self.calc_time_reduction()
			self.add_test_time()
		self.calc_test_reduction()
		self.print_csv()
	
	def calc_test_reduction(self):
		self.result["exhaustive_test_size"] = len(self.app.test_maps) * (2 ** len(self.app.perms))
		self.result["pariwise_test_size"] = len(self.app.test_maps) * pairwise(len(self.app.perms))
		self.result["all_and_nothing_test_size"] = 2 * len(self.app.test_maps)
		reduced_perm_size = 0
		for perms in self.app.test_maps.values():
			reduced_perm_size = reduced_perm_size + (2 ** len(perms))
		self.result["efficient_test_size"] = reduced_perm_size

	def calc_time_reduction(self):
		total_time = 0
		binary_time = 0
		efficient_time = 0
		pariwise_time = 0
		for test, run_time in self.test_results.items():			
			total_time = total_time + (run_time * (2 ** len(self.app.perms)))
			pariwise_time = pariwise_time + (run_time * pairwise(len(self.app.perms)))
			binary_time = binary_time + (2 * run_time)
			efficient_time = efficient_time + (run_time * (2 ** len(self.app.test_maps[test])))
		self.result["exhaustive_time"] = total_time
		self.result["pariwise_time"] = pariwise_time
		self.result["all_and_nothing_time"] = binary_time
		self.result["efficient_time"] = efficient_time
	
	def add_test_time(self):
		with open(self.app.json_file) as data_file:
			data = json.load(data_file)
			data["test_time"] = self.test_results
			with open(self.app.json_file, 'w') as outfile:
				json.dump(data, outfile, sort_keys=True, indent=4, separators=(',', ': '))
	
	def print_csv(self):
		if not os.path.exists(self.out):
			with open(self.out, "w") as out:
				out.write('app_id, perm#, test#, efficient_test_size, exhaustive_test_size, pariwise_test_size, all_and_nothing_test_size, efficient_time, exhaustive_time, pariwise_time, all_and_nothing_time \n')
		with open(self.out, "a") as out:
			out.write('{}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}\n'.format(self.app.pkg, len(self.app.perms), len(self.app.test_maps),	self.result.get("efficient_test_size"), self.result.get("exhaustive_test_size"), self.result.get("pariwise_test_size"), self.result.get("all_and_nothing_test_size"), self.result.get("efficient_time"), self.result.get("exhaustive_time"), self.result.get("pariwise_time"), self.result.get("all_and_nothing_time")))
	
# ---------------------------------- CoverageCalculator class -----------------------------------
class CoverageCalculator:

	COVERAGE_TYPES = ['LINE', 'BRANCH', 'INSTRUCTION']
	REPORT_TYPES = ['efficient', 'default', 'pairwise']

	def __init__(self, app):
		self.app = app
		self.report_dir = os.path.join(app.out_dir, 'report')
		self.out = os.path.join(self.app.out_dir, "coverage.csv")
		self.basic_coverage = {}

	def calc_coverage(self):
		for report_type in CoverageCalculator.REPORT_TYPES:
			self.calc_basic_coverage(report_type)
		self.print_csv()
	
	def calc_basic_coverage(self, report):
		coverage_report = os.path.join(self.report_dir, report + '.xml')
		if not os.path.exists(coverage_report):
			return
		self.basic_coverage[report] = {}
		root = xml.etree.ElementTree.parse(coverage_report).getroot()
		coverage = {}
		for package in root.findall('package'):
			my_pkg = package.get('name').replace("/", ".")			
			if not my_pkg.startswith(self.app.pkg):
				continue
			for source_file in package.findall('sourcefile'):
				source_name = source_file.get('name')
				coverage[source_name] = {}
				'''
				for counter in source_file.findall('counter'):
					count_type = counter.get('type')
					missed = int(counter.get('missed'))
					covered = int(counter.get('covered'))
					coverage[source_name][count_type] = (covered, missed)
				'''
				(mi, ci, mb, cb, ml, cl) = self.calc_line_branch_coverage(source_file)
				coverage[source_name]['LINE'] = (cl, ml)
				coverage[source_name]['INSTRUCTION'] = (ci, mi)
				coverage[source_name]['BRANCH'] = (cb, mb)
		total_coverage_c = {}
		total_coverage_m = {}
		for coverage_type in CoverageCalculator.COVERAGE_TYPES:
			total_coverage_c[coverage_type] = 0;
			total_coverage_m[coverage_type] = 0;
		for source_coverage in coverage.values():
			for coverage_type in source_coverage:
				total_coverage_c[coverage_type] = total_coverage_c[coverage_type] + source_coverage[coverage_type][0]
				total_coverage_m[coverage_type] = total_coverage_m[coverage_type] + source_coverage[coverage_type][1]
		for coverage_type in CoverageCalculator.COVERAGE_TYPES:	
			self.basic_coverage[report][coverage_type] = (total_coverage_c[coverage_type] / (total_coverage_c[coverage_type] + total_coverage_m[coverage_type]))

	
	def calc_line_branch_coverage(self, source_file_root):
		mi= ci= mb= cb = ml = cl = 0
		for line in source_file_root.findall('line'):
			my_mi = int(line.get('mi'))
			if my_mi == 0:
				cl = cl + 1
			else:
				ml = ml + 1
			mi = mi + my_mi
			ci = ci + int(line.get('ci'))
			mb = mb + int(line.get('mb'))
			cb = cb + int(line.get('cb'))
		return (mi, ci, mb, cb, ml, cl)


	def print_csv(self):
		if not os.path.exists(self.out):
			with open(self.out, "w") as out:
				header = 'app ,' + ', , ,'.join(CoverageCalculator.REPORT_TYPES) + '\n'
				out.write(header)
				header2 = ' ,' +  ('LINE, BRANCH, INSTRUCTION,'* len(CoverageCalculator.REPORT_TYPES)) + '\n'
				out.write(header2)
		row = []
		for report_type in CoverageCalculator.REPORT_TYPES:
			for coverage_type in CoverageCalculator.COVERAGE_TYPES:
				if self.basic_coverage.get(report_type):
					row.append(str(self.basic_coverage[report_type][coverage_type]))
		with open(self.out, "a") as out:
			out.write(self.app.pkg + ',' + ','.join(row) + '\n')


# ---------------------------------- Main functions -----------------------------------
def run_all_cactus_analyzer(root):
	apps = []
	for apk in os.listdir(root):
		if apk.endswith('.apk'):
			print "=" * 10, '{:^40}'.format(apk), "=" * 10
			app = run_cactus_analyzer(os.path.join(root, apk), None, None)
			apps.append(app)
			print "=" * 62
	run_cactus_expriments(root)

def run_cactus_expriments(dir):
	hasTestApk = TEST_FRAMEWORK != 'm'
	if not any(filename.endswith('.json') for filename in os.listdir(dir)):
		for filename in os.listdir(dir):
			dir_name = os.path.join(dir, filename)
			if os.path.isdir(dir_name) and not filename.startswith("_"):
				run_cactus_expriments(dir_name)
	else:
		for input in os.listdir(dir):
			if input.endswith('.json'):
				app = Application(os.path.join(dir, input))
				skip = print_debug_info("Running experiments for " + app.pkg)
				if skip:
					return
				rt = RuntimeCalculator(app)
				rt.calc_runtime(hasTestApk)
				if hasTestApk:
					cc = CoverageCalculator(app)
					cc.calc_coverage()
				clean_up_experiment(app)

def run_cactus_analyzer(apk_address, apk_test_address, output):
	input = create_json_input(apk_address, apk_test_address, output)
	analysis_time = {} 
	android_jars = ANDROID_HOME + "/platforms"
	run_cactus_jar(input, android_jars, 'Processing/updating input file')
	app = Application(input)
	analyzer = Analyzer(app)
	analyzer.gen_ipc()
	if TEST_FRAMEWORK == 'm':
		build_exposed_module(app)
		runner = MonkeyTestRunner(app)
		dynamic_analysis_time = runner.run_monkey()
		analysis_time['dynamic_analysis'] = dynamic_analysis_time if dynamic_analysis_time else 0
	else:
		# TODO (assume app is already instrumented)
		#run_cactus_jar(input, android_jars, 'Instrumenting apk', 'instrument', analysis_time)	
		dynamic_analysis_time = analyzer.run_dynamic_analysis()
		analysis_time['dynamic_analysis'] = dynamic_analysis_time if dynamic_analysis_time else 0
		run_cactus_jar(input, android_jars, 'Analyzing test', 'analysis_test', analysis_time)
	run_cactus_jar(input, android_jars, 'Analyzing app', 'analysis_app', analysis_time)
	run_cactus_jar(input, android_jars, 'Generating test config', 'gen_config', analysis_time)
	if PERFORMANCE:
		performance_out = os.path.join(app.out_dir, 'performance.csv')
		with open(performance_out, "w") as out:
			out.write(",".join(str(x) for x in analysis_time.keys()) + "\n")
			out.write(",".join(str(x) for x in analysis_time.values()) + "\n")
	clean_up_experiment(app)			
	return app
		

def process_input():
	global ANDROID_HOME, INTERACTIVE, VERBOSE, EMULATOR_ID, TEST_FRAMEWORK, PERFORMANCE
	parser = argparse.ArgumentParser(description='Run PATDroid')
	parser.add_argument('mode', help='PATDroid running mode (a=analysis, e=experiment, a=analyze all apps in a directory)', choices=['a','e', 'all'], action='store', default='a')
	parser.add_argument('apk_address', help='the path to the apk file or the directory containing apk files', nargs='?')	
	parser.add_argument('apk_test_address', help='the path to the test apk file', nargs='?')	
	parser.add_argument('-s', '--androidsdk', metavar='ANDROID_SDK', dest='android_sdk', help='the path to the Android\' SDK home directory (by default read from $ANDROID_HOME)')	
	parser.add_argument('-o', '--output', dest='output', help='the path to the output file')	
	parser.add_argument('-r', '--result', dest='result', help='the path to the analysis output json file')	
	parser.add_argument('-e', '--emulator', dest='emulator_id', help='the emulator id')	
	parser.add_argument('-v',"--verbose", help="increase output verbosity", action="store_true")
	parser.add_argument('-i',"--interactive", help="run interactively", action="store_true")
	parser.add_argument('-p',"--performance", help="measure analysis time", action="store_true")
	parser.add_argument('-t', '--testframework', metavar='Test Framework', dest='test_framework', choices=['e','r', 'm'], action='store', 
						help='test framework for running tests (e= Espresso, r=Robotium, m=Monkey)', default='e')
	args = parser.parse_args()
	VERBOSE = args.verbose
	INTERACTIVE = args.interactive	
	ANDROID_HOME = args.android_sdk
	EMULATOR_ID = args.emulator_id
	TEST_FRAMEWORK = args.test_framework
	PERFORMANCE = args.performance
	if not args.android_sdk:
		if os.environ.get('ANDROID_HOME'):
			ANDROID_HOME = os.environ.get('ANDROID_HOME')
			# TODO check for apksigner
		else:
			print 'ANDROID_HOME is missing'
			parser.print_help()
			sys.exit(2)
	input = None
	if args.mode == 'a':
		if not (args.apk_address or args.apk_test_address):
			print 'Main and/or Test apk(s) are missing'
			parser.print_help()
			sys.exit(2)			
	elif args.mode == 'all':
		if not (args.apk_address):
			print 'Root directory of all APK files is missing'
			parser.print_help()
			sys.exit(2)			
	else:
		if not args.result:
			print 'Analysis results directory (-r result) is missing'
			parser.print_help()
			sys.exit(2)						
	return args

def main():
	os.system('clear')
	args = process_input()
	if args.mode == 'a':
		run_cactus_analyzer(args.apk_address, args.apk_test_address, args.output)
	elif args.mode == 'all':
		run_all_cactus_analyzer(args.apk_address)		
	elif args.mode == 'e':
		run_cactus_expriments(os.path.abspath(args.result))

if __name__ == '__main__':
	
    main()
