package com.github.davidmoten.rx;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Set;
import java.util.concurrent.TimeUnit;

import rx.Observable;
import rx.functions.Func1;
import rx.observers.TestSubscriber;

public final class TestingHelper {

    public static <T, R> Builder<T, R> function(Func1<Observable<T>, Observable<R>> function) {
        return new Builder<T, R>().function(function);
    }

    private static class Case<T, R> {
        final List<T> from;
        final List<R> expected;

        Case(List<T> from, List<R> expected) {
            this.from = from;
            this.expected = expected;
        }
    }

    public static class Builder<T, R> {

        private final List<Case<T, R>> cases = new ArrayList<Case<T, R>>();

        private Func1<Observable<T>, Observable<R>> function;

        public ExpectBuilder<T, R> fromEmpty() {
            return new ExpectBuilder<T, R>(this, Collections.emptyList());
        }

        public ExpectBuilder<T, R> from(T... items) {
            return new ExpectBuilder<T, R>(this, Arrays.asList(items));
        }

        public Builder<T, R> function(Func1<Observable<T>, Observable<R>> function) {
            this.function = function;
            return this;
        }

        public Builder<T, R> expect(List<T> from, List<R> expected) {
            cases.add(new Case<T, R>(from, expected));
            return this;
        }

        public void runTests() {
            for (Case<T, R> c : cases) {
                UnsubscribeDetector<T> detector = UnsubscribeDetector.detect();
                TestSubscriber<R> sub = new TestSubscriber<R>();
                function.call(Observable.from(c.from).lift(detector)).subscribe(sub);
                sub.assertTerminalEvent();
                sub.assertNoErrors();
                sub.assertReceivedOnNext(c.expected);
                sub.assertUnsubscribed();
                try {
                    detector.latch().await(3, TimeUnit.SECONDS);
                } catch (InterruptedException e) {
                    // do nothing
                }
            }
            System.out.println("tests passed");
        }
    }

    public static class ExpectBuilder<T, R> {
        private final boolean empty;
        private List<T> list;
        private final Builder<T, R> builder;

        private ExpectBuilder(Builder<T, R> builder, List<T> list) {
            this.builder = builder;
            this.list = list;
            this.empty = false;
        }

        private ExpectBuilder(Builder<T, R> builder, boolean empty) {
            this.empty = empty;
            this.builder = builder;
        }

        public Builder<T, R> expectEmpty() {
            return builder.expect(list, Collections.<R> emptyList());
        }

        public Builder<T, R> expect(R... items) {
            return expect(Arrays.asList(items));
        }

        public Builder<T, R> expect(List<R> items) {
            return builder.expect(list, items);
        }

        public Builder<T, R> expect(Set<R> set) {
            return new Builder<T, R>();
        }
    }

    public static void main(String[] args) {
        // test count operator
        Func1<Observable<String>, Observable<Integer>> f = new Func1<Observable<String>, Observable<Integer>>() {
            @Override
            public Observable<Integer> call(Observable<String> o) {
                return o.count();
            }
        };
        TestingHelper.function(f)
        // test empty
                .fromEmpty().expect(0)
                // test non-empty count
                .from("a", "b").expect(2)
                // test single input
                .from("a").expect(1)
                // run tests
                .runTests();

    }
}

@@ -195,16 +195,83 @@
 eUnit;%0A%0A
+import junit.framework.TestCase;%0Aimport junit.framework.TestSuite;%0A
 import r
@@ -1621,32 +1621,242 @@
 ases) %7B%0A        
+        runTest(function, c);%0A            %7D%0A            System.out.println(%22tests passed%22);%0A        %7D%0A    %7D%0A%0A    private static %3CT, R%3E void runTest(Func1%3CObservable%3CT%3E, Observable%3CR%3E%3E function, Case%3CT, R%3E c) %7B%0A
         Unsubscr
@@ -1907,32 +1907,24 @@
 r.detect();%0A
-        
         Test
@@ -1976,24 +1976,16 @@
         
-   
-     
 function
@@ -2050,32 +2050,24 @@
 b);%0A        
-      
-  
 sub.assertTe
@@ -2077,32 +2077,24 @@
 nalEvent();%0A
-        
         sub.
@@ -2115,32 +2115,24 @@
 ();%0A        
-  
-      
 sub.assertRe
@@ -2165,24 +2165,16 @@
         
-     
-   
 sub.asse
@@ -2187,32 +2187,24 @@
 bscribed();%0A
-        
         try 
@@ -2217,24 +2217,16 @@
         
- 
-       
 detector
@@ -2270,24 +2270,16 @@
         
-    
-    
 %7D catch 
@@ -2317,24 +2317,16 @@
         
-        
 // do no
@@ -2335,88 +2335,8 @@
 ing%0A
-                %7D%0A            %7D%0A            System.out.println(%22tests passed%22);%0A
     
@@ -3281,32 +3281,516 @@
        %7D%0A    %7D%0A%0A
+    private static class MyTestSuite%3CT, R%3E extends TestSuite %7B%0A%0A        MyTestSuite(List%3CCase%3CT, R%3E%3E cases) %7B%0A            for (Case%3CT, R%3E c : cases) %7B%0A                addTest(new MyTestCase(c));%0A            %7D%0A        %7D%0A    %7D%0A%0A    private static class MyTestCase%3CT, R%3E extends TestCase %7B%0A%0A        private final Case%3CT, R%3E c;%0A%0A        MyTestCase(Case%3CT, R%3E c) %7B%0A            this.c = c;%0A        %7D%0A%0A        @Override%0A        protected void runTest() throws Throwable %7B%0A%0A        %7D%0A%0A    %7D%0A%0A
     public stati

add unsubscribe detecotr