package org.jbpm.test.timer;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;

import java.io.File;
import java.io.FilenameFilter;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Properties;

import org.jbpm.process.core.timer.GlobalSchedulerService;
import org.jbpm.runtime.manager.impl.DefaultRuntimeEnvironment;
import org.jbpm.runtime.manager.impl.SimpleRuntimeEnvironment;
import org.jbpm.services.task.identity.JBossUserGroupCallbackImpl;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.kie.api.event.process.DefaultProcessEventListener;
import org.kie.api.event.process.ProcessEventListener;
import org.kie.api.event.process.ProcessNodeLeftEvent;
import org.kie.api.event.process.ProcessStartedEvent;
import org.kie.api.event.rule.AgendaEventListener;
import org.kie.api.event.rule.BeforeMatchFiredEvent;
import org.kie.api.event.rule.DefaultAgendaEventListener;
import org.kie.api.io.ResourceType;
import org.kie.api.runtime.KieSession;
import org.kie.api.runtime.process.ProcessInstance;
import org.kie.internal.io.ResourceFactory;
import org.kie.internal.runtime.manager.Runtime;
import org.kie.internal.runtime.manager.RuntimeEnvironment;
import org.kie.internal.runtime.manager.RuntimeManager;
import org.kie.internal.runtime.manager.context.ProcessInstanceIdContext;
import org.kie.internal.task.api.UserGroupCallback;
import org.kie.internal.task.api.model.Status;
import org.kie.internal.task.api.model.TaskSummary;


public abstract class GlobalTimerServiceBaseTest extends TimerBaseTest{
    
    protected GlobalSchedulerService globalScheduler;
    protected RuntimeManager manager;
   
    protected abstract RuntimeManager getManager(RuntimeEnvironment environment);
    
    @After
    public void cleanup() {
        manager.close();
    }

    @Test
    public void testInterediateTiemrWithGlobalTestService() throws Exception {
        
        // prepare listener to assert results
        final List<Long> timerExporations = new ArrayList<Long>();
        ProcessEventListener listener = new DefaultProcessEventListener(){

            @Override
            public void afterNodeLeft(ProcessNodeLeftEvent event) {
                if (event.getNodeInstance().getNodeName().equals("timer")) {
                    timerExporations.add(event.getProcessInstance().getId());
                }
            }
            
        };
        
        SimpleRuntimeEnvironment environment = new DefaultRuntimeEnvironment();
        environment.setRegisterableItemsFactory(new TestRegisterableItemsFactory(listener));
        environment.setSchedulerService(globalScheduler);
        
        environment.addAsset(ResourceFactory.newClassPathResource("BPMN2-IntermediateCatchEventTimerCycle3.bpmn2"), ResourceType.BPMN2);

        manager = getManager(environment);

        Runtime runtime = manager.getRuntime(ProcessInstanceIdContext.get());
        KieSession ksession = runtime.getKieSession();
        ksession.addEventListener(listener);
        
        ProcessInstance processInstance = ksession.startProcess("IntermediateCatchEvent");
        assertTrue(processInstance.getState() == ProcessInstance.STATE_ACTIVE);
        // now wait for 1 second for first timer to trigger
        Thread.sleep(1500);
        // dispose session to force session to be reloaded on timer expiration
        manager.disposeRuntime(runtime);
        Thread.sleep(2000);
        
        runtime = manager.getRuntime(ProcessInstanceIdContext.get(processInstance.getId()));
        ksession = runtime.getKieSession();
        ksession.abortProcessInstance(processInstance.getId());
        processInstance = ksession.getProcessInstance(processInstance.getId());        
        assertNull(processInstance);
        // let's wait to ensure no more timers are expired and triggered
        Thread.sleep(3000);
   
        assertEquals(3, timerExporations.size());
        manager.disposeRuntime(runtime);
    }

    @Test
    public void testTimerStart() throws Exception {
        // prepare listener to assert results
        final List<Long> timerExporations = new ArrayList<Long>();
        ProcessEventListener listener = new DefaultProcessEventListener(){

            @Override
            public void beforeProcessStarted(ProcessStartedEvent event) {
                timerExporations.add(event.getProcessInstance().getId());
            }

 
            
        };
        
        SimpleRuntimeEnvironment environment = new DefaultRuntimeEnvironment();
        environment.setRegisterableItemsFactory(new TestRegisterableItemsFactory(listener));
        environment.setSchedulerService(globalScheduler);
        
        environment.addAsset(ResourceFactory.newClassPathResource("BPMN2-TimerStart2.bpmn2"), ResourceType.BPMN2);
        manager = getManager(environment);
        Runtime runtime = manager.getRuntime(ProcessInstanceIdContext.get());
        KieSession ksession = runtime.getKieSession();
        
        
        assertEquals(0, timerExporations.size());
        for (int i = 0; i < 5; i++) {
            Thread.sleep(1000);
        }
        manager.disposeRuntime(runtime);
        assertEquals(5, timerExporations.size());

    }

    @Test
    public void testTimerRule() throws Exception {
        // prepare listener to assert results
        final List<String> timerExporations = new ArrayList<String>();
        AgendaEventListener listener = new DefaultAgendaEventListener(){

            @Override
            public void beforeMatchFired(BeforeMatchFiredEvent event) {
                timerExporations.add(event.getMatch().getRule().getId());
            }

        };
        
        SimpleRuntimeEnvironment environment = new DefaultRuntimeEnvironment();
        environment.setRegisterableItemsFactory(new TestRegisterableItemsFactory(listener));
        environment.setSchedulerService(globalScheduler);
        environment.addAsset(ResourceFactory.newClassPathResource("timer-rules.drl"), ResourceType.DRL);
        manager = getManager(environment);
        Runtime runtime = manager.getRuntime(ProcessInstanceIdContext.get());
        
        assertEquals(0, timerExporations.size());
        for (int i = 0; i < 5; i++) {
            runtime.getKieSession().fireAllRules();
            Thread.sleep(1000);
        }
        
        manager.disposeRuntime(runtime);
        assertEquals(5, timerExporations.size());
    }
    
    @Test
    public void testInterediateTiemrWithHTAfterWithGlobalTestService() throws Exception {
        
        // prepare listener to assert results
        final List<Long> timerExporations = new ArrayList<Long>();
        ProcessEventListener listener = new DefaultProcessEventListener(){

            @Override
            public void afterNodeLeft(ProcessNodeLeftEvent event) {
                if (event.getNodeInstance().getNodeName().equals("timer")) {
                    timerExporations.add(event.getProcessInstance().getId());
                }
            }
            
        };
        Properties properties= new Properties();
        properties.setProperty("mary", "HR");
        properties.setProperty("john", "HR");
        UserGroupCallback userGroupCallback = new JBossUserGroupCallbackImpl(properties);
        SimpleRuntimeEnvironment environment = new DefaultRuntimeEnvironment();
        environment.setRegisterableItemsFactory(new TestRegisterableItemsFactory(listener));
        environment.setUserGroupCallback(userGroupCallback);
        environment.setSchedulerService(globalScheduler);
        
        environment.addAsset(ResourceFactory.newClassPathResource("BPMN2-IntermediateCatchEventTimerCycleWithHT.bpmn2"), ResourceType.BPMN2);
        manager = getManager(environment);
        Runtime runtime = manager.getRuntime(ProcessInstanceIdContext.get());
        KieSession ksession = runtime.getKieSession();
        ksession.addEventListener(listener);
        Map<String, Object> params = new HashMap<String, Object>();
        params.put("x", "1s###1s");
        ProcessInstance processInstance = ksession.startProcess("IntermediateCatchEvent", params);
        assertTrue(processInstance.getState() == ProcessInstance.STATE_ACTIVE);
        System.out.println("Disposed after start");
        // dispose session to force session to be reloaded on timer expiration
        manager.disposeRuntime(runtime);
        // now wait for 1 second for first timer to trigger
        Thread.sleep(1500);
        
        Thread.sleep(2000);
        
        runtime = manager.getRuntime(ProcessInstanceIdContext.get(processInstance.getId()));
        ksession = runtime.getKieSession();
        
        // get tasks
        List<Status> statuses = new ArrayList<Status>();
        statuses.add(Status.Reserved);
        List<TaskSummary> tasks = runtime.getTaskService().getTasksAssignedAsPotentialOwnerByStatus("john", statuses, "en-UK");
        assertNotNull(tasks);
        assertEquals(3, tasks.size());
        
        for (TaskSummary task : tasks) {
            runtime.getTaskService().start(task.getId(), "john");
            runtime.getTaskService().complete(task.getId(), "john", null);
        }
        
        ksession.abortProcessInstance(processInstance.getId());
        processInstance = ksession.getProcessInstance(processInstance.getId());        
        assertNull(processInstance);
        // let's wait to ensure no more timers are expired and triggered
        Thread.sleep(3000);
   
        
        manager.disposeRuntime(runtime);

        assertEquals(3, timerExporations.size());
    }
    
    @Test
    public void testInterediateTiemrWithHTBeforeWithGlobalTestService() throws Exception {
        
        // prepare listener to assert results
        final List<Long> timerExporations = new ArrayList<Long>();
        ProcessEventListener listener = new DefaultProcessEventListener(){

            @Override
            public void afterNodeLeft(ProcessNodeLeftEvent event) {
                if (event.getNodeInstance().getNodeName().equals("timer")) {
                    timerExporations.add(event.getProcessInstance().getId());
                }
            }
            
        };
        Properties properties= new Properties();
        properties.setProperty("mary", "HR");
        properties.setProperty("john", "HR");
        UserGroupCallback userGroupCallback = new JBossUserGroupCallbackImpl(properties);
        SimpleRuntimeEnvironment environment = new DefaultRuntimeEnvironment();
        environment.setRegisterableItemsFactory(new TestRegisterableItemsFactory(listener));
        environment.setUserGroupCallback(userGroupCallback);
        environment.setSchedulerService(globalScheduler);
        
        environment.addAsset(ResourceFactory.newClassPathResource("BPMN2-IntermediateCatchEventTimerCycleWithHT2.bpmn2"), ResourceType.BPMN2);
        manager = getManager(environment);
        Runtime runtime = manager.getRuntime(ProcessInstanceIdContext.get());
        KieSession ksession = runtime.getKieSession();
        ksession.addEventListener(listener);
        
        Map<String, Object> params = new HashMap<String, Object>();
        params.put("x", "1s###1s");
        ProcessInstance processInstance = ksession.startProcess("IntermediateCatchEvent", params);
        assertTrue(processInstance.getState() == ProcessInstance.STATE_ACTIVE);
        
        // get tasks
        List<Status> statuses = new ArrayList<Status>();
        statuses.add(Status.Reserved);
        List<TaskSummary> tasks = runtime.getTaskService().getTasksAssignedAsPotentialOwnerByStatus("john", statuses, "en-UK");
        assertNotNull(tasks);
        assertEquals(1, tasks.size());
        
        for (TaskSummary task : tasks) {
            runtime.getTaskService().start(task.getId(), "john");
            runtime.getTaskService().complete(task.getId(), "john", null);
        }
        // dispose session to force session to be reloaded on timer expiration
        manager.disposeRuntime(runtime);
        // now wait for 1 second for first timer to trigger
        Thread.sleep(1500);
        
        Thread.sleep(2000);
        
        runtime = manager.getRuntime(ProcessInstanceIdContext.get(processInstance.getId()));
        ksession = runtime.getKieSession();

        
        ksession.abortProcessInstance(processInstance.getId());
        processInstance = ksession.getProcessInstance(processInstance.getId());        
        assertNull(processInstance);
        // let's wait to ensure no more timers are expired and triggered
        Thread.sleep(3000);
   
   
        manager.disposeRuntime(runtime);

        assertEquals(3, timerExporations.size());
    }
    
 
    public static void cleanupSingletonSessionId() {
        File tempDir = new File(System.getProperty("java.io.tmpdir"));
        if (tempDir.exists()) {
            
            String[] jbpmSerFiles = tempDir.list(new FilenameFilter() {
                
                @Override
                public boolean accept(File dir, String name) {
                    
                    return name.endsWith("-jbpmSessionId.ser");
                }
            });
            for (String file : jbpmSerFiles) {
                
                new File(tempDir, file).delete();
            }
        }
    }
}
