package io.hawt.maven;

import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.Set;

import io.hawt.maven.util.IsolatedThreadGroup;
import org.apache.maven.artifact.Artifact;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.plugin.MojoFailureException;
import org.apache.maven.plugins.annotations.Execute;
import org.apache.maven.plugins.annotations.LifecyclePhase;
import org.apache.maven.plugins.annotations.Mojo;
import org.apache.maven.plugins.annotations.Parameter;
import org.apache.maven.plugins.annotations.ResolutionScope;

@Mojo(name = "run", defaultPhase = LifecyclePhase.PROCESS_TEST_CLASSES, requiresDependencyResolution = ResolutionScope.RUNTIME)
@Execute(phase = LifecyclePhase.PROCESS_TEST_CLASSES)
public class RunMojo extends BaseMojo {

    @Parameter(property = "hawtio.port", defaultValue = "8080")
    int port;

    @Parameter(property = "hawtio.context", defaultValue = "hawtio")
    String context;

    @Parameter(property = "hawtio.mainClass")
    String mainClass;

    @Parameter(property = "hawtio.arguments")
    String[] arguments;

    @Parameter(property = "hawtio.systemProperties")
    Map<String, String> systemProperties;

    boolean bootstrapMain = true;

    ClassLoader classLoader;

    @Override
    protected MojoLifecycle createMojoLifecycle() {
        return new DefaultMojoLifecycle(getLog());
    }

    @Override
    public void execute() throws MojoExecutionException, MojoFailureException {
        // use hawtio-app
        extendedPluginDependencyArtifactId = "hawtio-app";

        try {
            doPrepareArguments();
            doBeforeExecute();
            doExecute();
            doAfterExecute();
        } catch (Exception e) {
            throw new MojoExecutionException("Error executing", e);
        }
    }

    protected void doPrepareArguments() throws Exception {
        List<String> args = new ArrayList<String>();

        addCustomArguments(args);

        if (arguments != null) {
            args.addAll(Arrays.asList(arguments));
        }

        arguments = new String[args.size()];
        args.toArray(arguments);

        if (getLog().isDebugEnabled()) {
            StringBuilder msg = new StringBuilder("Invoking: ");
            msg.append(mainClass);
            msg.append(".main(");
            for (int i = 0; i < arguments.length; i++) {
                if (i > 0) {
                    msg.append(", ");
                }
                msg.append(arguments[i]);
            }
            msg.append(")");
            getLog().debug(msg);
        }
    }

    /**
     * To add any custom arguments
     *
     * @param args the arguments
     */
    protected void addCustomArguments(List<String> args) throws Exception {
        // noop
    }

    protected void doExecute() throws Exception {
        if (mainClass == null) {
            throw new IllegalArgumentException("Option mainClass must be specified");
        }

        if (systemProperties != null && !systemProperties.isEmpty()) {
            for (Map.Entry<String, String> entry : systemProperties.entrySet()) {
                System.setProperty(entry.getKey(), entry.getValue());
            }
            getLog().info("Adding system properties: " + systemProperties);
        }

        final IsolatedThreadGroup threadGroup = new IsolatedThreadGroup(this, mainClass);
        final Thread bootstrapThread = new Thread(threadGroup, new Runnable() {
            public void run() {
                try {
                    beforeBootstrapHawtio();

                    getLog().info("Starting hawtio ...");
                    getLog().info("*************************************");
                    Method hawtioMain = Thread.currentThread().getContextClassLoader().loadClass("io.hawt.app.App")
                            .getMethod("main", String[].class);
                    String[] args = new String[]{"--context", context, "--port", "" + port};
                    hawtioMain.invoke(null, new Object[]{args});

                    afterBootstrapHawtio();

                    beforeBootstrapMain();

                    if (bootstrapMain) {
                        getLog().info("Starting " + mainClass + "...");
                        getLog().info("*************************************");
                        Method main = Thread.currentThread().getContextClassLoader().loadClass(mainClass)
                                .getMethod("main", String[].class);
                        main.invoke(main, new Object[] {arguments});
                    }

                    afterBootstrapMain();

                } catch (Exception e) { // just pass it on
                    // let it be printed so end users can see the exception on the console
                    getLog().error("*************************************");
                    getLog().error("Error occurred while running main from: " + mainClass);
                    getLog().error(e);
                    getLog().error("*************************************");
                    Thread.currentThread().getThreadGroup().uncaughtException(Thread.currentThread(), e);
                }

                // notify before we die
                getLog().info("Terminating thread " + Thread.currentThread());
            }
        }, mainClass + ".main()");

        // resolve artifacts to be used
        Set<Artifact> artifacts = resolveArtifacts();
        resolvedArtifacts(artifacts);

        classLoader = getClassLoader(artifacts);
        bootstrapThread.setContextClassLoader(classLoader);

        bootstrapThread.start();
        mojoLifecycle.join(threadGroup);

        try {
            getLog().info("We will now terminate thread group " + threadGroup);
            mojoLifecycle.terminateThreads(threadGroup);
            threadGroup.destroy();
        } catch (IllegalThreadStateException e) {
            getLog().warn("Cannot destroy thread group " + threadGroup, e);
        }

        if (threadGroup.getUncaughtException() != null) {
            throw new MojoExecutionException("Uncaught exception", threadGroup.getUncaughtException());
        }
    }

    protected void beforeBootstrapMain() throws Exception {
        // noop
    }

    protected void afterBootstrapMain() throws Exception {
        // noop
    }

    protected void beforeBootstrapHawtio() throws Exception {
        // noop
    }

    protected void afterBootstrapHawtio() throws Exception {
        // noop
    }

}
