/*
 * @author Ranides Atterwim <ranides@gmail.com>
 * @copyright Ranides Atterwim
 * @license WTFPL
 * @url http://ranides.net/projects/assira
 */
package net.ranides.assira.events;

import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import net.ranides.assira.events.mockup.EventHitMap;
import net.ranides.assira.junit.NewAssert;
import net.ranides.assira.text.Wildcard;
import org.junit.Test;
import static org.junit.Assert.*;

public class EventDispatcherTest {
	

    @Test
    public void testPropagation() {
        EventHitMap hits = new EventHitMap();
        EventRouter main = new EventDispatcher();

        main.addEventListener(Event.class, hits.listener("global") );
        main.addEventListener(IOEvent.class, hits.listener("io") );
        main.addEventListener(ReadEvent.class, hits.listener("read") );
        main.addEventListener(WriteEvent.class, hits.listener("write") );
        main.addEventListener(Events.Failure.class, hits.listener("error") );
        main.addEventListener(BrokenEvent.class, event -> {
            throw new UnsupportedOperationException();
        });

        main.signalEvent(new WriteEvent());
        main.signalEvent(new ReadEvent());
        main.signalEvent(new Event(){});
        main.signalEvent(new BrokenEvent(){});

        assertEquals(Arrays.asList("error", "global", "io", "read", "write"), hits.names());

        assertEquals(Arrays.asList(9), hits.values("error"));
        assertEquals(Arrays.asList(0,3,6,7,8), hits.values("global"));
        assertEquals(Arrays.asList(1,4), hits.values("io"));
        assertEquals(Arrays.asList(2), hits.values("write"));
        assertEquals(Arrays.asList(5), hits.values("read"));

    }
    
    @Test
    public void testRemove() {
        EventHitMap hits = new EventHitMap();
        EventRouter main = new EventDispatcher();
        EventListener<Event> a = hits.listener("a");
        EventListener<Event> b = hits.listener("b");
        EventListener<Event> c = hits.listener("c");
        EventListener<Event> d = hits.listener("d");

        main.addEventListener(Event.class, a);
        main.addEventListener(Event.class, b);
        main.addEventListener(Event.class, c);
        main.addEventListener(Event.class, d);
                
        main.signalEvent(new Event(){});
        
        main.removeEventListener(Event.class, b);
        main.signalEvent(new Event(){});
        
        main.removeEventListener(Event.class, c);
        main.signalEvent(new Event(){});
        
        main.removeEventListener(Event.class, a);
        main.signalEvent(new Event(){});
        
        main.removeEventListener(Event.class, d);
        main.signalEvent(new Event(){});
        
        main.removeEventListener(Event.class, a);
        
        assertEquals(Arrays.asList("a", "b", "c", "d"), hits.names());
        assertEquals(Arrays.asList(0,4,7), hits.values("a"));
        assertEquals(Arrays.asList(1), hits.values("b"));
        assertEquals(Arrays.asList(2,5), hits.values("c"));
        assertEquals(Arrays.asList(3,6,8,9), hits.values("d"));
    }
    
    @Test
    public void testRemoveAll() {
        EventHitMap hits = new EventHitMap();
        EventRouter main = new EventDispatcher();
        EventListener<Event> a = hits.listener("a");
        EventListener<Event> b = hits.listener("b");
        EventListener<Event> c = hits.listener("c");

        main.addEventListener(Event.class, a);
        main.addEventListener(Event.class, b);
        main.addEventListener(Event.class, c);
                
        main.signalEvent(new Event(){});
        
        main.removeAllEventListeners();
        main.signalEvent(new Event(){});
        
        main.removeEventListener(Event.class, c);
        main.signalEvent(new Event(){});
        
        assertEquals(Arrays.asList("a", "b", "c"), hits.names());
        assertEquals(Arrays.asList(0), hits.values("a"));
        assertEquals(Arrays.asList(1), hits.values("b"));
        assertEquals(Arrays.asList(2), hits.values("c"));
    }
    
    @Test
    public void testGetEventListeners() {
        EventHitMap hits = new EventHitMap();
        EventRouter main = new EventDispatcher();
        EventListener<Event> a = hits.listener("a");
        EventListener<Event> b = hits.listener("b");

        main.addEventListener(Event.class, a);
        main.addEventListener(Event.class, b);
        main.addEventListener(IOEvent.class, a);
        main.addEventListener(ReadEvent.class, a);
        
                
        main.signalEvent(new ReadEvent(){});
        assertEquals(Arrays.asList("a", "b"), hits.names());
        assertEquals(Arrays.asList(0,2,3), hits.values("a"));
        assertEquals(Arrays.asList(1), hits.values("b"));
        
        Collection<EventBinding<?>> ev = main.getEventListeners();
        assertEquals(4, ev.size());
        assertEquals(4, main.getEventListenersCount());
        
        assertTrue( ev.contains(EventBinding.bind(Event.class, a)) );
        assertTrue( ev.contains(EventBinding.bind(IOEvent.class, a)) );
        assertTrue( ev.contains(EventBinding.bind(ReadEvent.class, a)) );
        assertTrue( ev.contains(EventBinding.bind(Event.class, b)) );
        
        List<String> expected = Arrays.asList(
            "{ event=Event listener=net.ranides.assira.events.mockup.EventHitMap$HitListener@* }",
            "{ event=Event listener=net.ranides.assira.events.mockup.EventHitMap$HitListener@* }",
            "{ event=IOEvent listener=net.ranides.assira.events.mockup.EventHitMap$HitListener@* }",
            "{ event=ReadEvent listener=net.ranides.assira.events.mockup.EventHitMap$HitListener@* }"
        );
        NewAssert.assertMatch(expected, main.getEventListeners(), (String ia, EventBinding<?> ib)->Wildcard.match(ia, ib.toString()));
        
        main.removeAllEventListeners();
        assertEquals(0, main.getEventListenersCount());
        assertEquals(Arrays.asList(), main.getEventListeners());
    }

    @SuppressWarnings("PMD")
    class IOEvent implements Event { }

    @SuppressWarnings("PMD")
    class ReadEvent extends IOEvent { }

    @SuppressWarnings("PMD")
    class WriteEvent extends IOEvent { }
    
    @SuppressWarnings("PMD")
    class BrokenEvent implements Event { }
	
}
