package core.ipc.repeatClient;

import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.logging.Level;
import java.util.logging.Logger;

import argo.jdom.JsonNode;
import argo.jdom.JsonNodeFactories;
import core.ipc.IIPCService;
import utilities.JSONUtility;
import utilities.StringUtilities;

public abstract class IPCClientService extends IIPCService {

	private static final Logger LOGGER = Logger.getLogger(IPCClientService.class.getName());
	protected static final long TIMEOUT_MS = 5000;

	protected File executingProgram; // The program used to execute this ipc client
	protected Thread mainThread, forceDestroyThread;
	protected Process mainProcess;
	protected BufferedReader input;

	public void setExecutingProgram(File executablePath) {
		if (!executablePath.canExecute()) {
			LOGGER.warning("File is not executable: " + executablePath.getAbsolutePath());
			return;
		}

		this.executingProgram = executablePath;
	}

	@Override
	public final void start() throws IOException {
		if (executingProgram == null || !executingProgram.exists()) {
			getLogger().warning("Launcher " + executingProgram.getAbsolutePath() + " does not exist.");
			return;
		}

		if (!executingProgram.canExecute()) {
			getLogger().warning("Launcher " + executingProgram.getAbsolutePath() + " is not executable.");
			return;
		}

		final String[] cmd = getLaunchCmd();
		if (cmd == null) {
			getLogger().warning("Unable to retrieve launch cmd for " + getName());
			return;
		} else {
			getLogger().info("Executing " + StringUtilities.join(cmd, " ") + " in " + new File(".").getAbsolutePath());
		}

		mainThread = new Thread() {
			@Override
			public void run() {
				try {
					String line;
					ProcessBuilder processBuilder = new ProcessBuilder(cmd);
					processBuilder.directory(new File("."));
					processBuilder.redirectErrorStream(true);
					mainProcess = processBuilder.start();

					input = new BufferedReader(new InputStreamReader(mainProcess.getInputStream()));

					while ((line = input.readLine()) != null) {
						String trimmed = line.trim();
						if (trimmed.length() == 0) {
							continue;
						}

						getLogger().info("[" + getName() + "] - " + trimmed);
					}

					mainProcess.waitFor();
			    } catch (Exception e) {
			    	getLogger().log(Level.WARNING, "Encounter exception while running process: " + cmd[0] + cmd[1], e);
			    } finally {
			    	try {
						input.close();
					} catch (IOException e) {
						getLogger().log(Level.WARNING, "Failed to close input stream for " + getName(), e);
					}
			    }
			}
		};

		mainThread.start();
	}

	@Override
	public final void stop() throws IOException {
		if (forceDestroyThread != null) {
			getLogger().info("Waiting for " + getName() + " to terminate...");
			return;
		}

		forceDestroyThread = new Thread() {
			@Override
			public void run() {
				mainProcess.destroy();
				getLogger().info("Destroyed");

				try {
					Thread.sleep(TIMEOUT_MS);
				} catch (InterruptedException e) {
					getLogger().log(Level.WARNING, "Interrupted while waiting for " + getName() + " to terminate", e);
				}

				if (mainProcess.isAlive()) {
					getLogger().info("Forcing " + getName() + " termination");
					mainProcess.destroyForcibly();
				}
			}
		};
		forceDestroyThread.start();
	}

	@Override
	public final boolean isRunning() {
		boolean result = mainThread != null && mainThread.isAlive();
		if (!result) {
			forceDestroyThread = null;
		}
		return result;
	}

	@Override
	public final boolean setPort(int newPort) {
		this.port = newPort;
		return true;
	}

	@Override
	protected JsonNode getSpecificConfig() {
		JsonNode config = super.getSpecificConfig();
		return JSONUtility.addChild(config, "executing_program", executingProgram == null ?
				JsonNodeFactories.nullNode() : JsonNodeFactories.string(executingProgram.getAbsolutePath()));
	}

	@Override
	protected boolean extractSpecificConfig(JsonNode node) {
		if (!super.extractSpecificConfig(node)) {
			return false;
		}

		if (node.isNode("executing_program") && !node.isNullNode("executing_program")) {
			setExecutingProgram(new File(node.getStringValue("executing_program")));
		}

		return true;
	}

	protected abstract String[] getLaunchCmd();
}

@@ -1091,12 +1091,101 @@
 null
- %7C%7C 
+) %7B%0A%09%09%09getLogger().warning(%22Launcher does not exist. Doing nothing.%22);%0A%09%09%09return;%0A%09%09%7D%0A%0A%09%09if (
 !exe

Fix a small bug that cause NullPointerException when launching IPC client.