package org.arefgard.flow.core;

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

import org.arefgard.flow.exception.IllegalVariableException;
import org.arefgard.flow.exception.UsecaseFlowException;
import org.arefgard.flow.exception.UsecaseFlowExecuteException;
import org.arefgard.flow.node.DecisionNode;
import org.arefgard.flow.node.IncludeNode;
import org.arefgard.flow.node.InvokeNode;
import org.arefgard.flow.node.Node;
import org.arefgard.flow.node.ReceiveNode;
import org.arefgard.flow.node.ReplyNode;
import org.arefgard.flow.node.ThrowNode;
import org.arefgard.flow.node.WaitNode;

/**
 * ユースケースフロー。
 * 
 * @author Takashi Yamashina
 * @since 1.0.0
 */
public class UsecaseFlow {
	
	/** ユースケースフロー定義ファイルのパス(クラスパス相対) */
	private String path;
	/** 拡張元ユースケースのパス(クラスパス相対) */
	private String extendsUsecase;
	
	/** 変数を保持する定義マップ */
	private Map<String, String> variables = new java.util.HashMap<String, String>();
	/** フローの各ノードを保持する定義マップ */
	private Map<String, Node> nodes = new java.util.HashMap<String, Node>();
	
	/** 入力オブジェクトを保持するマップ */
	private Map<String, Object> inputs = new java.util.HashMap<String, Object>();
	/** 出力オブジェクトを保持するマップ */
	private Map<String, Object> outputs = new java.util.HashMap<String, Object>();
	
	/** 開始ノード名 */
	private String startNode;
	/** 終了ノード名 */
	private String endNode;
	
	String getContainerPath() {
		return this.path;
	}
	
	void setContainerPath(String path) {
		this.path = path;
	}
	
	String getExtends() {
		return this.extendsUsecase;
	}
	
	void setExtends(String extendsUsecase) {
		this.extendsUsecase = extendsUsecase;
	}
	
	void addVar(String name, String className) {
		this.variables.put(name, className);
	}
	
	String getVar(String name) {
		return this.variables.get(name);
	}
	
	void addNode(Node node) {
		if(node instanceof ReceiveNode) {
			ReceiveNode receiveNode = (ReceiveNode)node;
			for(int i = 0, n = receiveNode.getPropertySize();i < n;i++) {
				if(!this.variables.containsKey(receiveNode.getProperty(i))) {
					
				}
			}
			
			this.startNode = node.getName();
		}else if(node instanceof ReplyNode) {
			ReplyNode replyNode = (ReplyNode)node;
			for(int i = 0, n = replyNode.getPropertySize();i < n;i++) {
				if(!this.variables.containsKey(replyNode.getProperty(i))) {
					
				}
			}
		}
		this.nodes.put(node.getName(), node);
	}
	
	Node getNode(String name) {
		return this.nodes.get(name);
	}

	/**
	 * 入力値を追加する。
	 * 
	 * @param name
	 * @param obj
	 * @throws UsecaseFlowException
	 */
	public void addInput(String name, Object obj) throws UsecaseFlowException {
		ReceiveNode node = (ReceiveNode)this.nodes.get(this.startNode);
		if(node.containsProperty(name)) {
			this.inputs.put(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 = inputs.keySet();
		for(Iterator<String> itr = keys.iterator(); itr.hasNext();) {
			String key = itr.next();
			if(!this.inputs.containsKey(key)) {
				  throw new IllegalVariableException(key + " is illeagal input.");
			}
		}
		this.inputs = inputs;
	}
	
	/**
	 * 
	 * @param name
	 * @return
	 */
	public Object getOutput(String name) throws UsecaseFlowException {
		ReplyNode node = (ReplyNode)this.nodes.get(this.endNode);
		if(node.containsProperty(name)) {
			return this.outputs.get(name);
		}else {
			throw new IllegalVariableException(name + " is illeagal output.");
		}
	}
	
	/**
	 * ユースケースフローを実行する。
	 * 
	 * @throws UsecaseFlowException ユースケースフローの実行に失敗した場合
	 * @throws Exception throwノードから例外をthrowした場合
	 */
	public void execute() throws UsecaseFlowException, Exception {
		ReceiveNode start = (ReceiveNode)this.nodes.get(startNode);
		String nextTo = start.getNextTo();
		Node node = null;
		while(true) {
			node = this.nodes.get(nextTo);
			if(node instanceof InvokeNode) {
				// <invoke>
				InvokeNode invokeNode = (InvokeNode)node;
				invokeNode.invoke(path, this.inputs);
				nextTo = invokeNode.getNextTo();
			}else if(node instanceof DecisionNode) {
				// <decide>
				DecisionNode decisionNode = (DecisionNode)node;
				nextTo = decisionNode.decide(this.inputs);
			}else if(node instanceof WaitNode) {
				// <wait>
				WaitNode waitNode = (WaitNode)node;
				try {
					waitNode.doWait();
				}catch(InterruptedException e) {
					throw new UsecaseFlowExecuteException("Wait interruped.", e);
				}
				nextTo = waitNode.getNextTo();
			}else if(node instanceof ThrowNode) {
				// <throw>
				ThrowNode throwNode = (ThrowNode)node;
				throwNode.throwException();
			}else if(node instanceof IncludeNode) {
				IncludeNode includeNode = (IncludeNode)node;
				includeNode.call(this.inputs);
			}else if(node instanceof ReplyNode) {
				// <reply>
				ReplyNode replyNode = (ReplyNode)node;
				for(int i = 0, n = replyNode.getPropertySize();i < n; i++) {
					this.outputs.put(replyNode.getProperty(i), this.inputs.get(replyNode.getProperty(i)));
				}
				this.endNode = replyNode.getName();
				break;
			}
		}
	}
	
	/**
	 * 入出力オブジェクトをクリアする。
	 * <br/>
	 * <br/>
	 * ユースケースフローを再利用する場合は、このメソッドを呼び出すことでパース直後の状態に
	 * 戻る。
	 */
	public void clear() {
		this.inputs.clear();
		this.outputs.clear();
	}
}
