package org.multiverse;

import java.util.concurrent.CountDownLatch;
import java.util.concurrent.atomic.AtomicLong;

import static org.junit.Assert.*;

/**
 * A TestThread that tracks if any throwable has been thrown by a thread.
 *
 * @author Peter Veentjer.
 */
public abstract class TestThread extends Thread {

    private final static AtomicLong idGenerator  = new AtomicLong();
    
    private volatile Throwable throwable;
    private volatile Boolean endedWithInterruptStatus;
    private volatile boolean startInterrupted;
    private volatile boolean printStackTrace = true;
    private long durationMs = -1;
    private CountDownLatch latch;

    public TestThread() {
        this("TestThread-"+idGenerator.incrementAndGet());
    }

    public TestThread(String name) {
        this(name, false);
    }

    public TestThread(String name, boolean startInterrupted) {
        super(name);
        this.startInterrupted = startInterrupted;
    }

    public void setLatch(CountDownLatch latch) {
        this.latch = latch;
    }

    public void setStartInterrupted(boolean startInterrupted) {
        this.startInterrupted = startInterrupted;
    }

    public void setPrintStackTrace(boolean printStackTrace) {
        this.printStackTrace = printStackTrace;
    }

    public boolean doesStartInterrupted() {
        return startInterrupted;
    }

    public Boolean hasEndedWithInterruptStatus() {
        return endedWithInterruptStatus;
    }

    public long getDurationMs() {
        return durationMs;
    }

    @Override
    public final void run() {
        if(latch!=null){
            latch.countDown();
        }

        if (startInterrupted) {
            interrupt();
        }

        long startMs = System.currentTimeMillis();
        try {

            doRun();
        } catch (Throwable ex) {
            if (printStackTrace) {
                System.out.printf("Thread %s has thrown an exception\n", getName());
                ex.printStackTrace();
            }
            this.throwable = ex;
        } finally {
            endedWithInterruptStatus = isInterrupted();
            durationMs = System.currentTimeMillis() - startMs;
        }
    }

    public abstract void doRun() throws Exception;

    public Throwable getThrowable() {
        return throwable;
    }

    public void assertInterrupted() {
        assertFailedWithException(InterruptedException.class);
    }

    public void assertEndedWithInterruptStatus(boolean interrupt) {
        assertEquals(interrupt,endedWithInterruptStatus);
    }

    public void assertFailedWithException(Class expected) {
        assertNotNull(throwable);
        assertTrue("Found exception: " + throwable.getClass().getName(), throwable.getClass().isAssignableFrom(expected));
    }

    public void assertNothingThrown() {
        assertNull(throwable);
    }
}
