//version: 1704659416
/*
 * DO NOT CHANGE THIS FILE!
 * Also, you may replace this file at any time if there is an update available.
 * Please check https://github.com/GregTechCEu/Buildscripts/blob/master/build.gradle for updates.
 * You can also run ./gradlew updateBuildScript to update your buildscript.
 */

import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar
import com.gtnewhorizons.retrofuturagradle.mcp.ReobfuscatedJar
import com.modrinth.minotaur.dependencies.ModDependency
import com.modrinth.minotaur.dependencies.VersionDependency
import org.gradle.api.tasks.testing.logging.TestExceptionFormat
import org.gradle.api.tasks.testing.logging.TestLogEvent
import org.gradle.internal.logging.text.StyledTextOutputFactory
import org.jetbrains.gradle.ext.Gradle

import static org.gradle.internal.logging.text.StyledTextOutput.Style

plugins {
    id 'java'
    id 'java-library'
    id 'base'
    id 'eclipse'
    id 'maven-publish'
    id 'org.jetbrains.gradle.plugin.idea-ext' version '1.1.7'
    id 'com.gtnewhorizons.retrofuturagradle' version '1.3.25'
    id 'net.darkhax.curseforgegradle' version '1.1.17' apply false
    id 'com.modrinth.minotaur' version '2.8.6' apply false
    id 'com.diffplug.spotless' version '6.13.0' apply false
    id 'com.palantir.git-version' version '3.0.0' apply false
    id 'com.github.johnrengelman.shadow' version '8.1.1' apply false
    id 'org.jetbrains.kotlin.jvm' version '1.8.0' apply false
    id 'org.jetbrains.kotlin.kapt' version '1.8.0' apply false
    id 'com.google.devtools.ksp' version '1.8.0-1.0.9' apply false
}

def out = services.get(StyledTextOutputFactory).create('an-output')


// Project properties

// Required properties: we don't know how to handle these being missing gracefully
checkPropertyExists("modName")
checkPropertyExists("modId")
checkPropertyExists("modGroup")
checkPropertyExists("minecraftVersion") // hard-coding this makes it harder to immediately tell what version a mod is in (even though this only really supports 1.12.2)
checkPropertyExists("apiPackage")
checkPropertyExists("accessTransformersFile")
checkPropertyExists("usesMixins")
checkPropertyExists("mixinsPackage")
checkPropertyExists("coreModClass")
checkPropertyExists("containsMixinsAndOrCoreModOnly")

// Optional properties: we can assume some default behavior if these are missing
propertyDefaultIfUnset("modVersion", "")
propertyDefaultIfUnset("includeMCVersionJar", false)
propertyDefaultIfUnset("autoUpdateBuildScript", false)
propertyDefaultIfUnset("modArchivesBaseName", project.modId)
propertyDefaultIfUnsetWithEnvVar("developmentEnvironmentUserName", "Developer", "DEV_USERNAME")
propertyDefaultIfUnset("generateGradleTokenClass", "")
propertyDefaultIfUnset("gradleTokenModId", "")
propertyDefaultIfUnset("gradleTokenModName", "")
propertyDefaultIfUnset("gradleTokenVersion", "")
propertyDefaultIfUnset("useSrcApiPath", false)
propertyDefaultIfUnset("includeWellKnownRepositories", true)
propertyDefaultIfUnset("includeCommonDevEnvMods", true)
propertyDefaultIfUnset("noPublishedSources", false)
propertyDefaultIfUnset("forceEnableMixins", false)
propertyDefaultIfUnsetWithEnvVar("enableCoreModDebug", false, "CORE_MOD_DEBUG")
propertyDefaultIfUnset("generateMixinConfig", true)
propertyDefaultIfUnset("usesShadowedDependencies", false)
propertyDefaultIfUnset("minimizeShadowedDependencies", true)
propertyDefaultIfUnset("relocateShadowedDependencies", true)
propertyDefaultIfUnset("separateRunDirectories", false)
propertyDefaultIfUnset("versionDisplayFormat", '$MOD_NAME \u2212 $VERSION')
propertyDefaultIfUnsetWithEnvVar("modrinthProjectId", "", "MODRINTH_PROJECT_ID")
propertyDefaultIfUnset("modrinthRelations", "")
propertyDefaultIfUnsetWithEnvVar("curseForgeProjectId", "", "CURSEFORGE_PROJECT_ID")
propertyDefaultIfUnset("curseForgeRelations", "")
propertyDefaultIfUnsetWithEnvVar("releaseType", "release", "RELEASE_TYPE")
propertyDefaultIfUnset("generateDefaultChangelog", false)
propertyDefaultIfUnset("customMavenPublishUrl", "")
propertyDefaultIfUnset("mavenArtifactGroup", getDefaultArtifactGroup())
propertyDefaultIfUnset("enableModernJavaSyntax", false)
propertyDefaultIfUnset("enableSpotless", false)
propertyDefaultIfUnset("enableJUnit", false)
propertyDefaultIfUnsetWithEnvVar("deploymentDebug", false, "DEPLOYMENT_DEBUG")


// Project property assertions

final String javaSourceDir = 'src/main/java/'
final String scalaSourceDir = 'src/main/scala/'
final String kotlinSourceDir = 'src/main/kotlin/'

final String modGroupPath = modGroup.toString().replace('.' as char, '/' as char)
final String apiPackagePath = apiPackage.toString().replace('.' as char, '/' as char)

String targetPackageJava = javaSourceDir + modGroupPath
String targetPackageScala = scalaSourceDir + modGroupPath
String targetPackageKotlin = kotlinSourceDir + modGroupPath

if (!getFile(targetPackageJava).exists() && !getFile(targetPackageScala).exists() && !getFile(targetPackageKotlin).exists()) {
    throw new GradleException("Could not resolve \"modGroup\"! Could not find ${targetPackageJava} or ${targetPackageScala} or ${targetPackageKotlin}")
}

if (apiPackage) {
    final String endApiPath = modGroupPath + '/' + apiPackagePath
    if (useSrcApiPath) {
        targetPackageJava = 'src/api/java/' + endApiPath
        targetPackageScala = 'src/api/scala/' + endApiPath
        targetPackageKotlin = 'src/api/kotlin/' + endApiPath
    } else {
        targetPackageJava = javaSourceDir + endApiPath
        targetPackageScala = scalaSourceDir + endApiPath
        targetPackageKotlin = kotlinSourceDir + endApiPath
    }
    if (!getFile(targetPackageJava).exists() && !getFile(targetPackageScala).exists() && !getFile(targetPackageKotlin).exists()) {
        throw new GradleException("Could not resolve \"apiPackage\"! Could not find ${targetPackageJava} or ${targetPackageScala} or ${targetPackageKotlin}")
    }
}

if (accessTransformersFile) {
    for (atFile in accessTransformersFile.split(",")) {
        String targetFile = 'src/main/resources/' + atFile.trim()
        if (!getFile(targetFile).exists()) {
            throw new GradleException("Could not resolve \"accessTransformersFile\"! Could not find " + targetFile)
        }
        tasks.deobfuscateMergedJarToSrg.accessTransformerFiles.from(targetFile)
        tasks.srgifyBinpatchedJar.accessTransformerFiles.from(targetFile)
    }
}

if (usesMixins.toBoolean()) {
    if (mixinsPackage.isEmpty()) {
        throw new GradleException("\"usesMixins\" requires \"mixinsPackage\" to be set!")
    }
    final String mixinPackagePath = mixinsPackage.toString().replaceAll('\\.', '/')
    targetPackageJava = javaSourceDir + modGroupPath + '/' + mixinPackagePath
    targetPackageScala = scalaSourceDir + modGroupPath + '/' + mixinPackagePath
    targetPackageKotlin = kotlinSourceDir + modGroupPath + '/' + mixinPackagePath
    if (!getFile(targetPackageJava).exists() && !getFile(targetPackageScala).exists() && !getFile(targetPackageKotlin).exists()) {
        throw new GradleException("Could not resolve \"mixinsPackage\"! Could not find ${targetPackageJava} or ${targetPackageScala} or ${targetPackageKotlin}")
    }
}

if (coreModClass) {
    final String coreModPath = coreModClass.toString().replaceAll('\\.', '/')
    String targetFileJava = javaSourceDir + modGroupPath + '/' + coreModPath + '.java'
    String targetFileScala = scalaSourceDir + modGroupPath + '/' + coreModPath + '.scala'
    String targetFileScalaJava = scalaSourceDir + modGroupPath + '/' + coreModPath + '.java'
    String targetFileKotlin = kotlinSourceDir + modGroupPath + '/' + coreModPath + '.kt'
    if (!getFile(targetFileJava).exists() && !getFile(targetFileScala).exists() && !getFile(targetFileScalaJava).exists() && !getFile(targetFileKotlin).exists()) {
        throw new GradleException("Could not resolve \"coreModClass\"! Could not find ${targetFileJava} or ${targetFileScala} or ${targetFileScalaJava} or ${targetFileKotlin}")
    }
}


// Plugin application

// Scala
if (getFile('src/main/scala').exists()) {
    apply plugin: 'scala'
}

if (getFile('src/main/kotlin').exists()) {
    apply plugin: 'org.jetbrains.kotlin.jvm'
}

// Kotlin
pluginManager.withPlugin('org.jetbrains.kotlin.jvm') {
    kotlin {
        jvmToolchain(8)
    }
    def disabledKotlinTaskList = [
            "kaptGenerateStubsMcLauncherKotlin",
            "kaptGenerateStubsPatchedMcKotlin",
            "kaptGenerateStubsInjectedTagsKotlin",
            "compileMcLauncherKotlin",
            "compilePatchedMcKotlin",
            "compileInjectedTagsKotlin",
            "kaptMcLauncherKotlin",
            "kaptPatchedMcKotlin",
            "kaptInjectedTagsKotlin",
            "kspMcLauncherKotlin",
            "kspPatchedMcKotlin",
            "kspInjectedTagsKotlin",
    ]
    tasks.configureEach { task ->
        if (task.name in disabledKotlinTaskList) {
            task.enabled = false
        }
    }
}

// Spotless
//noinspection GroovyAssignabilityCheck
project.extensions.add(com.diffplug.blowdryer.Blowdryer, 'Blowdryer', com.diffplug.blowdryer.Blowdryer) // make Blowdryer available in plugin application
if (enableSpotless.toBoolean()) {
    apply plugin: 'com.diffplug.spotless'

    // Spotless auto-formatter
    // See https://github.com/diffplug/spotless/tree/main/plugin-gradle
    // Can be locally toggled via spotless:off/spotless:on comments
    spotless {
        encoding 'UTF-8'

        format 'misc', {
            target '.gitignore'

            trimTrailingWhitespace()
            indentWithSpaces(4)
            endWithNewline()
        }
        java {
            target 'src/main/java/**/*.java', 'src/test/java/**/*.java' // exclude api as they are not our files

            def orderFile = project.file('spotless.importorder')
            if (!orderFile.exists()) {
                orderFile = Blowdryer.file('spotless.importorder')
            }
            def formatFile = project.file('spotless.eclipseformat.xml')
            if (!formatFile.exists()) {
                formatFile = Blowdryer.file('spotless.eclipseformat.xml')
            }

            toggleOffOn()
            importOrderFile(orderFile)
            removeUnusedImports()
            endWithNewline()
            //noinspection GroovyAssignabilityCheck
            eclipse('4.19.0').configFile(formatFile)
        }
        kotlin {
            target 'src/*/kotlin/**/*.kt'

            toggleOffOn()
            ktfmt('0.39')

            trimTrailingWhitespace()
            indentWithSpaces(4)
            endWithNewline()
        }
        scala {
            target 'src/*/scala/**/*.scala'
            scalafmt('3.7.1')
        }
    }
}

// Git version checking, also checking for if this is a submodule
if (project.file('.git/HEAD').isFile() || project.file('.git').isFile()) {
    apply plugin: 'com.palantir.git-version'
}

// Shadowing
if (usesShadowedDependencies.toBoolean()) {
    apply plugin: 'com.github.johnrengelman.shadow'
}


// Configure Java

java {
    toolchain {
        if (enableModernJavaSyntax.toBoolean()) {
            languageVersion.set(JavaLanguageVersion.of(17))
        } else {
            languageVersion.set(JavaLanguageVersion.of(8))
        }
        // Azul covers the most platforms for Java 8+ toolchains, crucially including MacOS arm64
        vendor.set(JvmVendorSpec.AZUL)
    }
    if (!noPublishedSources.toBoolean()) {
        withSourcesJar()
    }
}

tasks.withType(JavaCompile).configureEach {
    options.encoding = 'UTF-8'

    if (enableModernJavaSyntax.toBoolean()) {
        if (it.name in ['compileMcLauncherJava', 'compilePatchedMcJava']) {
            return
        }

        sourceCompatibility = 17
        options.release.set(8)

        javaCompiler.set(javaToolchains.compilerFor {
            languageVersion.set(JavaLanguageVersion.of(17))
            vendor.set(JvmVendorSpec.AZUL)
        })
    }
}

tasks.withType(ScalaCompile).configureEach {
    options.encoding = 'UTF-8'
}


// Allow others using this buildscript to have custom gradle code run
if (getFile('addon.gradle').exists()) {
    apply from: 'addon.gradle'
} else if (getFile('addon.gradle.kts').exists()) {
    apply from: 'addon.gradle.kts'
}


// Configure Minecraft

// Try to gather mod version from git tags if version is not manually specified
if (!modVersion) {
    try {
        modVersion = gitVersion()
    } catch (Exception ignored) {
        out.style(Style.Failure).text(
                "Mod version could not be determined! Property 'modVersion' is not set, and either git is not installed or no git tags exist.\n" +
                        "Either specify a mod version in 'gradle.properties', or create at least one tag in git for this project."
        )
        modVersion = 'NO-GIT-TAG-SET'
    }
}

if (includeMCVersionJar.toBoolean()){
    version = "${minecraftVersion}-${modVersion}"
}
else {
    version = modVersion
}

group = modGroup

base {
    archivesName = modArchivesBaseName
}

minecraft {
    mcVersion = minecraftVersion
    username = developmentEnvironmentUserName.toString()
    useDependencyAccessTransformers = true

    // Automatic token injection with RetroFuturaGradle
    if (gradleTokenModId) {
        injectedTags.put gradleTokenModId, modId
    }
    if (gradleTokenModName) {
        injectedTags.put gradleTokenModName, modName
    }
    if (gradleTokenVersion) {
        injectedTags.put gradleTokenVersion, modVersion
    }

    // JVM arguments
    extraRunJvmArguments.add("-ea:${modGroup}")
    if (usesMixins.toBoolean()) {
        extraRunJvmArguments.addAll([
            '-Dmixin.hotSwap=true',
            '-Dmixin.checks.interfaces=true',
            '-Dmixin.debug.export=true'
        ])
    }

    if (enableCoreModDebug.toBoolean()) {
        extraRunJvmArguments.addAll([
                '-Dlegacy.debugClassLoading=true',
                '-Dlegacy.debugClassLoadingFiner=true',
                '-Dlegacy.debugClassLoadingSave=true'
        ])
    }
}

if (coreModClass) {
    for (runTask in ['runClient', 'runServer']) {
        tasks.named(runTask).configure {
            extraJvmArgs.add("-Dfml.coreMods.load=${modGroup}.${coreModClass}")
        }
    }
}

if (generateGradleTokenClass) {
    tasks.injectTags.outputClassName.set(generateGradleTokenClass)
}

tasks.named('processIdeaSettings').configure {
    dependsOn('injectTags')
}


// Repositories

// Allow unsafe repos but warn
repositories.configureEach { repo ->
    if (repo instanceof UrlArtifactRepository) {
        if (repo.getUrl() != null && repo.getUrl().getScheme() == "http" && !repo.allowInsecureProtocol) {
            logger.warn("Deprecated: Allowing insecure connections for repo '${repo.name}' - add 'allowInsecureProtocol = true'")
            repo.allowInsecureProtocol = true
        }
    }
}

// Allow adding custom repositories to the buildscript
if (getFile('repositories.gradle').exists()) {
    apply from: 'repositories.gradle'
} else if (getFile('repositories.gradle.kts').exists()) {
    apply from: 'repositories.gradle.kts'
}

repositories {
    if (includeWellKnownRepositories.toBoolean() || includeCommonDevEnvMods.toBoolean()) {
        exclusiveContent {
            forRepository {
                //noinspection ForeignDelegate
                maven {
                    name = 'Curse Maven'
                    url = 'https://www.cursemaven.com'
                    // url = 'https://beta.cursemaven.com'
                }
            }
            filter {
                includeGroup 'curse.maven'
            }
        }
        exclusiveContent {
            forRepository {
                //noinspection ForeignDelegate
                maven {
                    name = 'Modrinth'
                    url = 'https://api.modrinth.com/maven'
                }
            }
            filter {
                includeGroup 'maven.modrinth'
            }
        }
        maven {
            name 'Cleanroom Maven'
            url 'https://maven.cleanroommc.com'
        }
        maven {
            name 'BlameJared Maven'
            url 'https://maven.blamejared.com'
        }
        maven {
            name 'GTNH Maven'
            url 'https://nexus.gtnewhorizons.com/repository/public/'
        }
    }
    if (usesMixins.toBoolean() || forceEnableMixins.toBoolean()) {
        // need to add this here even if we did not above
        if (!includeWellKnownRepositories.toBoolean()) {
            maven {
                name 'Cleanroom Maven'
                url 'https://maven.cleanroommc.com'
            }
        }
    }
    mavenLocal() // Must be last for caching to work
}


// Dependencies

// Configure dependency configurations
configurations {
    embed
    implementation.extendsFrom(embed)

    if (usesShadowedDependencies.toBoolean()) {
        for (config in [compileClasspath, runtimeClasspath, testCompileClasspath, testRuntimeClasspath]) {
            config.extendsFrom(shadowImplementation)
            config.extendsFrom(shadowCompile)
        }
    }
}

String mixinProviderSpec = 'zone.rong:mixinbooter:8.9'
dependencies {
    if (usesMixins.toBoolean()) {
        annotationProcessor 'org.ow2.asm:asm-debug-all:5.2'
        // should use 24.1.1 but 30.0+ has a vulnerability fix
        annotationProcessor 'com.google.guava:guava:30.0-jre'
        // should use 2.8.6 but 2.8.9+ has a vulnerability fix
        annotationProcessor 'com.google.code.gson:gson:2.8.9'

        mixinProviderSpec = modUtils.enableMixins(mixinProviderSpec, "mixins.${modId}.refmap.json")
        api (mixinProviderSpec) {
            transitive = false
        }

        annotationProcessor(mixinProviderSpec) {
            transitive = false
        }
    } else if (forceEnableMixins.toBoolean()) {
        runtimeOnly(mixinProviderSpec)
    }

    if (enableJUnit.toBoolean()) {
        testImplementation 'org.hamcrest:hamcrest:2.2'
        testImplementation 'org.junit.jupiter:junit-jupiter:5.9.2'
        testRuntimeOnly 'org.junit.platform:junit-platform-launcher'
    }

    if (enableModernJavaSyntax.toBoolean()) {
        annotationProcessor 'com.github.bsideup.jabel:jabel-javac-plugin:1.0.0'
        compileOnly('com.github.bsideup.jabel:jabel-javac-plugin:1.0.0') {
            transitive = false
        }
        // workaround for https://github.com/bsideup/jabel/issues/174
        annotationProcessor 'net.java.dev.jna:jna-platform:5.13.0'
        // Allow jdk.unsupported classes like sun.misc.Unsafe, workaround for JDK-8206937 and fixes Forge crashes in tests.
        patchedMinecraft 'me.eigenraven.java8unsupported:java-8-unsupported-shim:1.0.0'

        // allow Jabel to work in tests
        testAnnotationProcessor "com.github.bsideup.jabel:jabel-javac-plugin:1.0.0"
        testCompileOnly("com.github.bsideup.jabel:jabel-javac-plugin:1.0.0") {
            transitive = false // We only care about the 1 annotation class
        }
        testCompileOnly "me.eigenraven.java8unsupported:java-8-unsupported-shim:1.0.0"
    }

    compileOnlyApi 'org.jetbrains:annotations:24.1.0'
    annotationProcessor 'org.jetbrains:annotations:24.1.0'
    patchedMinecraft('net.minecraft:launchwrapper:1.17.2') {
        transitive = false
    }

    if (includeCommonDevEnvMods.toBoolean()) {
        implementation 'mezz.jei:jei_1.12.2:4.16.1.302'
        //noinspection DependencyNotationArgument
        implementation rfg.deobf('curse.maven:top-245211:2667280') // TOP 1.4.28
    }
}

pluginManager.withPlugin('org.jetbrains.kotlin.kapt') {
    if (usesMixins.toBoolean()) {
        dependencies {
            kapt(mixinProviderSpec)
        }
    }
}

if (getFile('dependencies.gradle').exists()) {
    apply from: 'dependencies.gradle'
} else if (getFile('dependencies.gradle.kts').exists()) {
    apply from: 'dependencies.gradle.kts'
}


// Test configuration

// Ensure tests have access to minecraft classes
sourceSets {
    test {
        java {
            compileClasspath += patchedMc.output + mcLauncher.output
            runtimeClasspath += patchedMc.output + mcLauncher.output
        }
    }
}

test {
    // ensure tests are run with java8
    javaLauncher = javaToolchains.launcherFor {
        languageVersion = JavaLanguageVersion.of(8)
    }.get()

    testLogging {
        events TestLogEvent.STARTED, TestLogEvent.PASSED, TestLogEvent.FAILED
        exceptionFormat TestExceptionFormat.FULL
        showExceptions true
        showStackTraces true
        showCauses true
        showStandardStreams true
    }

    if (enableJUnit.toBoolean()) {
        useJUnitPlatform()
    }
}


// Resource processing and jar building

processResources {
    // this will ensure that this task is redone when the versions change.
    inputs.property 'version', modVersion
    inputs.property 'mcversion', minecraftVersion
    // Blowdryer puts these files into the resource directory, so
    // exclude them from builds (doesn't hurt to exclude even if not present)
    exclude('spotless.importorder')
    exclude('spotless.eclipseformat.xml')

    // replace stuff in mcmod.info, nothing else
    filesMatching('mcmod.info') { fcd ->
        fcd.expand(
                'version': modVersion,
                'mcversion': minecraftVersion,
                'modid': modId,
                'modname': modName
        )
    }

    if (accessTransformersFile) {
        String[] ats = accessTransformersFile.split(',')
        ats.each { at ->
            rename "(${at})", 'META-INF/$1'
        }
    }
}

// Automatically generate a mixin json file if it does not already exist
tasks.register('generateAssets') {
    group = 'GT Buildscript'
    description = 'Generates a pack.mcmeta, mcmod.info, or mixins.{modid}.json if needed'
    doLast {
        // pack.mcmeta
        def packMcmetaFile = getFile('src/main/resources/pack.mcmeta')
        if (!packMcmetaFile.exists()) {
            packMcmetaFile.text = """{
  "pack": {
    "pack_format": 3,
    "description": "${modName} Resource Pack"
  }
}
"""
        }

        // mcmod.info
        def mcmodInfoFile = getFile('src/main/resources/mcmod.info')
        if (!mcmodInfoFile.exists()) {
            mcmodInfoFile.text = """[{
    "modid": "\${modid}",
    "name": "\${modname}",
    "description": "An example mod for Minecraft 1.12.2 with Forge",
    "version": "\${version}",
    "mcversion": "\${mcversion}",
    "logoFile": "",
    "url": "",
    "authorList": [],
    "credits": "",
    "dependencies": []
}]
"""
        }

        // mixins.{modid}.json
        if (usesMixins.toBoolean() && generateMixinConfig.toBoolean()) {
            def mixinConfigFile = getFile("src/main/resources/mixins.${modId}.json")
            if (!mixinConfigFile.exists()) {
                def mixinConfigRefmap = "mixins.${modId}.refmap.json"

                mixinConfigFile.text = """{
  "package": "${modGroup}.${mixinsPackage}",
  "refmap": "${mixinConfigRefmap}",
  "target": "@env(DEFAULT)",
  "minVersion": "0.8",
  "compatibilityLevel": "JAVA_8",
  "mixins": [],
  "client": [],
  "server": []
}
"""
            }
        }
    }
}

tasks.named('processResources').configure {
    dependsOn('generateAssets')
}

jar {
    manifest {
        attributes(getManifestAttributes())
    }

    // Add all embedded dependencies into the jar
    from provider {
        configurations.embed.collect {
            it.isDirectory() ? it : zipTree(it)
        }
    }

    if (useSrcApiPath && apiPackage) {
        from sourceSets.api.output
        dependsOn apiClasses

        include "${modGroupPath}/**"
        include "assets/**"
        include "mcmod.info"
        include "pack.mcmeta"
        if (accessTransformersFile) {
            include "META-INF/${accessTransformersFile}"
        }
    }
}

// Configure default run tasks
if (separateRunDirectories.toBoolean()) {
    runClient {
        workingDir = file('run/client')
    }

    runServer {
        workingDir = file('run/server')
    }
}

// Create API library jar
tasks.register('apiJar', Jar) {
    archiveClassifier.set 'api'
    if (useSrcApiPath) {
        from(sourceSets.api.java) {
            include "${modGroupPath}/${apiPackagePath}/**"
        }
        from(sourceSets.api.output) {
            include "${modGroupPath}/${apiPackagePath}/**"
        }
    } else {
        from(sourceSets.main.java) {
            include "${modGroupPath}/${apiPackagePath}/**"
        }

        from(sourceSets.main.output) {
            include "${modGroupPath}/${apiPackagePath}/**"
        }
    }
}

// Configure shadow jar task
if (usesShadowedDependencies.toBoolean()) {
    tasks.named('shadowJar', ShadowJar).configure {
        manifest {
            attributes(getManifestAttributes())
        }
        // Only shadow classes that are actually used, if enabled
        if (minimizeShadowedDependencies.toBoolean()) {
            minimize()
        }
        configurations = [
                project.configurations.shadowImplementation,
                project.configurations.shadowCompile
        ]
        archiveClassifier.set('dev')
        if (relocateShadowedDependencies.toBoolean()) {
            relocationPrefix = modGroup + '.shadow'
            enableRelocation = true
        }
    }
    configurations.runtimeElements.outgoing.artifacts.clear()
    configurations.apiElements.outgoing.artifacts.clear()
    configurations.runtimeElements.outgoing.artifact(tasks.named('shadowJar', ShadowJar))
    configurations.apiElements.outgoing.artifact(tasks.named('shadowJar', ShadowJar))
    tasks.named('jar', Jar) {
        enabled = false
        finalizedBy(tasks.shadowJar)
    }
    tasks.named('reobfJar', ReobfuscatedJar) {
        inputJar.set(tasks.named('shadowJar', ShadowJar).flatMap({it.archiveFile}))
    }
    AdhocComponentWithVariants javaComponent = (AdhocComponentWithVariants) project.components.findByName('java')
    javaComponent.withVariantsFromConfiguration(configurations.shadowRuntimeElements) {
        skip()
    }
    for (runTask in ['runClient', 'runServer']) {
        tasks.named(runTask).configure {
            dependsOn('shadowJar')
        }
    }
}

def getManifestAttributes() {
    def attributes = [:]
    if (coreModClass) {
        attributes['FMLCorePlugin'] = "${modGroup}.${coreModClass}"
    }
    if (!containsMixinsAndOrCoreModOnly.toBoolean() && (usesMixins.toBoolean() || coreModClass)) {
        attributes['FMLCorePluginContainsFMLMod'] = true
    }
    if (accessTransformersFile) {
        attributes['FMLAT'] = accessTransformersFile.toString()
    }

    if (usesMixins.toBoolean()) {
        attributes['ForceLoadAsMod'] = !containsMixinsAndOrCoreModOnly.toBoolean()
    }
    return attributes
}


// IDE Configuration

eclipse {
    classpath {
        downloadSources = true
        downloadJavadoc = true
    }
}

idea {
    module {
        inheritOutputDirs true
        downloadJavadoc true
        downloadSources true
    }
    project {
        settings {
            runConfigurations {
                '1. Setup Workspace'(Gradle) {
                    taskNames = ['setupDecompWorkspace']
                }
                '2. Run Client'(Gradle) {
                    taskNames = ['runClient']
                }
                '3. Run Server'(Gradle) {
                    taskNames = ['runServer']
                }
                '4. Run Obfuscated Client'(Gradle) {
                    taskNames = ['runObfClient']
                }
                '5. Run Obfuscated Server'(Gradle) {
                    taskNames = ['runObfServer']
                }
                if (enableSpotless.toBoolean()) {
                    '6. Apply Spotless'(Gradle) {
                        taskNames = ["spotlessApply"]
                    }
                    '7. Build Jars'(Gradle) {
                        taskNames = ['build']
                    }
                } else {
                    '6. Build Jars'(Gradle) {
                        taskNames = ['build']
                    }
                }
                'Update Buildscript'(Gradle) {
                    taskNames = ['updateBuildScript']
                }
                'FAQ'(Gradle) {
                    taskNames = ['faq']
                }
            }
            compiler.javac {
                afterEvaluate {
                    javacAdditionalOptions = '-encoding utf8'
                    moduleJavacAdditionalOptions = [
                            (project.name + '.main'): tasks.compileJava.options.compilerArgs.collect {
                                '"' + it + '"'
                            }.join(' ')
                    ]
                }
            }
        }
    }
}


// Deployment
def final modrinthApiKey = providers.environmentVariable('MODRINTH_API_KEY')
def final cfApiKey = providers.environmentVariable('CURSEFORGE_API_KEY')
final boolean isCIEnv = providers.environmentVariable('CI').getOrElse('false').toBoolean()

if (isCIEnv || deploymentDebug.toBoolean()) {
    artifacts {
        if (!noPublishedSources.toBoolean()) {
            archives sourcesJar
        }
        if (apiPackage) {
            archives apiJar
        }
    }
}

// Changelog generation
tasks.register('generateChangelog') {
    group = 'GT Buildscript'
    description = 'Generate a default changelog of all commits since the last tagged git commit'
    onlyIf {
        generateDefaultChangelog.toBoolean()
    }
    doLast {
        def lastTag = getLastTag()

        def changelog = runShell(([
                "git",
                "log",
                "--date=format:%d %b %Y",
                "--pretty=%s - **%an** (%ad)",
                "${lastTag}..HEAD"
        ] + (sourceSets.main.java.srcDirs + sourceSets.main.resources.srcDirs)
                .collect { ['--', it] }).flatten())

        if (changelog) {
            changelog = "Changes since ${lastTag}:\n${{("\n" + changelog).replaceAll("\n", "\n* ")}}"
        }
        def f = getFile('build/changelog.md')
        changelog = changelog ?: 'There have been no changes.'
        f.write(changelog, 'UTF-8')

        // Set changelog for Modrinth
        if (modrinthApiKey.isPresent() || deploymentDebug.toBoolean()) {
            modrinth.changelog.set(changelog)
        }
    }
}

if (cfApiKey.isPresent() || deploymentDebug.toBoolean()) {
    apply plugin: 'net.darkhax.curseforgegradle'
    //noinspection UnnecessaryQualifiedReference
    tasks.register('curseforge', net.darkhax.curseforgegradle.TaskPublishCurseForge) {
        disableVersionDetection()
        debugMode = deploymentDebug.toBoolean()
        apiToken = cfApiKey.getOrElse('debug_token')

        doFirst {
            def mainFile = upload(curseForgeProjectId, reobfJar)
            def changelogFile = getChangelog()
            def changelogRaw = changelogFile.exists() ? changelogFile.getText('UTF-8') : ""

            mainFile.displayName = versionDisplayFormat.replace('$MOD_NAME', modName).replace('$VERSION', modVersion)
            mainFile.releaseType = getReleaseType()
            mainFile.changelog = changelogRaw
            mainFile.changelogType = 'markdown'
            mainFile.addModLoader 'Forge'
            mainFile.addJavaVersion "Java 8"
            mainFile.addGameVersion minecraftVersion

            if (curseForgeRelations.size() != 0) {
                String[] deps = curseForgeRelations.split(';')
                deps.each { dep ->
                    if (dep.size() == 0) {
                        return
                    }
                    String[] parts = dep.split(':')
                    String type = parts[0], slug = parts[1]
                    def types = [
                            'req'   : 'requiredDependency', 'required': 'requiredDependency',
                            'opt'   : 'optionalDependency', 'optional': 'optionalDependency',
                            'embed' : 'embeddedLibrary',    'embedded': 'embeddedLibrary',
                            'incomp': 'incompatible',       'fail'    : 'incompatible']
                    if (types.containsKey(type)) type = types[type]
                    if (!(type in ['requiredDependency', 'embeddedLibrary', 'optionalDependency', 'tool', 'incompatible'])) {
                        throw new Exception('Invalid Curseforge dependency type: ' + type)
                    }
                    mainFile.addRelation(slug, type)
                }
            }

            for (artifact in getSecondaryArtifacts()) {
                def additionalFile = mainFile.withAdditionalFile(artifact)
                additionalFile.changelog = changelogRaw
            }
        }
    }
    tasks.curseforge.dependsOn(build)
    tasks.curseforge.dependsOn('generateChangelog')
}

if (modrinthApiKey.isPresent() || deploymentDebug.toBoolean()) {
    apply plugin: 'com.modrinth.minotaur'
    def final changelogFile = getChangelog()

    modrinth {
        token = modrinthApiKey.getOrElse('debug_token')
        projectId = modrinthProjectId
        versionName = versionDisplayFormat.replace('$MOD_NAME', modName).replace('$VERSION', modVersion)
        changelog = changelogFile.exists() ? changelogFile.getText('UTF-8') : ""
        versionType = getReleaseType()
        versionNumber = modVersion
        gameVersions = [minecraftVersion]
        loaders = ["forge"]
        debugMode = deploymentDebug.toBoolean()
        uploadFile = reobfJar
        additionalFiles = getSecondaryArtifacts()
    }
    if (modrinthRelations.size() != 0) {
        String[] deps = modrinthRelations.split(';')
        deps.each { dep ->
            if (dep.size() == 0) {
                return
            }
            String[] parts = dep.split(':')
            String[] qual = parts[0].split('-')
            addModrinthDep(qual[0], qual.length > 1 ? qual[1] : 'project', parts[1])
        }
    }
    tasks.modrinth.dependsOn(build)
    tasks.modrinth.dependsOn('generateChangelog')
}

def addModrinthDep(String scope, String type, String name) {
    com.modrinth.minotaur.dependencies.Dependency dep
    def types = [
            'req'   : 'required',
            'opt'   : 'optional',
            'embed' : 'embedded',
            'incomp': 'incompatible', 'fail': 'incompatible']
    if (types.containsKey(scope)) scope = types[scope]
    if (!(scope in ['required', 'optional', 'incompatible', 'embedded'])) {
        throw new Exception('Invalid modrinth dependency scope: ' + scope)
    }
    types = ['proj': 'project', '': 'project', 'p': 'project', 'ver': 'version', 'v': 'version']
    if (types.containsKey(type)) type = types[type]
    switch (type) {
        case 'project':
            dep = new ModDependency(name, scope)
            break
        case 'version':
            dep = new VersionDependency(name, scope)
            break
        default:
            throw new Exception('Invalid modrinth dependency type: ' + type)
    }
    project.modrinth.dependencies.add(dep)
}

if (customMavenPublishUrl) {
    String publishedVersion = modVersion

    publishing {
        publications {
            create('maven', MavenPublication) {
                //noinspection GroovyAssignabilityCheck
                from components.java

                if (apiPackage) {
                    artifact apiJar
                }

                // providers is not available here, use System for getting env vars
                groupId = System.getenv('ARTIFACT_GROUP_ID') ?: project.mavenArtifactGroup
                artifactId = System.getenv('ARTIFACT_ID') ?: project.modArchivesBaseName
                version = System.getenv('RELEASE_VERSION') ?: publishedVersion
            }
        }

        repositories {
            maven {
                url = customMavenPublishUrl
                allowInsecureProtocol = !customMavenPublishUrl.startsWith('https')
                credentials {
                    username = providers.environmentVariable('MAVEN_USER').getOrElse('NONE')
                    password = providers.environmentVariable('MAVEN_PASSWORD').getOrElse('NONE')
                }
            }
        }
    }
}

def getSecondaryArtifacts() {
    def secondaryArtifacts = [usesShadowedDependencies.toBoolean() ? tasks.shadowJar : tasks.jar]
    if (!noPublishedSources.toBoolean()) secondaryArtifacts += [sourcesJar]
    if (apiPackage) secondaryArtifacts += [apiJar]
    return secondaryArtifacts
}

def getReleaseType() {
    String type = project.releaseType
    if (!(type in ['release', 'beta', 'alpha'])) {
        throw new Exception("Release type invalid! Found \"" + type + "\", allowed: \"release\", \"beta\", \"alpha\"")
    }
    return type
}

/*
 * If CHANGELOG_LOCATION env var is set, that takes highest precedence.
 * Next, if 'generateDefaultChangelog' option is enabled, use that.
 * Otherwise, try to use a CHANGELOG.md file at root directory.
 */
def getChangelog() {
    def final changelogEnv = providers.environmentVariable('CHANGELOG_LOCATION')
    if (changelogEnv.isPresent()) {
        return new File(changelogEnv.get())
    }
    if (generateDefaultChangelog.toBoolean()) {
        return getFile('build/changelog.md')
    }
    return getFile('CHANGELOG.md')
}


// Buildscript updating

def buildscriptGradleVersion = '8.5'

tasks.named('wrapper', Wrapper).configure {
    gradleVersion = buildscriptGradleVersion
}

tasks.register('updateBuildScript') {
    group = 'GT Buildscript'
    description = 'Updates the build script to the latest version'

    if (gradle.gradleVersion != buildscriptGradleVersion && !Boolean.getBoolean('DISABLE_BUILDSCRIPT_GRADLE_UPDATE')) {
        dependsOn('wrapper')
    }

    doLast {
        if (performBuildScriptUpdate()) return
        print('Build script already up to date!')
    }
}

if (!project.getGradle().startParameter.isOffline() && !Boolean.getBoolean('DISABLE_BUILDSCRIPT_UPDATE_CHECK') && isNewBuildScriptVersionAvailable()) {
    if (autoUpdateBuildScript.toBoolean()) {
        performBuildScriptUpdate()
    } else {
        out.style(Style.SuccessHeader).println("Build script update available! Run 'gradle updateBuildScript'")
        if (gradle.gradleVersion != buildscriptGradleVersion) {
            out.style(Style.SuccessHeader).println("updateBuildScript can update gradle from ${gradle.gradleVersion} to ${buildscriptGradleVersion}\n")
        }
    }
}

static URL availableBuildScriptUrl() {
    new URL("https://raw.githubusercontent.com/GregTechCEu/Buildscripts/master/build.gradle")
}

static URL availableSettingsGradleUrl() {
    new URL("https://raw.githubusercontent.com/GregTechCEu/Buildscripts/master/settings.gradle")
}

boolean performBuildScriptUpdate() {
    if (isNewBuildScriptVersionAvailable()) {
        def buildscriptFile = getFile("build.gradle")
        def settingsFile = getFile("settings.gradle")
        availableBuildScriptUrl().withInputStream { i -> buildscriptFile.withOutputStream { it << i } }
        availableSettingsGradleUrl().withInputStream { i -> settingsFile.withOutputStream { it << i } }
        def out = services.get(StyledTextOutputFactory).create('buildscript-update-output')
        out.style(Style.Success).print("Build script updated. Please REIMPORT the project or RESTART your IDE!")
        return true
    }
    return false
}

boolean isNewBuildScriptVersionAvailable() {
    Map parameters = ["connectTimeout": 10000, "readTimeout": 10000]

    String currentBuildScript = getFile("build.gradle").getText()
    String currentBuildScriptHash = getVersionHash(currentBuildScript)
    String availableBuildScript = availableBuildScriptUrl().newInputStream(parameters).getText()
    String availableBuildScriptHash = getVersionHash(availableBuildScript)

    boolean isUpToDate = currentBuildScriptHash.empty || availableBuildScriptHash.empty || currentBuildScriptHash == availableBuildScriptHash
    return !isUpToDate
}

static String getVersionHash(String buildScriptContent) {
    String versionLine = buildScriptContent.find("^//version: [a-z0-9]*")
    if (versionLine != null) {
        return versionLine.split(": ").last()
    }
    return ""
}


// Faq

tasks.register('faq') {
    group = 'GT Buildscript'
    description = 'Prints frequently asked questions about building a project'
    doLast {
        print("\nTo update this buildscript to the latest version, run 'gradlew updateBuildScript' or run the generated run configuration if you are using IDEA.\n" +
                "To set up the project, run the 'setupDecompWorkspace' task, which you can run as './gradlew setupDecompWorkspace' in a terminal, or find in the 'modded minecraft' gradle category.\n\n" +
                "To add new dependencies to your project, place them in 'dependencies.gradle', NOT in 'build.gradle' as they would be replaced when the script updates.\n" +
                "To add new repositories to your project, place them in 'repositories.gradle'.\n" +
                "If you need additional gradle code to run, you can place it in a file named 'addon.gradle' (or either of the above, up to you for organization).\n\n" +
                "If your build fails to recognize the syntax of newer Java versions, enable Jabel in your 'gradle.properties' under the option name 'enableModernJavaSyntax'.\n" +
                "To see information on how to configure your IDE properly for Java 17, see https://github.com/GregTechCEu/Buildscripts/blob/master/docs/jabel.md\n\n" +
                "Report any issues or feature requests you have for this build script to https://github.com/GregTechCEu/Buildscripts/issues\n")
    }
}


// Helpers

def getDefaultArtifactGroup() {
    def lastIndex = project.modGroup.lastIndexOf('.')
    return lastIndex < 0 ? project.modGroup : project.modGroup.substring(0, lastIndex)
}

def getFile(String relativePath) {
    return new File(projectDir, relativePath)
}

def checkPropertyExists(String propertyName) {
    if (!project.hasProperty(propertyName)) {
        throw new GradleException("This project requires a property \"" + propertyName + "\"! Please add it your \"gradle.properties\". You can find all properties and their description here: https://github.com/GregTechCEu/Buildscripts/blob/main/gradle.properties")
    }
}

def propertyDefaultIfUnset(String propertyName, defaultValue) {
    if (!project.hasProperty(propertyName) || project.property(propertyName) == "") {
        project.ext.setProperty(propertyName, defaultValue)
    }
}

def propertyDefaultIfUnsetWithEnvVar(String propertyName, defaultValue, String envVarName) {
    def envVar = providers.environmentVariable(envVarName)
    if (envVar.isPresent()) {
        project.ext.setProperty(propertyName, envVar.get())
    } else {
        propertyDefaultIfUnset(propertyName, defaultValue)
    }
}

static runShell(command) {
    def process = command.execute()
    def outputStream = new StringBuffer()
    def errorStream = new StringBuffer()
    process.waitForProcessOutput(outputStream, errorStream)

    errorStream.toString().with {
        if (it) {
            throw new GradleException("Error executing ${command}:\n> ${it}")
        }
    }
    return outputStream.toString().trim()
}

def getLastTag() {
    def githubTag = providers.environmentVariable('GITHUB_TAG')
    return runShell('git describe --abbrev=0 --tags ' +
            (githubTag.isPresent() ? runShell('git rev-list --tags --skip=1 --max-count=1') : ''))
}
