package net.sourceforge.tuned;


import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.TimeUnit;
import java.util.logging.Level;
import java.util.logging.Logger;


public abstract class Timer implements Runnable {
	
	private final ThreadFactory threadFactory = new DefaultThreadFactory("Timer", Thread.NORM_PRIORITY, true);
	
	private ScheduledThreadPoolExecutor executor;
	private ScheduledFuture<?> scheduledFuture;
	private Thread shutdownHook;
	
	
	public synchronized void set(long delay, TimeUnit unit, boolean runBeforeShutdown) {
		// create executor if necessary
		if (executor == null) {
			executor = new ScheduledThreadPoolExecutor(1, threadFactory);
		}
		
		// cancel existing future task
		if (scheduledFuture != null) {
			scheduledFuture.cancel(true);
		}
		
		Runnable runnable = this;
		
		if (runBeforeShutdown) {
			try {
				addShutdownHook();
			} catch (Exception e) {
				// may fail if running with restricted permissions
				Logger.getLogger(getClass().getName()).log(Level.WARNING, e.getClass().getName() + ": " + e.getMessage());
			}
			
			// remove shutdown hook after execution
			runnable = new Runnable() {
				
				@Override
				public void run() {
					try {
						Timer.this.run();
					} finally {
						cancel();
					}
				}
			};
		} else {
			try {
				// remove existing shutdown hook, if any
				removeShutdownHook();
			} catch (Exception e) {
				// may fail if running with restricted permissions
				Logger.getLogger(getClass().getName()).log(Level.WARNING, e.getClass().getName() + ": " + e.getMessage());
			}
		}
		
		scheduledFuture = executor.schedule(runnable, delay, unit);
	}
	
	
	public synchronized void cancel() {
		removeShutdownHook();
		
		// stop executor
		executor.shutdownNow();
		
		scheduledFuture = null;
		executor = null;
	}
	
	
	private synchronized void addShutdownHook() {
		if (shutdownHook == null) {
			shutdownHook = new Thread(this);
			Runtime.getRuntime().addShutdownHook(shutdownHook);
		}
	}
	
	
	private synchronized void removeShutdownHook() {
		if (shutdownHook != null) {
			try {
				if (shutdownHook != Thread.currentThread()) {
					// can't remove shutdown hooks anymore, once runtime is shutting down,
					// so don't remove the shutdown hook, if we are running on the shutdown hook
					Runtime.getRuntime().removeShutdownHook(shutdownHook);
				}
			} finally {
				shutdownHook = null;
			}
		}
	}
	
}