package com.gh.gamecenter.plugin

import com.android.build.api.transform.Format
import com.android.build.api.transform.QualifiedContent
import com.android.build.api.transform.Transform
import com.android.build.api.transform.TransformInvocation
import com.android.build.gradle.AppExtension
import com.android.build.gradle.internal.pipeline.TransformManager
import com.gh.gamecenter.plugin.transform.ActivityTransformer
import com.gh.gamecenter.plugin.transform.DiskLruCacheTransformer
import com.gh.gamecenter.plugin.transform.ExoSourceManagerTransformer
import com.gh.gamecenter.plugin.transform.RoomTransformer
import javassist.ClassPool
import org.apache.commons.io.FileUtils
import org.apache.commons.io.IOUtils
import org.gradle.api.Project
import java.io.BufferedOutputStream
import java.io.File
import java.io.FileInputStream
import java.io.FileOutputStream
import java.util.jar.JarEntry
import java.util.jar.JarFile
import java.util.jar.JarOutputStream

class GhTransform(var project: Project) : Transform() {

    private val mClassPool = ClassPool.getDefault()
    private val mTransformHelper = GhTransformHelper()
    private val mExcludePackages = listOf(
        "com/google/android/material/chip",
        "com/google/android/material/textfield",
        "com/google/android/material/navigation",
        "com/google/android/material/datepicker",
        "com/google/android/material/circularreveal",
        "com/google/android/material/timepicker",
        "com/google/android/material/divider",
        "com/google/android/material/slider",
        "com/google/android/material/card",
        "com/google/android/material/transformation",
        "com/google/android/material/snackbar",
        "com/google/android/material/switchmaterial",
        "com/google/android/material/bottomappbar",
        "com/google/android/material/behavior",
        "com/google/android/material/floatingactionbutton",
    )

    override fun getName() = "GhTransform"

    init {
        mTransformHelper.addTransformer(ExoSourceManagerTransformer())
        mTransformHelper.addTransformer(DiskLruCacheTransformer())
        mTransformHelper.addTransformer(RoomTransformer())
        mTransformHelper.addTransformer(ActivityTransformer())
    }

    /**
     * 需要处理的数据类型，目前 ContentType有六种枚举类型，通常我们使用比较频繁的有前两种：
     * 1、CONTENT_CLASS：表示需要处理 java 的 class 文件。
     * 2、CONTENT_JARS：表示需要处理 java 的 class 与 资源文件。
     * 3、CONTENT_RESOURCES：表示需要处理 java 的资源文件。
     * 4、CONTENT_NATIVE_LIBS：表示需要处理 native 库的代码。
     * 5、CONTENT_DEX：表示需要处理 DEX 文件。
     * 6、CONTENT_DEX_WITH_RESOURCES：表示需要处理 DEX 与 java 的资源文件。 
     */
    override fun getInputTypes(): MutableSet<QualifiedContent.ContentType> {
        return TransformManager.CONTENT_CLASS
    }

    /**
     * 是否增量编译
     */
    // TODO Why not?
    override fun isIncremental() = false

    /**
     * Transform 要操作的内容范围，目前 Scope 有五种基本类型：
     * 1、PROJECT：只有项目内容
     * 2、SUB_PROJECTS：只有子项目
     * 3、EXTERNAL_LIBRARIES：只有外部库
     * 4、TESTED_CODE：由当前变体（包括依赖项）所测试的代码
     * 5、PROVIDED_ONLY：只提供本地或远程依赖项
     * SCOPE_FULL_PROJECT 是一个Scope集合，包含Scope.PROJECT,Scope.SUB_PROJECTS,Scope.EXTERNAL_LIBRARIES 这三项，即当前Transform的作用域包括当前项目、子项目以及外部的依赖库
     */
    override fun getScopes(): MutableSet<in QualifiedContent.Scope> {
        // 通常我们使用 SCOPE_FULL_PROJECT
        return TransformManager.SCOPE_FULL_PROJECT
    }

    override fun transform(transformInvocation: TransformInvocation?) {
        super.transform(transformInvocation)

        // 添加 android.jar path
        mClassPool?.appendClassPath((project.extensions.findByType(AppExtension::class.java)!!.bootClasspath[0].toString()))

        val outputProvider = transformInvocation?.outputProvider
        outputProvider?.deleteAll()

        transformInvocation?.inputs?.forEach { input ->
            input.directoryInputs.forEach { dirInput ->
                handleDirectory(dirInput.file)

                // 将input的目录复制到output指定目录
                val dest = outputProvider?.getContentLocation(
                    dirInput.name,
                    dirInput.contentTypes,
                    dirInput.scopes,
                    Format.DIRECTORY
                )
                FileUtils.copyDirectory(dirInput.file, dest)
            }
        }

        var index = 0
        transformInvocation?.inputs?.forEach { input ->
            input.jarInputs.forEach { jarInput ->
                if (jarInput.file.exists()) {
                    val srcFile = handleJar(jarInput.file)
                    // 必须给jar重新命名，否则会冲突
                    var jarName = jarInput.name
                    if (jarName.endsWith(".jar")) {
                        jarName = jarName.substring(0, jarName.length - 4)
                    }
                    val dest = outputProvider?.getContentLocation(
                        jarName + "_" + index,
                        jarInput.contentTypes,
                        jarInput.scopes,
                        Format.JAR
                    )
                    FileUtils.copyFile(srcFile, dest)
                    index++
                }
            }
        }
    }

    private fun handleDirectory(dir: File) {
        // 将类路径添加到 classPool 中
        mClassPool.insertClassPath(dir.absolutePath)

        if (dir.isDirectory) {
            dir.walkTopDown().forEach { file ->
                val filePath = file.absolutePath
                mClassPool.insertClassPath(filePath)
                if (shouldModify(filePath)) {
                    val inputStream = FileInputStream(file)
                    mTransformHelper.proceedModifyDir(filePath, inputStream)
                }
            }
        }
    }

    /**
     * 主要步骤：
     * 1.遍历所有jar文件
     * 2.解压jar然后遍历所有的class
     * 3.读取class的输入流并使用javassit修改，然后保存到新的jar文件中
     */
    fun handleJar(jarFile: File): File {
        mClassPool.appendClassPath(jarFile.absolutePath)
        val inputJarFile = JarFile(jarFile)
        val entries = inputJarFile.entries()
        //创建一个新的文件
        val outputJarFile = File(jarFile.parentFile, "temp_" + jarFile.name)
        if (outputJarFile.exists()) outputJarFile.delete()
        val jarOutputStream = JarOutputStream(BufferedOutputStream(FileOutputStream(outputJarFile)))
        while (entries.hasMoreElements()) {
            val jarInputEntry = entries.nextElement()
            val jarInputEntryName = jarInputEntry.name
            var isIgnore = false
            for (i in mExcludePackages.indices){
                if (jarInputEntryName.contains(mExcludePackages[i])) {
                    isIgnore = true
                    break
                }
            }
            if (isIgnore) continue
            val outputJarEntry = JarEntry(jarInputEntryName)
            jarOutputStream.putNextEntry(outputJarEntry)

            val inputStream = inputJarFile.getInputStream(jarInputEntry)
            if (!shouldModify(jarInputEntryName)) {
                jarOutputStream.write(IOUtils.toByteArray(inputStream))
                inputStream.close()
                continue
            }
            mTransformHelper.proceedModifyJar(jarInputEntryName, jarOutputStream, inputStream)
        }
        inputJarFile.close()
        jarOutputStream.closeEntry()
        jarOutputStream.flush()
        jarOutputStream.close()
        return outputJarFile
    }

    private fun shouldModify(filePath: String): Boolean {
        return filePath.endsWith(".class")
                && !filePath.contains("R.class")
//                && !filePath.contains("$")
                && !filePath.contains("R$")
                && !filePath.contains("BuildConfig.class")
    }

}