/*
 * @author Ranides Atterwim {@literal <ranides@gmail.com>}
 * @copyright Ranides Atterwim
 * @license WTFPL
 * @url http://ranides.net/projects/assira
 */
package net.ranides.assira.collection.maps;

import java.time.Duration;

import net.ranides.assira.system.RuntimeUtils;
import org.junit.Assume;
import org.junit.Test;

import static net.ranides.assira.junit.NewAssert.*;

/**
 *
 * @author Ranides Atterwim {@literal <ranides@gmail.com>}
 */
public class CacheMapTest {

    private static final class Text {

        public final String value;

        public Text(String value) {
            this.value = value;
        }

        public Text(int value) {
            this.value = "DEF-" + value;
        }

        @Override
        public String toString() {
            return value;
        }
    }

    private CacheMap<Integer, Text> fill(CacheMap<Integer, Text> cache) {
        for(int i=0; i<24; i++) {
            cache.get(i);
        }
        for(int i=24; i<48; i++) {
            cache.get(i, v -> new Text("INT-"+v));
        }
        return cache;
    }

    @Test
    public void testBasic() {
        testBasic(CacheMap.getInstance(Text::new));
        testBasic(CacheMap.getInstance(128, Text::new));
        testBasic(CacheMap.getInstance(Duration.ofMillis(1000), Text::new));
    }

    private void testBasic(CacheMap<Integer, Text> cache) {
        fill(cache);

        Text a = cache.get(15, () -> new Text("C15"));
        Text b = cache.get(70, () -> new Text("C70"));

        assertEquals("DEF-15", a.value);
        assertSame(a, cache.get(15));
        assertEquals("C70", b.value);
        assertSame(b, cache.get(70, () -> new Text("C70!")));

        a = null;
        b = null;

        RuntimeUtils.forceGC();

        Text c = cache.get(15, () -> new Text("C15"));
        Text d = cache.get(70, () -> new Text("C70"));

        Assume.assumeTrue("Unexpected GC behaviour", "C15".equals(c.value));
        Assume.assumeTrue("Unexpected GC behaviour", c == cache.get(15));
        Assume.assumeTrue("Unexpected GC behaviour", c == cache.get(15));
        Assume.assumeTrue("Unexpected GC behaviour", "C70".equals(d.value));
        Assume.assumeTrue("Unexpected GC behaviour", d == cache.get(70, () -> new Text("C70!")));

        cache.clear();

        Text e = cache.get(15, () -> new Text("R15"));
        Text f = cache.get(70, () -> new Text("R70"));
        assertEquals("R15", e.value);
        assertEquals("R70", f.value);
    }
//
//    @Test
//    public void testContainsKey() {
//        Cache<Integer, String> cache = createPMap();
//
//        assertFalse( cache.containsKey(5) );
//        assertFalse( cache.containsKey(12) );
//        assertFalse( cache.containsKey(15) );
//
//        assertTrue( cache.containsKey(16) );
//        assertTrue( cache.containsKey(30) );
//        assertTrue( cache.containsKey(47) );
//    }
//
//    @Test
//    public void testGet() {
//        Cache<Integer, String> cache = createPMap();
//        assertNull( cache.get(5) );
//        assertNull( cache.get(12) );
//        assertNull( cache.get(15) );
//
//        assertEquals("BNGV-16", cache.get(16));
//        assertEquals("BNGV-30", cache.get(30));
//        assertEquals("BNGV-47", cache.get(47));
//    }
//
//    @Test
//    public void testRemove() {
//        Cache<Integer, String> cache = createPMap();
//        assertTrue( cache.containsKey(32) );
//        cache.remove(32);
//        assertFalse( cache.containsKey(32) );
//        cache.remove(3);
//        cache.remove(300);
//        assertFalse( cache.containsKey(3) );
//        assertFalse( cache.containsKey(300) );
//    }
//
//    @Test
//    public void testClear() {
//        Cache<Integer, String> cache = createPMap();
//        cache.clear();
//        assertEquals(0, cache.size());
//        assertFalse( cache.containsKey(16) );
//        assertFalse( cache.containsKey(30) );
//        assertFalse( cache.containsKey(47) );
//    }
//
//    @Test
//    public void testSize() {
//        Cache<Integer, String> cache = createPMap();
//        assertEquals(32, cache.size());
//    }
//
//    @Test
//    public void testEntrySet() {
//        Cache<Integer, String> cache = createPMap();
//
//        Set<Map.Entry<Integer,String>> entries = cache.entrySet();
//        assertEquals(cache.size(), entries.size());
//
//        Map<Integer, String> copy1 = new java.util.OpenMap<>();
//        for(Map.Entry<Integer,String> entry : entries) {
//            copy1.put(entry.getKey(), entry.getValue());
//        }
//
//        assertEquals(cache, copy1);
//
//        Map<Integer, String> copy2 = new java.util.OpenMap<>();
//        Iterator<Map.Entry<Integer,String>> iterator = entries.iterator();
//        int i = 0;
//        while(iterator.hasNext()) {
//            Map.Entry<Integer,String> entry = iterator.next();
//            copy2.put(entry.getKey(), entry.getValue());
//            if( i++%2 == 0 ) {
//                iterator.remove();
//            }
//        }
//
//        assertEquals(copy1.size() / 2, cache.size());
//        assertEquals(copy1, copy2);
//    }
//
//    @Test
//    public void testKeySet() {
//        Cache<Integer, String> cache = createPMap();
//
//        Set<Integer> keys = cache.keySet();
//        assertEquals(cache.size(), keys.size());
//
//        Set<Integer> keys1 = new OpenSet<>(keys);
//        Map<Integer, String> copy1 = new java.util.OpenMap<>();
//        for(Integer key : keys1) {
//            copy1.put(key, cache.get(key));
//        }
//
//        assertEquals(cache, copy1);
//
//        Iterator<Integer> iterator = keys.iterator();
//        int i = 0;
//        while(iterator.hasNext()) {
//            iterator.next();
//            if( i++%2 == 0 ) {
//                iterator.remove();
//            }
//        }
//
//        assertEquals(copy1.size() / 2, cache.size());
//    }
//
//    @Test
//    public void testValues() {
//        Cache<Integer, String> cache = createPMap();
//
//        Collection<String> values = cache.values();
//        assertEquals(cache.size(), values.size());
//
//        Map<Integer, String> copy1 = new LinkedHashMap<>(cache);
//        List<String> values1 = new ArrayList<>(values);
//
//        assertEquals(cache.size(), copy1.size());
//
//        // kolejność nie musi być zachowana
//        assertTrue( CollectionUtils.equivalent(copy1.values(), values) );
//        // kolejność nie musi być zachowana
//        assertTrue( CollectionUtils.equivalent(values1, values) );
//
//        Iterator<String> iterator = values.iterator();
//        int i = 0;
//        while(iterator.hasNext()) {
//            iterator.next();
//            if( i++%2 == 0 ) {
//                iterator.remove();
//            }
//        }
//
//        assertEquals(copy1.size() / 2, cache.size());
//    }
//
//    @Test
//    public void testRelease() {
//        // we created that cache to check as much things as possible in one method
//        // we don't want to run "forceGC" more than once, because its very costly
//        Cache<Integer, String> cache0 = new Cache<>(32);
//		Random rand = new Random(777);
//
//        Cache<Integer, String> cache = new Cache<>(128, 16);
//        for(int i=0; i<256; i++) {
//            cache0.put(i, text(rand, 32));
//            cache.put(i, text(rand, 32));
//        }
//
//        assertEquals(32, cache0.hold());
//
//        // we should have only last 128 elements
//        assertEquals(128, cache.size());
//        // we should not hold anything (we didn't make any access till now)
//        assertEquals(0, cache.holdsize());
//
//        assertFalse( cache.containsKey(12) );
//        assertTrue( cache.containsKey(160) );
//        // we should hold 1 accessed element (i.e. 160)
//        assertEquals(1, cache.holdsize());
//
//        // we should hold as many as possible
//        assertEquals(16, cache.hold());
//        assertEquals(true, cache.hold(255)); // it will create "duplicate in holdlist"
//        assertEquals(false, cache.hold(17));
//        assertEquals(16, cache.holdsize());
//        assertEquals(128, cache.size());
//
//        // 512 KB is reasonable minimum of unreachable memory
//        // all soft references should stay
//        assertTrue( RuntimeUtils.performGC() > 512*1024);
//
//        assertEquals(128, cache.size());
//        assertEquals(16, cache.holdsize());
//        assertTrue(cache.containsKey(160)); // it will create "duplicate in holdlist"
//        assertTrue(cache.containsKey(252)); // it will create "duplicate in holdlist"
//        assertEquals(16, cache.holdsize());
//
//        // 512 KB is reasonable minimum of unreachable memory
//        // all soft references should go away
//        assertTrue( RuntimeUtils.forceGC() > 512*1024 );
//
//        // test clean-up code in "get".
//        // we can't invoke almost any method after GC, because it will run
//        // clean-up code in #relase method
//        // we can't invoke #contains on existing key, because it will create
//        // "duplicate in holdlist"
//        assertFalse(cache.containsKey(161));
//        assertFalse(cache.containsKey(240));
//
//        // we have 3 duplicates in holdlist
//        assertTrue(cache.size() < 128);
//        Assume.assumeTrue(13 == cache.size());
//        assertEquals(13, cache.size());
//        assertEquals(16, cache.holdsize());
//
//        assertTrue(cache.containsKey(160));
//        assertTrue(cache.containsKey(252));
//
//        // we dont have any duplicates
//        assertEquals(32, cache0.size());
//        assertEquals(32, cache0.holdsize());
//    }
//
//    @Test
//    public void testPass() {
//        Cache<Integer, String> cache = new Cache<>();
//        assertSame("A", cache.pass(17, "A"));
//        assertSame("B", cache.pass(17, "B"));
//        assertSame("C", cache.pass(17, "C"));
//        assertEquals(1, cache.size());
//    }
//
//    @Test
//    public void testConstruct() {
//        assertThrows(IllegalArgumentException.class, ()->{
//            new Cache<>(32,48);
//        });
//    }
//
//    @Category(JCategories.Slow.class)
//    @Test
//    public void testTTL() throws Exception {
//        Cache<Integer, String> cache = new Cache<>(32, Duration.ofMillis(1000));
//        for(int i=0; i<48; i++) {
//            cache.put(i, "BNGV-"+i);
//        }
//
//        Thread.sleep(400);
//
//        cache.put(102, "A");
//        cache.put(104, "B");
//        cache.put(108, "C");
//        Thread.sleep(400);
//
//        assertEquals(32, cache.entrySet().size() );
//        assertEquals(null, cache.get(1));
//        assertEquals("BNGV-31", cache.get(31));
//        assertEquals("A", cache.get(102));
//        assertEquals("B", cache.get(104));
//
//        cache.put(103, "D");
//        cache.put(105, "E");
//        cache.put(109, "F");
//        Thread.sleep(400);
//
//        assertEquals(null, cache.get(31));
//        assertEquals("A", cache.get(102));
//        assertEquals("D", cache.get(103));
//
//        Thread.sleep(400);
//        assertEquals(null, cache.get(31));
//        assertEquals(null, cache.get(102));
//        assertEquals("D", cache.get(103));
//
//    }
//
//    private static String text(Random rand, int size) {
//		StrBuilder sb = new StrBuilder(size);
//		for(int i=0; i<size; i++) {
//			sb.append( (char)(' '+rand.nextInt(128)) );
//		}
//		return sb.toString();
//	}
    
	
}
