package org.ensembl.healthcheck.testcase;

import java.io.IOException;
import java.sql.Connection;
import java.util.List;

import org.ensembl.healthcheck.DatabaseRegistryEntry;
import org.ensembl.healthcheck.ReportManager;
import org.ensembl.healthcheck.util.ActionAppendable;
import org.ensembl.healthcheck.util.ProcessExec;

/**
 * <p>
 * 	Abstract class providing methods for running tests that are started from
 * the shell in a separate process.
 * </p>
 * 
 * @author mnuhn
 *
 */
public abstract class AbstractShellBasedTestCase extends SingleDatabaseTestCase {

	/**
	 * <p>
	 * Specify here, if the test implementing this class uses speciesIds. If 
	 * true, the method
	 * </p>
	 * 
	 * createCommandLine(dbre, speciesId);
	 * 
	 * <p>
	 * will be called for creating commands. If the test implementing this 
	 * class doesn't use speciesId, set to false and
	 * </p>
	 * 
	 * createCommandLine(dbre);
	 * 
	 * <p>
	 * will be called to get the command for executing the shell based test.
	 * </p>
	 */
	boolean isSpeciesIdAware = false;

	public boolean isSpeciesIdAware() {
		return isSpeciesIdAware;
	}

	public void setSpeciesIdAware(boolean iteratesOverSpeciesIds) {
		this.isSpeciesIdAware = iteratesOverSpeciesIds;
	}

	/**
	 * <p>
	 * 	Creates a default processor for output. Sends everything to 
	 * the method dispatchMessage.
	 * </p>
	 * 
	 */
	protected Appendable createOutputProcessor(final EnsTestCase e, final Connection c) {
		return 
			new ActionAppendable() {
				@Override public void process(String message) {
					dispatchMessage(e, c, message.trim());
				}
				
			};
	}
	
	/**
	 * <p>
	 * 	Forward any message received from the healthcheck to the ReportManager.
	 * Meant to be overridden with behaviour specific to the test.
	 * </p>
	 * 
	 * @param message
	 */
	protected void dispatchMessage(final EnsTestCase e, final Connection c, String message) {
		ReportManager.correct(e, c, message);
	}

	/**
	 * <p>
	 * 	Creates an Appendable object to which stdout is delegated. Overwrite 
	 * this method to determine what is to be done with output sent to stdout 
	 * from the subprocess.
	 * </p>
	 * 
	 */
	protected Appendable createStdoutProcessor(final EnsTestCase e, final Connection c) {
		return createOutputProcessor(e, c);
	}

	/**
	 * <p>
	 * 	Creates an Appendable object to which stderr is delegated. Overwrite 
	 * this method to determine what is to be done with output sent to stderr 
	 * from the subprocess.
	 * </p>
	 * 
	 */
	protected Appendable createStderrProcessor(final EnsTestCase e, final Connection c) {
		return createOutputProcessor(e, c);
	}
	
	/**
	 * <p>
	 * 	Use this, if your test uses species ids.
	 * </p>
	 * 
	 * @param dbre
	 * @param speciesId
	 * @return
	 */
	protected String createCommandLine(final DatabaseRegistryEntry dbre, int speciesId) { return null; }
	
	/**
	 * <p>
	 * 	Use this, if your test is independent of species ids.
	 * </p>
	 * 
	 * @param dbre
	 * @param speciesId
	 * @return
	 */
	protected String createCommandLine(final DatabaseRegistryEntry dbre) { return null; }
	
	/**
	 * <p>
	 * 	Does not set any environment variable by default. If your test requires
	 * certain environment variables to be set, override this method.
	 * </p>
	 * 
	 * @return
	 */
	protected String[] environmentVarsToSet() {
		return new String[]{};
	}

	public boolean runShellTest(final DatabaseRegistryEntry dbre, int speciesId, boolean useSpeciesId) {
		
		boolean passes = true;
		
		final EnsTestCase currentTestCase = this;
		
		Appendable out = createStdoutProcessor(currentTestCase, dbre.getConnection());
		Appendable err = createStderrProcessor(currentTestCase, dbre.getConnection());
		
		String lastCmdThatWasRun = "";
		
		try {
			
			String shellCmd;
			
			if (useSpeciesId) {
				shellCmd = createCommandLine(dbre, speciesId);
			} else {
				shellCmd = createCommandLine(dbre);
			}
			
			int exit = 1;
			
			if (shellCmd!=null) {
				
				//
				// Running the command by creating an array avoids the 
				// problem of java breaking the command down at spaces to
				// divide it into command and arguments.
				//
				// The command is passed to the bash so things like pipes and
				// backticks are interpreted. 
				//
				String[] cmdLineItems = new String[] {
						"/bin/bash",
						"-c",
						shellCmd,
				};
				
				exit = ProcessExec.exec(
					cmdLineItems, 
					out, 
					err, 
					false, 
					environmentVarsToSet()
				);
				
				lastCmdThatWasRun = shellCmd;
				
			}
			
			if (exit == 0) {
				ReportManager.correct(
						this, 
						dbre.getConnection(), 
						"Command \n"
						+ lastCmdThatWasRun 
						+ "\ncompleted successfully"
				);
			} else {
				ReportManager.problem(
						this, 
						dbre.getConnection(), 
						"Command \n"
						+ lastCmdThatWasRun 
						+ "\ndid not complete successfully"
				);
				passes = false;
			}
		} catch (IOException e) {
			ReportManager.problem(
				this, 
				dbre.getConnection(),
				"Could not execute " 
				+ lastCmdThatWasRun 
				+ "\nGot the following error: "
				+ e.getMessage()
			);
			passes = false;
		} 
		return passes;
	}
	
	@Override
	public boolean run(final DatabaseRegistryEntry dbre) {
		
		boolean passes = true;
		
		if (isSpeciesIdAware) {
			
			List<Integer> dbre_speciesIds = dbre.getSpeciesIds();
			
			// Make sure species ids were configured. If not, the perl test will 
			// not be run. The warning message may be overlooked by a user, 
			// therefore the test is set to fail in order to get attention.
			//
			if (dbre_speciesIds.size() == 0) {
				
				logger.warning(
					"No species ids! Perhaps no databases were configured?"
					+ " This test will not be run."
				);
				passes = false;
			}

			for (int speciesId : dbre_speciesIds) {
				runShellTest(dbre, speciesId, true);
			}

		} else {
			runShellTest(dbre, 0, false);
		}
		return passes;
	}
};

@@ -116,16 +116,60 @@
 .List;%0A%0A
+import org.apache.commons.lang.StringUtils;%0A
 import o
@@ -3949,16 +3949,37 @@
 %09%0A%09%09%09if 
+(!StringUtils.isEmpty
 (shellCm
@@ -3983,14 +3983,9 @@
 lCmd
-!=null
+)
 ) %7B%0A
@@ -4539,24 +4539,145 @@
 llCmd;%0A%09%09%09%09%0A
+%09%09%09%7D else %7B%0A%09%09%09%09throw new RuntimeException(%22Shell based test %22+this.getName()+%22 has not returned a valid command line%22);%0A
 %09%09%09%7D%0A%09%09%09%0A%09%09%09

throw an exception if no command is found