package ipp.imse.fusionOptics.mse;


import ipp.imse.fusionOptics.imse.IMSEOptics;
import jafama.FastMath;

import java.util.ArrayList;
import java.util.LinkedList;

import fusionOptics.MinervaOpticsSettings;
import fusionOptics.Util;
import fusionOptics.drawing.VRMLDrawer;
import fusionOptics.interfaces.Absorber;
import fusionOptics.interfaces.NullInterface;
import fusionOptics.interfaces.Reflector;
import fusionOptics.surfaces.*;
import fusionOptics.types.*;

import oneLiners.OneLiners;

import binaryMatrixFile.AsciiMatrixFile;

import algorithmrepository.Algorithms;



/** Optics, geometry etc for AUG (I)MSE system */
public class AugMSESystem extends Optic {
	
	public static final double globalUp[] = new double[]{ 0,0,1 };
	
	/** NBI Geometry, auto generated by 
	 * 		minerva-aug/seed.minerva.neutralBeams.aug.AugHeatingBeams.main()
	 */

	public static final double nbiStartAll[][] = { 
		/*{ -1.9022743, -1.3847706, 0.0000000 }, //SE Q1 NORM LOW
		{ -1.8792922, -1.4524737, 0.0000000 }, //SE Q2 TANG LOW
		{ -1.8792922, -1.4524737, 0.0000000 }, //SE Q3 TANG HIGH (MSE)
		{ -1.9022743, -1.3847706, 0.0000000 }, //SE Q4 NORM HIGH //*/
		
		//faro Nov2013
		{ -1.9025827, -1.3843469, 0.0032065 },
		//{ -1.8863771, -1.4432604, 0.0438209 }, //Q2 as measured
		{ -1.8864786, -1.4431277, -0.0237673 }, //Q2 1 tile down
		{ -1.8792922, -1.4524737, 0.0000000 }, //SE Q3 TANG HIGH (MSE)
		{ -1.8930912, -1.3972985, 0.0112388 }, //*/

		{ 2.3980239, 1.4752788, 0.0000000 },
		{ 3.3800075, 1.6501967, 0.0000000 },
		{ 3.3800075, 1.6501967, 0.0000000 },
		{ 2.3980239, 1.4752788, 0.0000000 },
	};
	
	public static final double nbiUnitAll[][] = {
		/*{ 0.9181567, 0.3869007, -0.0854169 }, //SE Q1 NORM LOW
		{ 0.9639529, 0.2519894, -0.0854169 }, //SE Q2 TANG LOW
		{ 0.9639529, 0.2519894, 0.0854169 }, //SE Q3 TANG HIGH (MSE)
		{ 0.9181567, 0.3869007, 0.0854169 }, //SE Q4 NORM HIGH //*/
		
		//faro Nov2013
		{ 0.9181665, 0.3870091, -0.0848193 }, //Q1 as measured
		//{ 0.9640815, 0.2540840, -0.0773833 }, //Q2 as measured
		{ 0.9630601, 0.2538445, -0.0898785 }, // Q2 1 tile down
		{ 0.9639529, 0.2519894, 0.0854169 }, //as spec Q3 TANG HIGH (MSE)
		{ 0.9190940, 0.3842101, 0.0874582 }, //Q4 as measured //*/ 
		
		{ -0.9659023, -0.2444109, -0.0854169 },
		{ -0.9878308, -0.1038252, -0.1158040 },
		{ -0.9878308, -0.1038252, 0.1158040 },
		{ -0.9659023, -0.2444109, 0.0854169 },
	};
	

	/** The beam indecies actually match the AUG naming convention (except for the 1-based indexing)
	 * I think Q means 'Quelle' ('source' in German) */  
	public static final int BEAM_Q1 = 0;
	public static final int BEAM_Q2 = 1;
	public static final int BEAM_Q3 = 2;	
	public static final int BEAM_Q4 = 3;
	public static final int BEAM_Q5 = 4;
	public static final int BEAM_Q6 = 5;
	public static final int BEAM_Q7 = 6;
	public static final int BEAM_Q8 = 7;

	/** The standard MSE/CXRS etc beam is Q3 - SW upper tangential*/
	public static final int BEAM_MSE_STD = BEAM_Q3;
	
	public static final double nbiStart[] = nbiStartAll[BEAM_Q3];
	public static final double nbiUnit[] = nbiUnitAll[BEAM_Q3];

	/** Effective camera/view pos on mirror, taken by averaging rays position 
	 * @deprecated This should come from FeatureTransform loaded from the appropriate pulse */
	public static final double viewPos[] = new double[]{ -0.317, -2.262, -0.39 } ;
	
	
	/** A lot of guessing involved here */
	public static final double[][] calibLampPos = new double[][]{
		{ -2.029, -0.926, 0.349 },
		{ -2.29, -0.305, 0.517 },
		//{ -2.395, -0.254, 0.586 }, // 1151 -2116 586 in vrml space [ from comparing photos to augddd models in wendel ]
		{ -2.305, -0.154, 0.525 }, //not yet known
	};
	
	/** See cadToXIsNorth.sci 
	 * @deprecated This was all replaced by FARO measurements of the background
	 * */
	public static final double[][] pslTilePos = new double[][]{
		//corners   
		  { -1.8877235,  -0.9200544,    0.546  },  
		  { -1.9513165,  -0.7761211,    0.546  },
		  { -2.0095045,  -0.6098292,    0.546  },
		  { -2.0534889,  -0.4395265,    0.546  },
		  { -2.0830326,  -0.2664115,    0.546  },		  
		//PSL bolt holes (CAD)
		  { -1.8226343,  -0.9640161,  0.707494 },  
		  { -1.8822485,  -0.8417006,  0.707501 },
		  { -1.9460345,  -0.6809755,  0.707709 },
		  { -1.9959914,  -0.5153338,  0.708201 },
		  { -2.0317805,  -0.3467781,  0.708659 }, 
		  { -2.0522507,  -0.1756127,  0.710959 },
	};
	
	/**  Near and far corners of visible limiter tile surface
	 * 
	 * Near corner (B) is at front of near edge, before the curve. Far corner A, is edge of visible top surface
	 * 
	 * Cam 
	 *  ____
	 *  |\
	 *  | \
	 * 
	 *               /---------------A\ 
	 *           /--/                  \--\
	 *          B                          |
	 *          |                          |
	 *          |                          |
	 * 
	 * @deprecated This was all replaced by FARO measurements of the background
	 * (See cadToXIsNorth.sci) */
	public static final double[][] limiterCornerPos = new double[][]{
		//Far (A)
			//{ -2.040564,   -0.6566393,    0.490917   },
			//{ -2.0678342,  -0.6749588,    0.401438   },
			//{ -2.094283,   -0.6859216,    0.305182   },
			//{ -2.1077326,  -0.6958999,    0.204585   },
		    { -2.0387025,  -0.6638964,    0.478916 }, 
			//{ -2.0556011,  -0.6376378,    0.505048 },
			{ -2.0756255,  -0.6630227,    0.407736 },
			{ -2.1063394,  -0.6717399,    0.309466 },
			{ -2.1221824,  -0.6790582,    0.207682 },			
			//Near (B)
			{ -1.9711703,  -0.7946063,    0.498903   },
			{ -2.0068546,  -0.8093814,    0.403397   },
			{ -2.0329207,  -0.8196043,    0.306694   },  
			{ -2.0486839,  -0.8282440,    0.206992   },  

	};
		
	// [ FARO 2007 'msespiegel2007.txt' from M.Reich 7/12/11 ]
	public static final Object faroMirrorBoxStuff[][] = new Object[][]{
		{ new double[]{ -0.3058459, -2.2525532, -0.37934  },	"!M_MSE-RAHMEN-SPIEGEL       red      !"},
		{ new double[]{ -0.3433124, -2.1899284, -0.42208  },	"!M_KOMPNT001MSE-SPIEGEL6D   green    !"},
		{ new double[]{ -0.1103633, -1.8403003, -0.47437  },	"!M_PUNKT009                 blue     !"},
		{ new double[]{ -0.3441835, -2.1934425, -0.422    },	"!M_SPIEGEL-MSE-KOMP-EIN-D6  black    !"},
		{ new double[]{ -0.8632601, -0.2810374,  0.43     },	"!M_SPIEGEL_NORMAL           magenta  !"},
	};

	/** Conversions from our 'xIsNorth' space to the AUGDDD cad models space, so our VRML output can be used with the CAD models */  
	public static double vrmlToAUGDDDRotation = 1.963; // 90deg + 22.5deg, which is half an octant
	public static String vrmlScaleToAUGDDD = "Separator {\n" + //rescale to match the augddd STL models
			"Scale { scaleFactor 1000 1000 1000 }\n" +
			"Rotation { rotation 0 0 1  1.963 }";
	
	//public static final double nbiStart[] = {  -1.8792922,  -1.4524737,    0  }; //Tangential
	//public static final double nbiUnit[] = {  0.9639529,  0.2519894, 0.0854169  }; //Tangential
	
	//double nbiStart[] = { -1.9022743 , -1.3847706  , 0 }; //Normal
	//double nbiUnit[] = {  0.9181567, 0.3869007, 0.0854169  }; //Normal
	
	//Mirror position, very approximate guess O(10cm) [ e-mail from M.Reich 22/08/11 ] 
	//public double mirrorPos[] = new double[]{ -0.302726,  -2.22957,  -0.4 };
	//public double mirrorNorm[] = new double[]{ -0.876326,  -0.286449, 0.43};
	
	//Mirror position [ FARO 2007 'msespiegel2007.txt' from M.Reich 7/12/11 ]
	//public double mirrorPos[] = new double[]{ -0.3433124, -2.1899284, -0.42208 };
	//public double mirrorNorm[] = new double[]{ -0.8593589, -0.2797673, 0.4280568 };
	
	
	// Nov2013 FARO measurements of mirror - before moving it
	double mirrorPos[] = new double[]{ -0.3105, -2.2253, -0.3839 };
	double mirrorNorm[] = new double[]{ -0.8505,	-0.2712, 0.4510 };
	double mirrorFrameTopLeft[] = new double[]{ -0.3003, -2.1875, -0.3353 };
	double mirrorFrameBottomLeft[] = new double[]{  -0.3456, -2.1919, -0.4234 };
	double mirrorFrameBottomEdge[] = new double[]{ -0.3273, -2.2593, -0.4294 };
	double faroTubePoint[] = new double[]{ -0.3818, -2.5866, -0.336 };
	double faroTubeAxis[] = new double[]{ -0.2414, -0.9564,	0.1640  };
	double mirrorLength = 0.200; //about 20cm long, by memory
	double mirrorHeight; //calculated from cTL and CBL
	double frameWidth = 0.010; //about 1.5 - 2cm, by memory
	double rodsPitchAngle = 9.2 * Math.PI / 180; //angle of all 4 MSE rods to the horizontal, along the tube 
	double rodsRotateAngle = 3.1 * Math.PI / 180; //angle of across top/bottom 2 of the 4 MSE rods (and hence PEMs holder) to the horizontal , measured
	
	public Square beamPlane; 
	
	public TubeOptics2 tubeOptics;
	public MirrorBox mirrorBox;
	public Square mainMirror;
	
	public IMSEOptics imseOptics;
		
	public AugMSESystem() {
		this(null);
	}
	
	public AugMSESystem(IMSEOptics imseOptics) {
		super("AugMSESystem");
		this.imseOptics = imseOptics;
		
		//initCADFiddle();
		initFaroNov2013();
	}
	
	/** FARO Nov2013 based */ 
	private void initFaroNov2013() {
		tubeOptics = new TubeOptics2();
		
		if(imseOptics != null){
			//imseOptics = new IMSEOptics135_50();
			//imseOptics = new IMSEOptics105_35();
			imseOptics.shift(tubeOptics.fibreEnds.getCentre());
			tubeOptics.addElement(imseOptics);
			tubeOptics.fibreEnds.setInterface(NullInterface.ideal());
		}
		
		// ***** Mirror ****** //

		//construct the mirror plane according to hte faroNov2013 data
		double mirrorRight[] = Util.reNorm(Util.cross(mirrorNorm, globalUp)); 
		double mirrorUp[] = Util.cross(mirrorRight, mirrorNorm);
		
		//find the corners of the mirror from the nearest point on it's plane to the 3 frame corners
		double cTL[] = Algorithms.intersectionLinePlane(mirrorFrameTopLeft, mirrorNorm, mirrorPos, mirrorUp, mirrorRight);
		double cBL[] = Algorithms.intersectionLinePlane(mirrorFrameBottomLeft, mirrorNorm, mirrorPos, mirrorUp, mirrorRight);
		double cBE[] = Algorithms.intersectionLinePlane(mirrorFrameBottomEdge, mirrorNorm, mirrorPos, mirrorUp, mirrorRight);
		// cBE is not all the way to the outboard edge, so it'snt actually a corner
		
		double mirrorHeight = Util.length(Util.minus(cTL, cBL)) - 2 * frameWidth;
		
		mirrorUp = Util.reNorm(Util.minus(cTL, cBL)); //the actual up of the mirror, perp to the plane and parallel with the onbaord edge
		mirrorRight = Util.reNorm(Util.cross(mirrorNorm, mirrorUp));
		
		mirrorPos = new double[]{
				cBL[0] + (frameWidth + mirrorHeight/2) * mirrorUp[0] + (frameWidth + mirrorLength/2) * -mirrorRight[0],
				cBL[1] + (frameWidth + mirrorHeight/2) * mirrorUp[1] + (frameWidth + mirrorLength/2) * -mirrorRight[1],
				cBL[2] + (frameWidth + mirrorHeight/2) * mirrorUp[2] + (frameWidth + mirrorLength/2) * -mirrorRight[2],  
		};
		
		mainMirror = new Square("mainMirror", mirrorPos, mirrorNorm, mirrorUp, mirrorHeight, mirrorLength, 
				//Reflector.ideal());
				new Reflector(0.0, true)); //explicitly expect the mirror to work with a normal-like phase shift (see notes2013 'which way is up')

		addElement(mainMirror);
		
		
		// ***** Tube Optics ****** //
		
		//find the intersection of the tube measured optic axis and the measured mirror plane
		double tubeOrigin[] = Algorithms.intersectionLinePlane(faroTubePoint, faroTubeAxis, mirrorPos, mirrorUp, mirrorRight);
		
		double rotVec[] = Util.cross(Util.reNorm(faroTubeAxis), tubeOptics.fibreEnds.getNormal());
		double rotUVec[] = Util.reNorm(rotVec);
		//double rotAngle = FastMath.asin(rotVec[2] / rotUVec[2]);
		double rotAngle = -FastMath.acos(Util.dot(Util.reNorm(faroTubeAxis),  tubeOptics.fibreEnds.getNormal()));
								
		double rotMat[][] = Algorithms.rotationMatrix(rotUVec, rotAngle);		
				
		//rotate the tube into the measured axis
		tubeOptics.rotate(new double[]{ 0, 0, 0 }, rotMat);
		
		tubeOptics.shift(tubeOrigin);
		
		//the only important parts of the tube rotation-wise are the PEMs and pems holder etc, which we measured the angle of outside
		double currentTubeUp[] = tubeOptics.fibreEnds.getUp(); //current up after the weird rotation
		double zeroTubeUp[] = Util.reNorm(Util.cross(Util.cross(tubeOptics.fibreEnds.getNormal(), globalUp), tubeOptics.fibreEnds.getNormal())); //proper up-ish up
		double currentUpFix = FastMath.acos(Util.dot(currentTubeUp, zeroTubeUp)); //angle to rotate to get up as close as possible to globalUp
		
		//spin the tube along it's axis, to remove the spurious current up, and then rotate to the desire one 
		tubeOptics.rotate(tubeOptics.fibreEnds.getCentre(), 
						   Algorithms.rotationMatrix(Util.reNorm(tubeOptics.fibreEnds.getNormal()), -currentUpFix + rodsRotateAngle));
		
		
		mirrorBox = new MirrorBox();
		
		mirrorBox.shift(new double[]{ 0.020, -2.460, -0.513 });
		Util.rotateOnZ(mirrorBox, new double[]{ 0, 0, 0 }, -2*Math.PI/32); //spin 1/32th past (so that Y is now up the centre of the MSE sector)
		
		
		//FARO Nov2013
		double entraceCentre[] = new double[]{ -0.3882,	-2.2035,	-0.3569  };
		double entraceNormal[] = new double[]{ -0.8843,	0.2039,		0.4210 };
		
		
		mirrorBox.holeIris.setCentre(entraceCentre);
		mirrorBox.holeIris.setNormal(entraceNormal);
			
		addElement(tubeOptics);		
		addElement(mirrorBox);
		
		//VRMLDrawer.dumpRay("/tmp/mse2.vrml", this, null, 0.005, AugMSESystem.vrmlScaleToAUGDDD, "}");
		
	}
		
	/** This will all aligning the optics tube to the CAD data by fiddling, but we now have a FARO measurement of it */
	private void initCADFiddle() {
		// TODO Auto-generated method stub
		//
		//nudge the mirror to agree with FARO measurements
		//this doesn't really 'move' the mirror, since the original mirrorPos, is still on the mirror surface 
		double mirrorRight[] = Util.reNorm(Util.cross(mirrorNorm, new double[]{ 0,0,1 })); 
		double mirrorUp[] = Util.cross(mirrorRight, mirrorNorm);
		double shiftUp = 0.03, shiftIn = -0.08;
		mirrorPos = new double[]{
				mirrorPos[0] + shiftUp * mirrorUp[0] + shiftIn * mirrorRight[0],
				mirrorPos[1] + shiftUp * mirrorUp[1] + shiftIn * mirrorRight[1],
				mirrorPos[2] + shiftUp * mirrorUp[2] + shiftIn * mirrorRight[2],
		};		
		
		// Beam plane 
		double dCentre = 0.5; // dist of 'centre' along beam
		double beamCentre[] = new double[]{ 
				nbiStart[0] + dCentre * nbiUnit[0],
				nbiStart[1] + dCentre * nbiUnit[1],
				nbiStart[2] + dCentre * nbiUnit[2],
			};
		double centralLos[] = Util.reNorm(Util.minus(mirrorPos, beamCentre));
		double beamUp[] = Util.reNorm(Util.cross(centralLos, nbiUnit));
		double beamRight[] = Util.reNorm(Util.cross(nbiUnit, beamUp));
		beamPlane = new Square("beamPlane", beamCentre, beamRight, beamUp, 2, 2, null, null, NullInterface.ideal());
		
		//as built, the tube runs along the x axis from [0,0,0] at the centre of the main viewing mirror
		tubeOptics = new TubeOptics2();
		mirrorBox = new MirrorBox();
		
		if(imseOptics != null){
			//imseOptics = new IMSEOptics135_50();
			//imseOptics = new IMSEOptics105_35();
			imseOptics.shift(tubeOptics.fibreEnds.getCentre());
			tubeOptics.addElement(imseOptics);
			tubeOptics.fibreEnds.setInterface(NullInterface.ideal());
		}
		
		//taken originally from lots of the CAD pictures
		//and also some deduction from the in-vessel photos
		//the X here was fairly good. The y and z values much less so
		// Dec2011: Now shifted around to match the FARO data actually going through the hole.
		//mirrorBox.shift(new double[]{ 0.020, -2.440, -0.51 });
		
		// Mar2012: Shifted to larger R to match AUGDDD CAD data.
		// Aug2013: Seems to match the photo of the box reasonable (see pres/notesPostEPS)
		mirrorBox.shift(new double[]{ 0.020, -2.471, -0.513 });

		Util.rotateOnZ(mirrorBox, new double[]{ 0, 0, 0 }, -2*Math.PI/32); //spin 1/32th past (so that Y is now up the centre of the MSE sector)
		
		//rotate the tube up to the correct elevation angle
		Util.rotateOnY(tubeOptics, new double[]{ 0, 0, 0}, 9.8 * Math.PI / 180);
		
		//spin
		double tubeOpticRotate = 4.5  * Math.PI / 180; // ±1° assessed by aligning the photos with the CAD data (see notes2013 'which way is up')
		tubeOptics.rotate(tubeOptics.fibreEnds.getCentre(), 
						   Algorithms.rotationMatrix(Util.reNorm(tubeOptics.fibreEnds.getNormal()), 
									         tubeOpticRotate));
		
		//now rotate the tube in the horizontal plane so that it's at the correct angle
		//our coordinate system always has x as North
		double ang = 
			-Math.PI/2 + //from North, come back around so that tube is out to the east
			-2*Math.PI/32 + //East is on the MSE sector edge, so the center of the sector is 1/32th further around (16 sectors)
			//-3.11 * Math.PI / 180; //The actual angle of the tube from east - Measured from sheet ü3 (Tokamak with crude pipe)
			//-3.8 * Math.PI / 180; //The actual angle of the tube from east - Measured from sheet ü2 (Detailed pipe, no Tokamak)
			-2.9 * Math.PI / 180; //Angle to match tube in AUGDDD CAD data.
			
		Util.rotateOnZ(tubeOptics, new double[]{ 0, 0, 0 }, ang);
		
		
		//now move it's 0,0,0 to the real mirror position
		tubeOptics.shift(mirrorPos);
		
		//and adjust to match AUGDDD CAD data
		tubeOptics.shift(new double[]{ 0.010, 0.000, 0.000 });
			
		addElement(tubeOptics);
		addElement(mirrorBox);
		
		addElement(mainMirror);
		//surfaces.add(beamPlane);
	}
	
	/** Set geometry of MSE and slaved IMSE system to match cnofig on a certain day */ 
	public void setupForPulse(int pulse){
		if(pulse > 29000)
			throw new IllegalArgumentException(pulse + " looks like an AUG pulse number, this currently only takes the GMDS pulse number.");
				
		// Actually, i'm going to trust the FARO mirror data from MSE (not sure why)
		// and fiddle with the CCD geometry in IMSEOptics.setupForPulse()
		
		if(pulse >= 0/*154*/ && pulse <= 185){ 
			//Jan2013 last day. Matched to pulse 178 (IMSE comparison pulse)

			// GA optimised, p=178, θ = 26.84211581544059, φ = -162.73290525863897
			//  α = 3.147460741626234, (Δr.n) = 0.021795069790915316
			//mainMirror.setCentre(new double[]{ -0.32506631212756376, -2.268120982499499, -0.38563791576031314 });
			//mainMirror.setNormal(new double[]{ -0.8520415329808587, -0.26484467902652936, 0.4515335226391977 });
			//ccd.setUp(new double[]{ -0.011693163219413175, 0.17821859034865759, 0.9839214419800301 });

			// for 135_50_rescaled
			// GA optimised, p=178, θ = 26.86783965689429, φ = -162.4157715256032
			//  α = 3.164104641666381, (Δr.n) = 0.005903814301694178
			//mainMirror.setCentre(new double[]{ -0.31141002029072773, -2.2636751288576695, -0.39244027573301155 });
			//mainMirror.setNormal(new double[]{ -0.8503692297073675, -0.269495400248522, 0.4519340686558025 });
			//ccd.setUp(new double[]{ -0.011975078569145148, 0.1782868570599602, 0.9839056835352381 });
			
			//from below - it was fit for 351 (april) but also matches for 178 (jan) after lining up the image boundary
			// yippeeee :)
			mainMirror.setNormal(new double[]{ -0.8502870011673638, -0.27459576598445473, 0.44900911009602273 });
			
			
		}else if(pulse > 250 && pulse <= 368){
			//April2013 Hydrogen week (after the weekend). Matched to pulse 358 (the last W-melting pulse)

			// GA optimised, p=351, θ = 27.21748253742288, φ = -162.40844564941148
			//  α = -6.795115657304607, (Δr.n) = 2.2302364872402516E-4
			//mainMirror.setCentre(new double[]{ -0.30652818228406103, -2.2620858293948225, -0.39487197680139186 });
			//mainMirror.setNormal(new double[]{ -0.8476900291270517, -0.26876559626672475, 0.4573692914724054 });
			//ccd.setUp(new double[]{ 0.1560467506146714, 0.13496134414689825, 0.9784859974515043 });

			// GA optimised, p=351, θ = 27.129346969151896, φ = -162.36150177810725
			//  α = -6.826143120828392, (Δr.n) = 0.004937793066658801
			//mainMirror.setCentre(new double[]{ -0.3105798613448111, -2.2634048677050007, -0.39285378769161283 });
			//mainMirror.setNormal(new double[]{ -0.8481390104521779, -0.2696729040156826, 0.45600081555732885 });
			//ccd.setUp(new double[]{ 0.15656547393013426, 0.13481929577111362, 0.9784227153233992 });

			// GA optimised, p=351, θ = 26.980096434082828, φ = -162.23201323119562
			//  α = -0.07937303278128088, (Δr.n) = -0.0022215780844654816
			//mainMirror.setCentre(new double[]{ -0.3044273920276892, -2.261401909768353, -0.39591840519657545 });
			//mainMirror.setNormal(new double[]{ -0.8486556896012847, -0.27195057318386284, 0.4536809520492749 });
			//ccd.setUp(new double[]{ 0.15243866545122442, 0.13594794954891373, 0.9789180804791051 });

			// GA optimised, p=351, θ = 26.996007617439687, φ = -162.65946739246883
			//  α = -0.005468611267547163, (Δr.n) = 0.018521165382856922
			//mainMirror.setCentre(new double[]{ -0.32225285323674957, -2.2672050511027986, -0.3870393328047325 });
			//mainMirror.setNormal(new double[]{ -0.850540640803717, -0.2655741216987502, 0.4539284131062297 });
			//ccd.setUp(new double[]{ 0.1512021864362818, 0.13628547686121373, 0.9790629027869454 });
			
			// opst Nov2013
			//--- vvv ------------------ Cut Here ------------------ vvv ---
			// GA optimised, p=351, θ = 26.68012723894003, φ = -162.10240734974573
			//  α = 0.02326173654971623, (Δr.n) = 8.991698565806314E-5
			//mainMirror.setCentre(new double[]{ -0.29178646779323864, -2.2946808745667355, -0.39013124172472646 });
			mainMirror.setNormal(new double[]{ -0.8502870011673638, -0.27459576598445473, 0.44900911009602273 });
			//ccd.setUp(new double[]{ 0.17430109207350022, 0.12352324034158836, 0.9769140895685249 });
			//--- /666 ------------------ Cut Here ------------------ ^^^ ---
			
		}else if(pulse >= 438 && pulse <= 700){ //2nd and 3rd days of May2014
			
			//mainMirror.setNormal(new double[]{ -0.8502870011673638, -0.27459576598445473, 0.44900911009602273 });
			
				
		}else if(pulse == -20130701){ //that's a date, roughly the EPS, this means 'setup as in he EPS paper'
			// as EPS
			mainMirror.rotate(mainMirror.getCentre(), Algorithms.rotationMatrix(mainMirror.getUp(), 0.3 * Math.PI / 180));
			mainMirror.rotate(mainMirror.getCentre(), Algorithms.rotationMatrix(mainMirror.getRight(), 0.6 * Math.PI / 180));

		}else{
			throw new IllegalArgumentException("Unknown optics setup for pulse " + pulse);
		}
		
		if(imseOptics != null)
			imseOptics.setupForPulse(pulse);
		
		System.out.println("Optics configured for pulse " + pulse + ", hash = " + hashCode());
	}

	public static Optic makeAllBeamCylds(){
		return makeAllBeamCylds(0.05, 0.16);
	}
	
	public static Optic makeAllBeamCylds(double dL, double fwhm){
		double l0 = 0.2;
		double l1 = 2.9;		
		return makeAllBeamCylds(dL, fwhm, l0, l1);
	}
		
	public static Optic makeAllBeamCylds(double dL, double fwhm, double l0, double l1){
		LinkedList<Element> beams = new LinkedList<Element>();
		
		for(int i=0; i < nbiStartAll.length; i++){
			Optic beam = makeBeamCylds(i, dL, fwhm, l0, l1);
			beams.add(beam);
		}
		
		return new Optic("beamCylds", beams);
	}
	
	public static Optic makeBeamCylds() {
		return makeBeamCylds(1, 0.10, 0.16);
	}
	
	public static Optic makeBeamCylds(int iB, double dL, double fwhm) {
		double l0 = 0.2;
		double l1 = 2.9;		
		return makeBeamCylds(iB, dL, fwhm, l0, l1);
	}
		
	public static Optic makeBeamCylds(int iB, double dL, double fwhm, double l0, double l1) {
		LinkedList<Element> cylds = new LinkedList<Element>();
		
		
		for(int i=0; i < (l1 - l0) / dL; i++){
			double l = l0 + i * dL;			
			Cylinder clyd = new Cylinder(
					"cyld"+i,
					new double[]{ 
							nbiStartAll[iB][0] + l * nbiUnitAll[iB][0],
							nbiStartAll[iB][1] + l * nbiUnitAll[iB][1],
							nbiStartAll[iB][2] + l * nbiUnitAll[iB][2],							
					},
					nbiUnitAll[iB],
					fwhm/2, //radius, HWHM of beam
					dL, //length 
					NullInterface.ideal());
			clyd.setDrawingDetails(8, 10);
			
			cylds.add(clyd);
		}
		
		return new Optic("beamCylds" + iB, cylds);
	}
	
	
}
