/*
 * Copyright (c) 2021, 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 java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

import jdk.test.lib.apps.LingeredApp;
import jdk.test.lib.dcmd.CommandExecutorException;
import jdk.test.lib.dcmd.PidJcmdExecutor;
import jdk.test.lib.helpers.ClassFileInstaller;
import jdk.test.lib.process.OutputAnalyzer;
import jtreg.SkippedException;
import jdk.test.whitebox.WhiteBox;


public abstract class JCmdTestDumpBase {
    private static boolean isStatic; // set first in subclass for dump type
    protected static void setIsStatic(boolean value) {
        isStatic = value;
    }
    /**************************************
     * for a subclass to do static dump
     *    public class JCmdTestStaticDump extends JCmdTestDumpBase {
     *        public static void test() throws Exception {
     *            setIsStatic(true);
     *            // do test
     *        }
     *        public static void main(String[] args) throws Exception {
     *            runTest(JCmdTestStaticDump::test);
     *        }
     *    }
     ***************************************/
    public static interface JCmdTest {
        public void run() throws Exception;
    }

    public static void runTest(JCmdTest t) throws Exception {
        checkCDSEnabled();
        t.run();
    }
    private static final String TEST_CLASSES[] =
                             {"JCmdTestLingeredApp",
                              "jdk/test/lib/apps/LingeredApp",
                              "jdk/test/lib/apps/LingeredApp$1",
                              "jdk/test/lib/apps/LingeredApp$SteadyStateLock"};
    private static final String BOOT_CLASSES[] = {"Hello"};

    protected static String testJar = null;
    protected static String bootJar = null;
    protected static String allJars = null;

    public static final boolean useBoot = true;
    public static final boolean noBoot = !useBoot;
    public static final boolean EXPECT_PASS = true;
    public static final boolean EXPECT_FAIL = !EXPECT_PASS;

    // If delete the created archive after test.
    private static boolean keepArchive = false;
    public static void setKeepArchive(boolean v) { keepArchive = v; }
    public static void checkFileExistence(String fileName, boolean checkExist) throws Exception {
        File file = new File(fileName);
        boolean exist = file.exists();
        boolean resultIsTrue = checkExist ? exist : !exist;
        if (!resultIsTrue) {
            throw new RuntimeException("File " + fileName +  " should " + (checkExist ?  "exist" : "not exist"));
        }
    }

    protected static void buildJars() throws Exception {
        testJar = ClassFileInstaller.writeJar("test", TEST_CLASSES);
        bootJar = ClassFileInstaller.writeJar("boot", BOOT_CLASSES);
        System.out.println("Jar file created: " + testJar);
        System.out.println("Jar file created: " + bootJar);
        allJars = testJar + File.pathSeparator + bootJar;
    }

    private static void checkCDSEnabled() throws Exception {
        boolean cdsEnabled = WhiteBox.getWhiteBox().isSharingEnabled();
        if (!cdsEnabled) {
            throw new SkippedException("CDS is not available for this JDK.");
        }
    }

    private static boolean argsContain(String[] args, String flag) {
         for (String s: args) {
             if (s.contains(flag)) {
                 return true;
             }
         }
         return false;
    }

    private static boolean argsContainOpts(String[] args, String... opts) {
        boolean allIn = true;
        for (String f : opts) {
            allIn &= argsContain(args, f);
            if (!allIn) {
                break;
            }
        }
        return allIn;
    }

    protected static LingeredApp createLingeredApp(String... args) throws Exception {
        JCmdTestLingeredApp app  = new JCmdTestLingeredApp();
        try {
            LingeredApp.startAppExactJvmOpts(app, args);
        } catch (Exception e) {
            // Check flags used.
            if (argsContainOpts(args, new String[] {"-Xshare:off", "-XX:+RecordDynamicDumpInfo"}) ||
                argsContainOpts(args, new String[] {"-XX:+RecordDynamicDumpInfo", "-XX:ArchiveClassesAtExit="})) {
                // app exit premature due to incompactible args
                return null;
            }
            Process proc = app.getProcess();
            if (e instanceof IOException && proc.exitValue() == 0) {
                // Process started and exit normally.
                return null;
            }
            throw e;
        }
        return app;
    }

    private static int logFileCount = 0;
    private static void runWithArchiveFile(String archiveName, boolean useBoot,  String... messages) throws Exception {
        List<String> args = new ArrayList<String>();
        if (useBoot) {
            args.add("-Xbootclasspath/a:" + bootJar);
        }
        args.add("-cp");
        if (useBoot) {
            args.add(testJar);
        } else {
            args.add(allJars);
        }
        args.add("-Xshare:on");
        args.add("-XX:SharedArchiveFile=" + archiveName);
        args.add("-Xlog:class+load");

        LingeredApp app = createLingeredApp(args.toArray(new String[0]));
        app.setLogFileName("JCmdTest-Run-" + (isStatic ? "Static.log" : "Dynamic.log.") + (logFileCount++));
        app.stopApp();
        String output = app.getOutput().getStdout();
        if (messages != null) {
            for (String msg : messages) {
                if (!output.contains(msg)) {
                    throw new RuntimeException(msg + " missed from output");
                }
            }
        }
    }

    protected static OutputAnalyzer test(String fileName, long pid,
                             boolean useBoot, boolean expectOK, String... messages) throws Exception {
        System.out.println("Expected: " + (expectOK ? "SUCCESS" : "FAIL"));
        String archiveFileName = fileName != null ? fileName :
            ("java_pid" + pid + (isStatic ? "_static.jsa" : "_dynamic.jsa"));

        File archiveFile = new File(archiveFileName);

        String jcmd = "VM.cds " + (isStatic ? "static_dump" : "dynamic_dump");
        if (archiveFileName  != null) {
          jcmd +=  " " + archiveFileName;
        }

        PidJcmdExecutor cmdExecutor = new PidJcmdExecutor(String.valueOf(pid));
        OutputAnalyzer output = cmdExecutor.execute(jcmd, true/*silent*/);

        if (archiveFileName.contains("%p")) {
            archiveFileName = archiveFileName.replace("%p", "%d").formatted(pid);
        }

        if (expectOK) {
            output.shouldHaveExitValue(0);
            checkFileExistence(archiveFileName, true);
            runWithArchiveFile(archiveFileName, useBoot, messages);
            if (!keepArchive) {
                archiveFile.delete();
            }
        } else {
            if (!keepArchive) {
                checkFileExistence(archiveFileName, false);
            }
        }
        return output;
    }

    protected static void print2ln(String arg) {
        System.out.println("\n" + arg + "\n");
    }
}
