package seed.minerva.apps.imse;

import jafama.FastMath;

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

import cache.common.Cache;
import cache.randomAccessCache.RACacheService;

import binaryMatrixFile.BinaryMatrixFile;
import binaryMatrixFile.BinaryMatrixWriter;

import otherSupport.ColorMaps;
import otherSupport.RandomManager;
import otherSupport.StatusOutput;


import oneLiners.OneLiners;

import seed.minerva.MinervaOpticsSettings;
import seed.minerva.aug.mse.AugMSESystem;
import seed.minerva.aug.mse.AugMseFaroData;
import seed.minerva.optics.Util;
import seed.minerva.optics.collection.ImageCollector;
import seed.minerva.optics.collection.IntensityInfo;
import seed.minerva.optics.collection.LensDepolarisationInfoCollector;
import seed.minerva.optics.collection.PlaneAngleInfo;
import seed.minerva.optics.drawing.SVGCylindricalProjection;
import seed.minerva.optics.drawing.SVGRayDrawing;
import seed.minerva.optics.drawing.VRMLDrawer;
import seed.minerva.optics.interfaces.NullInterface;
import seed.minerva.optics.optics.Box;
import seed.minerva.optics.optics.SimpleDoubleConvexLens;
import seed.minerva.optics.optimisation.OptimiseLensCurvature;
import seed.minerva.optics.pointSpread.DualGaussianPSF;
import seed.minerva.optics.pointSpread.PSFGrid;
import seed.minerva.optics.pointSpread.PointSpreadBuilder;
import seed.minerva.optics.pointSpread.PointSpreadFunction;
import seed.minerva.optics.pointSpread.PointsPSF;
import seed.minerva.optics.surfaces.Cylinder;
import seed.minerva.optics.surfaces.Plane;
import seed.minerva.optics.surfaces.Square;
import seed.minerva.optics.tracer.Tracer;
import seed.minerva.optics.types.Element;
import seed.minerva.optics.types.Intersection;
import seed.minerva.optics.types.Optic;
import seed.minerva.optics.types.Pol;
import seed.minerva.optics.types.RaySegment;
import seed.minerva.optics.types.Surface;


/** Copy of PrrettyPictures, but tracing the system backwards from the fibre plane
 * in an attempt to match Jörg Hobirk's comment that backlit fibres give the smallest
 * spot nearer the mirror.
 * 
 * @author oliford
 *
 */
public class Backlighting {
	
	/*final static String outPath = MinervaSettings.getAppsOutputPath() + "/rayTracing/augImse/pictures-focusing/";
	final static int nRays = 10;
	final static double p0 = 0.58; // 0.4 to 0.8 ish
	final static double p1 = 0.62; // 0.4 to 0.8 ish
	final static int nPoints = 4;
	//*/
	
	final static String outPath = MinervaOpticsSettings.getAppsOutputPath() + "/rayTracing/augImse/backlighting/";
	final static int nRaysPerFibre = 40; 
	final static int nChannels = 14;
	final static int nVertical = 6;
	final static double fibreRotation = -30 * Math.PI / 180;
	final static double channelFibreSeparation = 0.002;
	final static double verticalFibreSeparation = 0.0013;
	final static double fibreEmissionRadius = 0.0001 / 2;
	final static double fibreEmissionAngle = 1.0 / 1.4;
	
	private final static double fibrePlaneAdjust = 0.000;
	
	//*/
	
	/** What rays to trace */
	public static double minIntensity = 0.01;
	public static boolean traceReflections = false;
	
	private static final double svgRayWidth = 0.0001;
	private static final double svgOpticWidth = 0.0003;
	
	/** Optical system setup */
	public static AugMSESystem sys = new AugMSESystem(null);
	
	//we require three optics;
	// 1) The imaging plane, which will collect the light INTENSITY.
	// 2) The polarisation sensitive plane, which collects the light polarisation.
	// 3) The inital target to fire rays at.
	// e.g. for the normal MSE system, 1 is thee fibre plane, 2 is the PEMs and 3 is the main mirror:		
	//public static Plane imagePlane = sys.tubeOptics.fibreEnds;
	public static Plane imagePlane = sys.beamPlane;
	public static Plane polarisationPlane = sys.beamPlane;
	public static Element initRaysTarget = sys.tubeOptics.lens4BackDish; //sys.mirrorBox.holeGlassFront;
	
	final static double wavelen = 593e-9;
	
	public static void main(String[] args) {
		drawRayDiagrams();
		//drawBeamCyldImages();
	}
	
	public static void drawRayDiagrams(){
		
		VRMLDrawer vrmlOut = new VRMLDrawer(outPath + "/pictures.vrml", 0.005);
		vrmlOut.addVRML("Separator {\n" + //rescale to match the augddd STL models
				"Scale { scaleFactor 1000 1000 1000 }\n" +
				"Rotation { rotation 0 0 1  1.963 }");
		vrmlOut.setDrawPolarisationFrames(false);
		//vrmlOut.setSkipRays(39);
		
		double rot[][] = new double[3][];
		rot[0] = sys.tubeOptics.fibreEnds.getNormal();
		rot[2] = Util.reNorm(Util.cross(rot[0], sys.mainMirror.getNormal()));
		rot[1] = Util.reNorm(Util.cross(rot[2], rot[0]));
				
		//One svg drawer in cartesian x/y/z and one in the mirror normal plane
		SVGRayDrawing svgOutCart = new SVGRayDrawing(outPath + "/raysCart", new double[]{ -2, -4, -1, 1, 0, 1 }, true);
		SVGRayDrawing svgOutFlat = new SVGRayDrawing(outPath + "/raysFlat", new double[]{ -2, -4, -1, 1, 0, 1 }, true, rot);
		SVGCylindricalProjection svgPol = new SVGCylindricalProjection(outPath + "/rayPol.svg", 5, -2, 2, true);
		//double cols[][] = ColorMaps.jet(nChannels);
		double cols[][] = ColorMaps.alternating2D2x2(nChannels, nVertical);
		svgOutCart.generateLineStyles(cols, svgRayWidth, svgOpticWidth);
		svgOutFlat.generateLineStyles(cols, svgRayWidth, svgOpticWidth);
		svgPol.generateLineStyles(cols, svgRayWidth, svgOpticWidth);
		
		
		AugMseFaroData losInfo = new AugMseFaroData();
		sys.addElement(AugMSESystem.makeAllBeamCylds());
		sys.addElement(losInfo.makeCylds());
		
		sys.addElement(new Cylinder("R2m5", new double[]{ 0,0,0}, new double[]{ 0,0,1}, 2.5, 2.2, NullInterface.ideal()));
		sys.addElement(new Cylinder("R0m8", new double[]{ 0,0,0}, new double[]{ 0,0,1}, 0.8, 2.2, NullInterface.ideal()));
		
		double A[] = losInfo.getCameraPos();
		double v[] = losInfo.getCameraViewVec();
		double u[] = losInfo.getCameraUpVec();
		
		System.out.println("hFOV = " + losInfo.getHorizFieldOfView()*180/Math.PI);
		System.out.println("vFOV = " + losInfo.getVertFieldOfView()*180/Math.PI);
		
		double ret[][][][] = losInfo.getPoints(0.01, sys.nbiStart, sys.nbiUnit, 0.20, -1); 
		//sys.addElement(new Optic("losIntBoxes", losInfo.intBoxes));
		//sys.addElement(new Box("basePlane", new double[]{ 0, 0, -2 }, new double[]{ -2, -2, -1 }, null, NullInterface.ideal()));
		
		StatusOutput stat = new StatusOutput(Backlighting.class, nChannels*nRaysPerFibre);
		for(int iV=0; iV < nVertical; iV++){
			for(int iC=0; iC < nChannels; iC++){
				double a =  (-(nChannels-1)/2 + iC) * channelFibreSeparation;
				double b =  (-(nVertical-1)/2 + iV) * verticalFibreSeparation;
				double x = a * FastMath.cos(fibreRotation) - b * FastMath.sin(fibreRotation);
				double y = a * FastMath.sin(fibreRotation) + b * FastMath.cos(fibreRotation);
				double fpC[] = sys.tubeOptics.fibreEnds.getCentre();
				double startPos[] = new double[]{
						fpC[0] + x * rot[1][0] + y * rot[2][0] + fibrePlaneAdjust * rot[0][0],
						fpC[1] + x * rot[1][1] + y * rot[2][1] + fibrePlaneAdjust * rot[0][1],
						fpC[2] + x * rot[1][2] + y * rot[2][2] + fibrePlaneAdjust * rot[0][2],
				};
				
				svgOutCart.getSVG3D().startGroup("point" + iC + "_" + x);
				svgOutFlat.getSVG3D().startGroup("point" + iC + "_" + x);
				svgPol.getSVG().startGroup("point" + iC + "_" + x);
				
				int nAttempts = 0, nHit = 0;
				
				for(int i=0; i < nRaysPerFibre; i++){
					 do{
						stat.doStatus(i);
						
						Pol.recoverAll();
						
						double r = FastMath.sqrt(RandomManager.instance().nextUniform(0, 1));
						double theta = RandomManager.instance().nextUniform(0, 1) * 2 * Math.PI;
						double dx = fibreEmissionRadius*r*FastMath.cos(theta);
						double dy = fibreEmissionRadius*r*FastMath.sin(theta);
						
						RaySegment ray = new RaySegment();
						ray.startPos = new double[]{
								startPos[0] + dx * rot[1][0] + dy * rot[2][0],
								startPos[1] + dx * rot[1][1] + dy * rot[2][1],
								startPos[2] + dx * rot[1][2] + dy * rot[2][2],
						};
						ray.dir = Tracer.generateRandomRayTowardSurface(ray.startPos, initRaysTarget);
						
						ray.length = Double.POSITIVE_INFINITY;
						ray.up = Util.cross(Util.reNorm(Util.cross(ray.dir, new double[]{0,0,1})), ray.dir);
						
						ray.E0 = PointSpreadFunction.getInputStatesForMuellerCalc();
						ray.wavelength = wavelen;
						
						Tracer.trace(sys, ray, 100, minIntensity, traceReflections);
						
						stat.doStatus(iC*nRaysPerFibre+i);
						
						List<Intersection> hits = ray.getIntersections(sys.beamPlane);
										
						if(hits.size() > 0){
							//int colNum = iV*nChannels+iC;
							int colNum = iC*nVertical+iV;
							vrmlOut.drawRay(ray, cols[colNum]);
							svgOutCart.drawRay(ray, colNum);
							svgOutFlat.drawRay(ray, colNum);
							svgPol.drawRay(ray, colNum);
							
							
							nHit++;
							break;
						}
						if(nAttempts > 10000){
							break;
						}
						nAttempts++;
					}while(true);
				}
				svgOutCart.getSVG3D().endGroup();
				svgOutFlat.getSVG3D().endGroup();
				svgPol.getSVG().endGroup();
				
				System.out.println("\n---------------------------------------- "+iC+" ----------------------------------------");
				System.out.println(iC + ":\t" + nAttempts + "\t" + nHit);
				
				
			}
		}
		
		vrmlOut.drawOptic(sys);
		vrmlOut.addVRML("}");
		vrmlOut.destroy();
		stat.done();

		svgOutCart.drawElement(sys);
		svgOutCart.destroy();

		svgOutFlat.drawElement(sys);
		svgOutFlat.destroy();
		
		svgPol.drawElement(sys);
		svgPol.destroy();
		
	}

}
