package seed.minerva.apps.imse;

import imseProc.proc.transform.FeatureTransform;
import imseProc.proc.transform.FeatureTransformCubic;
import jafama.FastMath;

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

import mds.GMDSFetcher;

import algorithmrepository.Algorithms;
import binaryMatrixFile.BinaryMatrixWriter;
import oneLiners.OneLiners;
import otherSupport.ColorMaps;
import otherSupport.RandomManager;
import otherSupport.SettingsManager;
import otherSupport.StatusOutput;
import seed.minerva.MinervaOpticsSettings;
import seed.minerva.aug.mse.AugMSESystem;
import seed.minerva.aug.mse.AugMseFaroData;
import seed.minerva.imse.IMSEOptics135_50;
import seed.minerva.imse.IMSEOptics135_50_rescaled;
import seed.minerva.optics.Util;
import seed.minerva.optics.collection.IntensityInfo;
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.IsoIsoInterface;
import seed.minerva.optics.interfaces.IsoIsoStdFresnel;
import seed.minerva.optics.interfaces.NullInterface;
import seed.minerva.optics.interfaces.Reflector;
import seed.minerva.optics.optics.Box;
import seed.minerva.optics.pointSpread.PointSpreadFunction;
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;

/** Output a grid on (R,Z) of beam  
 * 
 * 
 * @author minerva3
 *
 */
public class WhichWayIsUpForProc {
		
	private final static String outPath = MinervaOpticsSettings.getAppsOutputPath() + "/rayTracing/augImse/whichWayIsUpGrid/";
	private final static int nRaysDraw = 1;
	//*/
	
	/** 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 */
	private static AugMSESystem sys = new AugMSESystem(new IMSEOptics135_50_rescaled());
	
	// IMSE or AUG pulse number for matched hardware config
	//private final static int pulse = 178; // Jan2013 primary
	private final static int pulse = 351; // Apr2013 primary

	private static int beamIdx = AugMSESystem.BEAM_Q3;
	
	private static Plane imagePlane = sys.imseOptics.ccd;
	private static Plane polarisationPlane = sys.imseOptics.plate1;
	//private static Plane polarisationPlane = sys.tubeOptics.PEMsFront;
	private static Element initRaysTarget = sys.mainMirror; //sys.mirrorBox.holeGlassFront;
	
	private final static double wavelen = 653e-9;
	private final static double globaUp[] = new double[]{ 0, 0, 1 };
	
	
	//grid
	private final static double maxPitch = 14 * Math.PI / 180;
	private final static int nPitch = 7;
	private final static double u0 = 0.26, u1 = 0.82; //up and down (ish) 
	private final static int nU = 56;  
	private final static double a0 = -0.15, a1 = 0.15; //up and down (ish) 
	private final static int nA = 31;  
	private final static double b0 = -0.15, b1 = 0.15;  // back and forward (ish)
	private final static int nB = 31;  
	
	public static void main(String[] args) { (new WhichWayIsUpForProc()).run();	}
	
	private VRMLDrawer vrmlOut;
	private double targetPos[];
	
	private void run(){
		
		sys.setupForPulse(pulse);
	
		System.out.println("optics hash: " + sys.hashCode());
		
		//ignore the protection cover coating (or lack of)
		sys.mirrorBox.protectionCoverFront.setInterface(IsoIsoInterface.ideal());
		sys.mirrorBox.protectionCoverBack.setInterface(IsoIsoInterface.ideal());
		
		vrmlOut = new VRMLDrawer(outPath + "/pictures.vrml");
		if(vrmlOut != null){
			vrmlOut.setSmallLineLength(0.002);
			vrmlOut.setDrawPolarisationFrames(true);
			vrmlOut.addVRML(AugMSESystem.vrmlScaleToAUGDDD);			
			vrmlOut.setSkipRays(7);
		}
		
		//fit rays at the average location that the transform check saw them hit the mirror
		//rays from mid-beam axis dont work, something is very very wrong
		String serverID = SettingsManager.defaultGlobal().getProperty("imseProc.cis.gmdsServerID", "pccis");		
		GMDSFetcher gmds = GMDSFetcher.defaultInstance(serverID);
		FeatureTransformCubic xform = new FeatureTransformCubic(gmds, "AUG", pulse);
		String viewPointName = "view (TransformMatch-"+sys.hashCode()+")";		
		targetPos = xform.getPointByName(viewPointName).getPosXYZ();
		
		BinaryMatrixWriter out = new BinaryMatrixWriter(outPath + "/out.bin", 15);
		
		int nPoints = nU * nA * nB;
		
		double U[] = sys.nbiUnitAll[beamIdx];
		double B[] = Util.reNorm(Util.cross(U, globaUp));
		double A[] = Util.reNorm(Util.cross(B, U));
		
		StatusOutput stat = new StatusOutput(WhichWayIsUpForProc.class, nPoints);		
		for(int iU=0; iU < nU; iU++){
			double u = u0 + iU * (u1 - u0) / (nU - 1.0);
			
			for(int iA=0; iA < nA; iA++){
				double a = a0 + iA * (a1 - a0) / (nA - 1.0);
				
				for(int iB=0; iB < nB; iB++){
					double b = b0 + iB * (b1 - b0) / (nB - 1.0);
					
					double startPos[] = new double[]{ //-1.3, -1.3, 0.1 };
							sys.nbiStartAll[beamIdx][0] + u * U[0] + a * A[0] + b * B[0],
							sys.nbiStartAll[beamIdx][1] + u * U[1] + a * A[1] + b * B[1],
							sys.nbiStartAll[beamIdx][2] + u * U[2] + a * A[2] + b * B[2],
					};
						
					double psi[] = getFinalAngles(startPos);
					
					//camera LOS lon/lat
					double losVec[] = Util.minus(startPos, targetPos);
					double depth = Util.length(losVec);
					losVec = Util.reNorm(losVec);
					double lat = FastMath.asin(losVec[2]);
					double lon = FastMath.atan2(losVec[1], losVec[0]); //might need to do some rotation here if phi becomes discontinuous			
					
					for(int iP=0; iP < nPitch; iP++){
						double pitch = iP * maxPitch / (nPitch - 1.0);
						
							out.writeRow(iU, iA, iB, iP,
									u, a, b,
									lon, lat, depth,
									startPos,
									pitch,									
									psi[iP]);
						
						
					}
					
					stat.doStatus( (iU*nA + iA)*nB + iB );
				}
			}
			
		}
		stat.done();
		
		if(vrmlOut != null){
			vrmlOut.drawOptic(sys);
			vrmlOut.addVRML("}"); //end of rotate/transform
			vrmlOut.destroy();
		}
		
		
		
	}
	
	private double[] getFinalAngles(double startPos[]){	
		
		//global test element for polarisation definition consistent for all bundles - whatever that means
			
		//Bφ field direction at emission point
		double Bphi[] = Util.reNorm(Util.cross(startPos, globaUp));
		
		// VxBφ direction at emission, for Q3
		double VxB[] = Util.reNorm(Util.cross(sys.nbiUnitAll[beamIdx], Bphi)); // V x B for toroidal only
		
		Pol.recoverAll();
		
		RaySegment ray = new RaySegment();
		ray.wavelength = wavelen;
		ray.startPos =  startPos.clone();
		ray.dir = Util.reNorm(Util.minus(targetPos, startPos));
				
		ray.length = Double.POSITIVE_INFINITY;
		
		//ray.up = Util.cross(Util.reNorm(Util.cross(ray.dir, globaUp)), ray.dir); // This is MSE-like emission
		
		//randomise initial sense of up, as it shouldn't matter because we use ray.rotatePolRefFrame()
		ray.up = Util.reNorm(new double[]{
				RandomManager.instance().nextNormal(0, 1),
				RandomManager.instance().nextNormal(0, 1),
				RandomManager.instance().nextNormal(0, 1)
			});
		
		
		if(nPitch > 1){
			ray.E0 = new double[nPitch][4];
			for(int iP=0; iP < nPitch; iP++) {
				double pitch = iP * maxPitch / (nPitch - 1.0);
				//B with Bphi plus some Bz (downwards is normal +ve Ip)
				double B[] = Util.plus( Util.mul(Bphi, FastMath.cos(pitch)), new double[]{ 0, 0, -FastMath.sin(pitch) });
				
				double starkE[] = Util.reNorm(Util.cross(sys.nbiUnitAll[beamIdx], B)); // V x B
				
				ray.rotatePolRefFrame(starkE);
				
				ray.E0[iP] = new double[]{ 1, 0, 0, 0 };	
			}
		}else{
			ray.up = Util.cross(Util.reNorm(Util.cross(ray.dir, globaUp)), ray.dir); // This is MSE-like emission
			
			ray.E0 = new double[][]{ { 1, 0, 0, 0 } };
		}
							
		Tracer.trace(sys, ray, 100, minIntensity, traceReflections);
		
		if(vrmlOut != null)
			vrmlOut.drawRay(ray);
		
		List<Intersection> hits = ray.getIntersections(imagePlane); 
				
		double psi[] = OneLiners.fillArray(Double.NaN, nPitch);
		if(hits.size() > 0){
			if(vrmlOut != null){
				vrmlOut.drawRay(ray);
			}
			
			Intersection imgHit = hits.get(0);
			
			Intersection polHit = imgHit.incidentRay.findFirstEarlierIntersection(polarisationPlane);
			if(polHit == null)
				return psi;
						
			double Eproj[][] = Pol.projectToPlanesView(polHit, false);
			
			psi = new double[nPitch];
			for(int iP=0; iP < nPitch; iP++){
				psi[iP] = Pol.psi(Eproj[iP]);
			}
		}else{
			//seriously, wtf!?
		}
		
		return psi;
	}
}
