/*
 * Copyright 2011 the original author or authors.
 *
 * Licensed 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.gradle.launcher.daemon.client;

import org.gradle.api.GradleException;
import org.gradle.api.internal.classpath.DefaultModuleRegistry;
import org.gradle.api.logging.Logger;
import org.gradle.api.logging.Logging;
import org.gradle.launcher.daemon.DaemonExecHandleBuilder;
import org.gradle.launcher.daemon.bootstrap.DaemonGreeter;
import org.gradle.launcher.daemon.bootstrap.DaemonOutputConsumer;
import org.gradle.launcher.daemon.bootstrap.GradleDaemon;
import org.gradle.launcher.daemon.configuration.DaemonParameters;
import org.gradle.launcher.daemon.diagnostics.DaemonStartupInfo;
import org.gradle.launcher.daemon.registry.DaemonDir;
import org.gradle.process.ExecResult;
import org.gradle.process.internal.ExecHandle;
import org.gradle.util.Clock;
import org.gradle.util.CollectionUtils;
import org.gradle.util.GFileUtils;
import org.gradle.util.GradleVersion;

import java.io.File;
import java.util.ArrayList;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;

public class DefaultDaemonStarter implements DaemonStarter {

    private static final Logger LOGGER = Logging.getLogger(DefaultDaemonStarter.class);

    private final DaemonDir daemonDir;
    private final DaemonParameters daemonParameters;
    private final DaemonGreeter daemonGreeter;
    private final DaemonStartListener listener;

    public DefaultDaemonStarter(DaemonDir daemonDir, DaemonParameters daemonParameters, DaemonGreeter daemonGreeter, DaemonStartListener listener) {
        this.daemonDir = daemonDir;
        this.daemonParameters = daemonParameters;
        this.daemonGreeter = daemonGreeter;
        this.listener = listener;
    }

    public DaemonStartupInfo startDaemon() {
        DefaultModuleRegistry registry = new DefaultModuleRegistry();
        Set<File> bootstrapClasspath = new LinkedHashSet<File>();
        bootstrapClasspath.addAll(registry.getModule("gradle-launcher").getImplementationClasspath().getAsFiles());
        if (registry.getGradleHome() == null) {
            // Running from the classpath - chuck in everything we can find
            bootstrapClasspath.addAll(registry.getFullClasspath());
        }
        if (bootstrapClasspath.isEmpty()) {
            throw new IllegalStateException("Unable to construct a bootstrap classpath when starting the daemon");
        }

        new JvmVersionValidator().validate(daemonParameters);

        List<String> daemonArgs = new ArrayList<String>();
        daemonArgs.add(daemonParameters.getEffectiveJavaExecutable());

        List<String> daemonOpts = daemonParameters.getEffectiveJvmArgs();
        LOGGER.debug("Using daemon opts: {}", daemonOpts);
        daemonArgs.addAll(daemonOpts);
        //Useful for debugging purposes - simply uncomment and connect to debug
//        daemonArgs.add("-Xdebug");
//        daemonArgs.add("-Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=5006");
        daemonArgs.add("-cp");
        daemonArgs.add(CollectionUtils.join(File.pathSeparator, bootstrapClasspath));
        daemonArgs.add(GradleDaemon.class.getName());
        daemonArgs.add(GradleVersion.current().getVersion());
        daemonArgs.add(daemonDir.getBaseDir().getAbsolutePath());
        daemonArgs.add(String.valueOf(daemonParameters.getIdleTimeout()));
        daemonArgs.add(daemonParameters.getUid());

        //all remaining arguments are daemon startup jvm opts.
        //we need to pass them as *program* arguments to avoid problems with getInputArguments().
        daemonArgs.addAll(daemonOpts);

        DaemonStartupInfo daemonInfo = startProcess(daemonArgs, daemonDir.getVersionedDir());
        listener.daemonStarted(daemonInfo);
        return daemonInfo;
    }

    private DaemonStartupInfo startProcess(final List<String> args, final File workingDir) {
        LOGGER.info("Starting daemon process: workingDir = {}, daemonArgs: {}", workingDir, args);
        Clock clock = new Clock();
        try {
            GFileUtils.mkdirs(workingDir);

            DaemonOutputConsumer outputConsumer = new DaemonOutputConsumer();
            ExecHandle handle = new DaemonExecHandleBuilder().build(args, workingDir, outputConsumer);

            handle.start();
            LOGGER.debug("Gradle daemon process is starting. Waiting for the daemon to detach...");
            ExecResult result = handle.waitForFinish();
            LOGGER.debug("Gradle daemon process is now detached.");

            return daemonGreeter.parseDaemonOutput(outputConsumer.getProcessOutput(), result);
        } catch (GradleException e) {
            throw e;
        } catch (Exception e) {
            throw new GradleException("Could not start Gradle daemon.", e);
        } finally {
            LOGGER.info("An attempt to start the daemon took {}.", clock.getTime());
        }
    }

}