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

import java.util.LinkedList;
import java.util.List;
import net.ranides.assira.collection.ArrayUtils;
import net.ranides.assira.collection.SetUtils;
import net.ranides.assira.collection.map.LazyMap;
import net.ranides.assira.time.TimeUtils;
import net.ranides.assira.trace.LoggerUtils;
import static org.junit.Assert.*;
import org.junit.Test;


/**
 *
 * @author ranides
 */
@SuppressWarnings("PMD.AvoidDuplicateLiterals")
public class EventProactorTest {

    public EventProactorTest() {
        LoggerUtils.initTest();
    }

    private static class HitMap extends LazyMap<String, List<Integer>>  {
        private int counter = 0;

        @Override
        public List<Integer> apply(String source) {
            return new LinkedList<>();
        }

        public class HitListener<T extends Event> implements EventListener<T> {

            private final String name;

            public HitListener(String name) {
                this.name = name;
            }

            @Override
            public void handleEvent(T event) {
                // "handleEvent" can be invoked by many threads - have to be thread-safe
                synchronized(HitMap.this) {
                    get(name).add(counter++);
                }
            }
        }

    };

    @Test
    public void testPropagation() {
        HitMap hits = new HitMap();
        EventProactor unknown = EventProactor.newInstance("unknown", 2);
        EventProactor main = EventProactor.newInstance("name", 10);

        main.addEventListener(Event.class, hits.new HitListener<Event>("global") );
        main.addEventListener(IOEvent.class, hits.new HitListener<IOEvent>("io") );
        main.addEventListener(ReadEvent.class, hits.new HitListener<ReadEvent>("read") );
        main.addEventListener(WriteEvent.class, hits.new HitListener<WriteEvent>("write") );
        main.addEventListener(FloodEvent.class, hits.new HitListener<FloodEvent>("flood") );
        main.addEventListener(CoreEvent.Shutdown.class, hits.new HitListener<CoreEvent.Shutdown>("shutdown") );
        main.addEventListener(CoreEvent.Stop.class, hits.new HitListener<CoreEvent.Stop>("exit") );

        // we consume thread, but not lock handler
        TimeUtils.sleep(25);


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

        for(int i=0; i<100; i++) {
            main.signalEvent(new FloodEvent());
        }

        main.signalEvent(CoreEvent.stop(unknown));
        main.signalEvent(CoreEvent.stop(main) );

        main.stop();

        assertEquals(7, hits.size());
        
        assertEquals(108, hits.get("global").size());
        assertEquals(100, hits.get("flood").size());
        assertEquals(2, hits.get("io").size());
        assertEquals(1, hits.get("write").size());
        assertEquals(1, hits.get("read").size());
        assertEquals(3, hits.get("exit").size());
        assertEquals(1, hits.get("shutdown").size());

        unknown.dispose();
    }

    @Test
    public void testStop() {
        HitMap hits = new HitMap();
        EventReactor unknown = EventReactor.newInstance("unknown", 32, 1000);
        EventReactor main = EventReactor.newInstance("name", 32, 1000);

        main.addEventListener(Event.class, hits.new HitListener<Event>("global") );
        main.addEventListener(CoreEvent.Shutdown.class, hits.new HitListener<CoreEvent.Shutdown>("shutdown") );
        main.addEventListener(CoreEvent.Stop.class, hits.new HitListener<CoreEvent.Stop>("exit") );

        main.signalEvent(CoreEvent.stop(unknown) );
        main.stop();

        assertEquals(SetUtils.asHashSet("global", "exit", "shutdown"), hits.keySet());
        assertEquals(ArrayUtils.asList(0,2,4,6), hits.get("global"));
        assertEquals(ArrayUtils.asList(1,3), hits.get("exit"));
        assertEquals(ArrayUtils.asList(5), hits.get("shutdown"));

        unknown.dispose();
    }

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

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

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

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