/* 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.color.*;
import java.awt.Point;
import gnu.gcj.awt.Buffers;

public class ComponentColorModel extends ColorModel
{
  private static int sum(int[] values)
  {
    int sum = 0;
    for (int i=0; i<values.length; i++)
      sum += values[i];
    return sum;
  }

  public ComponentColorModel(ColorSpace colorSpace, int[] bits,
			     boolean hasAlpha,
			     boolean isAlphaPremultiplied,
			     int transparency, int transferType)
  {
    super(sum(bits), bits, colorSpace, hasAlpha, isAlphaPremultiplied,
	  transparency, transferType);
  }

  public int getRed(int pixel)
  {
    if (getNumComponents()>1) throw new IllegalArgumentException();
    return (int) getRGBFloat(pixel)[0];
  }

  public int getGreen(int pixel)
  {
    if (getNumComponents()>1) throw new IllegalArgumentException();
    return (int) getRGBFloat(pixel)[0];
  }
  
  public int getBlue(int pixel)
  {
    if (getNumComponents()>1) throw new IllegalArgumentException();
    return (int) getRGBFloat(pixel)[0];
  }

  public int getAlpha(int pixel)
  {
    if (getNumComponents()>1) throw new IllegalArgumentException();
    int shift = 8 - getComponentSize(getNumColorComponents());
    if (shift >= 0) return pixel << shift;
    return pixel >> (-shift);
  }
   
  public int getRGB(int pixel)
  {
    float[] rgb = getRGBFloat(pixel);
    int ret = getRGB(rgb);
    if (hasAlpha()) ret |= getAlpha(pixel) << 24;
    return ret;
  }


  /* FIXME: Is the values returned from toRGB() in the [0.0, 1.0] or the
     [0.0, 256) range? 
     
     we assume it is in the [0.0, 1.0] range along with the
     other color spaces. */
  
  /* Note, it's OK to pass a to large array to toRGB(). Extra
     elements are ignored. */
  
  private float[] getRGBFloat(int pixel)
  {
    float[] data = { pixel };
    return cspace.toRGB(data);
  }

  private float[] getRGBFloat(Object inData)
  {
    DataBuffer buffer =
    Buffers.createBufferFromData(transferType, inData,
				 getNumComponents());
    int colors = getNumColorComponents();
    float[] data = new float[colors];
    
    // FIXME: unpremultiply data that is premultiplied
    for (int i=0; i<colors; i++)
      {
	float maxValue = (1<<getComponentSize(i))-1;
	data[i] = buffer.getElemFloat(i)/maxValue; 
      }
    float[] rgb = cspace.toRGB(data);
    return rgb;
  }
  
  public int getRed(Object inData)
  {
    return (int) getRGBFloat(inData)[0]*255;
  }

  public int getGreen(Object inData)
  {
    return (int) getRGBFloat(inData)[1]*255;
  }

  public int getBlue(Object inData)
  {
    return (int) getRGBFloat(inData)[2]*255;
  }

  public int getAlpha(Object inData)
  {
    DataBuffer buffer =
      Buffers.createBufferFromData(transferType, inData,
				   getNumComponents());
    int shift = 8 - getComponentSize(getNumColorComponents());
    int alpha = buffer.getElem(getNumColorComponents());
    if (shift >= 0) return alpha << shift;
    return alpha >> (-shift);
  }

  private int getRGB(float[] rgb)
  {
    /* NOTE: We could cast to byte instead of int here. This would
       avoid bits spilling over from one bit field to
       another. But, if we assume that floats are in the [0.0,
       1.0] range, this will never happen anyway. */
    
    /* Remember to multiply BEFORE casting to int, otherwise, decimal
       point data will be lost. */
    int ret =
      (((int) (rgb[0]*255F)) << 16) |
      (((int) (rgb[1]*255F)) <<  8) |
      (((int) (rgb[2]*255F)) <<  0);
    return ret;
  }

  /**
   * @param inData pixel data of transferType, as returned by the
   * getDataElements method in SampleModel.
   */
  public int getRGB(Object inData)
  {
    float[] rgb = getRGBFloat(inData);
    int ret = getRGB(rgb);
    if (hasAlpha()) ret |= getAlpha(inData) << 24;
    return ret;
  }

  public Object getDataElements(int rgb, Object pixel)
  {
    // Convert rgb to [0.0, 1.0] sRGB values.
    float[] rgbFloats = {
      ((rgb >> 16)&0xff)/255.0F,
      ((rgb >>  8)&0xff)/255.0F,
      ((rgb >>  0)&0xff)/255.0F
    };

    // Convert from rgb to color space components.
    float[] data = cspace.fromRGB(rgbFloats);
    DataBuffer buffer = Buffers.createBuffer(transferType, pixel,
					     getNumComponents());
    int numColors = getNumColorComponents();
    
    if (hasAlpha())
      {
	float alpha = ((rgb >> 24)&0xff)/255.0F;
	
	/* If color model has alpha and should be premultiplied, multiply
	   color space components with alpha value. */
	if (isAlphaPremultiplied()) {
	  for (int i=0; i<numColors; i++)
	    data[i] *= alpha;
	}
	// Scale the alpha sample to the correct number of bits.
	alpha *= (1<<(bits[numColors]-1));
	// Arrange the alpha sample in the output array.
	buffer.setElemFloat(numColors, alpha);
      }
    for (int i=0; i<numColors; i++)
      {
	// Scale the color samples to the correct number of bits.
	float value = data[i]*(1<<(bits[i]-1));
	// Arrange the color samples in the output array.
	buffer.setElemFloat(i, value);
      }
    return Buffers.getData(buffer);
  }

  public int[] getComponents(int pixel, int[] components, int offset)
  {
    if (getNumComponents()>1) throw new IllegalArgumentException();
    if (components == null)
    components = new int[getNumComponents() + offset];
    components[offset] = pixel;
    return components;
  }

  public int[] getComponents(Object pixel, int[] components, int offset)
  {
    DataBuffer buffer = Buffers.createBuffer(transferType, pixel,
					     getNumComponents());
    int numComponents = getNumComponents();

    if (components == null)
      components = new int[numComponents + offset];

    for (int i=0; i<numComponents; i++)
      components[offset++] = buffer.getElem(i);

    return components;
  }

  public int getDataElement(int[] components, int offset)
  {
    if (getNumComponents()>1) throw new IllegalArgumentException();
    return components[offset];
  }

  public Object getDataElements(int[] components, int offset, Object obj)
  {
    DataBuffer buffer = Buffers.createBuffer(transferType, obj,
					     getNumComponents());
    int numComponents = getNumComponents();

    for (int i=0; i<numComponents; i++)
      buffer.setElem(i, components[offset++]);

    return Buffers.getData(buffer);
  }

  public ColorModel coerceData(WritableRaster raster,
			       boolean isAlphaPremultiplied) {
    if (this.isAlphaPremultiplied == isAlphaPremultiplied)
      return this;

    /* TODO: provide better implementation based on the
       assumptions we can make due to the specific type of the
       color model. */
    super.coerceData(raster, isAlphaPremultiplied);
    
    return new ComponentColorModel(cspace, bits, hasAlpha(),
				   isAlphaPremultiplied, // argument
				   transparency, transferType);
  }

  public boolean isCompatibleRaster(Raster raster)
  {
    return super.isCompatibleRaster(raster);
    // FIXME: Should we test something more here? (Why override?)
  }

  public WritableRaster createCompatibleWritableRaster(int w, int h)
  {
    SampleModel sm = createCompatibleSampleModel(w, h);
    Point origin = new Point(0, 0);
    return Raster.createWritableRaster(sm, origin);
  }

  public SampleModel createCompatibleSampleModel(int w, int h)
  {
    int pixelStride = getNumComponents();
    
    /* TODO: Maybe we don't need to create a new offset array each
       time, but rather use the same array every time. */
    int[] bandOffsets = new int[pixelStride];
    for (int i=0; i<pixelStride; i++) bandOffsets[i] = i;
    return new ComponentSampleModel(transferType, w, h,
				    pixelStride, pixelStride*w,
				    bandOffsets);
  }

  public boolean isCompatibleSampleModel(SampleModel sm)
  {
    return 
      (sm instanceof ComponentSampleModel) &&
      super.isCompatibleSampleModel(sm);
  }

  public WritableRaster getAlphaRaster(WritableRaster raster)
  {
    if (!hasAlpha()) return null;
    
    SampleModel sm = raster.getSampleModel();
    int[] alphaBand = { sm.getNumBands() - 1 };
    SampleModel alphaModel = sm.createSubsetSampleModel(alphaBand);
    DataBuffer buffer = raster.getDataBuffer();
    Point origin = new Point(0, 0);
    return Raster.createWritableRaster(alphaModel, buffer, origin);
  }
    
  public boolean equals(Object obj)
  {
    if (!(obj instanceof ComponentColorModel)) return false;
    return super.equals(obj);
  }
}
