package org.jcon.util.minor;

/**
 * This class is a renewable timer that emits a callback
 * when the timeout expires. To receive the callback the
 * client must implement TimerUser. To renew the timeout
 * call renewTimeout().
 * <p>
 * Possible future features are: <p> <pre>
 * - setRepeat(boolean repeat) - If true start is called
 *      after every timeout, causing the timer to repeat
 *      indefinitely, until stopped.
 * - setRandomLow(int randomLow)
 * - setRandomHigh(int randomHigh)
 * - setRandom(boolean random) - If true then the callback
 *     will occur at random intervals using the low and high.
 *     This will continue until the timeout is reached. </pre>
 *
 * @author Jack Harich
 */
public class Timer implements Runnable {

//---------- Private Fields ------------------------------
// Properties
private int       timeout;
private TimerUser timerUser;
// Internal
private boolean   timerRunning;
private long      startTime;
private Thread    myThread;

//---------- Runnable Implementation ---------------------
public void run() {
    renewTimeout();
    int sleepTime = timeout;
    while (timerRunning) {
        try {
            Thread.currentThread().sleep(sleepTime);
    
        } catch(InterruptedException ex) {
            // Caused by stop() - NOT YET TESTED ***
            print(".run() - Interrupted"); 
            myThread = null;
            return;
        }
        // Have we timed out?
        synchronized(this) {
            long currentTime = System.currentTimeMillis();
            long expiredTime = currentTime - startTime;
            if (expiredTime > timeout) {
                // Stop
                timerRunning = false;
                if (timerUser == null) {
                    print(".run() - timed out but no timerUser");   
                } else {
                    timerUser.timeoutExpired(this);   
                }
                myThread = null;
                return;
            } else {
                // Extend since must have renewed timeout
                // Honor timout from renewed startTime 
                sleepTime = (int)(timeout - expiredTime);             
            }
        }
    }
    myThread = null;
}    
//---------- Properties ----------------------------------
//----- timeout
/**
* Sets the time in milliseconds the timer will run until
* it times out. Upon timeout the TimeoutUser will be notified.
* The timeout default is zero.
* <p>
* Throws an IllegalStateException if the timeout is less than zero.
*/
public synchronized void setTimeout(int timeout) {
    if (timeout < 0) throw new IllegalArgumentException(
        "Timeout cannot be less than zero.");
    this.timeout = timeout;   
}    
public int getTimeout() {
    return timeout;   
}    
//----- timerUser
public void setTimerUser(TimerUser timerUser) {
    this.timerUser = timerUser;   
}    
public TimerUser getTimerUser() {
    return timerUser;   
}    
//---------- Public Methods ------------------------------
public synchronized void start() {
    if (! timerRunning) {
        timerRunning = true;
        myThread = new Thread(this);
        myThread.start();
    }
}   
/**
* This renews the timeout by starting over at the beginning
* of the timeout period. This is useful for clients who
* perform frequent work and want to avoid a timeout while
* working, or want to renew the timeout after each work
* step to avoid a timeout while working.
* <p>
* Throws an IllegalStateException if the timer is not running.
*/
public synchronized void renewTimeout() {
    if (! timerRunning) {
        throw new IllegalStateException(
            "Time is not running so it cannot be renewed.");
    } else {
        startTime = System.currentTimeMillis();
    }
}
/**
* Stops the timer immediately if it is running. There will
* be no further callbacks after the timer is stopped.
*/
public synchronized void stop() {
    if (myThread != null) myThread.interrupt();
}
//---------- Private Methods -----------------------------
//--- Std
private static void print(String text) {
    System.out.println("Timer" + text);
}

} // End class
