package de.ugoe.cs.cpdp;

import java.io.File;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.logging.Level;

import de.ugoe.cs.cpdp.execution.IExecutionStrategy;
import de.ugoe.cs.util.console.Console;
import de.ugoe.cs.util.console.TextConsole;

/**
 * Executable that can be used to run experiments.
 * @author Steffen Herbold
 *
 */
public class Runner {
	
	/**
	 * Main class. The arguments are {@link ExperimentConfiguration} files. Each experiment is started in a separate thread. The number of concurrently running threads is the number of logical processors of the host system. 
	 * @param args experiment configuration files
	 */
	public static void main(String[] args) {
		new TextConsole(Level.FINE);
		final int concurrentThreads = Runtime.getRuntime().availableProcessors()-2;
		final ExecutorService threadPool = Executors.newFixedThreadPool(concurrentThreads);
		for( String arg : args ) {
			File file = new File(arg);
			if( file.isFile() ) {
				createConfig(threadPool, file.getAbsolutePath());
			}
			else if( file.isDirectory() ) {
				for( File subfile : file.listFiles() ) {
					if( subfile.isFile() ) {
						createConfig(threadPool, subfile.getAbsolutePath());
					}
				}
			}
		}
		threadPool.shutdown();
		try {
			threadPool.awaitTermination(Long.MAX_VALUE, TimeUnit.SECONDS);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
	}
	
	/**
	 * Creates the config and starts the corresponding experiment
	 * @param threadPool 
	 * @param configFile location of the config file
	 */
	public static void createConfig(ExecutorService threadPool, String configFile) {
		ExperimentConfiguration config = null;
		try {
			config = new ExperimentConfiguration(configFile);
		} catch (Exception e) {
			Console.printerrln("Failure initializing the experiment configuration for configuration file " + configFile);
			e.printStackTrace();
		}

		if( config!=null ) {
			Console.trace(Level.FINE, config.toString());
			// Instantiate the class like it was given as parameter in the config file and cast it to the interface
			try {
				// Because we need to pass a parameter, a normal new Instance call is not possible
				Class<?> executionStrategyClass = Class.forName("de.ugoe.cs.cpdp.execution."+config.getExecutionStrategy());
				Constructor<?> executionStrategyConstructor = 
						executionStrategyClass.getConstructor(ExperimentConfiguration.class);
			
				IExecutionStrategy experiment = (IExecutionStrategy) executionStrategyConstructor.newInstance(config);
				threadPool.execute(experiment);
			} catch (NoSuchMethodException e) {
				Console.printerrln("Class \"" + config.getExecutionStrategy()+ "\" does not have the right Constructor");
				e.printStackTrace();
			} catch (SecurityException e) {
				Console.printerrln("Security manager prevents reflection");
				e.printStackTrace();
			} catch (IllegalArgumentException e) {
				Console.printerrln("Class \"" + config.getExecutionStrategy()+ "\" does not have a Constructor, which"
						+ "matches the given arguments");
				e.printStackTrace();
			} catch (InvocationTargetException e) {
				Console.printerrln("Constructor in Class \"" + config.getExecutionStrategy()+ "\" is not public");
				e.printStackTrace();
			} catch (InstantiationException e) {
				Console.printerrln("Cannot instantiate Class \"" + config.getExecutionStrategy()+"\"");
				e.printStackTrace();
			} catch (IllegalAccessException e) {
				Console.printerrln("Cannot access Class \"" + config.getExecutionStrategy()+"\"");
				e.printStackTrace();
			} catch (ClassNotFoundException e) {
				Console.printerrln("Class \"" + config.getExecutionStrategy()+ "\" was not found");
				e.printStackTrace();
			}
			
		}
		
	}
}
