package jogamp.opengl.util.pngj;

import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.zip.CRC32;

import jogamp.opengl.util.pngj.chunks.ChunkHelper;


/**
 * Reads a sequence of contiguous IDAT chunks
 */
class PngIDatChunkInputStream extends InputStream {
	private final InputStream inputStream;
	private final CRC32 crcEngine;
	private boolean checkCrc = true;
	private int lenLastChunk;
	private final byte[] idLastChunk = new byte[4];
	private int toReadThisChunk = 0;
	private boolean ended = false;
	private long offset; // offset inside whole inputstream (counting bytes before IDAT)

	// just informational
	static class IdatChunkInfo {
		public final int len;
		public final long offset;

		private IdatChunkInfo(final int len, final long offset) {
			this.len = len;
			this.offset = offset;
		}
	}

	List<IdatChunkInfo> foundChunksInfo = new ArrayList<IdatChunkInfo>();

	/**
	 * Constructor must be called just after reading length and id of first IDAT
	 * chunk
	 **/
	PngIDatChunkInputStream(final InputStream iStream, final int lenFirstChunk, final long offset) {
		this.offset = offset;
		inputStream = iStream;
		this.lenLastChunk = lenFirstChunk;
		toReadThisChunk = lenFirstChunk;
		// we know it's a IDAT
		System.arraycopy(ChunkHelper.b_IDAT, 0, idLastChunk, 0, 4);
		crcEngine = new CRC32();
		crcEngine.update(idLastChunk, 0, 4);
		foundChunksInfo.add(new IdatChunkInfo(lenLastChunk, offset - 8));

		// PngHelper.logdebug("IDAT Initial fragment: len=" + lenLastChunk);
		if (this.lenLastChunk == 0)
			endChunkGoForNext(); // rare, but...
	}

	/**
	 * does NOT close the associated stream!
	 */
	@Override
	public void close() throws IOException {
		super.close(); // thsi does nothing
	}

	private void endChunkGoForNext() {
		// Called after readging the last byte of one IDAT chunk
		// Checks CRC, and read ID from next CHUNK
		// Those values are left in idLastChunk / lenLastChunk
		// Skips empty IDATS
		do {
			final int crc = PngHelperInternal.readInt4(inputStream); //
			offset += 4;
			if (checkCrc) {
				final int crccalc = (int) crcEngine.getValue();
				if (lenLastChunk > 0 && crc != crccalc)
					throw new PngjBadCrcException("error reading idat; offset: " + offset);
				crcEngine.reset();
			}
			lenLastChunk = PngHelperInternal.readInt4(inputStream);
			toReadThisChunk = lenLastChunk;
			PngHelperInternal.readBytes(inputStream, idLastChunk, 0, 4);
			offset += 8;
			// found a NON IDAT chunk? this stream is ended
			ended = !Arrays.equals(idLastChunk, ChunkHelper.b_IDAT);
			if (!ended) {
				foundChunksInfo.add(new IdatChunkInfo(lenLastChunk, offset - 8));
				if (checkCrc)
					crcEngine.update(idLastChunk, 0, 4);
			}
			// PngHelper.logdebug("IDAT ended. next len= " + lenLastChunk + " idat?" +
			// (!ended));
		} while (lenLastChunk == 0 && !ended);
		// rarely condition is true (empty IDAT ??)
	}

	/**
	 * sometimes last row read does not fully consumes the chunk here we read
	 * the reamaing dummy bytes
	 */
	void forceChunkEnd() {
		if (!ended) {
			final byte[] dummy = new byte[toReadThisChunk];
			PngHelperInternal.readBytes(inputStream, dummy, 0, toReadThisChunk);
			if (checkCrc)
				crcEngine.update(dummy, 0, toReadThisChunk);
			endChunkGoForNext();
		}
	}

	/**
	 * This can return less than len, but never 0 Returns -1 if "pseudo file"
	 * ended prematurely. That is our error.
	 */
	@Override
	public int read(final byte[] b, final int off, final int len) throws IOException {
		if (ended)
			return -1; // can happen only when raw reading, see Pngreader.readAndSkipsAllRows()
		if (toReadThisChunk == 0)
			throw new PngjExceptionInternal("this should not happen");
		final int n = inputStream.read(b, off, len >= toReadThisChunk ? toReadThisChunk : len);
		if (n > 0) {
			if (checkCrc)
				crcEngine.update(b, off, n);
			this.offset += n;
			toReadThisChunk -= n;
		}
		if (toReadThisChunk == 0) { // end of chunk: prepare for next
			endChunkGoForNext();
		}
		return n;
	}

	@Override
	public int read(final byte[] b) throws IOException {
		return this.read(b, 0, b.length);
	}

	@Override
	public int read() throws IOException {
		// PngHelper.logdebug("read() should go here");
		// inneficient - but this should be used rarely
		final byte[] b1 = new byte[1];
		final int r = this.read(b1, 0, 1);
		return r < 0 ? -1 : (int) b1[0];
	}

	int getLenLastChunk() {
		return lenLastChunk;
	}

	byte[] getIdLastChunk() {
		return idLastChunk;
	}

	long getOffset() {
		return offset;
	}

	boolean isEnded() {
		return ended;
	}

	/**
	 * Disables CRC checking. This can make reading faster
	 */
	void disableCrcCheck() {
		checkCrc = false;
	}
}
