package jogamp.opengl.util.pngj.chunks;

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

import jogamp.opengl.util.pngj.ImageInfo;
import jogamp.opengl.util.pngj.PngjException;

/**
 * All chunks that form an image, read or to be written.
 * <p>
 * chunks include all chunks, but IDAT is a single pseudo chunk without data
 **/
public class ChunksList {
	// ref: http://www.w3.org/TR/PNG/#table53
	public static final int CHUNK_GROUP_0_IDHR = 0; // required - single
	public static final int CHUNK_GROUP_1_AFTERIDHR = 1; // optional - multiple
	public static final int CHUNK_GROUP_2_PLTE = 2; // optional - single
	public static final int CHUNK_GROUP_3_AFTERPLTE = 3; // optional - multple
	public static final int CHUNK_GROUP_4_IDAT = 4; // required (single pseudo chunk)
	public static final int CHUNK_GROUP_5_AFTERIDAT = 5; // optional - multple
	public static final int CHUNK_GROUP_6_END = 6; // only 1 chunk - requried

	/**
	 * All chunks, read (or written)
	 *
	 * But IDAT is a single pseudo chunk without data
	 */
	protected List<PngChunk> chunks = new ArrayList<PngChunk>();

	final ImageInfo imageInfo; // only required for writing

	public ChunksList(final ImageInfo imfinfo) {
		this.imageInfo = imfinfo;
	}

	/**
	 * Keys of processed (read or writen) chunks
	 *
	 * @return key:chunk id, val: number of occurrences
	 */
	public HashMap<String, Integer> getChunksKeys() {
		final HashMap<String, Integer> ck = new HashMap<String, Integer>();
		for (final PngChunk c : chunks) {
			ck.put(c.id, ck.containsKey(c.id) ? ck.get(c.id) + 1 : 1);
		}
		return ck;
	}

	/**
	 * Returns a copy of the list (but the chunks are not copied) <b> This
	 * should not be used for general metadata handling
	 */
	public ArrayList<PngChunk> getChunks() {
		return new ArrayList<PngChunk>(chunks);
	}

	protected static List<PngChunk> getXById(final List<PngChunk> list, final String id, final String innerid) {
		if (innerid == null)
			return ChunkHelper.filterList(list, new ChunkPredicate() {
				@Override
				public boolean match(final PngChunk c) {
					return c.id.equals(id);
				}
			});
		else
			return ChunkHelper.filterList(list, new ChunkPredicate() {
				@Override
				public boolean match(final PngChunk c) {
					if (!c.id.equals(id))
						return false;
					if (c instanceof PngChunkTextVar && !((PngChunkTextVar) c).getKey().equals(innerid))
						return false;
					if (c instanceof PngChunkSPLT && !((PngChunkSPLT) c).getPalName().equals(innerid))
						return false;
					return true;
				}
			});
	}

	/**
	 * Adds chunk in next position. This is used onyl by the pngReader
	 */
	public void appendReadChunk(final PngChunk chunk, final int chunkGroup) {
		chunk.setChunkGroup(chunkGroup);
		chunks.add(chunk);
	}

	/**
	 * All chunks with this ID
	 *
	 * @param id
	 * @return List, empty if none
	 */
	public List<? extends PngChunk> getById(final String id) {
		return getById(id, null);
	}

	/**
	 * If innerid!=null and the chunk is PngChunkTextVar or PngChunkSPLT, it's
	 * filtered by that id
	 *
	 * @param id
	 * @return innerid Only used for text and SPLT chunks
	 * @return List, empty if none
	 */
	public List<? extends PngChunk> getById(final String id, final String innerid) {
		return getXById(chunks, id, innerid);
	}

	/**
	 * Returns only one chunk
	 *
	 * @param id
	 * @return First chunk found, null if not found
	 */
	public PngChunk getById1(final String id) {
		return getById1(id, false);
	}

	/**
	 * Returns only one chunk or null if nothing found - does not include queued
	 * <p>
	 * If more than one chunk is found, then an exception is thrown
	 * (failifMultiple=true or chunk is single) or the last one is returned
	 * (failifMultiple=false)
	 **/
	public PngChunk getById1(final String id, final boolean failIfMultiple) {
		return getById1(id, null, failIfMultiple);
	}

	/**
	 * Returns only one chunk or null if nothing found - does not include queued
	 * <p>
	 * If more than one chunk (after filtering by inner id) is found, then an
	 * exception is thrown (failifMultiple=true or chunk is single) or the last
	 * one is returned (failifMultiple=false)
	 **/
	public PngChunk getById1(final String id, final String innerid, final boolean failIfMultiple) {
		final List<? extends PngChunk> list = getById(id, innerid);
		if (list.isEmpty())
			return null;
		if (list.size() > 1 && (failIfMultiple || !list.get(0).allowsMultiple()))
			throw new PngjException("unexpected multiple chunks id=" + id);
		return list.get(list.size() - 1);
	}

	/**
	 * Finds all chunks "equivalent" to this one
	 *
	 * @param c2
	 * @return Empty if nothing found
	 */
	public List<PngChunk> getEquivalent(final PngChunk c2) {
		return ChunkHelper.filterList(chunks, new ChunkPredicate() {
			@Override
			public boolean match(final PngChunk c) {
				return ChunkHelper.equivalent(c, c2);
			}
		});
	}

	@Override
	public String toString() {
		return "ChunkList: read: " + chunks.size();
	}

	/**
	 * for debugging
	 */
	public String toStringFull() {
		final StringBuilder sb = new StringBuilder(toString());
		sb.append("\n Read:\n");
		for (final PngChunk chunk : chunks) {
			sb.append(chunk).append(" G=" + chunk.getChunkGroup() + "\n");
		}
		return sb.toString();
	}

}
