/**
 * OpenKM, Open Document Management System (http://www.openkm.com)
 * Copyright (c) 2006-2017  Paco Avila & Josep Llort
 * <p>
 * No bytes were intentionally harmed during the development of this application.
 * <p>
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 * <p>
 * This program 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.  See the
 * GNU General Public License for more details.
 * <p>
 * You should have received a copy of the GNU General Public License along
 * with this program; if not, write to the Free Software Foundation, Inc.,
 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 */

package com.openkm.util;

import java.io.EOFException;
import java.io.IOException;
import java.io.InputStream;
import java.io.RandomAccessFile;
import java.util.Vector;

/**
 * This is a class that 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 = 512;
	private static final int BLOCK_MASK = 511;
	private static final int BLOCK_SHIFT = 9;

	private InputStream src;
	private RandomAccessFile ras;
	private long pointer;
	private Vector<byte[]> data;
	private int 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<byte[]>();
		length = 0;
		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[] = 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[] = 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 = 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 {
		if (ras != null) {
			ras.seek(loc);
			return;
		}
		if (loc < 0L)
			pointer = 0L;
		else
			pointer = loc;
	}

	public void seek(int loc) throws IOException {
		if (ras != null) {
			ras.seek(loc & 0xffffffff);
			return;
		}
		if (loc < 0)
			pointer = 0L;
		else
			pointer = loc;
	}

	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 {
		// if (ij.IJ.debugMode) ij.IJ.log("close: "+(data!=null?""+data.size():""));
		if (ras != null)
			ras.close();
		else {
			data.removeAllElements();
			src.close();
		}
	}
}
