package ar.com.hjg.pngj;

import java.io.OutputStream;

import ar.com.hjg.pngj.chunks.ChunkHelper;
import ar.com.hjg.pngj.chunks.ChunkRaw;

/**
 * Outputs a sequence of IDAT-like chunk, that is filled progressively until the
 * max chunk length is reached (or until
 * flush())
 */
public class IDatChunkWriter {

	private static final int MAX_LEN_DEFAULT = 32768; // 32K rather arbitrary - data only

	private final OutputStream outputStream;
	private final int maxChunkLen;
	private byte[] buf;

	private int offset = 0;
	private int availLen;
	private long totalBytesWriten = 0; // including header+crc
	private int chunksWriten = 0;

	public IDatChunkWriter(final OutputStream outputStream) {
		this(outputStream, 0);
	}

	public IDatChunkWriter(final OutputStream outputStream, final int maxChunkLength) {
		this.outputStream = outputStream;
		maxChunkLen = maxChunkLength > 0 ? maxChunkLength : IDatChunkWriter.MAX_LEN_DEFAULT;
		buf = new byte[maxChunkLen];
		availLen = maxChunkLen - offset;
		postReset();
	}

	public IDatChunkWriter(final OutputStream outputStream, final byte[] b) {
		this.outputStream = outputStream;
		buf = b != null ? b : new byte[IDatChunkWriter.MAX_LEN_DEFAULT];
		maxChunkLen = b.length;
		availLen = maxChunkLen - offset;
		postReset();
	}

	protected byte[] getChunkId() {
		return ChunkHelper.b_IDAT;
	}

	/**
	 * Writes a chhunk if there is more than minLenToWrite.
	 * 
	 * This is normally called internally, but can be called explicitly to force
	 * flush.
	 */
	public final void flush() {
		if (offset > 0 && offset >= minLenToWrite()) {
			final ChunkRaw c = new ChunkRaw(offset, getChunkId(), false);
			c.data = buf;
			c.writeChunk(outputStream);
			totalBytesWriten += c.len + 12;
			chunksWriten++;
			offset = 0;
			availLen = maxChunkLen;
			postReset();
		}
	}

	public int getOffset() {
		return offset;
	}

	public int getAvailLen() {
		return availLen;
	}

	/** triggers an flush+reset if appropiate */
	public void incrementOffset(final int n) {
		offset += n;
		availLen -= n;
		if (availLen < 0)
			throw new PngjOutputException("Anomalous situation");
		if (availLen == 0)
			flush();
	}

	/**
	 * this should rarely be used, the normal way (to avoid double copying) is
	 * to get the buffer and write directly to it
	 */
	public void write(final byte[] b, int o, int len) {
		while (len > 0) {
			final int n = len <= availLen ? len : availLen;
			System.arraycopy(b, o, buf, offset, n);
			incrementOffset(n);
			len -= n;
			o += n;
		}
	}

	/** this will be called after reset */
	protected void postReset() {
		// fdat could override this (and minLenToWrite) to add a prefix
	}

	protected int minLenToWrite() {
		return 1;
	}

	public void close() {
		flush();
		offset = 0;
		buf = null;
	}

	/**
	 * You can write directly to this buffer, using {@link #getOffset()} and
	 * {@link #getAvailLen()}. You should call
	 * {@link #incrementOffset(int)} inmediately after.
	 */
	public byte[] getBuf() {
		return buf;
	}

	public long getTotalBytesWriten() {
		return totalBytesWriten;
	}

	public int getChunksWriten() {
		return chunksWriten;
	}
}
