package jp.agentec.sinaburocast.common.proc;

import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import jp.agentec.sinaburocast.common.SinaburoConstant.LoggerName;
import jp.agentec.sinaburocast.common.exception.ProcessExecException;
import jp.agentec.sinaburocast.common.util.PropertyUtil;

import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang.math.NumberUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.log4j.Level;
import org.apache.log4j.Logger;

/**
 * 外部プロセスを実行するクラス<br>
 * コマンドは、スペースで区切って、文字列配列として<br>
 * 引数に渡す。
 *
 * 同時実行数の制御はしない。
 *
 * @author tsukada
 *
 */
public class ProcessUtil {
	private static final Logger LOGGER = Logger.getLogger(ProcessUtil.class);
	private static long NO_TIMEOUT = 0L;
	private static Log DEFAULT_LOGGER = LogFactory.getLog(LoggerName.PROCESS);

	/**
	 *
	 * 外部コマンドを実行する。(結果は自動的にログに出力し、リターンコードのみ返す)
	 *
	 * @param command コマンドリスト
	 * @param errLogLevel エラー時のログ出力エラーレベル
	 * @return 実行結果
	 *
	 * @throws ProcessExecException
	 */
	public static int execute(List<String> command, Level errLogLevel) throws ProcessExecException {
		StringBuffer stdErr = new StringBuffer();
		Map<String, String> env = new HashMap<String, String>();
		env.put("LANG", "C");
		int retVal = execute(command, null, stdErr, env, new DefaultReaderWorker(), DEFAULT_LOGGER, PropertyUtil.getInt("DEFAULT_PROCESS_TIMEOUT"));
		if (stdErr != null && stdErr.length() > 0) {
			LOGGER.log(errLogLevel, "stdErr:"+stdErr);
		}

		return retVal;
	}

	/**
	 * 外部コマンドを実行する。(リターンコードのみ返す)
	 *
	 * @param command
	 * @param timeout
	 * @param loggerName
	 * @return
	 * @throws ProcessExecException
	 */
	public static int execute(List<String> command, int timeout, String loggerName) throws ProcessExecException {
		Map<String, String> env = new HashMap<String, String>();
		env.put("LANG", "C");

		Log logger = StringUtils.isEmpty(loggerName)? DEFAULT_LOGGER: LogFactory.getLog(loggerName);
		int retVal = execute(command, null, null, env, new DefaultReaderWorker(), logger, timeout);

		return retVal;
	}

	/**
	 *
	 * 外部コマンドを実行する。(エラー出力をStringBufferにセットする)
	 * @param err
	 *
	 * @param command コマンドリスト
	 * @return 実行結果
	 *
	 * @throws ProcessExecException
	 */
	public static int execute(List<String> command, StringBuffer err, int timeout) throws ProcessExecException {
		return execute(command, null, err, null, null, DEFAULT_LOGGER, timeout);
	}

	/**
	 *
	 * 外部コマンドを実行する。(エラー出力をStringBufferにセットする)
	 * @param err
	 *
	 * @param command コマンドリスト
	 * @return 実行結果
	 *
	 * @throws ProcessExecException
	 */
	public static int execute(List<String> command, StringBuffer std, StringBuffer err, int timeout) throws ProcessExecException {
		return execute(command, std, err, null, null, DEFAULT_LOGGER, timeout);
	}

	/**
	 *
	 * 外部コマンドを実行する。(標準出力・エラー出力をStringで返す)
	 *
	 * @param command コマンドリスト
	 * @return 実行結果
	 *
	 * @throws ProcessExecException
	 */
	public static String execute(List<String> command) throws ProcessExecException {
		StringBuffer std = new StringBuffer();
		execute(command, std, std, null, null, DEFAULT_LOGGER, PropertyUtil.getInt("DEFAULT_PROCESS_TIMEOUT"));
		return std.toString();
	}

	/**
	 * 外部コマンドを実行する。
	 *
	 * @param command コマンドリスト
	 * @param stdOut 標準出力
	 * @return 実行結果
	 * @throws ProcessExecException
	 */
	public static int execute(List<String> command, StringBuffer stdOut) throws ProcessExecException {
		return execute(command, stdOut, null, null, null, DEFAULT_LOGGER, NO_TIMEOUT);
	}

	/**
	 * 外部コマンドを実行する。
	 *
	 * @param command コマンドリスト
	 * @param stdOut 標準出力
	 * @param stdErr 標準エラー出力
	 * @param env 環境変数マップ
	 * @return 実行結果
	 * @throws ProcessExecException
	 */
	@SuppressWarnings("unchecked")
	public static int execute(List<String> command, StringBuffer stdOut, StringBuffer stdErr, Map env) throws ProcessExecException {
		return execute(command, stdOut, stdErr, env, null, DEFAULT_LOGGER, NO_TIMEOUT);
	}

	/**
	 * 外部コマンドを実行する。
	 *
	 * @param command コマンドリスト
	 * @param stdOut 標準出力
	 * @param stdErr 標準エラー出力
	 * @param env 環境変数マップ
	 * @param millisecond タイムアウト(ミリ秒)
	 * @return 実行結果
	 * @throws ProcessExecException
	 */
	@SuppressWarnings("unchecked")
	public static int execute(List<String> command, StringBuffer stdOut, StringBuffer stdErr, Map env, long millisecond) throws ProcessExecException {
		return execute(command, stdOut, stdErr, env, null, DEFAULT_LOGGER, millisecond);
	}

	/**
	 * 外部コマンドを実行する。
	 *
	 * @param command コマンドリスト
	 * @param stdOut 標準出力
	 * @param stdErr 標準エラー出力
	 * @param env 環境変数マップ
	 * @param readerWorker 標準出力処理クラス
	 * @param errorLog エラーログ出力有無
	 * @param millisecond タイムアウト(ミリ秒)
	 * @return 実行結果
	 * @throws ProcessExecException
	 */
	@SuppressWarnings("unchecked")
	public static int execute(List<String> command, StringBuffer stdOut, StringBuffer stdErr, Map env, ReaderWorker readerWorker, Log logger, long millisecond) throws ProcessExecException {
		LOGGER.info("execute:" + command);

		ProcessExecutor processExecutor = new ProcessExecutor(stdOut, stdErr, readerWorker, logger, command, env);
		processExecutor.start();

		try {
			processExecutor.join(millisecond);
		} catch (InterruptedException e) {
			LOGGER.error("Thread sleep interrupted.", e);
		}

		if (!processExecutor.isCompleted()) {
			processExecutor.destroyProcess();
			if (processExecutor.getException() != null) {
				LOGGER.info("Process execution failed. " + command + " stdOut:" + stdOut  + "stdErr:" + stdErr);
				throw new ProcessExecException(processExecutor.getException(), "");
			}
			else {
				LOGGER.info("Process execution failed due to timeout after " + millisecond + " milliseconds " + command + " stdOut:" + stdOut  + "stdErr:" + stdErr);
				throw new ProcessExecException("Process was terminated due to timeout after " + millisecond + " milliseconds", "");
			}
		}

		return processExecutor.getRetVal();

	}

	public static void main(String[] args)  {
		StringBuffer stdOut = new StringBuffer();
		StringBuffer stdErr = new StringBuffer();
		String[] cmdArgs = new String[args.length - 1];
		for (int i = 0; i < cmdArgs.length; i++) {
			cmdArgs[i] = args[i + 1];
		}
		try {
			execute(Arrays.asList(cmdArgs), stdOut, stdErr, null, null, DEFAULT_LOGGER, NumberUtils.toLong(args[0]));
		} catch (ProcessExecException e) {
			e.printStackTrace();
		}
		System.out.println("stdOut:" + stdOut);
		System.out.println("stdErr:" + stdErr);
		System.out.println("FINISHED!!!!");
	}

}