/*
 * Copyright 2015 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.
 * 
 *      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.persistence;

import org.drools.core.command.impl.CommandBasedStatefulKnowledgeSession;
import org.drools.core.command.impl.ExecutableCommand;
import org.drools.core.command.impl.RegistryContext;
import org.jbpm.bpmn2.JbpmBpmn2TestCase;
import org.jbpm.compiler.xml.XmlRuleFlowProcessDumper;
import org.jbpm.persistence.session.objects.TestWorkItemHandler;
import org.jbpm.process.instance.impl.ProcessInstanceImpl;
import org.jbpm.ruleflow.core.RuleFlowProcess;
import org.jbpm.ruleflow.core.RuleFlowProcessFactory;
import org.jbpm.workflow.core.impl.ConnectionImpl;
import org.jbpm.workflow.core.impl.NodeImpl;
import org.jbpm.workflow.core.node.HumanTaskNode;
import org.junit.BeforeClass;
import org.junit.Test;
import org.kie.api.KieBase;
import org.kie.api.definition.process.Connection;
import org.kie.api.event.process.ProcessCompletedEvent;
import org.kie.api.event.process.ProcessEventListener;
import org.kie.api.event.process.ProcessNodeLeftEvent;
import org.kie.api.event.process.ProcessNodeTriggeredEvent;
import org.kie.api.event.process.ProcessStartedEvent;
import org.kie.api.event.process.ProcessVariableChangedEvent;
import org.kie.api.io.Resource;
import org.kie.api.runtime.KieSession;
import org.kie.api.runtime.Context;
import org.kie.internal.io.ResourceFactory;
import org.kie.internal.runtime.StatefulKnowledgeSession;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * This is a sample file to launch a process.
 */
public class DynamicProcessTest extends JbpmBpmn2TestCase {

    private static final Logger logger = LoggerFactory.getLogger(DynamicProcessTest.class);
    
    @BeforeClass
    public static void setup() throws Exception {
        if (PERSISTENCE) {
            setUpDataSource();
        }
    }

    @Test
	public void testDynamicProcess() throws Exception {		
		RuleFlowProcessFactory factory = RuleFlowProcessFactory.createProcess("org.jbpm.HelloWorld");
		factory
			// Header
			.name("HelloWorldProcess")
			.version("1.0")
			.packageName("org.jbpm")
			// Nodes
			.startNode(1).name("Start").done()
			.humanTaskNode(2).name("Task1").actorId("krisv").taskName("MyTask").done()
			.endNode(3).name("End").done()
			// Connections
			.connection(1, 2)
			.connection(2, 3);
		final RuleFlowProcess process = factory.validate().getProcess();
		Resource resource = ResourceFactory
                .newByteArrayResource(XmlRuleFlowProcessDumper.INSTANCE.dump(
                        process).getBytes());
        resource.setSourcePath("/tmp/dynamicProcess.bpmn2"); // source path or target path must be set to be added into kbase
        KieBase kbase = createKnowledgeBaseFromResources(resource);
		StatefulKnowledgeSession ksession = createKnowledgeSession(kbase);
		TestWorkItemHandler testHandler = new TestWorkItemHandler();
		ksession.getWorkItemManager().registerWorkItemHandler("Human Task", testHandler);
		ksession.addEventListener(new ProcessEventListener() {
			public void beforeVariableChanged(ProcessVariableChangedEvent arg0) {
			}
			public void beforeProcessStarted(ProcessStartedEvent arg0) {
				logger.info("{}", arg0);
			}
			public void beforeProcessCompleted(ProcessCompletedEvent arg0) {
			    logger.info("{}", arg0);
			}
			public void beforeNodeTriggered(ProcessNodeTriggeredEvent arg0) {
			    logger.info("{}", arg0);
			}
			public void beforeNodeLeft(ProcessNodeLeftEvent arg0) {
			    logger.info("{}", arg0);
			}
			public void afterVariableChanged(ProcessVariableChangedEvent arg0) {
			}
			public void afterProcessStarted(ProcessStartedEvent arg0) {
			}
			public void afterProcessCompleted(ProcessCompletedEvent arg0) {
			}
			public void afterNodeTriggered(ProcessNodeTriggeredEvent arg0) {
			}
			public void afterNodeLeft(ProcessNodeLeftEvent arg0) {
			}
		});

		final ProcessInstanceImpl processInstance = (ProcessInstanceImpl)
			ksession.startProcess("org.jbpm.HelloWorld");
		
		HumanTaskNode node = new HumanTaskNode();
		node.setName("Task2");
		node.setId(4);
		insertNodeInBetween(process, 2, 3, node);
		
		((CommandBasedStatefulKnowledgeSession) ksession).getRunner().execute(new ExecutableCommand<Void>() {
			public Void execute(Context context) {
				StatefulKnowledgeSession ksession = (StatefulKnowledgeSession) ((RegistryContext) context).lookup( KieSession.class );
				((ProcessInstanceImpl) ksession.getProcessInstance(processInstance.getId())).updateProcess(process);
				return null;
			}
		});

        assertProcessInstanceActive(processInstance);
		ksession.getWorkItemManager().completeWorkItem(testHandler.getWorkItem().getId(), null);
		assertProcessInstanceActive(processInstance);
		ksession.getWorkItemManager().completeWorkItem(testHandler.getWorkItem().getId(), null);
		assertProcessInstanceFinished(processInstance, ksession);
		
		ksession.dispose();
	}

	private static void insertNodeInBetween(RuleFlowProcess process, long startNodeId, long endNodeId, NodeImpl node) {
		if (process == null) {
			throw new IllegalArgumentException("Process may not be null");
		}
		NodeImpl selectedNode = (NodeImpl) process.getNode(startNodeId);
		if (selectedNode == null) {
			throw new IllegalArgumentException("Node " + startNodeId + " not found in process " + process.getId());
		}
		for (Connection connection: selectedNode.getDefaultOutgoingConnections()) {
			if (connection.getTo().getId() == endNodeId) {
				process.addNode(node);
				NodeImpl endNode = (NodeImpl) connection.getTo();
				((ConnectionImpl) connection).terminate();
				new ConnectionImpl(selectedNode, NodeImpl.CONNECTION_DEFAULT_TYPE, node, NodeImpl.CONNECTION_DEFAULT_TYPE);
				new ConnectionImpl(node, NodeImpl.CONNECTION_DEFAULT_TYPE, endNode, NodeImpl.CONNECTION_DEFAULT_TYPE);
				return;
			}
		}
		throw new IllegalArgumentException("Connection to node " + endNodeId + " not found in process " + process.getId());
	}
	
}
