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

import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import net.ranides.assira.collection.CollectionUtils;
import net.ranides.assira.math.Randomizer;
import net.ranides.assira.system.RuntimeUtils;
import net.ranides.assira.trace.LoggerUtils;
import org.apache.log4j.Level;
import static org.junit.Assert.*;
import org.junit.Test;

/**
 *
 * @author ranides
 */
public class CacheTest {
    
    public CacheTest() {
        LoggerUtils.resetLogger4j(Level.WARN);
    }
    
    public Cache<Integer, String> createPMap() {
        // tworzymy cache, który chroni wszystkie swoje elementy przed GC
        Cache<Integer, String> cache = new Cache<>(32);
        for(int i=0; i<48; i++) {
            cache.put(i, "BNGV-"+i);
        }
        return cache;
    }

    @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<Entry<Integer,String>> entries = cache.entrySet();
        assertEquals(cache.size(), entries.size());
        
        Map<Integer, String> copy1 = new HashMap<>();
        for(Entry<Integer,String> entry : entries) {
            copy1.put(entry.getKey(), entry.getValue());
        }
        
        assertEquals(cache, copy1);
        
        Map<Integer, String> copy2 = new HashMap<>();
        Iterator<Entry<Integer,String>> iterator = entries.iterator();
        int i = 0;
        while(iterator.hasNext()) {
            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 HashSet<>(keys);
        Map<Integer, String> copy1 = new HashMap<>();
        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.equalContent(copy1.values(), values) );
        // kolejność nie musi być zachowana
        assertTrue( CollectionUtils.equalContent(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);
        
        Cache<Integer, String> cache = new Cache<>(128, 16);
        for(int i=0; i<256; i++) {
            cache0.put(i, Randomizer.text(32));
            cache.put(i, Randomizer.text(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
        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() {
        try {
            Cache<Integer, String> cache3 = new Cache<>(32,48);
            fail("IllegalArgumentException expected");
        } catch(IllegalArgumentException cause) {
            assertTrue(true);
        }
    }
    
    
    
}
