package org.limewire.concurrent;

import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.TimeUnit;

import junit.framework.Test;

import org.limewire.service.ErrorCallback;
import org.limewire.service.ErrorCallbackStub;
import org.limewire.service.ErrorService;
import org.limewire.util.BaseTestCase;

public class ExecutorsHelperTest extends BaseTestCase {

    public ExecutorsHelperTest(String name) {
        super(name);
    }

    public static Test suite() {
        return buildTestSuite(ExecutorsHelperTest.class);
    }
    
    public void testProcessingQueueStarts() throws Exception {
        Runner r1 = new Runner();
        ExecutorService service = ExecutorsHelper.newProcessingQueue("Sam");
        service.execute(r1);
        assertTrue(r1.getRanLatch().await(100, TimeUnit.MILLISECONDS));
        assertEquals("Sam", r1.getName());
    }
    
    public void testProcessingQueueReuseThread() throws Exception {
        Runner r1 = new Runner();
        Runner r2 = new Runner();
        ExecutorService service = ExecutorsHelper.newProcessingQueue("Sammy");
        service.execute(r1);
        assertTrue(r1.getRanLatch().await(100, TimeUnit.MILLISECONDS));
        assertEquals("Sammy", r1.getName());
        Thread.sleep(100); // let any potential renaming happen.
        assertEquals("Sammy", r1.getThread().getName());
        service.execute(r2);
        assertTrue(r2.getRanLatch().await(100, TimeUnit.MILLISECONDS));
        assertSame(r1.getThread(), r2.getThread());
        assertEquals("Sammy", r2.getName());
    }
    
    public void testProcessingQueueThreadLingersThenDies() throws Exception {
        Runner r1 = new Runner();
        ExecutorService service = ExecutorsHelper.newProcessingQueue("SammyB");
        service.execute(r1);
        assertTrue(r1.getRanLatch().await(100, TimeUnit.MILLISECONDS));
        Thread thread = r1.getThread();
        assertEquals("SammyB", r1.getThread().getName());
        assertTrue(thread.isAlive());
        Thread.sleep(3500);
        assertTrue(thread.isAlive());
        Thread.sleep(2000);
        assertFalse(thread.isAlive());
        assertEquals("SammyB", r1.getThread().getName());
    }
    
    public void testProcessingQueueGoesSequentially() throws Exception {
        CountDownLatch runLatch = new CountDownLatch(1);
        Runner r1 = new Runner(runLatch);
        Runner r2 = new Runner();
        ExecutorService service = ExecutorsHelper.newProcessingQueue("SammyB");
        service.execute(r1);
        service.execute(r2);
        assertTrue(r1.getStartedLatch().await(100, TimeUnit.MILLISECONDS));
        assertFalse(r2.getStartedLatch().await(100, TimeUnit.MILLISECONDS));
        assertFalse(r1.getRanLatch().await(100, TimeUnit.MILLISECONDS));
        runLatch.countDown();
        assertTrue(r1.getRanLatch().await(100, TimeUnit.MILLISECONDS));
        assertTrue(r2.getRanLatch().await(100, TimeUnit.MILLISECONDS));
        assertSame(r1.getThread(), r2.getThread());
        assertEquals("SammyB", r1.getName());        
        assertEquals("SammyB", r2.getName());
    }
    
    public void testProcessingQueueUsesDaemonThread() throws Exception {
        Runner r1 = new Runner();
        ExecutorService service = ExecutorsHelper.newProcessingQueue("B");
        service.execute(r1);
        assertTrue(r1.getRanLatch().await(100, TimeUnit.MILLISECONDS));
        assertTrue(r1.getThread().isDaemon());
    }
    
    public void testProcessingQueueExceptionContinues() throws Exception {
        ErrorCallback oldCallback = ErrorService.getErrorCallback();
        try {
            ErrorCallbackStub testCallback = new ErrorCallbackStub();
            ErrorService.setErrorCallback(testCallback);
            assertEquals(0, testCallback.getExceptionCount());
            CountDownLatch runLatch = new CountDownLatch(1);
            Runner r1 = new Runner(runLatch, true);
            Runner r2 = new Runner();
            ExecutorService service = ExecutorsHelper.newProcessingQueue("Exceptionary");
            service.execute(r1);
            service.execute(r2);
            assertTrue(r1.getStartedLatch().await(100, TimeUnit.MILLISECONDS));
            assertFalse(r2.getStartedLatch().await(100, TimeUnit.MILLISECONDS));
            assertFalse(r1.getRanLatch().await(100, TimeUnit.MILLISECONDS));
            assertEquals(0, testCallback.getExceptionCount());
            runLatch.countDown();
            assertTrue(r1.getRanLatch().await(100, TimeUnit.MILLISECONDS));
            Thread.sleep(100); // let the exception propogate.
            assertEquals(1, testCallback.getExceptionCount());
            assertTrue(r2.getRanLatch().await(100, TimeUnit.MILLISECONDS));
            assertEquals(1, testCallback.getExceptionCount());
        } finally {
            ErrorService.setErrorCallback(oldCallback);
        }
    }
    

    public void testNewFixedSizeThreadPoolStartsMoreThanOneThread() throws Exception {
        ErrorCallback oldCallback = ErrorService.getErrorCallback();
        try {
            ErrorCallbackStub testCallback = new ErrorCallbackStub();
            ErrorService.setErrorCallback(testCallback);
            
            ExecutorService service = ExecutorsHelper.newFixedSizeThreadPool(2, "fixedSizePool");
            CountDownLatch runLatch = new CountDownLatch(1);
            Runner blockedRunner = new Runner(runLatch, false);
            Runner secondRunner = new Runner();
            service.execute(blockedRunner);
            service.execute(secondRunner);
            Thread.yield();
            assertTrue(blockedRunner.getStartedLatch().await(100, TimeUnit.MILLISECONDS));
            assertFalse(blockedRunner.getRanLatch().await(100, TimeUnit.MILLISECONDS));
            assertTrue("Second runner not run", secondRunner.getRanLatch().await(500, TimeUnit.MILLISECONDS));
            runLatch.countDown();
            assertTrue(blockedRunner.getRanLatch().await(100, TimeUnit.MILLISECONDS));
            assertEquals(0, testCallback.getExceptionCount());
        }
        finally {
            ErrorService.setErrorCallback(oldCallback);
        }
    }

    private static class Runner implements Runnable {
        private volatile Thread thread;
        private volatile String name;
        private final CountDownLatch startedLatch = new CountDownLatch(1);
        private final CountDownLatch ranLatch = new CountDownLatch(1);
        private final CountDownLatch runLatch;
        private final boolean throwException;
    
        public Runner() {
            this(null);
        }
        
        public Runner(CountDownLatch runLatch) {
            this(runLatch, false);
        }
        
        public Runner(boolean throwException) {
            this(null, throwException);
        }
        
        public Runner(CountDownLatch runLatch, boolean throwException) {
            this.throwException = throwException;
            this.runLatch = runLatch;
        }
        
        public void run() {
            thread = Thread.currentThread();
            name = thread.getName();
            startedLatch.countDown();
            if(runLatch != null) {
                try {   
                    if(!runLatch.await(10, TimeUnit.SECONDS))
                        fail("never got notified!");
                } catch(InterruptedException ie) {}
            }
            ranLatch.countDown();
           if(throwException)
               throw new RuntimeException("Abandon Hope.");
        }
        
        public CountDownLatch getRanLatch() {
            return ranLatch;
        }

        public CountDownLatch getStartedLatch() {
            return startedLatch;
        }

        public Thread getThread() {
            return thread;
        }
        
        public String getName() {
            return name;
        }
    }    
    
}
    