/* 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;

/**
 * @author Rolf W. Rasmussen <rolfwr@ii.uib.no>
 */
public abstract class SampleModel
{
  /** Width of image described. */
  protected int width;
  
  /** Height of image described. */
  protected int height;
  
  /** Number of bands in the image described. */
  protected int numBands;

  /** 
   * The DataBuffer type that is used to store the data of the image
   * described.
   */
  protected int dataType;

  public SampleModel(int dataType, int w, int h, int numBands)
  {
    if ((w<=0) || (h<=0)) throw new IllegalArgumentException();
	
    // FIXME: How can an int be greater than Integer.MAX_VALUE?
    // FIXME: How do we identify an unsupported data type?

    this.dataType = dataType;
    this.width = w;
    this.height = h;
    this.numBands = numBands;  
  }

  public final int getWidth()
  {
    return width;
  }

  public final int getHeight()
  {
    return height;
  }

  public final int getNumBands()
  {
    return numBands;
  }
    
  public abstract int getNumDataElements();
  
  public final int getDataType()
  {
    return dataType;
  }

  public int getTransferType()
  {
    // FIXME: Is this a reasonable default implementation?
    return dataType;
  }

  public int[] getPixel(int x, int y, int[] iArray, DataBuffer data)
  {
    if (iArray == null) iArray = new int[numBands];
    for (int b=0; b<numBands; b++) iArray[b] = getSample(x, y, b, data);
    return iArray;
  }
  
  /**
   *
   * This method is provided as a faster alternative to getPixel(),
   * that can be used when there is no need to decode the pixel into
   * seperate sample values.
   *
   * @param obj An array to return the pixel data in. If null, an
   * array of the right type and size will be created.
   *
   * @return A single pixel as an array object of a primitive type,
   * based on the transfer type. Eg. if transfer type is
   * DataBuffer.TYPE_USHORT, then a short[] object is returned.
   */
  public abstract Object getDataElements(int x, int y, Object obj,
					 DataBuffer data);

    
  public Object getDataElements(int x, int y, int w, int h, Object obj,
				DataBuffer data)
  {
    int size = w*h;
    int numDataElements = getNumDataElements();
    int dataSize = numDataElements*size;
    
    if (obj == null)
      {
	switch (getTransferType())
	  {
	  case DataBuffer.TYPE_BYTE:
	    obj = new byte[dataSize];
	    break;
	  case DataBuffer.TYPE_USHORT:
	    obj = new short[dataSize];
	    break;
	  case DataBuffer.TYPE_INT:
	    obj = new int[dataSize];
	    break;
	  default:
	    // Seems like the only sensible thing to do.
	    throw new ClassCastException();
	  }
      }
    Object pixelData = null;
    int outOffset = 0;
    for (int yy = y; yy<(y+h); yy++)
      {
	for (int xx = x; xx<(x+w); xx++)
	  {
	    pixelData = getDataElements(xx, yy, pixelData, data);
	    System.arraycopy(pixelData, 0, obj, outOffset,
			     numDataElements);
	    outOffset += numDataElements;
	  }
      }
    return obj;
  }

  public abstract void setDataElements(int x, int y, Object obj,
				       DataBuffer data);

  public void setDataElements(int x, int y, int w, int h,
			      Object obj, DataBuffer data)
  {
    int size = w*h;
    int numDataElements = getNumDataElements();
    int dataSize = numDataElements*size;
    
    Object pixelData;
    switch (getTransferType())
      {
      case DataBuffer.TYPE_BYTE:
	pixelData = new byte[numDataElements];
	break;
      case DataBuffer.TYPE_USHORT:
	pixelData = new short[numDataElements];
	break;
      case DataBuffer.TYPE_INT:
	pixelData = new int[numDataElements];
	break;
      default:
	// Seems like the only sensible thing to do.
	throw new ClassCastException();
      }
    int inOffset = 0;

    for (int yy=y; yy<(y+h); yy++)
      {
	for (int xx=x; xx<(x+w); xx++)
	  {
	    System.arraycopy(obj, inOffset, pixelData, 0,
			     numDataElements);
	    setDataElements(xx, yy, pixelData, data);
	    inOffset += numDataElements;
	  }
      }
  }

  public float[] getPixel(int x, int y, float[] fArray, DataBuffer data)
  {
    if (fArray == null) fArray = new float[numBands];
    
    for (int b=0; b<numBands; b++)
      {
        fArray[0] = getSampleFloat(x, y, b, data);
      }
    return fArray;
  }

  public double[] getPixel(int x, int y, double[] dArray, DataBuffer data) {
    if (dArray == null) dArray = new double[numBands];
    for (int b=0; b<numBands; b++)
      {
	dArray[0] = getSampleDouble(x, y, b, data);
      }
    return dArray;
  }

  /* FIXME: Should it return a banded or pixel interleaved array of
     samples? (Assume interleaved.) */
  public int[] getPixels(int x, int y, int w, int h, int[] iArray,
			 DataBuffer data)
  {
    int size = w*h;
    int outOffset = 0;
    int[] pixel = null;
    if (iArray == null) iArray = new int[w*h*numBands];
    for (int yy=y; yy<(y+h); yy++)
      {
	for (int xx=x; xx<(x+w); xx++)
	  {
	    getPixel(xx, yy, pixel, data);
	    System.arraycopy(pixel, 0, iArray, outOffset, numBands);
	    outOffset += numBands;
	  }
      }
    return iArray;
  }

  /* FIXME: Should it return a banded or pixel interleaved array of
     samples? (Assume interleaved.) */
  public float[] getPixels(int x, int y, int w, int h, float[] fArray,
			   DataBuffer data)
  {
    int size = w*h;
    int outOffset = 0;
    float[] pixel = null;
    if (fArray == null) fArray = new float[w*h*numBands];
    for (int yy=y; yy<(y+h); yy++)
      {
	for (int xx=x; xx<(x+w); xx++)
	  {
	    getPixel(xx, yy, pixel, data);
	    System.arraycopy(pixel, 0, fArray, outOffset, numBands);
	    outOffset += numBands;
	  }
      }
    return fArray;
  }
    
  /* FIXME: Should it return a banded or pixel interleaved array of
     samples? (Assume interleaved.) */
  public double[] getPixels(int x, int y, int w, int h, double[] dArray,
			    DataBuffer data)
  {
    int size = w*h;
    int outOffset = 0;
    double[] pixel = null;
    if (dArray == null) dArray = new double[w*h*numBands];
    for (int yy=y; yy<(y+h); yy++)
      {
	for (int xx=x; xx<(x+w); xx++)
	  {
	    getPixel(xx, yy, pixel, data);
	    System.arraycopy(pixel, 0, dArray, outOffset, numBands);
	    outOffset += numBands;
	  }
      }
    return dArray;
  }

  public abstract int getSample(int x, int y, int b, DataBuffer data);

  public float getSampleFloat(int x, int y, int b, DataBuffer data)
  {
    return getSample(x, y, b, data);
  }

  public double getSampleDouble(int x, int y, int b, DataBuffer data)
  {
    return getSampleFloat(x, y, b, data);
  }

  public int[] getSamples(int x, int y, int w, int h, int b,
			  int[] iArray, DataBuffer data)
  {
    int size = w*h;
    int outOffset = 0;
    if (iArray == null) iArray = new int[size];
    for (int yy=y; yy<(y+h); yy++)
      {
	for (int xx=x; xx<(x+w); xx++)
	  {
	    iArray[outOffset++] = getSample(xx, yy, b, data);
	  }
      }
    return iArray;
  }

  public float[] getSamples(int x, int y, int w, int h, int b,
			    float[] fArray, DataBuffer data)
  {
    int size = w*h;
    int outOffset = 0;
    if (fArray == null) fArray = new float[size];
    for (int yy=y; yy<(y+h); yy++)
      {
	for (int xx=x; xx<(x+w); xx++)
	  {
	    fArray[outOffset++] = getSampleFloat(xx, yy, b, data);
	  }
      }
    return fArray;
  }

  public double[] getSamples(int x, int y, int w, int h, int b,
			     double[] dArray, DataBuffer data)
  {
    int size = w*h;
    int outOffset = 0;
    if (dArray == null) dArray = new double[size];
    for (int yy=y; yy<(y+h); yy++)
      {
	for (int xx=x; xx<(x+w); xx++)
	  {
	    dArray[outOffset++] = getSampleDouble(xx, yy, b, data);
	  }
      }
    return dArray;
  }
  
  public void setPixel(int x, int y, int[] iArray, DataBuffer data)
  {
    for (int b=0; b<numBands; b++) setSample(x, y, b, iArray[b], data);
  }

  public void setPixel(int x, int y, float[] fArray, DataBuffer data)
  {
    for (int b=0; b<numBands; b++) setSample(x, y, b, fArray[b], data);
  }

  public void setPixel(int x, int y, double[] dArray, DataBuffer data)
  {
    for (int b=0; b<numBands; b++) setSample(x, y, b, dArray[b], data);
  }

  public void setPixels(int x, int y, int w, int h, int[] iArray,
			DataBuffer data)
  {
    int inOffset = 0;
    int[] pixel = new int[numBands];
    for (int yy=y; yy<(y+h); yy++)
      {
	for (int xx=x; xx<(x+w); xx++)
	  {
	    System.arraycopy(iArray, inOffset, pixel, 0, numBands);
	    setPixel(xx, yy, pixel, data);
	    inOffset += numBands;
	  }
      }
  }

  public void setPixels(int x, int y, int w, int h, float[] fArray,
			DataBuffer data)
  {
    int inOffset = 0;
    float[] pixel = new float[numBands];
    for (int yy=y; yy<(y+h); yy++)
      {
	for (int xx=x; xx<(x+w); xx++)
	  {
	    System.arraycopy(fArray, inOffset, pixel, 0, numBands);
	    setPixel(xx, yy, pixel, data);
	    inOffset += numBands;
	  }
      }
  }

  public void setPixels(int x, int y, int w, int h, double[] dArray,
			DataBuffer data)
  {
    int inOffset = 0;
    double[] pixel = new double[numBands];
    for (int yy=y; yy<(y+h); yy++)
      {
	for (int xx=x; xx<(x+w); xx++)
	  {
	    System.arraycopy(dArray, inOffset, pixel, 0, numBands);
	    setPixel(xx, yy, pixel, data);
	    inOffset += numBands;
	  }
      }
  }

  public abstract void setSample(int x, int y, int b, int s,
				 DataBuffer data);

  public void setSample(int x, int y, int b, float s,
			DataBuffer data)
  {
    setSample(x, y, b, (int) s, data);
  }

  public void setSample(int x, int y, int b, double s,
			DataBuffer data)
  {
    setSample(x, y, b, (float) s, data);
  }

  public void setSamples(int x, int y, int w, int h, int b,
			 int[] iArray, DataBuffer data)
  {
    int size = w*h;
    int inOffset = 0;
    for (int yy=y; yy<(y+h); yy++)
      for (int xx=x; xx<(x+w); xx++)
	setSample(xx, yy, b, iArray[inOffset++], data);
  }

  public void setSamples(int x, int y, int w, int h, int b,
			 float[] fArray, DataBuffer data)
  {
    int size = w*h;
    int inOffset = 0;
    for (int yy=y; yy<(y+h); yy++)
      for (int xx=x; xx<(x+w); xx++)
	setSample(xx, yy, b, fArray[inOffset++], data);

    }

    public void setSamples(int x, int y, int w, int h, int b,
			   double[] dArray, DataBuffer data) {
      int size = w*h;
      int inOffset = 0;
      for (int yy=y; yy<(y+h); yy++)
	for (int xx=x; xx<(x+w); xx++)
	  setSample(xx, yy, b, dArray[inOffset++], data);
    }

    public abstract SampleModel createCompatibleSampleModel(int w, int h);

    public abstract SampleModel createSubsetSampleModel(int[] bands);

    public abstract DataBuffer createDataBuffer();

    public abstract int[] getSampleSize();

    public abstract int getSampleSize(int band);
}
