/**
 * Copyright 2013 Netflix, Inc.
 * 
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 * http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package rx.operators;

import static org.junit.Assert.*;

import java.util.List;
import java.util.Vector;

import org.junit.Test;

import rx.Notification;
import rx.Observable;
import rx.Observer;
import rx.Subscription;
import rx.util.functions.Func1;

/**
 * Turns all of the notifications from an Observable into <code>onNext</code> emissions, and marks
 * them with their original notification types within {@link Notification} objects.
 * <p>
 * <img width="640" src="https://github.com/Netflix/RxJava/wiki/images/rx-operators/materialize.png">
 * <p>
 * See <a href="http://msdn.microsoft.com/en-us/library/hh229453(v=VS.103).aspx">here</a> for the
 * Microsoft Rx equivalent.
 */
public final class OperationMaterialize {

    /**
     * Materializes the implicit notifications of an observable sequence as explicit notification values.
     * 
     * @param sequence
     *            An observable sequence of elements to project.
     * @return An observable sequence whose elements are the result of materializing the notifications of the given sequence.
     * @see <a href="http://msdn.microsoft.com/en-us/library/hh229453(v=VS.103).aspx">Observable.Materialize(TSource) Method </a>
     */
    public static <T> Func1<Observer<Notification<T>>, Subscription> materialize(final Observable<T> sequence) {
        return new MaterializeObservable<T>(sequence);
    }

    private static class MaterializeObservable<T> implements Func1<Observer<Notification<T>>, Subscription> {

        private final Observable<T> sequence;

        public MaterializeObservable(Observable<T> sequence) {
            this.sequence = sequence;
        }

        @Override
        public Subscription call(final Observer<Notification<T>> observer) {
            return sequence.subscribe(new Observer<T>() {

                @Override
                public void onCompleted() {
                    observer.onNext(new Notification<T>());
                    observer.onCompleted();
                }

                @Override
                public void onError(Throwable e) {
                    observer.onNext(new Notification<T>(e));
                    observer.onCompleted();
                }

                @Override
                public void onNext(T value) {
                    observer.onNext(new Notification<T>(value));
                }

            });
        }

    }

    public static class UnitTest {
        @Test
        public void testMaterialize1() {
            // null will cause onError to be triggered before "three" can be returned
            final TestAsyncErrorObservable o1 = new TestAsyncErrorObservable("one", "two", null, "three");

            TestObserver Observer = new TestObserver();
            Observable<Notification<String>> m = Observable.create(materialize(o1));
            m.subscribe(Observer);

            try {
                o1.t.join();
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }

            assertFalse(Observer.onError);
            assertTrue(Observer.onCompleted);
            assertEquals(3, Observer.notifications.size());
            assertEquals("one", Observer.notifications.get(0).getValue());
            assertTrue(Observer.notifications.get(0).isOnNext());
            assertEquals("two", Observer.notifications.get(1).getValue());
            assertTrue(Observer.notifications.get(1).isOnNext());
            assertEquals(NullPointerException.class, Observer.notifications.get(2).getThrowable().getClass());
            assertTrue(Observer.notifications.get(2).isOnError());
        }

        @Test
        public void testMaterialize2() {
            final TestAsyncErrorObservable o1 = new TestAsyncErrorObservable("one", "two", "three");

            TestObserver Observer = new TestObserver();
            Observable<Notification<String>> m = Observable.create(materialize(o1));
            m.subscribe(Observer);

            try {
                o1.t.join();
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }

            assertFalse(Observer.onError);
            assertTrue(Observer.onCompleted);
            assertEquals(4, Observer.notifications.size());
            assertEquals("one", Observer.notifications.get(0).getValue());
            assertTrue(Observer.notifications.get(0).isOnNext());
            assertEquals("two", Observer.notifications.get(1).getValue());
            assertTrue(Observer.notifications.get(1).isOnNext());
            assertEquals("three", Observer.notifications.get(2).getValue());
            assertTrue(Observer.notifications.get(2).isOnNext());
            assertTrue(Observer.notifications.get(3).isOnCompleted());
        }

        @Test
        public void testMultipleSubscribes() {
            final TestAsyncErrorObservable o1 = new TestAsyncErrorObservable("one", "two", null, "three");

            Observable<Notification<String>> m = Observable.create(materialize(o1));

            TestObserver Observer1 = new TestObserver();
            m.subscribe(Observer1);

            TestObserver Observer2 = new TestObserver();
            m.subscribe(Observer2);

            try {
                o1.t.join();
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }

            assertEquals(3, Observer1.notifications.size());
            assertEquals(3, Observer2.notifications.size());
        }

    }

    private static class TestObserver implements Observer<Notification<String>> {

        boolean onCompleted = false;
        boolean onError = false;
        List<Notification<String>> notifications = new Vector<Notification<String>>();

        @Override
        public void onCompleted() {
            this.onCompleted = true;
        }

        @Override
        public void onError(Throwable e) {
            this.onError = true;
        }

        @Override
        public void onNext(Notification<String> value) {
            this.notifications.add(value);
        }

    }

    private static class TestAsyncErrorObservable extends Observable<String> {

        String[] valuesToReturn;

        TestAsyncErrorObservable(String... values) {
            valuesToReturn = values;
        }

        Thread t;

        @Override
        public Subscription subscribe(final Observer<String> observer) {
            t = new Thread(new Runnable() {

                @Override
                public void run() {
                    for (String s : valuesToReturn) {
                        if (s == null) {
                            System.out.println("throwing exception");
                            try {
                                Thread.sleep(100);
                            } catch (Throwable e) {

                            }
                            observer.onError(new NullPointerException());
                            return;
                        } else {
                            observer.onNext(s);
                        }
                    }
                    System.out.println("subscription complete");
                    observer.onCompleted();
                }

            });
            t.start();

            return new Subscription() {

                @Override
                public void unsubscribe() {

                }

            };
        }
    }
}

@@ -695,16 +695,64 @@
 .Vector;
+%0Aimport java.util.concurrent.ExecutionException;
 %0A%0Aimport
@@ -5466,16 +5466,64 @@
 cribes()
+ throws InterruptedException, ExecutionException
  %7B%0A     
@@ -5553,33 +5553,32 @@
 rrorObservable o
-1
  = new TestAsync
@@ -5700,25 +5700,24 @@
 aterialize(o
-1
 ));%0A%0A       
@@ -5725,452 +5725,162 @@
     
-TestObserver Observer1 = new TestObserver();%0A            m.subscribe(Observer1);%0A%0A            TestObserver Observer2 = new TestObserver();%0A            m.subscribe(Observer2);%0A%0A            try %7B%0A                o1.t.join();%0A            %7D catch (InterruptedException e) %7B%0A                throw new RuntimeException(e);%0A            %7D%0A%0A            assertEquals(3, Observer1.notifications.size());%0A            assertEquals(3, Observer2.notifications
+assertEquals(3, m.toList().toBlockingObservable().toFuture().get().size());%0A            assertEquals(3, m.toList().toBlockingObservable().toFuture().get()
 .siz
@@ -6706,24 +6706,33 @@
  %7D%0A%0A        
+volatile 
 Thread t;%0A%0A 

Make Materialize.testMultipleSubscribes test deterministic