package imseProc.platesGen;

import jafama.FastMath;

import java.nio.ByteBuffer;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Random;

import oneLiners.OneLiners;

import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Display;

import seed.minerva.optics.OpticApprox;
import seed.minerva.optics.Util;

import imseProc.core.ByteBufferImage;
import imseProc.core.DoubleFlatArrayImage;
import imseProc.core.IMSEProc;
import imseProc.core.ImagePipeController;
import imseProc.core.Img;
import imseProc.core.ImgPipe;
import imseProc.core.ImgSource;

/** Simple simulation of a set of plates, polariser and lens using
 * Veiras phase formula */
public class PlatesGenSource extends ImgPipe implements ImgSource {
	private DoubleFlatArrayImage images[];

	public int width;
	public int height;
	public int nImages;
	public double focalLength;
	public double pixelSize;
	public double polariserAng;
	public double inputAmp[];
	public double inputWavelen[];
	public double inputPolAng[];
	public double inputEllip[];
	
	public double plateInfo[][];
	
	private int lastGeneratedImage = -1;
	
	private boolean isIdle = false;

		//plates:
	//	optic axis, tilt, surface
	//polariser angle
	//init polarisation
	
		 
	public PlatesGenSource() {
		this.width = -1;
		this.images = null;
	}
	
	private void calc(){
		isIdle = false;
		IMSEProc.ensureFinalUpdate(this, new Runnable() { @Override public void run() { doCalc(); } });
	}
	
	private void doCalc() {
		
		int nPlates = plateInfo.length;
		double plateNorm[][] = new double[nPlates][];
		double plateUp[][] = new double[nPlates][];
		double plateRight[][] = new double[nPlates][];
		
		for(int iP=0; iP < nPlates; iP++){
			double L = plateInfo[iP][0];
			double no = plateInfo[iP][1];
			double ne = plateInfo[iP][2];
			double axisAng = plateInfo[iP][3];
			double axisTilt = plateInfo[iP][4];
			double plateTiltPara = plateInfo[iP][5];
			double plateTiltPerp = plateInfo[iP][6];
			if(L <= 0)
				continue;
			
			//start with axis up and normal in z
			plateNorm[iP] = new double[]{
					-FastMath.sin(plateTiltPara) * FastMath.sin(axisAng),
					-FastMath.sin(plateTiltPara) * FastMath.cos(axisAng),
					FastMath.cos(plateTiltPara),
				};
			plateUp[iP] = new double[]{
					FastMath.cos(plateTiltPara) * FastMath.sin(axisAng),
					FastMath.cos(plateTiltPara) * FastMath.cos(axisAng),
					FastMath.sin(plateTiltPara),
				};
			plateRight[iP] = Util.cross(plateNorm[iP], plateUp[iP]);
			

			plateNorm[iP] = OneLiners.rotateVectorAroundAxis(plateTiltPerp, plateUp[iP], plateNorm[iP]);
			plateRight[iP] = OneLiners.rotateVectorAroundAxis(plateTiltPerp, plateUp[iP], plateRight[iP]);
		}
		
		
		lastGeneratedImage = -1;
		images = new DoubleFlatArrayImage[nImages];
		
		double scanAng[] = new double[nImages];
		for(int iT=0; iT < nImages; iT++){
			double uT = (double)iT / nImages;
			scanAng[iT] = uT * 36000;
			
			double imageData[] = new double[width*height];			
			
			for(int iC=0; iC < inputAmp.length; iC++){
				
				double polAng = inputPolAng[iC];// + uT * 2 * Math.PI;
				double s0[] = new double[]{
						(1.0-inputEllip[iC]) * FastMath.cos(2*polAng), 
						(1.0-inputEllip[iC]) * FastMath.sin(2*polAng), 
						inputEllip[iC] 
				};
				
				for(int iY=0; iY < height; iY++){
					double uY = iY / (height-1.0);
					
					for(int iX=0; iX < width; iX++){
						double uX = iX / (width-1.0);
						
						double x = (iX  - width/2) * pixelSize; 
						double y = (iY  - height/2) * pixelSize;
						double d = FastMath.sqrt(x*x + y*y);
						
						double incidenceVec[] = Util.reNorm(new double[]{ x, y, focalLength });
						
							
						double s[] = s0.clone();					
						for(int iP=0; iP < nPlates; iP++){
							double L = plateInfo[iP][0];
							double no = plateInfo[iP][1];
							double ne = plateInfo[iP][2];
							double axisAng = plateInfo[iP][3];
							double axisTilt = plateInfo[iP][4];
							double plateTiltPara = plateInfo[iP][5];
							double plateTiltPerp = plateInfo[iP][6];
							double scanMax = plateInfo[iP][7];
							
							axisAng += uT * scanMax;
							
							if(L <= 0 || false)
								continue;
							
							double alpha = FastMath.acos(Util.dot(incidenceVec, plateNorm[iP]));
							double u = Util.dot(incidenceVec, plateUp[iP]);
							double r = Util.dot(incidenceVec, plateRight[iP]);
							double delta = FastMath.atan2(r, u);
							
							double opd = OpticApprox.waveplateOPD(1.0, no, ne, axisTilt, delta, alpha, L);
							double phi = 2*Math.PI*opd / inputWavelen[iC];
							
							
							double c2θ = FastMath.cos(2 * axisAng);
							double s2θ = FastMath.sin(2 * axisAng);
							double cφ = FastMath.cos(phi);
							double sφ = FastMath.sin(phi);
							
							//muller matrix for delay of φ at angle θ
							s = new double[]{
									s[0]*(c2θ*c2θ + cφ*s2θ*s2θ)		+s[1]*(1.0-cφ)*s2θ*c2θ			-s[2]*sφ*s2θ, 
									s[0]*(1.0-cφ)*s2θ*c2θ 			+s[1]*(s2θ*s2θ + cφ*c2θ*c2θ)	+s[2]*sφ*c2θ,
									s[0]*sφ*s2θ						-s[1]*sφ*c2θ					+s[2]*cφ,
							};						
						}
						
						//apply muller matrix of polariser and take just the intensity 
						imageData[iY*width+iX] += inputAmp[iC] * 0.5 * (FastMath.cos(2*polariserAng) * s[0] + FastMath.sin(2*polariserAng) * s[1]);
					}
				}
			}
			
			images[iT] = new DoubleFlatArrayImage(this, iT, width, height, imageData);
			
			setSeriesMetaData("platesGen/scanAngle", scanAng);
		
			lastGeneratedImage = iT;
			notifyImageSetChanged();
			
			System.out.print(".");
		}
		
		notifyImageSetChanged();
		
		System.out.println("Plates calc done");
		isIdle = true;
	}
			
	@Override
	public int getNumImages() { return (lastGeneratedImage >= 0) ? (lastGeneratedImage+1) : 0; }

	@Override
	public Img getImage(int imgIdx) {
		return (images != null && imgIdx >= 0 && imgIdx <= lastGeneratedImage)
					? images[imgIdx] : null;
	}
	
	@Override
	public Img[] getImageSet() { return lastGeneratedImage >= 0 ? Arrays.copyOf(images, lastGeneratedImage+1) : new Img[0]; }
			
	@Override
	public ImagePipeController createPipeController(Class interfacingClass, Object args[], boolean asSink) {
		ImagePipeController controller = null;
		if(interfacingClass == Composite.class){
			controller = new PlatesGenSWTControl((Composite)args[0], (Integer)args[1], this);
			controllers.add(controller);
		}
		return controller;
	}

	@Override
	public ImgSource clone() {
		return new PlatesGenSource();
	}
	
	public void setAll(int nImages, int width, int height, 
			double focalLength, double pixelSize, double polariserAng, double plateInfo[][],
			double inputAmp[], double inputWavelen[], double inputPolAng[], double inputEllip[]){
		this.nImages = nImages;
		this.width = width;
		this.height = height;
		this.focalLength = focalLength;
		this.pixelSize = pixelSize;
		this.polariserAng = polariserAng;
		this.plateInfo = plateInfo;
		this.inputAmp = inputAmp;
		this.inputWavelen = inputWavelen;
		this.inputPolAng = inputPolAng;
		this.inputEllip = inputEllip;
		
		calc();
	}

	public boolean isIdle() { return isIdle; };
}
