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

package com.lightcrafts.model.ImageEditor;

import com.lightcrafts.jai.utils.Functions;
import com.lightcrafts.jai.utils.Transform;
import com.lightcrafts.jai.JAIContext;
import com.lightcrafts.jai.opimage.HDROpImage;
import com.lightcrafts.model.LayerConfig;
import com.lightcrafts.model.OperationType;
import com.lightcrafts.model.SliderConfig;
import com.lightcrafts.model.Operation;

import com.lightcrafts.mediax.jai.*;
import com.lightcrafts.utils.ColorScience;

import java.awt.*;
import java.awt.image.DataBuffer;
import java.awt.image.RenderedImage;
import java.awt.image.renderable.ParameterBlock;
import java.text.DecimalFormat;

/**
 * Created by IntelliJ IDEA.
 * User: fabio
 * Date: Mar 30, 2005
 * Time: 9:55:56 AM
 * To change this template use File | Settings | File Templates.
 */
public class HDROperation extends BlendedOperation {
    private double radius = 250;
    private double gamma = 2.2;
    private double detail = 0.8;
    private short[] tableDataUShort = new short[0x10000];
    private byte[] tableDataByte = new byte[0x100];
    private LookupTableJAI byteLut = null;
    private LookupTableJAI ushortLut = null;

    private final static String RADIUS = "Radius";
    private final static String GAMMA = "Gamma";
    private final static String DETAIL = "Detail";

    public HDROperation(Rendering rendering, OperationType type) {
        super(rendering, type);

        DecimalFormat format = new DecimalFormat("0.00");

        addSliderKey(RADIUS);
        setSliderConfig(RADIUS, new SliderConfig(100, 500, radius, 10, true, format));
        addSliderKey(GAMMA);
        setSliderConfig(GAMMA, new SliderConfig(0.1, 10, gamma, .1, true, format));

        addSliderKey(DETAIL);
        setSliderConfig(DETAIL, new SliderConfig(0, 1, detail, .1, false, format));
    }

    public boolean neutralDefault() {
        return false;
    }

    static final OperationType type = new OperationTypeImpl("Tone Mapper");

    public void setSliderValue(String key, double value) {
        value = roundValue(key, value);
        
        if (key == RADIUS && radius != value) {
            radius = value;
        } else if (key == GAMMA && gamma != value) {
            gamma = value;
            byteLut = null;
            ushortLut = null;
        } else if (key == DETAIL && detail != value) {
            detail = value;
        } else
            return;
        
        super.setSliderValue(key, value);
    }

    private LookupTableJAI computeGammaTable(int dataType) {
        if (dataType == DataBuffer.TYPE_BYTE) {
            if (byteLut != null)
                return byteLut;
            for (int i = 0; i < tableDataByte.length; i++) {
                tableDataByte[i] = (byte) (0xFF * Math.pow(i / (double) 0xFF, gamma) + 0.5);
            }
            return byteLut = new LookupTableJAI(tableDataByte);
        } else {
            if (ushortLut != null)
                return ushortLut;
            for (int i = 0; i < tableDataUShort.length; i++) {
                tableDataUShort[i] = (short) (0xFFFF * Math.pow(i / (double) 0xFFFF, gamma) + 0.5);
            }
            return ushortLut = new LookupTableJAI(tableDataUShort, true);
        }
    }

    class DesaturateInvertProcessor implements ImageProcessor {
        public RenderedOp process(RenderedImage source) {
            RenderedImage singleChannel;
            if (source.getColorModel().getNumComponents() == 3) {
                double[][] yChannel = new double[][]{{ColorScience.Wr, ColorScience.Wg, ColorScience.Wb, 0}};

                ParameterBlock pb = new ParameterBlock();
                pb.addSource( source );
                pb.add( yChannel );
                singleChannel = JAI.create("BandCombine", pb, null);
            } else
                singleChannel = source;

            RenderedOp invert = JAI.create("Not", singleChannel, JAIContext.noCacheHint);       // Invert
            LookupTableJAI table = computeGammaTable(invert.getColorModel().getTransferType());
            ParameterBlock pb = new ParameterBlock();
            pb.addSource(invert);
            pb.add(table);
            // we cache this since convolution scans its input multiple times
            return JAI.create("lookup", pb, null);
        }
    }

    private DesaturateInvertProcessor desaturateInvert = new DesaturateInvertProcessor();

    private class ToneMaperTransform extends BlendedTransform {
        Operation op;

        ToneMaperTransform(PlanarImage source, Operation op) {
            super(source);
            this.op = op;
        }

        public PlanarImage setFront() {
            // Calculate a blurred desautuated inverted version of the source as a mask
            PlanarImage front = Functions.gaussianBlur(back, rendering, op, desaturateInvert, radius * scale);

            if (detail > 0) {
                RenderedImage singleChannel;
                if (back.getColorModel().getNumComponents() == 3) {
                    double[][] yChannel = new double[][]{{ColorScience.Wr, ColorScience.Wg, ColorScience.Wb, 0}};

                    ParameterBlock pb = new ParameterBlock();
                    pb.addSource( back );
                    pb.add( yChannel );
                    singleChannel = JAI.create("BandCombine", pb, null);
                } else
                    singleChannel = back;

                ParameterBlock pb = new ParameterBlock();
                pb.addSource( singleChannel );
                pb.add(2f * scale);
                pb.add(20f);
                RenderingHints hints = new RenderingHints(JAI.KEY_BORDER_EXTENDER,
                                                          BorderExtender.createInstance(BorderExtender.BORDER_COPY));
                RenderedOp bilateral = JAI.create("BilateralFilter", pb, hints);

                pb = new ParameterBlock();
                pb.addSource(bilateral);
                pb.addSource(front);
                pb.add("Overlay");
                pb.add(detail);
                front = JAI.create("Blend", pb, null);
            }

            return front;
        }
    }

    protected void updateOp(Transform op) {
        op.update();
    }

    protected BlendedTransform createBlendedOp(PlanarImage source) {
        return new HDROperation.ToneMaperTransform(source, this);
    }

    public OperationType getType() {
        return type;
    }

    public LayerConfig getDefaultLayerConfig() {
        return new LayerConfig(new LayerModeImpl("Soft Light"), .75);
    }
}
