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 IDAT chunks
 */
class PngIDatChunkInputStream extends InputStream {
	private final InputStream inputStream;
	private final CRC32 crcEngine;
	private int lenLastChunk;
	private byte[] idLastChunk = new byte[4];
	private int toReadThisChunk = 0;
	private boolean ended = false;
	private long offset; // offset inside inputstream

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

		private IdatChunkInfo(int len, int 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(InputStream iStream, int lenFirstChunk, int offset) {
		this.offset = (long) offset;
		inputStream = iStream;
		crcEngine = new CRC32();
		this.lenLastChunk = lenFirstChunk;
		toReadThisChunk = lenFirstChunk;
		// we know it's a IDAT
		System.arraycopy(ChunkHelper.b_IDAT, 0, idLastChunk, 0, 4);
		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(); // nothing
	}

	private void endChunkGoForNext() {
		// Called after readging the last byte of chunk
		// Checks CRC, and read ID from next CHUNK
		// Those values are left in idLastChunk / lenLastChunk
		// Skips empty IDATS
		do {
			int crc = PngHelper.readInt4(inputStream); //
			offset += 4;
			int crccalc = (int) crcEngine.getValue();
			if (lenLastChunk > 0 && crc != crccalc)
				throw new PngjBadCrcException("error reading idat; offset: " + offset);
			crcEngine.reset();
			lenLastChunk = PngHelper.readInt4(inputStream);
			if (lenLastChunk < 0)
				throw new PngjInputException("invalid len for chunk: " + lenLastChunk);
			toReadThisChunk = lenLastChunk;
			PngHelper.readBytes(inputStream, idLastChunk, 0, 4);
			offset += 8;
			ended = !Arrays.equals(idLastChunk, ChunkHelper.b_IDAT);
			if (!ended) {
				foundChunksInfo.add(new IdatChunkInfo(lenLastChunk, (int) (offset - 8)));
				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) {
			byte[] dummy = new byte[toReadThisChunk];
			PngHelper.readBytes(inputStream, dummy, 0, toReadThisChunk);
			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(byte[] b, int off, int len) throws IOException {
		if (toReadThisChunk == 0)
			throw new RuntimeException("this should not happen");
		int n = inputStream.read(b, off, len >= toReadThisChunk ? toReadThisChunk : len);
		if (n > 0) {
			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(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
		byte[] b1 = new byte[1];
		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;
	}
}
