package org.arefgard.icerya.flow.core;

import java.util.Iterator;
import java.util.Map;
import java.util.Set;

import org.arefgard.icerya.flow.exception.IllegalVariableException;
import org.arefgard.icerya.flow.exception.UsecaseFlowException;
import org.arefgard.icerya.flow.node.Flow;
import org.arefgard.icerya.flow.node.ReceiveNode;
import org.arefgard.icerya.flow.node.ReplyNode;

/**
 * ユースケースフロー。
 * <br/><br/>
 * ユースケースが終了するのを待って結果を得る「同期ユースケースフロー」と
 * ユースケースが終了するのを待たずに、後結果を得る「非同期ユースケースフロー」を
 * サポートする。<br/>
 * 作成したユースケースフローの処理時間が比較的長く、後で結果を知ればよい場合は
 * 「非同期ユースケースフロー」を使用する。結果が即時得られるような場合には、「同期
 * ユースフローを使用する。
 * 
 * @author Takashi Yamashina
 * @since 1.0.0
 */
public class UsecaseFlow {
	
	/** ユースケースフローのメイン名 **/
	private static final String MAIN_FLOW = "_main";
	
	/** ユースケースフロー定義ファイルのパス(クラスパス相対) */
	private String path;
	/** 継承元ユースケースのパス(クラスパス相対) */
	private String superUsecase;
	
	/** 変数を保持する定義マップ */
	private Map<String, String> variables = new java.util.HashMap<String, String>();
	/** シーケンスを保持するマップ */
	private Map<String, Flow> flows = new java.util.HashMap<String, Flow>();

	/** 例外 */
	private Exception throwable = null;
	
	private boolean finished = false;
	
	/**
	 * 例外を取得する。
	 * 
	 * @return ユースケースフロー中で発生した例外
	 */
	public Exception getTrowable() {
		return this.throwable;
	}
	
	/**
	 * 例外を設定する。
	 * 
	 * @param throwable ユースケースフロー中で発生した例外
	 */
	public void setThrowable(Exception throwable) {
		this.throwable = throwable;
	}
	
	public String getContainerPath() {
		return this.path;
	}
	
	void setContainerPath(String path) {
		this.path = path;
	}
	
	String getSuperUsecase() {
		return this.superUsecase;
	}
	
	void setSuperUsecase(String superUsecase) {
		this.superUsecase = superUsecase;
	}
	
	void addVar(String name, String className) {
		this.variables.put(name, className);
	}
	
	String getVar(String name) {
		return this.variables.get(name);
	}
	
	public void addFlow(String name, Flow flow) {
		this.flows.put(name, flow);
	}
	
	public Flow getFlow(String name) {
		return this.flows.get(name);
	}

	/**
	 * 入力値を追加する。
	 * 
	 * @param name
	 * @param obj
	 * @throws UsecaseFlowException
	 */
	public void addInput(String name, Object obj) throws UsecaseFlowException {
		ReceiveNode node = (ReceiveNode)this.flows.get(MAIN_FLOW).getStartNode();
		if(node.containsProperty(name)) {
			this.flows.get(MAIN_FLOW).addInput(name, obj);
		}else {
			throw new IllegalVariableException(name + "is illeagal input.");
		}
	}
	
	/**
	 * 入力値マップを設定する。
	 * 
	 * @param inputs
	 * @throws UsecaseFlowException
	 */
	public void setInputs(Map<String, Object> inputs) throws UsecaseFlowException {
		Set<String> keys = this.flows.get(MAIN_FLOW).getInputs().keySet();
		for(Iterator<String> itr = keys.iterator(); itr.hasNext();) {
			String key = itr.next();
			if(!this.flows.get(MAIN_FLOW).getInputs().containsKey(key)) {
				  throw new IllegalVariableException(key + " is illeagal input.");
			}
		}
		this.flows.get(MAIN_FLOW).setInputs(inputs);
	}
	
	/**
	 * 
	 * @param name
	 * @return
	 */
	public Object getOutput(String name) throws UsecaseFlowException {
		ReplyNode node = (ReplyNode)this.flows.get(MAIN_FLOW).getEndNode();
		if(node.containsProperty(name)) {
			return this.flows.get(MAIN_FLOW).getOutputs().get(name);
		}else {
			throw new IllegalVariableException(name + " is illeagal output.");
		}
	}
	
	/**
	 * 
	 * @return
	 * @throws UsecaseFlowException
	 */
	public Map<String, Object> getOutputs() throws UsecaseFlowException {
		return this.flows.get(MAIN_FLOW).getOutputs();
	}
	
	
	/**
	 * ユースケースフローを非同期実行する。
	 * 
	 * @throws UsecaseFlowException ユースケースフローの実行に失敗した場合
	 * @throws Exception throwノードから例外をthrowした場合
	 */
	public void executeAsync() throws UsecaseFlowException, Exception {
		Flow flow = this.getFlow(MAIN_FLOW);
		Thread thread = new Thread(flow);
		thread.start();
	}
	
	/**
	 * ユースケースフローを同期実行する。
	 * 
	 * @return 戻り値
	 * @throws UsecaseFlowException
	 * @throws Exception
	 */
	public Map<String, Object> executeSync() throws UsecaseFlowException, Exception {
		Flow flow = this.getFlow(MAIN_FLOW);
		flow.execute();
		if(this.throwable != null) {
			throw this.throwable;
		}
		return this.flows.get(MAIN_FLOW).getOutputs();
	}
	
	/**
	 * 入出力オブジェクトをクリアする。
	 * <br/>
	 * <br/>
	 * ユースケースフローを再利用する場合は、このメソッドを呼び出すことでパース直後の状態に
	 * 戻る。
	 */
	public void clear() {
		this.flows.get(MAIN_FLOW).clear();
	}

	public boolean isFinished() {
		return finished;
	}
	
	public void setFinished(boolean finished) {
		this.finished = finished;
	}
}
