/* Copyright (C) 2005-2011 Fabio Riccardi */

package com.lightcrafts.jai;

import com.lightcrafts.utils.ColorScience;

import com.lightcrafts.mediax.jai.LookupTableJAI;
import com.lightcrafts.mediax.jai.RasterFactory;
import com.lightcrafts.mediax.jai.RasterAccessor;
import com.lightcrafts.mediax.jai.RasterFormatTag;
import java.awt.image.WritableRaster;
import java.awt.image.Raster;
import java.awt.image.SampleModel;
import java.awt.image.RenderedImage;
import java.awt.*;

import sun.awt.image.ShortInterleavedRaster;

/**
 * Special case of LookupTableJAI to apply the lookup on the lightness of the image.
 * Only works on InterleavedSampleModel and on 16 bit images with uniform types
 */

public class LightnessLookupTable extends LookupTableJAI {
    public LightnessLookupTable(short[] shorts, boolean b) {
        super(shorts, b);
    }

    public WritableRaster lookup(Raster src, WritableRaster dst, Rectangle rect) {
        if (src.getNumBands() != 3)
            return super.lookup(src, dst, rect);

        // Validate source.
        if (src == null) {
            throw
              new IllegalArgumentException("Null Source");
        }

        SampleModel srcSampleModel = src.getSampleModel();
        if (!isIntegralDataType(srcSampleModel)) {
            throw
              new IllegalArgumentException("Source Data Type must be an Integral Data Type");
        }

        // Validate rectangle.
        if (rect == null) {
            rect = src.getBounds();
        } else {
            rect = rect.intersection(src.getBounds());
        }

        if (dst != null) {
            rect = rect.intersection(dst.getBounds());
        }

        // Validate destination.
        SampleModel dstSampleModel;
        if (dst == null) {	// create dst according to table
            dstSampleModel = getDestSampleModel(srcSampleModel,
                                                rect.width, rect.height);
            dst =
                RasterFactory.createWritableRaster(dstSampleModel,
                                                   new Point(rect.x, rect.y));
        } else {
            dstSampleModel = dst.getSampleModel();

            if (dstSampleModel.getTransferType() != getDataType() ||
                dstSampleModel.getNumBands() !=
                getDestNumBands(srcSampleModel.getNumBands())) {
                throw new
                  IllegalArgumentException("Incompatible Destination Image");
            }
        }

        // Add bit support?
        int sTagID = RasterAccessor.findCompatibleTag(null, srcSampleModel);
        int dTagID = RasterAccessor.findCompatibleTag(null, dstSampleModel);

        RasterFormatTag sTag = new RasterFormatTag(srcSampleModel,sTagID);
        RasterFormatTag dTag = new RasterFormatTag(dstSampleModel,dTagID);

        RasterAccessor s = new RasterAccessor(src, rect, sTag, null);
        RasterAccessor d = new RasterAccessor(dst, rect, dTag, null);

        /*int srcNumBands = s.getNumBands();
        int srcDataType = s.getDataType();

        int tblNumBands = getNumBands();
        int tblDataType = getDataType();*/

        int width = d.getWidth();
        int height = d.getHeight();
        int bands = d.getNumBands();
        // int dstDataType = d.getDataType();

        // Source information.
        int srcLineStride = s.getScanlineStride();
        int srcPixelStride = s.getPixelStride();
        int[] srcBandOffsets = s.getBandOffsets();

        short[][] srcData = s.getShortDataArrays();

        // Table information.
        int[] tblOffsets = getOffsets();

        short[][] tblData = getShortData();

        // Destination information.
        int dstLineStride = d.getScanlineStride();
        int dstPixelStride = d.getPixelStride();
        int[] dstBandOffsets = d.getBandOffsets();

        short[][] dstData = d.getShortDataArrays();

        int lowBandOffset = 0;
        for (int b = 0; b < bands; b++)
            if (srcBandOffsets[b] < srcBandOffsets[lowBandOffset])
                lowBandOffset = b;

        short[] sd = srcData[lowBandOffset];
        short[] dd = dstData[lowBandOffset];
        short[] td = tblData[0];

        int srcLineOffset = srcBandOffsets[lowBandOffset];
        int dstLineOffset = dstBandOffsets[lowBandOffset];
        int tblOffset = tblOffsets[0];

        for (int h = 0; h < height; h++) {
            int srcPixelOffset = srcLineOffset;
            int dstPixelOffset = dstLineOffset;

            srcLineOffset += srcLineStride;
            dstLineOffset += dstLineStride;

            final int scale = 0x4000;

            int minOffset = Math.min(srcBandOffsets[0], Math.min(srcBandOffsets[1], srcBandOffsets[2]));

            int wr = (int) (ColorScience.W[srcBandOffsets[0] - minOffset] * scale);
            int wg = (int) (ColorScience.W[srcBandOffsets[1] - minOffset] * scale);
            int wb = (int) (ColorScience.W[srcBandOffsets[2] - minOffset] * scale);

            for (int w = 0; w < width; w++) {
                int sr = sd[srcPixelOffset + 0] & 0xFFFF;
                int sg = sd[srcPixelOffset + 1] & 0xFFFF;
                int sb = sd[srcPixelOffset + 2] & 0xFFFF;

                int lum = (wr * sr + wg * sg + wb * sb) / scale;

                // prevent index out of bounds exceptions
                int index = lum - tblOffset;
                int val = td[index < 0 ? 0 : index >= 0xFFFF ? 0xFFFF : index] & 0xFFFF;

                // int mul = sg > 0 ? (scale * val) / sg : scale;
                int mul = lum > 0 ? (scale * val) / lum : scale;

                int prod = (mul * sr) / scale;
                dd[dstPixelOffset + 0] = prod > 0xFFFF ? (short) 0xFFFF : (short) prod;

                prod = (mul * sg) / scale;
                dd[dstPixelOffset + 1] = prod > 0xFFFF ? (short) 0xFFFF : (short) prod;

                prod = (mul * sb) / scale;
                dd[dstPixelOffset + 2] = prod > 0xFFFF ? (short) 0xFFFF : (short) prod;

                srcPixelOffset += srcPixelStride;
                dstPixelOffset += dstPixelStride;
            }
        }

        d.copyDataToRaster();

        return dst;
    }

    static final int clamp(int in, int maxVal) {
	return (in > maxVal ? maxVal : in);
    }
}
