/**
 * Copyright 2010 JBoss Inc
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      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.process.instance.timer;

import java.util.Collection;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;

import org.drools.common.InternalKnowledgeRuntime;
import org.drools.time.Job;
import org.drools.time.JobContext;
import org.drools.time.JobHandle;
import org.drools.time.TimerService;
import org.drools.time.impl.IntervalTrigger;
import org.jbpm.process.instance.InternalProcessRuntime;
import org.jbpm.process.instance.ProcessInstance;

/**
 * 
 * @author <a href="mailto:kris_verlaenen@hotmail.com">Kris Verlaenen</a>
 */
public class TimerManager {
    private long                     timerId    = 0;

    private InternalKnowledgeRuntime kruntime;
    private TimerService             timerService;
    private Map<Long, TimerInstance> timers     = new HashMap<Long, TimerInstance>();
    private Job                      processJob = new ProcessJob();

    public TimerManager(InternalKnowledgeRuntime kruntime,
                        TimerService timerService) {
        this.kruntime = kruntime;
        this.timerService = timerService;
    }

    public void registerTimer(final TimerInstance timer,
                              ProcessInstance processInstance) {
        timer.setId( ++timerId );
        timer.setProcessInstanceId( processInstance.getId() );
        timer.setActivated( new Date() );

        ProcessJobContext ctx = new ProcessJobContext( timer,
                                                       processInstance.getId(),
                                                       this.kruntime );

        JobHandle jobHandle = this.timerService.scheduleJob( processJob,
                                                             ctx,
                                                             new IntervalTrigger( timerService.getCurrentTime(),
                                                                                  null,
                                                                                  null,
                                                                                  -1,
                                                                                  timer.getDelay(),
                                                                                  timer.getPeriod(),
                                                                                  null,
                                                                                  null ) );
        timer.setJobHandle( jobHandle );
        timers.put( timer.getId(),
                    timer );
    }

    public void internalAddTimer(final TimerInstance timer) {
        ProcessJobContext ctx = new ProcessJobContext( timer,
                                                       timer.getProcessInstanceId(),
                                                       this.kruntime );

        long delay;
        Date lastTriggered = timer.getLastTriggered();
        if ( lastTriggered == null ) {
            Date activated = timer.getActivated();
            Date now = new Date();
            long timespan = now.getTime() - activated.getTime();
            delay = timer.getDelay() - timespan;
            if ( delay < 0 ) {
                delay = 0;
            }
        } else {
            Date now = new Date();
            long timespan = now.getTime() - lastTriggered.getTime();
            delay = timespan - timer.getPeriod();
            if ( delay < 0 ) {
                delay = 0;
            }
        }
        JobHandle jobHandle = this.timerService.scheduleJob( processJob,
                                                             ctx,
                                                             new IntervalTrigger( timerService.getCurrentTime(),
                                                                                  null,
                                                                                  null,
                                                                                  -1,
                                                                                  delay,
                                                                                  timer.getPeriod(),
                                                                                  null,
                                                                                  null ) );
        timer.setJobHandle( jobHandle );
        timers.put( timer.getId(),
                    timer );
    }

    public void cancelTimer(long timerId) {
        TimerInstance timer = timers.remove( timerId );
        if ( timer != null ) {
            timerService.removeJob( timer.getJobHandle() );
        }
    }

    public void dispose() {
        for ( TimerInstance timer : timers.values() ) {
            timerService.removeJob( timer.getJobHandle() );
        }
        timerService.shutdown();
    }

    public TimerService getTimerService() {
        return this.timerService;
    }

    public Collection<TimerInstance> getTimers() {
        return timers.values();
    }

    public long internalGetTimerId() {
        return timerId;
    }

    public void internalSetTimerId(long timerId) {
        this.timerId = timerId;
    }

    public void setTimerService(TimerService timerService) {
        this.timerService = timerService;
    }

    public class ProcessJob
        implements
        Job {

        public void execute(JobContext c) {
            ProcessJobContext ctx = (ProcessJobContext) c;

            Long processInstanceId = ctx.getProcessInstanceId();
            InternalKnowledgeRuntime kruntime = ctx.getKnowledgeRuntime();

            if ( processInstanceId == null ) {
                throw new IllegalArgumentException( "Could not find process instance for timer " );
            }

            ctx.getTimer().setLastTriggered( new Date() );

            ((InternalProcessRuntime) kruntime.getProcessRuntime())
            	.getSignalManager().signalEvent( processInstanceId,
                                                 "timerTriggered",
                                                  ctx.getTimer() );

            if ( ctx.getTimer().getPeriod() == 0 ) {
                TimerManager.this.timers.remove( ctx.getTimer().getId() );
            }
        }

    }

    public static class ProcessJobContext
        implements
        JobContext {
        private Long                     processInstanceId;
        private InternalKnowledgeRuntime kruntime;
        private TimerInstance            timer;

        private JobHandle     jobHandle;

        public ProcessJobContext(final TimerInstance timer,
                                 final Long processInstanceId,
                                 final InternalKnowledgeRuntime kruntime) {
            this.timer = timer;
            this.processInstanceId = processInstanceId;
            this.kruntime = kruntime;
        }

        public Long getProcessInstanceId() {
            return processInstanceId;
        }

        public InternalKnowledgeRuntime getKnowledgeRuntime() {
            return kruntime;
        }

        public JobHandle getJobHandle() {
            return this.jobHandle;
        }

        public void setJobHandle(JobHandle jobHandle) {
            this.jobHandle = jobHandle;
        }

        public TimerInstance getTimer() {
            return timer;
        }

    }

}
