import json
import os
import random
import re
import shutil
from base64 import b64encode, b64decode

import cv2
import numpy as np
import regex as re2
# from Cryptodome.Cipher import AES
# from Cryptodome.Random import get_random_bytes
# import chardet
import string
import random


def detect_encoding(file_path):
    with open(file_path, 'rb') as f:
        raw_data = f.read(1024)
        result = chardet.detect(raw_data)
    return result['encoding']


def get_classes_impl(result, class_type, path='.'):
    print("get_classes_impl start")
    listdir = os.listdir(path)
    for i in listdir:
        print(i)
        path_join = os.path.join(path, i)
        if os.path.isdir(path_join):
            if path.endswith('.') and i in ["build", "debug", "release"]:
                continue
            if path.endswith('src') and i in ["androidTest", "test"]:
                continue
            get_classes_impl(result, class_type, path_join)
        elif os.path.isfile(path_join):
            if os.path.splitext(i)[-1][1:] in class_type:
                result.append(path_join)
                print(result)
    print("get_classes_impl over")


def get_classes(class_type=None):
    print("get_classes start")
    if class_type is None:
        class_type = ["kt", "java"]
    result = []
    get_classes_impl(result, class_type)
    print(result)
    print("get_classes over")
    return result


def find_text_impl(reg, result, skip_name, path, is_one):
    listdir = os.listdir(path)
    for i in listdir:
        if is_one and len(result) > 0:
            return
        path_join = os.path.join(path, i)
        if os.path.isdir(path_join):
            if path.endswith('.') and i in ["build", "debug", "release"]:
                continue
            if path.endswith('src') and i in ["androidTest", "test"]:
                continue
            find_text_impl(reg, result, skip_name, path_join, is_one)
        elif os.path.isfile(path_join):
            if path.endswith('.') and i.split('.')[-1] in ['py']:
                continue
            is_skip = False
            for j in skip_name:
                if j in i:
                    is_skip = True
                    break
            if is_skip:
                continue
            with open(path_join, 'r', encoding='utf-8') as f:
                if not f.readable():
                    continue
                try:
                    text = f.read()
                except ValueError:
                    continue
                findall = re.findall(reg, text)
                if len(findall) > 0:
                    result.extend(findall)


def find_text(reg, path='.', is_one=False, skip_name=None):
    if skip_name is None:
        skip_name = []
    result = []
    if os.path.isdir(path):
        find_text_impl(reg, result, skip_name, path, is_one)
    else:
        with open(path, 'r', encoding='utf-8') as f:
            if f.readable():
                try:
                    text = f.read()
                    findall = re.findall(reg, text)
                    if len(findall) > 0:
                        result.extend(findall)
                except ValueError:
                    pass
    if is_one:
        if len(result) > 0:
            return result[0]
        else:
            return None
    return result


def find_file_impl(reg, result, skip_name, path='.'):
    listdir = os.listdir(path)
    for i in listdir:
        if len(result) > 0:
            return
        path_join = os.path.join(path, i)
        if os.path.isdir(path_join):
            if path.endswith('.') and i in ["build", "debug", "release"]:
                continue
            if path.endswith('src') and i in ["androidTest", "test"]:
                continue
            find_file_impl(reg, result, skip_name, path_join)
        elif os.path.isfile(path_join):
            if path.endswith('.') and i not in ["pro"]:
                continue
            is_skip = False
            for j in skip_name:
                if j in i:
                    is_skip = True
                    break
            if is_skip:
                continue
            with open(path_join, 'r', encoding='utf-8') as f:
                if not f.readable():
                    continue
                try:
                    text = f.read()
                except ValueError:
                    continue
                findall = re.findall(reg, text)
                if len(findall) > 0:
                    result.append(path_join)


def find_file(reg, path='.', skip_name=None):
    print(find_file)
    if skip_name is None:
        skip_name = []
    result = []
    if os.path.isdir(path):
        find_file_impl(reg, result, skip_name, path)
    else:
        with open(path, 'r', encoding='utf-8') as f:
            if f.readable():
                try:
                    text = f.read()
                    findall = re.findall(reg, text)
                    if len(findall) > 0:
                        result.append(path)
                except ValueError:
                    pass
    return result[0] if len(result) > 0 else ""


def get_class_path(path):
    with open(path, 'r') as f:
        text = f.read()
        package = re.search('package (.*?)\\s', text).group(1)
        name = os.path.basename(path)
        name = name[:name.rfind('.')]
        return package + '.' + name


def replace_map_text(mapping, path='.'):
    listdir = os.listdir(path)
    for i in listdir:
        path_join = os.path.join(path, i)
        if os.path.isdir(path_join):
            if path.endswith('.') and i in ["build", "debug", "release"]:
                continue
            if path.endswith('src') and i in ["androidTest", "test"]:
                continue
            replace_map_text(mapping, path_join)
        elif os.path.isfile(path_join):
            if path.endswith('.') and os.path.splitext(i)[-1][1:] not in ["pro"]:
                continue
            with open(path_join, 'r', encoding='utf-8') as f:
                if not f.readable():
                    continue
                try:
                    text = f.read()
                except ValueError:
                    continue
            with open(path_join, 'w', encoding='utf-8') as f:
                reverse = sorted(list(mapping.keys()), key=len, reverse=True)
                for name in reverse:
                    # text = text.replace(str(name), mapping[name])
                    text_result = re.sub(rf'\b{name}\b', mapping[name], text_result)
                f.write(text)


# 修复某些字符无法匹配的问题，如android:OEM
def sub_map_text(mapping, is_reverse=True, path='.', skip_type=None, skip_name=None):
    print("sub_map_text start")
    if len(mapping) == 0:
        return []
    if skip_type is None:
        skip_type = []
    if skip_name is None:
        skip_name = []
    skip_name.append('databinding')  # 跳过 databinding
    result = []
    sub_map_text_impl(mapping, result, skip_type, skip_name, is_reverse, path)
    print(result)
    print("sub_map_text over")
    return result


# def sub_map_text_impl(mapping, result, skip_type, skip_name, is_reverse=True, path='.'):
#     print("sub_map_text_impl start")
#     listdir = os.listdir(path)
#     for i in listdir:
#         path_join = os.path.join(path, i)
#         if os.path.isdir(path_join):
#             if path.endswith('.') and i in ["build", "debug", "release"]:
#                 continue
#             if path.endswith('src') and i in ["androidTest", "test"]:
#                 continue
#             sub_map_text_impl(mapping, result, skip_type, skip_name, is_reverse, path_join)
#         elif os.path.isfile(path_join):
#             if path.endswith('.') and os.path.splitext(i)[-1][1:] not in ["pro"]:
#                 continue
#             if os.path.splitext(i)[-1][1:] in skip_type:
#                 continue
#             is_skip = False
#             for j in skip_name:
#                 if j in i:
#                     print(j)
#                     is_skip = True
#                     break
#             if is_skip:
#                 continue
#             with open(path_join, 'r', encoding='utf-8') as f:
#                 if not f.readable():
#                     continue
#                 try:
#                     text = f.read()
#                 except ValueError:
#                     continue
#             with open(path_join, 'w', encoding='utf-8') as f:
#                 reverse = sorted(list(mapping.keys()), key=len, reverse=is_reverse)
#                 text_result = text
#                 for name in reverse:
#                     text_result = re2.sub(str(name), mapping[name], text_result)
#                     print(text_result)
#                 if text_result != text:
#                     result.append(path_join)
#                 print(result)
#                 f.write(text_result)
#                 print("sub_map_text_impl over")
# 修复datbinding问题
def sub_map_text_impl(mapping, result, skip_type, skip_name, is_reverse=True, path='.'):
    print("sub_map_text_impl start")
    listdir = os.listdir(path)
    for i in listdir:
        path_join = os.path.join(path, i)
        if os.path.isdir(path_join):
            if path.endswith('.') and i in ["build", "debug", "release"]:
                continue
            if path.endswith('src') and i in ["androidTest", "test"]:
                continue
            sub_map_text_impl(mapping, result, skip_type, skip_name, is_reverse, path_join)
        elif os.path.isfile(path_join):
            if path.endswith('.') and os.path.splitext(i)[-1][1:] not in ["pro"]:
                continue
            if os.path.splitext(i)[-1][1:] in skip_type:
                continue
            is_skip = False
            for j in skip_name:
                if j in i:
                    print(j)
                    is_skip = True
                    break
            if is_skip:
                continue

            # 开始读取文件内容
            with open(path_join, 'r', encoding='utf-8') as f:
                if not f.readable():
                    continue
                try:
                    text = f.read()
                except ValueError:
                    continue

            # 替换逻辑
            with open(path_join, 'w', encoding='utf-8') as f:
                reverse = sorted(list(mapping.keys()), key=len, reverse=is_reverse)
                text_result = text

                # 遍历映射规则替换
                for name in reverse:
                    text_result = re2.sub(str(name), mapping[name], text_result)

                # 方案 3：针对特定 import 的直接替换
                text_result = re.sub(
                    r'import varietyed\.scratched\.assemblyed\.(R|databinding\.\w+)',
                    r'import com.pots.qasdf.phonemanager.\1',
                    text_result
                )

                # 检查替换结果
                if text_result != text:
                    result.append(path_join)

                f.write(text_result)
    print("sub_map_text_impl over")


def replace_text(old, new, path='.'):
    print("replace_text")
    listdir = os.listdir(path)
    for i in listdir:
        path_join = os.path.join(path, i)
        if os.path.isdir(path_join):
            if path.endswith('.') and i in ["build", "debug", "release"]:
                continue
            if path.endswith('src') and i in ["androidTest", "test"]:
                continue
            replace_text(old, new, path_join)
        elif os.path.isfile(path_join):
            if path.endswith('.') and os.path.splitext(i)[-1][1:] not in ["pro"]:
                continue
            with open(path_join, 'r', encoding='utf-8') as f:
                if not f.readable():
                    continue
                try:
                    text = f.read()
                except ValueError:
                    continue
            with open(path_join, 'w', encoding='utf-8') as f:
                f.write(text.replace(old, new))
                print(text)


def get_random_package():
    packages = [get_random_string(4) for _ in range(get_random_int(1, 3))]
    return '.'.join(packages)


def check_mapping():
    print("check_mapping start")
    try:
        loads = json.load(open('mapping.json', 'r', encoding='utf-8'))
    except (ValueError, IOError):
        loads = {}
    old_mapping = loads.get('dir', {})

    packages = find_text('package (.*?)[;]?\\s', 'src')
    packages.append(applicationId)
    packages = sorted(set(packages), key=packages.index)
    packages.sort(key=len)
    print(packages)

    mapping = {}
    for i in packages:
        # 解决databingding包被混淆
        if 'databinding' in i:  # 跳过 databinding 包
            mapping[i] = i
            continue
        if applicationId not in i:
            continue
        value = old_mapping.get(i)
        if value is not None:
            mapping[i] = value
            continue
        mapping[i] = get_random_package()

        value = mapping.get(i[:i.rfind('.')])
        if value is not None:
            mapping[i] = value + '.' + mapping[i]
    loads["dir"] = mapping
    json.dump(loads, open('mapping.json', 'w', encoding='utf-8'), indent=4)
    print(json)
    print("check_mapping over")
    return mapping


def check_class_mapping():
    print("check_class_mapping start")
    try:
        loads = json.load(open('mapping.json', 'r', encoding='utf-8'))
        print(loads)
    except (ValueError, IOError):
        loads = {}
    old_mapping = loads.get('class', {})

    mapping = {}
    classes = get_classes()
    for i in classes:
        if '_D' in i or os.path.basename(i) == "GlobalConfig.kt":  # 跳过 GlobalConfig.kt
            continue
        print(i)
        name = os.path.basename(i)
        name = name[:name.rfind('.')]
        value = old_mapping.get(name)
        print(value)
        if value is not None:
            mapping[name] = value
            continue
        mapping[name] = get_title(get_random_string(4), True)
        print(mapping[name])
        # method_variable_mapping = generate_method_variable_mapping(i)  # 生成方法名和变量名的混淆映射
        # for old, new in method_variable_mapping.items():
        #     mapping[old] = new  # 添加方法名和变量名的混淆映射
    loads["class"] = mapping
    json.dump(loads, open('mapping.json', 'w', encoding='utf-8'), indent=4)
    print(json)
    print("check_class_mapping over")
    return mapping


# def generate_method_variable_mapping(class_file):
#     # 生成方法名和变量名的混淆映射
#     mapping = {}
#
#     with open(class_file, 'r', encoding='utf-8') as f:
#         text = f.read()
#
#     methods = re.findall(
#         r'\b(public|private|protected|static|final|abstract|synchronized)?\s*(<[^>]+>\s*)?(\w+)\s+(\w+)\s*\(',
#         text
#     )
#     variables = re.findall(
#         r'\b(public|private|protected|static|final|transient|volatile)?\s*(<[^>]+>\s*)?(\w+)\s+(\w+)\s*[;=]',
#         text
#     )
#
#     for method in methods:
#         old_name = method[3]
#         new_name = get_random_string(6)
#         mapping[old_name] = new_name
#
#     for variable in variables:
#         old_name = variable[3]
#         new_name = get_random_string(6)
#         mapping[old_name] = new_name
#
#     return mapping

def check_type_ids_mapping(ids_type):
    try:
        loads = json.load(open('mapping.json', 'r', encoding='utf-8'))
    except (ValueError, IOError):
        loads = {}
    old_mapping = loads.get(ids_type + "_ids", {})

    mapping = {}
    type_ids = find_text('<' + ids_type + ' name="(.*?)".*?>', get_path('.\\src\\main\\res'))
    for i in type_ids:
        if i.endswith('_D'):
            continue
        value = old_mapping.get(i)
        if value is not None:
            mapping[i] = value
            continue
        mapping[i] = get_random_string(5, False)
    type_ids = find_text('<item name="(.*?)".*? type="' + ids_type + '">', get_path('.\\src\\main\\res'))
    for i in type_ids:
        value = old_mapping.get(i)
        if value is not None:
            mapping[i] = value
            continue
        mapping[i] = get_random_string(5, False)
    if len(mapping) != 0:
        loads[ids_type + "_ids"] = mapping
        json.dump(loads, open('mapping.json', 'w', encoding='utf-8'), indent=4)
    return mapping


def check_styleable_mapping():
    try:
        loads = json.load(open('mapping.json', 'r', encoding='utf-8'))
    except (ValueError, IOError):
        loads = {}
    old_mapping = loads.get("styleable", {})

    mapping = {}
    styleables = find_text('<declare-styleable name=".*?">[\\s\\S]*?</declare-styleable>')
    for i in styleables:
        styleable_id = re.findall('<declare-styleable name="(.*?)">', i)[0]
        mapping[styleable_id] = old_mapping.get(styleable_id, get_random_string(5, False))
        # styleables_attr = re.findall('<attr name="(.*?)".*?/>', i)
        styleables_attr = re.findall('<attr name="(.*?)".*?>', i)  # 包括单行和多行定义
        for attr in styleables_attr:
            mapping[attr + '_A'] = old_mapping.get(attr + '_A', get_random_string(5))
    if len(mapping) != 0:
        loads["styleable"] = mapping
        json.dump(loads, open('mapping.json', 'w', encoding='utf-8'), indent=4)
    return mapping


def deal_ids_type(ids_type):
    type_ids_mapping = check_type_ids_mapping(ids_type)
    if len(type_ids_mapping) == 0:
        return
    print(type_ids_mapping)

    sub_map = {}
    for i in type_ids_mapping:
        sub_map['<' + ids_type + ' name="' + i + '"'] = '<' + ids_type + ' name="' + type_ids_mapping[i] + '"'
        sub_map['<item name="' + i + '"(.*? type="' + ids_type + '")>'] = \
            '<item name="' + type_ids_mapping[i] + '"\\g<1>>'
        sub_map['(?<=[^\\.])R\\.' + ids_type + '\\.' + i + '(?=\\W)'] = 'R.' + ids_type + '.' + type_ids_mapping[i]
        sub_map['(?<=>|")@' + ids_type + '/' + i + '(?=<|")'] = '@' + ids_type + '/' + type_ids_mapping[i]
        sub_map['<' + ids_type + '(.*?) parent="' + i + '"(.*?)>'] = \
            '<' + ids_type + '\\g<1> parent="' + type_ids_mapping[i] + '"\\g<2>>'
    sub_map_text(sub_map)


def check_view_ids_mapping():
    try:
        loads = json.load(open('mapping.json', 'r', encoding='utf-8'))
    except (ValueError, IOError):
        loads = {}
    mapping = loads.get('view_ids', {})

    view_ids = find_text('"\\@\\+id/(.*?)"', get_path('.\\src\\main\\res'))
    for i in view_ids:
        if i == 'root':
            continue
        key = get_title(i)
        if mapping.get(key) is not None:
            continue
        mapping[key] = get_random_string(7)
    loads["view_ids"] = mapping
    json.dump(loads, open('mapping.json', 'w', encoding='utf-8'), indent=4)
    return mapping


def check_layout_mapping():
    try:
        loads = json.load(open('mapping.json', 'r', encoding='utf-8'))
    except (ValueError, IOError):
        loads = {}
    mapping = loads.get('layout', {})

    layout_path = get_path('.\\src\\main\\res\\layout')
    listdir = os.listdir(layout_path)
    for i in listdir:
        name = i[:i.rfind('.')]
        if mapping.get(name) is not None:
            continue
        mapping[name] = get_random_string(6)
    loads["layout"] = mapping
    json.dump(loads, open('mapping.json', 'w', encoding='utf-8'), indent=4)
    return mapping


def check_res_mapping(res_type):
    try:
        loads = json.load(open('mapping.json', 'r', encoding='utf-8'))
    except (ValueError, IOError):
        loads = {}
    mapping = loads.get(res_type, {})

    res_path = get_path('.\\src\\main\\res')
    listdir = os.listdir(res_path)
    for i in listdir:
        if not i.startswith(res_type):
            continue
        for j in os.listdir(os.path.join(res_path, i)):
            if j.startswith('book_'):
                continue
            if j.endswith('.9.png'):
                name = j[:-len('.9.png')]
            else:
                name = j.rsplit('.', 1)[0]
            if mapping.get(name) is not None:
                continue
            mapping[name] = normalize_filename(get_random_string(8))
    if len(mapping) != 0:
        loads[res_type] = mapping
        json.dump(loads, open('mapping.json', 'w', encoding='utf-8'), indent=4)
    return mapping


used_words = set()


def get_random_string(length=8, is_ascii=True):
    global used_words  # 使用全局变量 used_words
    words = load_words_from_file('word_file.json')
    if words:
        while words:
            random_word = random.choice(words)
            if random_word not in used_words:
                used_words.add(random_word)
                if is_ascii and all(ord(c) < 128 for c in random_word):
                    return random_word
                elif not is_ascii:
                    return random_word
        return None


def load_words_from_file(file_path):
    try:
        with open(file_path, 'r', encoding='utf-8') as f:
            data = json.load(f)
            return data.get("word", [])  # 返回单词列表
    except FileNotFoundError:
        print(f"Error: Word file not found at: {file_path}")
        return []
    except json.JSONDecodeError:
        print(f"Error: Invalid JSON format in: {file_path}")
        return []


def get_random_int(a=0, b=10000):
    return random.randint(a, b)


def get_dictionary_string(length=10):
    random_str = random.choice(string.ascii_letters)
    random_str += ''.join(random.choice(string.ascii_letters + string.digits) for _ in range(length - 1))
    return random_str


def check_obfuscation_dictionary():
    if not os.path.exists('dictionary.txt'):
        dictionary = set()
        while len(dictionary) != 5000:
            dictionary.add(get_dictionary_string())
        with open('dictionary.txt', 'w', encoding='utf-8') as f:
            f.write('\n'.join(dictionary))
    with open('proguard-rules.pro', 'r', encoding='utf-8') as f:
        text = f.read()
    if '-obfuscationdictionary' in text:
        return
    text += '\n-obfuscationdictionary dictionary.txt'
    text += '\n-classobfuscationdictionary dictionary.txt'
    text += '\n-packageobfuscationdictionary dictionary.txt'
    with open('proguard-rules.pro', 'w', encoding='utf-8') as f:
        f.write(text)


def mkdir(dir_path):
    try:
        os.makedirs(dir_path)
    except OSError:
        pass


def replace_click_method():
    print("replace_click_method start")
    try:
        path = get_path('.\\src\\main\\java')
        file = find_file('fun View.setTrackedOnClickListener', path)
        if file:
            class_path = get_class_path(file) + '.setTrackedOnClickListener'
            # java类不能直接使用扩展函数
            sub_map_text({
                '([.@])setOnClickListener': '\\g<1>setTrackedOnClickListener',
            }, path=path, skip_type=['java'])
            add_import(class_path, get_classes(['kt']))
            print("replace_click_method over")
    except IndexError:
        return


def replace_package_in_imports(root_path, old_package, new_package):
    # 匹配旧的 import 包名部分的正则表达式
    patterns = [
        (re.compile(rf'import {re.escape(old_package)}(\.databinding\.\w+)'), rf'import {new_package}\1'),
        (re.compile(rf'import {re.escape(old_package)}\.R\b'), rf'import {new_package}.R'),
        (re.compile(rf'import {re.escape(old_package)}\.BuildConfig'), rf'import {new_package}.BuildConfig')
    ]

    # 遍历目录中的所有文件
    for dirpath, _, filenames in os.walk(root_path):
        for filename in filenames:
            # 只处理 .kt 和 .java 文件
            if filename.endswith(('.kt', '.java')):
                file_path = os.path.join(dirpath, filename)

                with open(file_path, 'r', encoding='utf-8') as file:
                    content = file.read()

                # 替换匹配到的旧包名
                updated_content = content
                for pattern, replacement in patterns:
                    updated_content = pattern.sub(replacement, updated_content)

                # 如果内容发生了改变，写回文件
                if content != updated_content:
                    with open(file_path, 'w', encoding='utf-8') as file:
                        file.write(updated_content)
                    print(f"已更新文件: {file_path}")


# def get_file_names_without_extension(root_path):
#     # 遍历 root_path 下的所有 .kt 和 .java 文件
#     all_files = []
#     for dirpath, _, filenames in os.walk(root_path):
#         for filename in filenames:
#             if filename.endswith(('.kt', '.java')):
#                 all_files.append(os.path.join(dirpath, filename))
#
#     return all_files


# def get_class_name(text, filename):
#     """
#     获取文件中定义的类名（适用于 Java 和 Kotlin 文件）
#     :param text: 文件内容
#     :param filename: 文件名
#     :return: 类名列表
#     """
#     if filename.endswith('.kt'):
#         class_pattern = r'\bclass\s+(\w+)'
#     elif filename.endswith('.java'):
#         class_pattern = r'\bclass\s+(\w+)'
#     else:
#         return []
#
#     # 获取类名
#     return re.findall(class_pattern, text)



def get_method_names(text, filename):
    """
    获取 Java 或 Kotlin 文件中的所有方法名
    :param text: 文件内容
    :param filename: 文件名
    :return: 方法名列表
    """
    if filename.endswith('.kt'):
        # Kotlin 方法名：匹配包括访问修饰符（private/public）和 fun 关键字，不排除 override
        method_pattern = r'\b(?:private|protected|public|internal|inline|suspend|)?\s*fun\s+(\w+)\s*\((.*?)\)\s*(?::\s*([\w<>,\s?*]*))?\s*{'
    elif filename.endswith('.java'):
        # Java 方法名：包括常见的修饰符和方法名
        method_pattern = r'\b(?:public|private|protected|static|final|void|int|boolean|float|long|double|char|short|byte|void|[\w<>]+)\s+(\w+)\s*\(.*\)\s*\{'
    else:
        return []

    # 匹配所有方法名
    return re.findall(method_pattern, text)

def get_variable_names(text, filename):
    """
    获取 Java 或 Kotlin 文件中的所有变量名
    :param text: 文件内容
    :param filename: 文件名
    :return: 变量名列表
    """
    if filename.endswith('.kt'):
        # Kotlin 变量名：支持 `val` 和 `var` 关键字的变量声明
        variable_pattern = r'\b(?:val|var)\s+(\w+)\s*(?=[;=])'
    elif filename.endswith('.java'):
        # Java 变量名：包括常见的类型声明
        variable_pattern = r'\b(?:int|boolean|float|long|double|char|short|byte|String|[\w<>]+)\s+(\w+)\s*(?=[;=])'
    else:
        return []

    # 匹配所有变量名
    return re.findall(variable_pattern, text)

def get_file_names_without_extension(root_path):
    """
    获取所有 `.kt` 和 `.java` 文件的路径（无扩展名）
    :param root_path: 根目录路径
    :return: 所有文件的路径列表
    """
    all_files = []
    for dirpath, _, filenames in os.walk(root_path):
        for filename in filenames:
            if filename.endswith(('.kt', '.java')):
                all_files.append(os.path.join(dirpath, filename))
    return all_files


# 存储每个文件名的映射关系
file_to_class_map = {}

def get_constant_names(text, filename):
    """
    获取 Java 或 Kotlin 文件中的所有常量名
    :param text: 文件内容
    :param filename: 文件名
    :return: 常量名列表
    """
    if filename.endswith('.kt'):
        # Kotlin 常量名：匹配以 `const val` 声明的常量
        constant_pattern = r'const\s+val\s+(\w+)\s*=\s*.*'
    elif filename.endswith('.java'):
        # Java 常量名：匹配以 `static final` 声明的常量
        constant_pattern = r'public\s+static\s+final\s+\w+\s+(\w+)\s*=\s*.*'
    else:
        return []

    # 匹配所有常量名
    return re.findall(constant_pattern, text)


def obfuscate_code_in_files(all_files):
    """
    遍历所有文件，混淆其中的方法名、变量名和常量名，并返回混淆名称的映射。
    :param all_files: 所有文件的路径列表。
    :return: 一个字典，键为原始名称，值为混淆后的名称。
    """
    name_mapping = {}  # 用于存储原始名称和混淆名称的映射
    constant_mapping = {}  # 用于存储常量的映射

    for file_path in all_files:
        current_file_name = os.path.basename(file_path).split('.')[0]  # 获取文件名
        # 读取文件内容
        with open(file_path, 'r', encoding='utf-8') as f:
            lines = f.readlines()

        # 获取文件中的常量名
        constants = get_constant_names("".join(lines), file_path)

        # 为常量名生成混淆名称
        for constant in constants:
            if constant not in name_mapping:
                name_mapping[constant] = get_random_string()
            constant_mapping[constant] = name_mapping[constant]

        # 获取类名（用于判断常量是否在类内引用）
        new_lines = []
        for line in lines:
            if line.startswith("import "):  # 跳过 import 行
                new_lines.append(line)
            else:
                # 替换常量名：进行统一的替换，不区分引用的类型
                for constant, obfuscated_name in constant_mapping.items():
                    # 直接替换常量，无论是在类内还是外部引用
                    line = re.sub(r'\b' + re.escape(constant) + r'\b', obfuscated_name, line)

                # 替换方法名和变量名
                for original_name, obfuscated_name in name_mapping.items():
                    line = re.sub(r'\b' + re.escape(original_name) + r'\b', obfuscated_name, line)

                new_lines.append(line)

        # 写回文件
        with open(file_path, 'w', encoding='utf-8') as f:
            f.writelines(new_lines)

    return constant_mapping


def replace_with_mapped_names(all_files, name_mapping, constant_mapping):
    """
    在所有文件中替换已经混淆的名称，保证不同文件间共享混淆名称。
    :param all_files: 所有文件的路径列表。
    :param name_mapping: 名称映射字典。
    :param constant_mapping: 常量名称映射字典。
    """
    for file_path in all_files:
        # 使用在 obfuscate_code_in_files 中存储的文件名
        current_file_name = file_to_class_map.get(file_path, None)
        if not current_file_name:
            print(f"未找到文件 {file_path} 的类名映射，跳过。")
            continue

        with open(file_path, 'r', encoding='utf-8') as f:
            lines = f.readlines()

        new_lines = []
        excluded_constants = set()

        # 第一步：提取 `const val` 定义的常量名并排除混淆
        for line in lines:
            match = re.match(r'^\s*const\s+val\s+(\w+)\s*=.*$', line)
            if match:
                constant_name = match.group(1)
                excluded_constants.add(constant_name)

        for line in lines:
            if line.startswith("import "):  # 跳过 import 行
                new_lines.append(line)
                continue

            # 跳过 context 或 activity 的调用
            if 'context.' in line or 'activity.' in line:
                new_lines.append(line)
                continue

            # 替换变量和方法名
            for original_name, obfuscated_name in name_mapping.items():
                if isinstance(original_name, tuple):
                    original_name = ''.join(original_name)

                # 如果是 `excluded_constants` 中的常量名，跳过替换
                if original_name in excluded_constants:
                    continue

                # 替换代码中的变量或方法名
                line = re.sub(r'\b' + re.escape(original_name) + r'\b', obfuscated_name, line)

            new_lines.append(line)

        with open(file_path, 'w', encoding='utf-8') as f:
            f.writelines(new_lines)

        print(f"文件 {file_path} 引用的名称已进行统一混淆处理。")





def deal_code():
    print("deal_code start")
    # 替换点击事件
    replace_click_method()

    # 生成包映射文件
    mapping = check_mapping()
    packages = list(mapping.keys())
    print("mapping: " + json.dumps(mapping, indent=4))

    # 移动文件
    root_path = get_path('.\\src\\main\\java')
    for key in packages:
        key = str(key)
        print(key)
        old_path = get_path(root_path + '\\' + key.replace('.', '\\'))
        new_path = get_path(root_path + '\\' + mapping[key].replace('.', '\\'))
        print("mapping[key]" + mapping[key])
        if not os.path.exists(old_path):
            mkdir(old_path)

        if not os.path.exists(new_path):
            mkdir(new_path)

        listdir = os.listdir(old_path)
        for i in listdir:
            path_join = os.path.join(old_path, i)
            if os.path.isdir(path_join):
                continue
            shutil.move(path_join, os.path.join(new_path, i))
    shutil.rmtree(get_path(root_path + '\\' + applicationId[:applicationId.find('.')]))

    # 修改manifest
    with open(get_path('.\\src\\main\\AndroidManifest.xml'), 'r', encoding='utf-8') as f:
        text = f.read()

    findall = re.findall('android:name=(".*?")', text)
    for i in findall:
        if i.startswith('".'):
            text = text.replace(i, '"' + applicationId + i[1:-1] + '"')
            print(text)
            print(applicationId)

    with open(get_path('.\\src\\main\\AndroidManifest.xml'), 'w', encoding='utf-8') as f:
        f.write(text)




    # 修改依赖
    sub_map = {}
    for i in mapping:
        sub_map['(?<=\\W)' + i + '(?=\\W)'] = mapping[i]
        print(sub_map['(?<=\\W)' + i + '(?=\\W)'])
        print(mapping[i])

    # 过滤修改
    sub_map['(?<=\\W)' + mapping[applicationId] + '.R(?=\\W)'] = applicationId + '.R'
    sub_map['(?<=\\W)' + mapping[applicationId] + '.databinding(?=\\W)'] = applicationId + '.databinding'
    sub_map['(?<=\\W)' + mapping[applicationId] + '.BuildConfig(?=\\W)'] = applicationId + '.BuildConfig'
    sub_map_text(sub_map)

    # 根包名下 R 单独处理
    new_path = get_path(root_path + '\\' + mapping[packages[0]].replace('.', '\\'))
    listdir = os.listdir(new_path)
    result_path = []
    for i in listdir:
        if os.path.isdir(os.path.join(new_path, i)):
            continue
        result_path.append(os.path.join(new_path, i))
    add_import(applicationId + '.R', result_path)
    add_import(applicationId + '.BuildConfig', result_path)

    # 类名
    print("类名 start")
    class_mapping = check_class_mapping()
    classes = get_classes()
    print(class_mapping)
    for i in classes:
        if '_D' in i or os.path.basename(i) == "GlobalConfig.kt":  # 跳过 GlobalConfig.kt
            continue
        name = os.path.basename(i)
        name = name[:name.rfind('.')]
        shutil.move(i, i.replace(name, class_mapping[name]))

    sub_map = {}
    for i in class_mapping:
        # 原规则匹配整个单词
        # 添加负向环视以排除前后是引号的情况,解决匹配attrs的问题，
        # 添加排除前缀 "styleable." 的逻辑
        # 原规则匹配整个单词，添加排除前缀 "styleable." 的逻辑，并允许后面有引号的匹配
        regex_pattern = r'(?<!")(?<!\bstyleable\.)\b' + i + r'\b'

        # 保持原有规则的匹配，优先处理非引号的情况
        sub_map[regex_pattern] = class_mapping[i]
        print(class_mapping[i])
    sub_map_text(sub_map)
    print("类名 over")


    # 混淆字典
    check_obfuscation_dictionary()
    print(root_path)
    print(mapping[applicationId])
    print(applicationId)
    # 获取混淆名称映射
    # all_files = get_file_names_without_extension(root_path)
    # name_mapping = obfuscate_code_in_files(all_files)
    #
    # # 获取常量映射
    # constant_mapping = {}  # 获取常量的映射
    # for file_path in all_files:
    #     with open(file_path, 'r', encoding='utf-8') as f:
    #         lines = f.readlines()
    #         constants = get_constant_names("".join(lines), file_path)
    #         for constant in constants:
    #             if constant not in name_mapping:
    #                 name_mapping[constant] = get_random_string()
    #             constant_mapping[constant] = name_mapping[constant]
    #
    # # 第一步：混淆代码，并获取所有混淆的名称映射
    # name_mapping = obfuscate_code_in_files(all_files)
    #
    # # 第二步：替换其他文件中的引用，保证同样的混淆名
    # replace_with_mapped_names(all_files, name_mapping, constant_mapping)


    replace_package_in_imports(root_path, mapping[applicationId], applicationId)


def deal_res_type(res_type):
    type_mapping = check_res_mapping(res_type)
    if len(type_mapping) == 0:
        return
    print(type_mapping)

    res_path = get_path('.\\src\\main\\res')
    listdir = os.listdir(res_path)
    for i in listdir:
        if not i.startswith(res_type):
            continue
        path_join = os.path.join(res_path, i)
        for j in os.listdir(path_join):
            if j.startswith('book_'):
                continue
            if j.endswith('.9.png'):
                name = j[:-len('.9.png')]
            else:
                name = j.rsplit('.', 1)[0]
            path = os.path.join(path_join, j)
            if '.DS_Store' not in path:
                # shutil.move(path, path.replace(name, type_mapping[name]))
                # 仅替换文件名部分
                dir_path, file_name = os.path.split(path)
                new_file_name = file_name.replace(name, type_mapping[name])
                new_path = os.path.join(dir_path, new_file_name)

                # 打印路径调试信息
                print(f"源路径: {path}")
                print(f"目标路径: {new_path}")

                # 移动文件
                shutil.move(path, new_path)

    sub_map = {}
    for i in type_mapping:
        sub_map['R\\.' + res_type + '\\.' + i + '(\\W)'] = 'R.' + res_type + '.' + type_mapping[i] + '\\g<1>'
        sub_map['(>|")@' + res_type + '/' + i + '(<|")'] = '\\g<1>@' + res_type + '/' + type_mapping[i] + '\\g<2>'
    sub_map_text(sub_map)


# 解决mipmip命名中含有大写字母造成的报错
# 'E' is not a valid file-based resource name character: File-based resource names must contain only lowercase a-z, 0-9, or underscore
def generate_random_letter():
    # 生成一个随机小写字母
    return random.choice(string.ascii_lowercase)


def normalize_filename(name):
    # 将名称转为小写
    normalized_name = name.lower()

    # 替换所有非法字符为随机字母
    normalized_name = re.sub(r'[^a-z]', lambda match: generate_random_letter(), normalized_name)

    # 确保名称以字母开头，如果不是，则以随机字母开头
    if not normalized_name[0].isalpha():
        normalized_name = generate_random_letter() + normalized_name[1:]

    return normalized_name


def add_image_noise(path):
    try:
        image = cv2.imread(path, cv2.IMREAD_UNCHANGED)
        if path.endswith('.9.png'):
            return
        # # 添加随机噪声
        # noise = np.random.randint(0, 2, image.shape, np.uint8)
        # image = cv2.add(image, noise)
        # 添加 ±10 的随机噪声
        noise = np.random.randint(0, 2, image.shape, np.int16)  # 噪声范围 [-10, 10]
        noisy_image = np.clip(image.astype(np.int16) + noise, 0, 255).astype(np.uint8)  # 防止溢出
        #计算原始图像与添加噪声后图像的差异：如果差值的范围包含 10（噪声范围），说明噪声添加成功。
        difference = np.abs(image.astype(np.int16) - noisy_image.astype(np.int16))
        print("Difference range:", difference.min(), "-", difference.max())

        new_path = path.rsplit('.', 1)[0] + '.webp'
        cv2.imwrite(new_path, noisy_image, [cv2.IMWRITE_WEBP_QUALITY, 75])
        if new_path != path:
            os.remove(path)
    except AttributeError:
        return


def deal_image():
    res_path = get_path('.\\src\\main\\res')
    listdir = os.listdir(res_path)
    for i in listdir:
        path_join = os.path.join(res_path, i)
        if os.path.isdir(path_join):
            for j in os.listdir(path_join):
                # add_image_noise(os.path.join(path_join, j))
                if j.endswith(('.png', '.jpg', '.jpeg', '.bmp', '.tiff', '.webp')):
                    add_image_noise(os.path.join(path_join, j))


def get_title(string, is_all_upper=False):
    splits = string.split('_')
    print(splits)
    s = ''
    for i in range(len(splits)):
        if i == 0 and not is_all_upper:
            s = splits[i]
        elif len(splits[i]) > 0:
            s += splits[i][0].upper() + splits[i][1:]
    return s


def add_import(path, listfile):
    for i in listfile:
        if 'databinding' in path:  # 跳过 databinding
            continue
        with open(i, 'r', encoding='utf-8') as f:
            text = f.read()
            if len(re.findall('import ' + path, text)) != 0:
                continue
            if i.endswith('kt'):
                text = re.sub('(package \\S+?\\s+?)(?=\\S)', '\\g<1>import ' + path + '\n', text, flags=re.S)
                print(text)
            elif i.endswith('java'):
                text = re.sub('(package \\S+?\\s+?)(?=\\S)', '\\g<1>import ' + path + ';\n', text, flags=re.S)
        with open(i, 'w', encoding='utf-8') as f:
            f.write(text)


def check_class_string_mapping():
    try:
        loads = json.load(open('mapping.json', 'r', encoding='utf-8'))
    except (ValueError, IOError):
        loads = {}
    mapping = loads.get('class_string', {})

    classes = get_classes(['kt'])
    for path in classes:
        if os.path.basename(path).startswith('AESHelper'):
            continue
        with open(path, 'r', encoding='utf-8') as f:
            text = f.read()
        text = re.sub('@SuppressLint.*?\n', '\n', text)
        text = re.sub('//[^"]*?(("[^"]*?){2})*?\n', '\n', text)
        text = re.sub(' +\n', '\n', text)
        text = re.sub('=\\s+', '= ', text)
        text = re.sub(':\\s+', ': ', text)
        text = re.sub(',\\s+', ', ', text)
        text = re.sub('\n{2,}', '\n\n', text)
        result_text = text
        strings = re.findall('".*?[^\\\\]"', re.sub(r'@[^)]*', lambda x: x.group().replace('"', "'"), result_text))
        last_string = ''
        for string in strings:
            if len(string) <= 4 or string.isspace() or \
                    string[1:-1] in ['\\\\n', '\\\\r', '\\\\\'', '\\\\\\"', '\\\\?', '&amp;', 'UTF-8']:
                continue
            if string in last_string:
                continue
            start = text.find(string)
            if start == -1:
                continue
            index = start + 1
            sign_stack = ['"']
            while len(sign_stack) != 0:
                if text[index] == '\\':
                    index += 1
                elif text[index] == '"' and sign_stack[-1] == '"':
                    sign_stack.pop()
                elif text[index] == '"':
                    sign_stack.append('"')
                elif text[index] == '}' and sign_stack[-1] == '${':
                    sign_stack.pop()
                elif text[index - 1:index + 1] == '${':
                    sign_stack.append('${')
                index += 1
            string = text[start:index]
            last_string = string
            params = []
            result = string
            if '$' in string:
                count = 0
                last = -1
                res = []
                for i in range(len(string)):
                    if last == -1 and string[i] == '$':
                        if count == 0:
                            last = i
                    elif last != -1:
                        if string[i] == '{':
                            count += 1
                        elif string[i] == '}':
                            count -= 1
                            if count == 0:
                                res.append((last, i + 1))
                                last = -1
                        elif count == 0:
                            last = -1
                for i in re.findall('\\$\\w*', string):
                    if i == '$':
                        continue
                    find = string.find(i)
                    res.append((find, find + len(i)))
                res = sorted(res, key=lambda l: l[0])
                last_res = None
                for i in res:
                    if last_res is not None and i[0] < last_res[1]:
                        continue
                    params.append(string[i[0]:i[1]])
                    last_res = i
                for i in params:
                    result = result.replace(i, "[str]")
            result = result[1:-1].replace('%', '%%').replace('\\\\', '\\').replace('[str]', '%s')
            if mapping.get(result) is not None:
                continue
            mapping[result] = get_random_string(5, False) + '_D'
    loads['class_string'] = mapping
    json.dump(loads, open('mapping.json', 'w', encoding='utf-8'), indent=4)
    return mapping


def deal_class_string():
    # 获取映射
    string_mapping = check_class_string_mapping()
    print(string_mapping)

    with open(get_path('.\\src\\main\\res\\values\\strings.xml'), 'r', encoding='utf-8') as f:
        text = f.read()
    rfind = text.rfind('\n')
    text_result = text[:rfind]
    for i in string_mapping:
        string = i
        string = string.replace('&', '&amp;')
        string = string.replace('?', '\\?')
        string = string.replace('\'', '\\\'')
        text_result += '\n    <string name="' + string_mapping[i] + '" translatable="false">' + string + '</string>'
    text_result += text[rfind:]
    with open(get_path('.\\src\\main\\res\\values\\strings.xml'), 'w', encoding='utf-8') as f:
        f.write(text_result)

    # 搜索 Int.string() 路径
    file_path = find_file('fun Int.string', get_path('.\\src\\main\\java'))
    class_path = get_class_path(file_path) + '.string' if file_path else ""
    classes = get_classes(['kt'])
    for path in classes:
        # 跳过 AESHelper 类中的字符串，避免嵌套
        if os.path.basename(path).startswith('AESHelper'):
            continue
        with open(path, 'r', encoding='utf-8') as f:
            text = f.read()
        text = re.sub('@SuppressLint.*?\n', '\n', text)
        text = re.sub('//[^"]*?(("[^"]*?){2})*?\n', '\n', text)
        text = re.sub(' +\n', '\n', text)
        text = re.sub('=\\s+', '= ', text)
        text = re.sub(':\\s+', ': ', text)
        text = re.sub(',\\s+', ', ', text)
        text = re.sub('\n{2,}', '\n\n', text)
        result_text = text
        strings = re.findall('".*?[^\\\\]"', re.sub(r'@[^)]*', lambda x: x.group().replace('"', "'"), result_text))
        last_string = ''
        for string in strings:
            if len(string) <= 4 or string.isspace() or \
                    string[1:-1] in ['\\\\n', '\\\\r', '\\\\\'', '\\\\\\"', '\\\\?', '&amp;', 'UTF-8']:
                continue
            if string in last_string:
                continue
            start = text.find(string)
            if start == -1:
                continue
            index = start + 1
            sign_stack = ['"']
            while len(sign_stack) != 0:
                if text[index] == '\\':
                    index += 1
                elif text[index] == '"' and sign_stack[-1] == '"':
                    sign_stack.pop()
                elif text[index] == '"':
                    sign_stack.append('"')
                elif text[index] == '}' and sign_stack[-1] == '${':
                    sign_stack.pop()
                elif text[index - 1:index + 1] == '${':
                    sign_stack.append('${')
                index += 1
            # 得到完整字符串
            string = text[start:index]
            last_string = string
            # 参数
            params = []
            result = string
            # 处理字符串拼接
            if '$' in string:
                count = 0
                last = -1
                res = []
                for i in range(len(string)):
                    if last == -1 and string[i] == '$':
                        if count == 0:
                            last = i
                    elif last != -1:
                        if string[i] == '{':
                            count += 1
                        elif string[i] == '}':
                            count -= 1
                            if count == 0:
                                res.append((last, i + 1))
                                last = -1
                        elif count == 0:
                            last = -1
                for i in re.findall('\\$\\w*', string):
                    if i == '$':
                        continue
                    find = string.find(i)
                    res.append((find, find + len(i)))
                res = sorted(res, key=lambda l: l[0])
                last_res = None
                for i in res:
                    if last_res is not None and i[0] < last_res[1]:
                        continue
                    params.append(string[i[0]:i[1]])
                    last_res = i
                for i in params:
                    result = result.replace(i, "[str]")
                for i in range(len(params)):
                    if params[i][1] == '{':
                        params[i] = params[i][2:-1]
                    else:
                        params[i] = params[i][1:]
                    # 每个参数加上 toString
                    params[i] = '(' + params[i] + ').toString()'
            # 拼接部分替换为 %s
            result = result[1:-1].replace('%', '%%').replace('\\\\', '\\').replace('[str]', '%s')

        # 添加 Int.string() 方法引用
        if class_path:
            if len(re.findall('import ' + class_path, text)) == 0:
                result_text = re.sub('(package \\S+?\\s+?)(?=\\S)',
                                     '\\g<1>import ' + class_path + '\n', result_text, flags=re.S)

        # 添加资源 R 引用
        if len(re.findall('import ' + applicationId + '.R', text)) == 0:
            result_text = re.sub('(package \\S+?\\s+?)(?=\\S)',
                                 '\\g<1>import ' + applicationId + '.R' + '\n', result_text, flags=re.S)

        # 去除 const
        result_text = re.sub('const (val.*?R\\.string\\.\\S+?\\.string\\(\\))', '\\g<1>', result_text)
        with open(path, 'w', encoding='utf-8') as f:
            f.write(result_text)


def check_xml_string_mapping():
    try:
        loads = json.load(open('mapping.json', 'r', encoding='utf-8'))
    except (ValueError, IOError):
        loads = {}
    old_mapping = loads.get('xml_string', {})
    strings = find_text('android:text="(?<!@string/)([^@"]+?)"', get_path('.\\src\\main\\res\\layout'),
                        skip_name=['notify', 'notity'])

    mapping = {}
    for i in strings:
        value = old_mapping.get(i)
        if value is not None:
            mapping[i] = value
            continue
        # 添加 '_D' 结尾，资源混淆时过滤不用再次改名
        mapping[i] = get_random_string(5, False) + '_D'
    loads['xml_string'] = mapping
    json.dump(loads, open('mapping.json', 'w', encoding='utf-8'), indent=4)
    return mapping


def deal_xml_string():
    # 获取映射
    string_mapping = check_xml_string_mapping()
    print(string_mapping)

    with open(get_path('.\\src\\main\\res\\values\\strings.xml'), 'r', encoding='utf-8') as f:
        text = f.read()
    rfind = text.rfind('\n')
    text_result = text[:rfind]
    for i in string_mapping:
        string = i
        string = string.replace('&', '&amp;')
        string = string.replace('?', '\\?')
        string = string.replace('\'', '\\\'')
        text_result += '\n    <string name="' + string_mapping[i] + '" translatable="false">' + string + '</string>'
    text_result += text[rfind:]
    with open(get_path('.\\src\\main\\res\\values\\strings.xml'), 'w', encoding='utf-8') as f:
        f.write(text_result)

    sub_map = {}
    for i in string_mapping:
        sub_map['(?<=android:text=")' + re.escape(i) + '(?=")'] = '@string/' + re.escape(string_mapping[i])
    # 替换时跳过通知布局
    sub_map_text(sub_map, path=get_path('.\\src\\main\\res\\layout'), skip_name=['notify', 'notity'])


def deal_code_string():
    # 处理布局文件中的明文字符串
    deal_xml_string()
    # 处理代码文件中的明文字符串
    deal_class_string()


def deal_res():
    # 改 string id
    deal_ids_type('string')

    # 改 color id
    deal_ids_type('color')

    # 改 dimen id
    deal_ids_type('dimen')

    # 改 style id
    deal_ids_type('style')

    # 改 declare-styleable
    styleable_mapping = check_styleable_mapping()
    print(styleable_mapping)
    sub_map = {}
    styleables = find_text('<declare-styleable name=".*?">[\\s\\S]*?</declare-styleable>')
    for i in styleables:
        result = i
        styleable_id = re.findall('<declare-styleable name="(.*?)">', i)[0]
        result = result.replace('<declare-styleable name="' + styleable_id + '">',
                                '<declare-styleable name="' + styleable_mapping[styleable_id] + '">')
        sub_map['R\\.styleable\\.' + styleable_id + '(\\W)'] = \
            'R.styleable.' + styleable_mapping[styleable_id] + '\\g<1>'
        # styleables_attr = re.findall('<attr name="(.*?)".*?/>', i)
        styleables_attr = re.findall('<attr name="(.*?)".*?>', i)  # 包括单行和多行定义
        for attr in styleables_attr:
            result = result.replace('<attr name="' + attr + '"', '<attr name="' + styleable_mapping[attr + '_A'] + '"')
            if attr.startswith('android:'):
                print(attr)
                sub_map['R\\.styleable\\.' + styleable_id + '_' + attr.replace(":","_") + '(\\W)'] = \
            'R.styleable.' + styleable_mapping[styleable_id] + '_' + styleable_mapping[attr + '_A'] + '\\g<1>'
            else:
                sub_map['R\\.styleable\\.' + styleable_id + '_' + attr + '(\\W)'] = \
            'R.styleable.' + styleable_mapping[styleable_id] + '_' + styleable_mapping[attr + '_A'] + '\\g<1>'
            sub_map['app:' + attr + '='] = 'app:' + styleable_mapping[attr + '_A'] + '='
            sub_map['\\?attr/' + attr] = '?attr/' + styleable_mapping[attr + '_A']  # 新增的替换规则
            sub_map['<item name=\"' + attr +'\"'] = '<item name=\"' + styleable_mapping[attr + '_A'] +'\"' # 新增的替换规则

        sub_map[re.escape(i)] = result
    sub_map_text(sub_map)

    # 改 view id
    view_ids = find_text('"\\@\\+id/(.*?)"', get_path('.\\src\\main\\res'))
    view_ids_mapping = check_view_ids_mapping()
    print(view_ids_mapping)
    sub_map = {}
    for i in view_ids:
        if i == 'root':
            continue
        key = get_title(i)
        sub_map['(>|")@(\\+)?id/' + i + '(<|")'] = '\\g<1>@\\g<2>id/' + view_ids_mapping[key] + '\\g<3>'
        sub_map['R\\.id\\.' + i + '(\\W)'] = 'R.id.' + view_ids_mapping[key] + '\\g<1>'
        sub_map['([bB]inding\\??(?:\\.\\w+)?\\??)\\.' + key + '(\\W)'] = '\\g<1>.' + view_ids_mapping[key] + '\\g<2>'
        sub_map['([bB]inding\\??(?:\\.\\w+)?\\??)\\.' + key +'\\.'+ key + '(\\W)'] = '\\g<1>.' + view_ids_mapping[key] +'.' +view_ids_mapping[key] +'\\g<2>'
        sub_map[r'\(binding as (\w+Binding)\)\.' + key + r'(\W)'] = r'(binding as \1).' + view_ids_mapping[key] + r'\2'
        sub_map['(?<=app:constraint_referenced_ids=".*?)' + i + '(?=.*?")'] = view_ids_mapping[key]
    sub_map_text(sub_map)

    # 改 layout 文件名
    layout_mapping = check_layout_mapping()
    print(layout_mapping)

    layout_path = get_path('.\\src\\main\\res\\layout')
    listdir = os.listdir(layout_path)
    for i in listdir:
        name = i[:i.rfind('.')]
        if layout_mapping.get(name) is None:
            continue
        path_join = os.path.join(layout_path, i)
        #解决layout出现大写的问题
        shutil.move(path_join, path_join.replace(name, normalize_filename(layout_mapping[name])))

    sub_map = {}
    for i in layout_mapping:
        sub_map['R\\.layout\\.' + i + '(\\W)'] = 'R.layout.' + layout_mapping[i] + '\\g<1>'
        # ViewBinding 同步修改
        sub_map[get_title(i, True) + 'Binding'] = get_title(layout_mapping[i], True) + 'Binding'
        sub_map['(>|")@layout/' + i + '(<|")'] = '\\g<1>@layout/' + layout_mapping[i] + '\\g<2>'
    sub_map_text(sub_map)

    # 改 drawable 文件名
    deal_res_type('drawable')

    # 改 mipmap 文件名
    deal_res_type('mipmap')

    # 改 raw 文件名
    deal_res_type('raw')

    # 改 raw 文件名
    deal_res_type('xml')

    # 改图片内容
    deal_image()


def get_path(path):
    paths = path.split('\\')
    return str(os.path.join(*paths))


def main():
    if not os.path.exists(get_path('.\\src\\main\\java\\' + applicationId[:applicationId.find('.')])):
        print(get_path('.\\src\\main\\java\\' + applicationId[:applicationId.find('.')]))
        return

    # 资源混淆
    deal_res()

    deal_code()


if __name__ == '__main__':
    if os.path.exists('build.gradle'):
        gradle_path = 'build.gradle'
    elif os.path.exists('build.gradle.kts'):
        gradle_path = 'build.gradle.kts'
    else:
        exit('找不到 build.gradle 文件')
    #获取build.gradle.kts里的namespace，以此确定混淆目录和包名
    applicationId = re.search('namespace .*?["\'](.*?)["\']', open(gradle_path, 'r', encoding='utf-8').read())[1]

    print(applicationId)
    main()
