package ij.io;
import ij.IJ;
import java.io.*;
import java.util.Vector;


/** This class uses a memory cache to allow seeking within
    an InputStream. Based on the JAI MemoryCacheSeekableStream class.
    Can also be constructed from a RandomAccessFile, which uses less
    memory since the memory cache is not required.
*/ 
public final class RandomAccessStream extends InputStream {

    private static final int BLOCK_SIZE = 1024;
    private static final int BLOCK_MASK = 1023;
    private static final int BLOCK_SHIFT = 10;

    private InputStream src;
    private RandomAccessFile ras;
    private long pointer;
    private Vector data;
    private long length;
    private boolean foundEOS;
    
    /** Constructs a RandomAccessStream from an InputStream. Seeking
        backwards is supported using a memory cache. */
    public RandomAccessStream(InputStream inputstream) {
        pointer = 0L;
        data = new Vector();
        length = 0L;
        foundEOS = false;
        src = inputstream;
    }

    /** Constructs a RandomAccessStream from an RandomAccessFile. */
    public RandomAccessStream(RandomAccessFile ras) {
        this.ras = ras;
    }

    public int getFilePointer() throws IOException {
        if (ras!=null)
            return (int)ras.getFilePointer();
        else
            return (int)pointer;
    }

    public long getLongFilePointer() throws IOException {
        if (ras!=null)
            return ras.getFilePointer();
        else
            return pointer;
    }

    public int read() throws IOException {
        if (ras!=null)
            return ras.read();
        long l = pointer + 1L;
        long l1 = readUntil(l);
        if (l1>=l) {
            byte abyte0[] = (byte[])data.elementAt((int)(pointer>>BLOCK_SHIFT));
            return abyte0[(int)(pointer++ & BLOCK_MASK)] & 0xff;
        } else
            return -1;
    }

    public int read(byte[] bytes, int off, int len) throws IOException {
        if(bytes == null)
            throw new NullPointerException();
        if (ras!=null)
            return ras.read(bytes, off, len);
        if (off<0 || len<0 || off+len>bytes.length)
            throw new IndexOutOfBoundsException();
        if (len == 0)
            return 0;
        long l = readUntil(pointer+len);
        if (l<=pointer)
            return -1;
        else {
            byte abyte1[] = (byte[])data.elementAt((int)(pointer >> BLOCK_SHIFT));
            int k = Math.min(len, BLOCK_SIZE - (int)(pointer & BLOCK_MASK));
            System.arraycopy(abyte1, (int)(pointer & BLOCK_MASK), bytes, off, k);
            pointer += k;
            return k;
        }
    }

    public final void readFully(byte[] bytes) throws IOException {
        readFully(bytes, bytes.length);
    }

    public final void readFully(byte[] bytes, int len) throws IOException {
        int read = 0;
        do {
            int l = read(bytes, read, len - read);
            if(l < 0) break;
            read += l;
        } while (read<len);
    }

    private long readUntil(long l) throws IOException {
        if (l<length)
            return l;
        if (foundEOS)
            return length;
        int i = (int)(l>>BLOCK_SHIFT);
        int j = (int)(length>>BLOCK_SHIFT);
        for (int k=j; k<=i; k++) {
            byte abyte0[] = new byte[BLOCK_SIZE];
            data.addElement(abyte0);
            int i1 = BLOCK_SIZE;
            int j1 = 0;
            while (i1>0) {
                int k1 = src.read(abyte0, j1, i1);
                if (k1==-1) {
                    foundEOS = true;
                    return length;
                }
                j1 += k1;
                i1 -= k1;
                length += k1;
            }
        }
        return length;
    }

    public void seek(long loc) throws IOException {
        //IJ.log("seek (long): "+loc+"  "+(ras!=null));
        if (ras!=null)
            {ras.seek(loc); return;}
        if (loc<0L)
            pointer = 0L;
        else
            pointer = loc;
    }

    public void seek(int loc) throws IOException {
        long lloc = ((long)loc)&0xffffffffL;
        //IJ.log("seek (int): "+lloc+"  "+(ras!=null));
        if (ras!=null) {
            ras.seek(lloc);
            return;
        }
        if (lloc<0L)
            pointer = 0L;
        else
            pointer = lloc;
    }

    public final int readInt() throws IOException {
        int i = read();
        int j = read();
        int k = read();
        int l = read();
        if ((i | j | k | l) < 0)
            throw new EOFException();
        else
            return (i << 24) + (j << 16) + (k << 8) + l;
    }

    public final long readLong() throws IOException {
        return ((long)readInt()<<32) + ((long)readInt()&0xffffffffL);
    }

    public final double readDouble() throws IOException {
        return Double.longBitsToDouble(readLong());
    }

    public final short readShort() throws IOException {
        int i = read();
        int j = read();
        if ((i | j) < 0)
            throw new EOFException();
        else
            return (short)((i<<8) + j);
    }

    public final float readFloat() throws IOException {
        return Float.intBitsToFloat(readInt());
    }
    
    public void close() throws IOException {
        //ij.IJ.log("close: "+(data!=null?""+data.size():""));
        if (ras!=null)
            ras.close();
        else {
            data.removeAllElements();
            src.close();
        }
    }
    
 
}