package io.nexilis

import org.gradle.api.Plugin
import org.gradle.api.initialization.Settings
import org.w3c.dom.Document
import org.w3c.dom.Element
import org.xml.sax.SAXException
import java.io.File
import java.io.IOException
import java.io.StringWriter
import java.nio.file.FileVisitResult
import java.nio.file.Files
import java.nio.file.Path
import java.nio.file.Paths
import java.nio.file.SimpleFileVisitor
import java.nio.file.StandardCopyOption
import java.nio.file.attribute.BasicFileAttributes
import javax.xml.parsers.DocumentBuilderFactory
import javax.xml.parsers.ParserConfigurationException
import javax.xml.transform.TransformerFactory
import javax.xml.transform.dom.DOMSource
import javax.xml.transform.stream.StreamResult
import kotlin.io.path.ExperimentalPathApi
import kotlin.io.path.extension
import kotlin.io.path.inputStream
import kotlin.io.path.name
import kotlin.io.path.nameWithoutExtension

const val libNameSpace = "com.example.dm"

class SettingsPlugin: Plugin<Settings> {
    override fun apply(settings: Settings) {
        println(">>>>>>>>>>> Settings")
        settings.gradle.settingsEvaluated {
            println(">>>>>>>>>>> settingsEvaluated")
            try {
                val moduleDir = File(settings.rootDir, "dynamic_core")
                if (moduleDir.exists()) {
                    moduleDir.deleteRecursively()
                    println(">>>>>>>>>>> delete:dir:${moduleDir.toPath()}")
                }
                if (!moduleDir.exists()) {
                    println(">>>>>>>>>>> create:dir:${moduleDir.toPath()}")
                    moduleDir.mkdirs()
                }
                val projectApp = settings.project(File(settings.rootDir, "app"))
                println(">>>>>>>>>>> copy:from:${projectApp.projectDir.toPath()}")
                projectApp.projectDir.toPath().copy(moduleDir.toPath())
                println(">>>>>>>>>>> copy:done")
                moduleDir.toPath().refactor()
                println(">>>>>>>>>>> refactor:done")
                settings.include(":dynamic_core")
                println(">>>>>>>>>>> include module:dynamic_core")
                val tmpBaseDir = File(projectApp.projectDir, "src/custom/java/io/nexilis/app")
                println(">>>>>>>>>>> create:base:dir:${tmpBaseDir.toPath()}")
                if (!tmpBaseDir.exists()) {
                    tmpBaseDir.mkdirs()
                }
                val activity = Paths.get(tmpBaseDir.toPath().toString(), "NexilisActivity.java")
                Files.writeString(activity, mainActivity)
                println(">>>>>>>>>>> create:activity:$activity")
            } catch (e: Exception) {
                e.printStackTrace()
            }
        }
    }
}

const val mainActivity = "package io.nexilis.app;\n" +
        "\n" +
        "import android.os.Bundle;\n" +
        "\n" +
        "import androidx.appcompat.app.AppCompatActivity;\n" +
        "\n" +
        "public class NexilisActivity extends AppCompatActivity {\n" +
        "\n" +
        "    @Override\n" +
        "    protected void onCreate(Bundle savedInstanceState) {\n" +
        "        super.onCreate(savedInstanceState);\n" +
        "    }\n" +
        "\n" +
        "}\n"
const val androidManifest = "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n" +
        "<manifest xmlns:dist=\"http://schemas.android.com/apk/distribution\">\n" +
        "\n" +
        "    <dist:module\n" +
        "        dist:instant=\"false\"\n" +
        "        dist:title=\"@string/title_dynamic_core\">\n" +
        "        <dist:delivery>\n" +
        "            <dist:on-demand />\n" +
        "        </dist:delivery>\n" +
        "        <dist:fusing dist:include=\"true\" />\n" +
        "    </dist:module>\n" +
        "    \n" +
        "</manifest>"

@OptIn(ExperimentalPathApi::class)
fun Path.copy(target: Path) {
    Files.walkFileTree(this, object : SimpleFileVisitor<Path>() {
        @Throws(IOException::class)
        override fun preVisitDirectory(dir: Path, attrs: BasicFileAttributes): FileVisitResult {
            if (dir.fileName.name == "build") {
                return FileVisitResult.SKIP_SUBTREE
            }
            val targetPath = target.resolve(this@copy.relativize(dir))
            if (!Files.exists(targetPath)) {
                Files.createDirectory(targetPath)
            }
            return FileVisitResult.CONTINUE
        }

        @Throws(IOException::class)
        override fun visitFile(file: Path, attrs: BasicFileAttributes): FileVisitResult {
            if (file.fileName.name == "strings.xml") return FileVisitResult.CONTINUE
            val targetPath = target.resolve(this@copy.relativize(file))
            Files.copy(file, targetPath, StandardCopyOption.REPLACE_EXISTING)
            if (file.isResource()) {
                val oldName = targetPath.fileName.nameWithoutExtension
                val oldExtension = targetPath.extension
                val newName = "app_$oldName.$oldExtension"
                val oldFile = targetPath.toFile()
                val newFile = File(oldFile.parentFile, newName)
                oldFile.renameTo(newFile)
                renamedFiles[oldName] = newName
            }
            if (file.fileName.name == "build.gradle") {
                var updatedContent = """plugins\s*\{[^}]*}""".toRegex()
                    .replace(
                        Files.readString(file).trimIndent(),
                        "plugins {\n    id 'com.android.dynamic-feature'\n}\n"
                    )
//                updatedContent = """namespace\s+(['"])[^'"]+"\s*""".toRegex()
//                    .replace(updatedContent, "'$libNameSpace'")
//                updatedContent = """applicationId\s+(['"])[^'"]+"\s*""".toRegex()
//                    .replace(updatedContent, "")
                updatedContent = """kotlinOptions\s*\{\s*jvmTarget\s*=\s*"11"\s*\}""".toRegex()
                    .replace(updatedContent, "")
                Files.writeString(targetPath, updatedContent)
            }
            if (file.fileName.name == "AndroidManifest.xml") {
                try {
                    val activity = file.replaceMainActivity("io.nexilis.app.NexilisActivity")
                    Files.writeString(file, activity)
                } catch (_: Exception) {
                }
                Files.writeString(targetPath, androidManifest)
            }
            return FileVisitResult.CONTINUE
        }
    })
}

fun Path.refactor() {
    Files.walkFileTree(this, object : SimpleFileVisitor<Path>() {
        override fun visitFile(p0: Path, p1: BasicFileAttributes?): FileVisitResult {
            if (p0.isSource()) {
                var isReplace: Boolean = false
                var content = Files.readString(p0)
                renamedFiles.forEach { (oldName, newName) ->
                    val newReference = newName.substringBefore(".")
                    val tmpContent = content.replace("""$oldName\b""".toRegex(), newReference)
                    if (content != tmpContent) {
                        content = tmpContent
                        isReplace = true
                    }
                }
                val tmpContent = content.replace("""\bR\.""".toRegex(), "$libNameSpace.R.")
                if (content != tmpContent) {
                    content = tmpContent
                    isReplace = true
                }
                if (isReplace) {
                    Files.writeString(p0, content)
                }
            }
            return FileVisitResult.CONTINUE
        }
    })
}

@Throws(ParserConfigurationException::class, IOException::class, SAXException::class)
fun Path.replaceMainActivity(string: String): String {
    val doc = DocumentBuilderFactory.newInstance()
        .newDocumentBuilder()
        .parse(this.inputStream())
    doc.documentElement.normalize()
    return doc.replace(string).asString()
}

fun Document.replace(string: String): Document {
    val activity = this.getElementsByTagName("activity")
    p@ for (i in 0..<activity.length) {
        val element = activity.item(i) as Element
        val intent = element.getElementsByTagName("intent-filter")
        for (j in 0..<intent.length) {
            val element1 = intent.item(j) as Element
            val action = element1.getElementsByTagName("action")
            for (k in 0..<action.length) {
                val element2 = action.item(k) as Element
                if (element2.getAttribute("android:name") == "android.intent.action.MAIN") {
                    element.setAttribute("android:name", string)
                    break@p
                }
            }
        }
    }
    return this
}

fun Document.asString(): String {
    val writer = StringWriter()
    TransformerFactory.newInstance()
        .newTransformer()
        .transform(DOMSource(this), StreamResult(writer))
    return writer.toString()
}

val renamedFiles = mutableMapOf<String, String>()

fun Path.isResource(): Boolean {
    if (this.fileName.name == "AndroidManifest.xml") return false
    if (this.parent.name.contains("drawable|layout|mipmap".toRegex())) {
        return this.fileName.extension.matches("xml|png|webp|jpeg|jpg|bitmap".toRegex())
    }
    return false
}

fun Path.isSource(): Boolean {
    return this.fileName.extension.matches("java|kt|xml".toRegex())
}