/*
 * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * This code is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 only, as
 * published by the Free Software Foundation.
 *
 * This code is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * version 2 for more details (a copy is included in the LICENSE file that
 * accompanied this code).
 *
 * You should have received a copy of the GNU General Public License version
 * 2 along with this work; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 * or visit www.oracle.com if you need additional information or have any
 * questions.
 */
import jdk.test.lib.json.JSONValue;
import jtreg.SkippedException;

import java.nio.file.Files;
import java.nio.file.Path;
import java.security.Provider;
import java.security.Security;

/*
 * @test
 * @bug 8342442
 * @library /test/lib
 */

/// This test runs on `internalProjection.json`-style files generated
/// by NIST's ACVP Server. See [https://github.com/usnistgov/ACVP-Server].
///
/// The files are either put into the `data` directory or another
/// directory specified by the `test.acvp.data` test property.
/// The test walks through the directory recursively and looks for
/// file names equal to or ending with `internalProjection.json` and
/// runs tests on them.
///
/// Set the `test.acvp.alg` test property to only test the specified algorithm.
///
/// Sample files can be downloaded from
/// [https://github.com/usnistgov/ACVP-Server/tree/master/gen-val/json-files].
///
/// By default, the test uses system-preferred implementations.
/// If you want to test a specific provider, set the
/// `test.acvp.provider` test property. The provider must be
/// registered.
///
/// Tests for each algorithm must be compliant to its specification linked from
/// [https://github.com/usnistgov/ACVP?tab=readme-ov-file#supported-algorithms].
///
/// Example:
/// ```
/// jtreg -Dtest.acvp.provider=SunJCE \
///       -Dtest.acvp.alg=ML-KEM \
///       -Dtest.acvp.data=/path/to/json-files/ \
///       -jdk:/path/to/jdk Launcher.java
/// ```
public class Launcher {

    private static final String ONLY_ALG
            = System.getProperty("test.acvp.alg");

    private static final Provider PROVIDER;

    private static int count = 0;
    private static int invalidTest = 0;
    private static int unsupportedTest = 0;

    static {
        var provProp = System.getProperty("test.acvp.provider");
        if (provProp != null) {
            var p = Security.getProvider(provProp);
            if (p == null) {
                System.err.println(provProp + " is not a registered provider name");
                throw new RuntimeException(provProp + " is not a registered provider name");
            }
            PROVIDER = p;
        } else {
            PROVIDER = null;
        }
    }

    public static void main(String[] args) throws Exception {

        var testDataProp = System.getProperty("test.acvp.data");
        Path dataPath = testDataProp != null
                ? Path.of(testDataProp)
                : Path.of(System.getProperty("test.src"), "data");
        System.out.println("Data path: " + dataPath);

        if (PROVIDER != null) {
            System.out.println("Provider: " + PROVIDER.getName());
        }
        if (ONLY_ALG != null) {
            System.out.println("Algorithm: " + ONLY_ALG);
        }

        try (var stream = Files.walk(dataPath)) {
            stream.filter(Files::isRegularFile)
                    .filter(p -> p.getFileName().toString()
                            .endsWith("internalProjection.json"))
                    .forEach(Launcher::run);
        }

        if (count > 0) {
            System.out.println();
            System.out.println("Test completed: " + count);
            System.out.println("Invalid tests: " + invalidTest);
            System.out.println("Unsupported tests: " + unsupportedTest);
        } else {
            throw new SkippedException("No supported test found");
        }
    }

    static void run(Path test) {
        try {
            JSONValue kat;
            try {
                kat = JSONValue.parse(Files.readString(test));
            } catch (Exception e) {
                System.out.println("Warning: cannot parse " + test + ". Skipped");
                invalidTest++;
                return;
            }
            var alg = kat.get("algorithm").asString();
            if (ONLY_ALG != null && !alg.equals(ONLY_ALG)) {
                return;
            }
            System.out.println(">>> Testing " + test + "...");
            switch (alg) {
                case "ML-DSA" -> {
                    ML_DSA_Test.run(kat, PROVIDER);
                    count++;
                }
                case "ML-KEM" -> {
                    ML_KEM_Test.run(kat, PROVIDER);
                    count++;
                }
                case "SHA2-256", "SHA2-224", "SHA3-256", "SHA3-224" -> {
                    SHA_Test.run(kat, PROVIDER);
                    count++;
                }
                default -> {
                    System.out.println("Skipped unsupported algorithm: " + alg);
                    unsupportedTest++;
                }
            }
        } catch (RuntimeException re) {
            throw re;
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }
}