/* Copyright (C) 2000  Free Software Foundation

   This file is part of libgcj.

This software is copyrighted work licensed under the terms of the
Libgcj License.  Please consult the file "LIBGCJ_LICENSE" for
details.  */

package java.awt.image;

import java.awt.Transparency;
import java.awt.color.ColorSpace;
import gnu.gcj.awt.Buffers;

/**
 * @author Rolf W. Rasmussen <rolfwr@ii.uib.no>
 */
public class IndexColorModel extends ColorModel
{
  private byte[] r;
  private byte[] g;
  private byte[] b;
  private byte[] a;
  private int[] argb;
  private byte[] cmap;
  private int start;
  private int transparent;
  private int size;
  
  public IndexColorModel(int bits, int size, byte[] r, byte[] g, byte[] b)
  {
    super(bits, nArray(bits, 3),
	  ColorSpace.getInstance(ColorSpace.CS_sRGB),
	  false,  // no transparency
	  false,  // no premultiplied
	  Transparency.OPAQUE,
	  Buffers.smallestAppropriateTransferType(bits));
    this.r = r;
    this.g = g;
    this.b = b;
    this.size = size;
  }

  public IndexColorModel(int bits, int size, byte[] r, byte[] g, byte[] b,
			 int transparent)
  {
    super(bits, nArray(bits, 4), 
	  ColorSpace.getInstance(ColorSpace.CS_sRGB),
	  true,  // has transparency
	  false,
	  Transparency.BITMASK,
	  Buffers.smallestAppropriateTransferType(bits));
    this.r = r;
    this.g = g;
    this.b = b;
    this.transparent = transparent;
    this.size = size;
  }

  public IndexColorModel(int bits, int size, byte[] r, byte[] g, byte[] b,
			 byte[] a)
  {
    super(bits, nArray(bits, 4),
	  ColorSpace.getInstance(ColorSpace.CS_sRGB),
	  true,  // has transparency
	  false,
	  Transparency.BITMASK,
	  Buffers.smallestAppropriateTransferType(bits));
    this.r = r;
    this.g = g;
    this.b = b;
    this.a = a;
    this.size = size;
  }

  public IndexColorModel(int bits, int size, byte[] cmap, int start,
			 boolean hasAlpha)
  {
    super(bits, nArray(bits, hasAlpha ? 4 : 3),
	  ColorSpace.getInstance(ColorSpace.CS_sRGB),
	  hasAlpha,
	  false,
	  hasAlpha ? Transparency.TRANSLUCENT : Transparency.OPAQUE,
	  Buffers.smallestAppropriateTransferType(bits));
    this.cmap = cmap;
    this.start = start;
    this.size = size;
  }

  public IndexColorModel(int bits, int size, byte[] cmap, int start,
			 boolean hasAlpha, int transparent,
			 int transferType)
  {
    super(bits, nArray(bits, hasAlpha ? 4 : 3),
	  ColorSpace.getInstance(ColorSpace.CS_sRGB),
	  hasAlpha,
	  false,
	  hasAlpha ? 
	  Transparency.TRANSLUCENT :
	  ((transparent < 0) ?
	   Transparency.OPAQUE :
	   Transparency.BITMASK),
	  transferType);
    this.cmap = cmap;
    this.start = start;
    this.size = size;
  }

  public final int getMapSize()
  {
    return size;
  }
  
  public final int getTransparentPixel()
  {
    return transparent;
  }

  public final void getReds(byte r[])
  {
    if (this.r == null) calcRGBArrays();
    System.arraycopy(this.r, 0, r, 0, getMapSize());
  }
  
  public final void getGreens(byte g[])
  {
    if (this.g == null) calcRGBArrays();
    System.arraycopy(this.g, 0, g, 0, getMapSize());
  }
  
  public final void getBlues(byte b[])
  {
    if (this.b == null) calcRGBArrays();
    System.arraycopy(this.b, 0, b, 0, getMapSize());
  }

  public final void getAlphas(byte a[])
  {
    if (this.a == null) calcAlphaArray();
    System.arraycopy(this.a, 0, a, 0, getMapSize());
  }

  public final void getRGBs(int rgb[])
  {
    if (this.argb == null) calcARGBArray();
    System.arraycopy(this.argb, 0, rgb, 0, getMapSize());
  }

  public int getRed(int pixel)
  {
    try
      {
	return r[pixel];
      }
    catch (NullPointerException npe)
      {
	calcRGBArrays();
	return r[pixel];
      }
  }

  public int getGreen(int pixel)
  {
    try
      {
	return g[pixel];
      }
    catch (NullPointerException npe)
      {
	calcRGBArrays();
	return g[pixel];
      }
  }

  public int getBlue(int pixel)
  {
    try
      {
	return b[pixel];
      }
    catch (NullPointerException npe)
      {
	calcRGBArrays();
	return b[pixel];
      }
  }
  
  public int getAlpha(int pixel)
  {
    try
      {
	return a[pixel];
      } 
    catch (NullPointerException npe)
      {
	calcAlphaArray();
	return a[pixel];
      }
  }

  private void calcRGBArrays() {
    int j=0;
    boolean hasAlpha = hasAlpha();
    r = new byte[size];
    g = new byte[size];
    b = new byte[size];
    if (hasAlpha) a = new byte[size];
    
    for (int i=0; i<size; i++)
      {
	r[i] = cmap[j++];
	g[i] = cmap[j++];
	b[i] = cmap[j++];
	if (hasAlpha()) a[i] = cmap[j++];
      }
  }

  private void calcAlphaArray()
  {
    int transparency = getTransparency();
    switch (transparency)
      {
      case Transparency.OPAQUE:
      case Transparency.BITMASK:
	a = nArray((byte) 255, size);
	if (transparency == Transparency.BITMASK)
	  a[transparent] = 0;
	break;
      case Transparency.TRANSLUCENT:
	calcRGBArrays();
      }
  }

  private void calcARGBArray()
  {
    int mapSize = getMapSize();
    argb = new int[mapSize];
    for (int p=0; p<mapSize; p++) argb[p] = getRGB(p);
  }
  
  public int getRed(Object inData)
  {
    return getRed(getPixelFromArray(inData));
  }

  public int getGreen(Object inData)
  {
    return getGreen(getPixelFromArray(inData));
  }

  public int getBlue(Object inData)
  {
    return getBlue(getPixelFromArray(inData));
  }
    
  public int getAlpha(Object inData)
  {
    return getAlpha(getPixelFromArray(inData));
  }

  public int getRGB(Object inData)
  {
    return getRGB(getPixelFromArray(inData));
  }

  public Object getDataElements(int rgb, Object pixel)
  {
    int av, rv, gv, bv;
    // using 8 bit values
    av = (rgb >>> 24) & 0xff;
    rv = (rgb >>> 16) & 0xff;
    gv = (rgb >>>  8) & 0xff;
    bv = (rgb >>>  0) & 0xff;
    
    int pixelValue = getPixelValue(av, rv, gv, bv);

    /* In this color model, the whole pixel fits in the first element
       of the array. */
    DataBuffer buffer = Buffers.createBuffer(transferType, pixel, 1);
    buffer.setElem(0, pixelValue);
    return Buffers.getData(buffer);
  }
    
  private int getPixelValue(int av, int rv, int gv, int bv)
  {
    if (r == null) calcRGBArrays();
    if (a == null) calcAlphaArray();
    
    int minDAlpha = 1<<8;
    int minDRGB = (1<<8)*(1<<8)*3;
    int pixelValue = -1;
    for (int i=0; i<size; i++)
      {
	int dAlpha = Math.abs(av-(a[i]&0xff));
	if (dAlpha > minDAlpha) continue;
	int dR = rv-(r[i]&0xff);
	int dG = gv-(g[i]&0xff);
	int dB = bv-(b[i]&0xff);
	int dRGB = dR*dR + dG*dG + dB*dB;
	
	if (dRGB >= minDRGB) continue;
	
	pixelValue = i;
	minDRGB = dRGB;
      }
    return pixelValue;
  }  

  public int[] getComponents(int pixel, int[] components, int offset)
  {
    int numComponents = getNumComponents();
    if (components == null) components = new int[offset + numComponents];
    components[offset++] = (r[pixel]&0xff);
    components[offset++] = (g[pixel]&0xff);
    components[offset++] = (b[pixel]&0xff);
    if (hasAlpha()) components[offset++] = (a[pixel]&0xff);
    return components;
  }
	
  public final int[] getComponents(Object pixel, int[] components,
				   int offset)
  {
    return getComponents(getPixelFromArray(pixel), components, offset);
  }
  
  public int getDataElement(int[] components, int offset)
  {
    int r = components[offset++];
    int g = components[offset++];
    int b = components[offset++];
    int a = hasAlpha() ? components[offset++] : 255;
    
    return getPixelValue(a, r, g, b);
  }
  
  public Object getDataElements(int[] components, int offset, Object pixel)
  {
    int pixelValue = getDataElement(components, offset);
    
    /* In this color model, the whole pixel fits in the first element
       of the array. */
    DataBuffer buffer = Buffers.createBuffer(transferType, pixel, 1);
    buffer.setElem(0, pixelValue);
    return Buffers.getData(buffer);
  }
    
  public SampleModel createCompatibleSampleModel(int w, int h)
  {
    int[] bandOffsets = {0};
    return new ComponentSampleModel(transferType, w, h,
				    1, // pixel stride
				    w, // scanline stride
				    bandOffsets);
  }
}
