package rsc.publisher;

import java.util.concurrent.ExecutionException;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.FutureTask;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;

import org.junit.Assert;
import org.junit.Test;
import rsc.test.TestSubscriber;
import rsc.util.ConstructorTestBuilder;

public class PublisherFutureTest {

    @Test
    public void constructors() {
        ConstructorTestBuilder ctb = new ConstructorTestBuilder(PublisherFuture.class);
        
        ctb.addRef("future", new FutureTask<>(() -> 1));
        ctb.addLong("timeout", Long.MIN_VALUE, Long.MAX_VALUE);
        ctb.addRef("unit", TimeUnit.SECONDS);
    }
    
    @Test(timeout = 2000)
    public void normal() {
        ScheduledExecutorService exec = Executors.newScheduledThreadPool(1);
        try {
            Future<Integer> f = exec.schedule(() -> 1, 500, TimeUnit.MILLISECONDS);
            
            TestSubscriber<Integer> ts = new TestSubscriber<>();
            
            new PublisherFuture<>(f).subscribe(ts);
            
            ts.await();
            
            ts.assertValue(1)
            .assertNoError()
            .assertComplete();
            
        } finally {
            exec.shutdown();
        }
    }

    @Test(timeout = 2000)
    public void normalBackpressured() throws Exception {
        ScheduledExecutorService exec = Executors.newScheduledThreadPool(1);
        try {
            Future<Integer> f = exec.schedule(() -> 1, 100, TimeUnit.MILLISECONDS);
            
            TestSubscriber<Integer> ts = new TestSubscriber<>(0);
            
            new PublisherFuture<>(f).subscribe(ts);
            
            Thread.sleep(500);
            
            ts.request(1);
            
            ts.await();
            
            ts.assertValue(1)
            .assertNoError()
            .assertComplete();
            
        } finally {
            exec.shutdown();
        }
    }
    
    @Test(timeout = 2000)
    public void timeout() {
        ScheduledExecutorService exec = Executors.newScheduledThreadPool(1);
        try {
            Future<Integer> f = exec.schedule(() -> 1, 1500, TimeUnit.MILLISECONDS);
            
            TestSubscriber<Integer> ts = new TestSubscriber<>();
            
            new PublisherFuture<>(f, 500, TimeUnit.MILLISECONDS).subscribe(ts);
            
            ts.await();
            
            ts.assertNoValues()
            .assertError(TimeoutException.class)
            .assertNotComplete();
            
        } finally {
            exec.shutdown();
        }
    }
    
    @Test(timeout = 2000)
    public void futureThrows() {
        ScheduledExecutorService exec = Executors.newScheduledThreadPool(1);
        try {
            Future<Integer> f = exec.schedule(() -> { throw new RuntimeException("forced failure"); }, 500, TimeUnit.MILLISECONDS);
            
            TestSubscriber<Integer> ts = new TestSubscriber<>();
            
            new PublisherFuture<>(f).subscribe(ts);
            
            ts.await();
            
            ts.assertNoValues()
            .assertError(ExecutionException.class)
            .assertErrorCause(RuntimeException.class)
            .assertNotComplete();
            
        } finally {
            exec.shutdown();
        }
    }

    @Test(timeout = 2000)
    public void noCrossCancel() throws Exception {
        ScheduledExecutorService exec = Executors.newScheduledThreadPool(2);
        try {
            Future<Integer> f = exec.schedule(() -> 1, 1500, TimeUnit.MILLISECONDS);
            
            TestSubscriber<Integer> ts = new TestSubscriber<>();
            
            exec.submit(() -> new PublisherFuture<>(f).subscribe(ts));
            
            Thread.sleep(500);
            
            ts.cancel();

            Thread.sleep(100);
            
            Assert.assertFalse("Future done?", f.isDone());
            
            Assert.assertFalse("Future cancelled?", f.isCancelled());
            
            ts.assertNoValues()
            .assertNoError()
            .assertNotComplete();
            
        } finally {
            exec.shutdown();
        }
    }

}