/*******************************************************************************
 * Copyright (c) 2007, 2018 compeople AG 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:
 * 	compeople AG (Stefan Liebig) - initial API and implementation
 *  IBM Corporation - bug fixes and enhancements
 *******************************************************************************/
package org.eclipse.equinox.internal.p2.sar;

import java.io.*;

/**
 * The SarInputStream reads a streaming archive as an InputStream. Methods are
 * provided to position at each successive entry in the archive, and the read
 * each entry as a normal input stream using read().
 */
public class SarInputStream extends InputStream {

	private final DataInputStream dataInputStream;
	private final int version;
	private InputStream contentStream;

	/**
	 * Constructor for SarInputStream.
	 *
	 * @param inputStream the input stream to use
	 * @throws IOException
	 */
	public SarInputStream(InputStream inputStream) throws IOException {

		this.dataInputStream = new DataInputStream(inputStream);

		// SarFile marker
		String marker = readString();
		if (!marker.equals(SarConstants.SARFILE_MARKER)) {
			throw new IOException("Does not contain org.eclipse.equinox.p2.sar marker.");
		}

		// SarFile version
		version = dataInputStream.readInt();
		if (version != SarConstants.SARFILE_VERSION) {
			throw new IOException("Unsupported version.");
		}
	}

	/**
	 * Closes this stream.
	 *
	 * @throws IOException on error
	 */
	@Override
	public void close() throws IOException {
		dataInputStream.close();
	}

	/**
	 * Since we do not support marking just yet, we return false.
	 *
	 * @return False.
	 */
	@Override
	public boolean markSupported() {
		return false;
	}

	/**
	 * Since we do not support marking just yet, we do nothing.
	 *
	 * @param markLimit The limit to mark.
	 */
	@Override
	public void mark(int markLimit) {
		// nothing
	}

	/**
	 * Since we do not support marking just yet, we do nothing.
	 */
	@Override
	public void reset() {
		// nothing
	}

	/**
	 * Get the next entry in this org.eclipse.equinox.p2.sar archive. This will skip
	 * over any remaining data in the current entry, if there is one, and place the
	 * input stream at the header of the next entry, and read the header and
	 * instantiate a new SarEntry from the header bytes and return that entry. If
	 * there are no more entries in the archive, null will be returned to indicate
	 * that the end of the archive has been reached.
	 *
	 * @return the next SarEntry in the archive, or null.
	 * @throws IOException on error
	 */
	public SarEntry getNextEntry() throws IOException {
		SarEntry sarEntry = new SarEntry(this);
		if (sarEntry.isEof())
			return null;

		byte[] content = readBytes();
		contentStream = new ByteArrayInputStream(content);
		return sarEntry;

	}

	/**
	 * Close the entry.
	 *
	 * @throws IOException
	 */
	public void closeEntry() throws IOException {
		contentStream.close();
	}

	/**
	 * @return String
	 * @throws IOException
	 */
	String readString() throws IOException {
		byte[] bytes = readBytes();
		if (bytes == null)
			return null;

		return new String(bytes, SarConstants.DEFAULT_ENCODING);
	}

	/**
	 * @return byte[]
	 * @throws IOException
	 */
	byte[] readBytes() throws IOException {
		int length = dataInputStream.readInt();
		if (length == -1)
			return null;

		byte[] bytes = new byte[length];
		dataInputStream.readFully(bytes, 0, length);
		return bytes;
	}

	/**
	 * @return int
	 * @throws IOException
	 */
	int readInt() throws IOException {
		return dataInputStream.readInt();
	}

	/**
	 * @return boolean
	 * @throws IOException
	 */
	boolean readBoolean() throws IOException {
		return dataInputStream.readBoolean();
	}

	/**
	 * @return long
	 * @throws IOException
	 */
	long readLong() throws IOException {
		return dataInputStream.readLong();
	}

	/**
	 * Reads a byte from the current tar archive entry.
	 *
	 * This method simply calls read( byte[], int, int ).
	 *
	 * @return The byte read, or -1 at EOF.
	 * @throws IOException on error
	 */
	@Override
	public int read() throws IOException {
		return contentStream.read();
	}

	/**
	 * Reads bytes from the current tar archive entry.
	 *
	 * This method is aware of the boundaries of the current entry in the archive
	 * and will deal with them as if they were this stream's start and EOF.
	 *
	 * @param buffer    The buffer into which to place bytes read.
	 * @param offset    The offset at which to place bytes read.
	 * @param numToRead The number of bytes to read.
	 * @return The number of bytes read, or -1 at EOF.
	 * @throws IOException on error
	 */
	@Override
	public int read(byte[] buffer, int offset, int numToRead) throws IOException {
		return contentStream.read(buffer, offset, numToRead);
	}

}