/*
 * Copyright 2010 Red Hat, Inc. and/or its affiliates.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *       http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.jbpm.bpmn2.xml;

import java.util.List;

import org.drools.core.xml.ExtensibleXmlParser;
import org.jbpm.bpmn2.core.Association;
import org.jbpm.bpmn2.core.Definitions;
import org.jbpm.bpmn2.core.IntermediateLink;
import org.jbpm.bpmn2.core.SequenceFlow;
import org.jbpm.compiler.xml.ProcessBuildData;
import org.jbpm.process.core.context.variable.VariableScope;
import org.jbpm.workflow.core.Node;
import org.jbpm.workflow.core.NodeContainer;
import org.jbpm.workflow.core.impl.NodeImpl;
import org.jbpm.workflow.core.node.CompositeContextNode;
import org.jbpm.workflow.core.node.EventSubProcessNode;
import org.jbpm.workflow.core.node.ForEachNode;
import org.jbpm.workflow.core.node.StartNode;
import org.kie.api.definition.process.Connection;
import org.w3c.dom.Element;
import org.xml.sax.Attributes;
import org.xml.sax.SAXException;

public class SubProcessHandler extends AbstractNodeHandler {

    protected Node createNode(Attributes attrs) {
        CompositeContextNode subProcessNode = new CompositeContextNode();
        String eventSubprocessAttribute = attrs.getValue("triggeredByEvent");
        if (eventSubprocessAttribute != null && Boolean.parseBoolean(eventSubprocessAttribute)) {
            subProcessNode = new EventSubProcessNode();
        }
        VariableScope variableScope = new VariableScope();
        subProcessNode.addContext(variableScope);
        subProcessNode.setDefaultContext(variableScope);

        String compensation = attrs.getValue("isForCompensation");
        if (compensation != null) {
            boolean isForCompensation = Boolean.parseBoolean(compensation);
            if (isForCompensation) {
                subProcessNode.setMetaData("isForCompensation", isForCompensation);
            }
        }
        subProcessNode.setAutoComplete(true);
        return subProcessNode;
    }

    @SuppressWarnings("unchecked")
    public Class generateNodeFor() {
        return CompositeContextNode.class;
    }

    public Object end(final String uri, final String localName,
            final ExtensibleXmlParser parser) throws SAXException {
        final Element element = parser.endElementBuilder();
        Node node = (Node) parser.getCurrent();

        // determine type of event definition, so the correct type of node can be generated
        boolean found = false;
        org.w3c.dom.Node xmlNode = element.getFirstChild();
        while (xmlNode != null) {
            String nodeName = xmlNode.getNodeName();
            if ("multiInstanceLoopCharacteristics".equals(nodeName)) {
                Boolean isAsync = Boolean.parseBoolean((String) node.getMetaData().get("customAsync"));
                // create new timerNode
                ForEachNode forEachNode = new ForEachNode();
                forEachNode.setId(node.getId());
                forEachNode.setName(node.getName());

                forEachNode.setAutoComplete(((CompositeContextNode) node).isAutoComplete());

                for (org.kie.api.definition.process.Node subNode : ((CompositeContextNode) node).getNodes()) {

                    forEachNode.addNode(subNode);
                }
                forEachNode.setMetaData("UniqueId", ((CompositeContextNode) node).getMetaData("UniqueId"));
                forEachNode.setMetaData(ProcessHandler.CONNECTIONS, ((CompositeContextNode) node).getMetaData(ProcessHandler.CONNECTIONS));
                VariableScope v = (VariableScope) ((CompositeContextNode) node).getDefaultContext(VariableScope.VARIABLE_SCOPE);
                ((VariableScope) ((CompositeContextNode) forEachNode.internalGetNode(2)).getDefaultContext(VariableScope.VARIABLE_SCOPE)).setVariables(v.getVariables());
                node = forEachNode;
                handleForEachNode(node, element, uri, localName, parser, isAsync);
                found = true;
                break;
            }
            xmlNode = xmlNode.getNextSibling();
        }
        if (!found) {
            handleCompositeContextNode(node, element, uri, localName, parser);
        }

        NodeContainer nodeContainer = (NodeContainer) parser.getParent();
        nodeContainer.addNode(node);
        ((ProcessBuildData) parser.getData()).addNode(node);

        return node;
    }

    @SuppressWarnings("unchecked")
    protected void handleCompositeContextNode(final Node node, final Element element, final String uri,
            final String localName, final ExtensibleXmlParser parser) throws SAXException {
        super.handleNode(node, element, uri, localName, parser);
        CompositeContextNode compositeNode = (CompositeContextNode) node;
        List<SequenceFlow> connections = (List<SequenceFlow>) compositeNode.getMetaData(ProcessHandler.CONNECTIONS);

        handleScript(compositeNode, element, "onEntry");
        handleScript(compositeNode, element, "onExit");

        List<IntermediateLink> throwLinks = (List<IntermediateLink>) compositeNode.getMetaData(ProcessHandler.LINKS);
        ProcessHandler.linkIntermediateLinks(compositeNode, throwLinks);

        ProcessHandler.linkConnections(compositeNode, connections);
        ProcessHandler.linkBoundaryEvents(compositeNode);

        // This must be done *after* linkConnections(process, connections)
        //  because it adds hidden connections for compensations
        List<Association> associations = (List<Association>) compositeNode.getMetaData(ProcessHandler.ASSOCIATIONS);
        ProcessHandler.linkAssociations((Definitions) compositeNode.getMetaData("Definitions"), compositeNode, associations);

        // TODO: do we fully support interruping ESP's? 
        /**
         * for( org.kie.api.definition.process.Node subNode : compositeNode.getNodes() ) {
         * if( subNode instanceof StartNode ) {
         * if( ! ((StartNode) subNode).isInterrupting() ) {
         * throw new IllegalArgumentException("Non-interrupting event subprocesses are not yet fully supported." );
         * }
         * }
         * }
         */

    }

    @SuppressWarnings("unchecked")
    protected void handleForEachNode(final Node node, final Element element, final String uri,
            final String localName, final ExtensibleXmlParser parser, boolean isAsync) throws SAXException {
        super.handleNode(node, element, uri, localName, parser);
        ForEachNode forEachNode = (ForEachNode) node;
        org.w3c.dom.Node xmlNode = element.getFirstChild();
        while (xmlNode != null) {
            String nodeName = xmlNode.getNodeName();
            if ("ioSpecification".equals(nodeName)) {
                readIoSpecification(xmlNode, dataInputs, dataOutputs, dataInputTypes, dataOutputTypes);
            } else if ("dataInputAssociation".equals(nodeName)) {
                readDataInputAssociation(xmlNode, inputAssociation);
            } else if ("dataOutputAssociation".equals(nodeName)) {
                readDataOutputAssociation(xmlNode, outputAssociation);
            } else if ("multiInstanceLoopCharacteristics".equals(nodeName)) {
                readMultiInstanceLoopCharacteristics(xmlNode, forEachNode, parser);
            }
            xmlNode = xmlNode.getNextSibling();
        }
        handleScript(forEachNode, element, "onEntry");
        handleScript(forEachNode, element, "onExit");

        List<SequenceFlow> connections = (List<SequenceFlow>) forEachNode.getMetaData(ProcessHandler.CONNECTIONS);
        ProcessHandler.linkConnections(forEachNode, connections);
        ProcessHandler.linkBoundaryEvents(forEachNode);

        // This must be done *after* linkConnections(process, connections)
        //  because it adds hidden connections for compensations
        List<Association> associations = (List<Association>) forEachNode.getMetaData(ProcessHandler.ASSOCIATIONS);
        ProcessHandler.linkAssociations((Definitions) forEachNode.getMetaData("Definitions"), forEachNode, associations);
        applyAsync(node, isAsync);
    }

    protected void applyAsync(Node node, boolean isAsync) {
        for (org.kie.api.definition.process.Node subNode : ((CompositeContextNode) node).getNodes()) {
            if (isAsync) {
                List<Connection> incoming = subNode.getIncomingConnections(NodeImpl.CONNECTION_DEFAULT_TYPE);
                if (incoming != null) {
                    for (Connection con : incoming) {
                        if (con.getFrom() instanceof StartNode) {
                            ((Node) subNode).setMetaData("customAsync", Boolean.toString(isAsync));
                            return;
                        }
                    }
                }

            }
        }
    }

    public void writeNode(Node node, StringBuilder xmlDump, int metaDataType) {
        throw new IllegalArgumentException("Writing out should be handled by specific handlers");
    }

}
