/*
 *      Copyright (C) 2012, 2016  higherfrequencytrading.com
 *      Copyright (C) 2016 Roman Leventov
 *
 *      This program is free software: you can redistribute it and/or modify
 *      it under the terms of the GNU Lesser General Public License as published by
 *      the Free Software Foundation, either version 3 of the License.
 *
 *      This program is distributed in the hope that it will be useful,
 *      but WITHOUT ANY WARRANTY; without even the implied warranty of
 *      MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *      GNU Lesser General Public License for more details.
 *
 *      You should have received a copy of the GNU Lesser General Public License
 *      along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

package net.openhft.chronicle.map;

import net.openhft.chronicle.bytes.NoBytesStore;
import net.openhft.chronicle.core.OS;
import net.openhft.chronicle.core.values.IntValue;
import net.openhft.chronicle.values.Values;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TemporaryFolder;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;

import java.io.IOException;
import java.lang.ref.WeakReference;
import java.util.Arrays;
import java.util.Collection;

import static org.junit.Assert.assertEquals;

@RunWith(Parameterized.class)
public class MemoryResourcesTest {

    @Parameterized.Parameters
    public static Collection<Object[]> data() {
        return Arrays.asList(new Object[][] {
                { false, false }, { false, true }, { true, false }, { true, true }
        });
    }

    @Rule
    public TemporaryFolder folder = new TemporaryFolder();

    private boolean replicated;
    private boolean persisted;

    public MemoryResourcesTest(boolean replicated, boolean persisted) {
        this.replicated = replicated;
        this.persisted = persisted;
    }

    @Before
    public void initNoBytesStore() {
        Assert.assertNotEquals(0, NoBytesStore.NO_PAGE);
    }

    @Test(timeout = 60_000)
    public void testChronicleMapCollectedAndDirectMemoryReleased() throws IOException {
        long nativeMemoryUsedBeforeMap = nativeMemoryUsed();
        WeakReference<ChronicleMap<IntValue, String>> ref = new WeakReference<>(getMap());
        Assert.assertNotNull(ref.get());
        long expectedNativeMemory = nativeMemoryUsedBeforeMap + ref.get().offHeapMemoryUsed();
        assertEquals(expectedNativeMemory, nativeMemoryUsed());
        // Wait until Map is collected by GC
        while (ref.get() != null) {
            System.gc();
            Thread.yield();
        }
        // Wait until Cleaner is called and memory is returned to the system
        while (nativeMemoryUsedBeforeMap != nativeMemoryUsed()) {
            System.gc();
            byte[] garbage = new byte[1_000_000];
            Thread.yield();
        }
    }

    private long nativeMemoryUsed() {
        if (persisted) {
            return OS.memoryMapped();
        } else {
            return OS.memory().nativeMemoryUsed();
        }
    }

    @Test
    public void testExplicitChronicleMapCloseReleasesMemory() throws IOException {
        long nativeMemoryUsedBeforeMap = nativeMemoryUsed();
        try (ChronicleMap<IntValue, String> map = getMap()) {
            long expectedNativeMemory = nativeMemoryUsedBeforeMap + map.offHeapMemoryUsed();
            assertEquals(expectedNativeMemory, nativeMemoryUsed());
        }
        assertEquals(nativeMemoryUsedBeforeMap, nativeMemoryUsed());
    }

    private ChronicleMap<IntValue, String> getMap() throws IOException {
        ChronicleMapBuilder<IntValue, String> builder = ChronicleMap
                .of(IntValue.class, String.class);
        if (replicated)
            builder.replication((byte) 1);
        builder.entries(1).averageValueSize(1);
        VanillaChronicleMap<IntValue, String, ?> map;
        if (persisted) {
            map = (VanillaChronicleMap<IntValue, String, ?>)
                    builder.createPersistedTo(folder.newFile());
        } else {
            map = (VanillaChronicleMap<IntValue, String, ?>) builder.create();
        }
        IntValue key = Values.newHeapInstance(IntValue.class);
        int i = 0;
        while (!map.hasExtraTierBulks()) {
            key.setValue(i++);
            map.put(key, "string" + i);
        }
        return map;
    }
}

@@ -2075,26 +2075,8 @@
 Test
-(timeout = 60_000)
 %0A   
@@ -2138,26 +2138,60 @@
 leased()
- throws IO
+%0A            throws IOException, Interrupted
 Exceptio
@@ -2775,23 +2775,32 @@
         
-while (
+for (int i = 0; 
 nativeMe
@@ -2838,16 +2838,39 @@
 ryUsed()
+ && i %3C 60_000_000; i++
 ) %7B%0A    
@@ -2951,38 +2951,39 @@
          Thread.
-yield(
+sleep(1
 );%0A        %7D%0A   
@@ -2971,32 +2971,108 @@
 p(1);%0A        %7D%0A
+        Assert.assertEquals(nativeMemoryUsedBeforeMap, nativeMemoryUsed());%0A
     %7D%0A%0A    priva

Fix testChronicleMapCollectedAndDirectMemoryReleased()