package imseProc.proc.fft;

import org.eclipse.swt.widgets.Composite;
import jafama.FastMath;
import edu.emory.mathcs.jtransforms.fft.DoubleFFT_2D;
import imseProc.core.ComplexImage;
import imseProc.core.ImagePipeController;
import imseProc.core.Img;
import imseProc.core.ImgProcPipe;
import imseProc.core.ImgSource;

public class FFTProcessor extends ImgProcPipe {
	
	private FFTSWTController controller;

	double gaussWinX0, gaussWinY0, gaussWinSigmaX, gaussWinSigmaY;
	
	/** Crop output images to +/- this. 0 means don't crop */
	private int crop = 0;
	
	public FFTProcessor() {
		super(ComplexImage.class);
	}
	
	public FFTProcessor(ImgSource source, int selectedIndex) {
		super(ComplexImage.class, source, selectedIndex);		
	}
	
	protected boolean checkOutputSet(int nImagesIn){
		 outWidth = (crop > 0) ? Math.min(2*crop, inWidth) : inWidth;
         outHeight = (crop > 0) ? Math.min(2*crop, inHeight) : inHeight;
         int nImagesOut = nImagesIn;
         
         //allocate or re-use
         ComplexImage newOutSet[] = ComplexImage.checkBulkAllocation(this, (ComplexImage[])imagesOut, outWidth, outHeight, nImagesOut);
         if(newOutSet != imagesOut){
        	 imagesOut = newOutSet;
        	 return true;
         }
         
         return false;
	}
	
	protected int[] sourceIndices(int outIdx){
		return new int[]{ outIdx };
	}

	@Override
	public boolean doCalc(Img imageOutGen, Img sourceSet[], boolean settingsHadChanged) throws InterruptedException {
		DoubleFFT_2D fft = new DoubleFFT_2D(inHeight, inWidth);
		
		Img imageIn = sourceSet[0];
		ComplexImage imageOut = (ComplexImage)imageOutGen;

		int n = imageIn.getHeight() * imageIn.getWidth();
		
		double data[] = new double[2*n];

		boolean gaussValid = false;
		double gaussWin = 1;
		if(gaussWinX0 > 0 && gaussWinY0 > 0 && gaussWinSigmaX > 0 && gaussWinSigmaY > 0){
			gaussValid = true;
		}
	
		imageIn.startReading(); try{

			for(int y=0; y < imageIn.getHeight(); y++){
				for(int x=0; x < imageIn.getWidth(); x++){
					if(gaussValid){
						double argX = - FastMath.pow2(((double)x - gaussWinX0) / gaussWinSigmaX) / 2; 
						double argY = - FastMath.pow2(((double)y - gaussWinY0) / gaussWinSigmaY) / 2; 
						gaussWin = FastMath.exp(argX + argY);
					}
					data[y*inWidth+x] = imageIn.getPixelValue(x, y) * gaussWin;
					if(Double.isNaN(data[y*inWidth+x]))
						data[y*inWidth+x] = 0;
				}
			}
		}finally{ imageIn.endReading(); }

		fft.realForwardFull(data);

		//otherwise change the image
		imageOut.fillFromJTransformsPackedData(data, true, inWidth, inHeight);
		
		return true;
	}

	@Override
	public FFTProcessor clone() { return new FFTProcessor(connectedSource, getSelectedSourceIndex());	}

	@Override
	/** For the sink side */
	public ImagePipeController createPipeController(Class interfacingClass, Object args[], boolean asSink) {
		if(interfacingClass == Composite.class){
			controller = new FFTSWTController((Composite)args[0], (Integer)args[1], this);
			controllers.add(controller);
			return controller;
		}
		return null;
	}

	public void setGaussWindow(double x0, double y0, double fwhmX, double fwhmY) {
		this.gaussWinX0 = x0;
		this.gaussWinY0 = y0;
		this.gaussWinSigmaX = fwhmX / 2.35;
		this.gaussWinSigmaY = fwhmY / 2.35;
		settingsChanged = true;
		updateAllControllers();
		if(autoCalc)
			calc();
	}
	
	public double getGaussWindowX0(){ return gaussWinX0; }
	public double getGaussWindowY0(){ return gaussWinY0; }
	public double getGaussWindowFWHMX(){ return gaussWinSigmaX * 2.35; }
	public double getGaussWindowFWHMY(){ return gaussWinSigmaY * 2.35; }


	
	public void setCrop(int crop) {
		this.crop = crop;
		this.settingsChanged = true;
		updateAllControllers();
		if(autoCalc)
			calc();
	}
	
	public int getCrop(){ return this.crop; }

}
