/*
 * Copyright (c) 2003 Sun Microsystems, Inc. All Rights Reserved.
 * 
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are
 * met:
 * 
 * - Redistribution of source code must retain the above copyright
 *   notice, this list of conditions and the following disclaimer.
 * 
 * - Redistribution 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.
 * 
 * Neither the name of Sun Microsystems, Inc. or the names of
 * contributors may be used to endorse or promote products derived from
 * this software without specific prior written permission.
 * 
 * This software is provided "AS IS," without a warranty of any kind. ALL
 * EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND WARRANTIES,
 * INCLUDING ANY IMPLIED WARRANTY OF MERCHANTABILITY, FITNESS FOR A
 * PARTICULAR PURPOSE OR NON-INFRINGEMENT, ARE HEREBY EXCLUDED. SUN
 * MICROSYSTEMS, INC. ("SUN") AND ITS LICENSORS SHALL NOT BE LIABLE FOR
 * ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF USING, MODIFYING OR
 * DISTRIBUTING THIS SOFTWARE OR ITS DERIVATIVES. IN NO EVENT WILL SUN OR
 * ITS LICENSORS BE LIABLE FOR ANY LOST REVENUE, PROFIT OR DATA, OR FOR
 * DIRECT, INDIRECT, SPECIAL, CONSEQUENTIAL, INCIDENTAL OR PUNITIVE
 * DAMAGES, HOWEVER CAUSED AND REGARDLESS OF THE THEORY OF LIABILITY,
 * ARISING OUT OF THE USE OF OR INABILITY TO USE THIS SOFTWARE, EVEN IF
 * SUN HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
 * 
 * You acknowledge that this software is not designed or intended for use
 * in the design, construction, operation or maintenance of any nuclear
 * facility.
 * 
 * Sun gratefully acknowledges that this software was originally authored
 * and developed by Kenneth Bradley Russell and Christopher John Kline.
 */

package demos.util;

import java.io.*;
import javax.media.opengl.*;
import com.sun.opengl.utils.*;

/**
 * Targa image reader adapted from sources of the <a href =
 * "http://java.sun.com/products/jimi/">Jimi</a> image I/O class library.
 *
 * <P>
 *
 * Image decoder for image data stored in TGA file format.
 * Currently only the original TGA file format is supported. This is
 * because the new TGA format has data at the end of the file, getting
 * to the end of a file in an InputStream orient environment presents
 * several difficulties which are avoided at the moment.
 *
 * <P>
 *
 * This is a simple decoder and is only setup to load a single image
 * from the input stream
 *
 * <P>
 *
 * @author    Robin Luiten
 * @author    Kenneth Russell
 * @version    $Revision: 1.3 $
 */

public class TGAImage {
  private Header header;
  private int    format;
  private byte[] data;

  private TGAImage(Header header) {
    this.header = header;
  }

  /**
   * This class reads in all of the TGA image header in addition it also
   * reads in the imageID field as it is convenient to handle that here.
   *
   * @author    Robin Luiten
   * @version   1.1
   */
  public static class Header {
    /** Set of possible file format TGA types */
    public final static int TYPE_NEW = 0;
    public final static int TYPE_OLD = 1;
    public final static int TYPE_UNK = 2;               // cant rewind stream so unknown for now.

    /**  Set of possible image types in TGA file */
    public final static int NO_IMAGE = 0;               // no image data
    public final static int UCOLORMAPPED = 1;           // uncompressed color mapped image
    public final static int UTRUECOLOR = 2;             // uncompressed true color image
    public final static int UBLACKWHITE = 3;            // uncompressed black and white image
    public final static int COLORMAPPED = 9;            // compressed color mapped image
    public final static int TRUECOLOR = 10;             // compressed true color image
    public final static int BLACKWHITE = 11;            // compressed black and white image

    /** Field image descriptor bitfield values definitions */
    public final static int ID_ATTRIBPERPIXEL = 0xF;
    public final static int ID_LEFTTORIGHT = 0x10;
    public final static int ID_TOPTOBOTTOM = 0x20;
    public final static int ID_INTERLEAVE  = 0xC0;

    /** Field image descriptor / interleave values */
    public final static int I_NOTINTERLEAVED = 0;
    public final static int I_TWOWAY = 1;
    public final static int I_FOURWAY = 2;

    /** Type of this TGA file format */
    private int tgaType;

    /** initial TGA image data fields */
    private int idLength;         // byte value
    private int colorMapType;     // byte value
    private int imageType;        // byte value

    /** TGA image colour map fields */
    private int firstEntryIndex;
    private int colorMapLength;
    private byte colorMapEntrySize;

    /** TGA image specification fields */
    private int xOrigin;
    private int yOrigin;
    private int width;
    private int height;
    private byte pixelDepth;
    private byte imageDescriptor;

    /** bitfields in imageDescriptor */
    private byte attribPerPixel;    // how many attribute bits per pixel
    private boolean leftToRight;    // order of data on scan line
    private boolean topToBottom;    // order scan lines stored
    private byte interleave;        // how rows are stored in image data

    private byte[] imageIDbuf;
    private String imageID;

    public Header(LEDataInputStream in) throws IOException {
      int ret;

      tgaType = TYPE_OLD; // dont try and get footer.

      // initial header fields
      idLength = in.readUnsignedByte();    
      colorMapType = in.readUnsignedByte();    
      imageType = in.readUnsignedByte();    

      // color map header fields
      firstEntryIndex = in.readUnsignedShort();
      colorMapLength = in.readUnsignedShort();
      colorMapEntrySize = in.readByte();

      // TGA image specification fields
      xOrigin = in.readUnsignedShort();
      yOrigin = in.readUnsignedShort();
      width = in.readUnsignedShort();
      height = in.readUnsignedShort();
      pixelDepth = in.readByte();
      imageDescriptor = in.readByte();

      attribPerPixel = (byte)(imageDescriptor & ID_ATTRIBPERPIXEL);
      leftToRight = (imageDescriptor & ID_LEFTTORIGHT) != 0;    // not used ?
      topToBottom = (imageDescriptor & ID_TOPTOBOTTOM) != 0;
      interleave  = (byte)((imageDescriptor & ID_INTERLEAVE) >> 6);

      if (idLength > 0) {
        imageIDbuf = new byte[idLength];
        in.read(imageIDbuf, 0, idLength);
        imageID = new String(imageIDbuf, "US-ASCII");
      }
    }

    public int tgaType()                 { return tgaType; }

    /** initial TGA image data fields */
    public int idLength()                { return idLength; }
    public int colorMapType()            { return colorMapType; }
    public int imageType()               { return imageType; }

    /** TGA image colour map fields */
    public int firstEntryIndex()         { return firstEntryIndex; }
    public int colorMapLength()          { return colorMapLength; }
    public byte colorMapEntrySize()      { return colorMapEntrySize; }

    /** TGA image specification fields */
    public int xOrigin()                 { return xOrigin; }
    public int yOrigin()                 { return yOrigin; }
    public int width()                   { return width; }
    public int height()                  { return height; }
    public byte pixelDepth()             { return pixelDepth; }
    public byte imageDescriptor()        { return imageDescriptor; }

    /** bitfields in imageDescriptor */
    public byte attribPerPixel()         { return attribPerPixel; }
    public boolean leftToRight()         { return leftToRight; }
    public boolean topToBottom()         { return topToBottom; }
    public byte interleave()             { return interleave; }

    public byte[] imageIDbuf()           { return imageIDbuf; }
    public String imageID()              { return imageID; }

    public String toString() {
      return "TGA Header " +
        " id length: " + idLength +
        " color map type: "+ colorMapType +
        " image type: "+ imageType +
        " first entry index: " + firstEntryIndex +
        " color map length: " + colorMapLength +
        " color map entry size: " + colorMapEntrySize +
        " x Origin: " + xOrigin +
        " y Origin: " + yOrigin +
        " width: "+ width +
        " height: "+ height +
        " pixel depth: "+ pixelDepth +
        " image descriptor: "+ imageDescriptor +
        (imageIDbuf == null ? "" : (" ID String: " + imageID));
    }
  }


  /**
   * Identifies the image type of the tga image data and loads
   * it into the JimiImage structure. This was taken from the
   * prototype and modified for the new Jimi structure
   */
  private void decodeImage(LEDataInputStream dIn) throws IOException {
    switch (header.imageType()) {
    case Header.UCOLORMAPPED:
      throw new IOException("TGADecoder Uncompressed Colormapped images not supported");

    case Header.UTRUECOLOR:    // pixelDepth 15, 16, 24 and 32
      switch (header.pixelDepth) {
      case 16:
        throw new IOException("TGADecoder Compressed 16-bit True Color images not supported");

      case 24:
      case 32:
        decodeRGBImageU24_32(dIn);
        break;
      }
      break;

    case Header.UBLACKWHITE:
      throw new IOException("TGADecoder Uncompressed Grayscale images not supported");

    case Header.COLORMAPPED:
      throw new IOException("TGADecoder Compressed Colormapped images not supported");

    case Header.TRUECOLOR:
      throw new IOException("TGADecoder Compressed True Color images not supported");

    case Header.BLACKWHITE:
      throw new IOException("TGADecoder Compressed Grayscale images not supported");
    }
  }
  
  /**
   * This assumes that the body is for a 24 bit or 32 bit for a
   * RGB or ARGB image respectively.
   */
  private void decodeRGBImageU24_32(LEDataInputStream dIn) throws IOException {
    int i;    // row index
    int j;    // column index
    int y;    // output row index
    int raw;  // index through the raw input buffer
    int rawWidth = header.width() * (header.pixelDepth() / 8);
    byte[] rawBuf = new byte[rawWidth];
    data = new byte[rawWidth * header.height()];

    if (header.pixelDepth() == 24) {
      format = GL.GL_BGR;
    } else {
      assert header.pixelDepth() == 32;
      format = GL.GL_BGRA;
    }

    for (i = 0; i < header.height(); ++i) {
      dIn.readFully(rawBuf, 0, rawWidth);

      if (header.topToBottom)
        y = header.height - i - 1; // range 0 to (header.height - 1)
      else
        y = i;

      System.arraycopy(rawBuf, 0, data, y * rawWidth, rawBuf.length);
    }
  }

  /** Returns the width of the image. */
  public int getWidth()    { return header.width(); }

  /** Returns the height of the image. */
  public int getHeight()   { return header.height(); }

  /** Returns the OpenGL format for this texture; e.g. GL.GL_BGR or GL.GL_BGRA. */
  public int getGLFormat() { return format; }

  /** Returns the raw data for this texture in the correct
      (bottom-to-top) order for calls to glTexImage2D. */
  public byte[] getData()  { return data; }

  /** Reads a Targa image from the specified file. */
  public static TGAImage read(String filename) throws IOException {
    return read(new FileInputStream(filename));
  }

  /** Reads a Targa image from the specified InputStream. */
  public static TGAImage read(InputStream in) throws IOException {
    LEDataInputStream dIn = new LEDataInputStream(new BufferedInputStream(in));

    Header header = new Header(dIn);
    TGAImage res = new TGAImage(header);
    res.decodeImage(dIn);
    return res;
  }
}