!include once #1027304 // Eclipse Collections

import java.nio.*;
import java.nio.channels.*;

// read-only, so far. should be thread-safe
final sclass BufferedDiskByteMemory64 implements IIntMemory64, AutoCloseable {
  File file;
  long size; // file size in ints
  RandomAccessFile raf;
  bool bigEndian = true;
  bool writable = true;
  bool debug;

  // byte buffers
  int byteBufferShift = 30; // each buffer is 1 GB
  long byteBufferSize = 1 << byteBufferShift;
  MappedByteBuffer[] byteBuffers;
  
  *(File *file) {
    this(file, false);
  }
  
  *(File *file, bool *writable) {
    this();
    load(file, writable);
  }
  
  void load(File file, bool writable default false) ctex {
    this.file = file;
    this.writable = writable;
    size = fileSize(file)/4;
    raf = newRandomAccessFile(file, writable ? "rw" : "r");
    
    byteBuffers = new MappedByteBuffer[toInt(rightShift_ceil(size, byteBufferShift))];
    FileChannel channel = raf.getChannel();
    for i over byteBuffers: {
      long pos = (long) i << (byteBufferShift+2);
      int len = toInt(min(1 << (byteBufferShift+2), size*4-pos));
      //print("Mapping bytes " + longToHex(pos) + "-" + longToHex(pos+len));
      byteBuffers[i] = channel.map(FileChannel.MapMode.READ_ONLY, pos, len);
      byteBuffers[i].order(bigEndian ? ByteOrder.BIG_ENDIAN : ByteOrder.LITTLE_ENDIAN);
    }
  }
  
  public synchronized void close {
    dispose raf;
  }
  
  ifndef BufferedDiskByteMemory64_unsynchronized synchronized endifndef
  public int get(long idx) {
    rangeCheck(idx, size);
    if (l1cache == null)
      ret get_raw(idx);
    long val = l1cache.getIfAbsent(idx, Long.MAX_VALUE);
    if (val == Long.MAX_VALUE)
      l1cache.put(idx, val = get_raw(idx));
    ret (int) val;
  }
    
  public byte getByte(long idx) {
    ret byteBuffers[(int) (idx >> byteBufferShift)].getByte((int) (idx & (byteBufferSize-1)));
  }
  
  public byte getInt(long idx) {
    // TODO: check if we hit a buffer boundary
    ret byteBuffers[(int) (idx >> byteBufferShift)].getInt((int) (idx & (byteBufferSize-1)));
  }
  
  public synchronized void set(long idx, int val) {
    checkWritable();
    rangeCheck(idx, size);
    CacheEntry e = loadCell(idx);
    e.data[(int) idx & (pageSize-1)] = val;
    e.dirty = true;
  }
  
  void checkWritable {
    if (!writable) fail("read-only");
  }
  
  public long size() {
    ret size;
  }
  
  public synchronized void ensureSize(int size) {
    this.size = max(this.size, size);
  }
}