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

// Configure rat dependencies for use in the custom task.
configure(rootProject) {
    configurations {
        ratDeps
    }

    dependencies {
        ratDeps libs.apache.rat.rat
    }
}

// Configure the rat validation task and all scanned directories.
allprojects {
    task("rat", type: RatTask) {
        group = 'Verification'
        description = 'Runs Apache Rat checks.'

        def defaultScanFileTree = project.fileTree(projectDir, {
            // Don't check under the project's build folder.
            exclude project.buildDir.name

            // Exclude any generated stuff.
            exclude "src/generated"

            // Don't recurse into local Lucene dev repo.
            exclude "/lucene"

            // Don't check any of the subprojects - they have their own rat tasks.
            exclude subprojects.collect { it.projectDir.name }

            // At the module scope we only check selected file patterns as folks have various .gitignore-d resources
            // generated by IDEs, etc.
            include "**/*.xml"
            include "**/*.md"
            include "**/*.py"
            include "**/*.sh"
            include "**/*.bat"
            // include build.gradle but exclude .gradle directories
            include "**/*.gradle"
            exclude ".gradle/**"

            // Exclude Eclipse
            exclude ".metadata"
            exclude ".settings"

            // Include selected patterns from any source folders. We could make this
            // relative to source sets but it seems to be of little value - all our source sets
            // live under 'src' anyway.
            include "src/**"
            exclude "src/**/*.png"
            exclude "src/**/*.txt"
            exclude "src/**/*.zip"
            exclude "src/**/*.properties"
            exclude "src/**/*.utf8"
            exclude "src/**/*.svg"
            exclude "src/**/*.csv"

            // TODO: SOLR-15601: Some of these should carry the license, perhaps?
            exclude "**/*.html"
            exclude "**/*.json"

            // Conditionally apply module-specific patterns. We do it here instead
            // of reconfiguring each project because the provider can be made lazy
            // and it's easier to manage this way.
            switch (project.path) {
                case ":":
                    include "gradlew"
                    include "gradlew.bat"
                    exclude ".idea"
                    exclude ".muse"
                    exclude ".git"

                    // Exclude github stuff (templates, workflows).
                    exclude ".github"

                    exclude "dev-tools/scripts/cloud.sh"
                    exclude "dev-tools/scripts/README.md"
                    exclude "dev-tools/scripts/create_line_file_docs.py"

                    // The root project also includes patterns for the include composite
                    // projects. Include their sources in the scan.
                    include "build-tools/build-infra/src/**"
                    include "build-tools/missing-doclet/src/**"
                    break

                case ":solr:modules:clustering":
                    exclude "src/test-files/META-INF/services/*"
                    break

                case ":solr:modules:langid":
                    exclude "**/langdetect-profiles/*"
                    break

                case ":solr:modules:ltr":
                    // TODO: SOLR-15601: Some of these should carry the license, perhaps?
                    exclude "**/*.py"
                    break

                case ":solr:modules:scripting":
                    // TODO: SOLR-15601: Some of these should carry the license, perhaps?
                    exclude "**/*.js"
                    break

                case ":solr:modules:s3-repository":
                    exclude "src/test-files/s3.conf" // empty file
                    break

                case ":solr:documentation":
                    exclude "src/markdown/*.md"
                    break

                case ":solr:core":
                    exclude "**/htmlStripReaderTest.html"
                    exclude "src/resources/*.json"
                    exclude "src/resources/*.xml"
                    exclude "src/test-files/**/*.csv"
                    exclude "src/test-files/**/*.json"
                    exclude "src/test-files/**/*.aff"
                    exclude "src/test-files/**/*.dic"
                    exclude "src/test-files/**/*.conf"
                    exclude "src/test-files/**/external_eff"
                    exclude "src/test-files/**/*.incl"
                    exclude "src/test-files/**/*-snippet-*.xml"
                    break

                case ":solr:server":
                    exclude "**/*.xml"
                    exclude "**/*.sh"
                    exclude "**/*.bat"
                    break

                case ":solr:webapp":
                    exclude "web/img/**"
                    break

                case ":solr:solr-ref-guide":
                    include "*.yml"
                    include "**/*.adoc"
                    exclude "ui-src/**"
                    exclude "node_modules/**"
                    break

                case ":solr:docker":
                    exclude "tests/**/*.xml"
                    break

                case ":solr:example":
                    exclude "**/*.xml"
                    exclude "films/README.md"
                    break

                case ":solr:solrj":
                    exclude "src/**/*.json"
                    exclude "src/test-files/**/*.cfg"
                    exclude "src/test-files/**/*.xml"
                    break
            }
        })
        inputFileTrees.add(defaultScanFileTree)
    }
}

/**
 * An Apache RAT adapter that validates whether files contain acceptable licenses.
 */
class RatTask extends DefaultTask {
    @InputFiles
    final ListProperty<ConfigurableFileTree> inputFileTrees = project.objects.listProperty(ConfigurableFileTree)

    @OutputFile
    final RegularFileProperty xmlReport = project.objects.fileProperty().convention(
        project.layout.buildDirectory.file("rat/rat-report.xml"))

    def generateReport(File reportFile) {
        // Set up ant rat task.
        def ratClasspath = project.rootProject.configurations.ratDeps.asPath
        ant.taskdef(resource: 'org/apache/rat/anttasks/antlib.xml', classpath: ratClasspath)

        // Collect all output files for debugging.
        String inputFileList = inputFileTrees.get().collectMany { fileTree ->
            fileTree.asList()
        }.sort().join("\n")
        project.file(reportFile.path.replaceAll('.xml$', '-filelist.txt')).setText(inputFileList, "UTF-8")

        // Run rat via ant.
        ant.report(format: 'xml', reportFile: reportFile, addDefaultLicenseMatchers: true) {
            // Pass all gradle file trees to the ant task (Gradle's internal adapters are used).
            inputFileTrees.get().each { fileTree ->
                fileTree.addToAntBuilder(ant, 'resources', FileCollection.AntType.ResourceCollection)
            }

            // BSD 4-clause stuff (is disallowed below)
            substringMatcher(licenseFamilyCategory: "BSD4 ", licenseFamilyName: "Original BSD License (with advertising clause)") {
                pattern(substring: "All advertising materials")
            }

            // BSD-like stuff
            substringMatcher(licenseFamilyCategory: "BSD  ", licenseFamilyName: "Modified BSD License") {
                // brics automaton
                pattern(substring: "Copyright (c) 2001-2009 Anders Moeller")
                // snowball
                pattern(substring: "Copyright (c) 2001, Dr Martin Porter")
                // UMASS kstem
                pattern(substring: "THIS SOFTWARE IS PROVIDED BY UNIVERSITY OF MASSACHUSETTS AND OTHER CONTRIBUTORS")
                // Egothor
                pattern(substring: "Egothor Software License version 1.00")
                // JaSpell
                pattern(substring: "Copyright (c) 2005 Bruno Martins")
                // d3.js
                pattern(substring: "THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS")
                // highlight.js
                pattern(substring: "THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS")
            }

            // MIT-like
            substringMatcher(licenseFamilyCategory: "MIT  ", licenseFamilyName:"Modified BSD License") {
                // ICU license
                pattern(substring: "Permission is hereby granted, free of charge, to any person obtaining a copy")
                // ui-grid
                pattern(substring: " ; License: MIT")
            }

            // Apache
            substringMatcher(licenseFamilyCategory: "AL   ", licenseFamilyName: "Apache") {
                pattern(substring: "Licensed to the Apache Software Foundation (ASF) under")
                // this is the old - school one under some files
                pattern(substring: 'Licensed under the Apache License, Version 2.0 (the "License")')
            }

            substringMatcher(licenseFamilyCategory: "GEN  ", licenseFamilyName: "Generated") {
                // svg files generated by gnuplot
                pattern(substring: "Produced by GNUPLOT")
                // snowball stemmers generated by snowball compiler
                pattern(substring: "Generated by Snowball")
                // parsers generated by antlr
                pattern(substring: "ANTLR GENERATED CODE")
            }

            approvedLicense(familyName: "Apache")
            approvedLicense(familyName: "The MIT License")
            approvedLicense(familyName: "Modified BSD License")
            approvedLicense(familyName: "Generated")
        }
    }

    def printUnknownFiles(File reportFile) {
        def ratXml = new XmlParser().parse(reportFile)
        def errors = []
        ratXml.resource.each { resource ->
            if (resource.'license-approval'.@name[0] == "false") {
                errors << "Unknown license: ${resource.@name}"
            }
        }
        def checkProp = "validation.rat.failOnError"
        project.failOrWarn(checkProp, "Detected license header issues", errors)
    }

    @TaskAction
    def execute() {
        def origEncoding = System.getProperty("file.encoding")
        try {
            File reportFile = xmlReport.get().asFile
            generateReport(reportFile)
            printUnknownFiles(reportFile)
        } finally {
            if (System.getProperty("file.encoding") != origEncoding) {
                throw new GradleException("Something is wrong: Apache RAT changed file.encoding to ${System.getProperty('file.encoding')}?")
            }
        }
    }
}
