/* Define the gradle plugins. */
plugins {
    id "idea"

    // Used to get info from git.
    id "com.palantir.git-version" version "3.4.0"

    // Used for pre-processor.
    id "systems.manifold.manifold-gradle-plugin" version "0.0.2-alpha"

    // Google error prone
    id "net.ltgt.errorprone" version "4.3.0"
}

/* Configure Pre-Processor. */
// Transfers the values set in settings.gradle file to the rest of the project
project.gradle.ext.getProperties().each { prop ->
    rootProject.ext.set(prop.key, prop.value)
//    println "Add prop in rootProject.ext [key:" + prop.key + ", value:" + prop.value + "]"
}

// Sets up manifold stuff
def writeBuildGradlePredefine(List<String> targetMcVersions, int activeMcVersionIndex) {
    // Build the list of preprocessors to use
    StringBuilder sb = new StringBuilder();
    sb.append("# DON'T TOUCH THIS FILE, This is handled by the build script\n");

    for (int i = 0; i < targetMcVersions.size(); i++) {
        String verStr = targetMcVersions[i].replace(".", "_");
        sb.append("MC_" + verStr + "=" + i.toString() + "\n");

        if (activeMcVersionIndex == i)
            sb.append("MC_VER=" + i.toString() + "\n");
    }

    // Check if this is a development build
    if (mod_version.toLowerCase().contains("dev")) {
        // WARNING: only use this for logging, we don't want to have confusion
        // when a method doesn't work correctly in the release build.
        sb.append("DEV_BUILD=\n");
    }

    new File(projectDir, "build.properties").text = sb.toString()
}

writeBuildGradlePredefine(rootProject.mcVers, rootProject.mcIndex)

/* Configure the project maven group. */
group = project.maven_group

/* Configure shared parts for each sub project. */
def javaProjects = ["fabric", "checker", "weaver", "evaluator"]
javaProjects.each { projectName ->
    project(projectName) { p ->

        /* Configure manifold gradle plugin. */
        apply plugin: "systems.manifold.manifold-gradle-plugin"
        manifold {
            manifoldVersion = rootProject.manifold_version
        }


        /* Configure error prone gradle plugin. */
        apply plugin: "net.ltgt.errorprone"

        /* Configure dependencies. */
        dependencies {
            /* For pre-processor: manifold */
            annotationProcessor("systems.manifold:manifold-preprocessor:${rootProject.manifold_version}")
            testAnnotationProcessor("systems.manifold:manifold-preprocessor:${rootProject.manifold_version}")

            /* For code generation: lombok */
            compileOnly "org.projectlombok:lombok:${project.lombok_version}"
            annotationProcessor "org.projectlombok:lombok:${project.lombok_version}"
            testCompileOnly "org.projectlombok:lombok:${project.lombok_version}"
            testAnnotationProcessor "org.projectlombok:lombok:${project.lombok_version}"

            /* For code weaver: custom annotation processor. */
            if (p.name == "fabric") {
                annotationProcessor project(":weaver")
                compileOnly project(":weaver")
            }

            /* For testing: JUnit */
            testImplementation(platform("org.junit:junit-bom:${project.junit_version}"))
            testImplementation("org.junit.jupiter:junit-jupiter")
            testRuntimeOnly("org.junit.platform:junit-platform-launcher")

            /* For Java class file reflection: Classgraph */
            testImplementation "io.github.classgraph:classgraph:${project.classgraph_version}"

            /* For error prone gradle plugin. */
            if (p.name != "checker") {
                errorprone("com.google.errorprone:error_prone_core:${project.errorprone_version}")
                errorprone(project(':checker'))
                compileOnly("com.google.errorprone:error_prone_annotations:${project.errorprone_annotations_version}")
                testImplementation("com.google.errorprone:error_prone_annotations:${project.errorprone_annotations_version}")
            }

            /* For IDE analyser: jetbrains annotations. */
            compileOnly "org.jetbrains:annotations:${project.jetbrains_annotations_version}"
            testCompileOnly "org.jetbrains:annotations:${project.jetbrains_annotations_version}"
        }

        /* Replace properties for each mc version. */
        processResources {
            // Define the locations to inject the properties.
            def resourceTargets = [
                    // Properties for each of the loaders
                    "fabric.mod.json",
                    "quilt.mod.json",
                    "META-INF/mods.toml",
                    "META-INF/neoforge.mods.toml",

                    // The mixins for each of the loaders
                    "fuji." + p.name + ".fabricLike.mixins.json"
            ]
            def intoTargets = ["$buildDir/resources/main/"] // Location of the built resources folder

            // The left side is what gets replaced in the mod info and the right side is where to get it from in the gradle.properties
            def replaceProperties = [
                    version                      : mod_version,
                    mod_id                       : mod_id,
                    group                        : maven_group,
                    minecraft_version            : minecraft_version,
                    compatible_minecraft_versions: compatible_minecraft_versions,
                    java_version                 : java_version,

                    mc_publish_dependencies_list : mc_publish_dependencies_list
            ]

            // replace any properties in the sub-projects with the values defined here
            inputs.properties replaceProperties
            replaceProperties.put "project", project
            filesMatching(resourceTargets) {
                expand replaceProperties
            }

            intoTargets.each { target ->
                if (file(target).exists()) {
                    copy {
                        from(sourceSets.main.resources) {
                            include resourceTargets
                            expand replaceProperties
                        }
                        into target
                    }
                }
            }
        }

        /* Configure Java compiler. */
        tasks.withType(JavaCompile).configureEach { task ->

            options.release = rootProject.java_version as Integer
            options.encoding = "UTF-8"

            /* Configure error-prone gradle plugin. */
            options.errorprone.excludedPaths = ".*/module/mixin/.*"
            options.errorprone {
                /* For readability, and Java version compatibility. */
                disable("StatementSwitchToExpressionSwitch")

                /* Let me compare the memory address. */
                disable("ReferenceEquality")

                /* Let me get the name from the AST. */
                disable("TreeToString")

                /* Use the double brace initialization. */
                // 1. For readability, the syntax is much simple and clear. A call to custom collection factory method to make a mutable collection is complex.
                // 2. The non-static inner class does capture the reference of enclosing outer class, but it's expected, the lifespans of outer class and its inner class fields are the same in this use-case. (Remember not to leak the reference of the inner class)
                // Note that for non-static inner class, the Gson will calls the field initializer for that inner class N times during the serialization. (Where N = the depth of that field)
                disable("DoubleBraceInitialization")
            }

            configure(options) {
                /* Include the 'parameter name' in compiled .class file, for command annotation processor reflection */
                options.compilerArgs << "-parameters"

                /* Keep the parameter names for command annotation processor. */
                options.compilerArgs << "-Xlint:-unchecked"

                /* Fail the compiling if there is any warning. */
                options.compilerArgs << "-Werror"

                /* Print the AP processing rounds. */
                // options.compilerArgs << "-XprintRounds"

                /* Specify the behaviour for implicit compilation. */
                // See: https://docs.oracle.com/javase/6/docs/technotes/tools/solaris/javac.html#processing
                options.compilerArgs << "-implicit:none"
            }
        }

        /* Copy LICENSE file into jar file. */
        jar {
            outputs.upToDateWhen { false }
            def licenseFilePath = rootProject.file("LICENSE")
            from(licenseFilePath) {
                rename { "${it}" }
            }
        }
    }
}

/* Configure for all projects. */
allprojects { p ->
    apply plugin: "maven-publish"

    repositories {
        mavenCentral()

        google()
//        maven { url "https://s01.oss.sonatype.org/content/repositories/snapshots/" }

        // For manifold preprocessor.
        maven { url "https://oss.sonatype.org/content/repositories/snapshots" }

        maven { url "https://jitpack.io" }
        maven { url "https://projectlombok.org/edge-releases" }

        // For text-placeholder and sgui mod.
        maven { url "https://maven.nucleoid.xyz" }

        // VanillaGradle and Mixins in common module.
//        maven { url "https://repo.spongepowered.org/maven/" }
    }

    /* Define the archive name. */
    archivesBaseName = "fuji-" + project.name
    // The `version` must starts with sem-ver to make the fabric loader happy
    def gitVersionDetails = versionDetails()
    version = "${project.mod_version}-${gitVersionDetails.gitHash}-mc${project.minecraft_version}"

    /* Copy resource files. */
    tasks.register('copyCommonResources', Copy) {
        from project(":common").file("src/main/resources/")
        into(file(p.file("build/resources/main/")))
        exclude '**/*.accesswidener'

        doFirst {
            println("Copy all resource files in :common project.")
        }
    }

    tasks.register('copyLanguageResources') {
        dependsOn('copyPullFromCrowdinResources', 'copyPushToCrowdinResources')
    }

    tasks.register('copyPullFromCrowdinResources', Copy) {
        def sourceDir = project(":crowdin").file("pull-from-crowdin/")
        def destinationDir = file("${layout.buildDirectory.get()}/resources/main/${languageDirectoryName}/")
        from(sourceDir)
        into(destinationDir)
        include "**/*.json"

        doFirst {
            println("Copy pull-from-crowdin language files from source to destination.")
            println("The source dir: " + sourceDir)
            println("The destination dir: " + destinationDir)
        }
    }

    tasks.register('copyPushToCrowdinResources', Copy) {
        mustRunAfter(tasks.named('copyPullFromCrowdinResources'))

        def sourceDir = project(":crowdin").file("push-to-crowdin/")

        def destinationDir = file("${layout.buildDirectory.get()}/resources/main/${languageDirectoryName}/")

        from(sourceDir)
        into(destinationDir)
        include "**/*.json"

        doFirst {
            println("Copy push-to-crowdin language files from source to destination.")
            println("The source dir: " + sourceDir)
            println("The destination dir: " + destinationDir)
        }
    }

    tasks.register('copyAccessWidenerResources', Copy) {
        from project(":common").file("build/resources/main/merged.fuji.accesswidener")
        into(file(p.file("build/resources/main")))
        rename "merged.fuji.accesswidener", "fuji.accesswidener"

        doFirst {
            println("Copy accesswidener file for specified MC version.")
        }
    }

    /* Configure test framework. */
    // NOTE If :fabric:test task failed for no reason, you can comment out this block, disable the test, and run again, see what's happening.
    test {
        outputs.upToDateWhen { false }
        useJUnitPlatform()
        filter {}
    }

}

