package org.phosphoresce.commons.thread;

import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;

import org.phosphoresce.commons.util.ObjectUtil;
import org.phosphoresce.commons.util.StringUtil;

/**
 * 処理非同期実行クラス<br>
 * <br>
 * 当クラスは時間のかかる処理タスクをバックグラウンドスレッド処理を行う上位抽象実行クラスです。<br>
 * スレッドインタフェースを拡張する形で処理に対する監視、制御を容易に扱うために設けられました。<br>
 * また、当クラスが扱うプロセスは一度のみの実行を想定しており、複数回の実行については別インスタンスを
 * 生成し、別の処理として実行を行う必要があります。<br>
 * 
 * @author Kitagawa<br>
 * 
 *<!--
 * 更新日		更新者			更新内容
 * 2009/05/14	Kitagawa		新規作成
 *-->
 */
public abstract class ProcessExecutor {

	/** 処理ステータス(待機) */
	private static final int PROCESS_READY = 0;

	/** 処理ステータス(実行中) */
	private static final int PROCESS_EXECUTING = 1;

	/** 処理ステータス(完了) */
	private static final int PROCESS_DONE = 2;

	/** 処理ステータス(一時停止) */
	private static final int PROCESS_WAIT = 3;

	/** 処理ステータス(キャンセル中) */
	private static final int PROCESS_CANCELING = 4;

	/** 処理ステータス(キャンセル) */
	private static final int PROCESS_CANCEL = 5;

	/** 処理ステータス(例外発生) */
	private static final int PROCESS_EXCEPTION = -1;

	/** 処理リスナ */
	private List listeners = new LinkedList();

	/** スレッドオブジェクト */
	private Thread thread = null;

	/** 発生例外 */
	private Throwable throwable = null;

	/** 処理結果 */
	private Map result = new HashMap();

	/** 処理ステータス */
	private int status = PROCESS_READY;

	/** メッセージ */
	private String message = null;

	/** 進捗不特定フラグ */
	private boolean progressUnspecified = true;

	/** 最小進捗カウント値 */
	private int progressMin = -1;

	/** 最大進捗カウント値 */
	private int progressMax = -1;

	/** 現在進捗カウント値 */
	private int progressCount = -1;

	/**
	 * コンストラクタ<br>
	 */
	public ProcessExecutor() {
		super();
	}

	/**
	 * 処理リスナを追加します。<br>
	 * @param listener 処理リスナオブジェクト
	 */
	public final void addListener(ProcessExecutorListener listener) {
		listeners.add(listener);
	}

	/**
	 * 指定された処理リスナを削除します。<br>
	 * @param listener 処理リスナオブジェクト
	 */
	public final void removeListener(ProcessExecutorListener listener) {
		listeners.remove(listener);
	}

	/**
	 * 登録されている処理リスナオブジェクトを取得します。<br>
	 * @return 
	 */
	public final ProcessExecutorListener[] getListeners() {
		return (ProcessExecutorListener[]) listeners.toArray(new ProcessExecutorListener[listeners.size()]);
	}

	/**
	 * バックグラウンドスレッドにおいて非同期処理を行います。<br>
	 * @throws Throwable 処理実行中に予期せぬエラーが発生した場合にスローされます
	 */
	protected abstract void process() throws Throwable;

	/**
	 * バックグラウンドスレッドプロセスのキャンセル処理を行います。<br>
	 * このメソッドが呼ばれることにより{@link #process()}で実行中の処理が中断されます。<br>
	 * @throws Throwable 処理実行中に予期せぬエラーが発生した場合にスローされます
	 */
	protected abstract void processCancel() throws Throwable;

	/**
	 * 処理メッセージを設定します。<br>
	 * ここで設定されたメッセージはリスナに対して通知が行われます。<br>
	 * @param message 処理メッセージ
	 */
	protected final void setMessage(String message) {
		String oldMessage = this.message;
		if (StringUtil.equals(message, oldMessage)) {
			return;
		}
		this.message = message;
		for (Iterator iterator = listeners.iterator(); iterator.hasNext();) {
			((ProcessExecutorListener) iterator.next()).updateMessage(this, message == null ? "" : message);
		}
	}

	/**
	 * 進捗不特定フラグを設定します。<br>
	 * @param progressUnspecified 進捗不特定フラグ
	 */
	protected final void setProgressUnspecified(boolean progressUnspecified) {
		if (this.progressUnspecified == progressUnspecified) {
			return;
		}
		this.progressUnspecified = progressUnspecified;
		for (Iterator iterator = listeners.iterator(); iterator.hasNext();) {
			((ProcessExecutorListener) iterator.next()).updateProgressUnspecified(this, progressUnspecified);
		}
	}

	/**
	 * 処理進捗カウントを設定します。<br>
	 * @param min 最小進捗カウント
	 * @param max 最大進捗カウント
	 * @param count 現在進捗カウント
	 */
	protected final void setProgress(int min, int max, int count) {
		if (progressMin == min && progressMax == max && progressCount == count) {
			return;
		}
		progressMin = min;
		progressMax = max;
		progressCount = count;
		for (Iterator iterator = listeners.iterator(); iterator.hasNext();) {
			((ProcessExecutorListener) iterator.next()).updateProgress(this, min, max, count);
		}
	}

	/**
	 * 処理結果を設定します。<br>
	 * ここで設定された結果はリスナに対して通知が行われます。<br>
	 * @param key 処理結果キー
	 * @param object 処理結果オブジェクト
	 */
	protected final void setResult(String key, Object object) {
		Object oldValue = result.get(key);
		if (ObjectUtil.equals(object, oldValue)) {
			return;
		}
		result.put(key, object);
		for (Iterator iterator = listeners.iterator(); iterator.hasNext();) {
			((ProcessExecutorListener) iterator.next()).updateResult(this, key, oldValue, object);
		}
	}

	/**
	 * 指定されたキーの処理結果を取得します。<br>
	 * @param key 処理結果キー
	 * @return 処理結果オブジェクト
	 */
	public final Object getResult(String key) {
		return result.get(key);
	}

	/**
	 * 処理時に例外が発生したか判定します。<br>
	 * @return 処理時に例外が発生した場合にtrueを返却
	 */
	public final boolean hasException() {
		return throwable != null;
	}

	/**
	 * 処理中に例外が発生した場合の例外オブジェクトを取得します。<br>
	 * @return 処理中に発生した例外オブジェクト
	 */
	public final Throwable getException() {
		return throwable;
	}

	/**
	 * 処理プロセスが開始待ち状態であるか判定します。<br>
	 * @return 開始待ち状態である場合にtrueを返却
	 */
	public final boolean isReady() {
		return status == PROCESS_READY;
	}

	/**
	 * 処理プロセスが実行中であるか判定します。<br>
	 * @return 実行中である場合にtrueを返却
	 */
	public final boolean isExecuting() {
		return status == PROCESS_EXECUTING || status == PROCESS_CANCELING;
	}

	/**
	 * 処理プロセスが終了状態にあるか判定します。<br>
	 * 終了状態と判定する条件として、正常終了した場合、キャンセルした場合、例外によって終了した場合となります。<br>
	 * ユーザは当メソッドによって終了状態を判定したのち、終了条件をさらに特定する必要があります。<br>
	 * @return 終了状態にある場合にtrueを返却
	 */
	public final boolean isDone() {
		return status == PROCESS_DONE || status == PROCESS_CANCEL || status == PROCESS_EXCEPTION;
	}

	/**
	 * 処理プロセスがキャンセルされたか判定します。<br>
	 * 但し、キャンセル中には当メソッドはtrueを返却しません。<br>
	 * @return キャンセルされた場合にtrueを返却
	 */
	public final boolean isCanceled() {
		return status == PROCESS_CANCEL;
	}

	/**
	 * 非同期処理を開始します。<br>
	 * 処理中の例外については{@link #getException()}によって取得します。<br>
	 */
	public final void start() {
		if (thread != null) {
			throw new IllegalThreadStateException("process is already started");
		}
		status = PROCESS_EXECUTING;
		thread = new Thread() {
			/**
			 * 処理を実行します。<br>
			 * @see java.lang.Thread#run()
			 */
			public void run() {
				try {
					process();
					if (status == PROCESS_CANCELING) {
						status = PROCESS_CANCEL;
						for (Iterator iterator = listeners.iterator(); iterator.hasNext();) {
							((ProcessExecutorListener) iterator.next()).processCanceled(ProcessExecutor.this);
						}
					} else {
						status = PROCESS_DONE;
						for (Iterator iterator = listeners.iterator(); iterator.hasNext();) {
							((ProcessExecutorListener) iterator.next()).processDone(ProcessExecutor.this);
						}
					}
				} catch (Throwable e) {
					throwable = e;
					status = PROCESS_EXCEPTION;
					for (Iterator iterator = listeners.iterator(); iterator.hasNext();) {
						((ProcessExecutorListener) iterator.next()).processException(ProcessExecutor.this, e);
					}
				}
			}
		};

		for (Iterator iterator = listeners.iterator(); iterator.hasNext();) {
			((ProcessExecutorListener) iterator.next()).processStart(this);
		}

		thread.start();
	}

	/**
	 * 実行されている非同期処理をキャンセルします。<br>
	 * 処理中の例外については{@link #getException()}によって取得します。<br>
	 */
	public final void cancel() {
		if (status == PROCESS_CANCELING || status == PROCESS_DONE || status == PROCESS_EXCEPTION || status == PROCESS_READY || status == PROCESS_WAIT) {
			return;
		}
		if (thread == null) {
			throw new IllegalThreadStateException("process is not started");
		}
		status = PROCESS_CANCELING;
		thread = new Thread() {
			/**
			 * 処理を実行します。<br>
			 * @see java.lang.Thread#run()
			 */
			public void run() {
				try {
					processCancel();
				} catch (Throwable e) {
					throwable = e;
					status = PROCESS_EXCEPTION;
				}
			}
		};

		for (Iterator iterator = listeners.iterator(); iterator.hasNext();) {
			((ProcessExecutorListener) iterator.next()).processCanceling(ProcessExecutor.this);
		}

		thread.start();
	}
}
