/* Copyright (C) 2009  CSE,IIT Bombay  http://www.cse.iitb.ac.in

This file is part of the ConStore open source storage facility for concept-nets.

ConStore is free software and distributed under the 
Creative Commons Attribution-Noncommercial-No Derivative Works 3.0 Unported License;
you can copy, distribute and transmit the work
with the work attribution in the manner specified by the author or licensor.
You may not use this work for commercial purposes and may not alter, 
transform, or build upon this work.

Please refer the legal code of the license, available at
http://creativecommons.org/licenses/by-nc-nd/3.0/legalcode

ConStore 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.  */

package iitb.con.io;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;

/**
 * BufferedFileAdapter uses ByteBuffer as the buffer to perform read and write 
 * file operations. It uses <tt>RandomAccessFile</tt> as the file operation mode.
 * 
 * @author Prathab K
 *
 */
public class BufferedFileAdapter implements IOAdapter {

    /** Default Buffer size */
    private static final int DEFAULT_BUFSIZE = 4096;
    
    /** Random Access File handle */
    private RandomAccessFile rafile;
    
    /** File channel to perform IO operations */
    private FileChannel fChannel;
    
    /** File buffer */
    private ByteBuffer buffer;
    
    /** Start location of file in the buffer */
    private long startLocation = -1;
    
    /** End location of file in the buffer */
    private long endLocation = -1;
    
    /** File handle */
    private File file;
    
    /**
     * Constructs the BufferedFileAdapter with specified file name and mode
     * @param name file name
     * @param mode file opening mode (r, rw)
     * @throws FileNotFoundException if file does not exists
     */
    public BufferedFileAdapter(String name, String mode)
    throws FileNotFoundException
    {
        rafile = new RandomAccessFile(name, mode);
        buffer = ByteBuffer.allocate(DEFAULT_BUFSIZE);
        fChannel = rafile.getChannel();
        file = new File(name);
    }
    
     /**
     * Constructs the BufferedFileAdapter with specified file name and mode
     * @param name file name
     * @param mode file opening mode (r, rw)
     * @param bufferSize buffer size 
     * @throws FileNotFoundException if file does not exists
     */
    public BufferedFileAdapter(String name, String mode, int bufferSize)
    throws FileNotFoundException
    {
        rafile = new RandomAccessFile(name, mode);
        buffer = ByteBuffer.allocate(bufferSize);
        fChannel = rafile.getChannel();
        file = new File(name);
    }
    
    /**
     * Reads the contents into the temporary buffer and returns it as ByteBuffer
     * @param location location to be read
     * @return contents as ByteBuffer
     * @throws IOException if file operation fails
     */
    public ByteBuffer readIntoBuffer(long location) throws IOException{
        buffer.clear(); //TODO: optimize 
        fChannel.read(buffer, location);
        return buffer;
    }
    /**
     * Reads the contents into the new buffer and returns it as ByteBuffer
     * @param location location to be read
     * @return contents as ByteBuffer
     * @throws IOException if file operation fails
     */
    private ByteBuffer readToBuffer(long location) throws IOException{
        ByteBuffer buf = ByteBuffer.allocate(buffer.capacity()); 
        fChannel.read(buf, location);
        buf.rewind();
        return buf;
    }
    
    /**
     * Reads the entire contents of the file as ByteBuffer array.<br>
     * The contents are divided as block based on the block size value
     * @return contents as ByteBuffer array
     * @throws IOException if file operation fails
     */
    public ByteBuffer[] readFileAsBlocks() throws IOException {
        long length = file.length();
        int blockSize = buffer.capacity();
        int numBlocks = (int) length / blockSize;
        if((length % blockSize) != 0) numBlocks++;
        
        ByteBuffer[] buffers = new ByteBuffer[numBlocks];
        for(int i=0; i < numBlocks; i++) {
            buffers[i] = readToBuffer((i * blockSize));
            buffers[i].flip();
        }
        return buffers;
    }
    
    /**
     * Reads the contents and return it as byte array.
     * @param location location of the content in file
     * @param length length of the content to read from the specified location
     * @return contents in a byte array
     */
    public byte[] read(long location, int length) throws IOException {
        byte[] b = new byte[length];

        if ((location < startLocation) || ((location + length -2) >= endLocation)) {
            buffer.clear();
            int n = fChannel.read(buffer, location);
            startLocation = location;
            endLocation = location + n -1;
            //System.out.println(file.getName() + " IO New Read " + n);
        }/*else
            System.out.println(file.getName() + " IO Buffer Cache Hit");*/
            

        int l = (int) (location - startLocation);
        for(int i = l, j= 0 ; i < l + length; i++, j++)
            b[j] = buffer.get(i);
        return b;

    }

    /**
     * Writes the contents to the specified location.
     * @param block the content block
     * @param location location to be written
     * @throws IOException if file operation fails
     */
    public void write(byte[] block, long location) throws IOException {
        fChannel.write(ByteBuffer.wrap(block), location);

    }
    
    /**
     * Writes the contents to the specified location.
     * @param block the content block
     * @param location location to be written
     * @throws IOException if file operation fails
     */
    public void write(ByteBuffer block, long location) throws IOException {
        block.rewind();
        fChannel.write(block, location);
        //fChannel.force(true);
    }
    
    /**
     * Writes the contents provided as array to the specified location.
     * @param blocks contents as array of blocks
     * @param location location to be written
     * @throws IOException if file operation fails
     */
    public void write(ByteBuffer[] blocks, long location) throws IOException {
        fChannel.position(location);
        fChannel.write(blocks);
    }

    /**
     * Returns the length of the file
     * @return file length
     */
    public long getFileLength(){
        return file.length();
    }
    
    /**
     * Closes the IO Adapter
     * @throws IOException if file operation fails
     */
    public void close() throws IOException {
        fChannel.close();
        rafile.close();
    }

}