/*
 * Copyright (c) 2015 Michael Hermann
 */

package org.gdstash.file;

import java.io.File;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;

/**
 *
 * @author hermannm
 */
public class GDByteBuffer extends GDAbstractBuffer {
  private int          bufferSize;
  private int          lastBlockSize;
  private long         fileSize;
  private List<byte[]> list;
  private long         pos;
  private ByteOrder    bo;
  
  public GDByteBuffer(String filename) {
    this(new File(filename), GDBuffer.SIZE_100MB);
  }
  
  public GDByteBuffer(String filename, int bufferSize) {
    this(new File(filename), bufferSize);
  }

  public GDByteBuffer(File file) {
    this(file, GDBuffer.SIZE_100MB);
  }
  
  public GDByteBuffer(File file, int bufferSize) {
    this.bufferSize = bufferSize;
    list            = new LinkedList<>();
    
    try (RandomAccessFile raf = new RandomAccessFile(file, "r");) {
      fileSize = (int) raf.length();
      long rest = fileSize;
      
      int size = 0;
      if (rest < bufferSize) {
        size          = (int) rest;
        lastBlockSize = (int) rest;
      } else {
        size = bufferSize;
      }
      
      while (size > 0) {
        byte[] bytes = new byte[size];
        raf.readFully(bytes);
        
        list.add(bytes);
        
        rest = rest - size;
        if (rest < bufferSize) {
          size = (int) rest;
          if (rest > 0) lastBlockSize = (int) rest;
        } else {
          size = bufferSize;
        }
      }
    }
    catch (IOException ex) {
      list = new LinkedList<>();
    }
    
    pos = 0;
    bo  = ByteOrder.LITTLE_ENDIAN;
  }
  
  // ==========================================================================
  // GDBuffer Methods
  // ==========================================================================

  @Override
  public byte[] getByteArray(int length) {
    return getByteArray(pos, length);
  }
  
  @Override
  public byte[] getByteArray(long pos, int length) {
    long listSize = (list.size() - 1) * bufferSize + lastBlockSize;
    
    if (listSize < pos) return null;
    
    int byteSize = length;
    if (listSize < pos + length) {
      byteSize = (int) (listSize - pos);
    }
    
    if (byteSize <= 0) return null;
    
    byte[] result = new byte[byteSize];

    long tempPos   = pos;
    int  tempLen   = length;
    int  resOffset = 0;
      
    long     listPos = 0;
    Iterator iter    = list.iterator();
    while (iter.hasNext()) {
      byte[] bytes = (byte[]) iter.next();
      
      if ((listPos <= tempPos) && 
          (listPos + bytes.length > tempPos)) {
        // Start of selection
        
        int  bufferOffset = (int) (tempPos - listPos);
        int  maxPos       = bufferOffset + tempLen;
        if (maxPos > bytes.length) maxPos = bytes.length;

        for (int i = bufferOffset; i < maxPos; i = i + 1) {
          result[i - bufferOffset + resOffset] = bytes[i];
        }

        listPos = listPos + bufferSize;
        
        if (listPos > tempPos + tempLen) {
          break;
        }
        
        tempPos   = listPos;
        tempLen   = tempLen - (maxPos - bufferOffset);
        resOffset = resOffset + (maxPos - bufferOffset);
      } else {
        listPos = listPos + bufferSize;
      }
    }
    
    this.pos = tempPos + tempLen;
    
    return result;
  }
  
  @Override
  public byte getByte() {
    byte[] bytes = getByteArray(1);

    return bytes[0];
  }
  
  @Override
  public int getInt() {
    byte[] bytes = getByteArray(4);
    
    return ByteBuffer.wrap(bytes).order(bo).getInt();
  }
  
  @Override
  public long getLong() {
    byte[] bytes = getByteArray(8);
    
    return ByteBuffer.wrap(bytes).order(bo).getLong();
  }
  
  @Override
  public short getShort() {
    byte[] bytes = getByteArray(2);
    
    return ByteBuffer.wrap(bytes).order(bo).getShort();
  }

  @Override
  public long getPosition() {
    return pos;
  }
  
  @Override
  public void setPosition(long pos) {
    this.pos = pos;
  }
  
  @Override
  public void close() {
    list = null;
  }
  
}
