package com.fp.bpmlib.flow;

import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;

import javax.naming.InitialContext;
import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.NoResultException;
import javax.persistence.Query;

import org.drools.core.common.InternalKnowledgeRuntime;
import org.drools.core.impl.InternalKnowledgeBase;
import org.drools.core.impl.StatefulKnowledgeSessionImpl;
import org.jbpm.ruleflow.instance.RuleFlowProcessInstance;
import org.jbpm.services.task.audit.impl.model.AuditTaskImpl;
import org.jbpm.services.task.audit.impl.model.BAMTaskSummaryImpl;
import org.jbpm.workflow.instance.node.HumanTaskNodeInstance;
import org.jbpm.workflow.instance.node.SubProcessNodeInstance;
import org.kie.api.definition.process.Node;
import org.kie.api.runtime.KieSession;
import org.kie.api.runtime.manager.RuntimeEngine;
import org.kie.api.runtime.manager.RuntimeManager;
import org.kie.api.runtime.process.NodeInstance;
import org.kie.api.runtime.process.ProcessInstance;
import org.kie.api.task.TaskService;
import org.kie.api.task.model.Task;
import org.kie.internal.runtime.manager.context.ProcessInstanceIdContext;

import com.fp.bpmlib.ProcessUtil;
import com.fp.bpmlib.ejb.local.JbpmBeanLocal;
import com.fp.bpmlib.task.client.HumanTaskClient;
import com.fp.simple.dto.TaskInfo;
import com.fp.simple.dto.metadata.ProcessInstanceMeta;

public class Monitor {

    private KieSession kiesession;

    private ProcessInstance processInstance = null;

    private TaskService taskService;

    private RuntimeManager runtimeManager;

    private RuntimeEngine runtimeEngine;

    private EntityManagerFactory emf;

    /**
     * Crea una instancia de flujo.
     */
    public Monitor(Long processid) throws Exception {
        JbpmBeanLocal local = (JbpmBeanLocal) new InitialContext().lookup("java:global/maiaear-2.1/bpmlib/jbpmbean");
        runtimeManager = local.getRuntimeManager();
        emf = local.getEmf();
        runtimeEngine = runtimeManager.getRuntimeEngine(ProcessInstanceIdContext.get(processid));
        taskService = runtimeEngine.getTaskService();
        kiesession = runtimeEngine.getKieSession();
        processInstance = kiesession.getProcessInstance(processid);
    }

    /**
     * Cierra la session de trabajo.
     */
    public void close() {
        // no cerrar la session, esta se cierra en el commit trabaja con JTA.
    }

    public KieSession getKiesession() {
        return kiesession;
    }

    public TaskService getTaskService() {
        return taskService;
    }

    public ProcessInstance getProcessInstance() {
        return processInstance;
    }

    /**
     * Obtiene el valor de variable.
     * 
     * @param pName Nombre de la varibale a buscar en una instancia de proceso.
     * @return Valor de variable
     * @throws Exception la exception
     */
    public Object getVariable(String pName) throws Exception {
        RuleFlowProcessInstance rfpi = (RuleFlowProcessInstance) this.processInstance;
        return rfpi.getVariable(pName);
    }

    /**
     * Obtiene el valor de status.
     * 
     * @param pVariables the variables
     * @return Valor de status
     * @throws Exception la exception
     */
    @SuppressWarnings("unchecked")
    public List<ProcessInstanceMeta> getStatus() throws Exception {
        List<ProcessInstanceMeta> data = new ArrayList<ProcessInstanceMeta>();
        if (this.processInstance == null) {
            return data;
        }

        RuleFlowProcessInstance rfpi = (RuleFlowProcessInstance) this.processInstance;
        InternalKnowledgeRuntime internal = rfpi.getKnowledgeRuntime();
        if (internal == null) {
            internal = new StatefulKnowledgeSessionImpl(1, (InternalKnowledgeBase) this.kiesession.getKieBase());
            rfpi.setKnowledgeRuntime(internal);
        }
        Collection<NodeInstance> nl = rfpi.getNodeInstances();

        for (NodeInstance ni : nl) {
            ProcessInstanceMeta pim = new ProcessInstanceMeta(this.processInstance.getId());
            data.add(pim);
            Map<String, Object> m = ni.getNode().getMetaData();
            pim.setH(Integer.valueOf("" + m.get("height")));
            pim.setW(Integer.valueOf("" + m.get("width")));
            pim.setX(Integer.valueOf("" + m.get("x")));
            pim.setY(Integer.valueOf("" + m.get("y")));
            pim.setName(processInstance.getProcessName());
            pim.setVersion(processInstance.getProcess().getVersion());
            pim.setActivity(ni.getNodeName());
            pim.setPmetaid(ni.getNodeId());
            Map<String, Object> var = pim.getVariables();
            Object tasks = this.getVariable(ProcessUtil.TASKS_METADATA);
            boolean req = true;
            if (tasks != null) {
                var.put(ProcessUtil.TASKS_METADATA, this.manageTasksMetaData((Map<String, Object>) tasks));
                req = false;
            }
            if (req) {
                tasks = this.getVariable(ProcessUtil.ACTUAL_TASK);
                if (tasks != null) {
                    var.put(ProcessUtil.ACTUAL_TASK, tasks);
                }
            }
            this.manageNode(pim, ni);
        }
        return data;
    }

    /**
     * Manage node.
     * 
     * @param pim the pim
     * @param ni the ni
     * @param pVariables the variables
     * @throws Exception la exception
     */
    private void manageNode(ProcessInstanceMeta pim, NodeInstance ni) throws Exception {
        if (ni instanceof HumanTaskNodeInstance) {
            HumanTaskNodeInstance sub = (HumanTaskNodeInstance) ni;
            pim.setWorkItem(sub.getWorkItemId());
            Task t = this.taskService.getTaskByWorkItemId(pim.getWorkItem());
            // getDocumentContentId es el contenido de entrada de la tarea.
            Map<String, Object> input = HumanTaskClient.getContenido(taskService.getContentById(t.getTaskData().getDocumentContentId()));
            TaskInfo info = (TaskInfo) input.get("taskMetaData");
            info.getAditionalData().put("workItem", sub.getWorkItemId());
            pim.addAddtionalInfo("taskMetaData", info);
            return;
        }
        if (ni instanceof SubProcessNodeInstance) {
            SubProcessNodeInstance sub = (SubProcessNodeInstance) ni;
            Monitor subflujo = new Monitor(sub.getProcessInstanceId());
            for (ProcessInstanceMeta pi : subflujo.getStatus()) {
                pim.addSubProcess(pi);
            }
            return;
        }
    }

    /**
     * Manage tasks meta data.
     * 
     * @param pTasksMetaData the tasks meta data
     * @return map
     * @throws Exception la exception
     */
    private Map<String, Object> manageTasksMetaData(Map<String, Object> pTasksMetaData) throws Exception {
        Map<String, Object> data = new HashMap<String, Object>();
        RuleFlowProcessInstance instance = (RuleFlowProcessInstance) processInstance;
        for (Entry<String, Object> element : pTasksMetaData.entrySet()) {
            Map<String, Object> record = new HashMap<String, Object>();
            Node[] ns = instance.getRuleFlowProcess().getNodes();
            for (Node n : ns) {
                if (n.getName().compareTo(element.getKey()) == 0) {
                    Map<String, Object> m = n.getMetaData();
                    record.put("h", Integer.valueOf("" + m.get("height")));
                    record.put("w", Integer.valueOf("" + m.get("width")));
                    record.put("x", Integer.valueOf("" + m.get("x")));
                    record.put("y", Integer.valueOf("" + m.get("y")));
                    record.put("name", n.getName());
                    break;
                }
            }
            // record.put(ProcessUtil.TASK_METADATA, element.getValue());
            data.put(element.getKey(), record);
        }
        return data;
    }

    /**
     * JPQL TAREA_SUMMARY
     */
    private static final String TAREA_SUMMARY = "select t from BAMTaskSummaryImpl t where t.taskId = :idtarea";

    /**
     * Obtiene el valor de unfinished tasks por Grupo.
     * 
     * @param pGroupId the user id
     * @param pCri the cri
     * @return Valor de unfinished tasks
     * @throws Exception la exception
     */
    public BAMTaskSummaryImpl getBAMTaskSummaryImpl(long idtarea) throws Exception {
        EntityManager em = null;
        BAMTaskSummaryImpl dato = null;
        try {
            em = emf.createEntityManager();
            Query qry = em.createQuery(Monitor.TAREA_SUMMARY);
            qry.setParameter("idtarea", idtarea);
            dato = (BAMTaskSummaryImpl) qry.getSingleResult();
        } catch (NoResultException e) {
            // retorna el objeto en null.
        } finally {
            if (em.isOpen()) {
                em.close();
            }
        }
        return dato;
    }

    /**
     * JPQL TAREA_SUMMARY
     */
    private static final String TAREA_AUDIT_IMP = "select t from AuditTaskImpl t where t.taskId = :idtarea";

    public AuditTaskImpl getAuditTaskImpl(long idtarea) throws Exception {
        EntityManager em = null;
        AuditTaskImpl dato = null;
        try {
            em = emf.createEntityManager();
            Query qry = em.createQuery(Monitor.TAREA_AUDIT_IMP);
            qry.setParameter("idtarea", idtarea);
            dato = (AuditTaskImpl) qry.getSingleResult();
        } catch (NoResultException e) {
            // retorna el objeto en null.
        } finally {
            if (em.isOpen()) {
                em.close();
            }
        }
        return dato;
    }
}
