package seed.minerva.apps.imse;

import cache.common.Cache;
import cache.randomAccessCache.RACache;
import cache.randomAccessCache.RACacheService;
import cache.randomAccessCache.RACacheSet;
import otherSupport.RandomManager;
import binaryMatrixFile.BinaryMatrixFile;

import seed.minerva.MinervaOpticsSettings;
import seed.minerva.aug.mse.AugMSESystem;
import seed.minerva.imse.IMSEOptics135_50;
import seed.minerva.optics.Util;
import seed.minerva.optics.collection.ImageCollector;
import seed.minerva.optics.drawing.VRMLDrawer;
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.surfaces.Plane;
import seed.minerva.optics.tracer.Tracer;
import seed.minerva.optics.types.Element;
import seed.minerva.optics.types.Pol;
import seed.minerva.optics.types.RaySegment;


/** PSF Info collection for the AUG IMSE system. 
 *  
 * @author oliford
 *
 */
public class PSFCollect implements Runnable {
	private final static String outPath = MinervaOpticsSettings.getAppsOutputPath() + "/rayTracing/augImse/psfCollect";
	private final static int nRaysPerSource = 100000;
	private final static int nRaysAbortNone = 2000; //abort after this many if none have been found
	private final static double rt2 = Math.sqrt(2);
	
	/*final static String cacheSet = "augImse-withWall-32x22x18"; //including wall, test run
	final static double gridDef[][] = new double[][]{ {-2.7, 0.5, 32 }, { -1.7, 0.5, 22 }, { -0.6, 1.2, 18 } }; //*/
	
	private final static String cacheSet = "augImse-withWall-64x44x36"; //including wall, test run
	private final static double gridDef[][] = new double[][]{ {-2.7, 0.5, 64 }, { -1.7, 0.5, 44 }, { -0.6, 1.2, 36 } }; //*/
	
	/*final static String cacheSet = "augImse-withWall-320x220x180"; //including wall 
	final static double gridDef[][] = new double[][]{ {-2.7, 0.5, 320 }, { -1.7, 0.5, 220 }, { -0.6, 1.2, 180 } }; //*/
	
	
	/*final static String cacheSet = "augImse-gridKeyedData-100x100";
	final static double gridDef[][] = new double[][]{ { -2.1, -0.4, 100 }, { -1.9, -0.6, 100 }, { -0.4, 0.4, 100} }; //*/

	/*final static String cacheSet = "augImse-small";
	final static double gridDef[][] = new double[][]{ { -1.8, -0.7, 10 }, { -1.7, -1.0, 10 }, { -0.2, 0.2, 10 } }; //*/
	
	/*final static String cacheSet = "augImse-vsmall"; //for debug, all points  in beam
	final static double gridDef[][] = new double[][]{ { -1.35, -1.25, 5 }, { -1.35, -1.30, 5 }, { 0.05, 0.10, 5 } }; //*/
	
	/*final static String cacheSet = "augImse-gridKeyedData-30x30-mueller";
	final static double gridDef[][] = new double[][]{{ -2.1, -0.5, 30.0 }, {-1.9, -0.8, 30.0}, {-0.4, 0.4, 30.0}}; //*/
	
	/** What rays to trace */
	private static double minIntensity = 0.001;
	private static boolean traceReflections = false;
	
	/** Optical system setup */
	private static AugMSESystem sys = new AugMSESystem(new IMSEOptics135_50());
	//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;
	private static Plane imagePlane = sys.imseOptics.ccd;
	private static Plane polarisationPlane = sys.tubeOptics.PEMsFront;
	private static Element initRaysTarget = sys.mirrorBox.protectionCoverFront;
	
	private final static double wavelen = 593e-9;
	
	private static double minX = Double.POSITIVE_INFINITY,  maxX = Double.NEGATIVE_INFINITY;
	private static double minY = Double.POSITIVE_INFINITY,  maxY = Double.NEGATIVE_INFINITY;
	private static double minZ = Double.POSITIVE_INFINITY,  maxZ = Double.NEGATIVE_INFINITY;

	private static int iY0=0, iY1=(int)gridDef[1][2], diY=1;
	private static int localID = 0;
	private static int nThreads = 4;
	private static boolean inhibitPrevCheck = false;
	private static Cache psfCache;
	
	private int threadID = 0;
	
	public static void main(String[] args) {
		
		psfCache = RACacheService.instance().getCache("optics.PSF");
		
		for(int i=0; i < args.length; i++){
			if(args[i].equalsIgnoreCase("--localid")) {
				localID = Integer.parseInt(args[i+1]); i++;
			}
			if(args[i].equalsIgnoreCase("--iY")){
				String parts[] = args[i+1].split(":");
				iY0 = Integer.parseInt(parts[0]); 
				diY = Integer.parseInt(parts[1]); 
				iY1 = Integer.parseInt(parts[2]);		
				i++;
			}
			if(args[i].equalsIgnoreCase("--noPrevCheck")){ //disable 'PSF already done' check
				inhibitPrevCheck = true;
			}
		}
		
		//((RACache)psfCache).cleanAllSets(cacheSet, true); if(true)return;
		
		
		PSFCollect rnr[] = new PSFCollect[nThreads];
		Thread thrd[] = new Thread[nThreads];
		for(int iT=0; iT < nThreads; iT++){
			rnr[iT] = new PSFCollect(iT);
			thrd[iT] = new Thread(rnr[iT]);
			thrd[iT].start();
		}
		
		int nAlive;
		do{
			try {
				Thread.sleep(100);
			} catch (InterruptedException e) { }
		
			nAlive = 0;
			for(int iT=0; iT < nThreads; iT++){
				if(thrd[iT].isAlive())
					nAlive++;
			}
			
		}while(nAlive > 0);
		//dumpGridComplete(psfGrid);
		//if(true)return;
	}
	
	public PSFCollect(int threadID) {
		this.threadID = threadID;
	}
	
	public void run(){
		String cacheTag = "localID-" + localID + "-threadID-"+threadID+"-iY-" + iY0 + "-" + diY + "-" + iY1;
		System.out.println(threadID + ": Saving PSFs to cache with tag '" + cacheTag + "'");
		
		PointSpreadBuilder psfBuild = new PointSpreadBuilder(polarisationPlane, outPath + "/psfData-"+threadID+".bin");
		PSFGrid psfGrid;
		synchronized (psfCache) {
			((RACache)psfCache).setCacheTag(cacheTag, false);
			psfGrid = new PSFGrid(psfCache, cacheSet, gridDef, DualGaussianPSF.class);	
		}
		
		//one of these for each thread
		RandomManager randMan = new RandomManager();
		
		for(int iY=(iY0 + threadID); iY <= iY1; iY += (diY * nThreads)) {
			ImageCollector imgCollect = new ImageCollector(-0.01, 0.01, 1000, -0.01, 0.01, 1000);
			
			for(int iZ=0; iZ < psfGrid.getNZ(); iZ++) {
				for(int iX=0; iX < psfGrid.getNX(); iX++) {
					
					if(!inhibitPrevCheck){
						PointSpreadFunction psfPrev = psfGrid.get(iX, iY, iZ);
						if(psfPrev != null){
							System.out.println("pos "+iX+", "+iY+", "+iZ+" already exists");
							continue;
						}
					}
					
					double startPos[] = psfGrid.gridPos(iX, iY, iZ);
					
					psfBuild.startNewPSF(startPos,
							//new GaussianPSF()
							new DualGaussianPSF(50)
							//new PointsPSF()
							//new MiniImagePSF(20, 20)
							);
					
					//VRMLDrawer v = new VRMLDrawer("/tmp/dbg.vrml", 0.001);
					//v.setDrawPolarisationFrames(false);
					for(int i=0; i < nRaysPerSource; i++) {
												RaySegment ray = new RaySegment();
						ray.startPos = startPos;
						ray.dir = Tracer.generateRandomRayTowardSurface(randMan, 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);
						//v.drawRay(ray);
						ray.processIntersections(imagePlane, psfBuild);
						ray.processIntersections(imagePlane, imgCollect);
						
						psfBuild.nextCoherentSet();
						Pol.recoverAll();

						if(i > nRaysAbortNone && psfBuild.getNPointsCollected() == 0)
							break;
					}
					//v.drawOptic(sys);
					//v.destroy();
					synchronized (psfCache) {
						((RACache)psfCache).setCacheTag(cacheTag, false);
						psfGrid.put(iX, iY, iZ, psfBuild.psfDone(true));	
					}
					
					System.out.println(iX + ", " + iY + ", " + iZ + ": " + psfBuild.getNPointsCollected());
					
					if(psfBuild.getNPointsCollected() > 0) {
						if(startPos[0] < minX) minX = startPos[0];
						if(startPos[0] > maxX) maxX = startPos[0];
						if(startPos[1] < minY) minY = startPos[1];
						if(startPos[1] > maxY) maxY = startPos[1];
						if(startPos[2] < minZ) minZ = startPos[2];
						if(startPos[2] > maxZ) maxZ = startPos[2];
					}
				}
			}
			
			imgCollect.writeImage(outPath + "/image-iY-"+iY+".bin");
		}
		
//		BinaryMatrixFile.mustWrite(outPath + "/psfAllData.bin", psfGrid.getAllData(), false);
	}
	
	
	/*
	private static void dumpGridComplete(PSFGrid psfGrid) {
		double g[][] = psfGrid.getGridDefinition();
		System.out.println("X: " + g[0][0] + " - " + g[0][1] + " x " + g[0][2]);
		System.out.println("Y: " + g[1][0] + " - " + g[1][1] + " x " + g[1][2]);
		System.out.println("Z: " + g[2][0] + " - " + g[2][1] + " x " + g[2][2]);
		
		double sourcePoses[][] = psfGrid.getSourcePositions();

		double minX = Double.POSITIVE_INFINITY, maxX = Double.NEGATIVE_INFINITY;
		double minY = Double.POSITIVE_INFINITY, maxY = Double.NEGATIVE_INFINITY;
		double minZ = Double.POSITIVE_INFINITY, maxZ = Double.NEGATIVE_INFINITY;
		//double dX = gX[1] - gX[0], dY = gY[1] - gY[0], dZ = gZ[1] - gZ[0];
		
		for(int i=0; i < sourcePoses.length; i++){
			if(sourcePoses[i][0] < minX) minX = sourcePoses[i][0];
			if(sourcePoses[i][0] > maxX) maxX = sourcePoses[i][0];
			if(sourcePoses[i][1] < minY) minY = sourcePoses[i][1];
			if(sourcePoses[i][1] > maxY) maxY = sourcePoses[i][1];
			if(sourcePoses[i][2] < minZ) minZ = sourcePoses[i][2];
			if(sourcePoses[i][2] > maxZ) maxZ = sourcePoses[i][2];
		}
		System.out.println(minX + " < X < " + maxX + ": " + ((minX - gX[0]) / dX) + " < iX < " + ((maxX - gX[0]) / dX));
		System.out.println(minY + " < Y < " + maxY + ": " + ((minY - gY[0]) / dY) + " < iY < " + ((maxY - gY[0]) / dY));
		System.out.println(minZ + " < Z < " + maxZ + ": " + ((minZ - gZ[0]) / dZ) + " < iZ < " + ((maxZ - gZ[0]) / dZ));
		
		BinaryMatrixFile.mustWrite(outPath + "/sourcePoses.bin", sourcePoses, false);
	}*/
		
}
