/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

plugins {
  id 'java-library'
  id 'org.apache.geode.gradle.geode-dependency-constraints'
}

compileJava.options.encoding = 'UTF-8'

dependencies {
  // log4j-core has an annotation processor that is passed on the compile-classpath via geode-core and others.
  // Fix Gradle warning here until we clean up our own classpath
  annotationProcessor 'org.apache.logging.log4j:log4j-core:' + DependencyConstraints.get('log4j.version')
}

String javaVersion = System.properties['java.version']
def versionMajor = JavaVersion.current().majorVersion.toInteger()
if (versionMajor < 17) {
  throw new GradleException("Java version 17 or later required, but was " + javaVersion)
}

// apply compiler options
gradle.taskGraph.whenReady({ graph ->
  tasks.withType(JavaCompile).each { javac ->
    javac.configure {
      options.encoding = 'UTF-8'
      options.compilerArgs.addAll([
        '--add-exports=java.management/com.sun.jmx.remote.security=ALL-UNNAMED',
        '--add-exports=java.base/sun.nio.ch=ALL-UNNAMED',
        '--add-exports=jdk.unsupported/sun.misc=ALL-UNNAMED',
        '--add-exports=jdk.unsupported/sun.reflect=ALL-UNNAMED',
        '-Xlint:-removal',
        '-Xlint:-deprecation'
      ])
    }
    javac.options.incremental = true
    javac.options.fork = true
    javac.options.forkOptions.with({
      memoryMaximumSize = "768m"
    })
    if (project.hasProperty('compileJVM') && !compileJVM.trim().isEmpty()) {
      javac.options.forkOptions.executable = compileJVM + "/bin/javac"
    }
  }
})

Collection<String> getConfigurationNamesFor(Project proj, String configName) {
  def configList = new HashSet<String>()
  def configFromName = proj.configurations.getByName(configName)
  configList.addAll(configFromName.extendsFrom.collectMany {
    getConfigurationNamesFor(proj, it.name).flatten().unique()
  })
  configList.add(configName)
  return configList
}

Collection<Configuration> getConfigurationsFor(Project proj, String configName) {
  def configNames = getConfigurationNamesFor(proj, configName)
  return configNames.collect { proj.configurations.getByName(it) }
}

// apply default manifest
gradle.taskGraph.whenReady({ graph ->
  tasks.withType(Jar).each { jar ->

    def projectDependencies = []
    def runtimeSet = new HashSet<String>()
    def allProjectDeps = []

    def runtimeConfigNameList = getConfigurationNamesFor(project, 'runtimeClasspath')
    runtimeConfigNameList.each { conf ->
      allProjectDeps.addAll(project.configurations.getByName(conf).getDependencies())
    }

    jar.doFirst {
      // Iterate over runtime classpath dependencies and separate project dependencies from library
      // dependencies.
      allProjectDeps.each { dependency ->
        if ( dependency instanceof ProjectDependency ) {
          def platformAttribute = Attribute.of("org.gradle.category", org.gradle.api.attributes.Category.class)
          def foundAttribute = dependency.attributes.getAttribute(platformAttribute)
          if (foundAttribute == null) {
            projectDependencies.add(dependency)
          } else if ('platform'.equals(foundAttribute)) {
            projectDependencies.add(dependency)
          }
        } else {
          project.configurations.runtimeClasspath.files(dependency).each { depJar ->
            runtimeSet.add(depJar.name)
          }
        }
      }

      // Iterate over project (parent) dependencies and remove its runtime library dependencies from
      // the current project's runtime library dependencies.
      // Also removes all parent project's runtime project dependencies from the current project.
      // This returns a unique set of parent project and library dependencies that are not found
      // within it's parent's project dependencies.
      def upstreamDeps = []
      projectDependencies.clone().each { ProjectDependency projectDependency ->
        Project geodeProject = projectDependency.getDependencyProject()

        Map depMap = new HashMap<Object,Object>()
        def runtimeConfigList = getConfigurationsFor(geodeProject, 'runtimeClasspath')
        runtimeConfigList.each { eachConfig ->
          depMap.putAll(geodeProject.configurations.getByName(eachConfig.name).dependencies.collectEntries {eachDep ->
            [eachDep.name, eachDep]
          }.findAll { !(it.value?.hasProperty('optional') && it.value.optional)})
        }

        def incoming = geodeProject.configurations.runtimeClasspath.getIncoming()
        def resolutionResult = incoming.getResolutionResult()
        def components = resolutionResult.allComponents

        upstreamDeps.addAll(components.findAll {componentResult ->
          depMap.containsKey(componentResult.moduleVersion.id.name)
        }.collect {it.moduleVersion.id.name + '-' + it.moduleVersion.version + '.jar'})
      }
      // TODO: can we put our projects on the ClassPath line still?
      runtimeSet.removeAll(upstreamDeps)

      def upstreamClasspath = manifest.attributes.get('Class-Path')
      if (upstreamClasspath) {
        runtimeSet.addAll(upstreamClasspath.split(' '))
      }
//      runtimeSet.removeAll(upstreamDeps)

      manifest {
        attributes.put("Manifest-Version", "1.0")
        attributes.put("Created-By", System.getProperty("user.name"))
        attributes.put("Title", rootProject.name)
        attributes.put("Version", archiveVersion)
        attributes.put("Organization", productOrg)
        attributes.put("Class-Path", runtimeSet.join(' '))
        attributes.put("Dependent-Modules", projectDependencies.collect({ "${it.name}-${it.version}" }).join(' '))
        attributes.put("Module-Name", project.name)
      }
    }
    jar.metaInf {
      from("$rootDir/geode-assembly/src/main/dist/LICENSE")
      if (jar.source.filter({ it.name.contains('NOTICE') }).empty) {
        from("$rootDir/NOTICE")
      }
    }
  }
})

configurations {
  testOutput {
    extendsFrom testImplementation
    description 'a dependency that exposes test artifacts'
  }
}

// This ensures that javadoc and source jars also have any prefix paths stripped and will
// be created as libs/foo-sources.jar instead of libs/extensions/foo-sources.jar for example.
tasks.all { task ->
  if (task instanceof Jar) {
    archiveBaseName = project.name
  }
}

tasks.register('jarTest', Jar) {
  dependsOn testClasses
  description 'Assembles a jar archive of test classes.'
  from sourceSets.test.output
  classifier 'test'
}

artifacts {
  testOutput (jarTest)
}

javadoc {
  destinationDir = file("$buildDir/javadoc")
  // Disabled strict HTML checking for Java 17 compatibility
  options.addStringOption('Xdoclint:none', '-quiet')
  options.encoding = 'UTF-8'
  exclude "**/internal/**"

  classpath += configurations.compileClasspath
}


