/**
 * Copyright 2014 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.internal.util;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.fail;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.atomic.AtomicInteger;

import org.junit.*;

import rx.Subscription;
import rx.exceptions.CompositeException;
import rx.internal.util.SubscriptionList;
import rx.subscriptions.Subscriptions;

public class SubscriptionListTest {

    @Test
    public void testSuccess() {
        final AtomicInteger counter = new AtomicInteger();
        SubscriptionList s = new SubscriptionList();
        s.add(new Subscription() {

            @Override
            public void unsubscribe() {
                counter.incrementAndGet();
            }

            @Override
            public boolean isUnsubscribed() {
                return false;
            }
        });

        s.add(new Subscription() {

            @Override
            public void unsubscribe() {
                counter.incrementAndGet();
            }

            @Override
            public boolean isUnsubscribed() {
                return false;
            }
        });

        s.unsubscribe();

        assertEquals(2, counter.get());
    }

    @Test(timeout = 1000)
    public void shouldUnsubscribeAll() throws InterruptedException {
        final AtomicInteger counter = new AtomicInteger();
        final SubscriptionList s = new SubscriptionList();

        final int count = 10;
        final CountDownLatch start = new CountDownLatch(1);
        for (int i = 0; i < count; i++) {
            s.add(new Subscription() {

                @Override
                public void unsubscribe() {
                    counter.incrementAndGet();
                }

                @Override
                public boolean isUnsubscribed() {
                    return false;
                }
            });
        }

        final List<Thread> threads = new ArrayList<Thread>();
        for (int i = 0; i < count; i++) {
            final Thread t = new Thread() {
                @Override
                public void run() {
                    try {
                        start.await();
                        s.unsubscribe();
                    } catch (final InterruptedException e) {
                        fail(e.getMessage());
                    }
                }
            };
            t.start();
            threads.add(t);
        }

        start.countDown();
        for (final Thread t : threads) {
            t.join();
        }

        assertEquals(count, counter.get());
    }

    @Test
    public void testException() {
        final AtomicInteger counter = new AtomicInteger();
        SubscriptionList s = new SubscriptionList();
        s.add(new Subscription() {

            @Override
            public void unsubscribe() {
                throw new RuntimeException("failed on first one");
            }

            @Override
            public boolean isUnsubscribed() {
                return false;
            }
        });

        s.add(new Subscription() {

            @Override
            public void unsubscribe() {
                counter.incrementAndGet();
            }

            @Override
            public boolean isUnsubscribed() {
                return false;
            }
        });

        try {
            s.unsubscribe();
            fail("Expecting an exception");
        } catch (RuntimeException e) {
            // we expect this
            assertEquals(e.getMessage(), "failed on first one");
        }

        // we should still have unsubscribed to the second one
        assertEquals(1, counter.get());
    }

    @Test
    public void testCompositeException() {
        final AtomicInteger counter = new AtomicInteger();
        SubscriptionList s = new SubscriptionList();
        s.add(new Subscription() {

            @Override
            public void unsubscribe() {
                throw new RuntimeException("failed on first one");
            }

            @Override
            public boolean isUnsubscribed() {
                return false;
            }
        });

        s.add(new Subscription() {

            @Override
            public void unsubscribe() {
                throw new RuntimeException("failed on second one too");
            }

            @Override
            public boolean isUnsubscribed() {
                return false;
            }
        });

        s.add(new Subscription() {

            @Override
            public void unsubscribe() {
                counter.incrementAndGet();
            }

            @Override
            public boolean isUnsubscribed() {
                return false;
            }
        });

        try {
            s.unsubscribe();
            fail("Expecting an exception");
        } catch (CompositeException e) {
            // we expect this
            assertEquals(e.getExceptions().size(), 2);
        }

        // we should still have unsubscribed to the second one
        assertEquals(1, counter.get());
    }


    @Test
    public void testUnsubscribeIdempotence() {
        final AtomicInteger counter = new AtomicInteger();
        SubscriptionList s = new SubscriptionList();
        s.add(new Subscription() {

            @Override
            public void unsubscribe() {
                counter.incrementAndGet();
            }

            @Override
            public boolean isUnsubscribed() {
                return false;
            }
        });

        s.unsubscribe();
        s.unsubscribe();
        s.unsubscribe();

        // we should have only unsubscribed once
        assertEquals(1, counter.get());
    }

    @Test(timeout = 1000)
    public void testUnsubscribeIdempotenceConcurrently()
            throws InterruptedException {
        final AtomicInteger counter = new AtomicInteger();
        final SubscriptionList s = new SubscriptionList();

        final int count = 10;
        final CountDownLatch start = new CountDownLatch(1);
        s.add(new Subscription() {

            @Override
            public void unsubscribe() {
                counter.incrementAndGet();
            }

            @Override
            public boolean isUnsubscribed() {
                return false;
            }
        });

        final List<Thread> threads = new ArrayList<Thread>();
        for (int i = 0; i < count; i++) {
            final Thread t = new Thread() {
                @Override
                public void run() {
                    try {
                        start.await();
                        s.unsubscribe();
                    } catch (final InterruptedException e) {
                        fail(e.getMessage());
                    }
                }
            };
            t.start();
            threads.add(t);
        }

        start.countDown();
        for (final Thread t : threads) {
            t.join();
        }

        // we should have only unsubscribed once
        assertEquals(1, counter.get());
    }

    @Test
    public void removeWhenEmpty() {
        SubscriptionList slist = new SubscriptionList();
        Subscription s = Subscriptions.empty();

        slist.remove(s);

        Assert.assertFalse(s.isUnsubscribed());
    }

    @Test
    public void removeNotIn() {
        SubscriptionList slist = new SubscriptionList();
        Subscription s0 = Subscriptions.empty();
        slist.add(s0);

        Assert.assertTrue(slist.hasSubscriptions());

        Subscription s = Subscriptions.empty();

        slist.remove(s);

        Assert.assertFalse(s.isUnsubscribed());

        slist.clear();

        Assert.assertTrue(s0.isUnsubscribed());

        Assert.assertFalse(slist.hasSubscriptions());
    }

    @Test
    public void unsubscribeClear() {
        SubscriptionList slist = new SubscriptionList();

        Assert.assertFalse(slist.hasSubscriptions());

        Subscription s0 = Subscriptions.empty();
        slist.add(s0);

        slist.unsubscribe();

        Assert.assertTrue(s0.isUnsubscribed());

        Assert.assertFalse(slist.hasSubscriptions());

        slist.clear();
    }

}
