/* ----------------------------------------
 * 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 
 * Version : 05/05/2021 : LBE
 * DM-ID : Redmine#7059 : 05/05/2021 : n point instead of 3 for trajectories drawing
 * FA-ID : Redmine#10072 : 13/05/2022 : empty values only before compute not on start change
 * VERSION : V2.13
 * FA-ID : redmine#12154 : 24/09/2025 : KHK : Fix bug on SC position
 * ----------------------------------------
 */
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.pt.client.controller.common.SpaceElementCollection;
import fr.tlse.irap.fit.FitPoint;
import fr.tlse.irap.pos.Position;
import fr.tlse.irap.spaceElement.Probe;
import fr.tlse.irap.spaceElement.SpaceElement;
import fr.tlse.irap.utils.CoordConverter;

public class JMapPosition {

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

    private static double rE = 215.095; // Solar radius to 1 AU
    /**
     * default is 20 days: 10 before date and 10 after For PSP, set it to 10 days, so 5 days before
     * and after
     */
    private int nbDays = 10;
    /**
     * Number of position displayed instead of t1, t2 and start position
     */
    private int nbTimeSteps = 30;

    private Date tStart = new Date();
    private Date tImpact = new Date();
    private Date t1 = new Date();
    private Date t2 = new Date();
    private ArrayList<Date> timeValues = new ArrayList<>();

    private Probe obsInSitu;
//	private Probe obsInSituT1;
//	private Probe obsInSituT2;
    private List<Probe> observers;
//	private List<Probe> observers, observersT1, observersT2; 

    private SpaceElement startSource, endSource;
    private boolean posToRecompute = false; //Used to clear positions juste before getting them and not as soons as start changed.
//	private Position startSourcePos;
//	private Position startSourcePosT1;
//	private Position startSourcePosT2;
//	private Position endSourcePos;
//	private Position endSourcePosT1;
//	private Position endSourcePosT2;

    private boolean bindStartDate = true;	// indicate if start date is bind to current date property

    public JMapPosition() {
    }

    public List<Probe> getObservers() {
        if (observers == null || observers.isEmpty()) {
            if (SpaceElementCollection.getSpaceElementWrapper() != null) {
                observers = new ArrayList<Probe>();
                //observersT1 = new ArrayList<Probe>();
                //observersT2 = new ArrayList<Probe>();
                for (Probe obs : SpaceElementCollection.getSpaceElementWrapper().getProbes()) {
                    if (obs.isObserver()) {
                        // Create observer list at tStart
                        observers.add(createObserver(obs.getName()));
                        //observersT1.add(createObserver(obs.getName()));
                        //observersT2.add(createObserver(obs.getName()));
                    }
                }
                // set default observer (STEREO-A)
                obsInSitu = observers.get(0);
                //setObsInSituT1(observersT1.get(0));
                //setObsInSituT2(observersT2.get(0));
            }
        }
        return observers;
    }

    private Probe createObserver(String name) {
        Probe observer = new Probe(name);
        return observer;
    }

    public void setStartSource(SpaceElement startSource) {
        this.startSource = startSource;
        // clear all positions of start source
        posToRecompute = true;
        //clearPositionsStartSource();
    }

    public SpaceElement getStartSource() {
        return startSource;
    }

    public void setEndSource(SpaceElement element) {
        if (this.endSource == null || element == null || this.endSource.getName() != element.getName()) {
            this.endSource = element;
            // clear all positions of end source
            //clearPositionsEndSource();
        }
//		else {
//			// clear pos at impact time
//			endSourcePos = null;
//		}
        clearPositionsEndSource();
    }

    public SpaceElement getEndSource() {
        return endSource;
    }

    /**
     * update dates
     */
    public void setDates(Date date) {
        tStart.setTime(date.getTime());
        t1.setTime(date.getTime() - (long) nbDays * 86400 * 1000);
        t2.setTime(date.getTime() + (long) nbDays * 86400 * 1000);
        log.info("Set dates: "+tStart+" "+t1+" "+t2);
        timeValues.clear();
        for (long lTime = t1.getTime(); lTime < t2.getTime(); lTime += (t2.getTime() - t1.getTime()) / nbTimeSteps) {
            timeValues.add(new Date(lTime));
        }
        timeValues.add(new Date(t2.getTime()));
        tImpact = null;
    }

    /**
     * update observer in situ
     */
    public void setObserverInSitu(String probeName) {
        // update current observer in situ  
        for (Probe obs : getObservers()) {
            if (obs.getName().contains(probeName)) {
                setObsInSitu(obs);
            }
        }
//		for (Probe obs : observersT1) {
//			if (obs.getName().contains(probeName)) {
//				setObsInSituT1(obs);
//			}
//		}		
//		for (Probe obs : observersT2) {
//			if (obs.getName().contains(probeName)) {
//				setObsInSituT2(obs);
//			}
//		}		
    }

    /**
     * Get the observer from source with its positions at tStart If position is not set, set
     * positions at tStart, t1 and T2 Return position at tStart, null in other case
     */
    public Probe getPositionObserver(String source) {
        for (Probe observer : getObservers()) {
            if (observer.getName().contains(source)) {
                if (observer.getRadius() == null) {
                    // Get position of observer at time tStart
                    //log.info("getObserverPosition for date " + tStart);
                    Probe obs = (Probe) JMapHelper.getPositionElement(observer, tStart);
                    //JMapHelper.getPositionsElement(obs,timeValues);
                    setPositionObserver(getObservers(), obs);
                    //log.info("getObserverPosition for date " + t1);
//	    			obs = (Probe) JMapHelper.getPositionElement(observer, t1);
//	    			setPositionObserver(observersT1, obs);
//					//log.info("getObserverPosition for date " + t2);
//	    			obs = (Probe) JMapHelper.getPositionElement(observer, t2);
//	    			setPositionObserver(observersT2, obs);

                }
                return observer;
            }
        }
        return null;
    }

    /**
     * Set position of Observer In Situ at time tStart
     */
    public void setPositionObsInSitu() {
        // Get position of observer at time tStart
        Probe obs = (Probe) JMapHelper.getPositionElement(obsInSitu, tStart);
        //JMapHelper.getPositionsElement(obs,timeValues);
        setPositionObserver(getObservers(), obs);
    }

    /**
     * Update position of observer in the list of observers
     */
    private void setPositionObserver(List<Probe> obsList, Probe observer) {
        for (Probe obs : obsList) {
            if (obs.getName().contains(observer.getName())) {
                obs.setX(observer.getX());
                obs.setY(observer.getY());
                obs.setZ(observer.getZ());
                obs.setPositions(observer.getPositions());
                obs.setTimeValues(observer.getTimeValues());
                // compute radial distance 
                obs.setRadius(Math.sqrt(Math.pow(observer.getX(), 2.0) + Math.pow(observer.getY(), 2.0) + Math.pow(observer.getZ(), 2.0)));
            }
        }
    }

    /**
     * Clear positions of all observers
     */
    public void clearPositionsObservers() {
        for (Probe obs : getObservers()) {
//			obs.setX(null);
//			obs.setY(null);
//			obs.setZ(null);
            obs.setRadius(null);
        }
    }

    /**
     * Get position of element at start time or impact time (if endSource)
     */
//    private SpaceElement getPositionSource(SpaceElement element) {
//        SpaceElement e;
//
//        if (element.getName().equals(startSource.getName())) {
//            // set start source position
//            startSourcePos = getSourcePosition(startSourcePos, startSource, getStartTime());
//            // get start source with its position
//            e = getPositionSource(startSource, startSourcePos);
//            
//        } else if (endSource != null && element.getName().equals(endSource.getName())) {
//            if (getImpactTime() != null) {
//                endSourcePos = getSourcePosition(endSourcePos, endSource, getImpactTime());
//                e = getPositionSource(endSource, endSourcePos);
//                //log.debug("source position at impact time is " + e);
//            } else {
//                endSourcePos = getSourcePosition(endSourcePos, endSource, getStartTime());
//                e = getPositionSource(endSource, endSourcePos);
//                //log.debug("source position at start time is " + e);
//            }
//        } else {
//            //log.info("getPosition for " + element + " and date " + getStartTime());
//            e = JMapHelper.getPositionElement(element, getStartTime());
//        }
//        return e;
//    }

//    /**
//     * Get position of element at T1 time
//     */
//    private SpaceElement getPositionSourceT1(SpaceElement element) {
//        SpaceElement e;
//
//        if (element.getName().equals(startSource.getName())) {
//            startSourcePosT1 = getSourcePosition(startSourcePosT1, startSource, getT1());
//            e = getPositionSource(startSource, startSourcePosT1);
//        } else if (endSource != null && element.getName().equals(endSource.getName())) {
//            endSourcePosT1 = getSourcePosition(endSourcePosT1, endSource, getT1());
//            e = getPositionSource(endSource, endSourcePosT1);
//        } else {
//            //log.info("getPosition for " + element + " and date " + getT1());
//            e = JMapHelper.getPositionElement(element, getT1());
//        }
//        return e;
//    }

//    /**
//     * Get position of element at T2 time
//     */
//    private SpaceElement getPositionSourceT2(SpaceElement element) {
//        SpaceElement e;
//
//        if (element.getName().equals(startSource.getName())) {
//            startSourcePosT2 = getSourcePosition(startSourcePosT2, startSource, getT2());
//            e = getPositionSource(startSource, startSourcePosT2);
//        } else if (endSource != null && element.getName().equals(endSource.getName())) {
//            endSourcePosT2 = getSourcePosition(endSourcePosT2, endSource, getT2());
//            e = getPositionSource(endSource, endSourcePosT2);
//        } else {
//            //log.info("getPosition for " + element + " and date " + getT2());
//            e = JMapHelper.getPositionElement(element, getT2());
//        }
//        return e;
//    }

//    /**
//     * Get position of source at a date
//     */
//    private Position getSourcePosition(Position sourcePos, SpaceElement source, Date date) {
//        Position position;
//        if (sourcePos != null) {
//            position = sourcePos;
//        } else {
//            if (source.getClass().equals(Sun.class)) {
//                position = new Position(0, 0, 0);
//            } else {
//                //log.info("getSourcePosition for " + source.getName() + " and date " + date);
//                SpaceElement e = JMapHelper.getPositionElement(source, date);
//                position = new Position(e.getX(), e.getY(), e.getZ());
//            }
//        }
//        return position;
//    }
//
//    private SpaceElement getPositionSource(SpaceElement source, Position sourcePos) {
//        if (sourcePos != null) {
//            source.setX(sourcePos.getX());
//            source.setY(sourcePos.getY());
//            source.setZ(sourcePos.getZ());
//            return source;
//        } else {
//            return null;
//        }
//    }

    public void clearPositionsSources() {
        clearPositionsStartSource();
        clearPositionsEndSource();
    }

    public void clearPositionsStartSource() {
        //startSourcePos = null;
        if(startSource.getPositions()!=null) {
            startSource.getPositions().clear();
        }
        //startSourcePosT1 = null;
        //startSourcePosT2 = null;
    }

    public void clearPositionsEndSource() {
        //endSourcePos = null;
        if(endSource!=null && endSource.getPositions()!=null) {
            endSource.getPositions().clear();
        }
        //endSourcePosT1 = null;
        //endSourcePosT2 = null;
    }

    /**
     * Get element elongations (alpha) between T1 and T2 Return alpha[3] => alpha[0]: contains
     * computed fitPoint at tStart => alpha[1]: contains computed fitPoint at tSun or tImpact =>
     * alpha[2]: contains computed fitPoint at tEnd
     */
    public FitPoint[] getElongations(SpaceElement element) {
        
        if(element.getTimeValues()==null || element.getTimeValues().isEmpty()) {
            element.setTimeValues(timeValues);
        }
        List<FitPoint> alpha = new ArrayList<FitPoint>();
        //Date date;
        //Probe obs;
        if(posToRecompute) {
            clearPositionsStartSource();
            posToRecompute = false;
        }
        log.info("getElongation called with time: "+timeValues.size());
        
        //positions list is Empty, fill it
        if(element.getPositions()==null || element.getPositions().size()!=timeValues.size()) {
            log.debug("***** getPositions "+timeValues.size()+" for " + element.getName());//+" old: "+element.getPositions().size());
            JMapHelper.getPositionsElement(element, timeValues).getPositions();
            //12154
            element.setStartTime(getT1().getTime() / 1000); 
        }
        if(getObsInSitu().getPositions()==null || getObsInSitu().getPositions().size()!=timeValues.size()) {
            log.debug("***** getPositions "+timeValues.size()+" for " + getObsInSitu().getName());//+" old: "+element.getPositions().size());
            JMapHelper.getPositionsElement(getObsInSitu(), timeValues);
            //12154
            getObsInSitu().setStartTime(getT1().getTime() / 1000);
        }
        
                
        for(int i=0;i<element.getTimeValues().size();i++) {
            Date lDate = element.getTimeValues().get(i);
            FitPoint lFp = new FitPoint();
            lFp.setTime(lDate.getTime());
//            element.setPosition(element.getPositions().get(i));
//            getObsInSitu().setPosition(getObsInSitu().getPositions().get(i));
            lFp.setElongation(computeElongation(element, getObsInSitu(), lDate));
            alpha.add(lFp);
        }
        // Compute elongation of element at time T1
        log.debug("Compute elongation for element " + element.getName() + " and observer " + getObsInSitu().getName() +" "+ alpha.size() +" : ");
        //log.info(" ======> at date " + getT1());
//        alpha[0] = new FitPoint();
//        alpha[0].setTime(getT1().getTime());
//        alpha[0].setElongation(computeElongation(getPositionSourceT1(element), getObsInSituT1(), getT1()));
        // Compute elongation of element at time tImpact if needed and add it at the end
        if (endSource != null && element.getName().equals(endSource.getName()) && getImpactTime() != null) {
            Date lDate = getImpactTime();
            //log.info("getObsInsituPosition for date " + date);
            //Probe obs = (Probe) JMapHelper.getPositionElement(getObsInSitu(), lDate);
            FitPoint lFp = new FitPoint();
            lFp.setTime(lDate.getTime());
            lFp.setElongation(computeElongation(element, getObsInSitu(), lDate));
            alpha.add(lFp);
                
                //log.debug("obs position at impact time is " + obs);
//        } else {
//            date = getStartTime();
//            obs = getObsInSitu();
        }
//        //log.info(" ======> at date " + date);
//        alpha[1] = new FitPoint();
//        alpha[1].setTime(date.getTime());
//        alpha[1].setElongation(computeElongation(getPositionSource(element), obs, date));
        // Compute elongation of element at time T2
        //log.info(" ======> at date " + getT2());
//        alpha[2] = new FitPoint();
//        alpha[2].setTime(getT2().getTime());
//        alpha[2].setElongation(computeElongation(getPositionSourceT2(element), getObsInSituT2(), getT2()));

        return alpha.toArray(new FitPoint[alpha.size()]);
    }

    
    
    /**
     * Compute elongation (alpha) between element and observer at a time Element and observer
     * position must have been computed at the same time
     */
    private static double computeElongation(SpaceElement pElt, Probe pObs, Date pDate) {
        Position lElt = null;
        Position lObs = null;
        //int lIdx = 0;
        for(int i=0;i<pElt.getPositions().size();i++) {
            //log.debug("compare "+pDate+" to "+pElt.getTimeValues().get(i));
            if(!pDate.after(pElt.getTimeValues().get(i)) || i==pElt.getPositions().size()-1) {
                //log.debug("Found pos at "+i+" set "+pObs.getPositions().get(i));
                //pElt.setPosition(pElt.getPositions().get(i));
                //pObs.setPosition(pObs.getPositions().get(i));
                lElt = pElt.getPositions().get(i);
                lObs = pObs.getPositions().get(i);
                break;
            }
        }


        // Compute observer spherical coordinates at observer time
        double[] longLat = CoordConverter.toSpheric(lObs.getX(), lObs.getY(), lObs.getZ());
        double longObs = longLat[0]; // longitude
        double rObs = longLat[2]; // radial distance

        // Compute element spherical coordinates at time t
        longLat = CoordConverter.toSpheric(lElt.getX(), lElt.getY(), lElt.getZ());
        double longElt = longLat[0]; // longitude
        double rSun = CoordConverter.toKm(1 / rE);
        double rElt = longLat[2] - rSun; // radial distance from sun surface

        // Compute delta long (beta) between element and observer at time t
        double beta;
        if (pObs.isBackward(pDate)) {
            beta = CoordConverter.normalizeLongitude(longElt - longObs);
        } else {
            beta = CoordConverter.normalizeLongitude(longObs - longElt);
        }

        // Compute elongation (alpha) of element at time t
        double alpha = JMapComputeHelper.computeElongation(rObs, rElt, beta);

        return alpha;
    }

    public Date getT1() {
        return t1;
    }

    public Date getT2() {
        return t2;
    }

    public Date getStartTime() {
        return tStart;
    }

    /**
     * Get the insitu observer with its position
     */
    public Probe getObsInSitu() {
        return obsInSitu;
    }

    private void setObsInSitu(Probe obs) {
        obsInSitu = obs;
    }

//	public Probe getObsInSituT1() {
//		return obsInSituT1;
//	}
//
//	public void setObsInSituT1(Probe obsInSituT1) {
//		this.obsInSituT1 = obsInSituT1;
//	}
//
//	public Probe getObsInSituT2() {
//		return obsInSituT2;
//	}
//
//	public void setObsInSituT2(Probe obsInSituT2) {
//		this.obsInSituT2 = obsInSituT2;
//	}
    public Date getImpactTime() {
        return tImpact;
    }

    public void setImpactTime(Date tImpact) {
        this.tImpact = tImpact;
    }

    public boolean isBindStartDate() {
        return bindStartDate;
    }

    public void bindStartDate() {
        this.bindStartDate = true;
    }

    public void unbindStartDate() {
        this.bindStartDate = false;
    }

}
