/*
 *  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.
 */

import groovy.transform.CompileStatic
import org.apache.groovy.gradle.GroovyLibraryExtension
import org.apache.groovy.gradle.JarJarTask
import org.apache.groovy.gradle.ReleaseInfoGenerator
import com.github.spotbugs.snom.Effort
import org.gradle.api.attributes.java.TargetJvmVersion

import com.github.spotbugs.snom.SpotBugsTask
import org.apache.groovy.gradle.CheckstyleHtmlReport

plugins {
    id 'java-library'
    id 'groovy'
    id 'checkstyle'
    id 'codenarc'
    id 'com.github.spotbugs'
    id 'org.apache.groovy-common'
    id 'org.apache.groovy-internal'
    id 'org.apache.groovy-tested'
    id 'org.apache.groovy-asciidoctor'
}

/**
 * This script defines a conventional plugin for all Groovy modules, be it
 * Groovy core or any of its libraries.
 */
if (sharedConfiguration.hasCodeCoverage.get()) {
    pluginManager.apply(JacocoPlugin)
}

def groovyLibrary = project.extensions.create('groovyLibrary', GroovyLibraryExtension, sharedConfiguration, java)

java {
    withSourcesJar()
    withJavadocJar()
}

def generateReleaseInfo = tasks.register('generateReleaseInfo', ReleaseInfoGenerator)

def groovydocJar = tasks.register('groovydocJar', Jar) {
    from groovydoc
    archiveClassifier = 'groovydoc'
    group = 'build'
    description = 'Assembles a jar archive containing the main groovydoc.'
}

configurations {
    groovydocElements {
        canBeConsumed = true
        canBeResolved = false
        attributes {
            attribute(Category.CATEGORY_ATTRIBUTE, objects.named(Category, Category.DOCUMENTATION))
            attribute(Bundling.BUNDLING_ATTRIBUTE, objects.named(Bundling, Bundling.EXTERNAL))
            attribute(DocsType.DOCS_TYPE_ATTRIBUTE, objects.named(DocsType, 'groovydoc'))
            attribute(Usage.USAGE_ATTRIBUTE, objects.named(Usage, Usage.JAVA_RUNTIME))
        }
        outgoing {
            artifact groovydocJar
        }
    }
    sourcesForAggregation {
        canBeConsumed = true
        canBeConsumed = true
        canBeResolved = false
        extendsFrom implementation, runtimeOnly
        attributes {
            attribute(Category.CATEGORY_ATTRIBUTE, objects.named(Category, Category.DOCUMENTATION))
            attribute(Bundling.BUNDLING_ATTRIBUTE, objects.named(Bundling, Bundling.EXTERNAL))
            attribute(DocsType.DOCS_TYPE_ATTRIBUTE, objects.named(DocsType, DocsType.SOURCES))
            attribute(Usage.USAGE_ATTRIBUTE, objects.named(Usage, 'aggregation'))
        }
        outgoing {
            sourceSets.main.java.srcDirs.each {
                artifact(it)
            }
            sourceSets.main.groovy.srcDirs.each {
                artifact(it)
            }
        }
    }
    javadocClasspath {
        canBeConsumed = true
        canBeResolved = false
        extendsFrom implementation, runtimeOnly, compileOnly, compileOnlyApi
        attributes {
            attribute(Category.CATEGORY_ATTRIBUTE, objects.named(Category, Category.LIBRARY))
            attribute(Bundling.BUNDLING_ATTRIBUTE, objects.named(Bundling, Bundling.EXTERNAL))
            attribute(Usage.USAGE_ATTRIBUTE, objects.named(Usage, 'javadocClasspath'))
        }
        outgoing {
            artifact tasks.named('jar')
        }
    }
    codenarc {
        resolutionStrategy.dependencySubstitution {
            substitute module('org.codehaus.groovy:groovy') using project(':')
            substitute module('org.codehaus.groovy:groovy-ant') using project(':groovy-ant')
            substitute module('org.codehaus.groovy:groovy-xml') using project(':groovy-xml')
            substitute module('org.codehaus.groovy:groovy-json') using project(':groovy-json')
            substitute module('org.codehaus.groovy:groovy-groovydoc') using project(':groovy-groovydoc')
            substitute module('org.codehaus.groovy:groovy-templates') using project(':groovy-templates')
        }

        exclude module: 'groovy-all'
    }
}

dependencies {
    compileOnly providers.provider { "com.github.spotbugs:spotbugs-annotations:${versions.spotbugs}" }

    codenarc "org.codenarc:CodeNarc:${versions.codenarc}"
    codenarc project(':groovy-templates')

    checkstyle "com.puppycrawl.tools:checkstyle:${versions.checkstyle}"
    checkstyle "commons-codec:commons-codec:1.19.0"
    spotbugs "com.github.spotbugs:spotbugs:${versions.spotbugs}"
    spotbugs "org.apache.logging.log4j:log4j-api:${versions.log4j2}"
    spotbugs "org.apache.logging.log4j:log4j-core:${versions.log4j2}"
    spotbugsSlf4j "org.slf4j:slf4j-simple:${versions.slf4j}"
}

def excludedFromManifest = [
    'Ant-Version',
    'Bnd-LastModified',
    'Created-By',
    'Originally-Created-By',
    'Tool'
]

tasks.named('jar') {
    archiveAppendix = 'raw'
    archiveClassifier.set('raw')
    groovyLibrary.configureManifest(manifest, excludedFromManifest)
}

tasks.withType(AbstractArchiveTask).configureEach {
    preserveFileTimestamps = false
    reproducibleFileOrder = true
}

tasks.withType(Jar).configureEach { jar ->
    metaInf {
        if (jar.name in ['jar', 'sourcesJar']) {
            if (file("${projectDir}/LICENSE").exists()) {
                from "${projectDir}/LICENSE"
            } else {
                from("${rootProject.projectDir}/licenses/LICENSE-BASE") {
                    rename 'LICENSE-BASE', 'LICENSE'
                }
            }
            if (file("${projectDir}/NOTICE").exists()) {
                from "${projectDir}/NOTICE"
            } else {
                from("${rootProject.projectDir}/notices/NOTICE-BASE") {
                    rename 'NOTICE-BASE', 'NOTICE'
                }
            }
        }
        from generateReleaseInfo
    }
}

tasks.register('jarjar', JarJarTask) {
    String projectName = project.name
    from = jar.archiveFile
    repackagedLibraries.from configurations.runtimeClasspath.incoming.artifactView {
        componentFilter { component ->
            if (component instanceof ModuleComponentIdentifier) {
                return component.module in groovyLibrary.repackagedDependencies.get()
            }
            return false
        }
    }.files
    untouchedFiles = [
            'groovy/cli/picocli/CliBuilder*.class',
            'groovy/cli/picocli/OptionAccessor*.class'
    ]
    patterns = [
            'org.antlr.**': 'groovyjarjarantlr4.@1', // antlr4
            'org.objectweb.**': 'groovyjarjarasm.@1',
            'picocli.**': 'groovyjarjarpicocli.@1'
    ]
    excludesPerLibrary = [
            '*': ['META-INF/maven/**', 'META-INF/*', 'META-INF/services/javax.annotation.processing.Processor', '**/module-info.class']
    ]
    includesPerLibrary = [
            'asm-util': ['org/objectweb/asm/util/Printer.class',
                         'org/objectweb/asm/util/Textifier*',
                         'org/objectweb/asm/util/ASMifier.class',
                         'org/objectweb/asm/util/Trace*']
    ]
    outputFile = tasks.named('jar').flatMap { layout.buildDirectory.file("libs/${it.archiveBaseName.get()}-${it.archiveVersion.get()}${(it.archiveClassifier.get() && it.archiveClassifier.get() != 'raw') ? '-' + it.archiveClassifier.get() : ''}.jar") }

    withManifest {
        String autoModName = "org.apache.${projectName.replace('-','.')}"
        attributes('Automatic-Module-Name': autoModName, 'Bundle-Name': "Groovy module: $projectName")
        groovyLibrary.configureManifest(it, excludedFromManifest)
        classpath = configurations.runtimeClasspath
    }
}

tasks.withType(AbstractCompile).configureEach {
    sourceCompatibility = sharedConfiguration.targetJavaVersion.get()
    targetCompatibility = sharedConfiguration.targetJavaVersion.get()
}

tasks.withType(Javadoc).configureEach {
    options.source = sharedConfiguration.targetJavaVersion.get()
}

tasks.named('compileTestGroovy') {
    options.forkOptions.jvmArgs += ['-Dspock.iKnowWhatImDoing.disableGroovyVersionCheck=true']
}

tasks.withType(CodeNarc).configureEach {
    ignoreFailures = true
    configFile = rootProject.file('config/codenarc/codenarc.groovy')
}

tasks.withType(SpotBugsTask).configureEach {
    excludeFilter = rootProject.file('config/spotbugs/exclude.xml')
    ignoreFailures = true
    effort = Effort.valueOf('MAX')
    maxHeapSize = '4g'
    reports {
        xml {
            required = false
        }
        html {
            required = true
            stylesheet = 'fancy.xsl'
        }
    }
}

tasks.register('checkstyle') {
    dependsOn tasks.withType(Checkstyle)
}

tasks.withType(Checkstyle).configureEach {chk ->
    showViolations = false
    ignoreFailures = true
    configFile = rootProject.file('config/checkstyle/checkstyle.xml')
    configProperties = ['rootProject.projectDir': rootProject.projectDir]
    def reportFile = layout.buildDirectory.file("reports/checkstyle/${name}.xml")
    reports {
        include('**/*.java')
        xml {
            outputLocation = reportFile
        }
    }
    finalizedBy {
        if (chk.source.size()) {
            // we use a closure here as a workaround, to preserve task configuration laziness
            tasks.register("${name}Report", CheckstyleHtmlReport) {
                dependsOn chk
                source.from(chk.source)
                configFile = rootProject.file('config/checkstyle/checkstyle-report.groovy')
                checkstyleReportFile = reportFile
                outputFile = layout.buildDirectory.file("reports/checkstyle/${chk.name}.html")
            }
        }
    }
}

// remove below if checkstyle fix up their dependency metadata to no longer have conflicts
configurations.checkstyle {
    resolutionStrategy.capabilitiesResolution.withCapability('com.google.collections:google-collections') {
        select('com.google.guava:guava:0')
    }
}

// Groovy doesn't publish the regular jars: it publishes
// the repackaged jars, which is why we can't use the
// default publication, and need to create our own
def factory = objects.newInstance(Services).softwareComponentFactory
def component = factory.adhoc('groovyLibrary')
components.add(component)

components.groovyLibrary {
    addVariantsFromConfiguration(configurations.groovydocElements) {
        mapToOptional()
    }
    addVariantsFromConfiguration(configurations.javadocElements) {
        mapToOptional()
    }
    addVariantsFromConfiguration(configurations.sourcesElements) {
        mapToOptional()
    }
}

// By declaring a codehaus capability we can tell Gradle that the user has to
// choose between "old" groovy and "new" groovy
List<String> capabilities = [
        "org.codehaus.groovy:${base.archivesName.get()}:${sharedConfiguration.groovyVersion.get()}",
        "org.apache.groovy:${base.archivesName.get()}:${sharedConfiguration.groovyVersion.get()}"
]
int targetJvmVersion = Integer.valueOf(sharedConfiguration.targetJavaVersion.get())
// First we create the "API" and "runtime" variants of Groovy for publication
def shadowApi = createConsumableConfiguration(objects, tasks, configurations, 'groovyApiElements', Usage.JAVA_API, 'jarjar', capabilities, targetJvmVersion)
def shadowRuntime = createConsumableConfiguration(objects, tasks, configurations, 'groovyRuntimeElements', Usage.JAVA_RUNTIME, 'jarjar', capabilities, targetJvmVersion)
dependencies {
    // All Groovy modules depend on the Groovy BOM which itself has constraints on all
    // Groovy modules, which brings nice alignment features!
    groovyApiElements platform(project(':groovy-bom'))
    groovyRuntimeElements platform(project(':groovy-bom'))
}
component.addVariantsFromConfiguration(shadowApi) {

}
component.addVariantsFromConfiguration(shadowRuntime) {

}
afterEvaluate {
    def repackaged = groovyLibrary.repackagedDependencies.get()
    if (!repackaged) {
        // only modules which do not repackage dependencies are going to inherit dependencies
        shadowApi.extendsFrom(configurations.api)
        shadowRuntime.extendsFrom(configurations.implementation)
        shadowRuntime.extendsFrom(configurations.runtimeOnly)
    }
}

@CompileStatic
static Configuration createConsumableConfiguration(ObjectFactory objects,
                                            TaskContainer tasks,
                                            ConfigurationContainer container,
                                            String name,
                                            String usage,
                                            String artifactTask,
                                            List<String> capabilities,
                                            int targetJvmVersion) {
    container.create(name) { Configuration cnf ->
        cnf.canBeConsumed = true
        cnf.canBeResolved = false
        cnf.attributes {
            it.attribute(Category.CATEGORY_ATTRIBUTE, objects.named(Category, Category.LIBRARY))
            it.attribute(LibraryElements.LIBRARY_ELEMENTS_ATTRIBUTE, objects.named(LibraryElements, LibraryElements.JAR))
            it.attribute(Usage.USAGE_ATTRIBUTE, objects.named(Usage, usage))
            // We use external because only Groovy core actually repackages dependencies
            it.attribute(Bundling.BUNDLING_ATTRIBUTE, objects.named(Bundling, Bundling.EXTERNAL))
            it.attribute(TargetJvmVersion.TARGET_JVM_VERSION_ATTRIBUTE, targetJvmVersion)
        }
        cnf.outgoing {
            it.artifact tasks.named(artifactTask)
            capabilities.each { capability ->
                it.capability(capability)
            }
        }
    }
}
