package io.quarkus.cli.commands;

import java.io.File;
import java.io.IOException;
import java.util.List;

import org.aesh.command.Command;
import org.aesh.command.CommandDefinition;
import org.aesh.command.CommandException;
import org.aesh.command.CommandNotFoundException;
import org.aesh.command.CommandResult;
import org.aesh.command.invocation.CommandInvocation;
import org.aesh.command.option.Argument;
import org.aesh.command.option.Option;
import org.aesh.command.option.OptionList;
import org.aesh.command.parser.CommandLineParserException;
import org.aesh.command.validator.CommandValidatorException;
import org.aesh.command.validator.OptionValidatorException;

import io.quarkus.devtools.project.BuildTool;

@CommandDefinition(name = "dev", description = "Starts up a development mode process for a Quarkus project.")
public class DevModeCommand implements Command<CommandInvocation> {

    @Option(shortName = 'd', completer = DevModeDebugCompleter.class, defaultValue = {
            "true" }, validator = DevModeDebugValidator.class, description = "If this server should be started in debug mode. "
                    +
                    "The default is to start in debug mode without suspending and listen on port 5005." +
                    " It supports the following options:\n" +
                    " \"false\" - The JVM is not started in debug mode\n" +
                    " \"true\" - The JVM is started in debug mode and suspends until a debugger is attached to port 5005\n" +
                    " \"client\" - The JVM is started in client mode, and attempts to connect to localhost:5005\n" +
                    "\"{port}\" - The JVM is started in debug mode and suspends until a debugger is attached to {port}")
    private String debug;

    @Option(shortName = 'u', hasValue = false, name = "suspend", description = "Whether or not the JVM launch, in debug mode, should be suspended."
            + "This parameter is only relevant when the JVM is launched in debug mode.")
    private boolean suspend;

    @Option(shortName = 'b', name = "build", description = "Build folder, if not set the default folder for the used build tool will be used")
    private File buildDir;

    @Option(shortName = 's', name = "source", description = "Source folder, if not set the default folder for the used build tool will be used")
    private File sourceDir;

    @Option(name = "jvm-args", shortName = 'j', description = "JVM arguments to the dev mode process")
    private String jvmArgs;

    @Option(name = "delete-dev-jar", shortName = 'e', hasValue = false, defaultValue = {
            "true" }, description = "Delete the dev jar after it finishes")
    private boolean deleteDevJar;

    @Option(name = "prevent-no-verify", shortName = 'p', defaultValue = {
            "false" }, hasValue = false, description = "This value is intended to be set to true when some generated bytecode is eroneous causing "
                    + " the JVM to crash when the verify:non option is set (which is on by default).")
    private boolean preventNoVerify;

    @Option(name = "no-deps", shortName = 'n', hasValue = false, defaultValue = {
            "false" }, description = "Whether changes in the projects that appear to be dependencies of the project containing the "
                    + " application to be launched should trigger hot-reload. By default they do.")

    @OptionList(name = "compiler-args", shortName = 'c', description = "Additional parameters to pass to javac when recompiling changed source files.")
    private List<String> compilerArgs;

    @Argument(description = "Path to the project, if not set it will use the current working directory")
    private File projectPath;

    private BuildTool buildTool;
    private File buildFile;

    @Override
    public CommandResult execute(CommandInvocation invocation) throws CommandException, InterruptedException {

        if (!verifyProjectStatus(invocation))
            return CommandResult.FAILURE;

        try {
            devMode(invocation);
        } catch (InterruptedException i) {
            invocation.println("Stopping dev-mode");
        }

        return CommandResult.SUCCESS;
    }

    private void devMode(CommandInvocation invocation) throws InterruptedException {
        if (buildTool.equals(BuildTool.MAVEN)) {
            File wrapper = ExecuteUtil.getMavenWrapper(projectPath.getAbsolutePath());
            if (wrapper != null) {
                ExecuteUtil.executeWrapper(invocation, wrapper, "quarkus:dev");
            } else {
                ExecuteUtil.executeMaven(projectPath, invocation, "quarkus:dev");
            }

        }
        //do gradle
        else {
            File wrapper = ExecuteUtil.getGradleWrapper(projectPath.getAbsolutePath());
            if (wrapper != null) {
                ExecuteUtil.executeWrapper(invocation, wrapper, "quarkusDev");
            } else {
                ExecuteUtil.executeGradle(projectPath, invocation, "quarkusDev");
            }
        }
    }

    private boolean verifyProjectStatus(CommandInvocation invocation) {

        if (projectPath == null)
            projectPath = new File(System.getProperty("user.dir"));

        buildTool = BuildTool.resolveExistingProject(projectPath.toPath());

        if (buildTool.getBuildFiles().length > 0)
            buildFile = new File(buildTool.getBuildFiles()[0]);

        if (buildFile == null || !buildFile.isFile()) {
            invocation.println("Was not able to find a build file in: " + projectPath);
            return false;
        }

        if (buildDir == null)
            buildDir = new File(buildTool.getBuildDirectory());

        if (sourceDir == null)
            sourceDir = resolveSourceDir();

        if (!sourceDir.isDirectory()) {
            invocation.println("ERROR: The project's sources directory does not exists (" + sourceDir);
            return false;
        }

        if (!buildDir.isDirectory() || !new File(buildDir, "classes").isDirectory()) {
            invocation.println("Build directory (" + buildDir + " wasn't found. Compiling...");
            //if we run interactive, the name is 'compile-project', otherwise it's 'quarkus compile-project'
            try {
                invocation.executeCommand("compile-project");
            } catch (CommandNotFoundException e) {
                try {
                    invocation.executeCommand("quarkus compile-project");
                } catch (CommandNotFoundException | CommandLineParserException | OptionValidatorException
                        | CommandValidatorException | CommandException | InterruptedException | IOException e2) {
                    invocation.println("Failure during compile, aborting: " + e2.getMessage());
                    return false;
                }
            } catch (CommandLineParserException | OptionValidatorException
                    | CommandValidatorException | CommandException | InterruptedException | IOException e) {
                invocation.println("Failure during compile, aborting: " + e.getMessage());
                return false;
            }
        }

        return true;
    }

    private File resolveSourceDir() {
        return new File(projectPath.getAbsolutePath() + File.separatorChar + "src"
                + File.separatorChar + "main" + File.separatorChar + "java");
    }
}
