/*******************************************************************************
 * Copyright (c) 2005, 2006 IBM Corporation and others.
 *
 * This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License 2.0
 * which accompanies this distribution, and is available at
 * https://www.eclipse.org/legal/epl-2.0/
 *
 * SPDX-License-Identifier: EPL-2.0
 *
 * Contributors:
 *     IBM Corporation - initial API and implementation
 *******************************************************************************/
package org.eclipse.core.internal.registry;

import java.io.*;

/**
 * Provides buffered read from a java.io.RandomAccessFile.
 */
public class BufferedRandomInputStream extends InputStream {

	private RandomAccessFile inputFile;
	private String filePath; // Canonical path to the underlying file used for logging
	private int buffer_size; // Current size of the buffer
	private int buffer_pos; // Current read position in the buffer
	/**
	 * The absolute position in the file where the buffered region starts.
	 */
	private long buffer_start = 0;

	/**
	 * The current value of the RAF's file pointer.
	 */
	private long file_pointer;

	private byte buffer[];

	public BufferedRandomInputStream(File file) throws IOException {
		this(file, 2048); // default buffer size
	}

	public BufferedRandomInputStream(File file, int bufferSize) throws IOException {
		filePath = file.getCanonicalPath();
		inputFile = new RandomAccessFile(file, "r"); //$NON-NLS-1$
		buffer = new byte[bufferSize];
		file_pointer = 0;
		resetBuffer();
	}

	private void resetBuffer() {
		buffer_pos = 0;
		buffer_size = 0;
		buffer_start = 0;
	}

	private int fillBuffer() throws IOException {
		buffer_pos = 0;
		buffer_start = file_pointer;
		buffer_size = inputFile.read(buffer, 0, buffer.length);
		file_pointer += buffer_size;
		return buffer_size;
	}

	@Override
	public int read() throws IOException {
		if (buffer_pos >= buffer_size) {
			if (fillBuffer() <= 0)
				return -1;
		}
		return buffer[buffer_pos++] & 0xFF;
	}

	@Override
	public int read(byte b[], int off, int len) throws IOException {
		int available = buffer_size - buffer_pos;
		if (available < 0)
			return -1;
		//the buffer contains all the bytes we need, so copy over and return
		if (len <= available) {
			System.arraycopy(buffer, buffer_pos, b, off, len);
			buffer_pos += len;
			return len;
		}
		// Use portion remaining in the buffer
		System.arraycopy(buffer, buffer_pos, b, off, available);
		if (fillBuffer() <= 0)
			return available;
		//recursive call to read again until we have the bytes we need
		return available + read(b, off + available, len - available);
	}

	@Override
	public long skip(long n) throws IOException {
		if (n <= 0)
			return 0;

		int available = buffer_size - buffer_pos;
		if (n <= available) {
			buffer_pos += n;
			return n;
		}
		resetBuffer();
		final int skipped = inputFile.skipBytes((int) (n - available));
		file_pointer += skipped;
		return available + skipped;
	}

	@Override
	public int available() {
		return (buffer_size - buffer_pos);
	}

	@Override
	public void close() throws IOException {
		inputFile.close();
		inputFile = null;
		buffer = null;
	}

	@Override
	public String toString() {
		return filePath;
	}

	/**
	 * Supplies functionality of the {@link java.io.RandomAccessFile#seek(long)} in
	 * a buffer-friendly manner.
	 *
	 * @param pos offset
	 * @throws IOException
	 */
	public void seek(long pos) throws IOException {
		if (pos >= buffer_start && pos < buffer_start + buffer_size) {
			//seeking within the current buffer
			buffer_pos = (int) (pos - buffer_start);
		} else {
			//seeking outside the buffer - just discard the buffer
			inputFile.seek(pos);
			file_pointer = pos;
			resetBuffer();
		}
	}

	/**
	 * Supplies functionality of the {@link java.io.RandomAccessFile#length()}.
	 * @return file length
	 * @throws IOException
	 */
	public long length() throws IOException {
		return inputFile.length();
	}

}
