package eta.runtime.io;

import java.util.TreeMap;
import java.util.Map;
import java.nio.ByteBuffer;
import java.lang.ref.WeakReference;

import static eta.runtime.RtsMessages.barf;

public class MemoryManager {
    private static int nextAddress = 0;

    private static TreeMap<Integer, WeakReference<ByteBuffer>> addressMap =
        new TreeMap<Integer, WeakReference<ByteBuffer>>();

    public static synchronized ByteBuffer allocateBuffer(int n, boolean direct) {
        ByteBuffer buf = allocateAnonymousBuffer(n, direct);
        buf.putInt(nextAddress);
        addressMap.put(nextAddress, new WeakReference<ByteBuffer>(buf));
        nextAddress += n;
        return buf;
    }

    /* Use if you want to allocate a buffer that won't be using the getAddress
       method and you don't want to pollute the addressMap. */
    public static synchronized ByteBuffer allocateAnonymousBuffer(int n, boolean direct) {
        if (direct) {
            return ByteBuffer.allocateDirect(n + 4); // Off-heap memory
        } else {
            return ByteBuffer.allocate(n + 4); // Heap memory
        }
    }

    public static ByteBuffer getBuffer(int address) {
        Map.Entry<Integer, WeakReference<ByteBuffer>>
            entry = addressMap.floorEntry(address);
        /* The 4 is the offset of the address slot
           in the ByteBuffer. */
        if (entry != null) {
            WeakReference<ByteBuffer> ref = entry.getValue();
            ByteBuffer buf = ref.get();
            if (buf != null) {
                int pos = address - entry.getKey() + 4;
                return (ByteBuffer) buf.duplicate().position(pos);
            }
        }
        barf("Invalid address!");
        return null;
    }

    public static int getAddress(ByteBuffer buf) {
        if (buf == null) {
            return -1;
        } else {
            return buf.getInt(0) + getPosition(buf);
        }
    }

    public static int getPosition(ByteBuffer buf) {
        return buf.position() - 4;
    }

    private static int realPosition(int pos) {
        return pos + 4;
    }

    public static int bufGetInt(ByteBuffer buf, int pos) {
        return buf.getInt(realPosition(pos));
    }

    public static void bufPutInt(ByteBuffer buf, int pos, int value) {
        buf.putInt(realPosition(pos), value);
    }

    public static ByteBuffer bufSetPosition(ByteBuffer buf, int pos) {
        return (ByteBuffer) buf.position(realPosition(pos));
    }

    public static ByteBuffer bufSetOffset(ByteBuffer buf, int offset) {
        return (ByteBuffer) buf.position(buf.position() + offset);
    }

    public static ByteBuffer bufSetLimit(ByteBuffer buf, int limit) {
        return (ByteBuffer) buf.limit(buf.position() + limit);
    }
}

@@ -382,16 +382,190 @@
 r%3E%3E();%0A%0A
+    /* Special buffer to represent null pointer in eta. Only contains%0A       the address -1 */%0A%0A    public static final ByteBuffer nullAddress = allocateBuffer(0,-1,false);%0A%0A
     publ
@@ -667,33 +667,24 @@
 f = allocate
-Anonymous
 Buffer(n, di
@@ -685,36 +685,8 @@
 (n, 
-direct);%0A        buf.putInt(
 next
@@ -688,24 +688,32 @@
  nextAddress
+, direct
 );%0A        a
@@ -825,24 +825,236 @@
 buf;%0A    %7D%0A%0A
+    public static synchronized ByteBuffer allocateBuffer(int n, int address, boolean direct) %7B%0A        ByteBuffer buf = allocateAnonymousBuffer(n, direct);%0A        buf.putInt(adddress);%0A        return buf;%0A    %7D%0A
     /* Use i
@@ -2134,79 +2134,8 @@
 ) %7B%0A
-        if (buf == null) %7B%0A            return -1;%0A        %7D else %7B%0A    
     
@@ -2179,26 +2179,16 @@
 n(buf);%0A
-        %7D%0A
     %7D%0A%0A 

create constant to represent null pointer address