import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar
import io.franzbecker.gradle.lombok.task.DelombokTask

plugins {
    id 'io.franzbecker.gradle-lombok' version '5.0.0' apply false
    id 'xyz.wagyourtail.unimined' version '1.1.1' apply false
    id 'com.github.johnrengelman.shadow' version '8.1.1'
    // id "com.dorongold.task-tree" version "2.1.1" to find the dependencies of runClient for the docker file
}

apply plugin: 'java'
apply plugin: 'maven-publish'

ext {
    platforms = ['fabric', 'lexforge', 'neoforge']
    platforms_cap = ['Fabric', 'Lexforge', 'Neoforge']
}

subprojects {
    apply plugin: 'java'
    apply plugin: 'java-library'
    apply plugin: 'idea'
    apply plugin: 'xyz.wagyourtail.unimined'
    apply plugin: 'io.franzbecker.gradle-lombok'
    apply plugin: 'com.github.johnrengelman.shadow'

    group 'me.earth.pingbypass'
    version rootProject.version

    base {
        archivesName = project.name
    }

    sourceSets {
        fabric
        neoforge
        lexforge
    }

    repositories {
        mavenCentral()
        maven {
            url = "https://files.minecraftforge.net/maven"
        }
        maven {
            name = "sponge"
            url = "https://repo.spongepowered.org/maven"
        }
        maven {
            url = "https://maven.wagyourtail.xyz/releases"
        }

        maven {
            name = '3arthMaven'
            url = 'https://3arthqu4ke.github.io/maven'
        }
    }

    unimined.minecraft {
        version rootProject.minecraft_version

        mappings {
            mojmap()
            devFallbackNamespace 'mojmap'
        }

        defaultRemapJar = false
    }

    unimined.minecraft(sourceSets.fabric) {
        combineWith(sourceSets.main)

        fabric {
            loader rootProject.fabric_version
        }

        defaultRemapJar = true
    }

    unimined.minecraft(sourceSets.neoforge) {
        combineWith(sourceSets.main)

        neoForged {
            loader rootProject.neoforge_version
            mixinConfig 'mixins.pingbypass.json'
            if (project.name == 'pb-client') {
                mixinConfig += 'mixins.pingbypass_client.json'
            } else if (project.name == 'pb-server') {
                mixinConfig += 'mixins.pingbypass_server.json'
            }
        }

        minecraftRemapper.config {
            // neoforge adds 1 conflict, where 2 interfaces have a method with the same name on yarn/mojmap,
            // but the method has different names in the intermediary mappings.
            // this is a conflict because they have a class that extends both interfaces.
            // this shouldn't be a problem as long as named mappings don't make the name of those 2 methods different.
            ignoreConflicts(true)
        }

        defaultRemapJar = true
    }

    unimined.minecraft(sourceSets.lexforge) {
        combineWith(sourceSets.main)

        minecraftForge {
            loader rootProject.lexforge_version
            mixinConfig 'mixins.pingbypass.json'
            if (project.name == 'pb-client') {
                mixinConfig += 'mixins.pingbypass_client.json'
            } else if (project.name == 'pb-server') {
                mixinConfig += 'mixins.pingbypass_server.json'
            }
        }

        minecraftRemapper.config {
            ignoreConflicts(true)
        }

        defaultRemapJar = true
    }

    configurations {
        lwjglAgent
        runtimeOnly.extendsFrom lwjglAgent

        // see pb-api build.gradle, these need to be on the classpath when building our JavaDoc
        javaDocLibs
        compileOnly.extendsFrom javaDocLibs
    }

    dependencies {
        compileOnly 'org.jetbrains:annotations:24.0.1'

        compileOnly 'org.projectlombok:lombok:1.18.30'
        annotationProcessor 'org.projectlombok:lombok:1.18.30'

        testCompileOnly 'org.projectlombok:lombok:1.18.30'
        testAnnotationProcessor 'org.projectlombok:lombok:1.18.30'

        testImplementation 'org.junit.jupiter:junit-jupiter-api:5.9.2'
        testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.9.2'

        api 'com.google.code.gson:gson:2.10.1'
        compileOnly 'org.spongepowered:mixin:0.8.5-SNAPSHOT'
        compileOnly 'me.earth.headlessmc:headlessmc:1.8.1'

        if (rootProject.property('hmc.lwjgl').toBoolean()) {
            lwjglAgent "me.earth.headlessmc:headlessmc-lwjgl:1.8.1"
        }
    }

    test {
        useJUnitPlatform()
        testLogging {
            showStandardStreams = true
            events "failed"
            exceptionFormat "full"
        }
    }

    def dependsOnBeforeJavadocAndSourcesJar = new HashSet<>(assemble.dependsOn)

    java {
        withSourcesJar()
        withJavadocJar()
    }

    assemble {
        // I do not want to build the javadocJar every time
        // TODO: better way? dependsOn contains TaskContainers, check each for task?
        dependsOn.removeIf { !dependsOnBeforeJavadocAndSourcesJar.contains(it) }
    }

    // TODO: even though we are doing all of this Intellij still complains about bytecode not matching the source...
    // (actually not if the files are local???)
    if (sourceSets.main.java.srcDirs.stream().anyMatch { it.exists()}) {
        tasks.register('delombok', DelombokTask) {
            dependsOn compileJava
            classpath += sourceSets.main.compileClasspath // without this we would fail as soon as we hit an MC class
            ext.outputDir = file(project.layout.buildDirectory.get().asFile.toPath().resolve('delombok'))
            outputs.dir(ext.outputDir)
            sourceSets.main.java.srcDirs.each {
                if (it.exists()) {
                    inputs.dir(it)
                    // not using -f pretty for now, we might want to use some of these
                    args(it,'-f', 'suppressWarnings:skip',
                            '-f', 'generated:skip',
                            '-f', 'danceAroundIdeChecks:skip',
                            '-f', 'generateDelombokComment:skip',
                            '-f', 'javaLangAsFQN:skip',
                            '-d', ext.outputDir)
                }
            }
        }

        javadoc {
            dependsOn delombok
            source = delombok.outputDir
            options.addStringOption('Xdoclint:none', '-quiet')
            options.linkSource true
        }

        sourcesJar {
            dependsOn delombok
            from delombok.outputDir
            // I tried every single exclude/include pattern but I could not get any to work
            exclude (fileTreeElement -> {
                return !((FileTreeElement) fileTreeElement)
                        .getFile()
                        .toPath()
                        .toAbsolutePath()
                        .startsWith(((File) delombok.outputDir).toPath().toAbsolutePath())
            })
        }
    }

    tasks.withType(JavaCompile).configureEach {
        if (JavaVersion.current().isJava9Compatible()) {
            it.options.release.set(17)
        }
    }

    // This allows Intellij to find ByteCode for our classes after a jar has been built.
    // Helpful for writing the Mixins in pb-security.
    idea {
        module {
            inheritOutputDirs = false
            outputDir = compileJava.destinationDirectory.getAsFile().getOrNull()
            testOutputDir = compileTestJava.destinationDirectory.getAsFile().getOrNull()
        }
    }

    // replace all ${version}s with the project version in these files:
    processFabricResources {
        inputs.property "version", project.version

        filesMatching("fabric.mod.json") {
            expand "version": project.version
        }
    }

    processNeoforgeResources {
        inputs.property "version", project.version

        filesMatching("META-INF/mods.toml") {
            expand "version": project.version
        }
    }

    processLexforgeResources {
        inputs.property "version", project.version

        filesMatching("META-INF/mods.toml") {
            expand "version": project.version
        }
    }

    // Forge Runs seem to have problems running from the build/classes folder
    // So instead we just run from the built jar
    afterEvaluate {
        fabricRunClient {
            configureTaskForLwjglAgent(project, it)
        }

        // TODO: check if we can run plugins with this? Is deencapsulation on the classpath?
        lexforgeRunClient {
            dependsOn(lexforgeJar)
            configureTaskForLwjglAgent(project, it)
            classpath = classpath.filter {
                !it.toString().contains("${project.name}/build/classes/java/".replace('/', File.separator))
                        && !it.toString().contains("${project.name}/build/resources/".replace('/', File.separator))
            }

            classpath += files("${projectDir}/build/libs/${project.name}-${project.version}-lexforge-dev.jar".replace('/', File.separator))
        }

        neoforgeRunClient {
            dependsOn(neoforgeJar)
            configureTaskForLwjglAgent(project, it)
            classpath = classpath.filter {
                !it.toString().contains("${project.name}/build/classes/java/".replace('/', File.separator))
                        && !it.toString().contains("${project.name}/build/resources/".replace('/', File.separator))
            }

            classpath += files("${projectDir}/build/libs/${project.name}-${project.version}-neoforge-dev.jar".replace('/', File.separator))
        }
    }
}

private void configureTaskForLwjglAgent(Project project, JavaExec exec) {
    exec.standardInput = System.in
    if (rootProject.property('hmc.lwjgl').toBoolean()) {
        exec.jvmArgs += ["-javaagent:${project.configurations.lwjglAgent.files.iterator().next()}"]
        exec.systemProperties['joml.nounsafe'] = 'true'
        exec.systemProperties['fabric.systemLibraries'] = "${project.configurations.lwjglAgent.files.iterator().next()}"
    }
}

subprojects {
    if (project == project(':pb-api')) {
        configurations {
            jarLibsImpl
            implementation.extendsFrom jarLibsImpl
        }

        // Add a shadowJar for each platform, currently the only dependency we need to include is deencapsulation, see pb-api build.gradle
        for (String platform_capitalized : rootProject.ext.platforms_cap) {
            def platform = platform_capitalized.toLowerCase()
            def remapJarTask = tasks.named("remap${platform_capitalized}Jar", AbstractArchiveTask).get()
            def shadowTask = tasks.register("${platform}ShadowJar", ShadowJar) {
                dependsOn(remapJarTask)
                group = 'build'
                archiveClassifier = "${platform}-release"
                from remapJarTask.outputs
                it.configurations += [ project.configurations.jarLibsImpl ]
            }

            rootProject.tasks.named('build') { finalizedBy(shadowTask) }
        }
    } else if ([project(':pb-server'), project(':pb-client')].contains(project)) {
        ext {
            extension = project.name.contains('server') ? 'server' : 'client'
            extension_cap = project.name.contains('server') ? 'Server' : 'Client'
        }

        dependencies {
            implementation project(':pb-api')
        }

        for (String platform_capitalized : rootProject.ext.platforms_cap) {
            def platform = platform_capitalized.toLowerCase()

            def remapJarTask = tasks.named("remap${platform_capitalized}Jar", AbstractArchiveTask).get()
            remapJarTask.archiveClassifier = "${platform}-plugin"
            def shadowTask = project(':pb-api').tasks.named("${platform}ShadowJar", AbstractArchiveTask).get()
            // Add a task which produces a Jar-In-Jar mod, which contains the pb-api
            // TODO: forge and fabric have tooling, I dont know if unimined supports forges JarJar, but I think there is support for fabric Jar-In-Jar?
            def standaloneTask = tasks.register("${platform}StandaloneJar", Jar) { jarTask ->
                inputs.property "version", project.version
                inputs.property "platform", platform
                dependsOn(remapJarTask, shadowTask)
                // for now, it seems that changes to the metadata.json etc. do not get registered
                outputs.upToDateWhen { false }

                duplicatesStrategy = DuplicatesStrategy.INCLUDE
                group = 'build'
                archiveBaseName = jar.archiveBaseName
                archiveClassifier = "${platform}-standalone"

                from(shadowTask.outputs.files.singleFile) {
                    rename {
                        platform == 'fabric' ? "META-INF/jars/${it}" : "META-INF/jarjar/${it}"
                    }
                }

                from({ zipTree(remapJarTask.outputs.files.singleFile) })
                if (platform == 'fabric') {
                    from(project.file('src/fabric/resources/fabric.mod.json')) {
                        eachFile {
                            filter { line ->
                                line.replace('"jars": []', "\"jars\": [ { \"file\": \"META-INF/jars/${shadowTask.outputs.files.singleFile.name}\" } ]")
                                        .replace("\${version}", project.version)
                            }
                        }
                    }
                } else {
                    from(project(':pb-api').file('metadata.json')) {
                        eachFile {
                            expand "version": project.version, "platform": platform
                        }

                        rename {
                            "META-INF/jarjar/${it}"
                        }
                    }
                }
            }

            rootProject.tasks.named('build') { finalizedBy(standaloneTask) }
        }

        afterEvaluate {
            def pb_api = project(':pb-api')

            fabricRunClient {
                dependsOn(pb_api.fabricJar)
                classpath += files("${pb_api.projectDir}/build/libs/${pb_api.name}-${pb_api.version}-fabric-dev.jar".replace('/', File.separator))
            }

            lexforgeRunClient {
                dependsOn(pb_api.lexforgeJar)
                classpath += files("${pb_api.projectDir}/build/libs/${pb_api.name}-${pb_api.version}-lexforge-dev.jar".replace('/', File.separator))
            }

            neoforgeRunClient {
                dependsOn(pb_api.neoforgeJar)
                classpath += files("${pb_api.projectDir}/build/libs/${pb_api.name}-${pb_api.version}-neoforge-dev.jar".replace('/', File.separator))
            }

            tasks.withType(org.gradle.jvm.tasks.Jar).configureEach {
                manifest {
                    attributes(
                        'MixinConfigs': "mixins.pingbypass_${extension}.json",
                        'MixinConnector': "me.earth.pingbypass.${extension}.launch.${extension_cap}MixinConnector",
                        'Implementation-Title': "PingBypass-${extension_cap}",
                        'Implementation-Version': project.version
                    )
                }
            }
        }
    }
}

afterEvaluate {
    publishing {
        repositories {
            // mavenLocal()
            // maven {
            //     name = 'SomewhereOnThisMachine'
            //     url = 'file://...'
            // }

            if (System.getenv("PB_OTHER_REPO") != null) {
                maven {
                    name = 'otherrepo'
                    url = "${System.getenv("PB_OTHER_REPO")}"
                }
            }

            if (System.getenv('DEPLOY_TO_GITHUB_PACKAGES_URL') == null) {
                maven {
                    name = 'BuildDirMaven'
                    url = rootProject.layout.buildDirectory.dir('maven')
                }
            } else {
                maven {
                    name = 'GithubPagesMaven'
                    url = System.getenv('DEPLOY_TO_GITHUB_PACKAGES_URL')
                    credentials {
                        username = System.getenv('GITHUB_USER')
                        password = System.getenv('GITHUB_TOKEN')
                    }
                }
            }
        }
    }

    subprojects {
        publishing {
            publications {
                "$project.name"(MavenPublication) {
                    ((MavenPublication) it).groupId "${project.group}"
                    ((MavenPublication) it).artifactId "${project.archivesBaseName.toLowerCase()}"
                    ((MavenPublication) it).version "${project.version}"
                    //noinspection GroovyAssignabilityCheck
                    from components.java
                    for (String platform_capitalized : rootProject.ext.platforms_cap) {
                        String platform = platform_capitalized.toLowerCase()
                        artifact project.tasks.named("${platform}Jar").get()
                        artifact project.tasks.named("remap${platform_capitalized}Jar").get()
                        if (project == project(':pb-api')) {
                            artifact project.tasks.named("${platform}ShadowJar").get() // publish shadowJar
                        } else if ([project(':pb-server'), project(':pb-client')].contains(project)) {
                            artifact project.tasks.named("${platform}StandaloneJar").get() // publish standalone jars
                        }
                    }
                }
            }
        }
    }
}
