package seed.minerva.imse.old;

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

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

import binaryMatrixFile.BinaryMatrixFile;
import binaryMatrixFile.BinaryMatrixWriter;

import oneLiners.OneLiners;

import seed.minerva.optics.collection.IntersectionProcessor;
import seed.minerva.optics.pointSpread.PointSpreadFunction;
import seed.minerva.optics.surfaces.Plane;
import seed.minerva.optics.types.Intersection;
import seed.minerva.optics.types.RaySegment;


/**
 * Handles both collection of points when build a PSF and the collection
 * of PSFs from different source points.
 * 
 * @deprecated 
 * 
 * @author oliford
 */
public class PointSpreadCollectorOld implements IntersectionProcessor {
	
	private Cache psfCache = RACacheService.instance().getCache("optics.PSF");
	
	/** List of points in currently building set */
	private LinkedList<Object[]> currentPSFPoints = new LinkedList<Object[]>();
	
	private double currentSource[];
	
	private PointSpreadFunction psf;
	private String setName;
	private String dataOutFileName;
	private BinaryMatrixWriter psfDataOut;
	
	private Plane polarisationPlane;
	
	public PointSpreadCollectorOld(String setName, Plane polarisationPlane) {
		this.setName = setName;
		this.polarisationPlane = polarisationPlane;
		this.dataOutFileName = null;
		
	}
	public PointSpreadCollectorOld(String setName, Plane polarisationPlane, String dataOutFileName) {
		this.setName = setName;
		this.polarisationPlane = polarisationPlane;
		this.dataOutFileName = dataOutFileName;
	}
	public PointSpreadCollectorOld(Cache psfCache, String setName, Plane polarisationPlane, String dataOutFileName) {
		this.psfCache = psfCache;
		this.setName = setName;
		this.polarisationPlane = polarisationPlane;
		this.dataOutFileName = dataOutFileName;
	}
	
	/** Start collecting of points for characterisation of a PSF */
	public void startNewPSF(double sourcePos[], PointSpreadFunction newPSF){
		this.currentSource = sourcePos;
		this.psf = newPSF;
		currentPSFPoints.clear();
	}
	
	/** Completes the current PSF */
	public PointSpreadFunction psfDone(boolean addToCache) {
		double pos[][] = new double[currentPSFPoints.size()][];
		double E[][][] = new double[currentPSFPoints.size()][][];
		double startDirs[][] = new double[currentPSFPoints.size()][];
		double startUps[][] = new double[currentPSFPoints.size()][];
		
		int i=0;
		for(Object posData[] : currentPSFPoints){
			pos[i] = (double[])posData[0];
			startDirs[i] = (double[])posData[1];
			startUps[i] = (double[])posData[2];
			E[i] = (double[][])posData[3];
			i++;
		}
		
		psf.setPoints(pos, E);
		psf.setSourceInfo(startDirs, startUps, E);
		
		if(addToCache && !psf.isEmpty())
			psfCache.put(setName, currentSource, psf);
		
		if(dataOutFileName != null){
			double psfData[] = psf.getCharacterisationData();
			if(psfData == null)
				throw new RuntimeException("fail");
			if(psfDataOut == null)
				psfDataOut = new BinaryMatrixWriter(dataOutFileName, psfData.length);
			psfDataOut.writeRow(psfData);
		}
		
		return psf;
	}
	
	/** Dumps the recorded points, the completed PSF's PDF and some regenerated points */ 
	public void dumpPSFInfo(String outPath, int nPoints){
		//points
		double pos[][] = new double[currentPSFPoints.size()][];		
		int i=0;
		for(Object posData[] : currentPSFPoints) {
			double posXY[] = (double[])posData[0];
			double startDir[] = (double[])posData[1];
			double startUp[] = (double[])posData[2];
			double E[][] = (double[][])posData[3];
			pos[i] = new double[E.length*4+2];
			pos[i][0] = posXY[0];
			pos[i][1] = posXY[1];
			pos[i][2] = startDir[0];
			pos[i][3] = startDir[1];
			pos[i][4] = startDir[2];
			pos[i][5] = startUp[0];
			pos[i][6] = startUp[1];
			pos[i][7] = startUp[2];
			for(int j=0; j < E.length; j++){
				pos[i][8+j*4+0] = E[j][0];
				pos[i][8+j*4+1] = E[j][1];
				pos[i][8+j*4+2] = E[j][2];
				pos[i][8+j*4+3] = E[j][3]; 
			}
			i++;
		}
		BinaryMatrixFile.mustWrite(outPath + "/points.bin", pos, false);
		
		if(psf == null){
			System.err.println("WARNING: PointSpreadCollector.dumpPSFInfo(): Points only, PSF not completed.");
			return;
		}
			
		// PDF
		double x[] = OneLiners.linSpace(psf.getMinX(), psf.getMaxX(), 105);
		double y[] = OneLiners.linSpace(psf.getMinY(), psf.getMaxY(), 95);
		BinaryMatrixFile.mustWrite(outPath + "/pdf.bin", y, x, psf.getGridProbability(x, y), true);
		
		// regen points
		BinaryMatrixFile.mustWrite(outPath + "/regenPoints.bin", psf.generatePoints(nPoints), true);
	}
	
	/** Returns a list of all source positions in the PSF cache. */
	public double[][] getSourcePositions(){
		List<Object> keys = psfCache.getKeys(setName);
		
		double sourcePos[][] = new double[keys.size()][];
		int i=0;
		for(Object key : keys){
			if(key instanceof double[]){
				sourcePos[i] = (double[])key;
				i++;
			}
		}
		return Arrays.copyOf(sourcePos, i);
	}

	public PointSpreadFunction getPSFExact(double[] sourcePos) {
		return (PointSpreadFunction) psfCache.get(setName, sourcePos);
	}
	
	public PointSpreadFunction getPSFNearest(double[] sourcePos) {
		return getPSFNearest(getSourcePositions(), sourcePos, Double.POSITIVE_INFINITY);
	}
	
	public PointSpreadFunction getPSFNearest(double sourcePoses[][], double[] sourcePos) {
		return getPSFNearest(sourcePoses, sourcePos, Double.POSITIVE_INFINITY);
	}
	
	public PointSpreadFunction getPSFNearest(double sourcePoses[][], double[] sourcePos, double maxDist) {
		
		double nearestPosActual[] = null;
		double nearestD2 = Double.POSITIVE_INFINITY;
		for(double pos[] : sourcePoses){
			
			double d2 = (sourcePos[0] - pos[0])*(sourcePos[0] - pos[0]) +
						(sourcePos[1] - pos[1])*(sourcePos[1] - pos[1]) +
						(sourcePos[2] - pos[2])*(sourcePos[2] - pos[2]);
			
			if(d2 < nearestD2){
				nearestPosActual = pos;
				nearestD2 = d2;
			}
						
		}
		
		return (nearestD2 < maxDist*maxDist) ? (PointSpreadFunction) psfCache.get(setName, nearestPosActual) : null;
	}

	/** @return {tl, tr, bl, br } */
	public PointSpreadFunction getBoundingPSFs(double[] sourcePos) {
		//return (PointSpreadFunction) psfCache.get(setName, sourcePos);
		
		List<Object> keys = psfCache.getKeys(setName);
		
		double nearestPosActual[] = null;
		double nearestD2 = Double.POSITIVE_INFINITY;
		for(Object key : keys){
			double pos[] = (double[])key;
			
			
			double d2 = (sourcePos[0] - pos[0])*(sourcePos[0] - pos[0]) +
						(sourcePos[1] - pos[1])*(sourcePos[1] - pos[1]) +
						(sourcePos[2] - pos[2])*(sourcePos[2] - pos[2]);
			
			if(d2 < nearestD2){
				nearestPosActual = pos;
				nearestD2 = d2;
			}
						
		}
		
		return (PointSpreadFunction) psfCache.get(setName, nearestPosActual);
	}
	
	/** Returns array of source positions and stats for all PSFs in the collection */
	public double[][] getAllData() {
		List<Object> keys = psfCache.getKeys(setName);
		
		double allData[][] = new double[keys.size()][];
		
		int i=0;
		for(Object key : keys){
			if(!(key instanceof double[]))
				continue;
			
			double sourcePos[] = (double[])key;
			
			PointSpreadFunction psf = (PointSpreadFunction) psfCache.get(setName, sourcePos);
			
			double data[] = psf.getCharacterisationData();
			double meanStartDir[] = psf.getMeanSourceDir();
			double meanStartUp[] = psf.getMeanSourceUp();
			
			allData[i] = new double[9 + data.length];
			System.arraycopy(sourcePos, 0, allData[i], 0, 3);
			System.arraycopy(meanStartDir, 0, allData[i], 3, 3);
			System.arraycopy(meanStartUp, 0, allData[i], 6, 3);
			System.arraycopy(data, 0, allData[i], 9, data.length);
		
			i++;
		}
		
		return Arrays.copyOf(allData, i);
	}
	
	@Override
	/** Adds ray/plane intersections (with polarisation) to the current PSF */
	public void nextIntersection(Intersection imgHit) {
		
		Plane imagePlane = (Plane)imgHit.surface;
		double imgPos[] = imagePlane.posXYZToPlaneUR(imgHit.pos);
		
		//Walk backwards until we find the last time it went through the polarisation sensitive plane
		RaySegment startRay = imgHit.incidentRay;
		Intersection polHit = null;
		
		do {
			if(startRay.endHit.surface == polarisationPlane)
				polHit = startRay.endHit;
			startRay = startRay.startHit.incidentRay;
		}while(startRay.startHit != null);
		
		if(polHit == null)
			return; //never found it and got to start of ray
		
		//make sure the sense of polarisation on the pol plane incident ray 
		//matches the pol plane's sense of up
		polHit.incidentRay.rotatePolRefFrame(polarisationPlane.getUp());
		
		
		currentPSFPoints.add(new Object[]{ imgPos, startRay.dir, startRay.up, polHit.incidentRay.E1 });
	}
	public int getNPointsCollected() {
		return currentPSFPoints.size();
	}
	
	public double[][] getCollectedPoints() {
		double pos[][] = new double[currentPSFPoints.size()][];
		int i = 0;
		for(Object posData[] : currentPSFPoints) {
			pos[i] = (double[])posData[0];
			i++;
		}		
		return pos;
	}
	
	/** It's possible to save a grid definition with a collector, for retrieval later. Stored in cache as a kind of meta data */
	public double[][] getGridDefinition() {
		return (double[][]) psfCache.get(setName, "grid");
	}
	
	public void setGridDefinition(double gX[], double gY[], double gZ[]) {
		psfCache.put(setName, "grid", new double[][]{ gX, gY, gZ });
	}
	public Cache getCacheSet() {
		return psfCache;
	}
	
	
	

}
