/* ----------------------------------------
 * Origin  : GFI INFORMATIQUE 
 * Project : CDPP Propagation Tool 
 * ----------------------------------------
 * History 
 * Version : 01/05/2015 : SCA : V2.1
 * Version : 24/03/2016 : SCA : V2.2 (PSWS-H2020)
 * Add comets
 * Version : 30/11/2023 : LBE : avoid triggering changes before init
 * Version : 05/08/2024 : LBE : V2.11
 * DM-ID : Redmine#l0763 : 05/08/2024 : PFSS grid lines
 * VERSION : V2.12
 * DM-ID : redmine#11799 : 08/04/2025 : KHK : PFSS mode – Br Factor
 * Version : V2.13
 * DM-ID : redmine#12279 : 10/09/2025 : LBE : Add field lines in 3d panel
 * ----------------------------------------
 */
package fr.tlse.irap.pt.client.controller.tools.sep;

import java.util.Date;

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

import fr.tlse.irap.pt.client.component.CustomComponent;
import fr.tlse.irap.pt.client.component.ICustomComponent;
import fr.tlse.irap.pt.client.controller.common.SpaceElementCollection;
import fr.tlse.irap.pt.client.controller.dialogbox.DialogBox;
import fr.tlse.irap.pt.client.controller.drawingPlane.DrawHelper.DrawingCmeType;
import fr.tlse.irap.pt.client.controller.eclipticPlane.EclipticPlaneController;
import fr.tlse.irap.pt.client.controller.timestamp.TimestampController;
import fr.tlse.irap.pt.client.controller.tools.IToolController.JMapTool;
import fr.tlse.irap.pt.client.controller.tools.IToolController.Tool;
import fr.tlse.irap.pt.client.controller.tools.carrington.CarringtonDashboardController;
import fr.tlse.irap.pt.client.controller.tools.plot.FluxPlotComponent;
import fr.tlse.irap.pt.client.controller.tools.plot.SpectroPlotComponent;
import fr.tlse.irap.pt.client.controller.tools.plot.VPlotComponent;
import fr.tlse.irap.fieldLine.FieldLine;
import fr.tlse.irap.fieldLine.FieldLineWrapper;
import fr.tlse.irap.pos.Position;
import fr.tlse.irap.pos.PositionWrapper;
import fr.tlse.irap.pt.client.services.ServiceFactory;
import fr.tlse.irap.spaceElement.Comet;
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.spaceElement.Sun;
import fr.tlse.irap.tool.sep.Sep;
import fr.tlse.irap.tool.sep.SepEnd;
import fr.tlse.irap.utils.CoordConverter;
import java.util.ArrayList;
import java.util.List;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.concurrent.Task;
import javafx.concurrent.Worker;
import javafx.fxml.FXML;
import javafx.scene.Cursor;
import javafx.scene.layout.GridPane;

public class SepDashboardController extends GridPane implements ICustomComponent {

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

    private BooleanProperty hasChanged = new SimpleBooleanProperty();
    private ObjectProperty<Date> startDate;

    public Sep sep = new Sep();
    public SepEnd sepEnd = new SepEnd();

    @FXML
    protected SepDashboardController rootSepDashboard;
    @FXML
    protected CarringtonDashboardController carringtonDashboard;
    @FXML
    protected VPlotComponent vPlotComponent;
    @FXML
    protected FluxPlotComponent fluxPlotComponent;
    @FXML
    protected SpectroPlotComponent spectroPlotComponent;
    @FXML
    public SepStartEndController sepStartEndController;

    protected EclipticPlaneController eclipticPlaneComponent;
    protected TimestampController timestampComponent;

    private int NB_STEP = 100;
    private Date resultDate = new Date();
    
    //11799  Get Br factor in PFSS mode (checkbox) and Start = SUN 
    private double brFactor = 2; // 2 by default 

    public SepDashboardController() {
        super();
        CustomComponent.initCustomComponent(this);

        // Change start field listener
        sepStartEndController.getHasChanged().addListener(
                new ChangeListener<Boolean>() {
            @Override
            public void changed(ObservableValue<? extends Boolean> observedBoolean, Boolean oldValue, Boolean newValue) {
                if (newValue) {
                    if (sep.getStartElement() != null) {
                        // update SEP model with start fields model
                        sepStartEndController.getStartModel().update(sep);
                        // update carrington view
                        updateCarringtonFromSep();
                        // update start fields
                        updateStartView();
                    }
                }
            }

        });

        // Change Carrington CME listener
        carringtonDashboard.getHasChanged().addListener(
                new ChangeListener<Boolean>() {
            @Override
            public void changed(ObservableValue<? extends Boolean> observedBoolean, Boolean oldValue, Boolean newValue) {
                if (!newValue) {
                    // update Carrington CME 
                    carringtonDashboard.updateCME();

                    if (carringtonDashboard.isValidCME()) {
                        // update Sep Model
                        updateSepFromCarrington();
                        // update carrington view 
                        carringtonDashboard.updateView(sep, sepEnd);
                        // update start fields
                        updateStartView();
                    }
                }
            }

        });

        // Change Carrington Fieldlines listener
        carringtonDashboard.getHasFieldLinesChanged().addListener(new ChangeListener<Boolean>() {
            @Override
            public void changed(ObservableValue<? extends Boolean> observedBoolean, Boolean oldValue, Boolean newValue) {
                if (!newValue) {
                    log.debug("***SepDashboardCtrl: CarringtonDashBoard Field lines changed");
                    eclipticPlaneComponent.updateFieldLinesWith(carringtonDashboard.getFieldLines(),
                            sep.getSunAttitudeT());
                    //11799
                    carringtonDashboard.setSepDashboardController(rootSepDashboard);
                }
            }
        });

        // Change Carrington GridFieldlines listener
        carringtonDashboard.getHasGridFieldLinesChanged().addListener(new ChangeListener<Boolean>() {
            @Override
            public void changed(ObservableValue<? extends Boolean> observedBoolean, Boolean oldValue, Boolean newValue) {
                if (!newValue) {
                    log.debug("***SepDashboardCtrl: CarringtonDashBoard Field lines changed");
                    eclipticPlaneComponent.updateGridFieldLinesWith(
                            carringtonDashboard.getGridFieldLines().getFieldLines(),
                            carringtonDashboard.getGridFieldLines().getFlTimes()
                    );
                }
            }
        });
        
        
        // Change VPlot listener
        vPlotComponent.getHasChanged().addListener(
                new ChangeListener<Boolean>() {
            @Override
            public void changed(ObservableValue<? extends Boolean> observedBoolean, Boolean oldValue, Boolean newValue) {
                if (!newValue) {
                    if (vPlotComponent.getVPlotModel().isValid()) {
                        // update Sep Model from Vplot
                        sep.setSpeedSolarWind(vPlotComponent.getVPlotModel().getSpeed());
                        // update start date 
                        startDate.set(vPlotComponent.getVPlotModel().getDate());
                    } else {
                        sep.setSpeedSolarWind(Sep.DEFAULT_SPEED);
                    }
                    initSepStart();
                }
            }
        });
        // Change SpectroPlot listener
        spectroPlotComponent.getHasChanged().addListener(
                new ChangeListener<Boolean>() {
            @Override
            public void changed(ObservableValue<? extends Boolean> observedBoolean, Boolean oldValue, Boolean newValue) {
                if (!newValue) {
                    if (spectroPlotComponent.getSpectroPlotModel().isValid()) {
                        // update start date 
                        startDate.set(spectroPlotComponent.getSpectroPlotModel().getDate());
                    }
                    initSepStart();
                }
            }
        });
    }

    /**
     * Dynamic component initialization
     */
    public void initComponentWith(EclipticPlaneController eclipticPlaneComponent, TimestampController timestampComponent) {

        if (this.startDate == null) {

            this.timestampComponent = timestampComponent;
            this.eclipticPlaneComponent = eclipticPlaneComponent;

            // Init dynamic part of components
            timestampComponent.initTimeScrollBar(0, NB_STEP);
            carringtonDashboard.initComponent(DrawingCmeType.SPIRAL);
            carringtonDashboard.setEndTime(sepEnd.getEndTime().getTime());

            // bind start date properties
            bindStartDate();
            // bind start/end properties
            bindStartEndSource();
            // bind particle properties
            bindParticle();
            // bind time slider properties
            this.timestampComponent.getResultTScrollBar().valueProperty().addListener(new ChangeListener<Number>() {
                @Override
                public void changed(ObservableValue<? extends Number> arg0, Number oldValue, Number newValue) {
                    updateResultTime(newValue.intValue());
                    carringtonDashboard.setCurrentTime(resultDate);
                    eclipticPlaneComponent.setCurrentTime(resultDate);
                }
            });
        }
        eclipticPlaneComponent.updateFieldLinesWith(carringtonDashboard.getFieldLines(),
                sep.getSunAttitudeT());
    }

    /**
     * Bind start date Listen to start date changes
     */
    private void bindStartDate() {
        // bind Radial start time to startDate
        sepStartEndController.getStartModel().getStartTime().bind(timestampComponent.getStartTimeProperty());
        // bind Radial current date to timestamp selected date
        this.startDate = timestampComponent.selectedDateProperty();
        if (startDate.get() != null) {
            // Send startDate to controller which need it
            carringtonDashboard.setStartDate(startDate);
            vPlotComponent.setStartDate(startDate);
            fluxPlotComponent.setStartDate(startDate);
            spectroPlotComponent.setStartDate(startDate);
        }
    }

    /**
     * Bind start/end source Listen to start source changes
     */
    private void bindStartEndSource() {
        // Set startSource to controller which need it
        vPlotComponent.setStartSource(sepStartEndController.getStartModel().getStartSource());
        spectroPlotComponent.setStartSource(sepStartEndController.getStartModel().getStartSource());
        // Set endSource to controller which need it
        carringtonDashboard.setEndSource(sepStartEndController.getEndSource(), sepStartEndController.getEndVisible());
        // init start/end source menu
        sepStartEndController.loadMenu();
        // Listen for space elements list change
        SpaceElementCollection.getHasChanged().addListener(new ChangeListener<Boolean>() {
            @Override
            public void changed(ObservableValue<? extends Boolean> observedBoolean, Boolean oldValue, Boolean newValue) {
                if (!newValue) {
                    // init start/end source menu
                    sepStartEndController.loadMenu();
                    // notify change for refreshing timetable
                    notifyChanged();
                }
            }
        });

        // Listen for start source change
        sepStartEndController.getStartModel().getStartSource().addListener(new ChangeListener<SpaceElement>() {
            @Override
            public void changed(ObservableValue<? extends SpaceElement> observable, SpaceElement oldValue, SpaceElement newValue) {
                sep.setStartElement(newValue);
            }
        });
        // first init
        sep.setStartElement(new Sun());
    }

    /**
     * Bind particle Listen to particle changes
     */
    private void bindParticle() {
        // Set startSource to controller which need it
        spectroPlotComponent.setParticle(sepStartEndController.getStartModel().getParticle());
    }

    /**
     * Configure views according to JMap Tool
     *
     * @param jMapTool
     */
    public void setJMapTool(JMapTool jMapTool) {

        sepStartEndController.setJMapTool(jMapTool);
        carringtonDashboard.setJMapTool(jMapTool);
    }

    /**
     * Initialize Sep Start fields (set default values and compute HAE central
     * axis)
     */
    public void initSepStart() {
        final Task<Object> initSepStartWorker = initSepStartWorker();

        initSepStartWorker.stateProperty().addListener(
                new ChangeListener<Worker.State>() {
            @Override
            public void changed(
                    ObservableValue<? extends Worker.State> observableValue,
                    Worker.State oldState, Worker.State newState) {

                if (newState == Worker.State.SUCCEEDED) {
                    // thread is done with success
                    endCursor();
                    // update carrington view
                    carringtonDashboard.updateView(sep, sepEnd);
                    // update start fields
                    updateStartView();

                } else if (newState == Worker.State.FAILED || newState == Worker.State.CANCELLED) {
                    // thread is failed or cancelled
                    endCursor();
                    String source = sepStartEndController.getStartModel().getStartSource().get().getName();
                    sepStartEndController.runStartPoint(new Sun());
                    DialogBox.warn("Position of " + source + " at start time has not be found.\nDefault start has been reset to SUN");
                    initSepStartWorker.getException().printStackTrace();
                }
            }

        });

        new Thread(initSepStartWorker).start();
        waitCursor();
    }

    private Task<Object> initSepStartWorker() {
        return new Task<Object>() {
            @Override
            protected Object call() throws Exception {
                // compute SEP start
                sepStartEndController.getStartModel().compute(sep, startDate.get());

                return true;
            }
        };
    }

    /**
     * Compute SEP end fields + impact
     *
     * @return
     * @throws Exception
     */
    public boolean computeSep() throws Exception {
        sepEnd = sepStartEndController.compute(sep);
        log.debug("SepDashboardController.computeSep() sepEnd:"
                + sepEnd.getEndSource().getName());
        return true;
    }

    /**
     * Update start view and notify the current change
     */
    public void updateStartView() {

        sepStartEndController.getStartModel().setUpViewWith(sep);
        notifyChanged();

    }

    /**
     * Update view from end model SEP
     */
    public void updateView() {
        if (sep.getEndElement() != null) {
            sepStartEndController.setUpViewWith(sepEnd);
        } else {
            sepStartEndController.clearView();
        }
    }

    /**
     * Update Ecliptic view and result time from SEP model and current step
     */
    public void updateEcliptic() {
        // update only the active tool
        if (eclipticPlaneComponent.getTool() == Tool.SEP) {
            // set Start/End source
            eclipticPlaneComponent.setStartSource(sep.getStartElement());
            eclipticPlaneComponent.setEndSource(sepEnd.getEndSource());
            // update result time 
            int step = (int) this.timestampComponent.getResultTScrollBar().valueProperty().get();
            updateResultTime(step);
            // update result date => ask ecliptic to be refreshed
            timestampComponent.updateResultDate();
            // enable slider
            timestampComponent.getResultTScrollBar().setDisable(false);

            eclipticPlaneComponent.updateFieldLinesWith(carringtonDashboard.getFieldLines(),
                    sep.getSunAttitudeT());
            carringtonDashboard.setEndTime(sepEnd.getEndTime().getTime());

        }
    }

    /**
     * Update result time from a step and redraw spiral with its new footpoint
     * longitude
     *
     * @param step
     */
    private void updateResultTime(int step) {
        if (eclipticPlaneComponent.getTool() == Tool.SEP) {
            // update result time
            setResultTime(step);
            // update ecliptic
            double footpointLong = sep.getLongSourceT() + ((sepEnd.getDeltaLong() / NB_STEP) * step);
            eclipticPlaneComponent.drawSpiral(
                    footpointLong,
                    sep.getRotationPeriod(),
                    sep.getSpeedSolarWind(),
                    sep.getSpreadCIR());
        }
//        carringtonDashboard.setTime(resultDate);
//        carringtonDashboard.setEndTime(sepEnd.getEndTime().getTime());
    }

    /**
     * Set result time from step between start time and end time
     *
     * @param step
     */
    private void setResultTime(int step) {
        resultDate.setTime(getResultTime(step));
        timestampComponent.setResultTime(resultDate);
    }

    /**
     * Get result time from step between start time and end time
     *
     * @param step
     * @return
     */
    private long getResultTime(int step) {
        long startTime = startDate.get().getTime();
        long endTime = sepEnd.getEndTime().getTimeInMillis();
        //if end = Sun, end time before start, reverse time slider
        if (endTime < startTime) {
            step = NB_STEP - step;
        }
        long newTime = startTime + ((endTime - startTime) / NB_STEP) * step;
        return newTime;
    }

    /**
     * Update Sep Model from Carrington CME
     */
    private void updateSepFromCarrington() {        
        Double longitude = carringtonDashboard.getLongSource();
        if (longitude != null) {
            sep.setLongSource(longitude);
        }
        longitude = carringtonDashboard.getLongExtent();
        if (longitude != null) {
            //11799 brFactor            
            //sep.setSpreadCIR(longitude / 2);
            // In Carrington dashboard, 
            // In PFSS mode Extend = Spread / brFactor
            // by default Extend = Spread * 2
            sep.setSpreadCIR(longitude / brFactor);
        }
        // compute HAE long start from Carrington source
        sepStartEndController.getStartModel().computeFromSource(sep);
    }

    /**
     * Update Carrington CME from Sep Model
     */
    public void updateCarringtonFromSep() {
        // compute Carrington source 
        sepStartEndController.getStartModel().computeSource(sep);
        // update carrington view 
        carringtonDashboard.updateView(sep, sepEnd);
    }

    public BooleanProperty getHasChanged() {
        return hasChanged;
    }

    public void setHasChanged(BooleanProperty hasChanged) {
        this.hasChanged = hasChanged;
    }

    public void notifyChanged() {
        getHasChanged().set(true);
        getHasChanged().set(false);
    }

    public ObjectProperty<Date> getStartDate() {
        return startDate;
    }

    public void waitCursor() {
        sepStartEndController.setDisable(true);
        carringtonDashboard.setDisable(true);
        vPlotComponent.setDisable(true);
        spectroPlotComponent.setDisable(true);
        rootSepDashboard.setCursor(Cursor.WAIT);
    }

    public void endCursor() {
        sepStartEndController.setDisable(false);
        carringtonDashboard.setDisable(false);
        vPlotComponent.setDisable(false);
        spectroPlotComponent.setDisable(false);
        rootSepDashboard.setCursor(null);
    }
    
    
    
    //11799
    public void updatingWithBrFactor() {        
        //11799 - Get Br factor in PFSS mode (checkbox) and Start = SUN 
        if ((sepStartEndController.getStartModel().getStartSource().get().getId() == 10)) {
            if (carringtonDashboard.isPfssMode()) {
                log.debug("--- Sep Propagation - PFSS mode ENABLE and Start = SUN ");
                brFactor = getBrFactor();
            } else {
                log.debug("--- Sep Propagation - PFSS mode DISABLE and Start = SUN ");
                // Disable processing with brFactor => by defaut is set to "2"
                brFactor = 2; // 2
            }
            carringtonDashboard.setBrFactor_SEP(brFactor);
            //log.debug("--- Sep Propagation - brFactor:" + brFactor);
            // Notify change
            log.debug("Triggering on Spread field to update all application (finally calling SepController.computeSep()).");
            sepStartEndController.getHasFieldChanged().set(true);
            sepStartEndController.getHasFieldChanged().set(false);
        }   
    }
    
    
    
    public double getBrFactor() {

        double brFactorTmp = 0;
        ArrayList<PositionWrapper> lStartPts = new ArrayList<>();
        ArrayList<Date> lTimes = new ArrayList<>();
        // Rs : 696 340km
        double Rs = 696340;

        Date lDateRequest = startDate.get();
        log.debug("--- SEP Propagation - BrFactor on date :" + lDateRequest);

        // By default END is set to Earth
        log.debug("--- START:" + sepStartEndController.getStartModel().getStartSource().get().getName());
        // END : do not used corotationStartEndController.getEndSource(), his position is not set
        Position lEndPos = sepEnd.getEndSource().getPosition();
        double lP[] = CoordConverter.toSpheric(lEndPos.getX(), lEndPos.getY(), lEndPos.getZ());
        log.debug("--- END :" + sepEnd.getEndSource().getName() + " - Position=(" + lEndPos.getX() + "/" + lEndPos.getY() + "/" + lEndPos.getZ() );
        log.debug("--- Long=" + lP[0] + " Lat=" + lP[1] + " length=" + lEndPos.length()
                + "(" + lEndPos.length() / Rs + " Rs)");

        //get input points from selected objects
        SpaceElementWrapper lstStartPoints = new SpaceElementWrapper();
        if (sepEnd.getEndSource() instanceof Probe) {
            lstStartPoints.getProbes().add((Probe) sepEnd.getEndSource());
        } else if (sepEnd.getEndSource() instanceof Planet) {
            lstStartPoints.getPlanets().add((Planet) sepEnd.getEndSource());
        } else if (sepEnd.getEndSource() instanceof Comet) {
            lstStartPoints.getComets().add((Comet) sepEnd.getEndSource());
        } else {
            log.debug("--- Unkown element END:" + sepEnd.getEndSource().getName());
        }

        // START : SUN
        SpaceElement lSunWithAtt = ServiceFactory.getPositionService().getPositionElement(
                sepStartEndController.getStartModel().getStartSource().get(), startDate.get());
        FieldLineWrapper lFls = ServiceFactory.getFieldLineService().getPSPFSSFieldLine(
                lstStartPoints, startDate.get(), sep.getSpeedSolarWind(), lSunWithAtt.getAttitude());

        List<FieldLine> fieldLines = lFls.getFieldLines();

        log.debug("Nb fieldLines returned:" + fieldLines.size());

        if (fieldLines.size() > 0) {

            // Data found
            // one date request, one line expected
            for (int i = 0; i < fieldLines.size(); i++) {
                FieldLine fieldLine = fieldLines.get(i);
                log.debug("Nb of points :" + fieldLine.getPoints().size());
//                for (int j = 0; j < fieldLine.getPoints().size(); j++) {
//                    FieldPoint fPoint = fieldLine.getPoints().get(j);
//
//                    System.out.println("---  point :" + j 
//                        + " - x,y,z :" + fPoint.getX()+ " , " + fPoint.getY()+ " , " + fPoint.getZ()+
//                        " - module (Km) :" + fPoint.length()+" - :" + fPoint.length()/Rs+" Rs");               
//                    System.out.println("---   point :" + j
//                            + " - Bx,By,Bz :" + fPoint.getBx() + " , " + fPoint.getBy() + " , " + fPoint.getBz() + " - Br :" + fPoint.Br());
//                }

                // get points at 1Rs and 2.5Rs                
                double Br1Rs, Br2_5Rs;

//                log.debug("--- Index PFSS = " + fieldLine.getPfssIdx());
//                log.debug("--- at Index PFSS Distance (Rs) = " + fieldLine.getPoints().get(fieldLine.getPfssIdx() + 1).length() / Rs);
//                log.debug("--- at surface Distance (Rs) = " + fieldLine.getPoints().get(fieldLine.getPoints().size() - 1).length() / Rs);
                // 2.5Rs : Index PFSS - Index in list of field points where parker points end and pfss starts.                
                if ((fieldLine.getPoints().get(fieldLine.getPfssIdx() + 1).length() / Rs)
                        < fieldLine.getPoints().get(fieldLine.getPoints().size() - 1).length() / Rs) {
                    // case 1 : Data is ordered from 1Rs to 2.5Rs
                    Br1Rs = fieldLine.getPoints().get(fieldLine.getPfssIdx() + 1).Br();
                    Br2_5Rs = fieldLine.getPoints().get(fieldLine.getPoints().size() - 1).Br();
                } else {
                    // case 2 : Data is ordered from 2.5Rs to 1Rs
                    Br1Rs = fieldLine.getPoints().get(fieldLine.getPoints().size() - 1).Br();
                    Br2_5Rs = fieldLine.getPoints().get(fieldLine.getPfssIdx() + 1).Br();
                }
                log.debug("--- Br1Rs = " + Br1Rs + " , Br2_5Rs = " + Br2_5Rs);

                // Br(r=1Rs) / (2.5*2.5 * Br(r=2.5Rs)))
                brFactorTmp = Br1Rs / (2.5 * 2.5 * Br2_5Rs);
                log.debug("--- BrFactor = Br1Rs / ((2.5 * 2.5) * Br2.5Rs) = " + brFactorTmp);

                // In Carrington dashboard, 
                // In PFSS mode 
                //      Extent = Spread * 2  / brFactor
                //      Spread = Extent * brFactor / 2
                // by default 
                //      Extent = Spread * 2
                //      Spread = Extent / 2
                brFactorTmp = 2 * (1 / brFactorTmp);
            }
        } else {
            // no cube found
            // Set Br factor to default value :2
            log.debug("--- No PFSS data found for this date:" + lDateRequest);
            log.debug("--- BrFactor = 2 ");
            DialogBox.warn("No PFSS data found for SEP Propagation!");
            brFactorTmp = 2;
        }
        return brFactorTmp;
    }
    
       
}
