package at.oefai.aaa.thread;

import java.io.Serializable;
import java.util.concurrent.CountDownLatch;

/**
 * Replacement for thread that allows safe stopping (actually pausing).
 * @author Stefan Rank
 */
public class StoppableThread implements Serializable {
    // the following are non-private to allow inner classes direct access
    IStoppable runIt = null;
    volatile CountDownLatch startLatch = null;
    volatile CountDownLatch stopLatch = null;
    volatile Thread blinker = null;
    volatile boolean stopped = true;

    private StoppableRunner stoppableRunner = new StoppableRunner();

    /* For autonumbering threads. */
    private static int threadInitNumber = 1;
    private static synchronized int nextThreadNum() {
        return threadInitNumber++;
    }


    public StoppableThread(final IStoppable prunIt) {
        assert (prunIt != null) : "non-null IStoppable required for StoppableThread";
        this.runIt = prunIt;
    }

    /** Starts me, if there is a startSignal I'll wait for it. */
    public final void start(final CountDownLatch startSignal) {
        if (! this.stopped || this.blinker != null) {
            return; // I am already running
        }
        this.startLatch = startSignal;
        this.blinker = new Thread(this.stoppableRunner, "StoppableThread-" + nextThreadNum());
        // use a lower priority so that graphics and interface often preempt StoppableThreads (agents).
        //blinker.setPriority(Thread.currentThread().getPriority() - 1);
        this.blinker.setPriority(Thread.MIN_PRIORITY);
        this.blinker.start();
    }

    /** Stops me, if there is a returnSignal I will give it (countDown). */
    public final void stop(final CountDownLatch returnSignal) {
        if (this.blinker == null || this.stopped) {
            if (returnSignal != null) {
                returnSignal.countDown();
            }
            return; // I am already stopped
        }
        this.stopLatch = returnSignal;
        this.blinker = null; // internal signal for stopping
        // if i ever use wait on something, notify() is necessary here
        // as i am using sleep inside the IStoppables wake them up:
        // could call interrupt() here to wake up sleeping thread
    }

    public final boolean isStopped() {
        return this.stopped;
    }

    /**
     * The StoppableRunner is only used internally to wrap around the IStoppable passed to this Thread-alike.
     * @author wrstl
     */
    private class StoppableRunner implements Runnable {
        public void run() {
            Thread thisThread = Thread.currentThread();
            if (StoppableThread.this.startLatch != null) {
                try { // wait for the start signal
                    StoppableThread.this.startLatch.await();
                } catch (InterruptedException e) {
                    // ignore
                } finally { // reset for the next start
                    StoppableThread.this.startLatch = null;
                }
            }
            StoppableThread.this.stopped = false;
            StoppableThread.this.runIt.onStart();
            while ((StoppableThread.this.blinker == thisThread)
                    && (! StoppableThread.this.runIt.isCompleted())) {
                Thread.yield(); // hope this helps
                StoppableThread.this.runIt.runOneLoop();
                Thread.yield();
            }
            StoppableThread.this.stopped = true;
            StoppableThread.this.runIt.onStop();
            if (StoppableThread.this.stopLatch != null) { // signal that I stopped
                StoppableThread.this.stopLatch.countDown();
                StoppableThread.this.stopLatch = null; // reset for the next stop
            }
        }
    }

}
