import org.objectweb.asm.ClassReader
import org.objectweb.asm.ClassWriter
import org.objectweb.asm.Opcodes
import org.objectweb.asm.tree.AnnotationNode
import org.objectweb.asm.tree.ClassNode
import org.objectweb.asm.tree.FieldNode
import org.objectweb.asm.tree.InnerClassNode
import org.objectweb.asm.tree.MethodNode

import java.nio.file.Paths
import java.util.jar.JarEntry
import java.util.jar.JarFile
import java.util.jar.JarOutputStream
import java.util.zip.ZipEntry

buildscript {
    repositories {
        maven { url "https://maven.aliyun.com/repository/public/" }
    }
    dependencies {
        classpath 'org.ow2.asm:asm-commons:9.2'
    }
}

Project frameworks = project(":Frameworks")

gradle.projectsEvaluated {
    Task frameworksJarTask = null
    try {
        frameworksJarTask = frameworks.tasks.named("jar").get()
    } catch (Throwable ex) {
        ex.printStackTrace()
    }

    if (frameworksJarTask == null) {
        return
    }

    File frameworksJar = null
    frameworksJarTask.outputs.files.files.forEach {
        if (it.getName() == "Frameworks.jar") {
            frameworksJar = it
        }
    }

    if (frameworksJar == null) {
        return
    }

    // 获取SDK路径
    // android.sdkDirectory.getAbsolutePath()
    File sdkJar = new File(Paths.get(android.sdkDirectory.getAbsolutePath(), "platforms",
            "android-${sdkInt}", "android.jar").toString())

    File resultJar = new File(frameworksJar.getParent(), "android-${sdkInt}.jar")

    File tempDir = new File(frameworks.getBuildDir(), "tmp")
    File inputFile = new File(tempDir, System.currentTimeMillis() + ".txt")

    Project currentProject = project
    Task currentPreBuild = currentProject.tasks.named("preBuild").get()
    currentPreBuild.doLast {
        // 只有当frameworksJarTask未执行时才添加输入文件
        if (!frameworksJarTask.getState().executed) {
            if (!tempDir.exists()) {
                tempDir.mkdirs()
            }
            if (!inputFile.exists()) {
                inputFile.createNewFile()
            }

            // 为了避免UP-TO-DATE导致doLast不执行,这里随机添加一个输入文件
            frameworksJarTask.inputs.file(inputFile)
        }
    }

    frameworksJarTask.doLast {
        inputFile.delete()
        // 只有在编译当前Module时才去合并对应的jar
        if (currentPreBuild.getState().executed) {
            println("mergeJar -> $currentProject")
            try {
                if (resultJar.exists()) {
                    resultJar.delete()
                }
                ClassMerge.mergeJar(resultJar, sdkJar, frameworksJar, "$sdkInt")
            } catch (Throwable ex) {
                ex.printStackTrace()
            }
        }
    }

    // 针对kotlin代码编译,需要加此设置才能编译通过
    if (project.getPlugins().findPlugin("kotlin-android") != null) {
        project.dependencies {
            compileOnly files(resultJar)
        }
    }

    tasks.withType(JavaCompile).configureEach {
        String taskName = it.name
        if (taskName.contains("UnitTest") || taskName.contains("AndroidTest")) {
            return
        }

        it.doFirst {
//            File androidJar = null
//            // android.jar在classpath中或者在bootstrapClasspath中
//            // TODO AndHub 规律暂时未知?
//            classpath.files.forEach {
//                if (it.getName() == "android.jar") {
//                    androidJar = it
//                }
//            }
//            options.bootstrapClasspath.files.forEach {
//                if (it.getName() == "android.jar") {
//                    androidJar = it
//                }
//            }
//
//            println("classpath framework jar = ${resultJar}")
//
//            try {
//                ClassMerge.mergeJar(resultJar, androidJar, frameworksJar, "$sdkInt")
//            } catch (Throwable ex) {
//                ex.printStackTrace()
//            }

            // android.jar在classpath中则需要设置classpath
            // android.jar在bootstrapClasspath中则需要设置bootstrapClasspath
            // 这里都设置一下
            classpath = files(resultJar, classpath)
            options.bootstrapClasspath = files(resultJar, options.bootstrapClasspath)
        }
    }
}

dependencies {
    compileOnly frameworks
}

class ClassMerge {
    /**
     * 合并jar文件中的class,规则:<br/>
     * 1、frameworksJar中的class如果在androidJar中能找到同类则进行合并后写入resultJar中<br/>
     * 2、frameworksJar中的class如果在androidJar找不到到同类则直接写入resultJar中
     *
     * @param resultJar 合并后的jar文件
     * @param androidJar SDK中对应版本的android.jar
     * @param frameworksJar Frameworks module编译出的jar文件
     *
     * @throws Exception
     */
    static void mergeJar(File resultJar, File androidJar, File frameworksJar, String sdkInt) throws Exception {
        // 这里只合并一次,不然在extractXxxAnnotations任务中会报错
        // WARN: Could not read file:
        // xxx/Frameworks/build/libs/android-32.jar!/android/graphics/drawable/Drawable.class;
        // size in bytes: 10378; file type: CLASS
        // java.util.zip.ZipException: zip END header not found
        // 可能是文件流被占用的原因
        if (resultJar.exists()) {
            return
        }
        JarOutputStream jos = new JarOutputStream(new FileOutputStream(resultJar))
        JarFile androidJarFile = new JarFile(androidJar)

        JarFile frameworksJarFile = new JarFile(frameworksJar)
        Enumeration<JarEntry> enumeration = frameworksJarFile.entries()
        while (enumeration.hasMoreElements()) {
            JarEntry jarEntry = enumeration.nextElement()
            String entryName = jarEntry.getName()
            // 过滤掉commons下面的class
            if (entryName.startsWith("commons/") || entryName.startsWith("META-INF/")) {
                continue
            }
            ZipEntry zipEntry = new ZipEntry(entryName)

            jos.putNextEntry(zipEntry)

            ZipEntry androidJarZip = androidJarFile.getEntry(entryName)

            byte[] byteCode
            if (androidJarZip != null && entryName.endsWith(".class")) {
                byte[] baseClassByte = androidJarFile.getInputStream(jarEntry).readAllBytes()
                byte[] copyClassByte = frameworksJarFile.getInputStream(jarEntry).readAllBytes()
                byteCode = mergeAndFixClass(baseClassByte, copyClassByte, sdkInt)
            } else {
                // android.jar中无此class,直接拷贝,无需合并
                byte[] baseClassByte = frameworksJarFile.getInputStream(jarEntry).readAllBytes()
                // 这里除了class还有文件夹,baseClassByte.length为0
                // META-INF/MANIFEST.MF
                if (entryName.endsWith(".class")) {
                    byteCode = mergeAndFixClass(baseClassByte, null, sdkInt)
                } else {
                    byteCode = baseClassByte
                }
            }

            jos.write(byteCode)
            jos.closeEntry()
        }
        jos.close()
        androidJarFile.close()
        frameworksJarFile.close()
    }

    private static byte[] mergeAndFixClass(byte[] baseClassByte, byte[] copyClassByte, String sdkInt) throws Exception {
        int api = Opcodes.ASM9
        int flags = ClassWriter.COMPUTE_FRAMES

        ClassReader baseClassReader = new ClassReader(baseClassByte)
        ClassNode baseClassNode = new ClassNode(api)
        baseClassReader.accept(baseClassNode, 0)

        // 需要将访问属性改为public的类
        Set<String> publicClass = new HashSet<>()
        publicClass.add("android/annotation/NonNull")
        publicClass.add("android/annotation/Nullable")
        if (publicClass.contains(baseClassNode.name)) {
            baseClassNode.access = baseClassNode.access | Opcodes.ACC_PUBLIC
        }

        List<MethodNode> baseClassMethods = fixMethod(baseClassNode.methods, sdkInt)
        List<FieldNode> baseClassFields = fixField(baseClassNode.fields, sdkInt)

        if (copyClassByte != null) {
            ClassReader copyClassReader = new ClassReader(copyClassByte)
            ClassNode copyClassNode = new ClassNode(api)
            copyClassReader.accept(copyClassNode, 0)

            List<MethodNode> methods = fixMethod(copyClassNode.methods, sdkInt)
            Formatter formatter = new Formatter(Locale.getDefault())
            for (MethodNode node : methods) {
                // 过滤掉重复方法,会什么会有重复方法?
                // 有些版本的android.jar中没有此方法,有些版本中有此方法,
                // 导致Frameworks.jar中的方法和android.jar中的方法重复
                if (methodExists(baseClassMethods, node)) {
                    String access = formatter.format("0x%04x", node.access).toString()
                    println("重复方法->[${copyClassNode.name}] $access ${node.name} ${node.desc}")
                } else {
                    baseClassMethods.add(node)
                }
            }

            // 添加字段
            List<FieldNode> fields = fixField(copyClassNode.fields, sdkInt)
            for (FieldNode node : fields) {
                baseClassFields.add(node)
            }

            // 添加内部类
            for (InnerClassNode node : copyClassNode.innerClasses) {
                if (!innerClassesExists(baseClassNode.innerClasses, node)) {
                    baseClassNode.innerClasses.add(node)
                }
            }
        }

        ClassWriter baseClassWriter = new ClassWriter(flags)
        baseClassNode.accept(baseClassWriter)
        return baseClassWriter.toByteArray()
    }

    private static boolean innerClassesExists(List<InnerClassNode> classNodes, InnerClassNode innerClass) {
        for (InnerClassNode node : classNodes) {
            if (node.name == innerClass.name) {
                return true
            }
        }
        return false
    }

    private static boolean methodExists(List<MethodNode> methods, MethodNode method) {
        for (MethodNode node : methods) {
            if (node.name == method.name && node.desc == method.desc) {
                return true
            }
        }
        return false
    }

    private static List<FieldNode> fixField(List<FieldNode> fields, String sdkInt) {
        for (FieldNode node : fields) {
            // 修改字段类型
            fixNodeDesc(node, node.visibleAnnotations, sdkInt)

            // 清除掉字段上的注解,如:
            // java.lang.Deprecated

            // RetentionPolicy.RUNTIME注解
            node.visibleAnnotations = Collections.emptyList()

            // RetentionPolicy.CLASS注解
            node.invisibleAnnotations = Collections.emptyList()
        }
        return fields
    }

    private static List<MethodNode> fixMethod(List<MethodNode> methods, String sdkInt) {
        for (MethodNode node : methods) {
            // 修改方法描述符
            fixNodeDesc(node, node.visibleAnnotations, sdkInt)

            int newAccess = node.access

            // 将将构造方法改为public
            if ("<init>" == node.name) {
                int maskFlag = newAccess & 0x000F
                // private、protected、默认修饰符
                if (maskFlag == Opcodes.ACC_PRIVATE
                        || maskFlag == Opcodes.ACC_PROTECTED
                        || maskFlag == 0) {
                    newAccess = (newAccess & 0xFFF0) | Opcodes.ACC_PUBLIC
                }
            }

            // 去掉方法deprecated标记
            if ((newAccess & 0xF0000) == Opcodes.ACC_DEPRECATED) {
                newAccess = newAccess & 0x0FFFF
            }
            node.access = newAccess

            // 清除掉方法参数和返回值上的注解,如:
            // android.annotation.Nullable
            // android.annotation.NonNull

            // RetentionPolicy.RUNTIME注解
            node.visibleAnnotations = Collections.emptyList()
            node.visibleParameterAnnotations = Collections.emptyList()

            // RetentionPolicy.CLASS注解
            node.invisibleAnnotations = Collections.emptyList()
            node.invisibleParameterAnnotations = Collections.emptyList()
        }
        return methods
    }

    private static void fixNodeDesc(Object node, List<AnnotationNode> annotations, String sdkInt) {
        if (node == null && annotations == null && annotations.size() == 0) {
            return
        }
        for (AnnotationNode annotation : annotations) {
            String desc = annotation.desc
            if (desc == "Lcommons/annotations/MethodDescriptor;"
                    || desc == "Lcommons/annotations/FieldType;") {
                List<Object> values = annotation.values
                // 注解方法和值依次排列,方法在前,值在后
                // MethodDescriptor [version, 31, value, (IZ)Landroid/window/TaskSnapshot;]
                // FieldType [version, 31, value, [Landroid/graphics/Insets;]
                if (values != null && values.size() == 4 && values.get(1).toString() == sdkInt) {
                    node.desc = values.get(3)
                }
            } else if (desc == "Lcommons/annotations/MethodDescriptor\$MethodDescriptors;"
                    || desc == "Lcommons/annotations/FieldType\$FieldTypes;") {
                // 注解方法和值依次排列,方法在前,值在后
                // annotationNode.values第一个值为value,第2个值为数组
                // [value, [org.objectweb.asm.tree.AnnotationNode@797081d1, org.objectweb.asm.tree.AnnotationNode@7035ba64]]
                for (AnnotationNode object : annotation.values.get(1)) {
                    List<Object> values = object.values
                    if (values != null && values.size() == 4 && values.get(1).toString() == sdkInt) {
                        node.desc = values.get(3)
                    }
                }
            }
        }
    }
}