package ij.plugin.filter;
import ij.*;
import ij.process.*;
import ij.gui.*;
import ij.measure.*;
import ij.plugin.ContrastEnhancer;
import ij.plugin.frame.Recorder;
import java.awt.*;
import java.util.*;


/** This class implements the Process/FFT/Custom Filter command. */
public class FFTCustomFilter implements  PlugInFilter, Measurements {

    private ImagePlus imp;
    private static int filterIndex = 1;
    private int slice;
    private int stackSize;  
    private ImageProcessor filter;
    private static boolean processStack;
    private boolean padded;
    private int originalWidth;
    private int originalHeight;
    private Rectangle rect = new Rectangle();

    public int setup(String arg, ImagePlus imp) {
        this.imp = imp;
        if (imp==null) {
            IJ.noImage();
            return DONE;
        }
        this.stackSize = imp.getStackSize();
        filter = getFilter();
        if (filter==null)
            return DONE;
        if (imp.getProperty("FHT")!=null) {
            IJ.error("FFT Custom Filter", "Spatial domain (non-FFT) image required");
            return DONE;
        } else
            return processStack?DOES_ALL+DOES_STACKS:DOES_ALL;
    }

    public void run(ImageProcessor ip) {
        slice++;
        FHT fht = newFHT(ip);
        if (slice==1)
            filter = resizeFilter(filter, fht.getWidth());
        ((FHT)fht).transform();
        customFilter(fht);      
        doInverseTransform(fht, ip);
        if (slice==1)
            ip.resetMinAndMax();
        if (slice==stackSize) {
            new ContrastEnhancer().stretchHistogram(imp, 0.0);
            imp.updateAndDraw();
        }
        IJ.showProgress(1.0);
        if (Recorder.record && slice==1)
            Recorder.recordCall("FFT.filter(imp,filter); //see Help/Examples/JavaScript/FFT Filter");
    }
    
    void doInverseTransform(FHT fht, ImageProcessor ip) {
        showStatus("Inverse transform");
        fht.inverseTransform();
        //if (fht.quadrantSwapNeeded)
        //  fht.swapQuadrants();
        fht.resetMinAndMax();
        ImageProcessor ip2 = fht;
        fht.setRoi(rect.x, rect.y, rect.width, rect.height);
        ip2 = fht.crop();
        int bitDepth = fht.originalBitDepth>0?fht.originalBitDepth:imp.getBitDepth();
        switch (bitDepth) {
            case 8: ip2 = ip2.convertToByte(true); break;
            case 16: ip2 = ip2.convertToShort(true); break;
            case 24:
                showStatus("Setting brightness");
                fht.rgb.setBrightness((FloatProcessor)ip2);
                ip2 = fht.rgb; 
                fht.rgb = null;
                break;
            case 32: break;
        }
        ip.insert(ip2, 0, 0);
    }

    FHT newFHT(ImageProcessor ip) {
        FHT fht;
        int width = ip.getWidth();
        int height = ip.getHeight();
        int maxN = Math.max(width, height);
        int size = 2;
        while (size<1.5*maxN) size *= 2;        
        rect.x = (int)Math.round((size-width)/2.0);
        rect.y = (int)Math.round((size-height)/2.0);
        rect.width = width;
        rect.height = height;
        FFTFilter fftFilter = new FFTFilter();
        if (ip instanceof ColorProcessor) {
            showStatus("Extracting brightness");
            ImageProcessor ip2 = ((ColorProcessor)ip).getBrightness();
            fht = new FHT(fftFilter.tileMirror(ip2, size, size, rect.x, rect.y));
            fht.rgb = (ColorProcessor)ip.duplicate(); // save so we can later update the brightness
        } else
            fht = new FHT(fftFilter.tileMirror(ip, size, size, rect.x, rect.y));
        fht.originalWidth = originalWidth;
        fht.originalHeight = originalHeight;
        fht.originalBitDepth = imp.getBitDepth();
        return fht;
    }
    
    void showStatus(String msg) {
        if (stackSize>1)
            IJ.showStatus("FFT: " + slice+"/"+stackSize);
        else
            IJ.showStatus(msg);
    }
        
    void customFilter(FHT fht) {
        int size = fht.getWidth();
        showStatus("Filtering");
        fht.swapQuadrants(filter);
        float[] fhtPixels = (float[])fht.getPixels();
        boolean isFloat = filter.getBitDepth()==32;
        for (int i=0; i<fhtPixels.length; i++) {
            if (isFloat)
                fhtPixels[i] = fhtPixels[i]*filter.getf(i);
            else
                fhtPixels[i] = (float)(fhtPixels[i]*(filter.get(i)/255.0));
        }
        fht.swapQuadrants(filter);
    }
    
    ImageProcessor getFilter() {
        int[] wList = WindowManager.getIDList();
        if (wList==null || wList.length<2) {
            IJ.error("FFT", "A filter (as an open image) is required.");
            return null;
        }
        String[] titles = new String[wList.length];
        for (int i=0; i<wList.length; i++) {
            ImagePlus imp = WindowManager.getImage(wList[i]);
            titles[i] = imp!=null?imp.getTitle():"";
        }
        if (filterIndex<0 || filterIndex>=titles.length)
            filterIndex = 1;
        GenericDialog gd = new GenericDialog("FFT Filter");
        gd.addChoice("Filter:", titles, titles[filterIndex]);
        if (stackSize>1)
            gd.addCheckbox("Process entire stack", processStack);
        gd.addHelp(IJ.URL+"/docs/menus/process.html#fft-filter");
        gd.showDialog();
        if (gd.wasCanceled())
            return null;
        filterIndex = gd.getNextChoiceIndex();
        if (stackSize>1)
            processStack = gd.getNextBoolean();
        ImagePlus filterImp = WindowManager.getImage(wList[filterIndex]);
        if (filterImp==imp) {
            IJ.error("FFT", "The filter cannot be the same as the image being filtered.");
            return null;
        }       
        if (filterImp.getStackSize()>1) {
            IJ.error("FFT", "The filter cannot be a stack.");
            return null;
        }       
        ImageProcessor filter = filterImp.getProcessor();
        if (filter!=null && filter.getBitDepth()!=32)       
            filter =  filter.convertToByte(true);       
        return filter;
    }
    
    ImageProcessor resizeFilter(ImageProcessor ip, int maxN) {
        int width = ip.getWidth();
        int height = ip.getHeight();
        if (width==maxN && height==maxN)
            return ip;
        showStatus("Scaling filter to "+ maxN + "x" + maxN);
        return ip.resize(maxN, maxN);
    }
        
}