/*
 * @author Douglas A. Lyon
 * @version  Oct 12, 2002.10:57:23 AM
 */
package ip.ppm;

import java.awt.image.ColorModel;
import java.awt.image.ImageConsumer;
import java.awt.image.ImageProducer;
import java.io.IOException;
import java.io.InputStream;
import java.util.Vector;


// ImageDecoder - abstract class for reading in an image
//
// Copyright (C) 1996 by Jef Poskanzer <jef@acme.com>.  All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions
// are met:
// 1. Redistributions of source code must retain the above copyright
//    notice, this list of conditions and the following disclaimer.
// 2. Redistributions in binary form must reproduce the above copyright
//    notice, this list of conditions and the following disclaimer in the
//    documentation and/or other materials provided with the distribution.
//
// THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
// ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
// OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
// HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
// LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
// OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
// SUCH DAMAGE.
//
// Visit the ACME Labs Java page for up-to-date versions of this and other
// fine Java utilities: http://www.acme.com/java/



/// Abstract class for reading in an image.
// <P>
// A framework for classes that read in and decode an image in
// a particular file format.
// <P>
// This provides a very simplified rendition of the ImageProducer interface.
// It requires the decoder to read the image a row at a time.  It requires
// use of the RGBdefault color model.
// If you want more flexibility you can always implement ImageProducer
// directly.
// <P>
// <A HREF="/resources/classes/Acme/JPM/Decoders/ImageDecoder.java">Fetch the software.</A><BR>
// <A HREF="/resources/classes/Acme.tar.gz">Fetch the entire Acme package.</A>
// <P>
// @see PpmDecoder
// @see Acme.JPM.Encoders.ImageEncoder

public abstract class ImageDecoder implements ImageProducer {

  private InputStream in;
  private int width, height;
  private boolean[] rowsRead;
  private int[][] rgbPixels;
  private boolean startedRead = false;
  private boolean gotSize = false;
  private boolean err = false;
  private boolean producing = false;
  private Vector consumers = new Vector();
  private static final ColorModel model = ColorModel.getRGBdefault();


  /// Constructor.
  // @param in The stream to read the bytes from.
  public ImageDecoder(InputStream in) {
    this.in = in;
  }


  // Methods that subclasses implement.

  /// Subclasses implement this to read in enough of the image stream
  // to figure out the width and height.
  abstract void readHeader(InputStream in) throws IOException;

  /// Subclasses implement this to return the width, or -1 if not known.
  abstract int getWidth();

  /// Subclasses implement this to return the height, or -1 if not known.
  abstract int getHeight();

  /// Subclasses implement this to read pixel data into the rgbRow
  // array, an int[width].  One int per pixel, no offsets or padding,
  // RGBdefault (AARRGGBB) color model.
  abstract void readRow(InputStream in, int row, int[] rgbRow) throws IOException;


  // Our own methods.

  void readImage() {
    try {
      readHeader(in);
      width = getWidth();
      height = getHeight();
      if (width == -1 || height == -1)
        err = true;
      else {
        rowsRead = new boolean[height];
        for (int row = 0; row < height; ++row)
          rowsRead[row] = false;
        gotSize = true;
        notifyThem();
        rgbPixels = new int[height][width];
        for (int row = 0; row < height; ++row) {
          readRow(in, row, rgbPixels[row]);
          rowsRead[row] = true;
          notifyThem();
        }
      }
    } catch (IOException e) {
      err = true;
      width = -1;
      height = -1;
      rowsRead = null;
      rgbPixels = null;
    }
  }

  private synchronized void notifyThem() {
    notifyAll();
  }

  void sendImage() {
    // Grab the list of consumers, in case it changes while we're sending.
    ImageConsumer[] c = new ImageConsumer[consumers.size()];
    int i;
    for (i = 0; i < c.length; ++i)
      c[i] = (ImageConsumer) consumers.elementAt(i);
    // Try to be as parallel as possible.
    waitForSize();
    for (i = 0; i < c.length; ++i)
      sendHead(c[i]);
    for (int row = 0; row < height; ++row)
      for (i = 0; i < c.length; ++i)
        sendPixelRow(c[i], row);
    for (i = 0; i < c.length; ++i)
      sendTail(c[i]);
    producing = false;
  }

  private synchronized void waitForSize() {
    while ((!err) && (!gotSize)) {
      try {
        wait();
      } catch (InterruptedException ignore) {
      }
    }
  }

  private synchronized void waitForRow(int row) {
    while ((!err) && (!rowsRead[row])) {
      try {
        wait();
      } catch (InterruptedException ignore) {
      }
    }
  }

  private void sendHead(ImageConsumer ic) {
    if (err)
      return;
    ic.setDimensions(width, height);
    ic.setColorModel(model);
    ic.setHints(
        ImageConsumer.TOPDOWNLEFTRIGHT | ImageConsumer.COMPLETESCANLINES |
        ImageConsumer.SINGLEPASS | ImageConsumer.SINGLEFRAME);
  }

  private void sendPixelRow(ImageConsumer ic, int row) {
    if (err)
      return;
    waitForRow(row);
    if (err)
      return;
    ic.setPixels(0, row, width, 1, model, rgbPixels[row], 0, width);
  }

  private void sendTail(ImageConsumer ic) {
    if (err)
      ic.imageComplete(ImageConsumer.IMAGEERROR);
    else
      ic.imageComplete(ImageConsumer.STATICIMAGEDONE);

  }


  // Methods from ImageProducer.

  /// This method is used to register an ImageConsumer with the
  // ImageProducer for access to the image data during a later
  // reconstruction of the Image.  The ImageProducer may, at its
  // discretion, start delivering the image data to the consumer
  // using the ImageConsumer interface immediately, or when the
  // next available image reconstruction is triggered by a call
  // to the startProduction method.
  // @see #startProduction
  public void addConsumer(ImageConsumer ic) {
    if (ic != null && !isConsumer(ic))
      consumers.addElement(ic);
  }

  /// This method determines if a given ImageConsumer object
  // is currently registered with this ImageProducer as one
  // of its consumers.
  public boolean isConsumer(ImageConsumer ic) {
    return consumers.contains(ic);
  }

  /// This method removes the given ImageConsumer object
  // from the list of consumers currently registered to
  // receive image data.  It is not considered an error
  // to remove a consumer that is not currently registered.
  // The ImageProducer should stop sending data to this
  // consumer as soon as is feasible.
  public void removeConsumer(ImageConsumer ic) {
    consumers.removeElement(ic);
  }

  /// This method both registers the given ImageConsumer object
  // as a consumer and starts an immediate reconstruction of
  // the image data which will then be delivered to this
  // consumer and any other consumer which may have already
  // been registered with the producer.  This method differs
  // from the addConsumer method in that a reproduction of
  // the image data should be triggered as soon as possible.
  // @see #addConsumer
  public void startProduction(ImageConsumer ic) {
    addConsumer(ic);
    if (!startedRead) {
      startedRead = true;
      new ImageDecoderRead(this);
    }
    if (!producing) {
      producing = true;
      sendImage();
    }
  }

  /// This method is used by an ImageConsumer to request that
  // the ImageProducer attempt to resend the image data one
  // more time in TOPDOWNLEFTRIGHT order so that higher
  // quality conversion algorithms which depend on receiving
  // pixels in order can be used to produce a better output
  // version of the image.  The ImageProducer is free to
  // ignore this call if it cannot resend the data in that
  // order.  If the data can be resent, then the ImageProducer
  // should respond by executing the following minimum set of
  // ImageConsumer method calls:
  // <PRE>
  //     ic.setHints( TOPDOWNLEFTRIGHT | [otherhints] );
  //     ic.setPixels( [...] );    // as many times as needed
  //     ic.imageComplete( [status] );
  // </PRE>
  // @see ImageConsumer#setHints
  public void requestTopDownLeftRightResend(ImageConsumer ic) {
    addConsumer(ic);
    waitForSize();
    sendHead(ic);
    for (int row = 0; row < height; ++row)
      sendPixelRow(ic, row);
    sendTail(ic);
  }

}


class ImageDecoderRead extends Thread {

  private ImageDecoder parent;

  public ImageDecoderRead(ImageDecoder parent) {
    this.parent = parent;
    start();
  }

  // Methods from Runnable.

  public void run() {
    parent.readImage();
  }

}