package it.eng.jhove.module.png;

import edu.harvard.hul.ois.jhove.Agent;
import edu.harvard.hul.ois.jhove.AgentType;
import edu.harvard.hul.ois.jhove.Checksummer;
import edu.harvard.hul.ois.jhove.Document;
import edu.harvard.hul.ois.jhove.DocumentType;
import edu.harvard.hul.ois.jhove.ErrorMessage;
import edu.harvard.hul.ois.jhove.ExternalSignature;
import edu.harvard.hul.ois.jhove.Identifier;
import edu.harvard.hul.ois.jhove.IdentifierType;
import edu.harvard.hul.ois.jhove.InfoMessage;
import edu.harvard.hul.ois.jhove.InternalSignature;
import edu.harvard.hul.ois.jhove.ModuleBase;
import edu.harvard.hul.ois.jhove.OutputHandler;
import edu.harvard.hul.ois.jhove.Property;
import edu.harvard.hul.ois.jhove.PropertyType;
import edu.harvard.hul.ois.jhove.RepInfo;
import edu.harvard.hul.ois.jhove.Signature;
import edu.harvard.hul.ois.jhove.SignatureType;
import edu.harvard.hul.ois.jhove.SignatureUseType;
import it.eng.jhove.*;
import java.io.ByteArrayOutputStream;
import java.io.DataInputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.RandomAccessFile;
import java.util.GregorianCalendar;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.zip.DataFormatException;
import java.util.zip.Inflater;
import java.util.zip.CRC32;

/*
 * This is a module for Jhove - JSTOR/Harvard Object Validation
 * Environment
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or (at
 * your option) any later version.
 *
 * This program is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
 * USA
 *
 */

/**
 * Class <code>PngModule</code> A module for Jhove - JSTOR/Harvard
 * Object Validation Environment intended to recognize and validate
 * PNG files formatted according to the <a
 * href="http://www.w3.org/TR/PNG/">W3C Functional
 * specification. ISO/IEC 15948:2003 (E)</a>
 *
 * This implementation lacks the control of IDATA content against the
 * palette size
 *
 * For  some   reason,  it  needs  java.util.zip.CRC32   for  the  CRC
 * computation instead of using Jhove own.
 *
 * Created: Mon Sep 25 12:07:29 2006
 *
 * @author <a href="mailto:saint@eng.it">Gian Uberto Lauri</a>
 * @version $Revision$
 */

// TODO write the IDATA decompression algorithm to validate the
// contents against the palette size.
// This requires placing the IDATA data on a separate stream in
// order to rebuild the compressed stream. A temporary file is
// advisable since the data size could grow to invade the core of even
// the stronger application servers.
public class PngModule extends ModuleBase {
    
    //    private final Checksummer chcks = new Checksummer();

    // Strangely, it seems it doesn't work with the other checksummer...
    private final CRC32 chcks = new CRC32();

    // PNG signature
    private static final int SIGNATURE[] = {137, 80, 78, 71, 13, 10, 26, 10};

    // Module instantiation constants.
    private static final String NAME = "PNG-engineering";
    private static final String RELEASE = "1.0";
    private static final int DATE[] = {2006, 9, 25};
    private static final String FORMAT[] = {
		"PNG",
		"Portable Network Graphics"
	};
    private static final String MIMETYPE[] = {"image/png"};
    private static final String COVERAGE = "PNG";
    private static final String WELLFORMED = null;
    private static final String VALIDITY = null;
    private static final String REPINFO = null;
    private static final String NOTE = "Work in progress";
    private static final String RIGHTS =
		"Copyright 2006 Engineering Ingengeria Informatica S.p.a." +
		"Released under the GNU Lesser General Public License." +
		"Cryptoserver Library Copyright Engiweb Security, all rights reserved";
    private static final boolean RANDOM = false;

    /*
     * Chunk signatures.
     *
     * Java *IS* Big Endian, PNG chunk signatures are 4 byte strings we
     * *CAN* read into an int variable since all of them have bit 7
     * set to 0.
     *
     * Therefore we can check each chunk signature against int
     * constants (one opcode executed, no loops).
     *
     * About names: these name violate the Java naming rules for
     * constants, but I prefer to keep the PNG chunk name cases, since
     * they are meaningful for the properties of each chunk.
     */
    private final static int IHDR_HEAD_SIG = 0x49484452;
    private final static int PLTE_HEAD_SIG = 0x504c5445;
    private final static int IDAT_HEAD_SIG = 0x49444154;
    private final static int IEND_HEAD_SIG = 0x49454e44;
    private final static int cHRM_HEAD_SIG = 0x6348524d;
    private final static int gAMA_HEAD_SIG = 0x67414d41;
    private final static int iCCP_HEAD_SIG = 0x69434350;
    private final static int sBIT_HEAD_SIG = 0x73424954;
    private final static int sRGB_HEAD_SIG = 0x73524742;
    private final static int tEXt_HEAD_SIG = 0x74455874;
    private final static int zTXt_HEAD_SIG = 0x7a545874;
    private final static int iTXt_HEAD_SIG = 0x69545874;
    private final static int bKGD_HEAD_SIG = 0x624b4744;
    private final static int hIST_HEAD_SIG = 0x68495354;
    private final static int pHYs_HEAD_SIG = 0x70485973;
    private final static int sPLT_HEAD_SIG = 0x73504c54;
    private final static int tIME_HEAD_SIG = 0x74494d45;
    private final static int tRNS_HEAD_SIG = 0x74524e53;
    // Property bit masks
    private final static int PROP_SAFE_TO_COPY = 0x00000020;
    private final static int PROP_PRIVATE = 0x00002000;
    private final static int PROP_RESERVED = 0x00200000;
    private final static int PROP_ANCILLARY = 0x20000000;

    // Maximum keyword lenght
    private final static int MAX_KEYWORD_LEN = 79;

    // Standard keyword for the creation timestamp
    private final static String CREATION_TIME_KEYWORD = "Creation Time";

    private final static int MAX_INT = 0x7FFFFFFF;

    // Color Type
	private final static int EachPixel_GRAYSCALE = 0;
	private final static int EachPixel_RGB = 2;
	private final static int EachPixel_PALETTE = 3;
	private final static int EachPixel_GRAYSCALE_ALPHA = 4;
	private final static int EachPixel_RGB_ALPHA = 6;

    /*------------------------------------------------------------------*
      |******************************************************************|
      |*                                                                *|
      |* Parser inner state flags.                                      *|
      |*                                                                *|
      |* The state is represented by a score of flags associated to the *|
      |* chunks that should appear no more than once. The code uses the *|
      |* flags to manage the partial ordering in the chunk layout       *|
      |*                                                                *|
      |* Flags have the  value RepInfo.UNDETERMINED  when the chunk may *|
      |* either appear or not,  RepInfo.TRUE when the chunk is expected *|
      |* but has  yet to be found, RepInfo.FALSE when  the chunk should *|
      |* not appear any more. Istantiation values are for documentation *|
      |* purpose only and are repeated in the initParse() method.       *|
      |*                                                                *|
      |******************************************************************|
      *------------------------------------------------------------------*/

    /*
     * Starting chunk, must be the first one, expected.
     */
    private int expectingIHDR = RepInfo.TRUE;

    /*
     * This is 3 state flag: it is unknown until you know you need to
     * find the PLTE chunk, when it turs to RepInfo.TRUE or/and you
     * know you should not find such block any more (i.e. from color
     * type and bit depth or because you already got this chunk.
     */
    private int expectingPLTE = RepInfo.UNDETERMINED;

    /*
     * Data chunk, turns to RepInfo.false upon finding the firs chunk
     * of this type
     */
    private int expectingIDAT = RepInfo.TRUE;

    /*
     * Ending chunk, this flag is used to handle the end of file
     * condition, if it happens when the flag is RepInfo.TRUE; then
     * the file is not well formed.
     */
    private int expectingIEND = RepInfo.TRUE;

    // non critical chunks

    private int expecting_cHRM = RepInfo.UNDETERMINED;
    private int expecting_gAMA = RepInfo.UNDETERMINED;
    private int expecting_iCCP = RepInfo.UNDETERMINED;
    private int expecting_sBIT = RepInfo.UNDETERMINED;
    private int expecting_sRGB = RepInfo.UNDETERMINED;
    private int expecting_tEXt = RepInfo.UNDETERMINED;
    private int expecting_zTXt = RepInfo.UNDETERMINED;
    private int expecting_iTXt = RepInfo.UNDETERMINED;
    private int expecting_bKGD = RepInfo.UNDETERMINED;
    private int expecting_hIST = RepInfo.UNDETERMINED;
    private int expecting_pHYs = RepInfo.UNDETERMINED;
    private int expecting_sPLT = RepInfo.UNDETERMINED;
    private int expecting_tIME = RepInfo.UNDETERMINED;
    private int expecting_tRNS = RepInfo.UNDETERMINED;

    // Palette size, in colours
    private int maxPaletteSize = 0;
    private int paletteSize = 0;
    private boolean shortPalette = false;
    private int colorDepth = 0;

    private Map keywordList;

    private final static String PNG_PROFILES[] =
		new String[] { "PNG GrayScale",             // 0
					   "Unused",                    // 1
					   "PNG Truecolor",             // 2
					   "PNG Indexed",               // 3
					   "PNG GrayScale with Alpha",  // 4
					   "PNG Truecolor with Alpha"}; // 5

    public static final boolean PNG_ENDIANITY=true;

    private final static String TITLE_KEYWORD = "Title";
    private final static String AUTHOR_KEYWORD = "Author";
    private final static String DESCRIPTION_KEYWORD = "Description";
    private final static String COPYRIGHT_KEYWORD = "Copyright";
    private final static String SOFTWARE_KEYWORD = "Software";
    private final static String DISCLAIMER_KEYWORD = "Disclaimer";
    private final static String WARNING_KEYWORD = "Warning";
    private final static String SOURCE_KEYWORD = "Source";
    private final static String COMMENT_KEYWORD = "Comment";
    private final static String PLTE_NOT_FOUND= "Expected PLTE chunk not found.";
    private final static String GAMA_AFTER_IDAT= "gAMA chunk found after IDAT ones.";
    private final static String EXTRA_GAMA= "Extra gAMA chunk found.";
    private final static String TEXT= "tEXT";
    private final static String ILLEGAL_BIT_DEPTH= "In IHDR, illegal value for bit depth for colour type ";
    private final static String ILLEGALE_PROFONDITA_DEI_BIT= "In IHDR, valore illegale per la profondita` dei bit per il colour type ";
    private final static String NO_ANCORA_W3C= " no ancora standardizzato dal W3C.";  
    
    /**
     * Crea una nuova istanza di <code>PngModule</code> .
     *
     */
    public PngModule() {
		super (NAME, RELEASE, DATE, FORMAT, COVERAGE, MIMETYPE, WELLFORMED,
			   VALIDITY, REPINFO, NOTE, RIGHTS, RANDOM);

		keywordList = new HashMap();

		keywordList.put(TITLE_KEYWORD,
						new Booolean(false,TITLE_KEYWORD));           //  Short (one line) title or caption for image
		keywordList.put(AUTHOR_KEYWORD,
						new Booolean(false,AUTHOR_KEYWORD));          //  Name of image's creator
		keywordList.put(DESCRIPTION_KEYWORD,
						new Booolean(false,DESCRIPTION_KEYWORD));     //  Description of image (possibly long)
		keywordList.put(COPYRIGHT_KEYWORD,
						new Booolean(false,COPYRIGHT_KEYWORD));       //  Copyright notice
		keywordList.put(CREATION_TIME_KEYWORD,
						new Booolean(false,CREATION_TIME_KEYWORD)); //  Time of original image creation
		keywordList.put(SOFTWARE_KEYWORD,
						new Booolean(false,SOFTWARE_KEYWORD));        //  Software used to create the image
		keywordList.put(DISCLAIMER_KEYWORD,
						new Booolean(false,DISCLAIMER_KEYWORD));      //  Legal disclaimer
		keywordList.put(WARNING_KEYWORD,
						new Booolean(false,WARNING_KEYWORD));         //  Warning of nature of content
		keywordList.put(SOURCE_KEYWORD,
						new Booolean(false,SOURCE_KEYWORD));          //  Device used to create the image
		keywordList.put(COMMENT_KEYWORD,
						new Booolean(false,COMMENT_KEYWORD));         //  Miscellaneous comment
    }
    // Implementation of edu.harvard.hul.ois.jhove.Module

    /**
     * <code>init</code>
     *
     * @param string a <code>String</code>
     * @exception Exception
     */
    public final void init(final String string) throws Exception {

    }

    /**
     * <code>initParse</code> initializes the status of the parser.
     *
     */
    public final void initParse() {
		super.initParse();
		expectingIHDR = RepInfo.TRUE;
		expectingPLTE = RepInfo.UNDETERMINED;
		expectingIDAT = RepInfo.TRUE;
		expectingIEND = RepInfo.TRUE;
		expecting_cHRM = RepInfo.UNDETERMINED;
		expecting_gAMA = RepInfo.UNDETERMINED;
		expecting_iCCP = RepInfo.UNDETERMINED;
		expecting_sBIT = RepInfo.UNDETERMINED;
		expecting_sRGB = RepInfo.UNDETERMINED;
		expecting_tEXt = RepInfo.UNDETERMINED;
		expecting_zTXt = RepInfo.UNDETERMINED;
		expecting_iTXt = RepInfo.UNDETERMINED;
		expecting_bKGD = RepInfo.UNDETERMINED;
		expecting_hIST = RepInfo.UNDETERMINED;
		expecting_pHYs = RepInfo.UNDETERMINED;
		expecting_sPLT = RepInfo.UNDETERMINED;
		expecting_tIME = RepInfo.UNDETERMINED;
		expecting_tRNS = RepInfo.UNDETERMINED;

		paletteSize = 0;
		maxPaletteSize = 0;
		shortPalette = false;
		colorDepth = 0;

		for (Iterator i = keywordList.values().iterator(); i.hasNext(); ) {
			((Booolean) i.next()).setFlag(false);
		}

    }

    /**
     * <code>parse</code> Parse the content of a stream PNG image and
     * store the results in RepInfo
     *
     * @param inputStream an <code>InputStream</code> An InputStream,
     * positioned at its beginning, which is generated from the object
     * to be parsed. If multiple calls to <code>parse</code> are made
     * on the basis of a nonzero value being returned, a new
     * InputStream must be provided each time.
     * @param repInfo a <code>RepInfo</code> A fresh (on the first
     * call) RepInfo object which will be modified to reflect the
     * results of the parsing If multiple calls to parse are made on
     * the basis of a nonzero value being returned, the same RepInfo
     * object should be passed with each call.
     * @param n an <code>int</code> Must be 0 in first call to
     * <code>parse<code>. If <code>parse<code> returns a nonzero
     * value, it must be called again with parseIndex equal to that
     * return value.
     * @return an <code>int</code>
     * @exception IOException
     */
    public final int parse(final InputStream inputStream,
						   final RepInfo repInfo,
						   final int n) throws IOException {

		StringBuffer sigName = new StringBuffer();

		initParse();

		// I have to pass it to each method, WHY SHOULD I USE a class
		// instance ???
		DataInputStream dstream = getBufferedDataStream (inputStream,
														 _app != null ?
														 _je.getBufferSize () : 0);
		Agent agent = new Agent ("Harvard University Library",
								 AgentType.EDUCATIONAL);
		agent.setAddress ("Engineering Ingegneria Informatica S.p.a., " +
						  "Direzione Supporto e Servizi Tecnologici, " +
						  "Corso Stati Uniti 23/A, 25100 Padova.");
		agent.setTelephone ("+39 (49) 8283-411");
		agent.setEmail("saint@eng.it");
		_vendor = agent;

		Document doc = new Document ("PNG (Portable Network Graphics): a file format (pronounced \"ping\"), for a lossless, portable, compressed individual computer graphics image transmitted across the Internet. Indexed-colour, greyscale, and truecolour images are supported, with optional transparency",
									 DocumentType.REPORT);
		agent = new Agent ("W3 Consortium",
						   AgentType.STANDARD);
		agent.setAddress ("E.M.E.A.: ERCIM, 2004 route des Lucioles, BP 93, 06902 Sophia-Antipolis Cedex, France\nJapan & Korea: Keio University, 5322 Endo, Fujisawa, Kanagawa 252-8520 Japan\nAll other countries: MIT, 32 Vassar Street, Room 32-G515, Cambridge, MA 02139 USA");
		agent.setTelephone ("E.M.E.A. : +33.4.92.38.75.90\nJapan & Korea: +81.466.49.1170\nAll other countries: +1.617.253.2613");
		agent.setWeb ("http://www.w3.org/");
		doc.setAuthor (agent);
		doc.setDate ("2003-11-10");
		doc.setIdentifier (new Identifier ("http://www.w3.org/Graphics/GIF/spec-gif87.txt",
										   IdentifierType.URL));
		_specification.add (doc);

		Signature sig = new InternalSignature (COVERAGE, SignatureType.MAGIC,
											   SignatureUseType.MANDATORY, 0);
		_signature.add (sig);

		sig = new ExternalSignature (".png", SignatureType.EXTENSION,
									 SignatureUseType.OPTIONAL);
		_signature.add (sig);

		_bigEndian = false;
		// end of parsing prologue.
		if (! checkSignBytes(dstream,SIGNATURE)) {
			repInfo.setMessage(new ErrorMessage ("Bad PNG Header", 0));
			repInfo.setWellFormed (RepInfo.FALSE);
			return 0;
		}
		repInfo.setFormat(COVERAGE);

		// If we got this far, take note that the signature is OK.
		repInfo.setSigMatch(_name);
		repInfo.setModule(this);
		// First chunk MUST be IHDR
		int declChunkLen = (int)(readUnsignedInt(dstream, PNG_ENDIANITY, this)
								 &MAX_INT);
		chcks.reset();

		int chunkSig = (int)(readUnsignedInt(dstream, PNG_ENDIANITY, this)&MAX_INT);
		chcks.update(int2byteArray(chunkSig));

		if (chunkSig != IHDR_HEAD_SIG ) {
			repInfo.setWellFormed(RepInfo.FALSE);
			repInfo.setMessage(new ErrorMessage("IHDR header not found where expected." ));
			return 0;
		}
		else {
			checkIHDR(dstream,repInfo,declChunkLen);

			if (somethingWrongP(repInfo))
				return 0;

			// not sure it's useful...
			expectingIHDR = RepInfo.FALSE;
		}

		// The IHDR is where it should be and it's fine, now let's
		// handle the other chunks.



		while (expectingIEND == RepInfo.TRUE) {
			declChunkLen = (int)(readUnsignedInt(dstream, PNG_ENDIANITY, this)
								 &MAX_INT);
			// Each chunk has its checsum;
			chcks.reset();

			chunkSig = (int)(readUnsignedInt(dstream, PNG_ENDIANITY, this)&MAX_INT);
			chcks.update(int2byteArray(chunkSig));

			switch (chunkSig) {

			case IHDR_HEAD_SIG:
				repInfo.setWellFormed(RepInfo.FALSE);
				repInfo.setMessage(new ErrorMessage("Duplicated IHDR chunk." ));
				break;

			case PLTE_HEAD_SIG:
				if (expectingPLTE == RepInfo.FALSE) {
					repInfo.setWellFormed(RepInfo.FALSE);
					repInfo.setMessage(new ErrorMessage("Unexpected or duplicated PLTE chunk." ));

					break;
				}
				checkPLTE(dstream,repInfo,declChunkLen);
				expectingPLTE = RepInfo.FALSE;

				break;

			case IDAT_HEAD_SIG:
				if (expectingPLTE == RepInfo.TRUE) {
					repInfo.setWellFormed(RepInfo.FALSE);
					repInfo.setMessage(new ErrorMessage(PLTE_NOT_FOUND ));

					break;
				}

				checkChunk(dstream, repInfo, declChunkLen,"IDAT");
				expectingIDAT = RepInfo.FALSE;
				break;

			case IEND_HEAD_SIG:
				if (expectingPLTE == RepInfo.TRUE) {
					repInfo.setWellFormed(RepInfo.FALSE);
					repInfo.setMessage(new ErrorMessage(PLTE_NOT_FOUND));

					break;
				}
				if (expectingIDAT == RepInfo.TRUE) {
					repInfo.setWellFormed(RepInfo.FALSE);
					repInfo.setMessage(new ErrorMessage("No IDAT chunk found." ));

					break;
				}
				checkChunk(dstream, repInfo, declChunkLen,"IEND");
				expectingIEND = RepInfo.FALSE;
				break;

			case cHRM_HEAD_SIG:
				if (expectingPLTE == RepInfo.FALSE) {
					repInfo.setWellFormed(RepInfo.FALSE);
					repInfo.setMessage(new ErrorMessage("cHRM chunk found after PLTE one." ));

					break;
				}
				if (expectingIDAT == RepInfo.FALSE) {
					repInfo.setWellFormed(RepInfo.FALSE);
					repInfo.setMessage(new ErrorMessage("cHRM chunk found after IDAT ones." ));


					break;
				}
				if (expecting_cHRM == RepInfo.FALSE) {
					repInfo.setWellFormed(RepInfo.FALSE);
					repInfo.setMessage(new ErrorMessage("Extra cHRM chunk found." ));


					break;
				}

				checkChunk(dstream, repInfo, declChunkLen,"cHRM");
				expecting_cHRM = RepInfo.FALSE;

				break;

			case gAMA_HEAD_SIG:
				if (expectingPLTE == RepInfo.FALSE) {
					repInfo.setWellFormed(RepInfo.FALSE);
					repInfo.setMessage(new ErrorMessage("gAMA chunk found after PLTE one." ));

					break;
				}
				if (expectingIDAT == RepInfo.FALSE) {
					repInfo.setWellFormed(RepInfo.FALSE);
					repInfo.setMessage(new ErrorMessage(GAMA_AFTER_IDAT));

					break;
				}
				if (expecting_gAMA == RepInfo.FALSE) {
					repInfo.setWellFormed(RepInfo.FALSE);
					repInfo.setMessage(new ErrorMessage(EXTRA_GAMA ));

					break;
				}

				checkChunk(dstream, repInfo, declChunkLen,"gAMA");
				expecting_gAMA = RepInfo.FALSE;

				break;

			case iCCP_HEAD_SIG:

				if (expectingPLTE == RepInfo.FALSE) {
					repInfo.setWellFormed(RepInfo.FALSE);
					repInfo.setMessage(new ErrorMessage("iCCP chunk found after PLTE one." ));

					break;
				}
				if (expectingIDAT == RepInfo.FALSE) {
					repInfo.setWellFormed(RepInfo.FALSE);
					repInfo.setMessage(new ErrorMessage("iCCP chunk found after IDAT ones." ));

					break;
				}
				if (expecting_iCCP == RepInfo.FALSE) {
					repInfo.setWellFormed(RepInfo.FALSE);
					repInfo.setMessage(new ErrorMessage("Extra iCCP chunk found." ));

					break;
				}
				if (expecting_sRGB == RepInfo.FALSE) {
					repInfo.setWellFormed(RepInfo.FALSE);
					repInfo.setMessage(new ErrorMessage("iCCP chunk with sRGB chunk found." ));

					break;
				}

				checkChunk(dstream, repInfo, declChunkLen,"iCCP");
				expecting_iCCP = RepInfo.FALSE;
				expecting_sRGB = RepInfo.FALSE;
				break;

			case sBIT_HEAD_SIG:
				if (expectingPLTE == RepInfo.FALSE) {
					repInfo.setWellFormed(RepInfo.FALSE);
					repInfo.setMessage(new ErrorMessage("sBIT chunk found after PLTE one." ));

					break;
				}
				if (expectingIDAT == RepInfo.FALSE) {
					repInfo.setWellFormed(RepInfo.FALSE);
					repInfo.setMessage(new ErrorMessage("sBIT chunk found after IDAT ones." ));

					break;
				}
				if (expecting_sBIT == RepInfo.FALSE) {
					repInfo.setWellFormed(RepInfo.FALSE);
					repInfo.setMessage(new ErrorMessage("Extra sBIT chunk found." ));

					break;
				}

				checkChunk(dstream, repInfo, declChunkLen,"sBIT");
				expecting_sBIT = RepInfo.FALSE;


				break;

			case sRGB_HEAD_SIG:
				if (expectingPLTE == RepInfo.FALSE) {
					repInfo.setWellFormed(RepInfo.FALSE);
					repInfo.setMessage(new ErrorMessage("sRGB chunk found after PLTE one." ));

					break;
				}
				if (expectingIDAT == RepInfo.FALSE) {
					repInfo.setWellFormed(RepInfo.FALSE);
					repInfo.setMessage(new ErrorMessage("sRGB chunk found after IDAT ones." ));

					break;
				}
				if (expecting_sRGB == RepInfo.FALSE) {
					repInfo.setWellFormed(RepInfo.FALSE);
					repInfo.setMessage(new ErrorMessage("Extra sRGB chunk found." ));

					break;
				}
				if (expecting_iCCP == RepInfo.FALSE) {
					repInfo.setWellFormed(RepInfo.FALSE);
					repInfo.setMessage(new ErrorMessage("iCPP chunk after sRGB chunk found." ));

					break;
				}

				checkChunk(dstream, repInfo, declChunkLen,"sRGB");
				expecting_sRGB = RepInfo.FALSE;
				expecting_iCCP = RepInfo.FALSE;

				break;

			case tEXt_HEAD_SIG:

				checkChunk(dstream, repInfo, declChunkLen, TEXT);
				break;

			case zTXt_HEAD_SIG:

				checkChunk(dstream, repInfo, declChunkLen,"zEXT");
				break;

			case iTXt_HEAD_SIG:

				checkChunk(dstream, repInfo, declChunkLen,"iEXT");
				break;

			case bKGD_HEAD_SIG:

				if (expectingPLTE == RepInfo.TRUE) {
					repInfo.setWellFormed(RepInfo.FALSE);
					repInfo.setMessage(new ErrorMessage("gAMA chunk found before PLTE one." ));


					break;
				}
				if (expectingIDAT == RepInfo.FALSE) {
					repInfo.setWellFormed(RepInfo.FALSE);
					repInfo.setMessage(new ErrorMessage(GAMA_AFTER_IDAT ));

					break;
				}
				if (expecting_gAMA == RepInfo.FALSE) {
					repInfo.setWellFormed(RepInfo.FALSE);
					repInfo.setMessage(new ErrorMessage(EXTRA_GAMA ));

					break;
				}

				checkChunk(dstream, repInfo, declChunkLen,"bKGRD");
				expecting_gAMA = RepInfo.FALSE;
				break;

			case hIST_HEAD_SIG:

				if (expectingPLTE == RepInfo.TRUE) {
					repInfo.setWellFormed(RepInfo.FALSE);
					repInfo.setMessage(new ErrorMessage("hIST chunk found before PLTE one." ));

					break;
				}
				if (expectingIDAT == RepInfo.FALSE) {
					repInfo.setWellFormed(RepInfo.FALSE);
					repInfo.setMessage(new ErrorMessage("hIST chunk found after IDAT ones." ));

					break;
				}
				if (expecting_hIST == RepInfo.FALSE) {
					repInfo.setWellFormed(RepInfo.FALSE);
					repInfo.setMessage(new ErrorMessage("Extra hIST chunk found." ));

					break;
				}

				checkChunk(dstream, repInfo, declChunkLen,"hIST");
				expecting_hIST = RepInfo.FALSE;
				break;

			case tRNS_HEAD_SIG:

				if (expectingPLTE == RepInfo.TRUE) {
					repInfo.setWellFormed(RepInfo.FALSE);
					repInfo.setMessage(new ErrorMessage("tRNS chunk found before PLTE one." ));

					break;
				}
				if (expectingIDAT == RepInfo.FALSE) {
					repInfo.setWellFormed(RepInfo.FALSE);
					repInfo.setMessage(new ErrorMessage("tRNS chunk found after IDAT ones." ));

					break;
				}
				if (expecting_tRNS == RepInfo.FALSE) {
					repInfo.setWellFormed(RepInfo.FALSE);
					repInfo.setMessage(new ErrorMessage("Extra tRNS chunk found." ));

					break;
				}

				checkChunk(dstream, repInfo, declChunkLen,"tRNS");
				expecting_tRNS = RepInfo.FALSE;
				break;

			case pHYs_HEAD_SIG:

				if (expectingIDAT == RepInfo.FALSE) {
					repInfo.setWellFormed(RepInfo.FALSE);
					repInfo.setMessage(new ErrorMessage("pHYs chunk found after IDAT ones." ));

					break;
				}
				if (expecting_pHYs == RepInfo.FALSE) {
					repInfo.setWellFormed(RepInfo.FALSE);
					repInfo.setMessage(new ErrorMessage("Extra pHYs chunk found." ));

					break;
				}

				checkChunk(dstream, repInfo, declChunkLen,"pHYs");
				expecting_pHYs = RepInfo.FALSE;
				break;

			case sPLT_HEAD_SIG:

				if (expectingIDAT == RepInfo.FALSE) {
					repInfo.setWellFormed(RepInfo.FALSE);
					repInfo.setMessage(new ErrorMessage("sPLT chunk found after IDAT ones." ));

					break;
				}
				checkChunk(dstream, repInfo, declChunkLen,"sPLT");

				break;

			case tIME_HEAD_SIG:
				if (expecting_tIME == RepInfo.FALSE) {
					repInfo.setWellFormed(RepInfo.FALSE);
					repInfo.setMessage(new ErrorMessage("Extra tIME chunk found." ));

					break;
				}

				checktIME(dstream, repInfo, declChunkLen);
				expecting_tIME = RepInfo.FALSE;
				break;

			default:
				// Some strong choices. Reject undefined non ancillary chunks
				if (( chunkSig & ( PROP_ANCILLARY  ) ) == 0) {
					repInfo.setValid(RepInfo.FALSE);
					repInfo.setMessage(new ErrorMessage("Unknown non ancillary chunk found. Datastream not interpretable!" ));

					break;

				}

				sigName.delete(0, sigName.length());

				sigName.append((char)((chunkSig & 0x7F000000) >>> 24) );
				sigName.append((char)((chunkSig & 0x007F0000) >>> 16) );
				sigName.append((char)((chunkSig & 0x00007F00) >>> 8) );
				sigName.append((char)((chunkSig & 0x0000007F)) );
				// a private chunk, check the CRC
				checkChunk(dstream, repInfo, declChunkLen, sigName.toString());
				break;
			}


			if (somethingWrongP(repInfo))
				return 0;

		}


		// epilogue
		if (expectingPLTE == RepInfo.TRUE) {
			repInfo.setWellFormed(RepInfo.FALSE);
			repInfo.setMessage(new ErrorMessage("PLTE chunk not found (but was expected)." ));

		}


		return 0;
    }

    /**
     * <code>parse</code>
     *
     * @param randomAccessFile a <code>RandomAccessFile</code>
     * @param repInfo a <code>RepInfo</code>
     * @exception IOException
     */
    public final void parse(final RandomAccessFile randomAccessFile,
							final RepInfo repInfo) throws IOException {
		// Not implemented.
    }

    /**
     * <code>applyDefaultParams</code>
     *
     * @exception Exception
     */
    public final void applyDefaultParams() throws Exception {

    }

    /**
     * <code>resetParams</code>
     *
     * @exception Exception
     */
    public final void resetParams() throws Exception {

    }

    /**
     * <code>param</code>
     *
     * @param string a <code>String</code>
     * @exception Exception
     */
    public final void param(final String string) throws Exception {

    }

    /**
     * <code>setVerbosity</code>
     *
     * @param n an <code>int</code>
     */
    public final void setVerbosity(final int n) {

    }

    /**
     * <code>getDefaultParams</code>
     *
     * @return a <code>List</code>
     */
    public final List getDefaultParams() {
		return null;
    }

    /**
     * <code>checkSignatures</code>
     *
     * @param file a <code>File</code>
     * @param inputStream an <code>InputStream</code>
     * @param repInfo a <code>RepInfo</code>
     * @exception IOException
     */
    public final void checkSignatures(final File file,
									  final InputStream inputStream,
									  final RepInfo repInfo) throws IOException {
		DataInputStream dis = getBufferedDataStream (inputStream, _app != null ?
													 _je.getBufferSize () : 0);
		if (! checkSignBytes(dis,SIGNATURE)) {
			repInfo.setConsistent (false);
			return;
		}
    }

    /**
     * <code>checkSignatures</code> Not used
     *
     * @param file a <code>File</code>
     * @param stream a <code>InputStream</code>
     * @param repInfo a <code>RepInfo</code>
     * @exception IOException
     */
    public final void checkSignatures(final File file,
									  final RandomAccessFile stream,
									  final RepInfo repInfo)
		throws IOException {
		// Not used.
    }

    /**
     * <code>show</code>
     *
     * @param outputHandler an <code>OutputHandler</code>
     */
    public final void show(final OutputHandler outputHandler) {

    }


    private final boolean checkSignBytes(final DataInputStream inputStream,
										 final int sigBytes[])
		throws IOException {
		int max = sigBytes.length;
		int c;
		boolean rv = true;

		for (int i = 0; i < max; i++) {
			c = readUnsignedByte(inputStream,this);
			rv &= (c == sigBytes[i]);
		}

		return rv;
    }


    private final void checkIHDR(final DataInputStream inputStream,
								 final RepInfo repInfo,
								 final int declChunkLen)
		throws IOException {

		// W3C recommendations states that height and width are integers that
		// range from 0 to 2^31
		int tmp = (int)(readUnsignedInt(inputStream, PNG_ENDIANITY, this)&0xFFFFFFFF);
		chcks.update(int2byteArray(tmp));

		if (tmp == 0) {
			repInfo.setValid(RepInfo.FALSE);
			repInfo.setMessage(new ErrorMessage("Illegal 0 value for height." ));

		}

		tmp = (int)(readUnsignedInt(inputStream, PNG_ENDIANITY, this)&0xFFFFFFFF);
		chcks.update(int2byteArray(tmp));

		if (tmp == 0) {
			repInfo.setValid(RepInfo.FALSE);
			repInfo.setMessage(new ErrorMessage("Illegal 0 value for width." ));

		}

		tmp = readUnsignedByte(inputStream, this);
		chcks.update((byte)tmp);

		int colorType = readUnsignedByte(inputStream, this);
		chcks.update((byte)colorType);

		switch (colorType) {
		case EachPixel_GRAYSCALE:
			if (tmp != 1 &&
				tmp != 2 &&
				tmp != 4 &&
				tmp != 8 &&
				tmp != 16) {
				repInfo.setValid(RepInfo.FALSE);
				repInfo.setMessage(new ErrorMessage(ILLEGAL_BIT_DEPTH +

													colorType + ": " +tmp ));

			}
			repInfo.setProfile(PNG_PROFILES[0]);

			expectingPLTE=RepInfo.FALSE;
		case EachPixel_PALETTE:
			if (tmp != 1 &&
				tmp != 2 &&
				tmp != 4 &&
				tmp != 8 ) {
				repInfo.setValid(RepInfo.FALSE);
				repInfo.setMessage(new ErrorMessage(ILLEGAL_BIT_DEPTH +
													colorType + ": " +tmp ));

			}
			// We need to find a palette!
			expectingPLTE = RepInfo.TRUE;
			colorDepth = tmp;
			maxPaletteSize = 1 << tmp ;
			repInfo.setProfile(PNG_PROFILES[3]);

			break;
		case EachPixel_GRAYSCALE_ALPHA:
			expectingPLTE=RepInfo.FALSE;
			if (tmp != 8 &&
				tmp != 16) {
				repInfo.setValid(RepInfo.FALSE);
				repInfo.setMessage(new ErrorMessage(ILLEGALE_PROFONDITA_DEI_BIT +
													colorType + ": " +tmp ));

			}

			repInfo.setProfile(PNG_PROFILES[4]);
			break;
		case EachPixel_RGB_ALPHA:
			expectingPLTE=RepInfo.FALSE;
			expecting_tRNS=RepInfo.FALSE;
			if (tmp != 8 &&
				tmp != 16) {
				repInfo.setValid(RepInfo.FALSE);
				repInfo.setMessage(new ErrorMessage(ILLEGALE_PROFONDITA_DEI_BIT +
													colorType + ": " +tmp ));

			}
			repInfo.setProfile(PNG_PROFILES[5]);
			break;
		case EachPixel_RGB:
			expectingPLTE=RepInfo.FALSE;
			expecting_tRNS=RepInfo.FALSE;
			if (tmp != 8 &&
				tmp != 16) {
				repInfo.setValid(RepInfo.FALSE);
				repInfo.setMessage(new ErrorMessage(ILLEGALE_PROFONDITA_DEI_BIT +
													colorType + ": " +tmp ));

			}

			repInfo.setProfile(PNG_PROFILES[2]);
			break;
		default:
			repInfo.setValid(RepInfo.FALSE);
			repInfo.setMessage(new ErrorMessage("In IHDR, valore illegale per il colour type (" +
												colorType +")"));

			break;
		}

		// Compression
		tmp = readUnsignedByte(inputStream, this);
		chcks.update((byte)tmp);

		if (tmp!=0) {
			repInfo.setMessage(new InfoMessage("Attenzione, tipo di compressine " +
											   tmp + " not conforme alla raccommandazione del W3C."));
		}


		// filtering
		tmp = readUnsignedByte(inputStream, this);
		chcks.update((byte)tmp);

		if (tmp!=0) {
			repInfo.setMessage(new InfoMessage("Attenzione, tipo di filtro " +
											   tmp + NO_ANCORA_W3C));
		}

		// interlace

		tmp = readUnsignedByte(inputStream, this);
		chcks.update((byte)tmp);

		if (tmp!=0 && tmp!=1) {
			repInfo.setMessage(new InfoMessage("Attenzione, tipo di interlacciamento " +
											   tmp + NO_ANCORA_W3C));
		}

		long crc32 = readUnsignedInt(inputStream, PNG_ENDIANITY, this);

		if (crc32 != chcks.getValue()) {
			repInfo.setValid(RepInfo.FALSE);
			repInfo.setMessage(new ErrorMessage("Errore CRC nel chunk IHDR" ));
		}

    }

    private final void checkPLTE(final DataInputStream inputStream,
								 final RepInfo repInfo,
								 final int declChunkLen)
		throws IOException {

		if ((declChunkLen % 3) != 0) {
			repInfo.setValid(RepInfo.FALSE);
			repInfo.setMessage(new ErrorMessage("Lunghezza PLTE non valida." ));
		}


		// Scan the palette
		paletteSize=0;

		for (int i = 0; i < declChunkLen; i++) {
			int tmp = readUnsignedByte(inputStream, this);
			chcks.update((byte)tmp);

			tmp = readUnsignedByte(inputStream, this);
			chcks.update((byte)tmp);

			tmp = readUnsignedByte(inputStream, this);
			chcks.update((byte)tmp);

			paletteSize++;

		}

		if (paletteSize > maxPaletteSize) {
			repInfo.setValid(RepInfo.FALSE);
			repInfo.setMessage(new ErrorMessage("Too many palette items in PLTE chunk" ));


		}
		shortPalette = (paletteSize < maxPaletteSize);
		long crc32 = readUnsignedInt(inputStream, PNG_ENDIANITY, this);

		if (crc32 != chcks.getValue()) {
			repInfo.setValid(RepInfo.FALSE);
			repInfo.setMessage(new ErrorMessage("CRC Error in PLTE chunk" ));

		}
    }

    private final void checktIME(final DataInputStream inputStream,
								 final RepInfo repInfo,
								 final int declChunkLen)
		throws IOException {
		int yhrHigh = readUnsignedByte(inputStream, this);
		chcks.update((byte)yhrHigh);

		int yhrLow = readUnsignedByte(inputStream, this);
		chcks.update((byte)yhrLow);

		int month = readUnsignedByte(inputStream, this);
		chcks.update((byte)month);

		if (month < 1 || month > 12) {
			repInfo.setValid(RepInfo.FALSE);
			repInfo.setMessage(new ErrorMessage("Illegal month value in tIME chunk"));

		}

		int day = readUnsignedByte(inputStream, this);
		chcks.update((byte)day);

		if (day < 1 || day > 31) {
			repInfo.setValid(RepInfo.FALSE);
			repInfo.setMessage(new ErrorMessage("Illegal day value in tIME chunk"));

		}

		int hour = readUnsignedByte(inputStream, this);
		chcks.update((byte)hour);

		if (hour < 0 || hour > 23) {
			repInfo.setValid(RepInfo.FALSE);
			repInfo.setMessage(new ErrorMessage("Illegal hour value in tIME chunk"));

		}

		int minute = readUnsignedByte(inputStream, this);
		chcks.update((byte)minute);

		if (minute < 0 || minute > 59) {
			repInfo.setValid(RepInfo.FALSE);
			repInfo.setMessage(new ErrorMessage("Illegal minute value in tIME chunk"));

		}

		int second = readUnsignedByte(inputStream, this);
		chcks.update((byte)second);

		if (second < 0 || second > 60) {
			repInfo.setValid(RepInfo.FALSE);
			repInfo.setMessage(new ErrorMessage("Illegal second value in tIME chunk"));

		}

		long crc32 = readUnsignedInt(inputStream, PNG_ENDIANITY, this);

		if (crc32 != chcks.getValue()) {
			repInfo.setValid(RepInfo.FALSE);
			repInfo.setMessage(new ErrorMessage("CRC Error in tIME chunk" ));

		}

		// Some operations to deal with GregorianCalendar class, that
		// has 0 base  month numbering and doesn't like leap seconds
		second = (second == 60) ? 59 : second;
		month--;
		repInfo.setLastModified(new GregorianCalendar((yhrHigh<<8)+yhrLow ,
													  month,
													  day,
													  hour,
													  minute,
													  second).getTime());

    }


    private final void checktEXT(final DataInputStream inputStream,
								 final RepInfo repInfo,
								 int declChunkLen)
		throws IOException {
		int c=-1;
		int keywordLen=0;
		StringBuffer buf = new StringBuffer();

		while (keywordLen < MAX_KEYWORD_LEN) {
			c = readUnsignedByte(inputStream, this);
			chcks.update((byte)c);

			declChunkLen--;

			if (c==0) {
				break;
			}

			keywordLen++;
			buf.append((char)c);
		}


		if (keywordLen == MAX_KEYWORD_LEN) {
			// we hit MAX_KEYWORD_LEN, let's check if there's the
			// mandatory 0 byte
			c = readUnsignedByte(inputStream, this);
			chcks.update((byte)c);
			declChunkLen--;

			if (c != 0) {
				// segnalare errore e scartare
				repInfo.setValid(RepInfo.FALSE);
				repInfo.setMessage(new ErrorMessage("Missing 0 byte after keyword"));

				checkChunk(inputStream, repInfo, declChunkLen, TEXT);
				buf.append((char)c);

			}


			return;
		}

		String keyword = buf.toString();

		// so far we got a keyword and the null ( 0 ) separator, lets'
		// get the value, set a property and check that everything is
		// OK with the CRC.

		buf.delete(0, buf.length() );

		while (declChunkLen > 0) {
			c = readUnsignedByte(inputStream, this);
			chcks.update((byte)c);
			declChunkLen--;

		}

		String value = buf.toString();

		Property p = new Property(keyword,
								  PropertyType.STRING,
								  value);
		repInfo.setProperty(p);
		Booolean bol = (Booolean)keywordList.get(keyword);

		if (bol != null) {
			bol.setFlag(true);
		}

		long crc32 = readUnsignedInt(inputStream, PNG_ENDIANITY, this);

		if (crc32 != chcks.getValue()) {
			repInfo.setValid(RepInfo.FALSE);
			repInfo.setMessage(new ErrorMessage("CRC Error in tEXT chunk" ));

		}


    }

    private final void checkChunk(final DataInputStream inputStream,
								  final RepInfo repInfo,
								  int declChunkLen,
								  final String chunkSig)
		throws IOException {
		int read = 0;

		while (read < declChunkLen) {
			int tmp = readUnsignedByte(inputStream, this);
			chcks.update((byte)tmp);
			read++;
		}
		long crc32 = readUnsignedInt(inputStream, PNG_ENDIANITY, this);

		if (crc32 != chcks.getValue()) {
			repInfo.setValid(RepInfo.FALSE);
			repInfo.setMessage(new ErrorMessage("CRC Error in " + chunkSig +" chunk" ));

		}

    }

    // Utility predicate
    private final boolean somethingWrongP(RepInfo repInfo) {
		return repInfo.getWellFormed() == RepInfo.FALSE ||
			repInfo.getValid() == RepInfo.FALSE;
    }

    private final String getCompressedString(DataInputStream dis,
											 int bytesToRead)
		throws IOException, DataFormatException {
		ByteArrayOutputStream bous = new ByteArrayOutputStream();
		Inflater pump = new Inflater();
		byte o[] = new byte[1024];

		while (bytesToRead > 0) {
			int c = 0;

			while (c < 1024 && bytesToRead > 0) {
				o[c++] = (byte)(readUnsignedByte(dis,this));
				bytesToRead--;
			}

			pump.setInput(o);

			c=1;
			while (!pump.needsInput() && c > 0) {
				c = pump.inflate(o);

				if (c>0)
					bous.write(o,0,c);
			}


		}

		return bous.toString("ISO-8859-1");

    }

    // Turns an 32 bit integer intto a byte array
    private final byte[] int2byteArray(int a)
	{
		byte b[]=new byte[]{0,0,0,0};

		b[3]=(byte)(a&0xFF);
		a=a>>8;

		b[2]=(byte)(a&0xFF);
		a=a>>8;

		b[1]=(byte)(a&0xFF);
		a=a>>8;

		b[0]=(byte)(a&0xFF);

		return b;

    }

}
