package ipp.imse.fusionOptics.apps.permIMSE;

import ipp.imse.fusionOptics.imse.IMSEOptics105_35;
import ipp.imse.fusionOptics.imse.IMSEOptics135_50;
import ipp.imse.fusionOptics.imse.IMSEOptics135_50_rescaled;
import ipp.imse.fusionOptics.imse.IMSEOptics180_50;
import ipp.imse.fusionOptics.imse.IMSEOptics75_25;
import ipp.imse.fusionOptics.imse.IMSEOptics75_25_expanded_filter;
import ipp.imse.fusionOptics.imse.IMSEOptics75_25_imgFielded;
import ipp.imse.fusionOptics.imse.IMSEOptics75_25_imgFielded_fibreFielded;
import ipp.imse.fusionOptics.imse.IMSEOptics75_25_rescaled;
import ipp.imse.fusionOptics.imse.IMSEOptics75_25_silly_second_diag;
import ipp.imse.fusionOptics.mse.AugMSESystem;
import ipp.imse.fusionOptics.mse.AugMseFaroData;
import ipp.imse.fusionOptics.permIMSE.PermIMSE;
import ipp.imse.fusionOptics.permIMSE.PermIMSEBackup;
import ipp.imse.fusionOptics.permIMSE.PermIMSEFlexible;
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.RACache;
import cache.randomAccessCache.RACacheService;
import fusionOptics.MinervaOpticsSettings;
import fusionOptics.Util;
import fusionOptics.collection.HitsCollector;
import fusionOptics.collection.ImageCollector;
import fusionOptics.collection.IntensityInfo;
import fusionOptics.collection.LensDepolarisationInfoCollector;
import fusionOptics.collection.PlaneAngleInfo;
import fusionOptics.drawing.SVGCylindricalProjection;
import fusionOptics.drawing.SVGRayDrawing;
import fusionOptics.drawing.VRMLDrawer;
import fusionOptics.interfaces.NullInterface;
import fusionOptics.optics.Box;
import fusionOptics.optics.SimpleDoubleConvexLens;
import fusionOptics.optimisation.OptimiseLensCurvature;
import fusionOptics.optimisationMulti.BendableDish;
import fusionOptics.optimisationMulti.MoveableElement;
import fusionOptics.optimisationMulti.OptimiseMulti;
import fusionOptics.optimisationMulti.RayBundle;
import fusionOptics.pointSpread.DualGaussianPSF;
import fusionOptics.pointSpread.PSFGrid;
import fusionOptics.pointSpread.PointSpreadBuilder;
import fusionOptics.pointSpread.PointSpreadFunction;
import fusionOptics.pointSpread.PointsPSF;
import fusionOptics.surfaces.Cylinder;
import fusionOptics.surfaces.Dish;
import fusionOptics.surfaces.Plane;
import fusionOptics.surfaces.Square;
import fusionOptics.tracer.Tracer;
import fusionOptics.types.Element;
import fusionOptics.types.Intersection;
import fusionOptics.types.Optic;
import fusionOptics.types.Pol;
import fusionOptics.types.RaySegment;
import fusionOptics.types.Surface;
import algorithmrepository.Algorithms;
import binaryMatrixFile.AsciiMatrixFile;
import binaryMatrixFile.BinaryMatrixFile;
import binaryMatrixFile.BinaryMatrixWriter;
import otherSupport.ColorMaps;
import otherSupport.StatusOutput;
import oneLiners.OneLiners;
import seed.optimization.genetic.MSHGPPConfig;
import seed.optimization.genetic.MegaSuperHyperGeneticProblemPacifier;
import seed.optimization.genetic.SuperGeneticMk2;

/** Genetic Algorithm optimisation of the whole AUG permanent IMSE system
 * 
 * 
 * Cost function needs to include:
 * 		Total light
 * 		Image quality
 * 		Vignetting
 * 
 * Parameters should be:
 * 		Position along their normal of each lens
 * 		Focal length of each lens surface 
 * 		Image plane position
 * 		Mirror position		
 * 
 * 
 * @author oliford
 * 
 */
public class IMSEOptimisation {
		
	final static String outPath = MinervaOpticsSettings.getAppsOutputPath() + "/rayTracing/permImse/optimise/";
	final static double R0 = 1.5;
	final static double R1 = 2.2;
	final static int nRaysPerBeamPoint = 1000; //need 1000 at the very least, because our success rate is very low 
	final static int nBeamPoints = 8;

	/** 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 PermIMSE sys = new PermIMSE();
	public static PermIMSEFlexible sys = new PermIMSEFlexible(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.imagePlane;
	public static Element initRaysTarget = sys.smallTargetDisc; //sys.mirrorBox.holeGlassFront;
	
	/** Don't draw rays that don't hit these elements */
	public static Surface mustHitToDraw = sys.portA11;
	//public static Surface mustHitToDraw = sys.mirror;
	//public static Surface mustHitToDraw = sys.l1Surface;
	
	final static double wavelen = 653e-9;
	
	public static void main(String[] args) {
	
		System.out.println("Sorce-target dist: " + Util.length(Util.minus(
				initRaysTarget.getBoundarySphereCentre(),
				Util.plus(AugMSESystem.nbiStartAll[AugMSESystem.BEAM_Q8], Util.mul(AugMSESystem.nbiUnitAll[AugMSESystem.BEAM_Q8], 0.54))
				)));
		
		//The new multi-optimisation thing from the ray tracer
		OptimiseMulti optim = new OptimiseMulti();
		
		
		//Generate ray bundles from source points (and give them to OptimseMulti)
		for(int iB=0; iB < 4; iB++){
			int beamIdx = 4 + iB; //beams 5,6,7,8 (0-based)
			
			for(int iBP=0; iBP < nBeamPoints; iBP++){ // beam point
				double R = R0 + (R1 - R0) * iBP / (nBeamPoints - 1);
				double t[] = Algorithms.cylinderLineIntersection(sys.nbiStartAll[beamIdx], sys.nbiUnitAll[beamIdx], 
						new double[]{ 0, 0, 0 }, new double[]{ 0, 0, 1 }, R*R);
				
				double pos[] = Util.plus(sys.nbiStartAll[beamIdx], Util.mul(sys.nbiUnitAll[beamIdx], Algorithms.min(t)));
				//System.out.println("point_Q" + (beamIdx+1) + "_" + iB + "_" + R + ": Rrec = " + FastMath.sqrt(pos[0]*pos[0] + pos[1]*pos[1]));
				
				//create a ray bundle (from the optimiser) from this point
				RayBundle rayBundle = new RayBundle((beamIdx+1) * 1000 + iBP); // bundle IDs are beam number * 1000, so 5th point of Q7 is 7004
				
				rayBundle.setImagePlane(imagePlane);
				//rayBundle.setTargetPos(new double[]{ ?? , ?? }); //will need to do later this to fix the magnification
				rayBundle.setSharpnessWeight(1.0); //we can weight the sharpness, intensity and targeting separately for each source point
				rayBundle.setIntensityWeight(1.0); 
				rayBundle.setTargetWeight(0.0);
				rayBundle.initRaysImaging(initRaysTarget, pos, wavelen, nRaysPerBeamPoint);
				
				optim.addRayBundle(rayBundle);
			}
		}
		
		optim.setTracingElements(sys); // Give it all the elements to trace
		
		//Setup all the parameters we want it to play with
		//optim.addParameter(new MoveableElement(imagePlane, imagePlane.getNormal(), -0.10, 0.10, 0.1)); //Move image plane +/- 10cm 
		
		optim.addParameter(new MoveableElement(sys.lens0, sys.lens0.getPlanarSurface().getNormal(), -0.02, 0.02, 0.1));  // Move Lens0 +/- 2cm
		optim.addParameter(new BendableDish(sys.lens0.getConvexSurface(), 0.70)); // Change curvate by +/- 50%
		
		optim.addParameter(new MoveableElement(sys.lens1, sys.lens1.getPlanarSurface().getNormal(), -0.02, 0.02, 0.1));  // Move Lens1 +/- 2cm
		optim.addParameter(new BendableDish(sys.lens1.getConvexSurface(), 0.70)); // Change curvate by +/- 50%
		
		optim.addParameter(new MoveableElement(sys.lens2, sys.lens2.getPlanarSurface().getNormal(), -0.05, 0.05, 0.1));  // Move Lens2 +/- 5cm
		optim.addParameter(new BendableDish(sys.lens2.getConvexSurface(), 0.70)); // Change curvate by +/- 50%

		optim.addParameter(new MoveableElement(sys.lens3, sys.lens3.getPlanarSurface().getNormal(), -0.05, 0.05, 0.1));  // Move Lens3 +/- 5cm
		optim.addParameter(new BendableDish(sys.lens3.getConvexSurface(), 0.70)); // Change curvate by +/- 50%

		// Tell it to draw some things occasionally
		optim.setHitsOutputFile(outPath + "/hits.bin");
		optim.setRegularDrawing(outPath + "/optim", VRMLDrawer.class, 10, 1);
		optim.setExtraVRML(AugMSESystem.vrmlScaleToAUGDDD);
		
		// Setup the actual optimiser
		// for which we use A.Meakins' very crazy Genetic Algorithm 
		MSHGPPConfig cfg = new MSHGPPConfig();
		cfg.populationSize = 10;
		cfg.numChildren = 2;
		cfg.initFromCurrent = true;
		MegaSuperHyperGeneticProblemPacifier opt = new MegaSuperHyperGeneticProblemPacifier(cfg);
		optim.setOptimiser(opt);

		optim.eval(); //Run an initial eval, so we can dump info 
		System.out.println("\nInitial parameter space: ");
		optim.dumpParams(); 
		System.out.println("\nInitial ray bundle info: ");
		optim.dumpRayBundles();
		
		optim.optimise(5000); // Go for it!
		
		System.out.println("\nFinal parameter space: ");
		optim.dumpParams();
		System.out.println("\nFinal ray bundle info: ");
		optim.dumpRayBundles();
		
		
	}
	
}
