// JpegHeaders.java
// $Id: JpegHeaders.java,v 1.1.2.4 1999/11/29 10:24:19 ylafon Exp $
// (c) COPYRIGHT MIT, INRIA and Keio, 1999.
// Please first read the full copyright statement in file COPYRIGHT.html
 
package org.w3c.tools.jpeg;

import java.io.*;
import java.util.Vector;

/**
 * @version $Revision: 1.1.2.4 $
 * @author  Benoît Mahé (bmahe@w3.org)
 * jpeg reading code adapted from rdjpgcom from The Independent JPEG Group 
 */
public class JpegHeaders implements Jpeg {
    
    protected File        jpegfile = null;
    protected InputStream in       = null;

    protected Vector vcom          = null;
    protected Vector vacom         = null;

    protected String comments[]    = null;
    protected String appcomments[] = null;

    public String[] getComments() {
	if (comments == null) {
	    comments = new String[vcom.size()];
	    vcom.copyInto(comments);
	}
	return comments;
    }

    public String[] getAPP12Comments() {
	if (appcomments == null) {
	    appcomments = new String[vacom.size()];
	    vacom.copyInto(appcomments);
	}
	return appcomments;
    }

    protected int scanHeaders()
	throws IOException, JpegException
    {
	int marker;
	vcom  = new Vector(1);
	vacom = new Vector(1);

	if (firstMarker() != M_SOI)
	    throw new JpegException("Expected SOI marker first");
	
	while (true) {
	    marker = nextMarker();
	    switch (marker) {
	    case M_SOF0:	 /* Baseline */
	    case M_SOF1:	 /* Extended sequential, Huffman */
	    case M_SOF2:	 /* Progressive, Huffman */
	    case M_SOF3:	 /* Lossless, Huffman */
	    case M_SOF5:	 /* Differential sequential, Huffman */
	    case M_SOF6:	 /* Differential progressive, Huffman */
	    case M_SOF7:	 /* Differential lossless, Huffman */
	    case M_SOF9:	 /* Extended sequential, arithmetic */
	    case M_SOF10:	 /* Progressive, arithmetic */
	    case M_SOF11:	 /* Lossless, arithmetic */
	    case M_SOF13:	 /* Differential sequential, arithmetic */
	    case M_SOF14:	 /* Differential progressive, arithmetic */
	    case M_SOF15:	 /* Differential lossless, arithmetic */
		//to do?
		skipVariable();
		break;
	    case M_SOS:	 /* stop before hitting compressed data */
		return marker;
	    case M_EOI:	 /* in case it's a tables-only JPEG stream */
		return marker;
	    case M_COM:
		vcom.addElement(processComment());
		break;
	    case M_APP12:
		/* Some digital camera makers put useful textual */
		/* information into APP12 markers, so we print   */
		/* those out too when in -verbose mode.          */
		vacom.addElement(processComment());
		break;
	    default:	          /* Anything else just gets skipped */
		skipVariable();  /* we assume it has a parameter count... */
		break;
	    }
	}
    }

    protected String processComment() 
	throws IOException, JpegException
    {
	int length;

	/* Get the marker parameter length count */
	length = read2bytes();
	/* Length includes itself, so must be at least 2 */
	if (length < 2)
	    throw new JpegException("Erroneous JPEG marker length");
	length -= 2;
	
	StringBuffer buffer = new StringBuffer(length);
	byte comment[] = new byte[length];
	int got, pos;
	pos = 0;
	while (length > 0) {
	    got = in.read(comment, pos, length);
	    if (got < 0)
		throw new JpegException("EOF while reading jpeg comment");
	    pos += got;
	    length -= got;
	}
	return new String(comment, "UTF8");
    }

    protected int read2bytes() 
	throws IOException, JpegException
    {
	int c1, c2;
	c1 = in.read();
	if (c1 == -1)
	    throw new JpegException("Premature EOF in JPEG file");
	c2 = in.read();
	if (c2 == -1)
	    throw new JpegException("Premature EOF in JPEG file");
	return (((int) c1) << 8) + ((int) c2);
    }

    /**
     * skip the body after a marker
     */
    protected void skipVariable() 
	throws IOException, JpegException
    {
	long len = (long)read2bytes() - 2;
	if (len < 0 )
	    throw new JpegException("Erroneous JPEG marker length");
	while (len > 0) {
	    long saved = in.skip(len);
	    if (saved < 0)
		throw new IOException("Error while reading jpeg stream");
	    len -= saved;
	}
    }

    protected int firstMarker()
	throws IOException, JpegException
    {
	int c1, c2;
	c1 = in.read();
	c2 = in.read();
	if (c1 != 0xFF || c2 != M_SOI)
	    throw new JpegException("Not a JPEG file");
	return c2;
    }

    protected int nextMarker() 
	throws IOException
    {
	int discarded_bytes = 0;
	int c;

	/* Find 0xFF byte; count and skip any non-FFs. */
	c = in.read();
	while (c != 0xFF)
	    c = in.read();

	/* Get marker code byte, swallowing any duplicate FF bytes.  Extra FFs
	 * are legal as pad bytes, so don't count them in discarded_bytes.
	 */
	do {
	    c = in.read();
	} while (c == 0xFF);

	return c;
    }

    /**
     * get the headers out of a file
     */
    public JpegHeaders(File jpegfile) 
	throws FileNotFoundException
    {
	this.jpegfile = jpegfile;
	this.in       = 
	    new BufferedInputStream( new FileInputStream(jpegfile));
	try {
	    scanHeaders();
	} catch (Exception ex) {
	    ex.printStackTrace();
	}
    }

    /**
     * get the headers out of a stream
     */
    public JpegHeaders(InputStream in) {
	this.in = in; 
	try {
	    scanHeaders();
	} catch (Exception ex) {
	    ex.printStackTrace();
	}
    }    

    public static void main(String args[]) {
	try {
	    JpegHeaders headers = new JpegHeaders(new File(args[0]));
	    String comments[] = headers.getComments();
	    if (comments != null) {
		System.out.println("Comments: ");
		for (int i = 0 ; i < comments.length ; i++) 
		    System.out.println(comments[i]);
	    }
	    comments = headers.getAPP12Comments();
	    if (comments != null) {
		System.out.println("APP12 Comments: ");
		for (int i = 0 ; i < comments.length ; i++) 
		    System.out.println(comments[i]);
	    }

	} catch (Exception ex) {
	    ex.printStackTrace();
	}
    }

}
