/**
 * 
 */
package org.jboss.test.faces;

import junit.framework.AssertionFailedError;
import junit.framework.TestCase;
import junit.framework.TestResult;

/**
 * @author asmirnov
 * @author Nick Belaevski
 */
public abstract class AbstractThreadedTest extends TestCase {

    /** 
     * The threads that are executing.
     */
    private Thread threads[] = null;
    /**
     * The tests TestResult.*/
    private TestResult testResult = null;

    public void interruptThreads() {
        if(threads != null) {
            for(int i = 0;i < threads.length;i++) {
                threads[i].interrupt();
            }
        }
    }

    /**
     * Override run so we can squirrel away the test result.
     * 
     */
    @Override
    public void run(final TestResult result) {
        testResult = result;
        super.run(result);
        testResult = null;
    }

    /**
     * Create instances of classes and run threads with it.
     * @param clazz - class of test thread implementation.
     * @param numThreads - number of threads to run.
     * @throws InstantiationException
     * @throws IllegalAccessException
     */
    protected void runTestCaseThreads(Class<?> clazz, int numThreads)  {
    	TestCaseRunnable[] runnables = new TestCaseRunnable[numThreads];
    	for (int i = 0; i < runnables.length; i++) {
			try {
				runnables[i]= (TestCaseRunnable) clazz.newInstance();
			} catch (Exception e) {
				testResult.addError(this, e);
				return;
			}
		}
    	runTestCaseRunnables(runnables);
    }
    /**
     * Run the test case threads.
     * @param runnables - array with instances of {@link TestCaseRunnable} with concrete tests
     */
    protected void runTestCaseRunnables (final TestCaseRunnable[] runnables) {
        if(runnables == null) {
            throw new IllegalArgumentException("runnables is null");
        }
        threads = new Thread[runnables.length];
        for(int i = 0;i < threads.length;i++) {
            threads[i] = new Thread(runnables[i]);
        }
        for(int i = 0;i < threads.length;i++) {
            threads[i].start();
        }
        try {
            for(int i = 0;i < threads.length;i++) {
                threads[i].join();
            }
        }
        catch(InterruptedException ignore) {
            System.out.println("Thread join interrupted.");
        }
        threads = null;
    }
    
    /**
     * Handle an exception. Since multiple threads won't have their
     * exceptions caught the threads must manually catch them and call
     * <code>handleException ()</code>.
     * @param t Exception to handle.*/
    private void handleException(final Throwable t) {
        synchronized(testResult) {
            if(t instanceof AssertionFailedError) {
                testResult.addFailure(this, (AssertionFailedError)t);
            }
            else {
                testResult.addError(this, t);
            }
        }
    }
    
    /**
     * A test case thread. Override runTestCase () and define
     * behaviour of test in there.*/
    public abstract class TestCaseRunnable implements Runnable {
        /**
         * Override this to define the test*/
        
        public abstract void runTestCase()
                              throws Throwable;
        /**
         * Run the test in an environment where
         * we can handle the exceptions generated by the test method.*/
        
        public void run() {
            try {
                runTestCase();
            }
            catch(Throwable t) /* Any other exception we handle and then we interrupt the other threads.*/ {
                handleException(t);
                interruptThreads();
            }
        }
    }

}
