package seed.minerva.apps.imse;

import jafama.FastMath;

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

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

import binaryMatrixFile.AsciiMatrixFile;
import binaryMatrixFile.BinaryMatrixFile;
import binaryMatrixFile.BinaryMatrixWriter;

import otherSupport.ColorMaps;
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.imse.IMSEOptics105_35;
import seed.minerva.imse.IMSEOptics135_50;
import seed.minerva.imse.IMSEOptics135_50_rescaled;
import seed.minerva.imse.IMSEOptics180_50;
import seed.minerva.imse.IMSEOptics75_25;
import seed.minerva.imse.IMSEOptics75_25_expanded_filter;
import seed.minerva.imse.IMSEOptics75_25_imgFielded;
import seed.minerva.imse.IMSEOptics75_25_imgFielded_fibreFielded;
import seed.minerva.imse.IMSEOptics75_25_rescaled;
import seed.minerva.imse.IMSEOptics75_25_silly_second_diag;
import seed.minerva.optics.Util;
import seed.minerva.optics.collection.HitsCollector;
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;


/** Generates the outputs for the AUG system for presentations etc.
 * 
 * @author oliford
 *
 */
public class PrettyPictures {
	
	/*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/pictures-fullFan-allBeams/";
	final static int nRays = 5000;
	final static double p0 = 0.27; // 0.4 to 0.8 ish
	final static double p1 = 0.81; // 0.4 to 0.8 ish
	final static int nPoints = 7;
	//*/
	
	/** 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(new IMSEOptics135_50_rescaled());
	
	//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.imseOptics.ccd;
	public static Plane polarisationPlane = sys.tubeOptics.PEMsFront;
	public static Element initRaysTarget = sys.mainMirror; //sys.mirrorBox.holeGlassFront;
	
	/** Don't draw rays that don't hit these elements */
	public static Surface mustHitToDraw = sys.tubeOptics.fibreEnds;
	
	final static double wavelen = 653e-9;
	
	public static void main(String[] args) {
		drawRayDiagrams();
		//drawBeamCyldImages();
	}
	
	public static void drawRayDiagrams(){
		
		VRMLDrawer vrmlOut = new VRMLDrawer(outPath + "/pictures.vrml", 0.005);
		vrmlOut.setDrawPolarisationFrames(false);
		vrmlOut.addVRML(AugMSESystem.vrmlScaleToAUGDDD);
		//vrmlOut.setSkipRays(39);
		
		sys.setupForPulse(178); //Jan2013 setup
		
		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(nPoints);
		svgOutCart.generateLineStyles(cols, svgRayWidth, svgOpticWidth);
		svgOutFlat.generateLineStyles(cols, svgRayWidth, svgOpticWidth);
		svgPol.generateLineStyles(cols, svgRayWidth, svgOpticWidth);

		drawVRMLFaroData(vrmlOut);
		
		System.out.println("Fibre plane centre: "); OneLiners.dumpArray(sys.tubeOptics.fibreEnds.getCentre());
		System.out.println("Fibre plane normal: "); OneLiners.dumpArray(sys.tubeOptics.fibreEnds.getNormal());
		
		//length of IMSE system (135-50)
		double l = 0.135 + 0.050 + 0.100;
		
		double c[] = sys.tubeOptics.fibreEnds.getCentre();
		double n[] = sys.tubeOptics.fibreEnds.getNormal(); 
		
		System.out.println("Approx CCD position:"); 
		OneLiners.dumpArray(
				new double[]{
					c[0] + l * n[0],
					c[1] + l * n[1],
					c[2] + l * n[2]
					});
		
		IntensityInfo intensityInfo = new IntensityInfo(sys);
		PlaneAngleInfo filterAngleProcs[] = new PlaneAngleInfo[]{
				new PlaneAngleInfo(sys.imseOptics.filterAtImage),
				new PlaneAngleInfo(sys.imseOptics.filterAtPEMs),
				new PlaneAngleInfo(sys.imseOptics.filterAtField)
			};
		
		HitsCollector mirrorHits = new HitsCollector(outPath + "/mirrorHits.bin", sys.mirrorBox.protectionCoverBack); 
		
		StatusOutput stat = new StatusOutput(PrettyPictures.class, nPoints*nRays);		
		for(int j=0; j < nPoints; j++){
			double p = p0 + (p1 - p0) * j / (nPoints - 1); 
			double startPos[] = new double[]{ //-1.3, -1.3, 0.1 };
					sys.nbiStart[0] + p * sys.nbiUnit[0],
					sys.nbiStart[1] + p * sys.nbiUnit[1],
					sys.nbiStart[2] + p * sys.nbiUnit[2],
			};
			
			svgOutCart.getSVG3D().startGroup("point" + j + "_" + p);
			svgOutFlat.getSVG3D().startGroup("point" + j + "_" + p);
			svgPol.getSVG().startGroup("point" + j + "_" + p);
			
			int nAttempts = 0, nHit = 0;
			
			for(int i=0; i < nRays; i++){
				 do{
					stat.doStatus(i);
					
					Pol.recoverAll();
					
					RaySegment ray = new RaySegment();
					ray.startPos =  startPos.clone();
					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(j*nRays+i);
					
					ray.processIntersections(null, intensityInfo);
					ray.processIntersections(sys.mainMirror, mirrorHits);
					ray.processIntersections(imagePlane, filterAngleProcs[0], filterAngleProcs[1], filterAngleProcs[2]);
					
					List<Intersection> hits = ray.getIntersections(mustHitToDraw); 
									
					if(hits.size() > 0){
						vrmlOut.drawRay(ray, cols[j]);
						svgOutCart.drawRay(ray, j);
						svgOutFlat.drawRay(ray, j);
						svgPol.drawRay(ray, j);
												
						nHit++;
						break;
					}
					if(nAttempts > 10000){
						break;
					}
					nAttempts++;
				}while(true);
			}
			svgOutCart.getSVG3D().endGroup();
			svgOutFlat.getSVG3D().endGroup();
			svgPol.getSVG().endGroup();
			
			System.out.println("\n---------------------------------------- "+j+" ----------------------------------------");
			System.out.println(j + "(p=" + p + "):\t" + nAttempts + "\t" + nHit);
			
			//intensityInfo.dump(true);
			
			intensityInfo.dump(Arrays.asList(new Surface[]{
					sys.mirrorBox.protectionCoverBack,
					sys.tubeOptics.fibreEnds,
					sys.imseOptics.plate1,
					sys.imseOptics.plate2,
					sys.imseOptics.ccd,				
					//((IMSEOptics75_25_Silly)sys.imseOptics).fibres2,
				}), sys.tubeOptics.fibreEnds, true);
			intensityInfo.reset();

			for(int k=0; k <3 ; k++){
				filterAngleProcs[k].dump();
				filterAngleProcs[k].reset();
			}
		}
		
		vrmlOut.drawOptic(sys);
		vrmlOut.addVRML("}"); //end of rotate/transform
		vrmlOut.destroy();
		stat.done();
		
		mirrorHits.destroy();

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

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

	private static void drawVRMLFaroData(VRMLDrawer vrmlOut) {
		AugMseFaroData losInfo = new AugMseFaroData();
		//sys.addElement(AugMSESystem.makeAllBeamCylds());
		//sys.addElement(losInfo.makeCylds());
		
		//sys.addElement(new Cylinder("R2m1", new double[]{ 0,0,0}, new double[]{ 0,0,1}, 2.1, 1.5, NullInterface.ideal()));
		//sys.addElement(new Cylinder("R1m0", new double[]{ 0,0,0}, new double[]{ 0,0,1}, 1.0, 1.5, NullInterface.ideal()));
		
		double A[] = losInfo.getCameraPos();
		double v[] = losInfo.getCameraViewVec();
		double u[] = losInfo.getCameraUpVec();
		
		vrmlOut.drawPolygonEdge(new double[][]{ 
				{ A[0], A[0] + 10 * v[0] }, 
				{ A[1], A[1] + 10 * v[1] }, 
				{ A[2], A[2] + 10 * v[2] },
		});
		vrmlOut.drawPolygonEdge(new double[][]{ 
				{ A[0], A[0] + 10 * u[0] }, 
				{ A[1], A[1] + 10 * u[1] }, 
				{ A[2], A[2] + 10 * u[2] },
		});
		
		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()));
	}

	public static void drawBeamCyldImages(){
		
		//double points[][] = cleanPoints("/work/ipp/augddd-models/vessel/limiter_s14_small.txt");
		double points[][] = cleanPoints("/work/ipp/augddd-models/mse-visible.txt");
		//double points[][] = Util.drawingToPoints(sys.makeAllBeamCylds(0.1).draw(), 0.005);
		//AugMseFaroData faro = new AugMseFaroData();
		//double points[][] = Util.drawingToPoints(faro.makeCylds(0.4).draw(), 0.1);
		int nRaysPerPoint = 500;
		
		BinaryMatrixWriter visibleOut = new BinaryMatrixWriter("/work/ipp/augddd-models/visible.bin", 4); 

		ImageCollector imgCollect =
				imagePlane == sys.imseOptics.ccd ? 
					new ImageCollector(		-0.006, 0.006, 600,  //For IMSE CCD plane
											-0.006, 0.006, 600 )
					: new ImageCollector(	-0.015, 0.015, 600,  //For Fibre plane
											-0.015, 0.015, 600 ); //*/
		
				
		StatusOutput stat = new StatusOutput(PrettyPictures.class, nRaysPerPoint*points.length);		
		for(int iP=0; iP < points.length; iP++){
			double startPos[] = points[iP];
			if(startPos == null)
				continue;
			
			int nHit = 0;
			for(int i=0; i < nRaysPerPoint; i++){
			 
				stat.doStatus(i);
				
				Pol.recoverAll();
				
				RaySegment ray = new RaySegment();
				ray.startPos =  startPos.clone();
				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(iP*nRays+i);
				
				nHit += ray.processIntersections(imagePlane, imgCollect);
				
				Pol.recoverAll();
				
			}
		
			System.out.println(iP + " / " + points.length + "\t" + nHit + " / " + nRaysPerPoint);
			if(nHit > 0)
				visibleOut.writeRow(points[iP], nHit);
			
		}
		
		imgCollect.writeImage(outPath + "/beamCyldsImage-beam3.bin");
		//imgCollect.writeImage(outPath + "/faroCylds.bin");
		visibleOut.close();
		
	}
	
	private static double[][] cleanPoints(String fileName){
		double points[][] = AsciiMatrixFile.mustLoad(fileName, false);
		
		double x0 = -2.500, x1 = -1.0;
		double y0 = -1.000, y1 =  1.0;
		double z0 =  0.000, z1 =  1.5;
		
		int nGood = 0;
		double ang = -1.963;
		for(int i=0;i < points.length;i++){
			points[i][0] /= 1000;
			points[i][1] /= 1000;
			points[i][2] /= 1000;
			double x = FastMath.cos(ang) * points[i][0] - FastMath.sin(ang) * points[i][1];
			double y = FastMath.sin(ang) * points[i][0] + FastMath.cos(ang) * points[i][1];
			double z = points[i][2];
			points[i][0] = x;
			points[i][1] = y;
			
			if(!(x >= x0 && x < x1 && y >= y0 && y < y1 && z >= z0 && z < z1)){
				points[i] = null;
				nGood++;
			}
		}
		
		System.out.println("nGood = " + nGood + " / " + points.length);
		return points;
	}
}
