/*
 *  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 org.apache.tools.ant.filters.ReplaceTokens
import org.codehaus.groovy.gradle.JarJarTask

apply plugin: 'osgi'

group = 'org.codehaus.groovy'
archivesBaseName = 'groovy'

ext.srcSpec = copySpec {
    from(projectDir) {
        exclude 'target',
                'benchmark',
                'subprojects/*/target',
                'buildSrc/target',
                '**/build',
                buildDir.path,
                'classes/**',
                'cruise/**',
                'security/groovykeys',
                '.clover/*',
                'local.build.properties',
                'gradle/wrapper',
                'gradlew',
                'gradlew.bat',
                'cobertura.ser',
                'junitvmwatcher*.properties',
                'out',
                'artifactory.properties', // generated by the CI server
                'gradle.properties.gz', // generated by the CI server
                '.github',            // used by Github/actions
                '**/*.iml',         // used by Intellij IDEA
                '**/*.ipr',         // used by Intellij IDEA
                '**/*.iws',         // used by Intellij IDEA
                '.idea',            // used by Intellij IDEA
                '.settings',        // used by Eclipse
                '.gradle',          // used by Gradle
                'buildSrc/.gradle'  // used by Gradle
    }
}

ext.docSpec = copySpec {
    into('html/api') {
        from javadocAll.destinationDir
    }
    into('html/gapi') {
        from groovydocAll.destinationDir
    }
    into('html/documentation') {
        from "$buildDir/asciidocAll/html5"
    }
    into('html/groovy-jdk') {
        from docGDK.destinationDir
    }
    into('licenses') {
        from 'licenses'
        include 'asciidoc-style-license.txt'
        include 'jquery-js-license.txt'
        include 'normalize-stylesheet-license.txt'
    }
    from "$projectDir/licenses/LICENSE-DOC"
    from "$projectDir/notices/NOTICE-BASE"
    rename 'LICENSE-DOC', 'LICENSE'
    rename 'NOTICE-BASE', 'NOTICE'
}

task copy(type: Copy) {
    into "$buildDir/meta"
}

// unnecessary entries which in addition may trigger unnecessary rebuilds
def excludedFromManifest = [
        'Ant-Version',
        'Originally-Created-By',
        'Bnd-LastModified',
        'Created-By'
]

ext.allManifest = manifest {
    attributes(
            'Extension-Name': 'groovy',
            'Specification-Title': 'Groovy: a powerful, dynamic language for the JVM',
            'Specification-Version': groovyBundleVersion,
            'Specification-Vendor': 'The Apache Software Foundation',
            'Implementation-Title': 'Groovy: a powerful, dynamic language for the JVM',
            'Implementation-Version': groovyBundleVersion,
            'Implementation-Vendor': 'The Apache Software Foundation',
            'Bundle-ManifestVersion': '2',
            'Bundle-Name': 'Groovy Runtime',
            'Bundle-Description': 'Groovy Runtime',
            'Bundle-Version': groovyBundleVersion,
            'Bundle-Vendor': 'The Apache Software Foundation',
            'Bundle-ClassPath': '.',
            'Eclipse-BuddyPolicy': 'dependent',
            'DynamicImport-Package': '*',
            'Main-Class': 'groovy.ui.GroovyMain')
}

ext.groovyOsgiManifest = {
    // Exclude the Bnd-LastModified attribute as it always triggers a rebuild without being really needed.
    from(allManifest) {
        eachEntry { details ->
            if (excludedFromManifest.any { it == details.key }) {
                details.exclude()
            }
        }
    }
    version = groovyBundleVersion
    instruction '-nouses', 'true'
    instruction 'Export-Package', "*;version=${groovyBundleVersion}"
    instruction 'Eclipse-ExtensibleAPI', 'true'
    classpath = sourceSets.main.runtimeClasspath
}

ext.subprojectOsgiManifest = {
    // Exclude attributes not needed for subprojects.
    from(allManifest) {
        eachEntry { details ->
            if (details.key in [*excludedFromManifest, 'Bnd-LastModified', 'Extension-Name', 'Bundle-Name', 'Bundle-Description', 'Main-Class']) {
                details.exclude()
            }
        }
    }
    version = groovyBundleVersion
    instruction '-nouses', 'true'
    instruction 'Export-Package', "*;version=${groovyBundleVersion}"
    instruction 'Fragment-Host', 'groovy'
    def folder = new File( "${projectDir}/subprojects/${symbolicName}/src/main/resources/META-INF/services" )
    if( folder.exists() ) {
        if (folder.listFiles().count { it.name ==~ /^(?!(org.codehaus.groovy.transform.ASTTransformation)$).*$/ } > 0) {
            instruction 'Require-Capability', 'osgi.extender;filter:="(osgi.extender=osgi.serviceloader.registrar)"'
            instruction 'Require-Capability', 'osgi.extender;filter:="(osgi.extender=osgi.serviceloader.processor)"'
            folder.eachFileMatch(~/^(?!(org.codehaus.groovy.transform.ASTTransformation)$).*$/) {
                instruction 'Require-Capability', "osgi.serviceloader;filter:=\"(osgi.serviceloader=${it.name})\";cardinality:=multiple"
                instruction 'Provide-Capability', "osgi.serviceloader;osgi.serviceloader=\"${it.name}\""
            }
        }
    }
    classpath = sourceSets.main.runtimeClasspath
}

allprojects {
    boolean isRoot = project == rootProject

    def producedJars = [jar]
    jar {
        appendix = 'raw'
        manifest {
            from(allManifest) {
                eachEntry { details ->
                    if (excludedFromManifest.any { it == details.key }) {
                        details.exclude()
                    }
                }
            }
        }
    }
    if (rootProject.indyCapable()) {
        task jarWithIndy(type: Jar) {
            dependsOn compileGroovyWithIndy
            classifier = 'indy'
            appendix = 'raw'
            from sourceSets.main.java.outputDir
            from sourceSets.main.groovy.outputDir
            from compileGroovyWithIndy.destinationDir
            from "${project.buildDir}/resources/main"
        }
        producedJars << jarWithIndy
    }
    producedJars.each { arch ->
        arch.metaInf {
            from("$projectDir/licenses/LICENSE-JARJAR")
            from("$projectDir/licenses") {
                into('licenses')
                include('asm-license.txt')
                include('antlr2-license.txt')
                include('antlr4-license.txt')
            }
            from("$projectDir/notices/NOTICE-JARJAR")
            from(generateReleaseInfo)
            rename '^([A-Z]+)-([^.]*)', '$1'
        }
        arch.exclude '**/package-info.class'

        task "jar${arch.name}"(type: JarJarTask) {
            dependsOn arch
            from = file(arch.archivePath)
            if (project == rootProject) {
                repackagedLibraries = files(configurations.runtime.incoming.artifactView {
                    componentFilter { component ->
                        if (component instanceof ModuleComponentIdentifier) {
                            return component.module in [
                                    'antlr', 'antlr-runtime', 'antlr4', 'antlr4-runtime', 'antlr4-annotations',
                                    'asm', 'asm-commons', 'asm-tree', 'asm-util',
                                    'commons-cli', 'picocli']
                        }
                        return false
                    }
                }.files)
            } else {
                repackagedLibraries = files()
            }
            jarjarToolClasspath = rootProject.configurations.tools
            untouchedFiles = [
                    'groovy/util/CliBuilder*.class',
                    'groovy/util/OptionAccessor*.class',
                    'groovy/cli/commons/CliBuilder*.class',
                    'groovy/cli/commons/OptionAccessor*.class',
                    'groovy/cli/picocli/CliBuilder*.class',
                    'groovy/cli/picocli/OptionAccessor*.class'
            ]
            patterns = [
                    'antlr.**'                 : 'groovyjarjarantlr.@1', // antlr2
                    'org.antlr.**'             : 'groovyjarjarantlr4.@1', // antlr4
                    'org.objectweb.**'         : 'groovyjarjarasm.@1',
                    'org.apache.commons.cli.**': 'groovyjarjarcommonscli.@1', // this is just for deprecated methods in FSF for 2_5_X!
                    'picocli.**'               : 'groovyjarjarpicocli.@1'
            ]
            excludesPerLibrary = [
                    '*': ['META-INF/**', '**/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 = file("$buildDir/libs/${arch.baseName}-${arch.version}${arch.classifier ? '-' + arch.classifier : ''}.jar")

            withManifest {
                def moduleName = "org.codehaus.${project.name.replace('-', '.')}"
                attributes('Automatic-Module-Name': moduleName)
                from(allManifest) {
                    eachEntry { details ->
                        if (excludedFromManifest.any { it == details.key }) {
                            details.exclude()
                        }
                    }
                }
            }
            withManifest(isRoot ? groovyOsgiManifest : subprojectOsgiManifest)
        }

    }

    if (project.name in ['groovy', 'groovy-test']) {
        task grooidjar(type: JarJarTask) {
            dependsOn jarjar
            from = file(jarjar.outputFile)
            if (isRoot) {
                repackagedLibraries = files(configurations.runtime.incoming.artifactView {
                    componentFilter { component ->
                        if (component instanceof ModuleComponentIdentifier) {
                            return component.module in ['openbeans']
                        }
                        return false
                    }
                }.files)
            } else {
                repackagedLibraries = files()
            }
            jarjarToolClasspath = rootProject.configurations.tools
            patterns = [
                    'com.googlecode.openbeans.**': 'groovyjarjaropenbeans.@1',
                    'org.apache.harmony.beans.**': 'groovyjarjarharmonybeans.@1',
                    'java.beans.**': 'groovyjarjaropenbeans.@1'
            ]
            excludesPerLibrary = [
                    '*': ['META-INF/NOTICE']
            ]
            excludes = ['META-INF/NOTICE', 'META-INF/INDEX.LIST']
            createManifest = false
            includedResources = [
                    ("$rootProject.projectDir/notices/${isRoot ? 'NOTICE-GROOIDJARJAR' : 'NOTICE-GROOID'}".toString()): 'META-INF/NOTICE'
            ]
            outputFile = file("$buildDir/libs/${jar.baseName}-${jar.version}-grooid.jar")
        }
    }
}

def producedJars = [jar]
if (rootProject.indyCapable()) {
    producedJars << jarWithIndy
}

producedJars.each {
    it.dependsOn('dgmConverter')
    it.from files(dgmConverter.outputDir)
}

subprojects { sp ->
    jar {
        metaInf {
            if (file("${projectDir}/LICENSE").exists()) {
                from "${projectDir}/LICENSE"
            } else {
                from "${rootProject.projectDir}/licenses/LICENSE-BASE"
            }
            if (file("${projectDir}/NOTICE").exists()) {
                from "${projectDir}/NOTICE"
            } else {
                from "${rootProject.projectDir}/notices/NOTICE-BASE"
            }
            rename '^([A-Z]+)-([^.]*)', '$1'
        }
        exclude '**/package-info.class'
    }
}

task sourceAllJar(type: Jar, dependsOn: { modules()*.sourceJar + rootProject.sourceJar }) {
    with sourceJar.rootSpec
    modules()*.sourceJar.each {
        with it.rootSpec
    }
    baseName = 'groovy-all'
    classifier = 'sources'
}

allprojects {
    task javadocJar(type: Jar, dependsOn: javadoc) {
        from javadoc.destinationDir
        classifier = 'javadoc'
    }
    task groovydocJar(type: Jar, dependsOn: groovydoc) {
        from groovydoc.destinationDir
        classifier = 'groovydoc'
    }
}

task javadocAllJar(type: Jar, dependsOn: javadocAll) {
    baseName = 'groovy-all'
    from javadocAll.destinationDir
    classifier = 'javadoc'
}

task groovydocAllJar(type: Jar, dependsOn: groovydocAll) {
    baseName = 'groovy-all'
    from groovydocAll.destinationDir
    classifier = 'groovydoc'
}

evaluationDependsOn('groovy-jaxb')

ext.distSpec = copySpec {
    duplicatesStrategy = DuplicatesStrategy.EXCLUDE
    from("$projectDir/licenses/LICENSE-BINZIP")
    from("$projectDir/notices/NOTICE-BINZIP")
    rename '^([A-Z]+)-([^.]*)', '$1'
    exclude { it.file.name =~ /-raw/ }
    into('lib') {
        from jarjar
        from modules()*.jarjar
        from(configurations.runtime) {
            exclude {
                it.file.name.startsWith('openbeans-') ||
                        it.file.name.startsWith('asm-') ||
                        it.file.name.startsWith('antlr-')
            }
        }
        from('src/bin/groovy.icns')
    }
    modules().configurations.runtime.each { conf ->
        into('lib') {
            from(conf) {
                exclude {
                    it.file.name.contains('livetribe-jsr223') ||
                            it.file.name.matches(/groovy-\d.*/) ||
                            it.file.name.startsWith('asm-') ||
                            it.file.name.startsWith('antlr-') ||
                            it.file.name.startsWith('antlr4-') ||
                            it.file.name.startsWith('openbeans-')
                }
            }
        }
    }
    into('lib/extras-jaxb') {
        from project(':groovy-jaxb').configurations.jaxb
        from project(':groovy-jaxb').configurations.jaxbRuntime
    }
    if (!rootProject.hasProperty('skipIndy')) {
        into('indy') {
            from jarjarWithIndy
            from modules()*.jarjarWithIndy
        }
    }
    if (!rootProject.hasProperty('skipGrooid')) {
        into('grooid') {
            from { new File(jar.archivePath.parent, "${jar.baseName}-${jar.version}-grooid.jar") }
            from {
                modules()*.jar.collect { j ->
                    new File(j.archivePath.parent, "${j.baseName}-${j.version}-grooid.jar")
                }
            }
        }
    }
    into('conf') {
        from 'src/conf'
    }
    into('bin') {
        from('src/bin') {
            filter(ReplaceTokens, tokens: [GROOVYJAR: jarjar.archiveName])
            fileMode = 0755
            exclude 'groovy.icns'
        }
        from('subprojects/groovy-docgenerator/src/main/resources/org/apache/groovy/docgenerator/groovy.ico')
    }
    into('licenses') {
        from 'licenses'
        include 'antlr2-license.txt'
        include 'asm-license.txt'
        include 'hamcrest-license.txt'
        include 'jline2-license.txt'
        include 'jsp-api-license.txt'
        include 'jsr166y-license.txt'
        include 'jsr223-license.txt'
        include 'junit4-license.txt'
        include 'junit5-license.txt'
        include 'servlet-api-license.txt'
        include 'xstream-license.txt'
    }
}

task distBin(type: Zip) {
    baseName = 'apache-groovy'
    appendix = 'binary'
    into("groovy-$version") {
        with distSpec
    }
    allprojects {
        if (project.name in ['groovy', 'groovy-test']) {
            distBin.dependsOn(grooidjar)
        }
    }
}

task distDoc(type: Zip, dependsOn: doc) {
    baseName = 'apache-groovy'
    appendix = 'docs'
    into("groovy-$version") {
        with docSpec
    }
}

task syncDoc(type: Copy, dependsOn: doc) {
    inputs.files javadoc.outputs.files
    inputs.files groovydoc.outputs.files

    destinationDir(file("$buildDir/html"))
    into('api') {
        from javadoc.destinationDir
    }
    into('gapi') {
        from groovydoc.destinationDir
    }
    // groovy-jdk already at the correct place
}

task distSrc(type: Zip) {
    baseName = 'apache-groovy'
    appendix = 'src'
    into("groovy-$version")
    with srcSpec
}

def installDir = {
    project.hasProperty('groovy_installPath') ? project.groovy_installPath :
            System.properties.installDirectory ?: "$buildDir/install"
}

task installGroovy(type: Sync, dependsOn: [checkCompatibility, distBin]) {
    description 'Generates a groovy distribution into an install directory'
    doLast {
        logger.lifecycle "Groovy installed under ${installDir()}"
    }
    with distSpec
    into installDir
}

task checkNoSnapshotVersions {
    doLast {
        if (project.isReleaseVersion) {
            // TODO use modules() and exclusions as per distSpec
            allprojects {
                project.configurations.runtime.resolvedConfiguration.resolvedArtifacts.each {
                    if (it.moduleVersion.id.version.endsWith("-SNAPSHOT")) {
                        throw new GradleException("Found snapshot dependency for non-snapshot Groovy: " + it.moduleVersion)
                    }
                }
            }
        }
    }
}
distBin.dependsOn checkNoSnapshotVersions

task dist(type: Zip, dependsOn: [checkCompatibility, distBin, distSrc, distDoc, syncDoc]) {
    description = 'Generates the binary, sources, documentation and full distributions'
    baseName = 'apache-groovy'
    duplicatesStrategy = DuplicatesStrategy.EXCLUDE
    appendix 'sdk'
    into "groovy-$version"
    from("$projectDir/licenses/LICENSE-SDK")
    from("$projectDir/notices/NOTICE-SDK")
    rename '^([A-Z]+)-([^.]*)', '$1'
    with distSpec
    into('doc') {
        with docSpec
    }
    into('src') {
        with srcSpec
    }

    if ((version.endsWith('SNAPSHOT') && !groovyBundleVersion.endsWith('SNAPSHOT'))
            || (!version.endsWith('SNAPSHOT') && groovyBundleVersion.endsWith('SNAPSHOT'))) {
        throw new GradleException("Incoherent versions. Found groovyVersion=$version and groovyBundleVersion=$groovyBundleVersion")
    }
}

task updateLicenses {
    description = 'Updates the various LICENSE files'
    ext.licensesDir = "${projectDir}/licenses"
    ext.licenseBaseFile = "${licensesDir}/LICENSE-BASE"

    ext.licenseSrcFile = "${projectDir}/LICENSE"
    ext.licenseDocGeneratorFile = "${projectDir}/subprojects/groovy-docgenerator/LICENSE"
    ext.licenseGroovyDocFile = "${projectDir}/subprojects/groovy-groovydoc/LICENSE"
    ext.licenseJsr223File = "${projectDir}/subprojects/groovy-jsr223/LICENSE"
    ext.licenseBinZipFile = "${licensesDir}/LICENSE-BINZIP"
    ext.licenseDocFile = "${licensesDir}/LICENSE-DOC"
    ext.licenseJarJarFile = "${licensesDir}/LICENSE-JARJAR"
    ext.licenseSdkFile = "${licensesDir}/LICENSE-SDK"
    inputs.files(licenseBaseFile, fileTree(licensesDir).include('*.txt'))
    outputs.files(licenseBinZipFile, licenseDocFile, licenseJarJarFile, licenseSrcFile,
            licenseDocGeneratorFile, licenseGroovyDocFile, licenseJsr223File, licenseSdkFile)

    doLast {
        def srcFiles = fileTree(licensesDir).include('*-SRC*.txt').sort { it.name }
        def docFiles = fileTree(licensesDir).include('*-DOC*.txt').sort { it.name }
        def jarjarFiles = fileTree(licensesDir).include('*-JARJAR*.txt').sort { it.name }
        def binzipFiles = fileTree(licensesDir) {
            include '*-JARJAR*.txt'
            include '*-BINZIP*.txt'
        }.sort { it.name }
        def docgeneratorFiles = fileTree(licensesDir).include('normalize-stylesheet-groovy-docgenerator.txt')
        def groovydocFiles = fileTree(licensesDir).include('normalize-stylesheet-groovy-groovydoc.txt')
        def jsr223Files = fileTree(licensesDir).include('jsr223-BINZIP-SRC.txt')
        def licenseHdr = '\n\n------------------------------------------------------------------------\n\n'
        [
                (licenseBinZipFile): binzipFiles,
                (licenseDocFile): docFiles,
                (licenseJarJarFile): jarjarFiles,
                (licenseSrcFile): srcFiles,
                (licenseDocGeneratorFile): docgeneratorFiles,
                (licenseGroovyDocFile): groovydocFiles,
                (licenseJsr223File): jsr223Files,
        ].each { outFile, inFiles ->
            file(outFile).withWriter('utf-8') { writer ->
                writer << ([file(licenseBaseFile)] + inFiles).collect {
                    it.text.replaceAll(/[\n\r]*$/, '')
                }.join(licenseHdr) + '\n'
            }
        }
        file(licenseSdkFile).withWriter { writer ->
            writer << [
                    file(licenseBinZipFile).text,
                    "This convenience zip embeds Groovy's src and doc zips.\nSee also src/LICENSE " +
                            "and doc/LICENSE files for additional license information."
            ].join(licenseHdr) + '\n'
        }
    }
}

task updateNotices {
    description = 'Updates the various NOTICE files'
    ext.noticesDir = "${projectDir}/notices"
    ext.noticeBaseFile = "${noticesDir}/NOTICE-BASE"

    ext.noticeSrcFile = "${projectDir}/NOTICE"
    ext.noticeGroovyConsoleFile = "${projectDir}/subprojects/groovy-console/NOTICE"
    ext.noticeBinZipFile = "${noticesDir}/NOTICE-BINZIP"
    ext.noticeGrooidFile = "${noticesDir}/NOTICE-GROOID"
    ext.noticeGrooidJarJarFile = "${noticesDir}/NOTICE-GROOIDJARJAR"
    ext.noticeJarJarFile = "${noticesDir}/NOTICE-JARJAR"
    ext.noticeSdkFile = "${noticesDir}/NOTICE-SDK"
    inputs.files(noticeBaseFile, fileTree(noticesDir).include('*.txt'))
    outputs.files(noticeBinZipFile, noticeGrooidFile, noticeGrooidJarJarFile,
            noticeJarJarFile, noticeSrcFile, noticeGroovyConsoleFile, noticeSdkFile)

    doLast {
        def srcFiles = fileTree(noticesDir).include('*-SRC*.txt').sort { it.name }
        def grooidFiles = fileTree(noticesDir).include('*-GROOID*.txt').sort { it.name }
        def jarjarFiles = fileTree(noticesDir).include('*-JARJAR*.txt').sort { it.name }
        def grooidJarjarFiles = fileTree(noticesDir) {
            include '*-JARJAR*.txt'
            include '*-GROOID*.txt'
        }.sort { it.name }
        def binzipFiles = fileTree(noticesDir) {
            include '*-JARJAR*.txt'
            include '*-GROOID*.txt'
            include '*-BINZIP*.txt'
        }.sort { it.name }
        def groovyconsoleFiles = fileTree(noticesDir).include('silkicons-BINZIP-SRC.txt')
        [
                (noticeBinZipFile): binzipFiles,
                (noticeGrooidFile): grooidFiles,
                (noticeGrooidJarJarFile): grooidJarjarFiles,
                (noticeJarJarFile): jarjarFiles,
                (noticeSrcFile): srcFiles,
                (noticeGroovyConsoleFile): groovyconsoleFiles,
        ].each { outFile, inFiles ->
            file(outFile).withWriter('utf-8') { writer ->
                writer << ([file(noticeBaseFile)] + inFiles).collect {
                    it.text.replaceAll(/[\n\r]*$/, '')
                }.join('\n\n')
            }
        }
        file(noticeSdkFile).withWriter { writer ->
            writer << [
                    file(noticeBinZipFile).text,
                    "This convenience zip embeds Groovy's src and doc zips.\nSee also src/NOTICE " +
                            "and doc/NOTICE files for additional notice information."
            ].join('\n\n')
        }
    }
}
