/*
 * Copyright 2012-2017 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.springframework.boot.cli.command.run;

import java.io.File;
import java.util.Arrays;
import java.util.List;
import java.util.logging.Level;

import joptsimple.OptionSet;
import joptsimple.OptionSpec;

import org.springframework.boot.cli.command.Command;
import org.springframework.boot.cli.command.OptionParsingCommand;
import org.springframework.boot.cli.command.options.CompilerOptionHandler;
import org.springframework.boot.cli.command.options.OptionSetGroovyCompilerConfiguration;
import org.springframework.boot.cli.command.options.SourceOptions;
import org.springframework.boot.cli.command.status.ExitStatus;
import org.springframework.boot.cli.compiler.GroovyCompilerScope;
import org.springframework.boot.cli.compiler.RepositoryConfigurationFactory;
import org.springframework.boot.cli.compiler.grape.RepositoryConfiguration;

/**
 * {@link Command} to 'run' a groovy script or scripts.
 *
 * @author Phillip Webb
 * @author Dave Syer
 * @author Andy Wilkinson
 * @see SpringApplicationRunner
 */
public class RunCommand extends OptionParsingCommand {

	public RunCommand() {
		super("run", "Run a spring groovy script", new RunOptionHandler());
	}

	@Override
	public String getUsageHelp() {
		return "[options] <files> [--] [args]";
	}

	public void stop() {
		if (this.getHandler() != null) {
			((RunOptionHandler) this.getHandler()).stop();
		}
	}

	private static class RunOptionHandler extends CompilerOptionHandler {

		private final Object monitor = new Object();

		private OptionSpec<Void> watchOption;

		private OptionSpec<Void> verboseOption;

		private OptionSpec<Void> quietOption;

		private SpringApplicationRunner runner;

		@Override
		protected void doOptions() {
			this.watchOption = option("watch", "Watch the specified file for changes");
			this.verboseOption = option(Arrays.asList("verbose", "v"),
					"Verbose logging of dependency resolution");
			this.quietOption = option(Arrays.asList("quiet", "q"), "Quiet logging");
		}

		public void stop() {
			synchronized (this.monitor) {
				if (this.runner != null) {
					this.runner.stop();
				}
				this.runner = null;
			}
		}

		@Override
		protected synchronized ExitStatus run(OptionSet options) throws Exception {
			synchronized (this.monitor) {
				if (this.runner != null) {
					throw new RuntimeException(
							"Already running. Please stop the current application before running another (use the 'stop' command).");
				}

				SourceOptions sourceOptions = new SourceOptions(options);

				List<RepositoryConfiguration> repositoryConfiguration = RepositoryConfigurationFactory
						.createDefaultRepositoryConfiguration();
				repositoryConfiguration.add(0, new RepositoryConfiguration("local",
						new File("repository").toURI(), true));

				SpringApplicationRunnerConfiguration configuration = new SpringApplicationRunnerConfigurationAdapter(
						options, this, repositoryConfiguration);

				this.runner = new SpringApplicationRunner(configuration,
						sourceOptions.getSourcesArray(), sourceOptions.getArgsArray());
				this.runner.compileAndRun();

				return ExitStatus.OK;
			}
		}

		/**
		 * Simple adapter class to present the {@link OptionSet} as a
		 * {@link SpringApplicationRunnerConfiguration}.
		 */
		private class SpringApplicationRunnerConfigurationAdapter
				extends OptionSetGroovyCompilerConfiguration
				implements SpringApplicationRunnerConfiguration {

			SpringApplicationRunnerConfigurationAdapter(OptionSet options,
					CompilerOptionHandler optionHandler,
					List<RepositoryConfiguration> repositoryConfiguration) {
				super(options, optionHandler, repositoryConfiguration);
			}

			@Override
			public GroovyCompilerScope getScope() {
				return GroovyCompilerScope.DEFAULT;
			}

			@Override
			public boolean isWatchForFileChanges() {
				return getOptions().has(RunOptionHandler.this.watchOption);
			}

			@Override
			public Level getLogLevel() {
				if (isQuiet()) {
					return Level.OFF;
				}
				if (getOptions().has(RunOptionHandler.this.verboseOption)) {
					return Level.FINEST;
				}
				return Level.INFO;
			}

			@Override
			public boolean isQuiet() {
				return getOptions().has(RunOptionHandler.this.quietOption);
			}

		}

	}

}
