package crown.server.util;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import javax.naming.InitialContext;
import javax.naming.NamingException;
import javax.persistence.Persistence;
import javax.transaction.TransactionManager;

import org.apache.log4j.Logger;
import org.drools.KnowledgeBase;
import org.drools.KnowledgeBaseFactory;
import org.drools.SystemEventListenerFactory;
import org.drools.agent.KnowledgeAgent;
import org.drools.agent.KnowledgeAgentConfiguration;
import org.drools.agent.KnowledgeAgentFactory;
import org.drools.definition.process.Connection;
import org.drools.definition.process.Node;
import org.drools.definition.process.WorkflowProcess;
import org.drools.event.knowledgeagent.AfterChangeSetAppliedEvent;
import org.drools.event.knowledgeagent.AfterChangeSetProcessedEvent;
import org.drools.event.knowledgeagent.AfterResourceProcessedEvent;
import org.drools.event.knowledgeagent.BeforeChangeSetAppliedEvent;
import org.drools.event.knowledgeagent.BeforeChangeSetProcessedEvent;
import org.drools.event.knowledgeagent.BeforeResourceProcessedEvent;
import org.drools.event.knowledgeagent.KnowledgeAgentEventListener;
import org.drools.event.knowledgeagent.KnowledgeBaseUpdatedEvent;
import org.drools.event.knowledgeagent.ResourceCompilationFailedEvent;
import org.drools.io.ResourceChangeScannerConfiguration;
import org.drools.io.ResourceFactory;
import org.drools.persistence.jpa.JPAKnowledgeService;
import org.drools.runtime.Environment;
import org.drools.runtime.EnvironmentName;
import org.drools.runtime.StatefulKnowledgeSession;
import org.drools.runtime.StatelessKnowledgeSession;
import org.drools.runtime.process.NodeInstance;
import org.drools.runtime.process.ProcessInstance;
import org.drools.runtime.process.WorkflowProcessInstance;
import org.jbpm.process.audit.JPAProcessInstanceDbLog;
import org.jbpm.process.audit.JPAWorkingMemoryDbLogger;
import org.jbpm.process.audit.NodeInstanceLog;
import org.jbpm.process.workitem.wsht.CommandBasedWSHumanTaskHandler;
import org.jbpm.workflow.instance.node.WorkItemNodeInstance;


import crown.lead.ws.SystemEventListener;
import crown.lead.ws.bo.ProcessInstanceBO;

public class JbpmUtil {
	private static Logger logger = Logger.getLogger(JbpmUtil.class);
	
	//public static String ACTION = "action";
	
	private KnowledgeAgent kagent;
	
	private String[] changeSets;
	
	private final String humanTaskIp = "127.0.0.1";
	
	private final int humanTaskPort = 9123;
	
	private final String persistenceStr = "CrownJBPMService.jpa";
	
	private StatefulKnowledgeSession ksession;
	
	private CommandBasedWSHumanTaskHandler humanTaskHandler;
	
	private JPAProcessInstanceDbLog processLog;
	
	private JPAWorkingMemoryDbLogger jPAWorkingMemoryDbLogger;
	
//	private Environment env;
	
	public JbpmUtil(String[] changeSets){
		SystemEventListenerFactory.setSystemEventListener(new SystemEventListener());
		setChangeSets(changeSets);
		initKnowledgeAgent();
		//initKsession();
//		initHumanTask(null);
	}
	
	public void initKsession(){
		if(ksession==null){
			synchronized(this) {
				newStatefulKnowledgeSession();
				initHumanTask(ksession);
			}
		}
		else{
			synchronized(ksession) {
				newStatefulKnowledgeSession();
				initHumanTask(ksession);
			}
		}
	}
	
	public void initKnowledgeAgent(){
		ResourceChangeScannerConfiguration sconf = ResourceFactory.getResourceChangeScannerService().newResourceChangeScannerConfiguration();
		sconf.setProperty("drools.resource.scanner.interval", "2");
		ResourceFactory.getResourceChangeScannerService().configure(sconf);
		
        KnowledgeAgentConfiguration aconf = KnowledgeAgentFactory.newKnowledgeAgentConfiguration();
        aconf.setProperty("drools.agent.scanDirectories", "true");
        aconf.setProperty("drools.agent.scanResources", "true");
        aconf.setProperty("drools.agent.newInstance", "" + true);
        
		ResourceFactory.getResourceChangeNotifierService().start();
		ResourceFactory.getResourceChangeScannerService().start();
        
		kagent = KnowledgeAgentFactory.newKnowledgeAgent("CrownAgent",KnowledgeBaseFactory.newKnowledgeBase(), aconf);
		
        kagent.addEventListener(new KnowledgeAgentEventListener() {

            public void beforeChangeSetApplied(BeforeChangeSetAppliedEvent event) {
            	logger.info("==================== BeforeChangeSetAppliedEvent");
            }

            public void afterChangeSetApplied(AfterChangeSetAppliedEvent event) {
            	logger.info("==================== AfterChangeSetAppliedEvent");
            }

            public void beforeChangeSetProcessed(BeforeChangeSetProcessedEvent event) {
            	logger.info("==================== BeforeChangeSetProcessedEvent");
            }

            public void afterChangeSetProcessed(AfterChangeSetProcessedEvent event) {
            	logger.info("==================== AfterChangeSetProcessedEvent");
            }

            public void beforeResourceProcessed(BeforeResourceProcessedEvent event) {
            	logger.info("==================== BeforeResourceProcessedEvent");
            }

            public void afterResourceProcessed(AfterResourceProcessedEvent event) {
            	logger.info("==================== AfterResourceProcessedEvent");
            }

            public void knowledgeBaseUpdated(KnowledgeBaseUpdatedEvent event) {
            	initKsession();
                logger.info("==================== KnowledgeBaseUpdatedEvent");
            }

            public void resourceCompilationFailed(ResourceCompilationFailedEvent event) {
            	logger.info("==================== ResourceCompilationFailedEvent");
            }
        });
		
		kagent.applyChangeSet(ResourceFactory.newUrlResource(changeSets[0]));
	}
	
	public List<String> getNextHumanNodes(ProcessInstance instance){
		return getNextHumanNodes(getCurrNode(instance));
	}
	
	public List<String> getNextHumanNodes(String processId,String nodeName){
		WorkflowProcess process = (WorkflowProcess)kagent.getKnowledgeBase().getProcess(processId);
		if(process==null){
			return null;
		}
		Node[] nodes = process.getNodes();
		for(Node node : nodes){
			if(nodeName.equals(node.getName())){
				return getNextHumanNodes(node);
			}
		}
		return null;
	}
	
	public  List<String> getNextHumanNodes(Node currNode) {
		List<String> arlResult = new ArrayList<String>();
		Map<String, List<Connection>> mapOutgoingConn = currNode.getOutgoingConnections();
		int limit = 0;
		for(Map.Entry<String, List<Connection>> entry: mapOutgoingConn.entrySet()){
			for (Connection conn : entry.getValue()) {
				if(limit == 100){//tire to look for
					break;
				}
				limit++;
				Node nextNode = conn.getTo();

				if (nextNode instanceof org.jbpm.workflow.core.node.HumanTaskNode) {
					arlResult.add(nextNode.getName());
					continue;
				} else if (nextNode instanceof org.jbpm.workflow.core.node.EndNode) {
					arlResult.add(nextNode.getName());
					continue;
				} else {
					List<String> tempArray = getNextHumanNodes(nextNode);//
					arlResult.addAll(tempArray);
				}
			}
		}
		logger.debug("NextHumanNodes:"+arlResult.toString());
		return arlResult;
	}
	
	public String getCurrNodeName(ProcessInstance instance) {
		return getCurrNode(instance).getName();
	}
	
	public Node getCurrNode(ProcessInstance instance){
		Collection<NodeInstance> nodeInstances = ((WorkflowProcessInstance)instance).getNodeInstances();
		if(nodeInstances.size()==0){
			throw new RuntimeException("ProcessInstance has not NodeInstances(empty).");
		}
		Node node = ((WorkflowProcess)kagent.getKnowledgeBase().getProcess(instance.getProcessId())).getNode(nodeInstances.iterator().next().getNodeId());
		if(node==null){
			throw new RuntimeException("ProcessInstance has not Node(empty).");
		}
		return node;
	}
	
	/**
	 * @param instanceId
	 * @return
	 */
	public  ProcessInstanceBO getOptionNodes(long instanceId) {
		List<NodeInstanceLog> nodeInstanceLogs;
		
		ProcessInstance instance = ksession.getProcessInstance(instanceId);
		
		if(instance!=null){
			WorkflowProcessInstance wfInstance = (WorkflowProcessInstance) instance;
			long nodeId = wfInstance.getNodeInstances().iterator().next().getNodeId();
//			nodeInstanceLogs = processLog.findNodeInstances(instanceId,String.valueOf(nodeId));
			nodeInstanceLogs = processLog.findNodeInstances(instanceId,String.valueOf(nodeId));
		}
		else{
			nodeInstanceLogs = processLog.findNodeInstances(instanceId);
		}
		
		ProcessInstanceBO piBO = new ProcessInstanceBO();
		
//		nodeInstanceLogs = processLog.findNodeInstances(instanceId);
		
		Collections.sort(nodeInstanceLogs, new Comparator<NodeInstanceLog>() {
			public int compare(NodeInstanceLog arg0, NodeInstanceLog arg1) {
				return new Long(arg0.getId()).compareTo(new Long(arg1.getId()));
			}
		});
		
		NodeInstanceLog nodeInstanceLog = nodeInstanceLogs.get(nodeInstanceLogs.size()-1);
		
		
		piBO.setProcessDefinitionId(nodeInstanceLog.getProcessId());
		piBO.setProcessInstanceId(nodeInstanceLog.getProcessInstanceId());
		piBO.setCurrentNodeName(nodeInstanceLog.getNodeName());

		ProcessInstanceBO bo2 = getLocalStateInfo(piBO.getCurrentNodeName());

		if(instance!=null){
			piBO.setArlOptionNode(getNextHumanNodes(instance.getProcessId(),piBO.getCurrentNodeName()));
		}
		else{
			piBO.setArlOptionNode(null);
		}
		piBO.setViewMode(bo2.getViewMode());
        piBO.setArlShowField(bo2.getArlShowField());
        piBO.setArlShowButton(bo2.getArlShowButton());
        piBO.setKSessionId(ksession.getId());
        piBO.setShowCurrentNodeName(bo2.isShowCurrentNodeName());
        piBO.setShowNextNodeName(bo2.isShowNextNodeName());
        
        return piBO;
	}
	
	public  ProcessInstanceBO getOptionNodes(String processId,String nodeName) {
		ProcessInstanceBO piBO = new ProcessInstanceBO();
		ProcessInstanceBO bo2 = getLocalStateInfo(nodeName);
		
		piBO.setProcessDefinitionId(processId);
		piBO.setProcessInstanceId(0);
		piBO.setCurrentNodeName(nodeName);
		piBO.setArlOptionNode(getNextHumanNodes(processId,nodeName));
		piBO.setViewMode(bo2.getViewMode());
        piBO.setArlShowField(bo2.getArlShowField());
        piBO.setArlShowButton(bo2.getArlShowButton());
        piBO.setShowCurrentNodeName(bo2.isShowCurrentNodeName());
        piBO.setShowNextNodeName(bo2.isShowNextNodeName());
        piBO.setKSessionId(ksession.getId());
        return piBO;
	}
	
	public synchronized ProcessInstanceBO completeUserTask(long processInstanceId,Map<String, Object> parameters){
		for(Map.Entry<String, Object> entry: parameters.entrySet()){
			logger.info("parameters: "+ entry.getKey()+" value: "+entry.getValue());
		}
		ProcessInstanceBO piBO = new ProcessInstanceBO();
		try {
			ProcessInstance instance = ksession.getProcessInstance(processInstanceId);
			if (instance != null) {
				WorkflowProcessInstance wfInstance = ((WorkflowProcessInstance)instance);
	            Iterator<NodeInstance> iterator = wfInstance.getNodeInstances().iterator();
				NodeInstance nodeInstance = iterator.next();
            	WorkItemNodeInstance workItemNodeInstance = null;
	            if (nodeInstance instanceof WorkItemNodeInstance) {
	                workItemNodeInstance = (WorkItemNodeInstance)nodeInstance;
	            }
	            else {
	                logger.warn("Warning while forcing complete work item! got warning: the object is not WorkItemNodeInstance instance");
	            }            
	            if (workItemNodeInstance != null) {
	                long workItemId = workItemNodeInstance.getWorkItemId();
	                ksession.getWorkItemManager().completeWorkItem(workItemId, parameters);
	                piBO = getOptionNodes(instance.getId());
	            }
			}else{
				logger.info("completeUserTask | Process " + processInstanceId + " already completed!");
			}
        }catch(Exception ex) {
            piBO.setErrorCode(Integer.valueOf(106));
			piBO.setErrorContent("Process ("+ processInstanceId +") can not go to ");
            throw new RuntimeException(ex.getMessage());
        }
		finally{
			try {
				
			} catch (Exception e) {
				logger.error("", e);
			}
		}
		return piBO;
	}
	
	private StatefulKnowledgeSession newStatefulKnowledgeSession(KnowledgeBase knowledgeBase){
		if(ksession!=null){
			ksession.dispose();
		}
		if(jPAWorkingMemoryDbLogger!=null){
			jPAWorkingMemoryDbLogger.dispose();
		}
		Environment environment = newEnvironment();
		ksession = JPAKnowledgeService
				.newStatefulKnowledgeSession(knowledgeBase, null, environment);
		jPAWorkingMemoryDbLogger = new JPAWorkingMemoryDbLogger(ksession);
		processLog = new JPAProcessInstanceDbLog(environment);
		return ksession;
	}
	
	public CommandBasedWSHumanTaskHandler initHumanTask(StatefulKnowledgeSession ksession){
		if(humanTaskHandler==null){
			humanTaskHandler = new CommandBasedWSHumanTaskHandler(ksession);
			humanTaskHandler.setConnection(humanTaskIp, humanTaskPort);
			humanTaskHandler.connect();
		}
		ksession.getWorkItemManager().registerWorkItemHandler("Human Task", humanTaskHandler);
		return humanTaskHandler;
	}
	
	public StatefulKnowledgeSession loadStatefulKnowledgeSession(int seesionid){
		return JPAKnowledgeService.loadStatefulKnowledgeSession(seesionid, kagent.getKnowledgeBase(), null, newEnvironment());
	}
	
	
	public Environment newEnvironment(){
		Environment env = KnowledgeBaseFactory.newEnvironment();
		env.set(EnvironmentName.ENTITY_MANAGER_FACTORY, Persistence
				.createEntityManagerFactory(persistenceStr));
		env.set(EnvironmentName.TRANSACTION_MANAGER, getTransactionManager());
		return env;
	}
	
	public  StatefulKnowledgeSession newStatefulKnowledgeSession(){
		return newStatefulKnowledgeSession(kagent.getKnowledgeBase());
	}
	
	public synchronized ProcessInstanceBO startProcess(String processId, Map<String, Object> parameters) {
		if(ksession==null){
			throw new RuntimeException("the StatefulKnowledgeSession not init.");
		}
		ProcessInstanceBO piBO = new ProcessInstanceBO();
//		{
//			String arlShowButton = "arlShowButton";
//			String arlShowField = "arlShowField";
//			String viewMode = "viewMode";
//			String showConvertNode = "showConvertNode";
//			String USER_ID = "userId";
//			String userKrisv = "krisv";
//			for(Map.Entry<String, Object> parameter : parameters.entrySet()){
//				
//			}
//        	parameters = new HashMap<String, Object>(2);
//        	parameters.put(USER_ID, userKrisv);
//        	parameters.put(ACTION, "");
//        	parameters.put(arlShowButton, "");
//        	parameters.put(arlShowField, "");
//        	parameters.put(viewMode, "Edit");
//        	parameters.put(showConvertNode, "");
//		}
		try{
			
			
			ProcessInstance instance = ksession.startProcess(processId, parameters);
			logger.debug("startProcess | after getCurrNode");
			piBO = getOptionNodes(instance.getId());
			return piBO;
		}
		finally{
			try {

			} catch (Exception e) {
				logger.error("", e);
			}
		}
	}
	
	
	
	
	
	
	public  ProcessInstanceBO getLocalStateInfo(String toState) {
		ProcessInstanceBO piBO = new ProcessInstanceBO();
		piBO.setCurrentNodeName(toState);
		piBO.setArlShowField(new ArrayList<String>());
		StatelessKnowledgeSession privateKsession = kagent.getKnowledgeBase().newStatelessKnowledgeSession();
		privateKsession.execute(piBO);
		privateKsession = null;
		return piBO;
	}
	

	public TransactionManager getTransactionManager(){
		try {
			return (TransactionManager) new InitialContext().lookup("java:/TransactionManager");
		} catch (NamingException e) {
			throw new RuntimeException(e);
		}
	}

	public String[] getChangeSets() {
		return changeSets;
	}

	public void setChangeSets(String[] changeSets) {
		this.changeSets = changeSets;
	}
	

	
	
}
