/*
 * Copyright 2011-2012 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 com.avast.server.hdfsshell.commands;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.core.io.Resource;
import org.springframework.shell.core.CommandMarker;
import org.springframework.shell.core.JLineShellComponent;
import org.springframework.shell.core.annotation.CliCommand;
import org.springframework.shell.core.annotation.CliOption;
import org.springframework.shell.support.logging.HandlerUtils;
import org.springframework.shell.support.util.IOUtils;
import org.springframework.shell.support.util.MathUtils;
import org.springframework.stereotype.Component;
import org.springframework.util.Assert;

import java.io.*;
import java.net.URL;
import java.util.ArrayList;
import java.util.Collection;
import java.util.logging.Logger;

/**
 * Copied from original ScriptCommand, the code is quite ugly...
 */
@Component
public class XScriptCommands implements CommandMarker {
    protected final Logger logger = HandlerUtils.getLogger(getClass());

    @Autowired
    private ApplicationContext applicationContext;

	@CliCommand(value = { "xscript" }, help = "Parses the specified resource file and executes its commands")
	public void script(
		@CliOption(key = { "", "file" }, help = "The file to locate and execute", mandatory = true) final File script,
		@CliOption(key = "lineNumbers", mandatory = false, specifiedDefaultValue = "true", unspecifiedDefaultValue = "false", help = "Display line numbers when executing the script") final boolean lineNumbers,
		@CliOption(key = "skipErrors", mandatory = false, specifiedDefaultValue = "true", unspecifiedDefaultValue = "true", help = "Skip error command inside script") final boolean skipErrors) {

		JLineShellComponent shell = applicationContext.getBean("shell", JLineShellComponent.class);
		Assert.notNull(script, "Script file to parse is required");
		double startedNanoseconds = System.nanoTime();
		final InputStream inputStream = openScript(script);

		BufferedReader in = null;
		try {
			in = new BufferedReader(new InputStreamReader(inputStream));
			String line;
			int i = 0;
			while ((line = in.readLine()) != null) {
				i++;
				if (lineNumbers) {
					logger.fine("Line " + i + ": " + line);
				} else {
					logger.fine(line);
				}
				if (!"".equals(line.trim())) {
					boolean success = shell.executeScriptLine(line);
					if (success && ((line.trim().startsWith("q") || line.trim().startsWith("ex")))) {
						break;
					} else if (!success) {
						if (!skipErrors) {
							// Abort script processing, given something went wrong
							throw new IllegalStateException("Script execution aborted");
						}
					}
				}
			}
		} catch (IOException e) {
			throw new IllegalStateException(e);
		} finally {
			IOUtils.closeQuietly(inputStream, in);
			double executionDurationInSeconds = (System.nanoTime() - startedNanoseconds) / 1000000000D;
			logger.fine("Script required " + MathUtils.round(executionDurationInSeconds, 3) + " seconds to execute");
		}
	}

	/**
	 * Opens the given script for reading
	 *
	 * @param script the script to read (required)
	 * @return a non-<code>null</code> input stream
	 */
	private InputStream openScript(final File script) {
		try {
			return new BufferedInputStream(new FileInputStream(script));
		} catch (final FileNotFoundException fnfe) {
			// Try to find the script via the classloader
			final Collection<URL> urls = findResources(script.getName());

			// Handle search failure
			Assert.notNull(urls, "Unexpected error looking for '" + script.getName() + "'");

			// Handle the search being OK but the file simply not being present
			Assert.notEmpty(urls, "Script '" + script + "' not found on disk or in classpath");
			Assert.isTrue(urls.size() == 1, "More than one '" + script + "' was found in the classpath; unable to continue");
			try {
				return urls.iterator().next().openStream();
			} catch (IOException e) {
				throw new IllegalStateException(e);
			}
		}
	}
	
	protected Collection<URL> findResources(final String path) {
		try {
			Resource[] resources = applicationContext.getResources(path);
			Collection<URL> list = new ArrayList<>(resources.length);
			for (Resource resource : resources) {
				list.add(resource.getURL());
			}
			return list;
		} catch (IOException ex) {
			logger.fine("Cannot find path " + path);
			// return Collections.emptyList();
			throw new RuntimeException(ex);
		}
	}

}