/*******************************************************************************
 * Copyright (c) 2003, 2019 IBM Corporation and others.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v2.0
 * which accompanies this distribution, and is available at
 * https://www.eclipse.org/legal/epl-2.0/
 *
 * Contributors:
 *     IBM Corporation - initial API and implementation
 *******************************************************************************/

package org.eclipse.wst.common.frameworks.internal.ui;

import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;


public class Timer {
	/**
	 * DoPostEvent is a runnable class that fires actionEvents to the listeners on the
	 * EventDispatchThread, via invokeLater.
	 * 
	 * @see #post
	 */
	class DoPostEvent implements Runnable {
		@Override
		public void run() {
			if (eventQueued) {
				fireActionPerformed(new ActionEvent(getSource(), 0, null));
				cancelEvent();
			}
		}

		Timer getTimer() {
			return Timer.this;
		}
	}

	protected ListenerList listenerList = new ListenerList();
	boolean eventQueued = false;
	int initialDelay, delay;
	boolean repeats = true, coalesce = true;
	private Runnable doPostEvent;
	// These fields are maintained by TimerQueue.
	// eventQueued can also be reset by the TimerQueue, but will only ever
	// happen in applet case when TimerQueues thread is destroyed.
	long expirationTime;
	Timer nextTimer;
	boolean running;

	/**
	 * Creates a Timer that will notify its listeners every <i>delay </i> milliseconds.
	 * 
	 * @param delay
	 *            The number of milliseconds between listener notification
	 * @param listener
	 *            An initial listener
	 * @see #setInitialDelay
	 * @see #setRepeats
	 */
	public Timer(int delay, ActionListener listener) {
		super();
		this.delay = delay;
		this.initialDelay = delay;

		doPostEvent = new DoPostEvent();

		if (listener != null) {
			addActionListener(listener);
		}
	}

	/**
	 * Adds an actionListener to the Timer
	 */
	public void addActionListener(ActionListener listener) {
		listenerList.add(listener);
	}

	synchronized void cancelEvent() {
		eventQueued = false;
	}

	/**
	 * Notify all listeners that have registered interest for notification on this event type. The
	 * event instance is lazily created using the parameters passed into the fire method.
	 * 
	 * @see EventListenerList
	 */
	protected void fireActionPerformed(ActionEvent e) {
		// Guaranteed to return a non-null array
		ActionListener[] listeners = listenerList.getListenerList();

		// Process the listeners last to first, notifying
		// those that are interested in this event
		for (int i = listeners.length - 1; i >= 0; i -= 1) {
			listeners[i].actionPerformed(e);
		}
	}

	/**
	 * Returns the Timer's delay.
	 * 
	 * @see #setDelay
	 */
	public int getDelay() {
		return delay;
	}

	/**
	 * Returns the Timer's initial delay.
	 * 
	 * @see #setDelay
	 */
	public int getInitialDelay() {
		return initialDelay;
	}

	/**
	 * Returns <b>true </b> if the Timer coalesces multiple pending <b>performCommand() </b>
	 * messages.
	 * 
	 * @see #setCoalesce
	 */
	public boolean isCoalesce() {
		return coalesce;
	}

	/**
	 * Returns <b>true </b> if the Timer will send a <b>actionPerformed() </b> message to its
	 * listeners multiple times.
	 * 
	 * @see #setRepeats
	 */
	public boolean isRepeats() {
		return repeats;
	}

	/**
	 * Returns <b>true </b> if the Timer is running.
	 * 
	 * @see #start
	 */
	public boolean isRunning() {
		return timerQueue().containsTimer(this);
	}

	synchronized void post() {
		if (!eventQueued) {
			eventQueued = true;
			org.eclipse.swt.widgets.Display.getDefault().asyncExec(doPostEvent);
		}
	}

	/**
	 * Removes an ActionListener from the Timer.
	 */
	public void removeActionListener(ActionListener listener) {
		listenerList.remove(listener);
	}

	/**
	 * Restarts a Timer, canceling any pending firings, and causing it to fire with its initial
	 * dely.
	 */
	public void restart() {
		stop();
		start();
	}

	/**
	 * Sets whether the Timer coalesces multiple pending ActionEvent firings. A busy application may
	 * not be able to keep up with a Timer's message generation, causing multiple
	 * <b>actionPerformed() </b> message sends to be queued. When processed, the application sends
	 * these messages one after the other, causing the Timer's listeners to receive a sequence of
	 * <b>actionPerformed() </b> messages with no delay between them. Coalescing avoids this
	 * situation by reducing multiple pending messages to a single message send. Timers coalesce
	 * their message sends by default.
	 */
	public void setCoalesce(boolean flag) {
		coalesce = flag;
	}

	/**
	 * Sets the Timer's delay, the number of milliseconds between successive <b>actionPerfomed()
	 * </b> messages to its listeners
	 * 
	 * @see #setInitialDelay
	 */
	public void setDelay(int delay) {
		if (delay < 0) {
			String msg = WTPCommonUIResourceHandler.getString(WTPCommonUIResourceHandler.Timer_UI_0, new Object[]{Integer.toString(delay)});
			throw new IllegalArgumentException(msg);
		}
		this.delay = delay;
	}

	/**
	 * Sets the Timer's initial delay. This will be used for the first "ringing" of the Timer only.
	 * Subsequent ringings will be spaced using the delay property.
	 * 
	 * @see #setDelay
	 */
	public void setInitialDelay(int initialDelay) {
		if (initialDelay < 0) {
			String msg = WTPCommonUIResourceHandler.getString(WTPCommonUIResourceHandler.Timer_UI_1, new Object[]{Integer.toString(initialDelay)});
			throw new IllegalArgumentException(msg);
		}
		this.initialDelay = initialDelay;
	}

	/**
	 * If <b>flag </b> is <b>false </b>, instructs the Timer to send <b>actionPerformed() </b> to
	 * its listeners only once, and then stop.
	 */
	public void setRepeats(boolean flag) {
		repeats = flag;
	}

	/**
	 * Starts the Timer, causing it to send <b>actionPerformed() </b> messages to its listeners.
	 * 
	 * @see #stop
	 */
	public void start() {
		timerQueue().addTimer(this, System.currentTimeMillis() + getInitialDelay());
	}

	/**
	 * Stops a Timer, causing it to stop sending <b>actionPerformed() </b> messages to its Target.
	 * 
	 * @see #start
	 */
	public void stop() {
		timerQueue().removeTimer(this);
		cancelEvent();
	}

	/**
	 * Returns the timer queue.
	 */
	TimerQueue timerQueue() {
		return TimerQueue.singleton();
	}

	/**
	 * Return the source for the ActionEvent that is fired.
	 * 
	 * @return
	 */
	protected Object getSource() {
		return this;
	}
}
