/* ----------------------------------------
 * Origin  : Akkodis 
 * Project : CDPP Propagation Tool 
 * ----------------------------------------
 * History 
 * Version : 07/01/2022 : LBE : V2.4
 * DM-ID : Redmine#9098 : 07/01/2022 : creation
 * Version : 25/11/2022 : LBE : V2.6
 * DM-ID : Redmine#10630 : AMDA zoom type
 * Version : 12/09/2023 : LBE : V2.8
 * DM-ID : Redmine#10629 : 12/09/2023 : SEP plot improvments
 * Version : 25/11/2024 : LBE : V2.11
 * DM-ID : Redmine#11104 : 25/11/2024 : VDA module added
 * Version : V2.12
 * DM-ID : Redmine#11103 : 25/03/2025 : LBE : Radio spectro interface added
 * ----------------------------------------
 */
package fr.tlse.irap.pt.client.controller.tools.plot;

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

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.RootController;
import fr.tlse.irap.pt.client.controller.common.Formatter;
import fr.tlse.irap.pt.client.controller.common.ISelectedItem;
import fr.tlse.irap.pt.client.controller.common.Item;
import fr.tlse.irap.pt.client.controller.common.MapMenu;
import fr.tlse.irap.pt.client.controller.common.constant.TOOLTIP;
import fr.tlse.irap.pt.client.controller.dialogbox.DialogBox;
import fr.tlse.irap.pt.client.controller.tools.common.MapLegend;
import fr.tlse.irap.pt.client.services.ServiceFactory;
import fr.tlse.irap.spaceElement.SpaceElement;
import fr.tlse.irap.spectro.Spectro;
import fr.tlse.irap.spectro.SpectroWrapper;
import fr.tlse.irap.pt.client.controller.tools.color.IColorTable;
import fr.tlse.irap.pt.client.controller.tools.color.UcarColorTable;
import fr.tlse.irap.pt.client.controller.tools.color.UcarColorTableType;
import fr.tlse.irap.pt.client.controller.tools.jmap.ClickPointsFileHandler;
import static fr.tlse.irap.pt.client.controller.tools.plot.VPlotItemBuilder.getCatalogTypes;
import fr.tlse.irap.fit.FitPoint;
import fr.tlse.irap.tool.sep.Sep.Particle;
import fr.tlse.irap.vplot.DateRange;
import fr.tlse.irap.vplot.DateRangeWrapper;
import java.awt.image.BufferedImage;
import java.io.File;
import java.util.Optional;
import javafx.beans.InvalidationListener;
import javafx.beans.Observable;
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.embed.swing.SwingFXUtils;
import javafx.event.EventHandler;
import javafx.fxml.FXML;
import javafx.geometry.Insets;
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.chart.XYChart.Data;
import javafx.scene.chart.XYChart.Series;
import javafx.scene.control.ButtonBar.ButtonData;
import javafx.scene.control.ButtonType;
import javafx.scene.control.Dialog;
import javafx.scene.control.Menu;
import javafx.scene.control.MenuItem;
import javafx.scene.control.ScrollPane;
import javafx.scene.control.TextField;
import javafx.scene.control.Tooltip;
import javafx.scene.image.ImageView;
import javafx.scene.input.MouseButton;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.GridPane;
import javafx.scene.layout.Pane;
import javafx.scene.paint.Color;
import javafx.scene.shape.Line;
import javafx.stage.FileChooser;
import javafx.util.Pair;

public class SpectroPlotComponent extends GridPane implements ICustomComponent, ISpectroPlotMenu {

    private static final Logger log = LoggerFactory.getLogger(SpectroPlotComponent.class);
    /**
     * Speed of light in m/s
     */
    private final static double C_MS = 299792458;
    /**
     * Astronomic units in meters
     */
    private final static double AU = 149597870700.0;

    private SpectroPlotModel spectroPlotModel = new SpectroPlotModel();
    private BooleanProperty hasChanged = new SimpleBooleanProperty();

    /**
     * Listened value from SepDashBoardComponent
     */
    private ObjectProperty<Date> startDate;
    /**
     * Listened value from SepDashBoardComponent
     */
    private SpaceElement startElement;
    /**
     * Listened value from SepDashBoardComponent
     */
    private Particle particle = Particle.proton; //default init for corotation and radial

    /**
     * List of displayed energies (contains indexes of ranges displayed)
     */
    //private List<Integer> curShownFlux = new ArrayList<Integer>();
    private List<Color> colorSeries = Arrays.asList(Color.BLUE, Color.RED, Color.VIOLET, Color.YELLOW, Color.PINK, Color.ORANGE, Color.GREEN,
            Color.GRAY, Color.BLACK, Color.CYAN, Color.CHOCOLATE, Color.BEIGE, Color.BLUEVIOLET, Color.DARKORANGE, Color.FUCHSIA, Color.GOLD);
    private SpectroWrapper fluxList = new SpectroWrapper();
    private List<DateRangeWrapper> menuDateRangesWrappers;
    /**
     * To record click to fit
     */
    //private FitParameterComponent fitParameterComponent;

    /* Selection properties */
    MenuItem savePointsItem, removePointItem, fitPointsItem;
    FitPoint selectedP = new FitPoint();

    boolean isClickPointsInProgress = false;
    /** Contains Slope/intercept result in m/s and seconds from 1970 */
    double currFitResult[] = null;

    /**
     * Series defined for the fitChart - [0]: click points - [1]: fit points
     * computed by click
     */
    Series<Number, Number> seriesClick = new Series<Number, Number>();
    Series<Number, Number> seriesFit = new Series<Number, Number>();

    /**
     * current color table
     */
    private IColorTable colorTable = null;

    @FXML
    protected GridPane root;
    @FXML
    protected BorderPane pane;
    //@FXML
    //protected Pane spectraPane;
    @FXML
    protected Pane nrjPane;
    @FXML
    protected LineChart<Number, Number> spectraChart;
    @FXML
    protected LineChart<Number, Number> vdaChart;
    @FXML
    protected LineChart<Number, Number> nrjChart;
    @FXML
    protected LineChart<Number, Number> nrjDateRangeChart;
//    @FXML
//    protected LineChart<Number, Number> spDateRangeChart;
    @FXML
    protected ImageView spectraImg;
    @FXML
    protected ScrollPane nrjItems;
    @FXML
    protected TextField legend;
    
    @FXML
    protected TextField fitResult;
    @FXML
    protected NumberAxis xAxis;
    @FXML
    protected LogarithmicAxis yAxis;
//    @FXML
//    protected NumberAxis xSpectraAxis;
    @FXML
    protected LogarithmicAxis ySpectraAxis;

    @FXML
    protected NumberAxis yVdaAxis;
    @FXML
    protected NumberAxis xVdaAxis;

    @FXML
    protected Pane spectraColorMap;
    @FXML
    protected ImageView spectraColorMapImg;
    @FXML
    protected LogarithmicAxis spectraColorMapAxis;

    protected MapLegend fluxLegend;

    private Double minX, maxX;

    private MapMenu spectroPlotMenu;

    //cursor lines
    private Line hLine;
    private Line vLine;
    // selection properties
    private boolean zoomSelection = false;
    private boolean isT1Selected = false;
    private long selectedT1, selectedT2;

    public enum ESEPPlotTool {
        TIMESERIES, SPECTRA, VDA
    };
    private ESEPPlotTool currTool = ESEPPlotTool.SPECTRA;

    private double fluxLineWidth = 0.5;

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

        // Init x-axis
        NumberAxis.DefaultFormatter xAxisFormatter = new NumberAxis.DefaultFormatter(xAxis) {
            @Override
            public String toString(Number object) {
                return Formatter.getTdf().format(object);
            }
        };
        xAxis.setTickLabelFormatter(xAxisFormatter);
        bindAxisChart(spectraChart, xAxisFormatter);
        bindAxisChart(nrjDateRangeChart, xAxisFormatter);
        //bindAxisChart(spDateRangeChart, xAxisFormatter);
        bindAxisChart(vdaChart, xAxisFormatter);

        // Create cross lines 
        hLine = new Line(0, 0, 10, 0);
        hLine.setStrokeWidth(0.7);
        hLine.getStrokeDashArray().addAll(4.0);
        vLine = new Line(0, 0, 10, 0);
        vLine.setStrokeWidth(0.7);
        vLine.getStrokeDashArray().addAll(4.0);
        pane.getChildren().add(hLine);
        pane.getChildren().add(vLine);

        // Init chart
        nrjChart.getStylesheets().add(getClass().getResource("SpectroPlotComponent.css").toString());
        nrjChart.setLegendVisible(false);
        vdaChart.getStylesheets().add(getClass().getResource("SpectroFitChart.css").toString());
        nrjDateRangeChart.getStylesheets().add(getClass().getResource("VPlotCatalog.css").toString());
        //spDateRangeChart.getStylesheets().add("VPlotCatalog.css");

        //add clickToFit 
        seriesClick.setName("ClickPoints");
        seriesFit.setName("FitLine");
        vdaChart.getData().add(seriesClick); // points to fit
        vdaChart.getData().add(seriesFit); // fit line

        // bind other charts axis 
        // Init context menu
        loadMenu();

        //mouse on right nrjs list panel
        // Manage mouse events 
        pane.addEventFilter(MouseEvent.MOUSE_PRESSED, new EventHandler<MouseEvent>() {
            @Override
            public void handle(MouseEvent mouseEvent) {
                mouseClick(mouseEvent);
            }
        });
        pane.addEventFilter(MouseEvent.MOUSE_MOVED, new EventHandler<MouseEvent>() {
            @Override
            public void handle(MouseEvent mouseEvent) {
                mouseMove(mouseEvent);
            }
        });

        // Create tooltips
        if (!TOOLTIP.isDisable()) {
            // Initialise tooltips except for Mac OS
            Tooltip.install(pane, new Tooltip(TOOLTIP.SEPPLOT_RIGHT_CLICK));
        }

    }

    private void bindAxisChart(XYChart<Number, Number> pChart, NumberAxis.DefaultFormatter xAxisFormatter) {
        NumberAxis lXAxisBind = (NumberAxis) pChart.getXAxis();
        lXAxisBind.setTickLabelFormatter(xAxisFormatter);
        lXAxisBind.lowerBoundProperty().bind(xAxis.lowerBoundProperty());
        lXAxisBind.upperBoundProperty().bind(xAxis.upperBoundProperty());
        lXAxisBind.tickUnitProperty().bind(xAxis.tickUnitProperty());
    }

    /**
     * Bind Start Element to the plot component
     */
    public void setStartSource(ObjectProperty<SpaceElement> startSourceProperty) {

        startSourceProperty.addListener(new ChangeListener<SpaceElement>() {
            @Override
            public void changed(ObservableValue<? extends SpaceElement> observable, SpaceElement oldValue, SpaceElement newValue) {
                if (!oldValue.getName().equals(newValue.getName())) {
                    // redraw chart from Solar speeds
                    startElement = newValue;
                    //vPlotMenu.getMenu(SpectroItemBuilder.DATA_SOURCE).setDisable(!(startElement instanceof Planet));
                    log.info("StartSource changed! refresh menu and redraw all");
                    //loadMenu();
                    resetZoomFields();
                    clearFitSeries();
                    fluxList.getTimedFluxes().clear();
                    drawAllFlux();
                }
            }
        });
        // first init 
        startElement = startSourceProperty.get();
        //vPlotMenu.getMenu(VPlotItemBuilder.DATA_SOURCE).setDisable(!(startElement instanceof Planet));
        //loadMenu();
        fluxList.getTimedFluxes().clear();
        drawAllFlux();
    }

    /**
     * Bind Particle Element to the plot component
     */
    public void setParticle(ObjectProperty<Particle> particleProperty) {

        particleProperty.addListener(new ChangeListener<Particle>() {
            @Override
            public void changed(ObservableValue<? extends Particle> observable, Particle oldValue, Particle newValue) {
                if (!oldValue.equals(newValue)) {
                    // redraw chart from Solar speeds
                    particle = newValue;
                    //vPlotMenu.getMenu(SpectroItemBuilder.DATA_SOURCE).setDisable(!(startElement instanceof Planet));
                    log.info("Particle changed! redraw all");
                    fluxList.getTimedFluxes().clear();
                    clearFitSeries();
                    drawAllFlux();
                }
            }
        });
        // first init 
        particle = particleProperty.get();
        //vPlotMenu.getMenu(VPlotItemBuilder.DATA_SOURCE).setDisable(!(startElement instanceof Planet));
        fluxList.getTimedFluxes().clear();
        drawAllFlux();
    }

    /**
     * Bind start date to the flarePlot component
     */
    public void setStartDate(ObjectProperty<Date> selectedDate) {
        startDate = selectedDate;
        // listen for change date
        startDate.addListener(new InvalidationListener() {
            public void invalidated(Observable observable) {
                // resize date axis according to new start date
                spectroPlotModel.setDates(startDate.get());
                minX = Math.floor((double) (spectroPlotModel.getT1()));
                maxX = Math.ceil((double) (spectroPlotModel.getT2()));
                resizeXAxis(minX, maxX);
                // redraw chart
                fluxList.getTimedFluxes().clear();
                //clearFitSeries();
                drawAllFlux();
            }
        });
        // init the FlarePlotComponent from the selected start date
        spectroPlotModel.setDates(startDate.get());
        minX = Math.floor((double) (spectroPlotModel.getT1()));
        maxX = Math.ceil((double) (spectroPlotModel.getT2()));
        resizeXAxis(minX, maxX);
        // redraw chart from flux list of the current flux type
        drawAllFlux();
    }

    /**
     * Mouse click event
     *
     * @param evt
     */
    private void mouseClick(MouseEvent evt) {

        double xAxisShift = xAxis.getLayoutX();
        double yAxisShift = yAxis.getLayoutY() + ((Node) yAxis.getParent()).getLayoutY();
//        double xSpAxisShift = xSpectraAxis.getLayoutX();
//        double ySpAxisShift = ySpectraAxis.getLayoutY() + ((Node) ySpectraAxis.getParent()).getLayoutY();
//        double yVdaAxisShift = yVdaAxis.getLayoutY() + ((Node) yVdaAxis.getParent()).getLayoutY();
        // Treat only if mouse is inside axes
        if (evt.getY() > yAxisShift && evt.getY() < yAxisShift + yAxis.getHeight()
                && evt.getX() > xAxisShift && evt.getX() < xAxisShift + xAxis.getWidth()) {

            if (evt.getButton() == MouseButton.PRIMARY) {
                spectroPlotMenu.hide();
            } else if (evt.getButton() == MouseButton.SECONDARY) {
                spectroPlotMenu.show(pane, evt.getScreenX(), evt.getScreenY());
            }

            spectroPlotMenu.getMenu(SpectroPlotItemBuilder.ZOOM).setVisible(true);

            double xAxisStep = xAxis.getWidth() / (xAxis.getUpperBound() - xAxis.getLowerBound());
            long XX = (long) (((evt.getX() - xAxisShift - 1) / xAxisStep) + xAxis.getLowerBound());
            double YY = getYValue(evt);

            //System.out.println("***** Mouse Click XX/YY " + XX + "/" + YY+ " getY(): "+evt.getY()+" lowerbound: "+yVdaAxis.getLowerBound());

            /* treat select/add Fit actions */
            if (ESEPPlotTool.VDA.equals(currTool)) {
                selectedP.setTime(XX);
                selectedP.setElongation(YY);
                if (isClickPointsInProgress && evt.getButton() == MouseButton.PRIMARY) {
                    /* add point in the click series */
                    addClickPoint(selectedP);
                }
            }

            if (zoomSelection) {
                if (!isT1Selected) {
                    selectedT1 = XX;
                    isT1Selected = true;
                    //System.out.println("** t1 selected " + XX);
                    //t1Item.setText(VPlotItemBuilder.ZOOM_T1 + Formatter.getSdf().format(selectedT1));
                } else {
                    //System.out.println("** t2 selected " + XX);
                    selectedT2 = XX;
                    //t2Item.setText(VPlotItemBuilder.ZOOM_T2 + Formatter.getSdf().format(selectedT2));
                    if (selectedT2 > selectedT1) {
                        resizeXAxis(selectedT1, selectedT2);
                        resetZoomFields();
                        // allow reset
                        // resetItem.setDisable(false);
                    }
                    isT1Selected = false;
                    zoomSelection = false;
                }
            }
//			}
        }

        // Treat spectro colormap click
        double xColMapShift = spectraColorMap.getLayoutX();
        double yColMapShift = spectraColorMap.getLayoutY() + ((Node) spectraColorMap.getParent()).getLayoutY();
        if (spectraChart.isVisible() && evt.getY() > yColMapShift && evt.getY() < yColMapShift + spectraColorMap.getHeight()
                && evt.getX() > xColMapShift && evt.getX() < xColMapShift + spectraColorMap.getWidth()) {

            changeColorMinMax(evt.getX(), evt.getY());

        }

    }

    /**
     * Mouse move event
     *
     * @param evt
     */
    private void mouseMove(MouseEvent evt) {
        double xAxisShift = xAxis.getLayoutX();
        double yAxisShift = yAxis.getLayoutY() + ((Node) yAxis.getParent()).getLayoutY();
        // Treat only if mouse is inside axis
        if (evt.getY() > yAxisShift && evt.getY() < yAxisShift + yAxis.getHeight()
                && evt.getX() > xAxisShift && evt.getX() < xAxisShift + xAxis.getWidth()) {

            double xAxisStep = xAxis.getWidth() / (xAxis.getUpperBound() - xAxis.getLowerBound());
            long XX = (long) (((evt.getX() - xAxisShift - 1) / xAxisStep) + xAxis.getLowerBound());
            double YY = getYValue(evt);

            //move cursor
            hLine.setStartX(xAxisShift);
            hLine.setEndX(xAxisShift + xAxis.getWidth());
            vLine.setStartY(yAxisShift);
            vLine.setEndY(yAxisShift + yAxis.getHeight());
            hLine.setStartY(evt.getY());
            hLine.setEndY(evt.getY());
            vLine.setStartX(evt.getX());
            vLine.setEndX(evt.getX());
            
            // Display mouse coordinates
            switch (currTool) {
                case VDA:
                    legend.setText("Time : " + Formatter.getSdf().format(XX) + " , 1/beta : " + Formatter.getFormatter2D().format(YY));
                    break;
                case SPECTRA:
                    legend.setText("Time : " + Formatter.getSdf().format(XX) + " , Energy : " + Formatter.getFormatter2D().format(YY));
                    break;
                case TIMESERIES:
                default:
                    legend.setText("Time : " + Formatter.getSdf().format(XX) + " , Flux : " + Formatter.getFormatterExp().format(YY));
            }

        }
    }

    /**
     * Get chart Y value depending on current active Tool Timeseries, spectra or VDA
     * @param evt Mouse click event.
     * @return 
     */
    private double getYValue(MouseEvent evt) {
        double lYRet = 0;
        double yAxisShift = yAxis.getLayoutY() + ((Node) yAxis.getParent()).getLayoutY();
        if (ESEPPlotTool.TIMESERIES.equals(currTool)) {
            double yAxisUpper = Math.log10(yAxis.getUpperBound());
            double yAxisLower = Math.log10(yAxis.getLowerBound());
            double yAxisStep = yAxis.getHeight() / (yAxisUpper - yAxisLower);
            double logYY = yAxisUpper - ((evt.getY() - yAxisShift - 1) / yAxisStep);
            lYRet = Math.pow(10, logYY);
        }
        if (ESEPPlotTool.SPECTRA.equals(currTool)) {
            double yAxisStep = ySpectraAxis.getHeight() / (ySpectraAxis.getUpperBound() - ySpectraAxis.getLowerBound());
            double lYY = ((evt.getY() - yAxisShift - 1) / yAxisStep) + ySpectraAxis.getLowerBound();
            lYRet = ySpectraAxis.getUpperBound() - lYY + ySpectraAxis.getLowerBound(); // invert YAxis
        }
        if (ESEPPlotTool.VDA.equals(currTool)) {
            double yAxisStep = yVdaAxis.getHeight() / (yVdaAxis.getUpperBound() - yVdaAxis.getLowerBound());
            double lYY = ((evt.getY() - yAxisShift - 1) / yAxisStep) + yVdaAxis.getLowerBound();
            lYRet = yVdaAxis.getUpperBound() - lYY + yVdaAxis.getLowerBound(); // invert YAxis
        }
        return lYRet;
    }

    /**
     * Show Dialog to change min/max color
     */
    private void changeColorMinMax(double pX, double pY) {
        Dialog<Pair<String, String>> dialog = new Dialog<>();
        dialog.setTitle("Choose min/max Color");

        // Set the button types.
        ButtonType okButtonType = new ButtonType("OK", ButtonData.OK_DONE);
        dialog.getDialogPane().getButtonTypes().addAll(okButtonType, ButtonType.CANCEL);

        GridPane gridPane = new GridPane();
        gridPane.setHgap(10);
        gridPane.setVgap(10);
        gridPane.setPadding(new Insets(20, 150, 10, 10));

        TextField lMin = new TextField("" + spectraColorMapAxis.getLowerBound());
        //lMin.setPromptText(""+spectraColorMapAxis.getLowerBound());
        TextField lMax = new TextField("" + spectraColorMapAxis.getUpperBound());
        //lMax.setPromptText(""+spectraColorMapAxis.getUpperBound());

        gridPane.add(lMin, 0, 0);
        //gridPane.add(new Label(" / "), 1, 0);
        gridPane.add(lMax, 1, 0);

        dialog.getDialogPane().setContent(gridPane);

        // Request focus on the first field by default.
        //Platform.runLater(() -> lMin.requestFocus());
        //set position
        dialog.getDialogPane().getScene().getWindow().setX(pX);
        dialog.getDialogPane().getScene().getWindow().setY(pY);
        dialog.setX(pX);
        dialog.setY(pY);

        // Convert the result to a min-max-pair when the login button is clicked.
        dialog.setResultConverter(dialogButton -> {
            if (dialogButton == okButtonType) {
                String lStrMin = lMin.getText().isEmpty() ? "" + spectraColorMapAxis.getLowerBound() : lMin.getText();
                String lStrMax = lMax.getText().isEmpty() ? "" + spectraColorMapAxis.getUpperBound() : lMax.getText();
                return new Pair<>(lStrMin, lStrMax);
            }
            return null;
        });

        Optional<Pair<String, String>> result = dialog.showAndWait();

        result.ifPresent(pair -> {
            //System.out.println("From=" + pair.getKey() + ", To=" + pair.getValue());
            setMinMaxSpectraColor(Double.valueOf(pair.getKey()), Double.valueOf(pair.getValue()));
        });
    }

    protected void setLegend(String legend) {
        if (!nrjChart.getData().isEmpty()) {
            nrjChart.getData().get(0).setName(legend);
        }
    }

    /**
     * Add the items to the menu of sep spectro plot.
     */
    private void loadMenu() {
        // load contextual menu

        getInsituRanges();

        // init legend
        fluxLegend = new MapLegend();
        setMapLegend(fluxList);
        nrjItems.setContent(fluxLegend);
        //setSpectroLegend();

    }

    @Override
    public void runSetZoom() {
        zoomSelection = true;
    }

    @Override
    public void runResetZoom() {
        resizeXAxis(minX, maxX);
        resetZoomFields();
    }

    private void setMapLegend(SpectroWrapper pSw) {
        // init legend
        if (pSw == null) {
            return;
        }
        int i = 0;
        List<Node> nodes = new ArrayList<Node>();
        for (Float[] lRange : pSw.getEnergyList()) {
            nodes.add(SpectroPlotModel.createNodeLabel("" + lRange[0] + "-" + lRange[1] + " MeV", colorSeries.get(i++ % colorSeries.size())));
        }
        fluxLegend.setNodes(nodes);
    }

    /**
     * get interpolated value in between Ya/Yb range
     *
     * @param pXa min X
     * @param pXb max X
     * @param pX value to interpolate
     * @param pYa min Y
     * @param pYb max Y
     * @return value in Y scale
     */
    private static double linearInterpol(double pXa, double pXb, double pX,
            double pYa, double pYb) {
        double lValueDest = pYa + (pX - pXa) * (pYb - pYa) / (pXb - pXa);
        return lValueDest;
    }

    /**
     * Relativistic proton velocity in m/s from its kinetic energy in MeV.
     *
     * @param double kineticEnergy [MeV]
     * @return proton velocity [m/s]
     */
    public double particleVelocity(double kineticEnergy) {
        // Constants
        double m = 1.67262192e-27;  // Rest mass of proton in kg
        if (Particle.electron.equals(particle)) {
            m = 9.1093837e-31; // Rest mass of electron in kg
        }

        // Convert kinetic energy from MeV to joules
        double K_joules = kineticEnergy * 1.602176634e-13;  // 1 MeV = 1.602176634e-13 J

        // Calculate velocity
        return Math.sqrt(1 - Math.pow(((K_joules / (m * C_MS * C_MS)) + 1), -2));
    }

    /**
     * compute spectro image and colorMap from data
     */
    private void computeSpectraImg() {
        //log.info("computeSpectroImg: Begin");
        //recompute colormap
        //log.info("computeSpectroImg fluxList min/max: " + fluxList.getMinValue() + "/" + fluxList.getMaxValue());
        double lFluxMin = spectraColorMapAxis.getLowerBound();
        double lFluxMax = spectraColorMapAxis.getUpperBound();

        colorTable = new UcarColorTable(
                Math.log10(lFluxMin), Math.log10(lFluxMax),
                UcarColorTableType.rainbow);
        //compute image
        //BufferedImage lBi = null;
        spectraImg.setImage(null);
        spectraColorMapImg.setImage(null);

        if (fluxList.getEnergyList() == null || fluxList.getEnergyList().isEmpty()) {
            return;
        }

        int lImgW = fluxList.getTimedFluxes().size();
        int lImgH = 255;

        double lMinNrj = Math.log10(fluxList.getEnergyList().get(0)[0]);
        double lMaxNrj = Math.log10(fluxList.getEnergyList().get(fluxList.getEnergyList().size() - 1)[1]);
        if (ESEPPlotTool.VDA.equals(currTool)) {
            lMaxNrj = 1d / particleVelocity(fluxList.getEnergyList().get(0)[0]);
            lMinNrj = 1d / particleVelocity(fluxList.getEnergyList().get(fluxList.getEnergyList().size() - 1)[1]);
            yVdaAxis.setLowerBound(lMinNrj);
            yVdaAxis.setUpperBound(lMaxNrj);
        }
        if (ESEPPlotTool.SPECTRA.equals(currTool)) {
            ySpectraAxis.setLowerBound(fluxList.getEnergyList().get(0)[0]);
            ySpectraAxis.setUpperBound(fluxList.getEnergyList().get(fluxList.getEnergyList().size() - 1)[1]);
        }
        //System.out.println("******** minmax y " + lMinNrj + " " + lMaxNrj + " *******");
        double lMinTime = xAxis.getLowerBound();
        double lMaxTime = xAxis.getUpperBound();

        if (fluxList.getTimedFluxes() != null && fluxList.getTimedFluxes().size() > 0) {
            //System.out.println("min-max="+colorTable.getMin() + " "+colorTable.getMax());
            BufferedImage lBi = new BufferedImage(lImgW, lImgH, BufferedImage.TYPE_INT_ARGB);
            //build raster
            int y = 0;
            if (fluxList.getTimedFluxes() != null) {
                for (int x = 0; x < fluxList.getTimedFluxes().size(); x++) {
                    Spectro lRecord = fluxList.getTimedFluxes().get(x);
                    //System.out.println("*** Time spectro "+x+": "+lRecord.getEventTime().getTimeInMillis()+" Min="+lMinTime);

                    Spectro lRecordNext = lRecord;
                    if (x < fluxList.getTimedFluxes().size() - 1) {
                        lRecordNext = fluxList.getTimedFluxes().get(x + 1);
                    }
                    if (lRecord.getEventTime().getTimeInMillis() < lMinTime || lRecordNext.getEventTime().getTimeInMillis() > lMaxTime) {
                        continue;
                    }

                    y = lBi.getHeight() - 1;
                    int xMin = (int) linearInterpol(lMinTime, lMaxTime, lRecord.getEventTime().getTimeInMillis(), 0, lImgW);
                    int xMax = (int) linearInterpol(lMinTime, lMaxTime, lRecordNext.getEventTime().getTimeInMillis(), 0, lImgW);
                    //log.info("Chunck from "+xMin+" to "+xMax+" at "+x);
                    int lFluxIdx = 0;
                    //System.out.println("Getting values from fluxList: "+lRecord.getFlux());
                    for (Float lVal : lRecord.getFlux()) {
                        double lValNrjMin = Math.log10(fluxList.getEnergyList().get(lFluxIdx)[0]);
                        double lValNrjMax = Math.log10(fluxList.getEnergyList().get(lFluxIdx)[1]);
                        if (ESEPPlotTool.VDA.equals(currTool)) {
                            //lVal=(float)(1d/(protonVelocity(lVal)/c));
                            lValNrjMin = 1d / particleVelocity(fluxList.getEnergyList().get(lFluxIdx)[1]);
                            lValNrjMax = 1d / particleVelocity(fluxList.getEnergyList().get(lFluxIdx)[0]);
                        }
//                        if (x == 0) {
//                            System.out.println("******** flux y " + lValNrjMin + " " + lValNrjMax + " *******");
//                        }
                        int yMin = (int) linearInterpol(lMinNrj, lMaxNrj, lValNrjMin, lImgH, 0);
                        int yMax = (int) linearInterpol(lMinNrj, lMaxNrj, lValNrjMax, lImgH, 0);
                        //log.info("******* compute spectro yMin/yMax "+yMin+"/"+yMax);
                        java.awt.Color lCol = colorTable.getColor(Math.log10(lVal));
                        if (lVal == 0.0f) {
                            lCol = new java.awt.Color(1, 1, 1, 0);//set 0 to transparent
                        } else {
                            //log.info("set color "+lCol.getRGB()+" at "+x+"/"+y+" for value "+lVal+" min/max: "+colorTable.getMin()+"/"+colorTable.getMax());
                        }
                        for (int i = xMin; i < xMax; i++) {
                            for (int j = yMax; j < yMin; j++) {
                                lBi.setRGB(i, j, lCol.getRGB());
                            }
                        }
                        y--;
                        lFluxIdx++;
                    }
                }

                //log.info("computeSpectroImg: drew "+x+" chunks" );
                //display image
                spectraImg.setImage(SwingFXUtils.toFXImage(lBi, null));
                //spectraImage = SwingFXUtils.toFXImage(lBi, null);
            }

            //Compute colormap
            int lWidth = 1;
            int lHeight = 50;
            double lMin = colorTable.getMin();
            double lMax = colorTable.getMax();
            BufferedImage lBiCm = new BufferedImage(lWidth, lHeight, BufferedImage.TYPE_INT_ARGB);
            for (int i = 0; i < lHeight; i++) {
                double lVal = lMin + ((lHeight - i - 1) / (double) lHeight) * (lMax - lMin);
                //log.info("cbar value: "+i+"=>"+lVal);
                java.awt.Color lC = colorTable.getColor(lVal);
                lBiCm.setRGB(0, i, lC.getRGB());
            }
            spectraColorMapImg.setImage(SwingFXUtils.toFXImage(lBiCm, null));
            //setSpectroColorMapLegend();
        }
    }

    /**
     * Switch spectro mode
     */
    @Override
    public void runShowSpectro(ESEPPlotTool pTool) {

        nrjChart.setVisible(ESEPPlotTool.TIMESERIES.equals(pTool));
        nrjItems.setVisible(ESEPPlotTool.TIMESERIES.equals(pTool));
        spectraImg.setVisible(!ESEPPlotTool.TIMESERIES.equals(pTool));
        spectraChart.setVisible(ESEPPlotTool.SPECTRA.equals(pTool));
        spectraColorMap.setVisible(!ESEPPlotTool.TIMESERIES.equals(pTool));
        vdaChart.setVisible(ESEPPlotTool.VDA.equals(pTool));
        fitResult.setVisible(ESEPPlotTool.VDA.equals(pTool) && currFitResult!=null);
        //show XAxis
        spectraChart.getXAxis().setVisible(ESEPPlotTool.SPECTRA.equals(pTool));
        vdaChart.getXAxis().setVisible(ESEPPlotTool.VDA.equals(pTool));
        nrjChart.getXAxis().setVisible(ESEPPlotTool.TIMESERIES.equals(pTool));
        nrjDateRangeChart.getXAxis().setVisible(ESEPPlotTool.SPECTRA.equals(pTool));
        currTool = pTool;
        computeSpectraImg();
        loadMenu();

    }

//    /**
//     * Switch 1/V mode
//     */
//    @Override
//    public void runShowVDA() {
//        log.info("runShowVDA: spectro:" + spectraImg.isVisible() + "    vda:" + showVDA);
//        showVDA = !showVDA;
//
//        //set axis log or linear
//        spectraChart.setVisible(!showVDA);
//        vdaChart.setVisible(showVDA);
//
//        //spectraImg.setVisible(!showVDA);
//        computeSpectraImg();
//        loadMenu();
//    }
    /**
     * Show/Hide one flux
     */
    @Override
    public void runShowEnergy(int pIdx) {
        if (!fluxLegend.isVisibleNode(fluxLegend.getChildren().get(pIdx))) {
            //fluxList.clear();
            drawFlux(pIdx, true);
            //curShownFlux.add(new Integer(pIdx));
        } else {
            drawFlux(pIdx, false);
            //curShownFlux.remove(new Integer(pIdx));
        }
    }

    /**
     * Show/Hide all flux plots //show spectro
     */
    @Override
    public void runShowAllEnergies() {

        boolean lIsOneShown = false;
        for (int i = 0; i < fluxList.getEnergyList().size(); i++) {
            if (fluxLegend.isVisibleNode(fluxLegend.getChildren().get(i))) {
                lIsOneShown = true;
                break;
            }
        }
        if (lIsOneShown) {
            clearDataCharts();
            for (int i = 0; i < fluxList.getEnergyList().size(); i++) {
                fluxLegend.setNodeOpacity(fluxLegend.getChildren().get(i), false);
            }
        } else {
            drawAllFlux();
        }
    }


    private void resetZoomFields() {
        selectedT1 = 0;
        isT1Selected = false;
        zoomSelection = false;
    }

    /**
     * Create plot series at index position from a FlareFlux list Version
     * compatible since Java 7
     */
    private void addPlot(SpectroWrapper flux, int index) {
        if ((flux != null) && (flux.getTimedFluxes() != null) && (flux.getTimedFluxes().size() > 0)) {
            // create chart plot series
            log.info("addPlot: Trying to add plot "+index);
            Series<Number, Number> lSeries = createSeries(index, flux);
            log.info("addPlot: Serie "+index+" created, adding to chart");
            nrjChart.getData().set(index, lSeries);
//            Node lLine = lSeries.getNode().lookup(".chart-series-area-line");
//            Color lCol = colorSeries.get(index % colorSeries.size());
//            String lRgb = String.format("%d, %d, %d", (int) (lCol.getRed() * 255), (int) (lCol.getGreen() * 255), (int) (lCol.getBlue() * 255));
//            lLine.setStyle("-fx-stroke: rgba(" + lRgb + ",1.0);");

            //nrjChart.
            //log.info("addPlot: Plot added at "+index+" data size: "+nrjChart.getData().size());
            // resize axis
            resizeXAxis(minX, maxX);
        }
    }

    /**
     * Draw flux series of a measure flux Version compatible since Java 7
     */
    private void drawFlux(final int index, final boolean pDispSwitch) {
        final Task<Object> getFlux = this.getFlux();
        getFlux.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) {

                    if (pDispSwitch) {
                        addPlot(fluxList, index);
                        //runShowLinesInChart(false);
                    } else {
                        erasePlot(index);
                    }
                    fluxLegend.setNodeOpacity(fluxLegend.getChildren().get(index), pDispSwitch);
//                    clearDataCharts();//clear all
//                    for (int i = 0; i < fluxList.getEnergyList().size(); i++) {
//                        addPlot(fluxList, i);
//                        if (i == index) {//process required record
//                            if (pDispSwitch) {
//                                fluxLegend.setNodeOpacity(fluxLegend.getChildren().get(i), true);
//                            } else {
//                                erasePlot(i);//Added by default so remove it
//                                fluxLegend.setNodeOpacity(fluxLegend.getChildren().get(i), false);
//                            }
//                        } else {
//                            // other graphs to display(default) or not
//                            if (!fluxLegend.isVisibleNode(fluxLegend.getChildren().get(i))) {
//                                erasePlot(i);
//                            }
//                        }
//                    }
                    endCursor();

                } else if (newState == Worker.State.FAILED || newState == Worker.State.CANCELLED) {
                    //fluxPlotModel.setValid(false);
                    endCursor(true);
                    log.info("get All flux failed - " + getFlux.getException().getMessage());
                }
            }
        });

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

    /**
     * Draw all flux series of a flux type Version compatible since Java 7
     */
    private void drawAllFlux() {
        final Task<Object> getFlux = this.getFlux();
        getFlux.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) {
                    //setMapLegend(fType);
                    log.info("Got new spectro data: " + fluxList.getEnergyList().size());
                    clearDataCharts();
                    //setColor => redraw spectra
                    setMinMaxSpectraColor(fluxList.getMinValue(), fluxList.getMaxValue());
                    yAxis.setLowerBound(fluxList.getMinValue());
                    yAxis.setUpperBound(fluxList.getMaxValue());
                    if (fluxList.getEnergyList() != null && !fluxList.getEnergyList().isEmpty()) {
                        ySpectraAxis.setLowerBound(fluxList.getEnergyList().get(0)[0]);
                        ySpectraAxis.setUpperBound(fluxList.getEnergyList().get(fluxList.getEnergyList().size() - 1)[1]);
                    }
                    loadMenu();
                    long lTime = System.currentTimeMillis();
                    log.info("Showing " + fluxList.getEnergyList().size() + " plots.");
                    for (int i = 0; i < fluxList.getEnergyList().size(); i++) {
                        //add plots
                        addPlot(fluxList, i);
                        // show legend
                        if (i < fluxLegend.getChildren().size()) {
                            fluxLegend.setNodeOpacity(fluxLegend.getChildren().get(i), true);
                        }
                    }
                    log.info("Plots added in " + (System.currentTimeMillis() - lTime) / 1000d + " s");
                    //runShowLinesInChart(false);
                    //fluxPlotModel.setValid(true);
                    endCursor();

                } else if (newState == Worker.State.FAILED || newState == Worker.State.CANCELLED) {
                    //fluxPlotModel.setValid(false);
                    endCursor(true);
                    log.info("get flux failed - " + getFlux.getException().getMessage());
                    getFlux.getException().printStackTrace();
                }
            }
        });

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

    /**
     * Get all energies between start and end dates
     */
    private Task<Object> getFlux() {
        return new Task<Object>() {
            @Override
            protected Object call() throws Exception {
                //fluxList.clear();
                if (fluxList.getTimedFluxes().isEmpty() && startElement!=null) {
                    log.debug("Call fetchSpectro web service for start " + startElement.getName() + " and particle " + particle);
                    fluxList = ServiceFactory.getSpectroService()
                            .fetchSpectro(spectroPlotModel.getStartDate(), spectroPlotModel.getEndDate(), startElement.getName(), particle);
                    //fluxList.add(flux);
                    log.debug("Call fetchSpectro web service returned");
                }
                return true;
            }
        };
    }

    /**
     * Erase plot at index position
     */
    private void erasePlot(int index) {
        if (index < nrjChart.getData().size()) {
            nrjChart.getData().set(index, new XYChart.Series<Number, Number>());
        }
        if (index < fluxLegend.getChildren().size()) {
            fluxLegend.setNodeOpacity(fluxLegend.getChildren().get(index), false);
        }
    }

    private void clearDataCharts() {
        nrjChart.getData().clear();
        //fill chart with empty lines to keep color/line order
        List<Series<Number, Number>> lSeries = new ArrayList<Series<Number, Number>>();
        for (Float[] lRange : fluxList.getEnergyList()) {
            lSeries.add(new XYChart.Series<Number, Number>());
        }
        nrjChart.getData().addAll(lSeries);
        //flareChart.getData().clear();
        //allFlaresVisibility = false;
    }

    private void resizeXAxis(double tMin, double tMax) {
        xAxis.setLowerBound(tMin);
        xAxis.setUpperBound(tMax);
        xAxis.setTickUnit((tMax - tMin) / FluxPlotModel.NB_TICS);
//        xSpectraAxis.setLowerBound(tMin);
//        xSpectraAxis.setUpperBound(tMax);
//        xSpectraAxis.setTickUnit((tMax - tMin) / FluxPlotModel.NB_TICS);
//        xVdaAxis.setLowerBound(tMin);
//        xVdaAxis.setUpperBound(tMax);
//        xVdaAxis.setTickUnit((tMax - tMin) / FluxPlotModel.NB_TICS);
        //generate image with new values
        computeSpectraImg();
    }

    /**
     * Change axes min max and recompute spectra
     *
     * @param pMin
     * @param pMax
     */
    private void setMinMaxSpectraColor(double pMin, double pMax) {
        log.info("New spectra data, min/max = " + pMin + "/" + pMax);
        spectraColorMapAxis.setLowerBound(pMin);
        spectraColorMapAxis.setUpperBound(pMax);
        computeSpectraImg();
    }

    /**
     * Create serie from flux at index pIdxList
     *
     * @param
     */
    private Series<Number, Number> createSeries(int pIdxList, SpectroWrapper pFluxes) {
        Series<Number, Number> lSeries = new Series<Number, Number>();
        //System.out.println("******************** cerateSeries: TimeFlux is "+pFluxes.getTimedFluxes().size());        
        if (pFluxes.getTimedFluxes() != null) {
            log.info("CreateSeries at " + pIdxList + ": " + pFluxes.getTimedFluxes().size() + " values");
            int lValueNo = 0;
            for (Spectro flux : pFluxes.getTimedFluxes()) {
                if (flux.getFlux() != null) {
                    Float lVal = flux.getFlux().get(pIdxList);
                    if (lVal == 0) {
                        lVal = Float.NaN;
                    }
                    //System.out.println("Adding "+lVal+" "+(lValueNo++));
                    lSeries.getData().add(new Data<Number, Number>(flux.getEventTime().getTimeInMillis(), lVal));
                } else {
                    log.info("Flux list is null at " + flux.getEventTime());
                }
            }
        }
        //log.info("CreateSeries end "+pIdxList);
        return lSeries;
    }

    public SpectroPlotModel getSpectroPlotModel() {
        return spectroPlotModel;
    }

    private void drawDateRangeBars(final LineChart pLineChart, final DateRange pDr) {
        pLineChart.getData().clear();
        NumberAxis yAxisRange = (NumberAxis) pLineChart.getYAxis();
        // create chart series
        //for (DateRange dateRange : dateRangesWrapper.getDateRanges()) {
        Series<Number, Number> series = new Series<Number, Number>();
        series.getData().add(new Data<Number, Number>(pDr.getStartTime().getTime(), yAxisRange.getLowerBound()));
        series.getData().add(new Data<Number, Number>(pDr.getStartTime().getTime(), yAxisRange.getUpperBound()));
        pLineChart.getData().add(series);
        series = new Series<Number, Number>();
        series.getData().add(new Data<Number, Number>(pDr.getEndTime().getTime(), yAxisRange.getLowerBound()));
        series.getData().add(new Data<Number, Number>(pDr.getEndTime().getTime(), yAxisRange.getUpperBound()));
        pLineChart.getData().add(series);
    }

    /**
     * ***********************************************************************
     ************************* add click actions *****************************
     * ***********************************************************************
     */
    /**
     * add point to click series
     *
     * @param p
     */
    private void addClickPoint(FitPoint p) {
        seriesClick.getData().add(new XYChart.Data<Number, Number>(p.getTime(), p.getElongation()));
        log.debug("New point added : (" + p.getTime() + " " + Formatter.getSdf().format(p.getTime()) + "," + p.getElongation() + ")");
        //log.debug("Size = " + seriesClick.getData().size());
    }

    public void clearFitSeries() {
        seriesClick.getData().clear();
        seriesFit.getData().clear();
    }

    public void startClickPoints() {
        // clear click series and blobs
        clearFitSeries();
        currFitResult = null;
        fitResult.setVisible(false);
        //clearBlobsSeries();
        // configure menu items
        removePointItem.setDisable(false);
        savePointsItem.setDisable(false);
        fitPointsItem.setDisable(false);
        // start click point

        setClickPointsInProgress(true);
    }

    /**
     * Remove last point of the click chart series
     */
    public void removeLastPoint() {
        int position = seriesClick.getData().size() - 1;
        if (position >= 0) {
            //log.debug("Remove last point at position : " + position);
            seriesClick.getData().remove(position);
        }
    }

    public List<FitPoint> getClickPoints() {
        if (!seriesClick.getData().isEmpty()) {
            List<FitPoint> fit = new ArrayList<FitPoint>();
            for (XYChart.Data<Number, Number> item : seriesClick.getData()) {
                FitPoint fitPoint = new FitPoint(item.getXValue().longValue(), item.getYValue().doubleValue());
                fit.add(fitPoint);
            }
            return fit;
        } else {
            return null;
        }
    }

    /**
     * Upload Click points from file
     * http://docs.oracle.com/javafx/2/ui_controls/file-chooser.htm
     */
    public boolean updloadClickPoints() {
        FileChooser fileChooser = new FileChooser();
        fileChooser.setTitle("Clicked points to upload");
        fileChooser.setInitialDirectory(new File(System.getProperty("user.home")));
        fileChooser.getExtensionFilters().addAll(
                new FileChooser.ExtensionFilter("All files", "*.*"),
                new FileChooser.ExtensionFilter("TXT", "*.txt"),
                new FileChooser.ExtensionFilter("ASCII", "*.asc")
        );
        File file = fileChooser.showOpenDialog(RootController.stage);
        if (file != null) {
            try {
                //log.debug("DEBUG JMapComponent:updloadClickPoints - Choosen file: " + file.getAbsolutePath());
                List<FitPoint> clickFilePoints = ClickPointsFileHandler.readClickPointsFile(file);
                // All is ok, enable fit
                startClickPoints();
                addClickSeries(clickFilePoints);

            } catch (Exception e) {
                e.printStackTrace();
                return false;
            }
            return true;
        }
        return false;
    }

    /**
     * Update click series from a CME click points
     */
    public void addClickSeries(List<FitPoint> fit) {
        if (fit != null) {
            seriesClick.getData().clear();
            for (FitPoint fitPoint : fit) {
                seriesClick.getData().add(new XYChart.Data<Number, Number>(fitPoint.getTime(), fitPoint.getElongation()));
            }
        }
    }

    /**
     * Save Click points from file
     * http://docs.oracle.com/javafx/2/ui_controls/file-chooser.htm
     */
    public void saveClickPoints() {
        // save clicked points
        if (this.getClickPoints() != null) {
            FileChooser fileChooser = new FileChooser();
            fileChooser.setTitle("Choose file name");
            fileChooser.setInitialDirectory(new File(System.getProperty("user.home")));
            fileChooser.getExtensionFilters().addAll(
                    new FileChooser.ExtensionFilter("All files", "*.*"),
                    new FileChooser.ExtensionFilter("TXT", "*.txt"),
                    new FileChooser.ExtensionFilter("ASCII", "*.asc")
            );
            //        if (userDir != null) {
            //            fileChooser.setInitialDirectory(userDir);
            //        }
            File file = fileChooser.showSaveDialog(RootController.stage);
            if (file != null) {
                if (!file.getName().contains(".")) {
                    file = new File(file.getAbsolutePath() + ".txt");
                }
                ClickPointsFileHandler.writeClickPointsFile(this.getClickPoints(), file);
            }
//	        userDir = fileChooser.getInitialDirectory();
        }
    }

    public void setClickPointsInProgress(boolean isClickPointsInProgress) {
        this.isClickPointsInProgress = isClickPointsInProgress;
        if (isClickPointsInProgress) {
            super.setCursor(Cursor.CROSSHAIR);
        } else {
            super.setCursor(null);
        }
    }

    /**
     * ***********************************************************************
     * ********************** Specific menu actions **************************
     * ***********************************************************************
     */
    @Override
    public void runRemoveLastPoint() {
        int position = seriesClick.getData().size() - 1;
        if (position >= 0) {
            //log.debug("Remove last point at position : " + position);
            seriesClick.getData().remove(position);
        }
    }

    /**
     * Compute fit slope and intercept from points clicked on graph
     */
    @Override
    public void runFitPoints() {
        setClickPointsInProgress(false);
        removePointItem.setDisable(true);
        fitPointsItem.setDisable(true);

        // create a fit series from the current click points series
        // create a fit series from the current click points series
//        getHasChanged().set(true);
//        getHasChanged().set(false);
        int n = getClickPoints().size();
        log.info("**** Computing vda from " + n + " points.");
        double sumX = 0, sumY = 0, sumXY = 0, sumX2 = 0;
        for (FitPoint lFp : getClickPoints()) {
            sumX += lFp.getElongation();
            sumY += lFp.getTime() / 1000;
            sumXY += lFp.getTime() / 1000 * lFp.getElongation();
            sumX2 += lFp.getElongation() * lFp.getElongation();
        }

        if (n * sumX2 - sumX * sumX != 0) {
            double slope = (n * sumXY - sumX * sumY) / (n * sumX2 - sumX * sumX);
            double intercept = (sumY - slope * sumX) / n;
            log.info("Slope = " + slope + " => path length [AU] = " + (C_MS * slope / AU));
            log.info("Intercept [" + intercept + "] = " + Formatter.getSdf().format(intercept * 1000));
            currFitResult = new double[]{slope,intercept};
            fitResult.setText("Path length : "+Formatter.getFormatter2D().format(C_MS * slope / AU)+" AU , t_SPR : "+Formatter.getSdf().format(intercept * 1000));
            fitResult.setVisible(true);
            for (double lX = yVdaAxis.getLowerBound(); lX < yVdaAxis.getUpperBound(); lX += 0.15) {
                //y = x*slope+intercept
                double lY = lX * slope * 1000 + 1000 * intercept;
                seriesFit.getData().add(new XYChart.Data<Number, Number>((long) lY, lX));
                //System.out.println("*** Adding point "+lY+" "+Formatter.getSdf().format(new Date((long)lY)) +" / "+lX)  ;
            }
        } else {
            log.info("**** Found slope/intercept: zero division n/sumX: " + n + "/" + sumX);
            return;
        }

    }

    @Override
    public void runClickPoints() {
        startClickPoints();
        //fitParameterComponent.reset();
    }

    @Override
    public void runClickPointsFile() {
        FileChooser fileChooser = new FileChooser();
        fileChooser.setTitle("Clicked points to upload");
        fileChooser.setInitialDirectory(new File(System.getProperty("user.home")));
        fileChooser.getExtensionFilters().addAll(
                new FileChooser.ExtensionFilter("All files", "*.*"),
                new FileChooser.ExtensionFilter("TXT", "*.txt"),
                new FileChooser.ExtensionFilter("ASCII", "*.asc")
        );
        File file = fileChooser.showOpenDialog(RootController.stage);
        if (file != null) {
            try {
                //log.debug("DEBUG JMapComponent:updloadClickPoints - Choosen file: " + file.getAbsolutePath());
                List<FitPoint> clickFilePoints = ClickPointsFileHandler.readClickPointsFile(file);
                // All is ok, enable fit
                startClickPoints();
                addClickSeries(clickFilePoints);

            } catch (Exception e) {
                e.printStackTrace();
                //return false;
            }
            //return true;
        }
        //return false;
    }

    @Override
    public void runSavePoints() {
        // save clicked points
        if (this.getClickPoints() != null) {
            FileChooser fileChooser = new FileChooser();
            fileChooser.setTitle("Choose file name");
            fileChooser.setInitialDirectory(new File(System.getProperty("user.home")));
            fileChooser.getExtensionFilters().addAll(
                    new FileChooser.ExtensionFilter("All files", "*.*"),
                    new FileChooser.ExtensionFilter("TXT", "*.txt"),
                    new FileChooser.ExtensionFilter("ASCII", "*.asc")
            );
            //        if (userDir != null) {
            //            fileChooser.setInitialDirectory(userDir);
            //        }
            File file = fileChooser.showSaveDialog(RootController.stage);
            if (file != null) {
                if (!file.getName().contains(".")) {
                    file = new File(file.getAbsolutePath() + ".txt");
                }
                ClickPointsFileHandler.writeClickPointsFile(this.getClickPoints(), file);
            }
//	        userDir = fileChooser.getInitialDirectory();
        }
    }

    /**
     * ************************************************
     ********* End click menu trigge'rs ***************
     * ************************************************
     */
    /**
     * Update InSitu from catalogue Contains menu building too
     *
     * @param source
     * @param type
     */
    public void getInsituRanges() {
        final Task<Object> getInsituRanges = this.getInsituRangesTask();
        final SpectroPlotComponent lThis = this;
        getInsituRanges.stateProperty().addListener(new ChangeListener<Worker.State>() {
            @Override
            public void changed(ObservableValue<? extends Worker.State> observableValue, Worker.State oldState, Worker.State newState) {
                spectroPlotMenu = new MapMenu();
                List<Item> menus = new ArrayList<Item>();

                menus.add(SpectroPlotItemBuilder.getShowSpectraItem(spectroPlotMenu.getMap(), lThis));
                
                if (ESEPPlotTool.VDA.equals(currTool)) {
                    menus.add(SpectroPlotItemBuilder.getAddFitItem(spectroPlotMenu.getMap(), lThis));
                }
                if (ESEPPlotTool.TIMESERIES.equals(currTool)) {
                    //menus.add(SpectroPlotItemBuilder.getFluxLineStrokeItem(spectroPlotMenu.getMap(), lThis));
                    menus.add(SpectroPlotItemBuilder.getShowNrjsItem(fluxList.getEnergyList(), spectroPlotMenu.getMap(), lThis));
                }

                Item lInSituMenuItem = new Item("In situ catalogue");
                if (newState == Worker.State.SUCCEEDED) {
                    if ((menuDateRangesWrappers != null) && (menuDateRangesWrappers.size() > 0)) {
                        //System.out.println("==>>> ************ SpectroPlotComponent got " + menuDateRangesWrappers.size() + " ranges ");
                        int lItemIdx = 0;
                        //for (VPlotModel.Catalog catalog : VPlotModel.Catalog.values()) {
                        //Item lCatItem = new Item(catalog.getName());
                        for (final VPlotModel.CatalogType catType : getCatalogTypes(VPlotModel.Catalog.CME)) {
                            Item lCatTypeItem = new Item(catType.getName());
                            if (menuDateRangesWrappers.get(lItemIdx).getDateRanges() == null) {
                                continue;
                            }
                            //System.out.println("Adding to menu spectroplot " + menuDateRangesWrappers.get(lItemIdx).getDateRanges().size() + " ranges");
                            for (final DateRange lDr : menuDateRangesWrappers.get(lItemIdx++).getDateRanges()) {
                                Item lDateItem = new Item("" + lDr.getStartTime());
                                spectroPlotMenu.getMap().put(lDateItem, new ISelectedItem() {
                                    @Override
                                    public void run() {
                                        //vPlotMenu.runInSituCatalog(catalog.name(), catType.name());
                                        long lCenterTime = (lDr.getStartTime().getTime() + lDr.getEndTime().getTime()) / 2l;

                                        log.debug("Menu click: Setting time on " + Formatter.getSdf().format(new Date(lCenterTime)));

                                        spectroPlotModel.setTime(lDr.getStartTime().getTime());
                                        // notify change
                                        getHasChanged().set(true);
                                        getHasChanged().set(false);
                                        //runInSituCatalog(catalog.name(), catType.name());
                                        drawDateRangeBars(nrjDateRangeChart, lDr);
                                        //drawDateRangeBars(spDateRangeChart, lDr);
                                    }
                                });
                                lCatTypeItem.add(lDateItem);
                            }
                            if (lCatTypeItem.hasChild()) {
                                //activate when time selection will be ok (getInsituRangesTask)
                                lInSituMenuItem.add(lCatTypeItem);
                            }
                        }
                        //if (lCatItem.hasChild()) {
                        //    lInSituMenuItem.add(lCatItem);
                        //}
                        //}
                    }
                    if (lInSituMenuItem.hasChild()) {
                        menus.add(lInSituMenuItem);
                    }
                    
                    menus.add(SpectroPlotItemBuilder.getZoomItem(spectroPlotMenu.getMap(), lThis));

                    spectroPlotMenu.addAll(menus);

                    if (ESEPPlotTool.VDA.equals(currTool)) {
                        /* Init addFit menu */
                        Menu addFitMenu = spectroPlotMenu.getMenu(SpectroPlotItemBuilder.ADD_FIT);
                        removePointItem = addFitMenu.getItems().get(1);
                        savePointsItem = addFitMenu.getItems().get(3);
                        fitPointsItem = addFitMenu.getItems().get(4);
                        removePointItem.setDisable(true);
                        savePointsItem.setDisable(true);
                        fitPointsItem.setDisable(true);
                    }

                    /* Init zoom menu */
                    Menu zoomMenu = spectroPlotMenu.getMenu(VPlotItemBuilder.ZOOM);
                    //t1Item = zoomMenu.getItems().get(0);
                    //t2Item = zoomMenu.getItems().get(1);
                    //resetItem = zoomMenu.getItems().get(2);

                    zoomMenu.setVisible(true);

                    endCursor();

                } else if (newState == Worker.State.FAILED || newState == Worker.State.CANCELLED) {
                    // thread is aborted
                    DialogBox.warn("No data found !");
                    if (getInsituRanges.getException() != null) {
                        log.info("no data found - " + getInsituRanges.getException().getMessage());
                    }
                    endCursor();
                }
            }
        });
        new Thread(getInsituRanges).start();
        waitCursor();
    }

    private Task<Object> getInsituRangesTask() {
        return new Task<Object>() {
            @Override
            protected Object call() {
                //System.out.println("==>>> ************ SpectroPlotComponent fetchDateRange till " + new Date(100l * 365 * 24 * 60 * 60 * 1000l));
                //+ " with " + spectroPlotModel.getSource() + " " + spectroPlotModel.getType());
                menuDateRangesWrappers = new ArrayList<DateRangeWrapper>();
                //if (spectroPlotMenu.getSource() != null && spectroPlotMenu.getType() == VPlotModel.DataSource.REAL) {
                //for (VPlotModel.Catalog catalog : VPlotModel.Catalog.values()) {
                for (final VPlotModel.CatalogType catType : getCatalogTypes(VPlotModel.Catalog.CME)) {
                    //System.out.println("==>>> ************ SpectroPlotComponent fetchDateRange for " + catType + " from " + (new Date(0l)) + " till " + new Date(100l * 365 * 24 * 60 * 60 * 1000l) + " " + (100l * 365 * 24 * 60 * 60 * 1000l));
                    DateRangeWrapper lDrw = ServiceFactory.getVPlotService().fetchDateRanges(
                            new Date(0l),//1970
                            new Date(100l * 365 * 24 * 60 * 60 * 1000l),//2070
                            VPlotModel.Catalog.SEP.name(), "STEREO-A", catType.name());
                    //System.out.println("************====> Adding for " + VPlotModel.Catalog.SEP.name() + " " + catType.name() + " " + lDrw.getDateRanges().size());
                    menuDateRangesWrappers.add(lDrw);
                }
                //}
                //log.info("Number of found DateRanges=" + dateRangesWrapper.getDateRanges().size());
//                } else {
//                    dateRangesWrapper = null;
//                    //log.info("No data");
                //}
                return true;
            }
        };
    }

    private void waitCursor() {
        pane.setDisable(true);
        root.setCursor(Cursor.WAIT);
    }

    private void endCursor() {
        // default = without error
        endCursor(false);
    }

    private void endCursor(boolean withError) {
        pane.setDisable(withError);
        root.setCursor(null);
    }

    public BooleanProperty getHasChanged() {
        return hasChanged;
    }

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

}
