package de.accso.flexinale.common.api.eventbus;

import de.accso.flexinale.common.api.eventbus.PublisherAndSubscriberTestdata.MyNumberAndMixedEventPublisher;
import de.accso.flexinale.common.api.eventbus.PublisherAndSubscriberTestdata.MyNumberAndMixedEventSubscriber;
import de.accso.flexinale.common.api.eventbus.PublisherAndSubscriberTestdata.MyNumberAndTextAndMixedEventSubscriber;
import de.accso.flexinale.common.api.eventbus.PublisherAndSubscriberTestdata.MyTextEventPublisher;
import de.accso.flexinale.common.api.event.AbstractEvent;
import de.accso.flexinale.common.api.event.Event;
import de.accso.flexinale.common.api.event.EventTestdata.MyMixedEvent;
import de.accso.flexinale.common.api.event.EventTestdata.MyNumberEvent;
import de.accso.flexinale.common.api.event.EventTestdata.MyTextEvent;
import de.accso.flexinale.common.infrastructure.eventbus.InMemorySyncEventBus;
import de.accso.flexinale.common.infrastructure.eventbus.InMemorySyncEventBusFactory;
import de.accso.flexinale.common.shared_kernel.Versionable;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;

import java.util.HashMap;
import java.util.Map;
import java.util.Random;
import java.util.Set;

import static de.accso.flexinale.common.api.eventbus.EventSubscriptionAtStart.START_READING_FROM_NOW;
import static org.assertj.core.api.AssertionsForClassTypes.assertThat;

@SuppressWarnings("unused")
class InMemorySyncEventBusAndSpyHandlingMultipleEventTypesTest {

    InMemorySyncEventBusFactory factory;

    InMemorySyncEventBus<MyNumberEvent> numberBus;
    InMemorySyncEventBus<MyTextEvent>     textBus;
    InMemorySyncEventBus<MyMixedEvent>   mixedBus;

    @BeforeEach
    public void setUp() {
        factory = new InMemorySyncEventBusFactory();

        numberBus = (InMemorySyncEventBus<MyNumberEvent>) factory.createOrGetEventBusFor(MyNumberEvent.class);
        textBus   = (InMemorySyncEventBus<MyTextEvent>)   factory.createOrGetEventBusFor(MyTextEvent.class);
        mixedBus  = (InMemorySyncEventBus<MyMixedEvent>)  factory.createOrGetEventBusFor(MyMixedEvent.class);
    }

    @Test
    void testOneEventTypeWithTwoInOnePublisherAndTwoInOneSubscriber() {
        // arrange
        Random random = new Random();

        int numEventsToPublish = 10;

        MyNumberAndMixedEventPublisher   twoInOnePublisher = new MyNumberAndMixedEventPublisher(numberBus, mixedBus);
        MyNumberAndMixedEventSubscriber twoInOneSubscriber = new MyNumberAndMixedEventSubscriber();

        // act
        numberBus.subscribe(MyNumberEvent.class, twoInOneSubscriber, START_READING_FROM_NOW);

        MyNumberEvent lastNumberEvent = null;
        for (int counter=0; counter<numEventsToPublish; counter++) {
            lastNumberEvent = new MyNumberEvent(random.nextInt(), random.nextInt());
            twoInOnePublisher.post(MyNumberEvent.class, lastNumberEvent);
        }

        // assert
        assertThat(twoInOneSubscriber.lastReceivedNumberEvent).isEqualTo(lastNumberEvent);
        assertThat(twoInOneSubscriber.lastReceivedMixedEvent).isNull();
        assertThat(twoInOneSubscriber.counterOfRetrievedEvents).isEqualTo(numEventsToPublish);
    }

    @SuppressWarnings({"RedundantCast", "unchecked", "rawtypes"})
    @Test
    void testTwoEventTypesWithTwoInOnePublisherAndOneExtraPublisherAndAThreeInOneSubscriber() {
        // arrange
        Random random = new Random();

        int numEventsToPublish = 100;

        MyNumberAndMixedEventPublisher numberAndMixedPublisher = new MyNumberAndMixedEventPublisher(numberBus, mixedBus);
        MyTextEventPublisher textPublisher = new MyTextEventPublisher(textBus);
        MyNumberAndTextAndMixedEventSubscriber threeInOneSubscriber = new MyNumberAndTextAndMixedEventSubscriber();

        // act
        numberBus.subscribe(MyNumberEvent.class, (EventSubscriber) threeInOneSubscriber, START_READING_FROM_NOW);
          textBus.subscribe(  MyTextEvent.class, (EventSubscriber) threeInOneSubscriber, START_READING_FROM_NOW);
         mixedBus.subscribe( MyMixedEvent.class, (EventSubscriber) threeInOneSubscriber, START_READING_FROM_NOW);

        MyNumberEvent lastNumberEvent = null;
        MyTextEvent     lastTextEvent = null;
        MyMixedEvent   lastMixedEvent = null;
        for (int counter=0; counter<numEventsToPublish; counter++) {
            lastNumberEvent = new MyNumberEvent(random.nextInt(), random.nextInt());
            numberAndMixedPublisher.post(MyNumberEvent.class, lastNumberEvent);

            lastMixedEvent = new MyMixedEvent(""+random.nextInt(), random.nextInt());
            numberAndMixedPublisher.post2(MyMixedEvent.class, lastMixedEvent);

            lastTextEvent = new MyTextEvent(""+random.nextInt(), ""+random.nextInt());
            textPublisher.post(MyTextEvent.class, lastTextEvent);
        }

        // assert
        assertThat(threeInOneSubscriber.lastReceivedNumberEvent).isEqualTo(lastNumberEvent);
        assertThat(threeInOneSubscriber.lastReceivedTextEvent).isEqualTo(lastTextEvent);
        assertThat(threeInOneSubscriber.lastReceivedMixedEvent).isEqualTo(lastMixedEvent);
        assertThat(threeInOneSubscriber.counterOfRetrievedEvents).isEqualTo(numEventsToPublish * 3);
    }

    @SuppressWarnings({"unchecked", "rawtypes"})
    @Test
    void testNineEventTypesWithAllInOnePublishersAndAllInOneSubscriber() {
        // arrange, 1

        class E1 extends AbstractEvent implements Event {
            @Override
            public Version version() {
                return Versionable.initialVersion();
            }
        }
        class E2 extends AbstractEvent implements Event {
            @Override
            public Version version() {
                return Versionable.initialVersion();
            }
        }
        class E3 extends AbstractEvent implements Event {
            @Override
            public Version version() {
                return Versionable.initialVersion();
            }
        }
        class E4 extends AbstractEvent implements Event {
            @Override
            public Version version() {
                return Versionable.initialVersion();
            }
        }
        class E5 extends AbstractEvent implements Event {
            @Override
            public Version version() {
                return Versionable.initialVersion();
            }
        }
        class E6 extends AbstractEvent implements Event {
            @Override
            public Version version() {
                return Versionable.initialVersion();
            }
        }
        class E7 extends AbstractEvent implements Event {
            @Override
            public Version version() {
                return Versionable.initialVersion();
            }
        }
        class E8 extends AbstractEvent implements Event {
            @Override
            public Version version() {
                return Versionable.initialVersion();
            }
        }
        class E9 extends AbstractEvent implements Event {
            @Override
            public Version version() {
                return Versionable.initialVersion();
            }
        }

        EventBus<E1> E1bus = factory.createOrGetEventBusFor(E1.class);
        EventBus<E2> E2bus = factory.createOrGetEventBusFor(E2.class);
        EventBus<E3> E3bus = factory.createOrGetEventBusFor(E3.class);
        EventBus<E4> E4bus = factory.createOrGetEventBusFor(E4.class);
        EventBus<E5> E5bus = factory.createOrGetEventBusFor(E5.class);
        EventBus<E6> E6bus = factory.createOrGetEventBusFor(E6.class);
        EventBus<E7> E7bus = factory.createOrGetEventBusFor(E7.class);
        EventBus<E8> E8bus = factory.createOrGetEventBusFor(E8.class);
        EventBus<E9> E9bus = factory.createOrGetEventBusFor(E9.class);

        class Publisher9 implements EventPublisher.EventPublisher9<E1,E2,E3,E4,E5,E6,E7,E8,E9> {
            @Override
            public String getName() {
                return Publisher9.class.getName();
            }

            @Override
            public void post (final Class<E1> eventType, final E1 event) { E1bus.publish(eventType, event); }
            @Override
            public void post2(final Class<E2> eventType, final E2 event) { E2bus.publish(eventType, event); }
            @Override
            public void post3(final Class<E3> eventType, final E3 event) { E3bus.publish(eventType, event); }
            @Override
            public void post4(final Class<E4> eventType, final E4 event) { E4bus.publish(eventType, event); }
            @Override
            public void post5(final Class<E5> eventType, final E5 event) { E5bus.publish(eventType, event); }
            @Override
            public void post6(final Class<E6> eventType, final E6 event) { E6bus.publish(eventType, event); }
            @Override
            public void post7(final Class<E7> eventType, final E7 event) { E7bus.publish(eventType, event); }
            @Override
            public void post8(final Class<E8> eventType, final E8 event) { E8bus.publish(eventType, event); }
            @Override
            public void post9(final Class<E9> eventType, final E9 event) { E9bus.publish(eventType, event); }
        }

        class Subscriber9 implements EventSubscriber.EventSubscriber9<E1,E2,E3,E4,E5,E6,E7,E8,E9> {
            final Map<Class<?>,Integer> event2CounterMap = new HashMap<>();

            private void incEventCounter(Class<?> eventType) {
                Integer counter = event2CounterMap.get(eventType);
                if (event2CounterMap.get(eventType) == null) {
                    event2CounterMap.put(eventType, 1);
                } else {
                    event2CounterMap.put(eventType, counter+1);
                }
            }

            @Override
            public String getName() {
                return Subscriber9.class.getName();
            }

            @Override
            public String getGroupName() {
                return InMemorySyncEventBusAndSpyHandlingMultipleEventTypesTest.class.getPackageName();
            }

            @Override
            public void receive (final E1 event) { incEventCounter(E1.class); }
            @Override
            public void receive2(final E2 event) { incEventCounter(E2.class); }
            @Override
            public void receive3(final E3 event) { incEventCounter(E3.class); }
            @Override
            public void receive4(final E4 event) { incEventCounter(E4.class); }
            @Override
            public void receive5(final E5 event) { incEventCounter(E5.class); }
            @Override
            public void receive6(final E6 event) { incEventCounter(E6.class); }
            @Override
            public void receive7(final E7 event) { incEventCounter(E7.class); }
            @Override
            public void receive8(final E8 event) { incEventCounter(E8.class); }
            @Override
            public void receive9(final E9 event) { incEventCounter(E9.class); }
        }

        // ----------------------------------------------------------------------------------------------------------

        // arrange, 2

        Publisher9   publisher9 = new Publisher9();
        Subscriber9 subscriber9 = new Subscriber9();

        E1bus.subscribe(E1.class,                   subscriber9, START_READING_FROM_NOW);
        E2bus.subscribe(E2.class, (EventSubscriber) subscriber9, START_READING_FROM_NOW);
        E3bus.subscribe(E3.class, (EventSubscriber) subscriber9, START_READING_FROM_NOW);
        E4bus.subscribe(E4.class, (EventSubscriber) subscriber9, START_READING_FROM_NOW);
        E5bus.subscribe(E5.class, (EventSubscriber) subscriber9, START_READING_FROM_NOW);
        E6bus.subscribe(E6.class, (EventSubscriber) subscriber9, START_READING_FROM_NOW);
        E7bus.subscribe(E7.class, (EventSubscriber) subscriber9, START_READING_FROM_NOW);
        E8bus.subscribe(E8.class, (EventSubscriber) subscriber9, START_READING_FROM_NOW);
        E9bus.subscribe(E9.class, (EventSubscriber) subscriber9, START_READING_FROM_NOW);

        // act
        int numEventsToPublish = 100;
        for (int counter=0; counter<numEventsToPublish; counter++) {
            publisher9.post (E1.class, new E1());
            publisher9.post2(E2.class, new E2());
            publisher9.post3(E3.class, new E3());
            publisher9.post4(E4.class, new E4());
            publisher9.post5(E5.class, new E5());
            publisher9.post6(E6.class, new E6());
            publisher9.post7(E7.class, new E7());
            publisher9.post8(E8.class, new E8());
            publisher9.post9(E9.class, new E9());
        }

        // assert
        Set<Class<?>> classes = subscriber9.event2CounterMap.keySet();
        classes.forEach(eventClass -> {
            assertThat(subscriber9.event2CounterMap.get(eventClass)).isEqualTo(numEventsToPublish);
        });
    }
}
