package org.arefgard.icerya.flow.node;

import java.util.Map;

import org.arefgard.icerya.flow.core.UsecaseFlow;
import org.arefgard.icerya.flow.exception.UsecaseFlowException;


public class Flow implements Runnable {

	private String name;
	/** フロー内のノードを保持するマップ */
	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 UsecaseFlow parent;
	
	/** 開始ノード名 */
	private String startNode;
	/** 終了ノード名 */
	private String endNode;
	
	public Flow(String name, UsecaseFlow parent) {
		this.name = name;
		this.parent = parent;
	}
	
	public void addNode(Node node) {
		if(node instanceof ReceiveNode) {
			this.startNode = node.getName();
		}
		this.nodes.put(node.getName(), node);
	}
	
	public Node getNode(String name) {
		return this.nodes.get(name);
	}
	
	public Map<String, Node> getNodes() {
		return this.nodes;
	}
	
	/**
	 * 入力値を追加する。
	 * 
	 * @param name
	 * @param obj
	 * @throws UsecaseFlowException
	 */
	public void addInput(String name, Object obj) throws UsecaseFlowException {
		this.inputs.put(name, obj);
	}
	
	/**
	 * 入力値マップを設定する。
	 * 
	 * @param inputs
	 * @throws UsecaseFlowException
	 */
	public void setInputs(Map<String, Object> inputs) throws UsecaseFlowException {
		this.inputs = inputs;
	}
	
	public Map<String, Object> getInputs() {
		return this.inputs;
	}
	
	/**
	 * 
	 * @param name
	 * @return
	 */
	public Object getOutput(String name) throws UsecaseFlowException {
		return this.outputs.get(name);
	}
	
	public Map<String, Object> getOutputs() {
		return this.outputs;
	}
	
	
	public void run() {
		try {
			this.execute();
			
		}catch(Exception e) {
		}
	}
	
	public void execute(Map<String, Object> param) throws Exception {
		this.inputs = param;
		execute();
	}
	
	public void execute() throws Exception {
		ReceiveNode start = (ReceiveNode)this.nodes.get(this.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(this.parent.getContainerPath(), 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) {
					this.parent.setThrowable(e);
					if(this.name.equals("_main")) {
						this.parent.setFinished(true);
					}
					break;
				}
				nextTo = waitNode.getNextTo();
			}else if(node instanceof ThrowNode) {
				// <throw>
				ThrowNode throwNode = (ThrowNode)node;
				try {
					this.parent.setThrowable(throwNode.getThrowable());
				}catch(Exception e) {
					if(this.name.equals("_main")) {
						this.parent.setFinished(true);
					}
					break;
				}
				if(this.name.equals("_main")) {
					this.parent.setFinished(true);
				}
				break;
			}else if(node instanceof IncludeNode) {
				// <include>
				IncludeNode includeNode = (IncludeNode)node;
				includeNode.call(this.inputs);
				nextTo = includeNode.getNextTo();
			}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();
				if(this.name.equals("_main")) {
					this.parent.setFinished(true);
				}
				break;
			}else if(node instanceof WhileNode) {
				// <while>
				WhileNode whileNode = (WhileNode)node;
				nextTo = whileNode.doWhile(this.inputs);
			}else if(node instanceof SyncNode) {
				// <sync>
				SyncNode syncNode = (SyncNode)node;
				nextTo = syncNode.execute();
			}else if(node instanceof LoadNode) {
				// <load>
				LoadNode loadNode = (LoadNode)node;
				loadNode.load(this.parent.getContainerPath(), this.inputs);
				nextTo = loadNode.getNextTo();
			}
		}
	}

	public Node getStartNode() {
		return this.nodes.get(this.startNode);
	}
	
	public Node getEndNode() {
		return this.nodes.get(this.endNode);
	}

	public void clear() {
		this.inputs.clear();
		this.outputs.clear();
	}

	public UsecaseFlow getParent() {
		return parent;
	}

	public void setParent(UsecaseFlow parent) {
		this.parent = parent;
	}


	
	
}
