package zemberek.lm.compression;

import java.io.File;
import java.io.IOException;
import java.io.RandomAccessFile;

public class InMemoryBigByteArray {

  private static final int MAX_BUF = 0x3fffffff;
  final int pageShift;
  final int indexMask;
  int count;
  int blockSize;
  byte[][] data;

  public InMemoryBigByteArray(File file) throws IOException {
    RandomAccessFile raf = new RandomAccessFile(file, "r");
    count = raf.readInt();
    blockSize = raf.readInt();
    int pageLength = getPowerOf2(MAX_BUF / blockSize, MAX_BUF / blockSize);
    pageShift = 32 - Integer.numberOfLeadingZeros(pageLength - 1);
    indexMask = (1 << pageShift - 1) - 1;
    long l = 0;
    int pageCounter = 0;
    while (l < count * blockSize) {
      pageCounter++;
      l += (pageLength * blockSize);
    }
    data = new byte[pageCounter][];
    int total = 0;
    for (int i = 0; i < pageCounter; i++) {
      if (i < pageCounter - 1) {
        data[i] = new byte[pageLength * blockSize];
        total += pageLength * blockSize;
      } else {
        data[i] = new byte[count * blockSize - total];
      }
      raf.readFully(data[i]);
    }
    raf.close();
  }

  int getPowerOf2(int k, int limit) {
    if (k <= 2) {
      return 1;
    }
    int i = 1;
    while (i < k) {
      i *= 2;
    }
    if (i >= limit) {
      return i / 2;
    } else {
      return i;
    }
  }

  public int getCount() {
    return count;
  }

  public int getBlockSize() {
    return blockSize;
  }

  public void get(int index, byte[] buff) {
    final int pageId = index >>> pageShift;
    final int pageIndex = (index & indexMask) * blockSize;
    byte[] d = data[pageId];
    System.arraycopy(d, pageIndex, buff, 0, blockSize);
  }

  public int getInt(int index) {
    final int pageId = index >>> pageShift;
    final int pageIndex = (index & indexMask) * blockSize;
    byte[] d = data[pageId];
    switch (blockSize) {
      case 1:
        return d[pageIndex] & 0xff;
      case 2:
        return ((d[pageIndex] & 0xff) << 8) | (d[pageIndex + 1] & 0xff);
      case 3:
        return ((d[pageIndex] & 0xff) << 16) | ((d[pageIndex + 1] & 0xff) << 8) | (d[pageIndex + 2]
            & 0xff);
    }
    return -1;
  }

  public float getFloat(int index) {
    final int pageId = index >>> pageShift;
    final int pageIndex = (index & indexMask) * blockSize;
    byte[] d = data[pageId];
    return Float.intBitsToFloat(
        ((d[pageIndex] & 0xff) << 24) |
            ((d[pageIndex + 1] & 0xff) << 16) |
            ((d[pageIndex + 2] & 0xff) << 8) |
            (d[pageIndex + 3] & 0xff));
  }

}
