#!/usr/bin/python

import os

STUB_FILE_NAME = "MethodHookerStubs"

TEMP_STUB_CLASS_WRAPPER = """package com.swift.sandhook.xposedcompat.hookstub;

import static com.swift.sandhook.xposedcompat.hookstub.HookStubManager.hookBridge;
import static com.swift.sandhook.xposedcompat.hookstub.HookStubManager.getMethodId;
import static com.swift.sandhook.xposedcompat.hookstub.HookStubManager.originMethods;
import static com.swift.sandhook.xposedcompat.utils.DexLog.printCallOriginError;

/**
*   this file is auto gen by genhookstubs.py
*   it is for sandhook internal hooker & backup methods
**/
public class MethodHookerStubs%d {
%s
}
"""

TEMP_STUB_HOOK_METHOD_NAME = """stub_hook_%d"""
TEMP_STUB_HOOK_BACKUP_NAME = """stub_backup_%d"""
TEMP_STUB_CALL_ORIGIN_NAME = """call_origin_%d_%d"""

TEMP_STUB_GET_METHOD_ID_NAME = """getMethodId(%d, %d)"""

JAVA_TYPE_INT = "int"
JAVA_CAST_INT = "(int)"
JAVA_TYPE_LONG = "long"

TEMP_STUB_HOOK_METHOD = """
    public static %s %s(%s) throws Throwable {
        return %s hookBridge(%s, %s %s);
    }
"""

TEMP_STUB_BACKUP_METHOD = """
    public static %s %s(%s) throws Throwable {
        try {
            printCallOriginError(originMethods[%s]);
        } catch (Throwable throwable) {}
        return 0;
    }
"""

TEMP_STUB_CALL_ORIGIN_CLASS = """
    static class %s implements CallOriginCallBack {
        @Override
        public long call(long... args) throws Throwable {
            return %s(%s);
        }
    }
"""

TEMP_STUB_INFO = """
    public static boolean hasStubBackup = %s;
    public static int[] stubSizes = {%s};
"""


STUB_SIZES_32 = [10,20,30,30,30,30,30,20,10,10,5,5,3]
STUB_SIZES_64 = [10,20,30,30,30,30,50,50]
HAS_BACKUP = False


def getMethodId(args, index):
    return TEMP_STUB_GET_METHOD_ID_NAME % (args, index)

def getMethodHookName(index):
    return TEMP_STUB_HOOK_METHOD_NAME % index

def getMethodBackupName(index):
    return TEMP_STUB_HOOK_BACKUP_NAME % index

def getCallOriginClassName(args, index):
    return TEMP_STUB_CALL_ORIGIN_NAME % (args, index)


def genArgsList(is64Bit, isDefine, length):
    args_list = ""
    for i in range(length):
        if (i != 0):
            args_list += ", "
        if isDefine:
            if (is64Bit):
                args_list += (JAVA_TYPE_LONG + " " + "a" + str(i))
            else:
                args_list += (JAVA_TYPE_INT + " " + "a" + str(i))
        else:
            args_list += ("a" + str(i))
    return args_list


def genArgsListForCallOriginMethod(is64Bit, length):
    arg_name = """args[%s]"""
    args_list = ""
    for i in range(length):
        if (i != 0):
            args_list += ", "
        if (is64Bit):
            args_list += arg_name % i
        else:
            args_list += (JAVA_CAST_INT + arg_name % i)
    return args_list


def genHookMethod(is64Bit, args, index):
    java_type = JAVA_TYPE_LONG if is64Bit else JAVA_TYPE_INT
    cast = "" if is64Bit else JAVA_CAST_INT
    args_list_pre = ", " if args > 0 else ""
    args_list = genArgsList(is64Bit, False, args)
    args_list_def = genArgsList(is64Bit, True, args)
    call_origin_obj = ("new " + getCallOriginClassName(args, index) + "()") if HAS_BACKUP else "null"
    method = TEMP_STUB_HOOK_METHOD % (java_type, getMethodHookName(index), args_list_def, cast, getMethodId(args, index), call_origin_obj, args_list_pre + args_list)
    return method


def genBackupMethod(is64Bit, args, index):
    java_type = JAVA_TYPE_LONG if is64Bit else JAVA_TYPE_INT
    args_list_def = genArgsList(is64Bit, True, args)
    method = TEMP_STUB_BACKUP_METHOD % (java_type, getMethodBackupName(index), args_list_def, getMethodId(args, index))
    return method

def genCallOriginClass(is64Bit, args, index):
    method = TEMP_STUB_CALL_ORIGIN_CLASS % (getCallOriginClassName(args, index), getMethodBackupName(index), genArgsListForCallOriginMethod(is64Bit, args))
    return method

def genStubInfo32():
    hasStub = "true" if HAS_BACKUP else "false"
    stubSizes = ""
    for args in range(len(STUB_SIZES_32)):
        if (args != 0):
            stubSizes += ", "
        stubSizes += str(STUB_SIZES_32[args])
    return TEMP_STUB_INFO % (hasStub, stubSizes)

def genStubInfo64():
    hasStub = "true" if HAS_BACKUP else "false"
    stubSizes = ""
    for args in range(len(STUB_SIZES_64)):
        if (args != 0):
            stubSizes += ", "
        stubSizes += str(STUB_SIZES_64[args])
    return TEMP_STUB_INFO % (hasStub, stubSizes)    

def gen32Stub(packageDir):
    class_content = genStubInfo32()
    class_name = STUB_FILE_NAME + "32"
    for args in range(len(STUB_SIZES_32)):
        for index in range(STUB_SIZES_32[args]):
            class_content += """\n\n\t//stub of arg size %d, index %d""" % (args, index)
            class_content += genHookMethod(False, args, index)
            if HAS_BACKUP:
                class_content += "\n"
                class_content += genCallOriginClass(False, args, index)
                class_content += "\n"
                class_content += genBackupMethod(False, args, index)
                class_content += "\n"
    class_str = TEMP_STUB_CLASS_WRAPPER % (32, class_content)
    javaFile = open(os.path.join(packageDir, class_name + ".java"), "w")
    javaFile.write(class_str)
    javaFile.close()


def gen64Stub(packageDir):
    class_content = genStubInfo64()
    class_name = STUB_FILE_NAME + "64"
    for args in range(len(STUB_SIZES_64)):
        for index in range(STUB_SIZES_64[args]):
            class_content += """\n\n\t//stub of arg size %d, index %d""" % (args, index)
            class_content += genHookMethod(True, args, index)
            if HAS_BACKUP:
                class_content += "\n"
                class_content += genCallOriginClass(True, args, index)
                class_content += "\n"
                class_content += genBackupMethod(True, args, index)
                class_content += "\n"
    class_str = TEMP_STUB_CLASS_WRAPPER % (64, class_content)
    javaFile = open(os.path.join(packageDir, class_name + ".java"), "w")
    javaFile.write(class_str)
    javaFile.close()


def genStub(packageDir):
    for fileName in os.listdir(packageDir):
        if fileName.startswith(STUB_FILE_NAME):
            os.remove(os.path.join(packageDir, fileName))
    gen32Stub(packageDir)
    gen64Stub(packageDir)


if __name__ == "__main__":
    genStub(os.path.join(os.path.dirname(os.path.realpath(__file__)),
                         "src/main/java/com/swift/sandhook/xposedcompat/hookstub"))
