package ar.com.hjg.pngj;

import java.io.File;
import java.io.InputStream;
import java.util.List;

import ar.com.hjg.pngj.chunks.PngChunk;
import ar.com.hjg.pngj.chunks.PngChunkACTL;
import ar.com.hjg.pngj.chunks.PngChunkFCTL;
import ar.com.hjg.pngj.chunks.PngChunkFDAT;
import ar.com.hjg.pngj.chunks.PngChunkIDAT;

/**
 */
public class PngReaderApng extends PngReaderByte {

	public PngReaderApng(final File file) {
		super(file);
		dontSkipChunk(PngChunkFCTL.ID);
	}

	public PngReaderApng(final InputStream inputStream) {
		super(inputStream);
		dontSkipChunk(PngChunkFCTL.ID);
	}

	private Boolean apngKind = null;
	private boolean firsIdatApngFrame = false;
	protected PngChunkACTL actlChunk; // null if not APNG
	private PngChunkFCTL fctlChunk; // current (null for the pseudo still frame)

	/**
	 * Current frame number (reading or read). First animated frame is 0. Frame
	 * -1 represents the IDAT (default image)
	 * when it's not part of the animation
	 */
	protected int frameNum = -1; // incremented after each fctl finding

	public boolean isApng() {
		if (apngKind == null) {
			// this triggers the loading of first chunks;
			actlChunk = (PngChunkACTL) getChunksList().getById1(PngChunkACTL.ID); // null if not apng
			apngKind = actlChunk != null;
			firsIdatApngFrame = fctlChunk != null;

		}
		return apngKind.booleanValue();
	}

	public void advanceToFrame(final int frame) {
		if (frame < frameNum)
			throw new PngjInputException("Cannot go backwards");
		if (frame >= getApngNumFrames())
			throw new PngjInputException("Frame out of range " + frame);
		if (frame > frameNum) {
			addChunkToSkip(PngChunkIDAT.ID);
			addChunkToSkip(PngChunkFDAT.ID);
			if (chunkseq.getIdatSet() != null && !chunkseq.getIdatSet().isDone())
				chunkseq.getIdatSet().done(); // seems to be necessary sometimes (we should check this)
			while (frameNum < frame & !chunkseq.isDone())
				if (streamFeeder.feed(chunkseq) <= 0)
					break;
		}
		if (frame == frameNum) { // prepare to read rows. at this point we have a new
			dontSkipChunk(PngChunkIDAT.ID);
			dontSkipChunk(PngChunkFDAT.ID);
			rowNum = -1;
			imlinesSet = null;// force recreation (this is slightly dirty)
			// seek the next IDAT/fDAT - TODO: set the expected sequence number
			while (!chunkseq.isDone() && !chunkseq.getCurChunkReader().isFromDeflatedSet())
				if (streamFeeder.feed(chunkseq) <= 0)
					break;
		} else
			throw new PngjInputException("unexpected error seeking from frame " + frame);
	}

	/**
	 * True if it has a default image (IDAT) that is not part of the animation.
	 * In that case, we consider it as a
	 * pseudo-frame (number -1)
	 */
	public boolean hasExtraStillImage() {
		return isApng() && !firsIdatApngFrame;
	}

	/**
	 * Only counts true animation frames.
	 */
	public int getApngNumFrames() {
		if (isApng())
			return actlChunk.getNumFrames();
		else
			return 0;
	}

	/**
	 * 0 if it's to been played infinitely. -1 if not APNG
	 */
	public int getApngNumPlays() {
		if (isApng())
			return actlChunk.getNumPlays();
		else
			return -1;
	}

	@Override
	public IImageLine readRow() {
		// TODO Auto-generated method stub
		return super.readRow();
	}

	@Override
	public boolean hasMoreRows() {
		// TODO Auto-generated method stub
		return super.hasMoreRows();
	}

	@Override
	public IImageLine readRow(final int nrow) {
		// TODO Auto-generated method stub
		return super.readRow(nrow);
	}

	@Override
	public IImageLineSet<? extends IImageLine> readRows() {
		// TODO Auto-generated method stub
		return super.readRows();
	}

	@Override
	public IImageLineSet<? extends IImageLine> readRows(final int nRows, final int rowOffset, final int rowStep) {
		// TODO Auto-generated method stub
		return super.readRows(nRows, rowOffset, rowStep);
	}

	@Override
	public void readSkippingAllRows() {
		// TODO Auto-generated method stub
		super.readSkippingAllRows();
	}

	@Override
	protected ChunkSeqReaderPng createChunkSeqReader() {
		final ChunkSeqReaderPng cr = new ChunkSeqReaderPng(false) {

			@Override
			public boolean shouldSkipContent(final int len, final String id) {
				return super.shouldSkipContent(len, id);
			}

			@Override
			protected boolean isIdatKind(final String id) {
				return id.equals(PngChunkIDAT.ID) || id.equals(PngChunkFDAT.ID);
			}

			@Override
			protected DeflatedChunksSet createIdatSet(final String id) {
				final IdatSet ids = new IdatSet(id, getCurImgInfo(), deinterlacer);
				ids.setCallbackMode(callbackMode);
				return ids;
			}

			@Override
			protected void startNewChunk(final int len, final String id, final long offset) {
				super.startNewChunk(len, id, offset);
			}

			@Override
			protected void postProcessChunk(final ChunkReader chunkR) {
				super.postProcessChunk(chunkR);
				if (chunkR.getChunkRaw().id.equals(PngChunkFCTL.ID)) {
					frameNum++;
					final List<PngChunk> chunkslist = chunkseq.getChunks();
					fctlChunk = (PngChunkFCTL) chunkslist.get(chunkslist.size() - 1);
					// as this is slightly dirty, we check
					if (chunkR.getChunkRaw().getOffset() != fctlChunk.getRaw().getOffset())
						throw new PngjInputException("something went wrong");
					final ImageInfo frameInfo = fctlChunk.getEquivImageInfo();
					getChunkseq().updateCurImgInfo(frameInfo);
				}
			}

			@SuppressWarnings("unlikely-arg-type")
			@Override
			protected boolean countChunkTypeAsAncillary(final String id) {
				// we don't count fdat as ancillary data
				return super.countChunkTypeAsAncillary(id) && !id.equals(id.equals(PngChunkFDAT.ID));
			}

		};
		return cr;
	}

	/**
	 * @see #frameNum
	 */
	public int getFrameNum() {
		return frameNum;
	}

	@Override
	public void end() {
		// TODO Auto-generated method stub
		super.end();
	}

	public PngChunkFCTL getFctl() {
		return fctlChunk;
	}

}
