/**
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You 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.apache.activemq.apollo.util;

import java.util.LinkedList;
import java.util.TreeMap;
import java.util.concurrent.TimeUnit;

public abstract class TimerHeap<V> {
    private final TreeMap<Long, LinkedList<V>> timers = new TreeMap<Long, LinkedList<V>>();
    private final TimeUnit resolution = TimeUnit.NANOSECONDS;
    private int size = 0;

    public final void addAbsolute(V timed, long time, TimeUnit timeUnit) {
        addInternal(timed, System.nanoTime() + resolution.convert(resolution.convert(time, timeUnit), timeUnit));
    }

    public final void addRelative(V timed, long delay, TimeUnit timeUnit) {

        addInternal(timed, System.nanoTime() + resolution.convert(delay, timeUnit));
    }

    private void addInternal(V timed, long eTime) {
        LinkedList<V> list = new LinkedList<V>();
        list.add(timed);

        LinkedList<V> old = timers.put(eTime, list);
        if (old != null) {
            list.addAll(old);
        }
        size++;
    }

    public int size() {
        return size;
    }

    /**
     * Returns the time of the next scheduled event.
     * 
     * @return -1 if there are no events, otherwise the time that the next timer
     *         should fire.
     */
    public final long timeToNext(TimeUnit unit) {
        if (timers.isEmpty()) {
            return -1;
        } else {
            return unit.convert(Math.max(0, timers.firstKey() - System.nanoTime()), resolution);
        }
    }

    /**
     * Executes ready timers.
     */
    public final void executeReadyTimers() {
        LinkedList<V> ready = null;
        if (timers.isEmpty()) {
            return;
        } else {
            long now = System.nanoTime();
            long first = timers.firstKey();
            if (first > now) {
                return;
            }
            ready = new LinkedList<V>();

            while (first <= now) {
                ready.addAll(timers.remove(first));
                if (timers.isEmpty()) {
                    break;
                }
                first = timers.firstKey();

            }
        }

        for (V timed : ready) {
            try {
                execute(timed);
                size--;
            } catch (Throwable thrown) {
                thrown.printStackTrace();
            }
        }
    }

    /**
     * Subclass must override this to execute ready timers
     * 
     * @param ready
     *            The ready operation.
     */
    protected abstract void execute(V ready);
}
