package org.arefgard.icerya.flow.core;

import org.arefgard.icerya.flow.node.DecisionNode;
import org.arefgard.icerya.flow.node.Flow;
import org.arefgard.icerya.flow.node.IncludeNode;
import org.arefgard.icerya.flow.node.InvokeNode;
import org.arefgard.icerya.flow.node.LoadNode;
import org.arefgard.icerya.flow.node.ReceiveNode;
import org.arefgard.icerya.flow.node.ReplyNode;
import org.arefgard.icerya.flow.node.SyncNode;
import org.arefgard.icerya.flow.node.ThrowNode;
import org.arefgard.icerya.flow.node.WaitNode;
import org.arefgard.icerya.flow.node.WhileNode;
import org.xml.sax.Attributes;
import org.xml.sax.ContentHandler;
import org.xml.sax.Locator;
import org.xml.sax.SAXException;

/**
 *  ユースケースフローをパースするためのContentHandler。
 * 
 * @author Takashi Yamashina
 * @since 1.0.0
 */
public class FlowHandler implements ContentHandler {

	private static final String NODE_USECASE = "usecase";
	private static final String NODE_FLOW = "flow";
	private static final String NODE_VARIABLE = "var";
	private static final String NODE_RECEIVE = "receive";
	private static final String NODE_REPLY = "reply";
	private static final String NODE_INVOKE = "invoke";
	private static final String NODE_DECISION = "decision";
	private static final String NODE_WAIT = "wait";
	private static final String NODE_THROW = "throw";
	private static final String NODE_INCLUDE = "include";
	private static final String NODE_LOAD = "load";
	private static final String NODE_SYNC = "sync";
	private static final String NODE_WHILE = "while";
	
	private static final String NODE_PARAMETER = "parameter";
	private static final String NODE_NAVIGATION = "navigation";
	private static final String NODE_RETURN = "return";
	
	private static final String ATTR_CONTAINER = "container";
	private static final String ATTR_NAME = "name";
	private static final String ATTR_CLASS = "class";
	private static final String ATTR_NEXTTO = "nextTo";
	private static final String ATTR_TYPE = "type";
	private static final String ATTR_METHOD = "method";
	private static final String ATTR_TIME = "time";
	private static final String ATTR_PATH = "path";
	private static final String ATTR_INHERIT = "inherit";
	private static final String ATTR_FLOW = "flow";
	private static final String ATTR_VAR = "var";
	private static final String ATTR_FLOWNAME = "flowName";
	
	private UsecaseFlow ucFlow;
	private String nowFlow;
	private String nowReceive;
	private String nowReply;
	private String nowInvoke;
	private String nowDecision;
	private String nowNavigation;
	private String nowWhile;
	private String nowSync;
	
	public FlowHandler(UsecaseFlow flow) {
		this.ucFlow = flow;
	}
	
	public void characters(char[] ch, int start, int length)
			throws SAXException {
		if(this.nowDecision != null && this.nowNavigation != null) {
			DecisionNode now = (DecisionNode)this.ucFlow.getFlow(this.nowFlow).getNode(this.nowDecision);
			now.addProperty(new String(ch, start, length).trim(), this.nowNavigation);
		}if(this.nowWhile != null) {
			WhileNode now = (WhileNode)this.ucFlow.getFlow(this.nowFlow).getNode(this.nowWhile);
			if(now.getExpr() != null) {
				now.setExpr(now.getExpr() + new String(ch, start, length).trim());
			}else {
				now.setExpr(new String(ch, start, length).trim());
			}
		}
	}

	public void endDocument() throws SAXException {

	}

	public void endElement(String uri, String localName, String qName)
			throws SAXException {
		if(qName.endsWith(NODE_FLOW)) {
			// <flow>
			this.nowFlow = null;
		}else if(qName.endsWith(NODE_RECEIVE)) {
			// <bean>
			this.nowReceive = null;
		}else if(qName.endsWith(NODE_REPLY)) {
			// <bean>
			this.nowReply = null;
		}else if(qName.endsWith(NODE_INVOKE)) {
			// <invoke>
			this.nowInvoke = null;
		}else if(qName.endsWith(NODE_DECISION)) {
			// <decision>
			this.nowDecision = null;
		}else if(this.nowDecision != null && qName.endsWith(NODE_NAVIGATION)) {
			this.nowNavigation = null;
		}else if(qName.endsWith(NODE_WHILE)) {
			this.nowWhile = null;
		}else if(qName.endsWith(NODE_SYNC)) {
			this.nowSync = null;
		}
	}

	public void endPrefixMapping(String prefix) throws SAXException {

	}

	public void ignorableWhitespace(char[] ch, int start, int length)
			throws SAXException {

	}

	public void processingInstruction(String target, String data)
			throws SAXException {

	}

	public void setDocumentLocator(Locator locator) {

	}

	public void skippedEntity(String name) throws SAXException {

	}

	public void startDocument() throws SAXException {

	}

	public void startElement(String uri, String localName, String qName, Attributes atts) throws SAXException {
		if(qName.endsWith(NODE_USECASE)) {
			this.ucFlow.setSuperUsecase(atts.getValue(ATTR_INHERIT));
			if(this.ucFlow.getSuperUsecase() != null) {
				UsecaseFlowDefinitionReader reader = new UsecaseFlowDefinitionReader(this.ucFlow);
				try {
					reader.parse(this.ucFlow.getSuperUsecase());
				}catch(Exception e) {
					new SAXException("Super usecase flow parse fail.", e);
				}
			}
			String containerPath = atts.getValue(ATTR_CONTAINER);
			if(containerPath != null && !containerPath.isEmpty()) {
				this.ucFlow.setContainerPath(containerPath);
			}
		}else if(qName.endsWith(NODE_FLOW)) {
			String name = atts.getValue(ATTR_NAME);
			Flow flow = this.ucFlow.getFlow(name);
			if(flow == null) {
				flow = new Flow(name, this.ucFlow);
			}
			this.ucFlow.addFlow(name, flow);
			this.nowFlow = name;
		}else if(qName.endsWith(NODE_VARIABLE )) {
			this.ucFlow.addVar(atts.getValue(ATTR_NAME), atts.getValue(ATTR_CLASS));
		}else if(qName.endsWith(NODE_RECEIVE)) {
			// <receive>
			ReceiveNode node = new ReceiveNode();
			node.setName(atts.getValue(ATTR_NAME));
			node.setParent(this.ucFlow.getFlow(this.nowFlow));
			node.setNextTo(atts.getValue(ATTR_NEXTTO));
			this.nowReceive = node.getName();
			this.ucFlow.getFlow(this.nowFlow).addNode(node);
		}else if(this.nowReceive != null && qName.endsWith(NODE_PARAMETER)) {
			ReceiveNode node = (ReceiveNode)this.ucFlow.getFlow(this.nowFlow).getNode(nowReceive);
			node.addProperty(atts.getValue(ATTR_NAME));
		}else if(qName.endsWith(NODE_REPLY)) {
			// <reply>
			ReplyNode node = new ReplyNode();
			node.setName(atts.getValue(ATTR_NAME));
			node.setParent(this.ucFlow.getFlow(this.nowFlow));
			this.nowReply = node.getName();
			this.ucFlow.getFlow(this.nowFlow).addNode(node);
		}else if(this.nowReply != null && qName.endsWith(NODE_PARAMETER)) {
			ReplyNode node = (ReplyNode)this.ucFlow.getFlow(this.nowFlow).getNode(nowReply);
			node.addProperty(atts.getValue(ATTR_NAME));
		}else if(qName.endsWith(NODE_INVOKE)) {
			// <invoke>
			InvokeNode node = new InvokeNode();
			node.setName(atts.getValue(ATTR_NAME));
			node.setParent(this.ucFlow.getFlow(this.nowFlow));
			node.setType(atts.getValue(ATTR_TYPE));
			node.setMethod(atts.getValue(ATTR_METHOD));
			node.setNextTo(atts.getValue(ATTR_NEXTTO));
			this.nowInvoke = node.getName();
			this.ucFlow.getFlow(this.nowFlow).addNode(node);
		}else if(this.nowInvoke != null && qName.endsWith(NODE_PARAMETER)) {
			InvokeNode node = (InvokeNode)this.ucFlow.getFlow(this.nowFlow).getNode(nowInvoke);
			node.addProperty(atts.getValue(ATTR_NAME));
		}else if(this.nowInvoke != null && qName.endsWith(NODE_RETURN)) {
			InvokeNode node = (InvokeNode)this.ucFlow.getFlow(this.nowFlow).getNode(nowInvoke);
			node.setReturn(atts.getValue(ATTR_NAME));
		}else if(qName.endsWith(NODE_DECISION)) {
			// <decision>
			DecisionNode node = new DecisionNode();
			node.setName(atts.getValue(ATTR_NAME));
			node.setParent(this.ucFlow.getFlow(this.nowFlow));
			this.nowDecision = node.getName();
			this.ucFlow.getFlow(this.nowFlow).addNode(node);
		}else if(this.nowDecision != null && qName.endsWith(NODE_NAVIGATION)) {
			this.nowNavigation = atts.getValue(ATTR_NEXTTO);
		}else if(qName.endsWith(NODE_WAIT)) {
			// <wait>
			WaitNode node = new WaitNode();
			node.setName(atts.getValue(ATTR_NAME));
			node.setParent(this.ucFlow.getFlow(this.nowFlow));
			node.setTime(Long.parseLong(atts.getValue(ATTR_TIME)));
			node.setNextTo(atts.getValue(ATTR_NEXTTO));
			this.ucFlow.getFlow(this.nowFlow).addNode(node);
		}else if(qName.endsWith(NODE_THROW)) {
			// <throw>
			ThrowNode node = new ThrowNode();
			node.setName(atts.getValue(ATTR_NAME));
			node.setParent(this.ucFlow.getFlow(this.nowFlow));
			node.setClassName(atts.getValue(ATTR_CLASS));
			this.ucFlow.getFlow(this.nowFlow).addNode(node);
		}else if(qName.endsWith(NODE_INCLUDE)) {
			// <include>
			IncludeNode node = new IncludeNode();
			node.setName(atts.getValue(ATTR_NAME));
			node.setParent(this.ucFlow.getFlow(this.nowFlow));
			node.setPath(atts.getValue(ATTR_PATH));
			String flowName = atts.getValue(ATTR_FLOWNAME);
			if(flowName != null && !flowName.isEmpty()) {
				node.setFlowName(flowName);
			}
			node.setInherit(Boolean.parseBoolean(atts.getValue(ATTR_INHERIT)));
			node.setNextTo(atts.getValue(ATTR_NEXTTO));
			this.ucFlow.getFlow(this.nowFlow).addNode(node);
		}else if(qName.endsWith(NODE_LOAD)) {
			// <load>
			LoadNode node = new LoadNode();
			node.setName(atts.getValue(ATTR_NAME));
			node.setParent(this.ucFlow.getFlow(this.nowFlow));
			node.setType(atts.getValue(ATTR_TYPE));
			node.setVar(atts.getValue(ATTR_VAR));
			node.setNextTo(atts.getValue(ATTR_NEXTTO));
			this.ucFlow.getFlow(this.nowFlow).addNode(node);
		}else if(qName.endsWith(NODE_WHILE)) {
			// <while>
			WhileNode node = new WhileNode();
			node.setName(atts.getValue(ATTR_NAME));
			node.setParent(this.ucFlow.getFlow(this.nowFlow));
			node.setFlow(atts.getValue(ATTR_FLOW));
			node.setNextTo(atts.getValue(ATTR_NEXTTO));
			this.nowWhile = node.getName();
			this.ucFlow.getFlow(this.nowFlow).addNode(node);
		}else if(qName.endsWith(NODE_SYNC)) {
			// <sync>
			SyncNode node = new SyncNode();
			node.setName(atts.getValue(ATTR_NAME));
			node.setParent(this.ucFlow.getFlow(this.nowFlow));
			node.setNextTo(atts.getValue(ATTR_NEXTTO));
			this.nowSync = node.getName();
			this.ucFlow.getFlow(this.nowFlow).addNode(node);
		}else if(this.nowSync != null && qName.endsWith(NODE_PARAMETER)) {
			SyncNode node = (SyncNode)this.ucFlow.getFlow(this.nowFlow).getNode(this.nowSync);
			node.addFlow(atts.getValue(ATTR_NAME));
		}
	}

	public void startPrefixMapping(String prefix, String uri)
			throws SAXException {

	}

}
