package org.arefgard.container.flow.service;

import java.io.InputStream;
import java.util.List;
import java.util.Map;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.xpath.XPath;
import javax.xml.xpath.XPathConstants;
import javax.xml.xpath.XPathExpression;
import javax.xml.xpath.XPathExpressionException;
import javax.xml.xpath.XPathFactory;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.arefgard.container.aop.ClassGenerator;
import org.arefgard.container.aop.MethodInvocation;
import org.arefgard.container.flow.WorkflowExecutor;
import org.w3c.dom.Document;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

public class ServiceWorkflowExecutor implements WorkflowExecutor {
	
	private Log log = LogFactory.getLog(ServiceWorkflowExecutor.class);
	
	private static String SEQUENCE_NODE_RECEIVE = "receive";
	private static String SEQUENCE_NODE_INVOKE = "invoke";
	private static String SEQUENCE_NODE_REPLY = "reply";
	private static String SEQUENCE_NODE_THROW = "throw";
	private static String SEQUENCE_NODE_DICISION = "decision";
	private static String SEQUENCE_NODE_WHILE = "while";

	private static String XPATH_NODE_INTERFACE = "/usecase/interfaces/interface";
	private static String XPATH_NODE_SEQUENCE = "/usecase/sequence/child::*";
	private static String XPATH_NODE_PARAMETER = "parameter";
	private static String XPATH_NODE_NAVIGATION = "navigation";
	
	private static String XPATH_ATTR_NAME = "string(@name)";
	private static String XPATH_ATTR_TYPE = "string(@type)";
	private static String XPATH_ATTR_METHOD = "string(@method)";
	private static String XPATH_ATTR_CONDITION = "string(@condition)";
	private static String XPATH_ATTR_NEXTTO = "string(@nextTo)";
	
	private XPathExpression XPATH_INTERFACE;
	private XPathExpression XPATH_SEQUENCE;
	private XPathExpression XPATH_PARAMETER;
	private XPathExpression XPATH_NAVIGATION;
	
	private XPathExpression XPATH_NAME;
	private XPathExpression XPATH_TYPE;
	private XPathExpression XPATH_METHOD;
	private XPathExpression XPATH_CONDITION;
	private XPathExpression XPATH_NEXTTO;
	
	private Map<String, Interface> input = null;
	private Map<String, Sequence> sequence = null;
	private Map<String, Object> output = null;
	
	private String flowPath = null;
	private String statringPoint = null;
	
	public ServiceWorkflowExecutor(String flowPath) {
		this.flowPath = flowPath;
		input = new java.util.HashMap<String, Interface>();
		sequence = new java.util.HashMap<String, Sequence>();
		output = new java.util.HashMap<String, Object>();
		
		XPathFactory factory = XPathFactory.newInstance();
		XPath xpath = factory.newXPath();
		try {
			XPATH_INTERFACE = xpath.compile(XPATH_NODE_INTERFACE);
			XPATH_SEQUENCE = xpath.compile(XPATH_NODE_SEQUENCE);
			XPATH_PARAMETER = xpath.compile(XPATH_NODE_PARAMETER);
			XPATH_NAVIGATION = xpath.compile(XPATH_NODE_NAVIGATION);
			
			XPATH_NAME = xpath.compile(XPATH_ATTR_NAME);
			XPATH_TYPE = xpath.compile(XPATH_ATTR_TYPE);
			XPATH_METHOD = xpath.compile(XPATH_ATTR_METHOD);
			XPATH_CONDITION = xpath.compile(XPATH_ATTR_CONDITION);
			XPATH_NEXTTO = xpath.compile(XPATH_ATTR_NEXTTO);
		}catch(Exception e) {
		}
		parseWorkflow();
	}
	
	public void execute() throws Exception {
		Receive start = (Receive)sequence.get(this.statringPoint);
		
		for(int i = 0; i < start.getParameterLength(); i++) {
			start.getParameter(i);
		}
		String navi = start.getNextTo();
		log.debug("start:" + navi);
		Object result = null;
		while(true) {
			Sequence sequenceDef = sequence.get(navi);
			if(sequenceDef instanceof Invoke) {
				Invoke invokeDef = (Invoke)sequenceDef;
				Object obj = input.get(invokeDef.getType()).getValue();
				String method = invokeDef.getMethod();
				List<Object> paramList = new java.util.ArrayList<Object>();
				for(int i = 0; i < invokeDef.getParameterLength(); i++) {
					paramList.add(input.get(invokeDef.getParameter(i)).getValue());
				}
				
				MethodInvocation invokeMethod = new MethodInvocation(obj);
				result = invokeMethod.execute(method, paramList);
				
				navi = invokeDef.getNextTo();
				log.debug("invoke:" + navi);
			}else if(sequenceDef instanceof Reply) {
				Reply replyDef = (Reply)sequenceDef;
				for(int i = 0; i < replyDef.getParameterLength(); i++) {
					String param = replyDef.getParameter(i);
					output.put(param, input.get(param).getValue());
				}
				log.debug("reply");
				break;
			}else if(sequenceDef instanceof Throw) {
				Throw throwDef = (Throw)sequenceDef;
				Object obj = ClassGenerator.generate(throwDef.getType());
				log.debug("throw");
				throw (Exception)obj;
			}else if(sequenceDef instanceof Decision) {
				Decision decisionDef = (Decision)sequenceDef;
				Navigation[] navis = decisionDef.getAllNavigations();
				for(int i = 0, n = navis.length; i < n; i++) {
					String left = navis[i].getLeft();
					String expr = navis[i].getExpr();
					String right = navis[i].getRight();
					if(left.equals("@return")) {
						if(result instanceof Boolean) {
							if(expr.equals("==") && right.equals("true") ||
								expr.equals("!=") && right.equals("false")) {
								if(((Boolean)result).booleanValue()) {
									navi = navis[i].getNextTo();
									break;
								}
							}else {
								if(!((Boolean)result).booleanValue()) {
									navi = navis[i].getNextTo();
									break;
								}
							}
						}
					}
				}
			}else if(sequenceDef instanceof While) {
				
			}
		}
	}

	public Object getOutput(String key) {
		return output.get(key);
	}

	public void setInput(String key, Object obj) {
		Interface interfaceDef = input.get(key);
		interfaceDef.setValue(obj);
		
		input.put(key, interfaceDef);
	}
	
	private void parseWorkflow() {
		InputStream is = ClassLoader.getSystemResourceAsStream(flowPath);
		if(is == null) {
			
		}
		
		DocumentBuilderFactory factory;
		Document doc = null;
		try {
			factory = DocumentBuilderFactory.newInstance();
			DocumentBuilder builder = factory.newDocumentBuilder();
			doc = builder.parse(is);
		}catch(Exception e) {
			
		}
		
		// 入出力インタフェースを得る
		try {
			NodeList interfaces = (NodeList)XPATH_INTERFACE.evaluate(doc, XPathConstants.NODESET);
			
			Node interfaceNode;
			for(int i = 0; i < interfaces.getLength();i++) {
				interfaceNode = interfaces.item(i);
				String name = this.getName(interfaceNode);
				String type = this.getType(interfaceNode);
				Interface interfaceDef = new Interface(name, type);
				input.put(name, interfaceDef);
			}
		}catch(Exception e) {
			e.printStackTrace();
		}
		
		// 処理シーケンスを得る
		try {
			NodeList sequences = (NodeList)XPATH_SEQUENCE.evaluate(doc, XPathConstants.NODESET);
			
			Node sequenceNode;
			for(int i = 0; i < sequences.getLength();i++) {
				sequenceNode = sequences.item(i);
				String nodeName = sequenceNode.getNodeName();
				String name;
				String type;
				String method;
				if(nodeName.endsWith(SEQUENCE_NODE_RECEIVE)) {
					// receiveノードの場合
					name = getName(sequenceNode);
					Receive receiveDef = new Receive(name);
					NodeList parameters = (NodeList)XPATH_PARAMETER.evaluate(sequenceNode, XPathConstants.NODESET);
					for(int j = 0; j < parameters.getLength();j++) {
						Node parameterNode = parameters.item(j);
						String param = getName(parameterNode);
						receiveDef.addParameter(param);
					}
					receiveDef.setNextTo(getNextTo(sequenceNode));
					sequence.put(name, receiveDef);
					this.statringPoint = name;
				}else if(nodeName.endsWith(SEQUENCE_NODE_INVOKE)) {
					// invokeノードの場合
					name = getName(sequenceNode);
					type = getType(sequenceNode);
					method = getMethod(sequenceNode);
					Invoke invokeDef = new Invoke(name, type, method);
					NodeList parameters = (NodeList)XPATH_PARAMETER.evaluate(sequenceNode, XPathConstants.NODESET);
					for(int j = 0; j < parameters.getLength();j++) {
						Node parameterNode = parameters.item(j);
						String param = getName(parameterNode);
						invokeDef.addParameter(param);
					}
					invokeDef.setNextTo(getNextTo(sequenceNode));
					sequence.put(name, invokeDef);
				}else if(nodeName.endsWith(SEQUENCE_NODE_REPLY)) {
					// replyノードの場合
					name = getName(sequenceNode);
					Reply replyDef = new Reply(name);
					NodeList parameters = (NodeList)XPATH_PARAMETER.evaluate(sequenceNode, XPathConstants.NODESET);
					for(int j = 0; j < parameters.getLength();j++) {
						Node parameterNode = parameters.item(j);
						String param = getName(parameterNode);
						replyDef.addParameter(param);
					}
					sequence.put(name, replyDef);
				}else if(nodeName.endsWith(SEQUENCE_NODE_THROW)) {
					// throwノードの場合
					name = getName(sequenceNode);
					type = getType(sequenceNode);
					Throw throwDef = new Throw(name, type);
					sequence.put(name, throwDef);
				}else if(nodeName.endsWith(SEQUENCE_NODE_DICISION)) {
					// decisionノードの場合
					name = getName(sequenceNode);
					Decision decisionDef = new Decision(name);
					NodeList navigations = (NodeList)XPATH_NAVIGATION.evaluate(sequenceNode, XPathConstants.NODESET);
					for(int j = 0, n = navigations.getLength(); j < n; j++) {
						Node navi = navigations.item(j);
						String condition = getCondition(navi);
						String nextTo = getNextTo(navi);
						decisionDef.addNavigation(new Navigation(condition, nextTo));
					}
					sequence.put(name, decisionDef);
				}else if(nodeName.endsWith(SEQUENCE_NODE_WHILE)) {
					// whileノードの場合
					
				}
			}
		}catch(Exception e) {
			e.printStackTrace();
		}
		
	}
	
	private String getName(Object obj) throws XPathExpressionException {
		return (String)XPATH_NAME.evaluate(obj, XPathConstants.STRING);
	}
	
	private String getType(Object obj) throws XPathExpressionException {
		return (String)XPATH_TYPE.evaluate(obj, XPathConstants.STRING);
	}
	
	private String getMethod(Object obj) throws XPathExpressionException {
		return (String)XPATH_METHOD.evaluate(obj, XPathConstants.STRING);
	}
	
	private String getCondition(Object obj) throws XPathExpressionException {
		return (String)XPATH_CONDITION.evaluate(obj, XPathConstants.STRING);
	}
	
	private String getNextTo(Object obj) throws XPathExpressionException {
		return (String)XPATH_NEXTTO.evaluate(obj, XPathConstants.STRING);
	}
}
