/* ----------------------------------------
 * Origin  : GFI INFORMATIQUE 
 * Project : CDPP Propagation Tool 
 * ----------------------------------------
 * History 
 * Version : 01/05/2015 : SCA : V2.1
 * Version : 25/04/2016 : SCA : V2.2 (PSWS-H2020)
 *  Add Z axis in observer radial distance computation 
 *  Swap STEREO-A / STEREO-B FOV after 14/07/2015
 * VERSION : V2.12
 * DM-ID : redmine#12066 : 05/03/2025 : KHK : update limit solar wind 
 * ----------------------------------------
 */
package fr.tlse.irap.pt.client.controller.tools.jmap;

import java.util.ArrayList;
import java.util.Date;
import java.util.List;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import fr.tlse.irap.fit.Fit;
import fr.tlse.irap.fit.FitPoint;
import fr.tlse.irap.pt.client.services.ServiceFactory;
import fr.tlse.irap.spaceElement.Probe;
import fr.tlse.irap.tool.radial.CMEType;
import fr.tlse.irap.tool.radial.DragModel;
import fr.tlse.irap.utils.CoordConverter;
import fr.tlse.irap.utils.ToolHelper;

public class JMapCompute {

	protected static final Logger log = LoggerFactory.getLogger(JMapCompute.class);

	public JMapPosition jMapPosition = new JMapPosition();
	
	private static int MIN_SPEED = 201;
	
        // 12066 update limit solar wind 800 to 3000
        private static int MAX_SPEED = 3000;
       
	private static int FIT_ERROR_XSIZE = MAX_SPEED - MIN_SPEED + 1;
	private static int FIT_ERROR_YSIZE = 180;

	private Date tSun = new Date();

	private List<FitPoint> fitPoints;
	private List<FitPoint> clickPoints;
	private List<Fit> fitCatalog;
	private double fitError[][] = new double[FIT_ERROR_YSIZE][FIT_ERROR_XSIZE];
	private List<List<FitPoint>> blobs = new ArrayList<List<FitPoint>>();
	
	/* best beta and speed computed by click algo */
	private double betaStart = Double.NaN;	// delta longitude between CME and observer (beta) at tStart
	private double speedSolarWind = 500.0;	// speed at time t at START point, expressed in km/s
	private double rotationPeriod = 25.38;	
	private DragModel dragModel;
	private double distStart;				// radial distance at time t of START point, expressed in km	
	private double longStart = 0;	 		// HAE longitude of Start (at tStart)
	private int blobsInterval = 8;
	private boolean isComputed;

	private CMEType cmeType = CMEType.FIXED_POINT;
	private boolean linkcatMode = false;

	public JMapCompute() {
	}

	public void setJMapPosition (JMapPosition jMapPosition) {
		this.jMapPosition = jMapPosition;
	}

	public void updateLongStart() {
		Probe obs = jMapPosition.getObsInSitu();
		double[] HAELongLat = CoordConverter.toSpheric(obs.getX(), obs.getY(), obs.getZ());
		// update longStart
		if (obs.isBackward(jMapPosition.getStartTime())) {
			longStart = CoordConverter.normalizeLongitude(HAELongLat[0] + betaStart);
		} else {
			longStart = CoordConverter.normalizeLongitude(HAELongLat[0] - betaStart);
		}
	}

	/** 
	 * update data from a catalog fit 
	 * @param fitId
	 */
	public void updateFromFit(int fitId) {
		if (fitId >= 0) {
			Fit fit = fitCatalog.get(fitId);
			jMapPosition.getStartTime().setTime(fit.getSunTime().getTimeInMillis());
			tSun.setTime(fit.getSunTime().getTimeInMillis());
			betaStart = fit.getBeta();                       
			speedSolarWind = fit.getSpeed();                        
		}
	}

	/**
	 * Compute Click algorithm
	 */
	public void computeClick() {
		double radialDistanceObs;
		double radialDistance0;
		double radialDistance;
		double alpha0;
		double beta;
		int index0=0;
              
		setFitPoints(null);

		// Compute Radial distance of observer
		radialDistanceObs = jMapPosition.getObsInSitu().getRadius();
		//log.debug("radialDistanceObs=" + radialDistanceObs);

		Double alphaTh[] = new Double[clickPoints.size()];
		Long deltaT[] = new Long[clickPoints.size()];

		// Get min elongation of the click points
		alpha0 = JMapComputeHelper.minElongation(clickPoints);
		// Get min time in seconds of the click points
		Long initialSeconds = JMapComputeHelper.minTime(clickPoints) / 1000;
		for (int i = 0; i < clickPoints.size(); i++) {
			/* deltaTime ti in seconds */
			deltaT[i] = clickPoints.get(i).getTime() / 1000 - initialSeconds;
			if (deltaT[i] == 0) {
				index0 = i;
			}
		}
		//log.debug("alpha0 = " + alpha0);
		
		for (int i = 0; i < FIT_ERROR_YSIZE; i++) {
			beta = i + 1;
			for (int j = 0; j < FIT_ERROR_XSIZE; j++) {
				speedSolarWind = j + MIN_SPEED;
				/* Compute radial distance for alpha0 elongation */
				radialDistance0 = JMapComputeHelper.computeRadialDistance(radialDistanceObs, alpha0, beta);
				for (int k = 0; k < clickPoints.size(); k++) {
					radialDistance = radialDistance0 + getSpeedSolarWind() * deltaT[k];
					/* Compute theoric elongation using Fixed phi approximation */
					alphaTh[k] = JMapComputeHelper.computeElongation(radialDistanceObs, radialDistance, beta);
				}
				/* Compute error between click points alpha and alpha theoric */
				fitError[i][j] = JMapComputeHelper.computeError(clickPoints, alphaTh);
			}
		}

		/* min value will solve the best fit parameters beta and speed */
		int minIndex[] = JMapComputeHelper.minIndex(fitError, FIT_ERROR_YSIZE, FIT_ERROR_XSIZE);
		betaStart = minIndex[0] + 1;
		speedSolarWind = minIndex[1] + MIN_SPEED;
		
		/* Compute delta time between clicked t0SUN and t0 */
		alpha0 = clickPoints.get(index0).getElongation();
		radialDistance0 = JMapComputeHelper.computeRadialDistance(radialDistanceObs, alpha0, betaStart);
		double deltaSeconds = radialDistance0 / speedSolarWind;
		/* Compute initial Time tSUN */
		double sunSeconds = initialSeconds - deltaSeconds;
		jMapPosition.getStartTime().setTime((long)sunSeconds * 1000);
		tSun.setTime((long)sunSeconds * 1000);
		//log.debug("computeClick is done");
	}

	/**
	 * Compute Fit InSitu algorithm
	 */
	public void computeInSitu() {
		try {
			Probe obs = jMapPosition.getObsInSitu();
			//	Compute beta from longStart and longObs at start time
    		double[] HAELongLat = CoordConverter.toSpheric(obs.getX(), obs.getY(), obs.getZ());
    		double longObsT = HAELongLat[0];
    		if (obs.isBackward(jMapPosition.getStartTime())) {
	    		betaStart = CoordConverter.normalizeLongitude(longStart - longObsT);
			} else {
				betaStart = CoordConverter.normalizeLongitude(longObsT - longStart);
			}
			// get observer orbital motion factor (deg/s)
    		double gamma = getOrbMotionFactor(obs, jMapPosition.getStartTime());
			// get beta at tSun
			double betaSun = betaStart - gamma * ((jMapPosition.getStartTime().getTime() - tSun.getTime())/1000);
			
			if (dragModel == null) {
				// Compute fits points from tSun with constant speed
				fitPoints = JMapComputeHelper.computeFitPoints(jMapPosition.getObsInSitu().getRadius(), speedSolarWind, betaSun, gamma, tSun);
			}
			else {
				// Compute fits points from tSun with variable speed
				fitPoints = JMapComputeHelper.computeFitPoints(jMapPosition.getObsInSitu().getRadius(), dragModel, betaSun, gamma, tSun);
			}
			//log.debug("Fit points size = " + fitPoints.size());
			//log.debug("computeFit is done");
			setComputed(true);
		}
		catch (Exception e) {
			//e.printStackTrace();
			log.info("In situ observer position is not set");
			setComputed(false);
		}
	}

	/**
	 * Compute Fit from betaStart, speedSolarWind and tStart 
	 */
	public void computeFit() {
		try {
			Probe obs = jMapPosition.getObsInSitu();
			
			fitPoints = JMapComputeHelper.computeFitPoints( 
					obs.getRadius(), 
					speedSolarWind, 
					betaStart, 
					getOrbMotionFactor(obs, jMapPosition.getStartTime()), 
					jMapPosition.getStartTime());
			
			//log.debug("Fit points size = " + fitPoints.size());
			setComputed(true);
		}
		catch (Exception e) {
			e.printStackTrace();
			setComputed(false);
		}

	}

	/**
	 * Compute Fits from Catalog
	 */
	public void computeFits(List<Fit> fitCatalog) {
		Probe obs;
		try {
			// get observer orbital motion factor (deg/s)
            for (Fit fit : fitCatalog) {
    			if (fit.getBeta().isNaN()) {
    				// Get position of observer in Situ at fit tSun in HEEQ 
    				obs = (Probe) ServiceFactory.getPositionService().getHEEQPositionElement(jMapPosition.getObsInSitu(), fit.getSunTime().getTime());
    				// compute beta from fit coordinates and position observer (in HEEQ)
        			if (!fit.getLongitude().isNaN()) {
        	    		double longObs = CoordConverter.toSpheric(obs.getX(), obs.getY(), obs.getZ())[0];
        	    		if (obs.isBackward(fit.getSunTime().getTime())) {
        		    		fit.setBeta(CoordConverter.normalizeLongitude(fit.getLongitude() - longObs));
        				} else {
        					fit.setBeta(CoordConverter.normalizeLongitude(longObs - fit.getLongitude()));
        				}
        			}
    			}
				else {
					// Get position of observer in Situ at fit tSun in HAE 
					obs = (Probe) JMapHelper.getPositionElement(jMapPosition.getObsInSitu(), fit.getSunTime().getTime());
				}
		        obs.setRadius(Math.sqrt(Math.pow(obs.getX(), 2.0) + Math.pow(obs.getY(), 2.0) + Math.pow(obs.getZ(), 2.0)));
    			// get observer orbital motion factor (deg/s) at fit start time
    			double gamma = getOrbMotionFactor(jMapPosition.getObsInSitu(), fit.getSunTime().getTime()); 
    			// Compute fit points from tSun
    			fit.setFitPoints(JMapComputeHelper.computeFitPoints( obs.getRadius(), fit.getSpeed(), fit.getBeta(), gamma, fit.getSunTime().getTime()));
			}
			setComputed(true);
			this.fitCatalog = fitCatalog;
		}
		catch (Exception e) {
			e.printStackTrace();
			this.fitCatalog = null;
			setComputed(false);
		}
	}

	/**
	 * Compute blobs pattern
	 */
	public void computeBlobs() {
		long period = 600; 				// delta period = 10 minutes between each points
		long maxSec = 6 * 24 * 3600; 	// max period = 6 days
		Date time = new Date();
		
		try {
			Probe obs = jMapPosition.getObsInSitu();
			long tStart = jMapPosition.getStartTime().getTime() / 1000;
			long timeSun = tSun.getTime() / 1000;
			double dr = ToolHelper.computeRadialDistance(obs);
			boolean isBackward = obs.isBackward(tSun);

			// compute deltas between 2 blobs
			long deltaTime = blobsInterval * 3600;	// delta time (s)
			double omega = Math.toDegrees(ToolHelper.getRotationFrequency(rotationPeriod));
			double gamma = getOrbMotionFactor(obs, tSun); // observer orbital motion factor (deg/s)
			double deltaBeta = deltaTime * (omega + gamma); // delta longitude (deg) 

			// compute start time and beta of the first blob
			double betaSun = betaStart - gamma * (tStart - timeSun); // beta at tSun
			int nbBlobs = (int)((180 - betaSun) / deltaBeta);
			double beta0 = betaSun + nbBlobs*deltaBeta;
			long t0 = timeSun;
			if (isBackward) {
				t0 += nbBlobs * deltaTime;
			}
			else {
				t0 -= nbBlobs * deltaTime;
			}

			// compute the Blob pattern => the result is a list of fitPoints
	    	for (List<FitPoint> fitPoints : blobs) {
	    		fitPoints.clear();
	    	}
	    	blobs.clear();

			int maxBlobs = (int) (180.0 / deltaBeta);
			double beta = beta0;
			long ti = t0;
			for (int i = 0; i < maxBlobs; i++) {
				// compute blob i
				time.setTime((long) (ti * 1000));
				blobs.add(JMapComputeHelper.computeFitPoints(dr, speedSolarWind, beta, gamma, time, period, maxSec));
				// increments
				beta -= deltaBeta;
				if (isBackward) {
					ti -= deltaTime;
					if (betaSun - beta > 180) {
						if (ti < timeSun) {
							ti = (long) (ti + rotationPeriod * 3600 * 24);
						}
					}
				} else { // normal way
					ti += deltaTime;
					if (betaSun - beta > 180) {
						if (ti > timeSun) {
							ti = (long) (ti - rotationPeriod * 3600 * 24);
						}
					}
				}
			}
			//log.debug("Compute blobs is done");
		}
		catch (Exception e) {
			log.info("obs in situ position is not set");
		}
	}

    /**
     * Get orbital motion factor of an observer in degree/s
     * @param obs
     * @return
     */
    public double getOrbMotionFactor(Probe obs, Date date) {
    	double orbMotionFactor = 1.0;
    	if (obs.isBackward(date)) {
			orbMotionFactor = -0.96;
    	}
    	else {
    		orbMotionFactor = 1.04;
    	}
     	return orbMotionFactor / (24 * 3600);
    }


    public double getBetaStart() {
        return betaStart;
    }

    public void setBetaStart(double betaStart) {
        this.betaStart = betaStart;
    }

    public double getSpeedSolarWind() {
        return speedSolarWind;
    }

    public void setSpeedSolarWind(double speedSolarWind) {
        this.speedSolarWind = speedSolarWind;
    }

	public boolean isComputed() {
		return isComputed;
	}

	public void setComputed(boolean isComputed) {
		this.isComputed = isComputed;
	}

	public List<FitPoint> getClickPoints() {
		return clickPoints;
	}

	public void setClickPoints(List<FitPoint> clickPoints) {
		this.clickPoints = clickPoints;
	}

	public List<FitPoint> getFitPoints() {
		return fitPoints;
	}

	public void setFitPoints(List<FitPoint> fitPoints) {
		this.fitPoints = fitPoints;
	}

	public double[][] getFitError() {
		return fitError;
	}

	public void setFitError(double fitError[][]) {
		this.fitError = fitError;
	}

	public List<Fit> getFitCatalog() {
		return fitCatalog;
	}

	public void setFitCatalog(List<Fit> fitCatalog) {
		this.fitCatalog = fitCatalog;
	}

	public List<List<FitPoint>> getBlobs() {
		return blobs;
	}

	public void setBlobs(List<List<FitPoint>> blobs) {
		this.blobs = blobs;
	}

	public Double getLongStart() {
		return longStart;
	}

	public void setLongStart(double longStart) {
		this.longStart = longStart;
	}

	public JMapPosition getJMapPosition() {
		return jMapPosition;
	}

	public Date getSunTime() {
		return tSun;
	}

	public double getDistStart() {
		return distStart;
	}

	public void setDistStart(double distStart) {
		this.distStart = distStart;
	}

	public double getRotationPeriod() {
		return rotationPeriod;
	}

	public void setRotationPeriod(double rotationPeriod) {
		this.rotationPeriod = rotationPeriod;
	}

	public int getBlobsInterval() {
		return blobsInterval;
	}

	public void setBlobsInterval(int blobsInterval) {
		this.blobsInterval = blobsInterval;
	}

	public void setDragModel(DragModel dragModel) {
		this.dragModel = dragModel;
	}

	public CMEType getCmeType() {
		return cmeType;
	}

	public void setCmeType(CMEType cmeType) {
		this.cmeType = cmeType;
	}

	public boolean isLinkcatMode() {
		return linkcatMode;
	}

	public void setLinkcatMode(boolean linkcatMode) {
		this.linkcatMode = linkcatMode;
	}
}
