import org.gradle.api.GradleException
import org.gradle.api.artifacts.Configuration
import org.gradle.api.component.AdhocComponentWithVariants
import org.gradle.api.publish.maven.MavenPublication
import org.gradle.api.tasks.bundling.Jar
import org.gradle.api.tasks.testing.Test
import org.gradle.kotlin.dsl.*

plugins {
  `java-library`
  `maven-publish`
  id("com.github.johnrengelman.shadow")
}

// make fat Jar available to other subprojects
val fatJarConfiguration: Configuration = configurations.create("fatJar")

val fatJarPublication: MavenPublication = publishing.publications.create<MavenPublication>("fatJar")

// ideally we'd configure this automatically based on project dependencies
val firstPartySourcesJarsConfiguration: Configuration = configurations.create("firstPartySourcesJars")

val relocations = mapOf(
  // pkl-core dependencies
  "org.antlr.v4." to "org.pkl.thirdparty.antlr.v4.",
  // https://github.com/oracle/graal/issues/1644 has been fixed,
  // but native-image still fails when shading com.oracle.truffle 
  //"com.oracle.truffle" to "org.pkl.thirdparty.truffle",
  "org.graalvm." to "org.pkl.thirdparty.graalvm.",
  "org.organicdesign.fp." to "org.pkl.thirdparty.paguro.",
  "org.snakeyaml.engine." to "org.pkl.thirdparty.snakeyaml.engine.",
  "org.msgpack." to "org.pkl.thirdparty.msgpack.",
  "org.w3c.dom." to "org.pkl.thirdparty.w3c.dom",
  "com.oracle.svm.core." to "org.pkl.thirdparty.svm.",

  // pkl-cli dependencies
  "org.jline." to "org.pkl.thirdparty.jline.",
  "com.github.ajalt.clikt." to "org.pkl.thirdparty.clikt.",
  "kotlin." to "org.pkl.thirdparty.kotlin.",
  "kotlinx." to "org.pkl.thirdparty.kotlinx.",
  "org.intellij." to "org.pkl.thirdparty.intellij.",
  "org.fusesource.jansi." to "org.pkl.thirdparty.jansi",
  "org.fusesource.hawtjni." to "org.pkl.thirdparty.hawtjni",

  // pkl-doc dependencies
  "org.commonmark." to "org.pkl.thirdparty.commonmark.",
  "org.jetbrains." to "org.pkl.thirdparty.jetbrains.",
  
  // pkl-config-java dependencies
  "io.leangen.geantyref." to "org.pkl.thirdparty.geantyref.",

  // pkl-codegen-java dependencies
  "com.squareup.javapoet." to "org.pkl.thirdparty.javapoet.",

  // pkl-codegen-kotlin dependencies
  "com.squareup.kotlinpoet." to "org.pkl.thirdparty.kotlinpoet.",
)

val nonRelocations = listOf("com/oracle/truffle/")

tasks.shadowJar {
  inputs.property("relocations", relocations)

  archiveClassifier.set(null as String?)

  configurations = listOf(project.configurations.runtimeClasspath.get())

  exclude("META-INF/maven/**")
  exclude("META-INF/upgrade/**")
  exclude("META-INF/versions/19/**")

  // org.antlr.v4.runtime.misc.RuleDependencyProcessor
  exclude("META-INF/services/javax.annotation.processing.Processor")

  exclude("module-info.*")

  for ((from, to) in relocations) {
    relocate(from, to)
  }

  // necessary for service files to be adapted to relocation
  mergeServiceFiles()
}

// workaround for https://github.com/johnrengelman/shadow/issues/651
components.withType(AdhocComponentWithVariants::class.java).forEach { c ->
  c.withVariantsFromConfiguration(project.configurations.shadowRuntimeElements.get()) {
    skip()
  }
}

val testFatJar by tasks.registering(Test::class) {
  testClassesDirs = files(tasks.test.get().testClassesDirs)
  classpath =
    // compiled test classes
    sourceSets.test.get().output +
      // fat Jar
      tasks.shadowJar.get().outputs.files +
      // test-only dependencies
      // (test dependencies that are also main dependencies must already be contained in fat Jar;
      // to verify that, we don't want to include them here)
      (configurations.testRuntimeClasspath.get() - configurations.runtimeClasspath.get())
}

tasks.check {
  dependsOn(testFatJar)
}

val validateFatJar by tasks.registering {
  val outputFile = file("$buildDir/validateFatJar/result.txt")
  inputs.files(tasks.shadowJar)
  inputs.property("nonRelocations", nonRelocations)
  outputs.file(outputFile)

  doLast {
    val unshadowedFiles = mutableListOf<String>()
    zipTree(tasks.shadowJar.get().outputs.files.singleFile).visit {
      val fileDetails = this
      val path = fileDetails.relativePath.pathString
      if (!(fileDetails.isDirectory ||
          path.startsWith("org/pkl/") ||
          path.startsWith("META-INF/") || 
          nonRelocations.any { path.startsWith(it) })) {
        // don't throw exception inside `visit` 
        // as this gives a misleading "Could not expand ZIP" error message
        unshadowedFiles.add(path)
      }
    }
    if (unshadowedFiles.isEmpty()) {
      outputFile.writeText("SUCCESS")
    } else {
      outputFile.writeText("FAILURE")
      throw GradleException("Found unshadowed files:\n" + unshadowedFiles.joinToString("\n"))
    }
  }
}
tasks.check {
  dependsOn(validateFatJar)
}

val resolveSourcesJars by tasks.registering(ResolveSourcesJars::class) {
  configuration.set(configurations.runtimeClasspath)
  outputDir.set(project.file("$buildDir/resolveSourcesJars"))
}

val fatSourcesJar by tasks.registering(MergeSourcesJars::class) {
  plugins.withId("pklJavaLibrary") {
    inputJars.from(tasks.named("sourcesJar"))
  }
  inputJars.from(firstPartySourcesJarsConfiguration)
  inputJars.from(resolveSourcesJars.map { fileTree(it.outputDir) })

  mergedBinaryJars.from(tasks.shadowJar)
  relocatedPackages.set(relocations)
  outputJar.fileProvider(provider {
    file(tasks.shadowJar.get().archiveFile.get().asFile.path.replace(".jar", "-sources.jar"))
  })
}

artifacts {
  add("fatJar", tasks.shadowJar)
}

publishing {
  publications {
    named<MavenPublication>("fatJar") {
      project.shadow.component(this)

      // sources Jar is fat
      artifact(fatSourcesJar.flatMap { it.outputJar.asFile }) {
        classifier = "sources"
      }

      plugins.withId("pklJavaLibrary") {
        val javadocJar by tasks.existing(Jar::class)
        // Javadoc Jar is not fat (didn't invest effort)
        artifact(javadocJar.flatMap { it.archiveFile }) {
          classifier = "javadoc"
        }
      }
    }
  }
}
