/* ----------------------------------------
 * Origin  : GFI INFORMATIQUE 
 * Project : CDPP Propagation Tool 
 * ----------------------------------------
 * History 
 * Version : V2.12
 * DM-ID : Redmine#10626 : 04/04/2025 : Speed profile Chart
 * Version : V2.13
 * FA-ID : Redmine#12166 : 04/04/2025 : remove double impacted bodies
 * VERSION : V2.13
 * DM-ID : redmine#12151: 12/09/2025 : KHK : Change x axis to logarithmic axis 
 * ----------------------------------------
 */
package fr.tlse.irap.pt.client.controller.tools.radial;

import com.sun.javafx.charts.Legend;
import java.net.URL;
import java.util.ResourceBundle;

import fr.tlse.irap.pt.client.component.DialogComponent;
import fr.tlse.irap.pt.client.component.IDialogComponent;
import fr.tlse.irap.pt.client.controller.common.SpaceElementCollection;
import fr.tlse.irap.pt.client.controller.tools.plot.LogarithmicAxis;
import fr.tlse.irap.pt.client.controller.tools.timetable.TimeTableController;
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.Sun;
import fr.tlse.irap.tool.radial.Radial;
import fr.tlse.irap.tool.radial.RadialEnd;
import fr.tlse.irap.utils.CoordConverter;
import java.util.ArrayList;
import java.util.List;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.concurrent.Task;
import javafx.concurrent.Worker;
import javafx.fxml.FXML;
import javafx.fxml.Initializable;
import javafx.scene.Cursor;
import javafx.scene.Node;
import javafx.scene.chart.LineChart;
import javafx.scene.chart.NumberAxis;
import javafx.scene.chart.XYChart;
import javafx.scene.control.Button;
import javafx.scene.layout.AnchorPane;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 *
 * @author frup03304
 * @version
 */
public class SpeedChartComponent extends AnchorPane implements Initializable, IDialogComponent {

    protected static final Logger logger = LoggerFactory.getLogger(TimeTableController.class);
    public static DialogComponent dialog;
    private final static int MAX_RADIUS_AU = 60;

    @FXML
    protected AnchorPane root;
    @FXML
    private Button closeBtn;
    @FXML
    //12151
    //protected LineChart speedChart;
    protected LineChart<Number, Number> speedChart;
            
    @FXML
    protected LogarithmicAxis xAxisSpeedDist;
    
    private Radial radial;
    
    final List<RadialEnd> impactedBodies = new ArrayList<>(); //contains result of bodies impact thread
    private boolean computing;

    @Override
    public void initialize(URL location, ResourceBundle resources) {

//        closeBtn.setOnMouseClicked(new EventHandler<Event>() {
//            @Override
//            public void handle(Event event) {
//                //TODO close
//            }
//        });
    }

    public void initComponentWith(Radial pRadial) {
        radial = pRadial;
        dialog.show();
        root.setStyle("-fx-background-color: white");
        //speedChart.getStylesheets().add("fr/tlse/irap/pt/client/controller/tools/externalLinks/SpeedChartComponent.css");
        //initialize chart with one series: dragLine      
        speedChart.getData().addAll(new XYChart.Series<Number, Number>());        
        //System.out.println("*** initComponentWith updatePlotFromData");
        updatePlotFromData();
    }

    public void updatePlotFromData() {
        computeDragLine();
        computeBodies();
    }

    /**
     * Update a legend item with some css style
     *
     * @param pName Name of item
     * @param pStyle Style to set
     */
    private void updatePlotLegend(String pName, String pStyle) {
        for (Node n : speedChart.getChildrenUnmodifiable()) {
            if (n instanceof Legend) {
                for (Legend.LegendItem legendItem : ((Legend) n).getItems()) {
                    //System.out.println("*** found legend Item "+legendItem.getText()+" "+((Legend) n).getId());
                    if (pName.equals(legendItem.getText())) {
                        //System.out.println("*** !!Matched legend Item "+legendItem.getText()+" !!!"+ legendItem.getSymbol().getStyle());
                        legendItem.getSymbol().setStyle(pStyle);
                    }
                }
            }
        }
    }

    /**
     * Build drag line in plot from radial
     */
    private void computeDragLine() {

        if (radial == null) {
            return;
        }

        speedChart.getData().subList(1, speedChart.getData().size()).clear();
        XYChart.Series<Number, Number> lPlotData = new XYChart.Series<Number, Number>();

        //from radius evolution, get time, then speed
        double lRadiusKm = CoordConverter.rSUN;
        double lEndRadius = CoordConverter.toKm(MAX_RADIUS_AU);

        for (double i = lRadiusKm; i < lEndRadius; i += i/10) {
            long lTime = radial.getDragTime(i);
            double lSpeed = radial.getDragSpeed(lTime);
            lPlotData.getData().add(new XYChart.Data<Number, Number>(CoordConverter.toAU(i), lSpeed));
            //System.out.println("Speedplot adding "+CoordConverter.toAU(i)+" "+lSpeed+" "+lTime);
        }
        speedChart.getData().set(0, lPlotData);
    }

    private void resizeAxis(NumberAxis axis, double tMin, double tMax) {
        axis.setLowerBound(tMin);
        axis.setUpperBound(tMax);
        //axis.setTickUnit((tMax - tMin) / NB_TICS);
    }
    //12151
    private void resizeAxis(LogarithmicAxis axis, double tMin, double tMax) {
        axis.setLowerBound(tMin);
        axis.setUpperBound(tMax);
        //axis.setTickUnit((tMax - tMin) / NB_TICS);
    }
    

    /**
     * Draw second serie on plot with result of impacted bodies computed in
     * impactedBodies.
     */
    private void updateBodiesPositions() {

        if (radial == null) {
            return;
        }

        //filter bodies to avoid doubles
        List<SpaceElement> lFilterBodies = new ArrayList<>();
        List<RadialEnd> lFilterIb = new ArrayList<>();
        
        for (RadialEnd lEnd : impactedBodies) {
            if(!lFilterBodies.contains(lEnd.getEndSource())) {
                lFilterBodies.add(lEnd.getEndSource());
                lFilterIb.add(lEnd);
            }else {
                logger.warn("Removed a double in impacted bodies: "+lEnd.getEndSource().getName());
            }
        }
        
        
        
        double lMaxDist = Double.MIN_VALUE;
        speedChart.getData().subList(1, speedChart.getData().size()).clear();
        int lIdx = 1; //start at 1, 0 is dragLine
        List<String> lStylesToUpdate = new ArrayList<>();
        speedChart.applyCss();
        //update data
        for (RadialEnd lEnd : lFilterIb) {
            long lTime = radial.getDragTime(lEnd.getDistEnd());
            lMaxDist = Math.max(lMaxDist, lEnd.getDistEnd());
            double lSpeed = radial.getDragSpeed(lTime);
            XYChart.Series<Number, Number> lPlotData = new XYChart.Series<Number, Number>();
            lPlotData.setName(lEnd.getEndSource().getName());
            //case of sun
            if(lEnd.getDistEnd()<CoordConverter.rSUN) {//if lower than sun radius set minimum at sun surface
                lEnd.setDistEnd(CoordConverter.rSUN);
                
            }
            
            //12151
            //  lPlotData.getData().add(new XYChart.Data<Number, Number>(CoordConverter.toAU(lEnd.getDistEnd()), lSpeed));
            // Math.log10( dist_AU + 1 )
            lPlotData.getData().add(new XYChart.Data<Number, Number>(CoordConverter.toAU(lEnd.getDistEnd()), lSpeed));

            
            speedChart.getData().add(lPlotData);
            //System.out.println("SpeedChart added " + CoordConverter.toAU(lEnd.getDistEnd()) + " " + lSpeed + " " + lEnd.getEndSource().getName() + " " + lEnd.getEndSource().getColor() + " " + lPlotData.getNode().getStyle());
        }

        String lCustomStyle = "";
        String lNewCssStr = "";
        //update style
        for (RadialEnd lEnd : lFilterIb) {
            final String lPlotCssLabel = ".series" + lIdx + ".chart-line-symbol";
            Node lLineSymbol = speedChart.lookup(lPlotCssLabel);
            String lNodeStyle = "-fx-background-color: " + lEnd.getEndSource().getColor() + ", " + lEnd.getEndSource().getColor() + ";";
            if (lEnd.isSun()) {
                lNodeStyle = "-fx-background-color: orange, yellow;\n"
                        + " -fx-background-insets: 0, 2;\n"
                        + " -fx-background-radius: 8px;\n"
                        + " -fx-padding: 8px;";
            } else if (lEnd.getEndSource() instanceof Planet) {
                lNodeStyle = "-fx-background-color: " + lEnd.getEndSource().getColor() + ", " + lEnd.getEndSource().getColor() + ";";
            } else if (lEnd.getEndSource() instanceof Probe) {
                lNodeStyle = "-fx-background-color: " + lEnd.getEndSource().getColor() + ", " + lEnd.getEndSource().getColor() + ";\n"
                        + " -fx-background-insets: 0;\n"
                        + " -fx-background-radius: 0;\n"
                        + " -fx-padding: 7px 7px 7px 7px;\n"
                        + " -fx-shape: \"M2,0 L6,5 L10,0 L12,0 L12,2 L7,6 L12,10 L12,12 L10,12 L6,7 L2,\n"
                        + "    12 L0,12 L0,10 L5,6 L0,2 L0,0 Z\"";
            }
            if (lLineSymbol != null) {
                lStylesToUpdate.add(lNodeStyle);
                lLineSymbol.setStyle(lNodeStyle);
                //System.out.println("*** Adding style to symbol " + lEnd.getEndSource().getName() + " " + lPlotCssLabel + " " + lEnd.getEndSource().getColor());
            }
            lIdx++;

        }
        //when all data added, change legend
        lIdx = 0;
        for (RadialEnd lEnd : lFilterIb) {
            if (lStylesToUpdate.size() > lIdx) {
                updatePlotLegend(lEnd.getEndSource().getName(), lStylesToUpdate.get(lIdx++));
            }
        }
        //12151
        //resizeAxis((NumberAxis) speedChart.getXAxis(), 0, Math.ceil(CoordConverter.toAU(lMaxDist)));
        double lXMin = CoordConverter.toAU(CoordConverter.rSUN);
        double lXMax = Math.ceil(CoordConverter.toAU(lMaxDist) +1 );
        System.out.println("************** resize axis: min="+lXMin+" max="+lXMax);
        resizeAxis((LogarithmicAxis) speedChart.getXAxis(), lXMin, lXMax);
        
//resizeAxis((LogarithmicAxis) speedChart.getXAxis(), 0, Math.ceil(CoordConverter.toAU(lMaxDist)));
    }

    /**
     * Fill timetable process launched in a dedicated thread
     */
    private void computeBodies() {
        Task<Object> worker = computeBodiesWorker();
        computing=true;
        worker.stateProperty().addListener(new ChangeListener<Worker.State>() {
            @Override
            public void changed(ObservableValue<? extends Worker.State> observableValue, Worker.State oldState, Worker.State newState) {
                //System.out.println("*** Thread computeBodies "+newState.toString());
                if (newState == Worker.State.SUCCEEDED || newState == Worker.State.FAILED || newState == Worker.State.CANCELLED) {
                    //update bodies position on chart
                    updateBodiesPositions();
                    endCursor();
                    computing=false;
                }
            }
        });

        Thread lComputeBodiesThread = new Thread(worker);
        lComputeBodiesThread.start();

        waitCursor();
    }

    /**
     * Fill timetable process launched in a dedicated thread
     */
    private synchronized Task<Object> computeBodiesWorker() {
        return new Task<Object>() {
            @Override
            protected Object call() throws Exception {
                synchronized (impactedBodies) {
                    // Initialize
                    impactedBodies.clear();
                    //initTimeTable();

                    if (SpaceElementCollection.getSpaceElementWrapper() != null) {
                        logger.info("Compute propagation for SUN");
                        try {
                            RadialEnd rEnd = ServiceFactory.getToolService().computeRadial(radial, new Sun());
                            //fillTimeTable(getRadialProperty(rEnd));
                            if (rEnd.isImpacted()) {
                                impactedBodies.add(rEnd);
                                rEnd.getEndSource().setColor("#FFEF00");//yellow
                            }
                        } catch (Exception e) {
                            logger.debug("Sun has not been computed");
                        }
                        logger.info("Compute propagation for probes");
                        //fillTimeTable(new RadialProperty("Probes"));
                        for (Probe probe : SpaceElementCollection.getSpaceElementWrapper().getProbes()) {
                            if (probe.isDefined()) {
                                try {
                                    RadialEnd rEnd = ServiceFactory.getToolService().computeRadial(radial, probe);
                                    //fillTimeTable(getRadialProperty(rEnd));
                                    if (rEnd.isImpacted()) {
                                        impactedBodies.add(rEnd);
                                        System.out.println("**** added impacted probe "+rEnd.getEndSource().getName());
                                    }
                                } catch (Exception e) {
                                    logger.debug("Probe has not been computed : " + probe.getName());
                                }
                            }
                        }
                        logger.info("Compute propagation for planets");
                        //fillTimeTable(new RadialProperty("Planets"));
                        for (Planet planet : SpaceElementCollection.getPlanets()) {
                            try {
                                //System.out.println("**** compute radial planet "+planet.getName());
                                RadialEnd rEnd = ServiceFactory.getToolService().computeRadial(radial, planet);
                                //fillTimeTable(getRadialProperty(rEnd));
                                if (rEnd.isImpacted()) {
                                    impactedBodies.add(rEnd);
                                    //System.out.println("**** added impacted planet "+rEnd.getEndSource().getName());
                                }
                            } catch (Exception e) {
                                logger.debug("Planet has not been computed : " + planet.getName());
                            }
                        }
                        logger.info("Compute propagation for comets");
                        //fillTimeTable(new RadialProperty("Comets"));
                        for (Comet comet : SpaceElementCollection.getComets()) {
                            try {
                                RadialEnd rEnd = ServiceFactory.getToolService().computeRadial(radial, comet);
                                if (rEnd.isImpacted()) {
                                    impactedBodies.add(rEnd);
                                    System.out.println("**** added impacted comet "+rEnd.getEndSource().getName());
                                }
                            } catch (Exception e) {
                                logger.debug("Comet has not been computed : " + comet.getName());
                            }
                        }
                    }
                    return true;
                }
            }
        };
    }

    @Override
    public void setDialog(DialogComponent dlg) {
        dialog = dlg;
    }

    public DialogComponent getDialog() {
        return dialog;
    }

    private void waitCursor() {
        this.setDisable(true);
        this.getScene().setCursor(Cursor.WAIT);
    }

    private void endCursor() {
        this.setDisable(false);
        this.getScene().setCursor(null);
    }

    /**
     * @return the radial
     */
    public Radial getRadial() {
        return radial;
    }

    /**
     * @param radial the radial to set
     */
    public void setRadial(Radial radial) {
        this.radial = radial;
    }

    /**
     * @return the computing
     */
    public boolean isComputing() {
        return computing;
    }

}
