/*
 * SPDX-License-Identifier: Apache-2.0
 *
 * The OpenSearch Contributors require contributions made to
 * this file be licensed under the Apache-2.0 license or a
 * compatible open source license.
 */

/*
 * Licensed to Elasticsearch under one or more contributor
 * license agreements. See the NOTICE file distributed with
 * this work for additional information regarding copyright
 * ownership. Elasticsearch 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.
 */

/*
 * Modifications Copyright OpenSearch Contributors. See
 * GitHub history for details.
 */

package org.opensearch.gradle.precommit;

import org.apache.commons.io.IOUtils;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.UncheckedIOException;
import java.util.regex.Pattern;

public class LicenseAnalyzer {
    /*
     * Order here matters. License files can often contain multiple licenses for which the particular piece of software may by used under.
     * We should order these in order of most permissive to least permissive such that we identify the license as the most permissive for
     * purposes of redistribution. Search order is as defined below so the license will be identified as the first pattern to match.
     */
    private static final LicenseMatcher[] matchers = new LicenseMatcher[] {
        new LicenseMatcher("Apache-2.0", true, false, Pattern.compile("Apache.*License.*[vV]ersion.*2\\.0", Pattern.DOTALL)),
        new LicenseMatcher(
            "BSD-2-Clause",
            true,
            false,
            Pattern.compile(
                ("Redistribution and use in source and binary forms, with or without\n"
                    + "modification, are permitted provided that the following conditions\n"
                    + "are met:\n"
                    + "\n"
                    + " 1\\. Redistributions of source code must retain the above copyright\n"
                    + "    notice, this list of conditions and the following disclaimer\\.\n"
                    + " 2\\. Redistributions in binary form must reproduce the above copyright\n"
                    + "    notice, this list of conditions and the following disclaimer in the\n"
                    + "    documentation and/or other materials provided with the distribution\\.\n"
                    + "\n"
                    + "THIS SOFTWARE IS PROVIDED BY .+ (``|''|\")AS IS(''|\") AND ANY EXPRESS OR\n"
                    + "IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES\n"
                    + "OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED\\.\n"
                    + "IN NO EVENT SHALL .+ BE LIABLE FOR ANY DIRECT, INDIRECT,\n"
                    + "INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES \\(INCLUDING, BUT\n"
                    + "NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,\n"
                    + "DATA, OR PROFITS; OR BUSINESS INTERRUPTION\\) HOWEVER CAUSED AND ON ANY\n"
                    + "THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT\n"
                    + "\\(INCLUDING NEGLIGENCE OR OTHERWISE\\) ARISING IN ANY WAY OUT OF THE USE OF\n"
                    + "THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE\\.").replaceAll("\\s+", "\\\\s*"),
                Pattern.DOTALL
            )
        ),
        new LicenseMatcher(
            "BSD-3-Clause",
            true,
            false,
            Pattern.compile(
                ("\n"
                    + "Redistribution and use in source and binary forms, with or without\n"
                    + "modification, are permitted provided that the following conditions\n"
                    + "are met:\n"
                    + "\n"
                    + " (1\\.)? Redistributions of source code must retain the above copyright\n"
                    + "    notice, this list of conditions and the following disclaimer\\.\n"
                    + " (2\\.)? Redistributions in binary form must reproduce the above copyright\n"
                    + "    notice, this list of conditions and the following disclaimer in the\n"
                    + "    documentation and/or other materials provided with the distribution\\.\n"
                    + " ((3\\.)? The name of .+ may not be used to endorse or promote products\n"
                    + "    derived from this software without specific prior written permission\\.|\n"
                    + "  (3\\.)? Neither the name of .+ nor the names of its\n"
                    + "     contributors may be used to endorse or promote products derived from\n"
                    + "     this software without specific prior written permission\\.)\n"
                    + "\n"
                    + "THIS SOFTWARE IS PROVIDED BY .+ (``|''|\")AS IS(''|\") AND ANY EXPRESS OR\n"
                    + "IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES\n"
                    + "OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED\\.\n"
                    + "IN NO EVENT SHALL .+ BE LIABLE FOR ANY DIRECT, INDIRECT,\n"
                    + "INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES \\(INCLUDING, BUT\n"
                    + "NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,\n"
                    + "DATA, OR PROFITS; OR BUSINESS INTERRUPTION\\) HOWEVER CAUSED AND ON ANY\n"
                    + "THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT\n"
                    + "\\(INCLUDING NEGLIGENCE OR OTHERWISE\\) ARISING IN ANY WAY OUT OF THE USE OF\n"
                    + "THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE\\.\n").replaceAll("\\s+", "\\\\s*"),
                Pattern.DOTALL
            )
        ),
        new LicenseMatcher(
            "CDDL-1.0",
            true,
            false,
            Pattern.compile("COMMON DEVELOPMENT AND DISTRIBUTION LICENSE.*Version 1.0", Pattern.DOTALL)
        ),
        new LicenseMatcher(
            "CDDL-1.1",
            true,
            false,
            Pattern.compile("COMMON DEVELOPMENT AND DISTRIBUTION LICENSE.*Version 1.1", Pattern.DOTALL)
        ),
        new LicenseMatcher("ICU", true, false, Pattern.compile("ICU License - ICU 1.8.1 and later", Pattern.DOTALL)),
        new LicenseMatcher(
            "MIT",
            true,
            false,
            Pattern.compile(
                ("\n"
                    + "Permission is hereby granted, free of charge, to any person obtaining a copy of\n"
                    + "this software and associated documentation files \\(the \"Software\"\\), to deal in\n"
                    + "the Software without restriction, including without limitation the rights to\n"
                    + "use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies\n"
                    + "of the Software, and to permit persons to whom the Software is furnished to do\n"
                    + "so, subject to the following conditions:\n"
                    + "\n"
                    + "The above copyright notice and this permission notice shall be included in all\n"
                    + "copies or substantial portions of the Software\\.\n"
                    + "\n"
                    + "THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n"
                    + "IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n"
                    + "FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT\\. IN NO EVENT SHALL THE\n"
                    + "AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n"
                    + "LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n"
                    + "OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\n"
                    + "SOFTWARE\\.?\n").replaceAll("\\s+", "\\\\s*"),
                Pattern.DOTALL
            )
        ),
        new LicenseMatcher("MPL-1.1", true, false, Pattern.compile("Mozilla Public License.*Version 1.1", Pattern.DOTALL)),
        new LicenseMatcher("MPL-2.0", true, false, Pattern.compile("Mozilla\\s*Public\\s*License\\s*Version\\s*2\\.0", Pattern.DOTALL)),
        new LicenseMatcher("XZ", false, false, Pattern.compile("Licensing of XZ for Java", Pattern.DOTALL)),
        new LicenseMatcher("EPL-2.0", true, false, Pattern.compile("Eclipse Public License - v 2.0", Pattern.DOTALL)),
        new LicenseMatcher("LGPL-2.1", true, true, Pattern.compile("GNU LESSER GENERAL PUBLIC LICENSE.*Version 2.1", Pattern.DOTALL)),
        new LicenseMatcher("LGPL-3.0", true, true, Pattern.compile("GNU LESSER GENERAL PUBLIC LICENSE.*Version 3", Pattern.DOTALL)) };

    public static LicenseInfo licenseType(File licenseFile) {
        for (LicenseMatcher matcher : matchers) {
            boolean matches = matcher.matches(licenseFile);
            if (matches) {
                return new LicenseInfo(matcher.getIdentifier(), matcher.spdxLicense, matcher.sourceRedistributionRequired);
            }
        }

        throw new IllegalStateException("Unknown license for license file: " + licenseFile);
    }

    public static class LicenseInfo {
        private final String identifier;
        private final boolean spdxLicense;
        private final boolean sourceRedistributionRequired;

        public LicenseInfo(String identifier, boolean spdxLicense, boolean sourceRedistributionRequired) {
            this.identifier = identifier;
            this.spdxLicense = spdxLicense;
            this.sourceRedistributionRequired = sourceRedistributionRequired;
        }

        public String getIdentifier() {
            return identifier;
        }

        public boolean isSpdxLicense() {
            return spdxLicense;
        }

        public boolean isSourceRedistributionRequired() {
            return sourceRedistributionRequired;
        }
    }

    private static class LicenseMatcher {
        private final String identifier;
        private final boolean spdxLicense;
        private final boolean sourceRedistributionRequired;
        private final Pattern pattern;

        LicenseMatcher(String identifier, boolean spdxLicense, boolean sourceRedistributionRequired, Pattern pattern) {
            this.identifier = identifier;
            this.spdxLicense = spdxLicense;
            this.sourceRedistributionRequired = sourceRedistributionRequired;
            this.pattern = pattern;
        }

        public String getIdentifier() {
            return identifier;
        }

        public boolean matches(File licenseFile) {
            try (FileInputStream fis = new FileInputStream(licenseFile)) {
                String content = String.join("\n", IOUtils.readLines(fis, "UTF-8")).replaceAll("\\*", " ");
                return pattern.matcher(content).find();
            } catch (IOException e) {
                throw new UncheckedIOException(e);
            }
        }
    }
}
