/*
 * Copyright (C) The Apache Software Foundation. All rights reserved.
 *
 * This software is published under the terms of the Apache Software License
 * version 1.1, a copy of which has been included with this distribution in
 * the LICENSE file.
 */
package org.apache.james.util;

import java.util.NoSuchElementException;
//import org.apache.avalon.phoenix.Service;
import java.util.Timer;
import java.util.TimerTask;
import java.util.*;
import java.lang.reflect.*;
import org.apache.avalon.framework.activity.Startable;
import org.apache.avalon.framework.activity.Disposable;
import org.apache.avalon.framework.activity.Initializable;
//import org.apache.avalon.phoenix.Block;
import org.apache.avalon.cornerstone.services.scheduler.TimeScheduler;
import org.apache.avalon.cornerstone.services.scheduler.TimeTrigger;
import org.apache.avalon.cornerstone.services.scheduler.Target;
import org.apache.avalon.cornerstone.services.scheduler.PeriodicTimeTrigger;

/**
 * This service provides a way to regularly schedule jobs.
 *
 * @author <a href="mailto:harmeet@apache.org">Harmeet Bedi</a>
 */
public class TimeSchedulerImpl 
    implements TimeScheduler, Initializable, Disposable
{

    private static class Entry {
        final String id;
        final Target trg;
        final long offset;
        final Long scheduledTime;
        Entry(long offset,String id,Target trg) {
            this.id = id;
            this.trg = trg;
            this.offset = offset;
            this.scheduledTime = new Long(System.currentTimeMillis()+offset);
        }
    }  // class Entry

    private static class Cache {
        private final SortedMap timerMap = new TreeMap();
        private final Map idMap = new HashMap();
        synchronized void put(Entry entry) {
            putInternal(entry);
        }
        synchronized void reset(String id) {
            Entry entry = (Entry)idMap.remove(id);
            timerMap.remove(entry.scheduledTime);
            entry = new Entry(entry.offset,entry.id,entry.trg);
            putInternal(entry);
        }
        synchronized void remove(String id) {
            Entry entry = (Entry)idMap.remove(id);
            if ( entry != null )
                timerMap.remove(entry.scheduledTime);
        }
        synchronized void execFirstTask() throws InterruptedException {
            // sleep if there is nothing.
            while ( timerMap.isEmpty() )
                Thread.currentThread().sleep(1000);

            Long first = (Long)timerMap.firstKey();
            long future = first.longValue()-System.currentTimeMillis();
            if ( future > 0 )
                Thread.currentThread().sleep(future);
            else {
                Entry entry = (Entry)timerMap.remove(first);
                idMap.remove(entry.id);
                entry.trg.targetTriggered(entry.id);
            }
        }
        private void putInternal(Entry entry) {
            // move by on millisec, if there is a stored entry
            while ( timerMap.containsKey(entry.scheduledTime) ) {
                entry = new Entry(entry.offset+1,entry.id,entry.trg);
            }
            timerMap.put(entry.scheduledTime,entry);
            idMap.put(entry.id,entry);
        }
        public synchronized String toString() { 
            return timerMap.size()+", "+idMap.size(); 
        }
    }  // Cache

    private static class TriggerThread extends Thread {
        private final Cache cache;
        TriggerThread(Cache cache) {
            super("timer-task-trigger");
            this.cache = cache;
        }
        public void run() {
            while ( Thread.currentThread().isInterrupted() ) {
                try {
                    cache.execFirstTask();
                } catch(Throwable t) {
                    if ( t instanceof InterruptedException )
                        break;
                    else
                        t.printStackTrace();
                }
            }
        }
    }

    // name to TimerTask map.
    private Cache cache;
    private Thread triggerThread;

    public void initialize()
    {
        cache = new Cache();
        triggerThread = new TriggerThread(cache);
        triggerThread.start();
    }

    public void dispose()
    {
        triggerThread.interrupt();
        cache = null;
    }

    /** this method converts Avalon TimeTrigger mechanism to JDK Timer based 
     * mechanism. 
     * The Trigger is expected to be <PeriodicTimeTrigger>. 
     * Warning: This method may have cause a very small and in most cases 
     * inconsequential skew in the alarm mechanism. 
     */
    public void addTrigger(String name, TimeTrigger trigger, Target target ) {
        log();
        if ( ( trigger instanceof PeriodicTimeTrigger ) == false ) 
        {
            throw new RuntimeException
                ("Currently only the periodic timer is supported");
        }
        PeriodicTimeTrigger ptm = (PeriodicTimeTrigger)trigger;
        cache.put(new Entry(ptm.getOffset(),name,target));
    }
    public void removeTrigger( String name )
        throws NoSuchElementException 
    {
        cache.remove(name);
    }

    public void resetTrigger( String name ) throws NoSuchElementException 
    {
        log();
        cache.reset(name);
    }

    private static int counter = 0;
    private void log() {
        counter++;
        if ( counter % 500 == 0 )
            System.out.println(cache.toString());
    }
}

