/* ----------------------------------------
 * Origin  : GFI INFORMATIQUE 
 * Project : CDPP Propagation Tool 
 * ----------------------------------------
 * History 
 * Version : 01/05/2015 : SCA : V2.1
 *  Creation 
 * Version : 24/03/2016 : SCA : V2.2 (PSWS-H2020)
 *  Add comets
 *  Swap STEREO-A / STEREO-B cameras after 14/07/2015
 * Version : 04/06/2021 : LBE
 * FA-ID : Redmine#9410 : 04/06/2021 : copy time files on clone
 * Version : 05/12/2022 : LBE : 2.6
 * FA-ID : Redmine#10660 : 05/12/2022 : take local instruments for SOHO
 * VERSION : V2.12
 * DM-ID : redmine#12067 : 27/03/2025 : KHK : Problem with positioning satellites on the Ecliptic Plane in SEP Propagation mode.
 * ----------------------------------------
 */
package fr.tlse.irap.pt.client.controller.common;

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

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

import fr.tlse.irap.pos.Position;
import fr.tlse.irap.pt.client.services.ServiceFactory;
import fr.tlse.irap.spaceElement.Camera;
import fr.tlse.irap.spaceElement.Comet;
import fr.tlse.irap.spaceElement.Instruments;
import fr.tlse.irap.spaceElement.Planet;
import fr.tlse.irap.spaceElement.Probe;
import fr.tlse.irap.spaceElement.SpaceElement;
import fr.tlse.irap.spaceElement.SpaceElementWrapper;
import fr.tlse.irap.utils.CoordConverter;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.SimpleBooleanProperty;

/**
 *
 * This static class contains all the space elements with their positions at
 * current time It contains also all the planet orbit positions
 *
 * Positions of space elements are updated when time is changed.
 *
 * @author frup03304
 * @version
 */
public class SpaceElementCollection {

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

    private static List<Planet> planets = new ArrayList<Planet>();
    private static List<Probe> probes = new ArrayList<Probe>();
    private static List<Comet> comets = new ArrayList<Comet>();
    private static SpaceElementWrapper spaceElementWrapper;
    private static Map<String, List<Camera>> probeCameras;

    private static BooleanProperty hasChanged = new SimpleBooleanProperty(); // space element list change property

    private static Date startDate = new Date();			// selected start date 
    private static Date endDate = new Date();
    private static Date currentStartDate = new Date(); 	// start date used to get positions of S/C and comets
    private static Date currentEndDate = new Date();
    private static Date currentDate = new Date(); 		// ecliptic view date

    private static long PERIOD_DAYS = 30;				// period used to get positions of S/C
    public static int COMETS_NB_MAX = 50;

    public static SpaceElementWrapper getSpaceElementWrapper() {
        return spaceElementWrapper;
    }

    public static void setSpaceElementWrapper(SpaceElementWrapper spaceElementWrapper) {
        SpaceElementCollection.spaceElementWrapper = spaceElementWrapper;
    }

    /**
     * Get orbit positions of space elements at date d.
     * <li>For planets, period = nb days of 1 orbit period, step duration is 3
     * days for inner planets and 5 days for outer planets. Planets orbits are
     * getting from 3DView once between start date and an end date defined
     * specifically for each planet.
     * <li>For spacecraft, period = 30 days, step duration is 3 days.
     * Spacecrafts orbits are getting from 3DView for a period of 30 days from
     * start date.
     * <br>Clear comets if current start date is out of current period (start
     * date, end date)
     *
     * @param d : current date
     * @param dStart : current start date
     */
    public static void getPositions(Date d, Date dStart) {
        //log.debug("getPosition for date = " + d + " and start date = " + dStart+" old start = "+startDate);              
        log.debug("getPosition OLD startDate = " + startDate + " and OLD endDate = " + endDate);
        log.debug("getPosition OLD currentStartDate = " + currentStartDate + " and OLD currentEndDate = " + currentEndDate);              

        if (spaceElementWrapper == null) {
            // init start end dates
            startDate.setTime(dStart.getTime());
            endDate.setTime(dStart.getTime() + getPeriodTime());
            currentStartDate.setTime(dStart.getTime());
            currentEndDate.setTime(dStart.getTime() + getPeriodTime());

            // get orbits of all space elements (except comets) from 3DView WS at start date
            spaceElementWrapper = ServiceFactory.getPositionService().getOrbits(dStart);
            // init global lists of space elements without position
            for (Probe probe : spaceElementWrapper.getProbes()) {
                Probe p = new Probe(probe.getName());
                p.setId(probe.getId());
                p.setColor(probe.getColor());
                p.setStartDate(dStart);
                probes.add(p);
            }
            planets.clear();
            for (Planet planet : spaceElementWrapper.getPlanets()) {
                Planet p = new Planet(planet.getName());
                p.setId(planet.getId());
                p.setColor(planet.getColor());
                planets.add(p);
            }

            //log.debug("init => change");
            hasChanged.set(true);
        } else {
            boolean startDateIsChanged = false;

            if (!dStart.equals(startDate)) {
                startDate.setTime(dStart.getTime());
                endDate.setTime(dStart.getTime() + getPeriodTime());
                currentStartDate.setTime(dStart.getTime());
                currentEndDate.setTime(dStart.getTime() + getPeriodTime());

                // clear orbits comets
                if (!spaceElementWrapper.getComets().isEmpty()) {
                    spaceElementWrapper.getComets().clear();
                    comets.clear();
                }
                startDateIsChanged = true;                
            } else {
                if (d.before(currentStartDate) || d.after(currentEndDate)) {
                    // 12067 Problem with positioning satellites on the Ecliptic Plane in SEP Propagation mode.
                    if (d.after(startDate) && d.before(endDate)) {
                        // date belongs to standard period 
                        currentStartDate.setTime(startDate.getTime());
                    } else {
                        // date is out of standard period 
                        currentStartDate.setTime(d.getTime() - getPeriodTime() / 2);
                    }
                    currentEndDate.setTime(currentStartDate.getTime() + getPeriodTime());
                    startDateIsChanged = true;
                }
            }

            if (startDateIsChanged) {               
                log.debug("getPosition NEW startDate = " + startDate + " and NEW endDate = " + endDate);
                log.debug("getPosition NEW currentStartDate = " + currentStartDate + " and NEW currentEndDate = " + currentEndDate);
                
                SpaceElementWrapper elements = new SpaceElementWrapper();

                // all S/C positions have to be recomputed
                for (Probe p : spaceElementWrapper.getProbes()) {
                    p.setStartDate(currentStartDate);
                    p.setPositions(null);
                    elements.getProbes().add(p);                   
                }
                if (!elements.getProbes().isEmpty()) {
                    // get positions of some space elements from 3DView WS
                    elements = ServiceFactory.getPositionService().getOrbitElements(elements, currentStartDate);
                    if (elements != null) {
                        for (Probe p : elements.getProbes()) {
                            Probe pCur = spaceElementWrapper.getProbe(p.getId());
                            //if (pCur.getInServiceDate() == null || pCur.getStartDate().after(pCur.getInServiceDate())) {
                            // update positions of spacecraft                               
                            pCur.setPositions(p.getPositions());                           
                            //}
                        }
                    }
                }

                // update S/C list in other components 
                hasChanged.set(true);
                log.debug("S/C list has been updated => change");
            }
        }
    }

    /**
     *
     * Get available comets between 2 dates having an orbit position near from a
     * defined distance of start element.
     * <br>List of comets are updated and is notified by a change.
     *
     * @param startSource
     * @param distSource
     * @param dStart
     * @param dEnd
     */
    public static void getAvailableComets(SpaceElement startSource, long distSource, Date tStart, Date tEnd) {

        if (spaceElementWrapper != null) {
            // Get available Comets from 3DView WS between 2 dates
            SpaceElementWrapper elements = ServiceFactory.getPositionService().getAvailableComets(tStart, tEnd, startSource, distSource);

            if (elements != null) {
                // clear orbits comets
                spaceElementWrapper.getComets().clear();
                comets.clear();
                if (elements.getComets().size() > COMETS_NB_MAX) {
                    while (elements.getComets().size() > COMETS_NB_MAX) {
                        elements.getComets().remove(elements.getComets().size() - 1);
                    }
                }
                // update list of comets
                spaceElementWrapper.setComets(elements.getComets());
                // set list of comets using by IHM
                for (Comet comet : elements.getComets()) {
                    Comet c = new Comet(comet.getName());
                    c.setId(comet.getId());
                    c.setColor(comet.getColor());
                    c.setOrbTimeFiles(comet.getOrbTimeFiles());
                    comets.add(c);
                }
            }
            // notify change when comet list has changed 
            hasChanged.set(true);
            log.debug("Comet list has been updated => change");
        }
    }

    /**
     * Get orbit positions of comets from start date
     * <li>period = nb days of 1 orbit period, step duration is 2 days.
     * <br>comets orbits are getting from 3DView between start date and an end
     * date defined specifically for each comet.
     */
    public static void getCometsPositions(Date tStart) {

        if (spaceElementWrapper != null) {
            // update positions of comets
            for (Comet comet : spaceElementWrapper.getComets()) {
                SpaceElementWrapper orbitComets = new SpaceElementWrapper();
                orbitComets.getComets().add(comet);
                // Get orbit of Comet 1 by 1
                orbitComets = ServiceFactory.getPositionService().getOrbitElements(orbitComets, tStart);
                if (orbitComets != null) {
                    for (Comet c : orbitComets.getComets()) {
                        // update positions of comets
                        spaceElementWrapper.getComet(c.getId()).setPositions(c.getPositions());
                        //log.debug("Update position of comet " + c.getName());
                    }
                }
            }
        }
    }

    /**
     * Update orbital position of space elements at date d.
     * <li>Current position of each space elements at current time is set from
     * all positions getting from 3DView.
     * <li>Real coordinate at date d is an interpolation between the 2 near
     * positions of the space element orbit.
     * <br>Compute transformation to be applied to have coordinates of space
     * elements at date d in Ecliptic System Coordinates (display coordinates).
     *
     * @param d : current date
     */
    public static void updatePositions(Date d) {
        //log.debug("Update positions at date "+d);
        if (spaceElementWrapper != null) {              
            // set current date
            setCurrentDate(d);            
            // init probe cameras
            initObsCameras();          
            // update positions of space elements at time t 
            for (SpaceElement e : spaceElementWrapper.getSpaceElements()) { 
                updatePosition(e, d);
            }

            // transform position of space elements at time t in Ecliptic System Coordinates (display coordinates)  
            Planet earth = spaceElementWrapper.getPlanet("EARTH");
            // Compute angle between negative Y axis and earth position in HAE.
            // Substract this angle to have longitude in HEE (System coordinate of Ecliptic plane) 
            double phi = Math.PI / 2 + Math.atan2(earth.getY(), earth.getX());
            SpaceElement.setRotate(-phi);
            SpaceElement.setTranslateX(1d);
            SpaceElement.setTranslateY(-1d);
            SpaceElement.setTranslateZ(-1d);
            SpaceElement.setScale(CoordConverter.toEcliptic(1d));

            // reset change
            hasChanged.set(false);
        }
    }

    /**
     * Update orbital position of comets at date d.
     * <li>Current position of each space elements at current time is set from
     * all positions getting from 3DView.
     * <li>Real coordinate at date d is an interpolation between the 2 near
     * positions of the space element orbit.
     *
     * @param d : current date
     */
    public static void updateCometsPositions(Date d) {

        if (spaceElementWrapper != null) {
            for (Comet c : spaceElementWrapper.getComets()) {
                updatePosition(c, d);
            }
        }

        // reset change
        hasChanged.set(false);
    }

    /**
     * Update position of space element at date d If position is not found,
     * element defined flag is set to false
     *
     * @param e
     * @param d
     */
    private static void updatePosition(SpaceElement e, Date d) {

        e.setDefined(false);

        if (e.getPositions() != null) {
            try {

                log.debug("UpdatePosition " + e.getName() + " " + d + " step(s)=" + e.getStep() + " datasize=" + e.getDataSize() + " posSize=" + e.getPositions().size());
                // update position of space elements at time t 
                int timeStep = e.getStep();
                long timeData = (long) e.getDataSize() * timeStep;
                // compute delta step in seconds between ]-timeData, timeData[
                long dt = (d.getTime() / 1000 - e.getStartTime()) % timeData;
                if (dt < 0) {  
                    // reduce delta step scope to [0, timeData[
                    dt += timeData;
                }    
//                log.debug("--- SpaceElement e= " + e.toString());
//                log.debug("--- SpaceElement e.getStartDate()= " + e.getStartDate());
//                log.debug("--- SpaceElement e.getEndDate()= " + e.getEndDate());
//                log.debug("--- dt= " + dt);
                
                // get corresponding step index 
                int i = (int) (dt / timeStep);
                if (i == e.getPositions().size() - 1) {
                    // update current position
                    e.setPosition(e.getPositions().get(i));                                        
                } else {
                    // interpolate real value between i and i+1 positions                   
                    double di = (1.0 * dt / timeStep) - i;
                    double xi = e.getPositions().get(i).getX();
                    double yi = e.getPositions().get(i).getY();
                    double zi = e.getPositions().get(i).getZ();
                    double xj = e.getPositions().get(i + 1).getX();
                    double yj = e.getPositions().get(i + 1).getY();
                    double zj = e.getPositions().get(i + 1).getZ();
                    e.setPosition(new Position(xi + (xj - xi) * di, yi + (yj - yi) * di, zi + (zj - zi) * di));  
                }
                e.setDefined(true);
            } catch (Exception ex) {
                //ex.printStackTrace();
            }
        }
    }

    private static long getPeriodTime() {
        return PERIOD_DAYS * 86400 * 1000;
    }

    /**
     * Get cameras of an observatory defined locally
     *
     * @param obs
     * @return
     */
    private static List<Camera> getObsCameras(Probe obs) {
        List<Camera> insts = new ArrayList<Camera>();

        if (obs.getName().equals("STEREO-A")) {
//		switch (obs.getName()) {
//		case "STEREO-A":
            insts.add(new Camera("EUVI", obs.getId(), obs.getColor(), "SECCHI", 0, 0.88));
            insts.add(new Camera("COR1", obs.getId(), obs.getColor(), "SECCHI", 0.76, 0.72, true));
            insts.add(new Camera("COR2", obs.getId(), obs.getColor(), "SECCHI", 2.44, 3.68, true));
            insts.add(new Camera("HI1", obs.getId(), obs.getColor(), "SECCHI", 14, 20.2));
            insts.add(new Camera("HI2", obs.getId(), obs.getColor(), "SECCHI", 53.8, 70.4));
//			break;
        } else if (obs.getName().equals("STEREO-B")) {
//		case "STEREO-B":
            insts.add(new Camera("EUVI", obs.getId(), obs.getColor(), "SECCHI", 0, 0.86));
            insts.add(new Camera("COR1", obs.getId(), obs.getColor(), "SECCHI", 0.71, 0.68, true));
            insts.add(new Camera("COR2", obs.getId(), obs.getColor(), "SECCHI", 2.53, 3.46, true));
            insts.add(new Camera("HI1", obs.getId(), obs.getColor(), "SECCHI", 14, 20.2));
            insts.add(new Camera("HI2", obs.getId(), obs.getColor(), "SECCHI", 53.8, 70.4));
//			break;
        } else if (obs.getName().equals("SOHO")) {
//		case "SOHO":
            insts.add(new Camera("EIT", obs.getId(), obs.getColor(), "EIT", 0, 0.88));
            insts.add(new Camera("C2", obs.getId(), obs.getColor(), "LASCO", 0.76, 0.72, true));
            insts.add(new Camera("C3", obs.getId(), obs.getColor(), "LASCO", 2.44, 3.68, true));
//			break;
        } else if (obs.getName().equals("SDO")) {
//		case "SDO":
            insts.add(new Camera("AIA", obs.getId(), obs.getColor(), "AIA", 0, 0.8));
//			break;
        } else if (obs.getName().equals("PSP")) {
//		case "PSP":
            insts.add(new Camera("WISPR-IN", obs.getId(), obs.getColor(), "WISPR", 33, 40));
            insts.add(new Camera("WISPR-OUT", obs.getId(), obs.getColor(), "WISPR", 79, 58));
//			break;
        } else if (obs.getName().equals("SOLO")) {
//		case "SOLO":
            insts.add(new Camera("SOLOHI", obs.getId(), obs.getColor(), "SOLOHI", 0, 40));
            insts.add(new Camera("PHI-FDT", obs.getId(), obs.getColor(), "PHI", 0, 2));
            insts.add(new Camera("PHI-HRT", obs.getId(), obs.getColor(), "PHI", 0, 0.28));
            insts.add(new Camera("METIS-EUV", obs.getId(), obs.getColor(), "METIS", 0, 2.9));
            insts.add(new Camera("METIS-VIS", obs.getId(), obs.getColor(), "METIS", 0, 2.9));
            insts.add(new Camera("EUI-FSI", obs.getId(), obs.getColor(), "EUI", 0, 3.8));
            insts.add(new Camera("EUI-HRI", obs.getId(), obs.getColor(), "EUI", 0, 0.28));
            insts.add(new Camera("SPICE", obs.getId(), obs.getColor(), "SPICE", 0, 0.13));
            insts.add(new Camera("STIX", obs.getId(), obs.getColor(), "SPICE", 0, 1));
//			break;
        }
//		default:	
//			break;
//		}
        return insts;
    }

    /**
     *
     * @param d
     */
    private static void initObsCameras() {       
        if (probeCameras == null) {
            probeCameras = new HashMap<String, List<Camera>>();
            for (Probe obs : spaceElementWrapper.getProbes()) {
                if (obs.isObserver()) {                   
                    Instruments insts = null;
                    try {
                        insts = ServiceFactory.getPositionService().getProbeInstruments(obs.getId());
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                    if (insts != null && !insts.getCameraList().isEmpty() && obs.getId()!=-21) {
                        probeCameras.put(obs.getName(), insts.getCameraList());
                    } else {
                        // defined locally
                        List<Camera> cams = getObsCameras(obs);
                        if (!cams.isEmpty()) {
                            probeCameras.put(obs.getName(), cams);
                        }
                    }
                }
            }
        }
    }

    /**
     * Get a camera list of a spacecraft
     *
     * @return
     */
    public static List<Camera> getCameras(String observer) {
        return probeCameras.get(observer);
    }

    public static List<Planet> getPlanets() {
        return planets;
    }

    public static void setPlanets(List<Planet> planets) {
        SpaceElementCollection.planets = planets;
    }

    public static List<Probe> getProbes() {
        return probes;
    }

    public static void setProbes(List<Probe> probes) {
        SpaceElementCollection.probes = probes;
    }

    public static List<Comet> getComets() {
        return comets;
    }

    public static void setComets(List<Comet> comets) {
        SpaceElementCollection.comets = comets;
    }

    public static BooleanProperty getHasChanged() {
        return hasChanged;
    }

    /**
     * @return the currentDate
     */
    public static Date getCurrentDate() {
        return currentDate;
    }

    /**
     * @param currentDate the currentDate to set
     */
    public static void setCurrentDate(Date currentDate) {
        SpaceElementCollection.currentDate = currentDate;
    }

    /**
     * @return the currentStartDate
     */
    public static Date getCurrentStartDate() {
        return currentStartDate;
    }

}
