package net.ranides.assira.observable;

import net.ranides.assira.collection.prototype.PrototypeMap;
import net.ranides.assira.events.EventDispatcher;
import net.ranides.assira.events.EventRouter;
import net.ranides.test.ContractTesters;
import net.ranides.test.data.TMap;
import net.ranides.test.data.TMaps;
import org.junit.Before;
import org.junit.Test;

import java.util.*;
import java.util.regex.Pattern;

import static org.junit.Assert.*;

public class ObservablePrototypeMapTest {

    private final TMap<Object, Object> $map = TMaps.MAP_SI;

    List<MapEvent<Object, Object>> events;
    EventRouter router;
    PrototypeMap map1;
    PrototypeMap map2;
    PrototypeMap map3;
    PrototypeMap map4;

    @Before
    public void init() {
        events = new ArrayList<>();
        router = EventDispatcher.newInstance();
        router.addEventListener(MapEvent.class, v -> events.add(v));

        map1 = new ObservablePrototypeMap(router);
        map2 = new ObservablePrototypeMap(router, map1);
        map3 = new ObservablePrototypeMap(router, map2);
        map4 = new ObservablePrototypeMap(router, map3);

        map1.getDeclaredMap().put("a", 11);
        map1.getDeclaredMap().put("b", 12);
        map2.getDeclaredMap().put("c", 13);
        map2.getDeclaredMap().put("d", 14);
        map3.getDeclaredMap().put("e", 15);
        map3.getDeclaredMap().put("f", 16);
    }

    @Test
    public void testSuite() {
        ContractTesters.runner()
            .param("map!", $map)
            .ignore(Pattern.compile(".*_HC"))
            .ignore("MapTester.basicSerializable")
            .function(new int[0], array -> $map.list(array).into(new ObservablePrototypeMap(EventDispatcher.newInstance())))
            .run();
    }

    @Test
    public void basic() {
        assertEquals(12, map1.get("b"));
        assertEquals(12, map2.get("b"));
        assertEquals(12, map3.get("b"));
        assertEquals(12, map4.get("b"));

        assertEquals(16, map3.get("f"));
        assertEquals(16, map4.get("f"));

        assertTrue(map1.containsKey("b"));
        assertTrue(map4.containsKey("b"));

        assertTrue(map3.containsKey("f"));
        assertTrue(map4.containsKey("f"));

        assertTrue(map1.getDeclaredMap().containsKey("b"));
        assertFalse(map4.getDeclaredMap().containsKey("b"));

        assertTrue(map3.containsKey("f"));
        assertFalse(map4.getDeclaredMap().containsKey("f"));

        long declared = events.stream().map(event -> System.identityHashCode(event.map())).distinct().count();
        assertEquals(3, declared);
        assertEquals(6, events.size());
    }

    @Test
    public void entrySet_remove() {
        List<Map.Entry<Object, Object>> entries = new ArrayList<>(map1.entrySet());

        Iterator<Map.Entry<Object, Object>> a = map1.entrySet().iterator();
        a.next();
        a.remove();

        for (Map.Entry<Object, Object> entry : entries) {
            assertNotNull(entry.getKey());
            assertNotNull(entry.getValue());
        }

        assertEquals(1, map1.size());
        assertEquals(7, events.size());

        a.next();
        a.remove();

        assertEquals(0, map1.size());
        assertEquals(8, events.size());
    }

}