package com.hwangjr.rxbus;

import com.hwangjr.rxbus.annotation.Produce;
import com.hwangjr.rxbus.annotation.Subscribe;
import com.hwangjr.rxbus.entity.EventType;
import com.hwangjr.rxbus.entity.ProducerEvent;
import com.hwangjr.rxbus.entity.SubscriberEvent;
import com.hwangjr.rxbus.finder.Finder;
import com.hwangjr.rxbus.thread.EventThread;
import com.hwangjr.rxbus.thread.ThreadEnforcer;

import org.junit.Before;
import org.junit.Test;

import java.util.Arrays;
import java.util.Comparator;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import java.util.SortedSet;
import java.util.TreeSet;

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

/**
 * Test case for subscribers which unregister while handling an event.
 */
public class UnregisteringSubscriberTest {

    private static final String EVENT = "Hello";
    private static final String BUS_IDENTIFIER = "test-bus";

    private Bus bus;

    @Before
    public void setUp() throws Exception {
        bus = new Bus(ThreadEnforcer.ANY, BUS_IDENTIFIER, new SortedSubscriberFinder());
    }

    @Test
    public void unregisterInSubscriber() {
        UnregisteringStringCatcher catcher = new UnregisteringStringCatcher(bus);
        bus.register(catcher);
        bus.post(EVENT);

        assertEquals("One correct event should be delivered.", Arrays.asList(EVENT), catcher.getEvents());

        bus.post(EVENT);
        bus.post(EVENT);
        assertEquals("Shouldn't catch any more events when unregistered.", Arrays.asList(EVENT), catcher.getEvents());
    }

    @Test
    public void unregisterInSubscriberWhenEventProduced() throws Exception {
        UnregisteringStringCatcher catcher = new UnregisteringStringCatcher(bus);

        bus.register(new StringProducer());
        bus.register(catcher);
        assertEquals(Arrays.asList(StringProducer.VALUE), catcher.getEvents());

        bus.post(EVENT);
        bus.post(EVENT);
        assertEquals("Shouldn't catch any more events when unregistered.",
                Arrays.asList(StringProducer.VALUE), catcher.getEvents());
    }

    @Test
    public void unregisterProducerInSubscriber() throws Exception {
        final Object producer = new Object() {
            private int calls = 0;

            @Produce(
                    thread = EventThread.IMMEDIATE
            )
            public String produceString() {
                calls++;
                if (calls > 1) {
                    fail("Should only have been called once, then unregistered and never called again.");
                }
                return "Please enjoy this hand-crafted String.";
            }
        };
        bus.register(producer);
        bus.register(new Object() {
            @Subscribe(
                    thread = EventThread.IMMEDIATE
            )
            public void firstUnsubscribeTheProducer(String produced) {
                bus.unregister(producer);
            }

            @Subscribe(
                    thread = EventThread.IMMEDIATE
            )
            public void shouldNeverBeCalled(String uhoh) {
                fail("Shouldn't receive events from an unregistered producer.");
            }
        });
    }

    /**
     * Delegates to {@code Finder.ANNOTATED}, then sorts results by {@code SubscriberEvent#toString}
     */
    static class SortedSubscriberFinder implements Finder {

        static Comparator<SubscriberEvent> subscriberComparator = new Comparator<SubscriberEvent>() {
            @Override
            public int compare(SubscriberEvent eventSubscriber, SubscriberEvent eventSubscriber1) {
                return eventSubscriber.toString().compareTo(eventSubscriber1.toString());
            }
        };

        @Override
        public Map<EventType, ProducerEvent> findAllProducers(Object listener) {
            return Finder.ANNOTATED.findAllProducers(listener);
        }

        @Override
        public Map<EventType, Set<SubscriberEvent>> findAllSubscribers(Object listener) {
            Map<EventType, Set<SubscriberEvent>> found = Finder.ANNOTATED.findAllSubscribers(listener);
            Map<EventType, Set<SubscriberEvent>> sorted = new HashMap<>();
            for (Map.Entry<EventType, Set<SubscriberEvent>> entry : found.entrySet()) {
                SortedSet<SubscriberEvent> subscribers = new TreeSet<>(subscriberComparator);
                subscribers.addAll(entry.getValue());
                sorted.put(entry.getKey(), subscribers);
            }
            return sorted;
        }
    }
}
