/*
 * Copyright (c) 1997, 2009, Oracle and/or its affiliates. All rights reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * This code is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 only, as
 * published by the Free Software Foundation.  Oracle designates this
 * particular file as subject to the "Classpath" exception as provided
 * by Oracle in the LICENSE file that accompanied this code.
 *
 * This code is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * version 2 for more details (a copy is included in the LICENSE file that
 * accompanied this code).
 *
 * You should have received a copy of the GNU General Public License version
 * 2 along with this work; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 * or visit www.oracle.com if you need additional information or have any
 * questions.
 */



package javax.swing;



import java.util.*;
import java.util.concurrent.*;
import java.util.concurrent.locks.*;
import java.util.concurrent.atomic.AtomicLong;
import sun.awt.AppContext;



/**
 * Internal class to manage all Timers using one thread.
 * TimerQueue manages a queue of Timers. The Timers are chained
 * together in a linked list sorted by the order in which they will expire.
 *
 * @author Dave Moore
 * @author Igor Kushnirskiy
 */
class TimerQueue implements Runnable
{
    private static final Object sharedInstanceKey =
        new StringBuffer("TimerQueue.sharedInstanceKey");
    private static final Object expiredTimersKey =
        new StringBuffer("TimerQueue.expiredTimersKey");

    private final DelayQueue<DelayedTimer> queue;
    private volatile boolean running;
    private final Lock runningLock;

    /* Lock object used in place of class object for synchronization.
     * (4187686)
     */
    private static final Object classLock = new Object();

    /** Base of nanosecond timings, to avoid wrapping */
    private static final long NANO_ORIGIN = System.nanoTime();

    /**
     * Constructor for TimerQueue.
     */
    public TimerQueue() {
        super();
        queue = new DelayQueue<DelayedTimer>();
        // Now start the TimerQueue thread.
        runningLock = new ReentrantLock();
        startIfNeeded();
    }


    public static TimerQueue sharedInstance() {
        synchronized (classLock) {
            TimerQueue sharedInst = (TimerQueue)
                                    SwingUtilities.appContextGet(
                                                        sharedInstanceKey);
            if (sharedInst == null) {
                sharedInst = new TimerQueue();
                SwingUtilities.appContextPut(sharedInstanceKey, sharedInst);
            }
            return sharedInst;
        }
    }


    void startIfNeeded() {
        if (! running) {
            runningLock.lock();
            try {
                final ThreadGroup threadGroup =
                    AppContext.getAppContext().getThreadGroup();
                java.security.AccessController.doPrivileged(
                    new java.security.PrivilegedAction<Object>() {
                    public Object run() {
                        Thread timerThread = new Thread(threadGroup, TimerQueue.this,
                                                        "TimerQueue");
                        timerThread.setDaemon(true);
                        timerThread.setPriority(Thread.NORM_PRIORITY);
                        timerThread.start();
                        return null;
                    }
                });
                running = true;
            } finally {
                runningLock.unlock();
            }
        }
    }

    void addTimer(Timer timer, long delayMillis) {
        timer.getLock().lock();
        try {
            // If the Timer is already in the queue, then ignore the add.
            if (! containsTimer(timer)) {
                addTimer(new DelayedTimer(timer,
                                      TimeUnit.MILLISECONDS.toNanos(delayMillis)
                                      + now()));
            }
        } finally {
            timer.getLock().unlock();
        }
    }

    private void addTimer(DelayedTimer delayedTimer) {
        assert delayedTimer != null && ! containsTimer(delayedTimer.getTimer());

        Timer timer = delayedTimer.getTimer();
        timer.getLock().lock();
        try {
            timer.delayedTimer = delayedTimer;
            queue.add(delayedTimer);
        } finally {
            timer.getLock().unlock();
        }
    }

    void removeTimer(Timer timer) {
        timer.getLock().lock();
        try {
            if (timer.delayedTimer != null) {
                queue.remove(timer.delayedTimer);
                timer.delayedTimer = null;
            }
        } finally {
            timer.getLock().unlock();
        }
    }

    boolean containsTimer(Timer timer) {
        timer.getLock().lock();
        try {
            return timer.delayedTimer != null;
        } finally {
            timer.getLock().unlock();
        }
    }


    public void run() {
        runningLock.lock();
        try {
            while (running) {
                try {
                    Timer timer = queue.take().getTimer();
                    timer.getLock().lock();
                    try {
                        DelayedTimer delayedTimer = timer.delayedTimer;
                        if (delayedTimer != null) {
                            /*
                             * Timer is not removed after we get it from
                             * the queue and before the lock on the timer is
                             * acquired
                             */
                            timer.post(); // have timer post an event
                            timer.delayedTimer = null;
                            if (timer.isRepeats()) {
                                delayedTimer.setTime(now()
                                    + TimeUnit.MILLISECONDS.toNanos(
                                          timer.getDelay()));
                                addTimer(delayedTimer);
                            }
                        }
                    } catch (SecurityException ignore) {
                    } finally {
                        timer.getLock().unlock();
                    }
                } catch (InterruptedException ie) {
                    // Shouldn't ignore InterruptedExceptions here, so AppContext
                    // is disposed gracefully, see 6799345 for details
                    if (AppContext.getAppContext().isDisposed()) {
                        break;
                    }
                }
            }
        }
        catch (ThreadDeath td) {
            // Mark all the timers we contain as not being queued.
            for (DelayedTimer delayedTimer : queue) {
                delayedTimer.getTimer().cancelEvent();
            }
            throw td;
        } finally {
            running = false;
            runningLock.unlock();
        }
    }


    public String toString() {
        StringBuilder buf = new StringBuilder();
        buf.append("TimerQueue (");
        boolean isFirst = true;
        for (DelayedTimer delayedTimer : queue) {
            if (! isFirst) {
                buf.append(", ");
            }
            buf.append(delayedTimer.getTimer().toString());
            isFirst = false;
        }
        buf.append(")");
        return buf.toString();
    }

    /**
     * Returns nanosecond time offset by origin
     */
    private static long now() {
        return System.nanoTime() - NANO_ORIGIN;
    }

    static class DelayedTimer implements Delayed {
        // most of it copied from
        // java.util.concurrent.ScheduledThreadPoolExecutor

        /**
         * Sequence number to break scheduling ties, and in turn to
         * guarantee FIFO order among tied entries.
         */
        private static final AtomicLong sequencer = new AtomicLong(0);

        /** Sequence number to break ties FIFO */
        private final long sequenceNumber;


        /** The time the task is enabled to execute in nanoTime units */
        private volatile long time;

        private final Timer timer;

        DelayedTimer(Timer timer, long nanos) {
            this.timer = timer;
            time = nanos;
            sequenceNumber = sequencer.getAndIncrement();
        }


        final public long getDelay(TimeUnit unit) {
            return  unit.convert(time - now(), TimeUnit.NANOSECONDS);
        }

        final void setTime(long nanos) {
            time = nanos;
        }

        final Timer getTimer() {
            return timer;
        }

        public int compareTo(Delayed other) {
            if (other == this) { // compare zero ONLY if same object
                return 0;
            }
            if (other instanceof DelayedTimer) {
                DelayedTimer x = (DelayedTimer)other;
                long diff = time - x.time;
                if (diff < 0) {
                    return -1;
                } else if (diff > 0) {
                    return 1;
                } else if (sequenceNumber < x.sequenceNumber) {
                    return -1;
                }  else {
                    return 1;
                }
            }
            long d = (getDelay(TimeUnit.NANOSECONDS) -
                      other.getDelay(TimeUnit.NANOSECONDS));
            return (d == 0) ? 0 : ((d < 0) ? -1 : 1);
        }
    }
}
