#!/usr/bin/env python
# -*- coding: utf-8 -*-

import os
import os.path
import sys
import traceback
import jtrace.util
import jtrace.config
import jtrace.util
import jtrace.vm
import datetime
import time

PRINT_TRACE = True

class Context(object):
    def __init__(self, apps_pkg_to_zh, queue_for_traces, queue_for_progress):
        """
        Init fucntion for class Context
        """
        self.apps_pkg_to_zh = apps_pkg_to_zh
        self.queue_for_traces = queue_for_traces
        self.queue_for_progress = queue_for_progress
        self.sess = None
        self.pid = None
        self.dev = None
        self.ver = None
        self.port = None
        self.pkg_name = ""
        self.pid_name = ""
        self.log_file = None
        self.hook_file = None
        self.queue = None
        self.hooks = []

    def monitor(self):
        """
        Start to trace the application
        """
        # Create Session
        jtrace.proto.forward(self.pid, self.port, self.dev)
        time.sleep(0.1)
        socket_conn = jtrace.proto.connect('127.0.0.1', self.port)
        time.sleep(0.1)
        sess = jtrace.vm.Session(socket_conn)
        time.sleep(0.01)
        sess.allclasses()
        # sess.allthreads()
        self.sess = sess

        # Do hook
        err_msg, trace_configs = jtrace.util.get_trace_configs()
        if err_msg:
            print err_msg
            return

        for line in trace_configs:
            trace_zh = line[0]
            api_name = line[1]
            cpath, mname = jtrace.util.str2fqclass(api_name)
            flag = self.cmd_hook_methods(self.ver, trace_zh, api_name, cpath, mname)
            time.sleep(0.001)
            if flag == False:
                # print "Failed to do hook for: ", api_name
                pass

    def flush_logs(self):
        """
        Flush the logs
        """
        jtrace.util.flush_to_disk(self.log_file)
        jtrace.util.flush_to_disk(self.hook_file)

    def report_hit(self, t):
        """
        Callback of the hook
        """
        api_args = t[0]

        # args passed
        args = t[1:]

        # second args is trace name in chinese
        trace_zh = args[0]
        api_name = args[1]

        now = str(datetime.datetime.now())
        zh = self.apps_pkg_to_zh[self.pkg_name]
        trace = (now, zh, self.pkg_name, self.pid_name, trace_zh, api_name, api_args)
        self.queue_for_traces.put(trace)

    def cmd_hook_methods(self, ver, trace_zh, api_name, cpath, mpath):
        """
        Apply a hook for the api_name
        """

        if ver == '4' and str(cpath) == 'Landroid/media/MediaRecorder;':
            # if PRINT_TRACE:
            #     print "Workaround: android.media.MediaRecorder does not work on android 4.0"
            return False

        sess = self.sess

        hook_args = (trace_zh, api_name)

        c = sess.get_class_by_name(cpath)
        if not c:
            # print "Hook failed - can not find the class: ", cpath, mpath
            return False
        refID = c["refTypeId"]
        sess.get_methods(refID)
        # debug beg
        #if cpath in ["Landroid/content/Intent;", "Landroid/location/Location;"]
        #if cpath in ["Landroid/location/Location;"]:
        #if cpath in ["Landroid/net/ConnectivityManager;", "Landroid/telephony/TelephonyManager;"]:
        #if cpath in ["Landroid/telephony/TelephonyManager;"]:
        #   print "Class: ", cpath, " has ", sess.get_methods(refID)
        # debug end
        methods = sess.get_methods_by_refid_name(refID, mpath)
        if not methods:
            # print "Hook failed - can not find the method: ", cpath, mpath
            return False
        for m in methods:
            # print "Hook: ", cpath, mpath, c, m
            if cpath == "Landroid/telephony/TelephonyManager;" and mpath == "setDataEnabled":
                if m["signature"] != '(Z)V':
                    # print "Skip: ", cpath, mpath, m
                    continue
                else:
                    pass
                    # print "Select: ", cpath, mpath, m
            if cpath == "Landroid/content/Intent;" and mpath == "<init>":
                if not m["signature"].startswith('(Ljava/lang/String;'):
                    # print "Skip: ", cpath, mpath, m
                    continue
                else:
                    pass
                    # print "Select: ", cpath, mpath, m
            if cpath == "Landroid/location/LocationManager;" and mpath in ["requestSingleUpdate", "requestLocationUpdates"]:
                if not m["signature"].startswith('(Ljava/lang/String;'):
                    #print "Skip: ", cpath, mpath, m
                    continue
                else:
                    pass
                    #print "Select: ", cpath, mpath, m

            try:
                h = sess.do_hook(api_name=api_name, classID=c["refTypeId"], methodID=m["methodId"], func=self.report_hit, args=hook_args)
                sess.hooks.append(h)
            except:
                # print "Hook failed - can not trace the event: ", cpath, mpath, c, m
                pass
        return True


class App:
    def __init__(self, apps_pkg_to_zh, queue_for_traces, queue_for_progress):
        """
        Init function for class App
        """
        self.ctxt = Context(apps_pkg_to_zh, queue_for_traces, queue_for_progress)

    def run_command(self, sn="", ver="", pid_name="", pkg_name="", log_name="", queue=None, write_log=True):
        """
        Run the cmd to do the trace
        """
        self.ctxt.port = jtrace.util.alloc_port()
        self.ctxt.pid = pid_name
        self.ctxt.dev = sn
	try:
            self.ctxt.ver = ver.split('.')[0]
	except:
            self.ctxt.ver = ''
        self.ctxt.pid_name = pid_name
        self.ctxt.pkg_name = pkg_name

        write_log = False
        if write_log:
            self.ctxt.log_file = open(log_name, 'w')
            self.ctxt.hook_file = open(log_name + '.hook', 'w')
        else:
            self.ctxt.log_file = None
            self.ctxt.hook_file = None

        self.ctxt.queue = queue

        self.ctxt.monitor()

    def panic(self, why, exit=False, exc=False):
        """
        Panic when something went wrong
        """
        sys.stderr.write("panic: !! %s\n" % (why,))
        sys.stderr.flush()
        if exc:
            traceback.print_exc()
        # if exit:
        #     sys.exit(-1)

    def do_trace(self, sn, ver, pid, pkg, queue=None):
        """
        Start the trace
        """
        try:
            self.run_command(sn=sn, ver=ver, pid_name=pid, pkg_name=pkg, queue=queue)
        except Exception as err:
            self.panic(err, exc=True)

    def stop_trace(self):
        """
        Stop the trace
        """
        if self.ctxt is None:
            print "ctxt none"
            return
        if self.ctxt.sess is None:
            print "ctxt.sess none"
            return

        self.ctxt.sess.stop()

        self.ctxt.flush_logs()

