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

package org.apache.cassandra.simulator;

import java.lang.management.ManagementFactory;
import java.lang.management.RuntimeMXBean;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.Consumer;
import java.util.stream.Collectors;

import org.slf4j.Logger;

import org.apache.cassandra.utils.concurrent.Threads;

import io.netty.util.concurrent.FastThreadLocal;
import picocli.CommandLine;

public class SimulatorUtils
{
    public static RuntimeException failWithOOM()
    {
        List<long[]> oom = new ArrayList<>();
        for (int i = 0 ; i < 1024 ; ++i)
            oom.add(new long[0x7fffffff]);
        throw new AssertionError();
    }

    public static void dumpStackTraces(Logger logger)
    {
        Map<Thread, StackTraceElement[]> threadMap = Thread.getAllStackTraces();
        String prefix = "   ";
        String delimiter = "\n" + prefix;
        threadMap.forEach((thread, ste) ->
                          logger.error("{}:\n{}", thread, Threads.prettyPrint(ste, false, prefix, delimiter, "")));
        FastThreadLocal.destroy();
    }

    private static void verifyAndlogSimulatorArgs(List<String> args)
    {
        RuntimeMXBean runtimeMxBean = ManagementFactory.getRuntimeMXBean();
        final List<String> jvmArgs = runtimeMxBean.getInputArguments();
        System.err.printf("JVM Args: %s%n", jvmArgs.stream().collect(Collectors.joining("\" \"", "\"", "\"")));
        System.err.printf("Command Args: %s%n", args.stream().collect(Collectors.joining("\" \"", "\"", "\"")));

        assert jvmArgs.stream().anyMatch(arg -> arg.startsWith("-Xbootclasspath/a") && arg.endsWith("simulator-bootstrap.jar")) :
        "must launch JVM with -Xbootclasspath/a:simulator-bootstrap.jar";
        assert jvmArgs.stream().anyMatch(arg -> arg.startsWith("-javaagent:") && arg.endsWith("simulator-asm.jar")) :
        "must launch JVM with -javaagent:simulator-asm.jar";
        if (!jvmArgs.stream().anyMatch(arg -> arg.equals("-XX:-BackgroundCompilation")))
            System.err.println("JVM Argument -XX:-BackgroundCompilation not set, non-determinism possible");
        if (!jvmArgs.stream().anyMatch(arg -> arg.equals("-XX:-TieredCompilation")))
            System.err.println("JVM Argument -XX:-TieredCompilation not set, non-determinism possible");
        if (!jvmArgs.stream().anyMatch(arg -> arg.equals("-XX:CICompilerCount=1")))
            System.err.println("JVM Argument -XX:CICompilerCount=1 not set, non-determinism possible");
        if (!jvmArgs.stream().anyMatch(arg -> arg.startsWith("-XX:Tier4CompileThreshold=")))
            System.err.println("JVM Argument -XX:Tier4CompileThreshold not set, non-determinism possible.");
        if (!jvmArgs.stream().anyMatch(arg -> arg.equals("-Dcassandra.disable_tcactive_openssl=true")))
            System.err.println("JVM Argument -Dcassandra.disable_tcactive_openssl=true not set, non-determinism possible. Typically set -XX:Tier4CompileThreshold=1000");

        // log4j support
        if (!jvmArgs.stream().anyMatch(arg -> arg.equals("-Dlog4j2.disableJmx=true")))
            System.err.println("JVM Argument -Dlog4j2.disableJmx=true not set, non-determinism possible");
        if (!jvmArgs.stream().anyMatch(arg -> arg.equals("-Dlog4j.shutdownHookEnabled=false")))
            System.err.println("JVM Argument -Dlog4j.shutdownHookEnabled=false not set, non-determinism possible");
        if (!jvmArgs.stream().anyMatch(arg -> arg.equals("-Dcassandra.simulator.skiplog4jreload=true")))
            System.err.println("JVM Argument -Dcassandra.simulator.skiplog4jreload=true not set, non-determinism possible");
    }

    public static CommandLine prepareRunner(Object command, CommandLine.IFactory factory, Consumer<Exception> exceptionHandler)
    {
        CommandLine cli = new CommandLine(command, factory);
        cli.setExecutionStrategy(parseResult -> {
            verifyAndlogSimulatorArgs(parseResult.originalArgs());
            return new CommandLine.RunLast().execute(parseResult);
        });
        return exceptionHandler == null ? cli :
               cli.setExecutionExceptionHandler((ex, commandLine, fullParseResult) -> {
                   if (ex != null) exceptionHandler.accept(ex);
                   return commandLine.getCommandSpec().exitCodeOnExecutionException();
               });
    }

    public static void executeWithExceptionThrowing(Object command, CommandLine.IFactory factory, String[] args)
    {
        AtomicReference<Exception> cause = new AtomicReference<>();
        int exitCode;
        if ((exitCode = prepareRunner(command, factory, cause::set).execute(args)) == 0)
            return;
        if (cause.get() == null)
            throw new RuntimeException("Simulation failed with exit code: " + exitCode);
        else
            throw new RuntimeException("Simulation failed with exit code: " + exitCode, cause.get());
    }
}
