package net.oni2.platformtools.applicationinvoker;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.Date;
import java.util.List;
import java.util.Vector;

import net.oni2.platformtools.PlatformInformation;
import net.oni2.platformtools.PlatformInformation.Platform;

/**
 * @author Christian Illy
 */
public class ApplicationInvoker {
	private static Vector<String> buildCommandLine(EExeType exeType,
			File program, Vector<String> params, boolean ignoreFileNotFound)
			throws ERuntimeNotInstalledException, FileNotFoundException {
		if (!ignoreFileNotFound)
			if (program.getParentFile() != null && !program.exists())
				throw new FileNotFoundException();
		Vector<String> cmdLine = new Vector<String>();
		switch (exeType) {
			case DOTNET:
				if (!DotNet.isInstalled())
					throw new ERuntimeNotInstalledException(
							"No .NET runtime found");
				if (PlatformInformation.getPlatform() != Platform.WIN)
					cmdLine.add(DotNet.getRuntimeExe().getPath());
				cmdLine.add(program.getPath());
				break;
			case JAR:
				if (!Java.isInstalled())
					throw new ERuntimeNotInstalledException("JRE not found");
				cmdLine.add(Java.getRuntimeExe().getPath());
				cmdLine.add("-jar");
				cmdLine.add(program.getPath());
				break;
			case OSBINARY:
				cmdLine.add(program.getPath());
				break;
			case WINEXE:
				switch (PlatformInformation.getPlatform()) {
					case LINUX:
						if (!Wine.isInstalled())
							throw new ERuntimeNotInstalledException(
									"Can not run Windows executable because Wine was not found");
						cmdLine.add(Wine.getRuntimeExe().getPath());
						cmdLine.add(program.getPath());
						break;
					case MACOS:
						throw new ERuntimeNotInstalledException(
								"Can not run a Windows executable on MacOS");
					case WIN:
						cmdLine.add(program.getPath());
						break;
					case UNKNOWN:
						throw new ERuntimeNotInstalledException(
								"Can not run a Windows executable on an unidentified system");
				}
				break;
		}
		if (params != null)
			cmdLine.addAll(params);
		return cmdLine;
	}

	/**
	 * Execute a short running application and wait for termination
	 * 
	 * @param exeType
	 *            Type of executable to be run
	 * @param workingDirectory
	 *            Working directory for executed process
	 * @param program
	 *            Executable path
	 * @param params
	 *            List of command and arguments
	 * @param ignoreFileNotFound
	 *            Ignore if the program file is not found
	 * @return Error code and list of output lines
	 * @throws IOException
	 *             Exc
	 * @throws ERuntimeNotInstalledException
	 *             If the program to be executed requires a runtime which could
	 *             not be found on the system
	 * @throws FileNotFoundException
	 *             Program to be executed not found
	 */
	public static ApplicationInvocationResult executeAndWait(EExeType exeType,
			File workingDirectory, File program, Vector<String> params,
			boolean ignoreFileNotFound) throws ERuntimeNotInstalledException,
			FileNotFoundException, IOException {
		return executeAndWait(workingDirectory,
				buildCommandLine(exeType, program, params, ignoreFileNotFound));
	}

	/**
	 * Execute an app without waiting
	 * 
	 * @param exeType
	 *            Type of executable to be run
	 * @param workingDirectory
	 *            Working directory for executed process
	 * @param program
	 *            Executable path
	 * @param params
	 *            List of command and arguments
	 * @param ignoreFileNotFound
	 *            Ignore if the program file is not found
	 * @throws ERuntimeNotInstalledException
	 *             If the program to be executed requires a runtime which could
	 *             not be found on the system
	 * @throws FileNotFoundException
	 *             Program to be executed not found
	 */
	public static void execute(EExeType exeType, File workingDirectory,
			File program, Vector<String> params, boolean ignoreFileNotFound)
			throws ERuntimeNotInstalledException, FileNotFoundException {
		execute(buildCommandLine(exeType, program, params, ignoreFileNotFound),
				workingDirectory);
	}

	/**
	 * Execute a short running application and wait for termination
	 * 
	 * @param workingDirectory
	 *            Working directory for executed process
	 * @param cmdLine
	 *            List of command and arguments
	 * @return Error code and list of output lines
	 * @throws IOException
	 *             Exc
	 */
	private static ApplicationInvocationResult executeAndWait(
			File workingDirectory, Vector<String> cmdLine) throws IOException {
		long start = new Date().getTime();
		ProcessBuilder pb = new ProcessBuilder(cmdLine);
		pb.redirectErrorStream(true);
		Process proc = pb.start();
		if (workingDirectory != null)
			pb.directory(workingDirectory);

		InputStream is = proc.getInputStream();
		InputStreamReader isr = new InputStreamReader(is);
		BufferedReader br = new BufferedReader(isr);

		String line;
		Vector<String> lines = new Vector<String>();

		while ((line = br.readLine()) != null) {
			lines.add(line);
		}
		try {
			proc.waitFor();
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		return new ApplicationInvocationResult(proc.exitValue(), cmdLine,
				lines, (int) (new Date().getTime() - start));
	}

	/**
	 * Execute an app without waiting
	 * 
	 * @param cmd
	 *            Command and parameters
	 * @param workingDirectory
	 *            Working directory of app
	 */
	private static void execute(List<String> cmd, File workingDirectory) {
		try {
			ProcessBuilder pb = new ProcessBuilder(cmd);
			if (workingDirectory != null)
				pb.directory(workingDirectory);
			pb.start();
		} catch (IOException e) {
			e.printStackTrace();
		}
	}

}
