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

/*
 * $RCSfile: AddOpImage.java,v $
 *
 * Copyright (c) 2005 Sun Microsystems, Inc. All rights reserved.
 *
 * Use is subject to license terms.
 *
 * $Revision: 1.1 $
 * $Date: 2005/02/11 04:56:12 $
 * $State: Exp $
 */
package com.lightcrafts.jai.opimage;

import java.awt.Rectangle;
import java.awt.image.DataBuffer;
import java.awt.image.Raster;
import java.awt.image.RenderedImage;
import java.awt.image.SampleModel;
import java.awt.image.WritableRaster;
import com.lightcrafts.mediax.jai.ImageLayout;
import com.lightcrafts.mediax.jai.PointOpImage;
import com.lightcrafts.mediax.jai.RasterAccessor;
import com.lightcrafts.mediax.jai.RasterFormatTag;
import com.lightcrafts.mediax.jai.RasterFactory;
import java.util.Map;

import com.lightcrafts.media.jai.util.ImageUtil;
import com.lightcrafts.media.jai.util.JDKWorkarounds;

final class UnSharpMaskOpImage extends PointOpImage {

    /* Source 1 band increment */
    private int s1bd = 1;

    /* Source 2 band increment */
    private int s2bd = 1;

    protected double gain;
    protected int threshold;

    /**
     * Constructs an <code>AddOpImage</code>.
     *
     * <p>The <code>layout</code> parameter may optionally contains the
     * tile grid layout, sample model, and/or color model. The image
     * dimension is determined by the intersection of the bounding boxes
     * of the two source images.
     *
     * <p>The image layout of the first source image, <code>source1</code>,
     * is used as the fall-back for the image layout of the destination
     * image. Any layout parameters not specified in the <code>layout</code>
     * argument are set to the same value as that of <code>source1</code>.
     *
     * @param source1  The first source image.
     * @param source2  The second source image.
     * @param layout   The destination image layout.
     */
    public UnSharpMaskOpImage(RenderedImage source1,
                              RenderedImage source2,
                              Map config,
                              ImageLayout layout,
                              double gain, int threshold) {
        super(source1, source2, layout, config, true);

        this.gain = gain;
        this.threshold = threshold;

        // Get the source band counts.
        int numBands1 = source1.getSampleModel().getNumBands();
        int numBands2 = source2.getSampleModel().getNumBands();

        // Handle the special case of adding a single band image to
        // each band of a multi-band image.
        int numBandsDst;
        if (layout != null && layout.isValid(ImageLayout.SAMPLE_MODEL_MASK)) {
            SampleModel sm = layout.getSampleModel(null);
            numBandsDst = sm.getNumBands();

            // One of the sources must be single-banded and the other must
            // have at most the number of bands in the SampleModel hint.
            if (numBandsDst > 1 &&
                ((numBands1 == 1 && numBands2 > 1) ||
                 (numBands2 == 1 && numBands1 > 1))) {
                // Clamp the destination band count to the number of
                // bands in the multi-band source.
                numBandsDst = Math.min(Math.max(numBands1, numBands2),
                                       numBandsDst);

                // Create a new SampleModel if necessary.
                if (numBandsDst != sampleModel.getNumBands()) {
                    sampleModel =
                            RasterFactory.createComponentSampleModel(sm,
                                                                     sampleModel.getTransferType(),
                                                                     sampleModel.getWidth(),
                                                                     sampleModel.getHeight(),
                                                                     numBandsDst);

                    if (colorModel != null &&
                        !JDKWorkarounds.areCompatibleDataModels(sampleModel,
                                                                colorModel)) {
                        colorModel =
                                ImageUtil.getCompatibleColorModel(sampleModel,
                                                                  config);
                    }
                }

                // Set the source band increments.
                s1bd = numBands1 == 1 ? 0 : 1;
                s2bd = numBands2 == 1 ? 0 : 1;
            }
        }

        // Set flag to permit in-place operation.
        permitInPlaceOperation();
    }

    /**
     * Adds the pixel values of two source images within a specified
     * rectangle.
     *
     * @param sources   Cobbled sources, guaranteed to provide all the
     *                  source data necessary for computing the rectangle.
     * @param dest      The tile containing the rectangle to be computed.
     * @param destRect  The rectangle within the tile to be computed.
     */
    protected void computeRect(Raster[] sources,
                               WritableRaster dest,
                               Rectangle destRect) {
        // Retrieve format tags.
        RasterFormatTag[] formatTags = getFormatTags();

        RasterAccessor s1 = new RasterAccessor(sources[0], destRect,
                                               formatTags[0],
                                               getSourceImage(0).getColorModel());
        RasterAccessor s2 = new RasterAccessor(sources[1], destRect,
                                               formatTags[1],
                                               getSourceImage(1).getColorModel());
        RasterAccessor d = new RasterAccessor(dest, destRect,
                                              formatTags[2], getColorModel());

        switch (d.getDataType()) {
        case DataBuffer.TYPE_BYTE:
            computeRectByte(s1, s2, d);
            break;
        case DataBuffer.TYPE_USHORT:
            computeRectUShort(s1, s2, d);
            break;
        case DataBuffer.TYPE_SHORT:
            computeRectShort(s1, s2, d);
            break;
        case DataBuffer.TYPE_INT:
            computeRectInt(s1, s2, d);
            break;
        case DataBuffer.TYPE_FLOAT:
            computeRectFloat(s1, s2, d);
            break;
        case DataBuffer.TYPE_DOUBLE:
            computeRectDouble(s1, s2, d);
            break;
        }

        if (d.needsClamping()) {
            d.clampDataArrays();
        }
        d.copyDataToRaster();
    }

    private void computeRectByte(RasterAccessor src1,
                                 RasterAccessor src2,
                                 RasterAccessor dst) {
        int s1LineStride = src1.getScanlineStride();
        int s1PixelStride = src1.getPixelStride();
        int[] s1BandOffsets = src1.getBandOffsets();
        byte[][] s1Data = src1.getByteDataArrays();

        int s2LineStride = src2.getScanlineStride();
        int s2PixelStride = src2.getPixelStride();
        int[] s2BandOffsets = src2.getBandOffsets();
        byte[][] s2Data = src2.getByteDataArrays();

        int dwidth = dst.getWidth();
        int dheight = dst.getHeight();
        int bands = dst.getNumBands();
        int dLineStride = dst.getScanlineStride();
        int dPixelStride = dst.getPixelStride();
        int[] dBandOffsets = dst.getBandOffsets();
        byte[][] dData = dst.getByteDataArrays();

        int c = (int) (gain * 0x100);

        for (int b = 0, s1b = 0, s2b = 0; b < bands;
             b++, s1b += s1bd, s2b += s2bd) {
            byte[] s1 = s1Data[s1b];
            byte[] s2 = s2Data[s2b];
            byte[] d = dData[b];

            int s1LineOffset = s1BandOffsets[s1b];
            int s2LineOffset = s2BandOffsets[s2b];
            int dLineOffset = dBandOffsets[b];

            for (int h = 0; h < dheight; h++) {
                int s1PixelOffset = s1LineOffset;
                int s2PixelOffset = s2LineOffset;
                int dPixelOffset = dLineOffset;

                s1LineOffset += s1LineStride;
                s2LineOffset += s2LineStride;
                dLineOffset += dLineStride;

                for (int w = 0; w < dwidth; w++) {
                    int src = s1[s1PixelOffset] & 0xFF;
                    d[dPixelOffset] = ImageUtil.clampByte(src + c * (src - (int) (s2[s2PixelOffset] & 0xFF)) / 0x100);

                    s1PixelOffset += s1PixelStride;
                    s2PixelOffset += s2PixelStride;
                    dPixelOffset += dPixelStride;
                }
            }
        }
    }

    private static double sigmoid(double x) {
        final double s = 0.05;
        return 1 / (1 + Math.exp(- s * (x + 60)));
    }

    static final int sigmoidTableLenght = 16 * 1024;
    static final float sigmoidTable[] = new float[sigmoidTableLenght];

    static {
        for (int i = 0; i < sigmoidTableLenght; i++)
            sigmoidTable[i] = (float) sigmoid(0.02 * (i - sigmoidTableLenght / 2));

        /* try {
            FileOutputStream tableDump = new FileOutputStream("/Stuff/tableDump");
            for (float entry : sigmoidTable)
                tableDump.write((Float.toString(entry) + '\n').getBytes());
            tableDump.close();
        } catch (Exception e) {
            e.printStackTrace();
        } */
    }

    private static double sigmoidT(double x) {
        int idx = (int) (50 * x + 0.5) + sigmoidTableLenght / 2;
        if (idx < 0)
            return 0;
        else if (idx >= sigmoidTableLenght)
            return 1;
        else
            return sigmoidTable[idx];
    }

    public static void main( String[] args ) {
        System.out.println("Here: ");
    }

    private void computeRectUShort(RasterAccessor src1,
                                   RasterAccessor src2,
                                   RasterAccessor dst) {
        int s1LineStride = src1.getScanlineStride();
        int s1PixelStride = src1.getPixelStride();
        int[] s1BandOffsets = src1.getBandOffsets();
        short[][] s1Data = src1.getShortDataArrays();

        int s2LineStride = src2.getScanlineStride();
        int s2PixelStride = src2.getPixelStride();
        int[] s2BandOffsets = src2.getBandOffsets();
        short[][] s2Data = src2.getShortDataArrays();

        int dwidth = dst.getWidth();
        int dheight = dst.getHeight();
        int bands = dst.getNumBands();
        int dLineStride = dst.getScanlineStride();
        int dPixelStride = dst.getPixelStride();
        int[] dBandOffsets = dst.getBandOffsets();
        short[][] dData = dst.getShortDataArrays();

        int c = (int) (gain * 256);
        int t = 256 * threshold;

        short[] s1 = s1Data[0];
        short[] s2 = s2Data[0];
        short[] d = dData[0];

        int s1LineOffset = s1BandOffsets[0];
        int s2LineOffset = s2BandOffsets[0];
        int dLineOffset = dBandOffsets[0];

        for (int h = 0; h < dheight; h++) {
            int s1PixelOffset = s1LineOffset;
            int s2PixelOffset = s2LineOffset;
            int dPixelOffset = dLineOffset;

            s1LineOffset += s1LineStride;
            s2LineOffset += s2LineStride;
            dLineOffset += dLineStride;

            for (int w = 0; w < dwidth; w++) {
                if (bands == 3) {
                    int s10 = s1[s1PixelOffset+0] & 0xFFFF;
                    int s20 = s2[s2PixelOffset+0] & 0xFFFF;
                    int d0 = s10 - s20;

                    int s11 = s1[s1PixelOffset+1] & 0xFFFF;
                    int s21 = s2[s1PixelOffset+1] & 0xFFFF;
                    int d1 = s11 - s21;

                    int s12 = s1[s1PixelOffset+2] & 0xFFFF;
                    int s22 = s2[s1PixelOffset+2] & 0xFFFF;
                    int d2 = s12 - s22;

                    double diff = Math.sqrt(d0 * d0 + d1 * d1 + d2 * d2);
                    double s = sigmoidT(20 * diff - t);

                    d[dPixelOffset+0] = ImageUtil.clampUShort(s10 + (int) (c * d0 * s / 256.));
                    d[dPixelOffset+1] = ImageUtil.clampUShort(s11 + (int) (c * d1 * s / 256.));
                    d[dPixelOffset+2] = ImageUtil.clampUShort(s12 + (int) (c * d2 * s / 256.));
                } else {
                    int ss1 = s1[s1PixelOffset] & 0xFFFF;
                    int ss2 = s2[s1PixelOffset] & 0xFFFF;
                    int dd = ss1 - ss2;

                    double s = sigmoidT(20 * Math.abs(ss1 - ss2) - t);

                    d[dPixelOffset] = ImageUtil.clampUShort(ss1 + (int) (c * dd * s / 256.));
                }
                s1PixelOffset += s1PixelStride;
                s2PixelOffset += s2PixelStride;
                dPixelOffset += dPixelStride;
            }
        }
    }

    private void computeRectShort(RasterAccessor src1,
                                  RasterAccessor src2,
                                  RasterAccessor dst) {
        int s1LineStride = src1.getScanlineStride();
        int s1PixelStride = src1.getPixelStride();
        int[] s1BandOffsets = src1.getBandOffsets();
        short[][] s1Data = src1.getShortDataArrays();

        int s2LineStride = src2.getScanlineStride();
        int s2PixelStride = src2.getPixelStride();
        int[] s2BandOffsets = src2.getBandOffsets();
        short[][] s2Data = src2.getShortDataArrays();

        int dwidth = dst.getWidth();
        int dheight = dst.getHeight();
        int bands = dst.getNumBands();
        int dLineStride = dst.getScanlineStride();
        int dPixelStride = dst.getPixelStride();
        int[] dBandOffsets = dst.getBandOffsets();
        short[][] dData = dst.getShortDataArrays();

        for (int b = 0, s1b = 0, s2b = 0; b < bands;
             b++, s1b += s1bd, s2b += s2bd) {
            short[] s1 = s1Data[s1b];
            short[] s2 = s2Data[s2b];
            short[] d = dData[b];
            int c = (int) (gain * (Short.MAX_VALUE + 1) + 0.5);

            int s1LineOffset = s1BandOffsets[s1b];
            int s2LineOffset = s2BandOffsets[s2b];
            int dLineOffset = dBandOffsets[b];

            for (int h = 0; h < dheight; h++) {
                int s1PixelOffset = s1LineOffset;
                int s2PixelOffset = s2LineOffset;
                int dPixelOffset = dLineOffset;

                s1LineOffset += s1LineStride;
                s2LineOffset += s2LineStride;
                dLineOffset += dLineStride;

                for (int w = 0; w < dwidth; w++) {
                    int src = s1[s1PixelOffset];
                    d[dPixelOffset] = ImageUtil.clampRoundShort(src + c * (src - s2[s2PixelOffset]) / (Short.MAX_VALUE + 1));

                    s1PixelOffset += s1PixelStride;
                    s2PixelOffset += s2PixelStride;
                    dPixelOffset += dPixelStride;
                }
            }
        }
    }

    private void computeRectInt(RasterAccessor src1,
                                RasterAccessor src2,
                                RasterAccessor dst) {
        int s1LineStride = src1.getScanlineStride();
        int s1PixelStride = src1.getPixelStride();
        int[] s1BandOffsets = src1.getBandOffsets();
        int[][] s1Data = src1.getIntDataArrays();

        int s2LineStride = src2.getScanlineStride();
        int s2PixelStride = src2.getPixelStride();
        int[] s2BandOffsets = src2.getBandOffsets();
        int[][] s2Data = src2.getIntDataArrays();

        int dwidth = dst.getWidth();
        int dheight = dst.getHeight();
        int bands = dst.getNumBands();
        int dLineStride = dst.getScanlineStride();
        int dPixelStride = dst.getPixelStride();
        int[] dBandOffsets = dst.getBandOffsets();
        int[][] dData = dst.getIntDataArrays();

        /*
         * The destination data type may be any of the integral data types.
         * The "clamp" function must clamp to the appropriate range for
         * that data type.
         */
        switch (sampleModel.getTransferType()) {
        case DataBuffer.TYPE_BYTE:
            for (int b = 0, s1b = 0, s2b = 0; b < bands;
                 b++, s1b += s1bd, s2b += s2bd) {
                int[] s1 = s1Data[s1b];
                int[] s2 = s2Data[s2b];
                int[] d = dData[b];
                int c = (int) (gain * 0x100 + 0.5);

                int s1LineOffset = s1BandOffsets[s1b];
                int s2LineOffset = s2BandOffsets[s2b];
                int dLineOffset = dBandOffsets[b];

                for (int h = 0; h < dheight; h++) {
                    int s1PixelOffset = s1LineOffset;
                    int s2PixelOffset = s2LineOffset;
                    int dPixelOffset = dLineOffset;

                    s1LineOffset += s1LineStride;
                    s2LineOffset += s2LineStride;
                    dLineOffset += dLineStride;

                    for (int w = 0; w < dwidth; w++) {
                        int src = s1[s1PixelOffset] & 0xFF;
                        d[dPixelOffset] = ImageUtil.clampRoundByte(src + c * (src - (int) (s2[s2PixelOffset] & 0xFF)) / 0x100);

                        s1PixelOffset += s1PixelStride;
                        s2PixelOffset += s2PixelStride;
                        dPixelOffset += dPixelStride;
                    }
                }
            }
            break;

        case DataBuffer.TYPE_USHORT:
            for (int b = 0, s1b = 0, s2b = 0; b < bands;
                 b++, s1b += s1bd, s2b += s2bd) {
                int[] s1 = s1Data[s1b];
                int[] s2 = s2Data[s2b];
                int[] d = dData[b];
                long c = (long) (gain * 0x10000 + 0.5);

                int s1LineOffset = s1BandOffsets[s1b];
                int s2LineOffset = s2BandOffsets[s2b];
                int dLineOffset = dBandOffsets[b];

                for (int h = 0; h < dheight; h++) {
                    int s1PixelOffset = s1LineOffset;
                    int s2PixelOffset = s2LineOffset;
                    int dPixelOffset = dLineOffset;

                    s1LineOffset += s1LineStride;
                    s2LineOffset += s2LineStride;
                    dLineOffset += dLineStride;

                    for (int w = 0; w < dwidth; w++) {
                        int src = s1[s1PixelOffset] & 0xFFFF;
                        d[dPixelOffset] = ImageUtil.clampRoundUShort(src + c * (src - (int) (s2[s2PixelOffset] & 0xFFFF)) / 0x10000);

                        s1PixelOffset += s1PixelStride;
                        s2PixelOffset += s2PixelStride;
                        dPixelOffset += dPixelStride;
                    }
                }
            }
            break;

        case DataBuffer.TYPE_SHORT:
            for (int b = 0, s1b = 0, s2b = 0; b < bands;
                 b++, s1b += s1bd, s2b += s2bd) {
                int[] s1 = s1Data[s1b];
                int[] s2 = s2Data[s2b];
                int[] d = dData[b];
                int c = (int) (gain * (Short.MAX_VALUE + 1) + 0.5);

                int s1LineOffset = s1BandOffsets[s1b];
                int s2LineOffset = s2BandOffsets[s2b];
                int dLineOffset = dBandOffsets[b];

                for (int h = 0; h < dheight; h++) {
                    int s1PixelOffset = s1LineOffset;
                    int s2PixelOffset = s2LineOffset;
                    int dPixelOffset = dLineOffset;

                    s1LineOffset += s1LineStride;
                    s2LineOffset += s2LineStride;
                    dLineOffset += dLineStride;

                    for (int w = 0; w < dwidth; w++) {
                        int src = s1[s1PixelOffset];
                        d[dPixelOffset] = ImageUtil.clampRoundShort(src + c * (src - s2[s2PixelOffset]) / (Short.MAX_VALUE + 1));

                        s1PixelOffset += s1PixelStride;
                        s2PixelOffset += s2PixelStride;
                        dPixelOffset += dPixelStride;
                    }
                }
            }
            break;

        case DataBuffer.TYPE_INT:
            for (int b = 0, s1b = 0, s2b = 0; b < bands;
                 b++, s1b += s1bd, s2b += s2bd) {
                int[] s1 = s1Data[s1b];
                int[] s2 = s2Data[s2b];
                int[] d = dData[b];
                long c = (long) (gain * ((long) Integer.MAX_VALUE + 1) + 0.5);

                int s1LineOffset = s1BandOffsets[s1b];
                int s2LineOffset = s2BandOffsets[s2b];
                int dLineOffset = dBandOffsets[b];

                for (int h = 0; h < dheight; h++) {
                    int s1PixelOffset = s1LineOffset;
                    int s2PixelOffset = s2LineOffset;
                    int dPixelOffset = dLineOffset;

                    s1LineOffset += s1LineStride;
                    s2LineOffset += s2LineStride;
                    dLineOffset += dLineStride;

                    for (int w = 0; w < dwidth; w++) {
                        int src = s1[s1PixelOffset];
                        d[dPixelOffset] = ImageUtil.clampRoundInt(src + c * (src - s2[s2PixelOffset]) / ((long) Integer.MAX_VALUE + 1));

                        s1PixelOffset += s1PixelStride;
                        s2PixelOffset += s2PixelStride;
                        dPixelOffset += dPixelStride;
                    }
                }
            }
            break;
        }
    }

    private void computeRectFloat(RasterAccessor src1,
                                  RasterAccessor src2,
                                  RasterAccessor dst) {
        int s1LineStride = src1.getScanlineStride();
        int s1PixelStride = src1.getPixelStride();
        int[] s1BandOffsets = src1.getBandOffsets();
        float[][] s1Data = src1.getFloatDataArrays();

        int s2LineStride = src2.getScanlineStride();
        int s2PixelStride = src2.getPixelStride();
        int[] s2BandOffsets = src2.getBandOffsets();
        float[][] s2Data = src2.getFloatDataArrays();

        int dwidth = dst.getWidth();
        int dheight = dst.getHeight();
        int bands = dst.getNumBands();
        int dLineStride = dst.getScanlineStride();
        int dPixelStride = dst.getPixelStride();
        int[] dBandOffsets = dst.getBandOffsets();
        float[][] dData = dst.getFloatDataArrays();

        for (int b = 0, s1b = 0, s2b = 0; b < bands;
             b++, s1b += s1bd, s2b += s2bd) {
            float[] s1 = s1Data[s1b];
            float[] s2 = s2Data[s2b];
            float[] d = dData[b];
            float c = (float) gain;

            int s1LineOffset = s1BandOffsets[s1b];
            int s2LineOffset = s2BandOffsets[s2b];
            int dLineOffset = dBandOffsets[b];

            for (int h = 0; h < dheight; h++) {
                int s1PixelOffset = s1LineOffset;
                int s2PixelOffset = s2LineOffset;
                int dPixelOffset = dLineOffset;

                s1LineOffset += s1LineStride;
                s2LineOffset += s2LineStride;
                dLineOffset += dLineStride;

                for (int w = 0; w < dwidth; w++) {
                    float src = s1[s1PixelOffset];
                    d[dPixelOffset] = src + c * (src - s2[s2PixelOffset]);

                    s1PixelOffset += s1PixelStride;
                    s2PixelOffset += s2PixelStride;
                    dPixelOffset += dPixelStride;
                }
            }
        }
    }

    private void computeRectDouble(RasterAccessor src1,
                                   RasterAccessor src2,
                                   RasterAccessor dst) {
        int s1LineStride = src1.getScanlineStride();
        int s1PixelStride = src1.getPixelStride();
        int[] s1BandOffsets = src1.getBandOffsets();
        double[][] s1Data = src1.getDoubleDataArrays();

        int s2LineStride = src2.getScanlineStride();
        int s2PixelStride = src2.getPixelStride();
        int[] s2BandOffsets = src2.getBandOffsets();
        double[][] s2Data = src2.getDoubleDataArrays();

        int dwidth = dst.getWidth();
        int dheight = dst.getHeight();
        int bands = dst.getNumBands();
        int dLineStride = dst.getScanlineStride();
        int dPixelStride = dst.getPixelStride();
        int[] dBandOffsets = dst.getBandOffsets();
        double[][] dData = dst.getDoubleDataArrays();

        for (int b = 0, s1b = 0, s2b = 0; b < bands;
             b++, s1b += s1bd, s2b += s2bd) {
            double[] s1 = s1Data[s1b];
            double[] s2 = s2Data[s2b];
            double[] d = dData[b];
            double c = gain;

            int s1LineOffset = s1BandOffsets[s1b];
            int s2LineOffset = s2BandOffsets[s2b];
            int dLineOffset = dBandOffsets[b];

            for (int h = 0; h < dheight; h++) {
                int s1PixelOffset = s1LineOffset;
                int s2PixelOffset = s2LineOffset;
                int dPixelOffset = dLineOffset;

                s1LineOffset += s1LineStride;
                s2LineOffset += s2LineStride;
                dLineOffset += dLineStride;

                for (int w = 0; w < dwidth; w++) {
                    double src = s1[s1PixelOffset];
                    d[dPixelOffset] = src + c * (src - s2[s2PixelOffset]);

                    s1PixelOffset += s1PixelStride;
                    s2PixelOffset += s2PixelStride;
                    dPixelOffset += dPixelStride;
                }
            }
        }
    }
}
