package org.jbpm.test.persistence.timer;

import static org.jbpm.test.JBPMHelper.processStateName;

import java.util.HashMap;
import java.util.Map;

import org.drools.core.process.instance.WorkItemHandler;
import org.jbpm.test.JbpmJUnitBaseTestCase;
import org.junit.Before;
import org.junit.Test;
import org.kie.api.runtime.KieSession;
import org.kie.api.runtime.manager.RuntimeEngine;
import org.kie.api.runtime.process.ProcessInstance;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * See JBPM-3170/JBPM-3391
 */
public class InMemoryTimerPersistenceTest extends JbpmJUnitBaseTestCase {

    // General setup
    private static final Logger logger = LoggerFactory.getLogger(InMemoryTimerPersistenceTest.class);

    // Test processses
    
    private final static String PROCESS_FILE_NAME = "boundaryTimerProcess.bpmn";
    private final static String PROCESS_NAME = "BoundaryTimerEventProcess";
    private final static String WORK_ITEM_HANLDER_TASK = "Human Task";
    
    private final static String TIMER_FIRED_PROP = "timerFired";
    private final static String TIMER_FIRED_TIME_PROP = "afterTimerTime";

    public InMemoryTimerPersistenceTest() { 
        super(true, false);
    }
    
    @Before
    public void setup() { 
        System.clearProperty(TIMER_FIRED_PROP);
        System.clearProperty(TIMER_FIRED_TIME_PROP);
    }
    
    @Test
    public void boundaryEventTimerAndCompleteHumanTaskWithoutPersistence() throws InterruptedException {
        createRuntimeManager(PROCESS_FILE_NAME);
        RuntimeEngine runtimeEngine = getRuntimeEngine();
        KieSession ksession = runtimeEngine.getKieSession();
       
        // Do stuff
        HumanTaskMockHandler humanTaskMockHandler = new HumanTaskMockHandler();
        ProcessInstance process = registerHTHandlerAndStartProcess(ksession, humanTaskMockHandler);
        
        sleepAndVerifyTimerRuns(process.getState());
        completeWork(ksession, humanTaskMockHandler);
   
        // The process reaches the end node
        int processState = process.getState();
        assertEquals("Expected process state to be " + processStateName[ProcessInstance.STATE_COMPLETED],
                ProcessInstance.STATE_COMPLETED, processState);
    }
    

    private ProcessInstance registerHTHandlerAndStartProcess(KieSession ksession, HumanTaskMockHandler humanTaskMockHandler) { 
        // Register Human Task Handler
        ksession.getWorkItemManager().registerWorkItemHandler(WORK_ITEM_HANLDER_TASK, humanTaskMockHandler);
    
        // Start the process 
        ProcessInstance process = ksession.startProcess(PROCESS_NAME);
        long processId = process.getId();
        assertTrue("process id not saved", processId > 0);
        
        // The process is in the Human Task waiting for its completion
        int processState = process.getState();
        assertEquals("Expected process state to be " + processStateName[ProcessInstance.STATE_ACTIVE] + " not "
                + processStateName[processState], ProcessInstance.STATE_ACTIVE, processState);
        
        return process;
    
    }

    private void completeWork(KieSession ksession, HumanTaskMockHandler humanTaskMockHandler) {
        assertTrue("The work item task handler does not have a work item!", humanTaskMockHandler.workItem != null);
        long workItemId = humanTaskMockHandler.workItem.getId();
        assertTrue("work item id not saved", workItemId > 0);
        
        // The Human Task is completed
        Map<String, Object> results = new HashMap<String, Object>();
        try {
            ksession.getWorkItemManager().completeWorkItem(workItemId, results);
        } catch (Exception e) {
            logger.warn("Work item could not be completed!");
            e.printStackTrace();
            fail(e.getClass().getSimpleName() + " thrown when completing work item: " + e.getMessage());
        }
    }

    private void sleepAndVerifyTimerRuns(int processState) throws InterruptedException { 
        // wait 3 seconds to see if the boss is notified
        if (processState == ProcessInstance.STATE_ACTIVE) {
            int sleep = 2000;
            logger.debug("Sleeping {} seconds", sleep / 1000);
            Thread.sleep(sleep);
            logger.debug("Awake!");
        }
        
        long afterSleepTime = System.currentTimeMillis();
        assertTrue("The timer has not fired!", timerHasFired());
        assertTrue("The timer did not fire on time!", afterSleepTime > timerFiredTime() );
        int timerFiredCount = timerFiredCount();
        assertTrue("The timer only fired " + timerFiredCount + " times.", timerFiredCount >= 1 );
    }

    
    private boolean timerHasFired() { 
        String hasFired = System.getProperty(TIMER_FIRED_PROP);
        if( hasFired != null ) { 
            return true;
        }
        return false;
    }


    private int timerFiredCount() { 
        String timerFiredCount = System.getProperty(TIMER_FIRED_PROP);
        if( timerFiredCount == null ) { 
           return 0; 
        }
        return Integer.parseInt(timerFiredCount);
    }


    private long timerFiredTime() { 
        String timerFiredCount = System.getProperty(TIMER_FIRED_TIME_PROP);
        if( timerFiredCount == null ) { 
           return 0; 
        }
        return Long.parseLong(timerFiredCount);
    }


    private static class HumanTaskMockHandler implements WorkItemHandler {

        private org.kie.api.runtime.process.WorkItemManager workItemManager;
        private org.kie.api.runtime.process.WorkItem workItem;

        public void executeWorkItem(org.kie.api.runtime.process.WorkItem workItem, org.kie.api.runtime.process.WorkItemManager manager) {
            this.workItem = workItem;
            this.workItemManager = manager;
            logger.debug("Work completed!");
        }

        public void abortWorkItem(org.kie.api.runtime.process.WorkItem workItem, org.kie.api.runtime.process.WorkItemManager manager) {
            this.workItemManager.abortWorkItem(workItem.getId());
            logger.debug("Work aborted.");
        }

    }
}
