package ar.com.hjg.pngj.chunks;

import java.util.ArrayList;
import java.util.List;

import ar.com.hjg.pngj.PngjException;

/**
 * We consider "image metadata" every info inside the image except for the most
 * basic image info (IHDR chunk - ImageInfo
 * class) and the pixels values.
 * <p>
 * This includes the palette (if present) and all the ancillary chunks
 * <p>
 * This class provides a wrapper over the collection of chunks of a image (read
 * or to write) and provides some high
 * level methods to access them
 */
public class PngMetadata {
	private final ChunksList chunkList;
	private final boolean readonly;

	public PngMetadata(final ChunksList chunks) {
		chunkList = chunks;
		if (chunks instanceof ChunksListForWrite)
			readonly = false;
		else
			readonly = true;
	}

	/**
	 * Queues the chunk at the writer
	 * <p>
	 * lazyOverwrite: if true, checks if there is a queued "equivalent" chunk
	 * and if so, overwrites it. However if that
	 * not check for already written chunks.
	 */
	public void queueChunk(final PngChunk c, final boolean lazyOverwrite) {
		final ChunksListForWrite cl = getChunkListW();
		if (readonly)
			throw new PngjException("cannot set chunk : readonly metadata");
		if (lazyOverwrite)
			ChunkHelper.trimList(cl.getQueuedChunks(), c2 -> ChunkHelper.equivalent(c, c2));
		cl.queue(c);
	}

	public void queueChunk(final PngChunk c) {
		queueChunk(c, true);
	}

	private ChunksListForWrite getChunkListW() {
		return (ChunksListForWrite) chunkList;
	}

	// ///// high level utility methods follow ////////////

	// //////////// DPI

	/**
	 * returns -1 if not found or dimension unknown
	 */
	public double[] getDpi() {
		final PngChunk c = chunkList.getById1(ChunkHelper.pHYs, true);
		if (c == null)
			return new double[] { -1, -1 };
		else
			return ((PngChunkPHYS) c).getAsDpi2();
	}

	public void setDpi(final double x) {
		setDpi(x, x);
	}

	public void setDpi(final double x, final double y) {
		final PngChunkPHYS c = new PngChunkPHYS(chunkList.imageInfo);
		c.setAsDpi2(x, y);
		queueChunk(c);
	}

	// //////////// TIME

	/**
	 * Creates a time chunk with current time, less secsAgo seconds
	 * <p>
	 * 
	 * @return Returns the created-queued chunk, just in case you want to
	 *         examine or modify it
	 */
	public PngChunkTIME setTimeNow(final int secsAgo) {
		final PngChunkTIME c = new PngChunkTIME(chunkList.imageInfo);
		c.setNow(secsAgo);
		queueChunk(c);
		return c;
	}

	public PngChunkTIME setTimeNow() {
		return setTimeNow(0);
	}

	/**
	 * Creates a time chunk with diven date-time
	 * <p>
	 * 
	 * @return Returns the created-queued chunk, just in case you want to
	 *         examine or modify it
	 */
	public PngChunkTIME setTimeYMDHMS(final int yearx, final int monx, final int dayx, final int hourx, final int minx,
			final int secx) {
		final PngChunkTIME c = new PngChunkTIME(chunkList.imageInfo);
		c.setYMDHMS(yearx, monx, dayx, hourx, minx, secx);
		queueChunk(c, true);
		return c;
	}

	/**
	 * null if not found
	 */
	public PngChunkTIME getTime() {
		return (PngChunkTIME) chunkList.getById1(ChunkHelper.tIME);
	}

	public String getTimeAsString() {
		final PngChunkTIME c = getTime();
		return c == null ? "" : c.getAsString();
	}

	// //////////// TEXT

	/**
	 * Creates a text chunk and queue it.
	 * <p>
	 * 
	 * @param k
	 *            : key (latin1)
	 * @param val
	 *            (arbitrary, should be latin1 if useLatin1)
	 * @param useLatin1
	 * @param compress
	 * @return Returns the created-queued chunks, just in case you want to
	 *         examine, touch it
	 */
	public PngChunkTextVar setText(final String k, final String val, final boolean useLatin1, final boolean compress) {
		if (compress && !useLatin1)
			throw new PngjException("cannot compress non latin text");
		PngChunkTextVar c;
		if (useLatin1) {
			if (compress)
				c = new PngChunkZTXT(chunkList.imageInfo);
			else
				c = new PngChunkTEXT(chunkList.imageInfo);
		} else {
			c = new PngChunkITXT(chunkList.imageInfo);
			((PngChunkITXT) c).setLangtag(k); // we use the same orig tag (this is not quite right)
		}
		c.setKeyVal(k, val);
		queueChunk(c, true);
		return c;
	}

	public PngChunkTextVar setText(final String k, final String val) {
		return setText(k, val, false, false);
	}

	/**
	 * gets all text chunks with a given key
	 * <p>
	 * returns null if not found
	 * <p>
	 * Warning: this does not check the "lang" key of iTxt
	 */
	@SuppressWarnings("unchecked")
	public List<? extends PngChunkTextVar> getTxtsForKey(final String k) {
		@SuppressWarnings("rawtypes")
		final List c = new ArrayList();
		c.addAll(chunkList.getById(ChunkHelper.tEXt, k));
		c.addAll(chunkList.getById(ChunkHelper.zTXt, k));
		c.addAll(chunkList.getById(ChunkHelper.iTXt, k));
		return c;
	}

	/**
	 * Returns empty if not found, concatenated (with newlines) if multiple! -
	 * and trimmed
	 * <p>
	 * Use getTxtsForKey() if you don't want this behaviour
	 */
	public String getTxtForKey(final String k) {
		final List<? extends PngChunkTextVar> li = getTxtsForKey(k);
		if (li.isEmpty())
			return "";
		final StringBuilder t = new StringBuilder();
		for (final PngChunkTextVar c : li)
			t.append(c.getVal()).append("\n");
		return t.toString().trim();
	}

	/**
	 * Returns the palette chunk, if present
	 * 
	 * @return null if not present
	 */
	public PngChunkPLTE getPLTE() {
		return (PngChunkPLTE) chunkList.getById1(PngChunkPLTE.ID);
	}

	/**
	 * Creates a new empty palette chunk, queues it for write and return it to
	 * the caller, who should fill its entries
	 */
	public PngChunkPLTE createPLTEChunk() {
		final PngChunkPLTE plte = new PngChunkPLTE(chunkList.imageInfo);
		queueChunk(plte);
		return plte;
	}

	/**
	 * Returns the TRNS chunk, if present
	 * 
	 * @return null if not present
	 */
	public PngChunkTRNS getTRNS() {
		return (PngChunkTRNS) chunkList.getById1(PngChunkTRNS.ID);
	}

	/**
	 * Creates a new empty TRNS chunk, queues it for write and return it to the
	 * caller, who should fill its entries
	 */
	public PngChunkTRNS createTRNSChunk() {
		final PngChunkTRNS trns = new PngChunkTRNS(chunkList.imageInfo);
		queueChunk(trns);
		return trns;
	}

}
