/*
 * Copyright 2006-2021 DLR, Germany
 * 
 * SPDX-License-Identifier: EPL-1.0
 * 
 * https://rcenvironment.de/
 */

package de.rcenvironment.core.command.internal.handlers;

import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;

import org.apache.commons.io.FileUtils;
import org.apache.commons.logging.LogFactory;
import org.osgi.framework.Version;

import de.rcenvironment.core.command.common.CommandException;
import de.rcenvironment.core.command.spi.CommandContext;
import de.rcenvironment.core.command.spi.CommandDescription;
import de.rcenvironment.core.command.spi.CommandPlugin;
import de.rcenvironment.core.toolkitbridge.transitional.ConcurrencyUtils;
import de.rcenvironment.core.toolkitbridge.transitional.StatsCounter;
import de.rcenvironment.core.utils.common.StringUtils;
import de.rcenvironment.core.utils.common.VersionUtils;
import de.rcenvironment.core.utils.common.textstream.receivers.CapturingTextOutReceiver;
import de.rcenvironment.toolkit.modules.concurrency.api.TaskDescription;

/**
 * Provides built-in console commands, like printing "help" or version information.
 * 
 * @author Robert Mischke
 */
public class BuiltInCommandPlugin implements CommandPlugin {

    private static final String CMD_DEV = "dev";

    private static final String CMD_DUMMY = "dummy";

    private static final String CMD_HELP = "help";

    private static final String CMD_HELP_DEV = "help --dev";

    private static final String CMD_OSGI = "osgi";

    private static final String CMD_STATS = "stats";

    private static final String CMD_TASKS = "tasks";

    private static final String CMD_VERSION = "version";

    private static final String CMD_CRASH = "force-crash";

    @Override
    public Collection<CommandDescription> getCommandDescriptions() {
        final Collection<CommandDescription> contributions = new ArrayList<CommandDescription>();
        contributions.add(new CommandDescription(CMD_HELP, "", false, "list available commands"));
        contributions.add(new CommandDescription(CMD_VERSION, "[--detailed]", false, "print version information"));

        // developer commands
        contributions.add(new CommandDescription(CMD_DEV, "", true, "alias of \"help --dev\" [deprecated]"));
        contributions.add(new CommandDescription(CMD_DUMMY, "", true, "prints a test message"));
        contributions.add(new CommandDescription(CMD_CRASH, "<delay>", true,
            "\"kills\" the instance without proper shutdown at <delay> milliseconds after the command is executed"));
        contributions.add(new CommandDescription(CMD_HELP_DEV, "", true, "list available commands (including developer commands)"));
        contributions.add(new CommandDescription(CMD_STATS, "", true, "show internal statistics"));
        contributions.add(new CommandDescription(CMD_OSGI, "[-o <filename>] <command>", true,
            "executes an OSGi/Equinox console command; use -o to write text output to a file"));
        contributions.add(new CommandDescription(CMD_TASKS, "[-a] [-i]", true, "show information about internal tasks",
            "-a - Show all tasks, including inactive ones",
            "-i - Extended information: list tasks with a unique id"));
        // // Only the command description of the 'saveto' command is specified here. The functionality of the command is implemented in
        // {@link
        // // MultiCommandHandler}
        // contributions.add(new CommandDescription(MultiCommandHandler.SAVETO, "[-m] (<filename>|--auto) <command(s)>", false,
        // "saves the output of the command(s) to a file",
        // "-m: mirrors the output to the command console",
        // "<filename>: name of the file into which the output should be stored",
        // "--auto: auto-generates a filename",
        // "<command(s)>: list of commands which should be executed"));

        return contributions;
    }

    @Override
    public void execute(CommandContext context) throws CommandException {
        String cmd = context.consumeNextToken();
        if (CMD_HELP.equals(cmd)) {
            boolean devOption = context.consumeNextTokenIfEquals("--dev");
            performHelp(context, devOption);
        } else if (CMD_VERSION.equals(cmd)) {
            performVersion(context);
        } else if (CMD_OSGI.equals(cmd)) {
            performOsgi(context);
        } else if (CMD_CRASH.equals(cmd)) {
            performCrash(context);
        } else if (CMD_DEV.equals(cmd)) {
            // deprecated alias of "help --dev"
            performHelp(context, true);
        } else if (CMD_STATS.equals(cmd)) {
            performStats(context);
        } else if (CMD_TASKS.equals(cmd)) {
            performTasks(context);
        } else if (CMD_DUMMY.equals(cmd)) {
            performDummy(context);
            // } else if (MultiCommandHandler.SAVETO.equals(cmd)) {
            // throw CommandException.syntaxError("The 'saveto' command can only be used as a prefix of a list of commands!", context);
        } else {
            throw new IllegalStateException();
        }
    }

    private void performHelp(CommandContext context, boolean devOption) throws CommandException {
        context.setDeveloperCommandSetEnabled(devOption);
        // "rce" or "rce help" -> print user help
        throw CommandException.requestHelp(context);
    }

    /**
     * Dummy command for testing.
     * 
     * @throws CommandException
     */
    private void performDummy(final CommandContext context) throws CommandException {
        context.println("Dummy command executing");
    }

    private void performOsgi(CommandContext context) throws CommandException {
        final EquinoxConsoleCommandInvoker commandInvoker = new EquinoxConsoleCommandInvoker();
        final boolean logToFile = context.consumeNextTokenIfEquals("-o");
        if (!logToFile) {
            commandInvoker.execute(context);
        } else {
            String outputFilename = context.consumeNextToken();
            if (outputFilename == null) {
                throw CommandException.syntaxError("Missing filename after -o parameter", context);
            }
            final CapturingTextOutReceiver outputReceiver = new CapturingTextOutReceiver();
            CommandContext wrappedContext =
                new CommandContext(context.consumeRemainingTokens(), outputReceiver, context.getInvokerInformation());
            commandInvoker.execute(wrappedContext);
            File outputFile = new File(outputFilename);
            try {
                FileUtils.write(outputFile, outputReceiver.getBufferedOutput());
                context.println("Logged output to " + outputFile.getAbsolutePath());
            } catch (IOException e) {
                context.println("Internal error: Failed to write to output file " + outputFile.getAbsolutePath());
            }
        }
    }

    private void performVersion(CommandContext context) {
        if (context.consumeNextTokenIfEquals("--detailed")) {
            context.println("RCE platform version: " + VersionUtils.getVersionOfPlatformBundles());
            context.println("RCE core version: " + VersionUtils.getVersionOfCoreBundles());
            context.println("RCE product version: " + VersionUtils.getVersionOfProduct());
        } else {
            Version version = VersionUtils.getVersionOfProduct();
            String buildId = VersionUtils.getBuildIdAsString(version);
            if (buildId == null) {
                buildId = "-";
            }
            context.println(StringUtils.format("%s (build ID: %s)", VersionUtils.getVersionAsString(version), buildId));
        }
    }

    private void performCrash(CommandContext context) throws CommandException {
        String token = context.consumeNextToken();

        if (token == null || context.hasRemainingTokens()) {
            throw CommandException.wrongNumberOfParameters(context);
        }

        try {

            int delayMsec = Integer.parseInt(token);
            LogFactory.getLog(getClass()).warn(StringUtils.format("Killing the instance (using System.exit(1)) in %,d msec...", delayMsec));
            ConcurrencyUtils.getAsyncTaskService().scheduleAfterDelay(new Runnable() {

                @Override
                @TaskDescription("Simulate an instance crash (triggered by console command)")
                public void run() {
                    System.exit(1);
                }
            }, delayMsec);
        } catch (NumberFormatException e) {
            throw CommandException.syntaxError(
                StringUtils.format("You need to specify the delay in milliseconds. %s is not a valid number.", token),
                context);
        }
    }

    /**
     * Prints statistics about asynchronous tasks.
     * 
     * @param context
     * 
     * @return String the console output
     * @throws CommandException on syntax error
     */
    private void performTasks(CommandContext context) throws CommandException {
        boolean addTaskIds = false;
        boolean includeInactive = false;
        String token;
        while ((token = context.consumeNextToken()) != null) {
            switch (token) {
            case "-a": // "all"
                includeInactive = true;
                break;
            case "-i": // "ids"
                addTaskIds = true;
                break;
            default:
                throw CommandException.syntaxError("Unknown parameter: " + token, context);
            }
        }
        context.println(ConcurrencyUtils.getThreadPoolManagement().getFormattedStatistics(addTaskIds, includeInactive));
    }

    private void performStats(CommandContext context) {
        for (String line : StatsCounter.getFullReportAsStandardTextRepresentation()) {
            context.println(line);
        }
    }

}
