package org.arefgard.icerya.flow.node;

import java.util.Map;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.arefgard.icerya.flow.core.UsecaseFlow;
import org.arefgard.icerya.flow.exception.UsecaseFlowException;

/**
 * フローを表すスレッド。
 * 
 * @author Takashi Yamashina
 * @since 1.0.0
 *
 */
public class Flow implements Runnable {

	/** ロガー */
	private static final Log log = LogFactory.getLog(Flow.class);
	
	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();
		if(log.isDebugEnabled()) {
			log.debug("-----[receive]-----");
			log.debug("  name = " + start.getName() +
					  ", nextTo = " + start.getName());
		}
		Node node = null;
		while(true) {
			node = this.nodes.get(nextTo);
			if(node instanceof InvokeNode) {
				// <invoke>
				String containerPath = this.parent.getContainerPath();
				InvokeNode invokeNode = (InvokeNode)node;
				invokeNode.invoke(containerPath, this.inputs);
				if(log.isDebugEnabled()) {
					log.debug("invoke:");
					log.debug("  name = " + invokeNode.getName() + 
							  ", method = " + invokeNode.getMethod() + 
							  ", nextTo = " + invokeNode.getNextTo());
				}
				nextTo = invokeNode.getNextTo();
			}else if(node instanceof DecisionNode) {
				// <decide>
				DecisionNode decisionNode = (DecisionNode)node;
				nextTo = decisionNode.decide(this.inputs);
				if(log.isDebugEnabled()) {
					log.debug("decide:");
					log.debug("  name = " + decisionNode.getName() + 
							  ", nextTo = " + nextTo);
				}
			}else if(node instanceof WaitNode) {
				// <wait>
				WaitNode waitNode = (WaitNode)node;
				if(log.isDebugEnabled()) {
					log.debug("wait:");
					log.debug("  name = " + waitNode.getName() + 
							  ", time = " + waitNode.getTime() + 
							  ", nextTo = " + waitNode.getNextTo());
				}
				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;
				if(log.isDebugEnabled()) {
					log.debug("-----[throw]-----");
					log.debug("  name = " + throwNode.getName() + 
							  ", className = " + throwNode.getClassName());
				}
				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;
				if(log.isDebugEnabled()) {
					log.debug("include:");
					log.debug("  name = " + includeNode.getName() + 
							  ", path = " + includeNode.getPath() + 
							  ", flowName = " + includeNode.getFlowName() + 
							  ", nextTo = " + includeNode.getNextTo());
				}
				includeNode.call(this.inputs);
				nextTo = includeNode.getNextTo();
			}else if(node instanceof ReplyNode) {
				// <reply>
				ReplyNode replyNode = (ReplyNode)node;
				if(log.isDebugEnabled()) {
					log.debug("-----[reply]-----");
					log.debug("  name = " + replyNode.getName());
				}
				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);
				if(log.isDebugEnabled()) {
					log.debug("while:");
					log.debug("  name = " + whileNode.getName() +
							  ", nextTo = " + nextTo);
				}
			}else if(node instanceof SyncNode) {
				// <sync>
				SyncNode syncNode = (SyncNode)node;
				nextTo = syncNode.execute();
				if(log.isDebugEnabled()) {
					log.debug("sync:");
					log.debug("  name = " + syncNode.getName() +
							  ", nextTo = " + nextTo);
				}
			}else if(node instanceof LoadNode) {
				// <load>
				LoadNode loadNode = (LoadNode)node;
				loadNode.load(this.parent.getContainerPath(), this.inputs);
				nextTo = loadNode.getNextTo();
				if(log.isDebugEnabled()) {
					log.debug("load:");
					log.debug("  name = " + loadNode.getName() +
							  ", nextTo = " + nextTo);
				}
			}
		}
	}

	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;
	}


	
	
}
