/* ----------------------------------------
 * Origin  : GFI INFORMATIQUE 
 * Project : CDPP Propagation Tool 
 * ----------------------------------------
 * History 
 * Version : 17/11/2017 : SCA : V2.2.2
 *  Creation
 * Version : 05/05/2021 : LBE
 * DM-ID : Redmine#7043 : 05/05/2021 : Fermi-LAT units corrected
 * Version : 28/01/2022 : LBE
 * FA-ID : Redmine#9809 : 28/01/2022 : Fermi-LAT plot fixed
 * Version : 25/11/2022 : LBE : V2.6
 * DM-ID : Redmine#10630 : AMDA zoom type
 * Version : 06/04/2023 : LBE : V2.7
 * DM-ID : redmine#10486 : 06/04/2023 : removed 0 and gaps added
 * Version : 12/09/2023 : LBE : V2.8
 * DM-ID : Redmine#10629 : 12/09/2023 : SEP plot improvments
 * ----------------------------------------
 */
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.common.FlareCollection;
import fr.tlse.irap.pt.client.controller.common.Formatter;
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.tools.common.MapLegend;
import fr.tlse.irap.pt.client.controller.tools.plot.FluxPlotModel.FluxMeasure;
import fr.tlse.irap.pt.client.controller.tools.plot.FluxPlotModel.FluxType;
import fr.tlse.irap.flare.Flare;
import fr.tlse.irap.flare.Flare.FlareType;
import fr.tlse.irap.flare.FlareFlux;
import fr.tlse.irap.flare.FlareFluxWrapper;
import fr.tlse.irap.pt.client.services.ServiceFactory;
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.event.EventHandler;
import javafx.fxml.FXML;
import javafx.scene.Cursor;
import javafx.scene.Node;
import javafx.scene.chart.LineChart;
import javafx.scene.chart.NumberAxis;
import javafx.scene.chart.NumberAxis.DefaultFormatter;
import javafx.scene.chart.XYChart;
import javafx.scene.chart.XYChart.Data;
import javafx.scene.chart.XYChart.Series;
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.input.MouseButton;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.GridPane;
import javafx.scene.paint.Color;
import javafx.scene.text.Text;

public class FluxPlotComponent extends GridPane implements ICustomComponent, IFluxPlotMenu {

    private static final Logger log = LoggerFactory.getLogger(FluxPlotComponent.class);

    private FluxPlotModel fluxPlotModel = new FluxPlotModel();
    private BooleanProperty hasChanged = new SimpleBooleanProperty();

    private ObjectProperty<Date> startDate;

    private FluxType curFluxType = FluxType.SOFT_XRAYS;
    private List<Color> colorSeries = Arrays.asList(Color.BLUE, Color.RED, Color.VIOLET, Color.YELLOW);
    private List<FlareFluxWrapper> fluxList = new ArrayList<FlareFluxWrapper>();
    private List<Flare> flares;

    @FXML
    protected GridPane root;
    @FXML
    protected BorderPane pane;
    @FXML
    protected LineChart<Number, Number> fluxChart;
    @FXML
    protected LineChart<Number, Number> flareChart;
    @FXML
    protected ScrollPane scrollItems;
    @FXML
    protected TextField legend;
    @FXML
    protected NumberAxis xAxis;
    @FXML
    protected LogarithmicAxis yAxis;
    @FXML
    protected Text flareClassText;

    protected MapLegend fluxLegend;
    private boolean allFlaresVisibility;

    private Double minX, maxX;
    //private Double minY, maxY;

    private MapMenu fluxPlotMenu;
    //private MenuItem t1Item, t2Item, resetItem;

    // selection properties
    private boolean zoomSelection = false;
    private boolean isT1Selected = false;
    private long selectedT1, selectedT2;

    public FluxPlotComponent() {
        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);

        // Init y-axis
        yAxis.setLabel("Intensity (Watts/m²)");//default Value
        //yAxis.setAutoRanging(true);
//		minY = yAxis.getLowerBound();
//		maxY = yAxis.getUpperBound();
//		resizeYAxis(minY, maxY);

        // Init chart
        fluxChart.getStylesheets().add(getClass().getResource("FluxPlotComponent.css").toString());
        flareChart.getStylesheets().add(getClass().getResource("FlareChart.css").toString());
        fluxChart.setLegendVisible(false);
        // bind other charts axis 
        bindAxisChart(flareChart, xAxisFormatter);

        // Create flux chart series 
        for (int i = 0; i < 2; i++) {
            //for (int i=0; i<4; i++) {
            fluxChart.getData().add(new XYChart.Series<Number, Number>());
        }

        // Create flares chart series 
        for (int i = 0; i < 3; i++) {
            flareChart.getData().add(new XYChart.Series<Number, Number>());
        }

        // Init context menu
        loadMenu();

        // 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.FLUXPLOT_RIGHT_CLICK));
        }

    }

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

    /**
     * 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) {
                System.out.println("***** Changed startDate listened");
                // resize date axis according to new start date
                fluxPlotModel.setDates(startDate.get());
                minX = Math.floor((double) (fluxPlotModel.getT1()));
                maxX = Math.ceil((double) (fluxPlotModel.getT2()));
                resizeXAxis(minX, maxX);
                // redraw chart from flux list of the current flux type
                fluxList.clear();
                drawAllFlux(curFluxType);
            }
        });
        // init the FlarePlotComponent from the selected start date
        fluxPlotModel.setDates(startDate.get());
        minX = Math.floor((double) (fluxPlotModel.getT1()));
        maxX = Math.ceil((double) (fluxPlotModel.getT2()));
        resizeXAxis(minX, maxX);
        // redraw chart from flux list of the current flux type 
        drawAllFlux(curFluxType);
    }

    /**
     * Mouse click event
     *
     * @param evt
     */
    private void mouseClick(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()) {

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

//			if (!fluxPlotModel.isValid()) {
//				fluxPlotMenu.getMenu(VPlotItemBuilder.ZOOM).setVisible(false);
//			}
//			else {
            fluxPlotMenu.getMenu(VPlotItemBuilder.ZOOM).setVisible(true);

            double xAxisStep = xAxis.getWidth() / (xAxis.getUpperBound() - xAxis.getLowerBound());
            long XX = (long) (((evt.getX() - xAxisShift - 1) / xAxisStep) + xAxis.getLowerBound());
//				calendar.setTimeInMillis(XX);
//				FlareFlux nearest = flux.getNearestFromTime(calendar);
//				XX = nearest.getEventTime().getTimeInMillis();
            if (zoomSelection) {
                if (!isT1Selected) {
                    selectedT1 = XX;
                    isT1Selected = true;
                    //t1Item.setText(VPlotItemBuilder.ZOOM_T1 + Formatter.getSdf().format(selectedT1));
                } else {
                    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;
                }
            }
//			}
        }
    }

    /**
     * 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());
            double yAxisUpper = Math.log10(yAxis.getUpperBound());
            double yAxisLower = Math.log10(yAxis.getLowerBound());
            //double yAxisStep = yAxis.getHeight() / (yAxis.getUpperBound() - yAxis.getLowerBound());
            double yAxisStep = yAxis.getHeight() / (yAxisUpper - yAxisLower);

            long XX = (long) (((evt.getX() - xAxisShift - 1) / xAxisStep) + xAxis.getLowerBound());
            //double YY = yAxis.getUpperBound() - ((evt.getY() - yAxisShift - 1) / yAxisStep);
            double logYY = yAxisUpper - ((evt.getY() - yAxisShift - 1) / yAxisStep);
            double YY = Math.pow(10, logYY);
//			if (fluxPlotModel.isValid()) {
//				// Display the "nearest" values from curve
//				calendar.setTimeInMillis(XX);
//				FlareFlux nearest = flux.getNearestFromTime(calendar);
//				XX = nearest.getEventTime().getTimeInMillis();
//				YY = nearest.getFlux().doubleValue();
//			}
            // Display mouse coordinates
            if (!curFluxType.equals(FluxType.GAMMA)) {
                legend.setText("Time : " + Formatter.getSdf().format(XX) + " , Intensity 2 : " + Formatter.getFormatterExp().format(YY));
            } else {
                legend.setText("Time : " + Formatter.getSdf().format(XX) + " , Count : " + Formatter.getFormatterExp().format(YY));
            }
            
            
        }
    }

    protected void setLegend(String legend) {
        if (fluxChart.getData().size() > 0) {
            fluxChart.getData().get(0).setName(legend);
        }
    }

    /**
     * Add the items to the menu of flare plot.
     */
    private void loadMenu() {
        // load contextual menu
        fluxPlotMenu = new MapMenu();
        List<Item> menus = new ArrayList<Item>();
        menus.add(FluxPlotItemBuilder.getShowFluxItem(fluxPlotMenu.getMap(), this));
        if (!curFluxType.equals(FluxType.GAMMA)) {        
            menus.add(FluxPlotItemBuilder.getShowFlaresItem(fluxPlotMenu.getMap(), this));
        }
        menus.add(FluxPlotItemBuilder.getZoomItem(fluxPlotMenu.getMap(), this));
        fluxPlotMenu.addAll(menus);

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

//        zoomMenu.setVisible(false);

        // init legend
        fluxLegend = new MapLegend();
        setMapLegend(curFluxType);
        scrollItems.setContent(fluxLegend);
    }

    private void setMapLegend(FluxType fType) {
        // init legend
        int i = 0;
        List<Node> nodes = new ArrayList<Node>();
        for (FluxMeasure fm : FluxPlotItemBuilder.getMeasures(fType)) {
            nodes.add(FluxPlotModel.createNodeLabel(fm, colorSeries.get(i++)));
        }
        fluxLegend.setNodes(nodes);
    }

    @Override
    public void runSetZoom() {
        //t2Item.setDisable(false);
        zoomSelection = true;
    }


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

    @Override
    public void runShowAllFlares() {

        if (flareChart.getData().size() > 0) {
            // swap all Flare visibility
            allFlaresVisibility = !allFlaresVisibility;
            for (int i = 0; i < FluxPlotModel.fc.length; i++) {
                Node node = flareChart.getData().get(i).getNode();
                node.setVisible(allFlaresVisibility);
            }
        } else {
            drawAllFlares();
        }
    }

    @Override
    public void runShowFlares(FlareType fCat) {
        drawFlares(fCat);
    }

    /**
     * Show/Hide one flux plot Version compatible since Java 7
     */
    @Override
    public void runShowFlux(FluxType fType, FluxMeasure fMeasure, int index) {
        if (fType != curFluxType) {
            setMapLegend(fType);
            fluxList.clear();
            if (fType.equals(FluxType.GAMMA)) {
                yAxis.setLabel("Counts (counts/cm^2/s)");
                Number lLowerBound = 1.E-7;
                Number lUpperBound = 1.E-1;
                yAxis.setRange(new Number[]{lLowerBound, lUpperBound}, false);
                flareClassText.setVisible(false);
            } else {
                yAxis.setLabel("Intensity (Watts/m²)");
                Number lLowerBound = 1.E-9;
                Number lUpperBound = 1.E-3;
                yAxis.setRange(new Number[]{lLowerBound, lUpperBound}, false);
                flareClassText.setVisible(true);
            }
            drawFlux(fType, index, true);
            curFluxType = fType;
            loadMenu();
        } else {
            drawFlux(fType, index, false);
        }
    }

    /**
     * Show/Hide all flux plots
     */
    @Override
    public void runShowAllFlux(FluxType fType) {
        if (fType == curFluxType) {
            if (isPlot(0)) {
                for (int i = 0; i < fluxChart.getData().size(); i++) {
                    erasePlot(i);
                }
            } else {
                drawAllFlux(fType);
            }
        } else {
            setMapLegend(fType);
            for (int i = 0; i < fluxChart.getData().size(); i++) {
                erasePlot(i);
            }
            fluxList.clear();
            if (fType.equals(FluxType.GAMMA)) {
                yAxis.setLabel("Counts (counts/cm^2/s)");
                Number lLowerBound = 1.E-7;
                Number lUpperBound = 1.E-1;
                yAxis.setRange(new Number[]{lLowerBound, lUpperBound}, false);
                flareClassText.setVisible(false);
            } else {
                yAxis.setLabel("Intensity (Watts/m²)");
                Number lLowerBound = 1.E-9;
                Number lUpperBound = 1.E-3;
                yAxis.setRange(new Number[]{lLowerBound, lUpperBound}, false);
                flareClassText.setVisible(true);
            }
            drawAllFlux(fType);
            curFluxType = fType;
            loadMenu();
        }
    }


    private void resetZoomFields() {
        selectedT1 = 0;
        isT1Selected = false;
        zoomSelection = false;
//        t1Item.setText(VPlotItemBuilder.ZOOM_T1);
//        t2Item.setText(VPlotItemBuilder.ZOOM_T2);
//        t2Item.setDisable(true);
//        resetItem.setDisable(true);
    }

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

                    clearDataCharts();
                    for (int i = 0; i < fluxList.size(); i++) {
                        addPlot(fluxList.get(i), i);
                        if (newFType) {
                            fluxLegend.setNodeOpacity(fluxLegend.getChildren().get(i), false);
                        }
                        if (fluxLegend.isVisibleNode(fluxLegend.getChildren().get(i))) {
                            if (i == index) {
                                erasePlot(i);
                            }
                        } else if (i == index) {
                            fluxLegend.setNodeOpacity(fluxLegend.getChildren().get(i), true);
                        } else {
                            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 FluxType fType) {
        final Task<Object> getFlux = this.getFlux(fType);
        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);

                    clearDataCharts();
                    for (int i = 0; i < fluxList.size(); i++) {
                        addPlot(fluxList.get(i), i);
                        // show legend
                        if (i < fluxLegend.getChildren().size()) {
                            fluxLegend.setNodeOpacity(fluxLegend.getChildren().get(i), true);
                        }
                    }
                    //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 measures flare flux of a flux type between start and end dates
     */
    private Task<Object> getFlux(final FluxType fType) {
        return new Task<Object>() {
            @Override
            protected Object call() throws Exception {
                
                System.out.println("***** Call getFlux");
                
                //fluxList.clear();
                if (fluxList.isEmpty()) {
                    for (FluxMeasure fm : FluxPlotItemBuilder.getMeasures(fType)) {
                        //log.info("Call getFlareFlux web service for type " + fType.name() + " and measure " + fm.getDirName());
                        FlareFluxWrapper flux = ServiceFactory.getFlareService()
                                .getFlux(fType.name(), fm.getDirName(), fluxPlotModel.getStartDate(), fluxPlotModel.getEndDate());
                        fluxList.add(flux);
                    }
                }
                return true;
            }
        };
    }

    /**
     * Draw flares of one category
     *
     * @param fCat
     */
    private void drawFlares(final FlareType fCat) {
        final Task<Object> getFlares = this.getFlares();
        getFlares.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
                    if (flareChart.getData().size() == 0) {
                        for (int i = 0; i < FluxPlotModel.fc.length; i++) {
                            addFlares(flares, i);
                            Node node = flareChart.getData().get(i).getNode();
                            node.setVisible(!node.isVisible());
                        }
                    }
                    // swap visibility
                    for (int i = 0; i < FluxPlotModel.fc.length; i++) {
                        if (fCat.equals(FluxPlotModel.fc[i])) {
                            Node node = flareChart.getData().get(i).getNode();
                            node.setVisible(!node.isVisible());
                        }
                    }
                    endCursor();

                } else if (newState == Worker.State.FAILED || newState == Worker.State.CANCELLED) {
                    endCursor(true);
                    log.info("get flares failed - " + getFlares.getException().getMessage());
                }
            }
        });

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

    /**
     * Draw all flares
     */
    private void drawAllFlares() {
        final Task<Object> getFlares = this.getFlares();
        getFlares.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
                    for (int i = 0; i < FluxPlotModel.fc.length; i++) {
                        addFlares(flares, i);
                    }
                    endCursor();

                } else if (newState == Worker.State.FAILED || newState == Worker.State.CANCELLED) {
                    // aborted
                    endCursor(true);
                    log.info("get all flares failed - " + getFlares.getException().getMessage());
                }
            }
        });

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

    private Task<Object> getFlares() {
        return new Task<Object>() {
            @Override
            protected Object call() throws Exception {
                if (flareChart.getData().size() == 0) {
                    if (curFluxType.equals(FluxType.SOFT_XRAYS)) {
                        // get flares only for Soft X-Rays
                        flares = FlareCollection.getFlaresAround(startDate.getValue());
                    } else {
                        flares = null;
                    }
                }
                return true;
            }
        };
    }

    /**
     * Create plot series at index position from a FlareFlux list Version compatible since Java 7
     */
    private void addPlot(FlareFluxWrapper flux, int index) {
        if ((flux != null) && (flux.getFlares() != null) && (flux.getFlares().size() > 0)) {
            // create chart plot series
            List<Series<Number, Number>> lSeries = createSeries(flux.getFlares());
            if(lSeries.size()==1) {
                fluxChart.getData().add(index, lSeries.get(0));
            }else {
                fluxChart.getData().addAll(index, lSeries);
                //fluxChart.getStylesheets().add("FluxPlotComponent_GAMMA.css");
            }
            // resize axis
            resizeXAxis(minX, maxX);
        }
    }

    private void addFlares(List<Flare> flares, int index) {
        if (flares != null) {
            double yMin = ((LogarithmicAxis) flareChart.getYAxis()).getLowerBound() * 0.1;
            Series<Number, Number> series = new Series<Number, Number>();
            for (Flare flare : flares) {
                if (flare.getCategory().equals(FluxPlotModel.fc[index])) {
                    series.getData().add(new Data<Number, Number>(flare.getEventTime().getTime(), yMin));
                    series.getData().add(new Data<Number, Number>(flare.getEventTime().getTime(), flare.getRealIntensity()));
                    series.getData().add(new Data<Number, Number>(flare.getEventTime().getTime(), yMin));
                }
            }
            flareChart.getData().add(index, series);
        }
    }

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

    /**
     * Test if a plot exists at index position
     */
    private boolean isPlot(int index) {
        if (index < fluxChart.getData().size()) {
            return (fluxChart.getData().get(index).getData().size() > 0);
        } else {
            return false;
        }
    }

    private void clearDataCharts() {
        fluxChart.getData().clear();
        flareChart.getData().clear();
        allFlaresVisibility = false;
    }

//	/**
//	 * Version compatible since Java 8 
//	 */
//	@Override
//	public void runShowPlot(FluxType fType, FluxMeasure fMeasure, int index) {
//		if (fType == curFluxType) {
//			if (isPlot(index)) {
//				erasePlot(index);
//			}
//
//			else {
//				drawFlux(fType, fMeasure, index);
//			}
//		}
//		else {
//			setMapLegend(fType);
//			for (int i=0; i<fluxChart.getData().size(); i++) {
//				erasePlot(i);
//			}
//			drawFlux(fType, fMeasure, index);
//			curFluxType = fType;
//		}
//	}
//	/**
//	 * Draw flare flux series of a measure flux  
//	 * Version compatible since Java 8  
//	 */
//   private void drawFlux(final FluxType fType, final FluxMeasure fMeasure, final int index) {
//		final Task<Object> getFlux = this.getFlux(fType, fMeasure, index);
//		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 (flaresList.size() > 0) {
//						drawPlot(flaresList.get(0), index);
//					}
//					//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());
//				}
//			}
//		});
//
//		new Thread(getFlux).start();
//		waitCursor();
//	}
//
//	/**
//	 * Get flare flux of a flux measure between start and End Dates (WS) 
//	 * Version compatible since Java 8  
//	 */
//	private Task<Object> getFlux(final FluxType fType, final FluxMeasure fMeasure, final int index) {
//		return new Task<Object>() {
//			@Override
//			protected Object call() throws Exception {
//				FlareFluxWrapper flux = ServiceFactory.getFlareService()
//						.getFlux(fType.name(), fMeasure.getDirName(), fluxPlotModel.getStartDate(), fluxPlotModel.getEndDate());
//				fluxList.clear();
//				fluxList.add(flux);
//				return true;
//			}
//		};
//	}
    //	/**
//	 * Draw all flare flux series of a flux type  
//	 * Version compatible since Java 8  
//	 */
//    private void drawAllFlux(final FluxType fType) {
//		final Task<Object> getFlux = this.getFlux(fType);
//		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);
//					
//					fluxChart.getData().clear();
//					for (int i=0; i<flaresList.size(); i++) {
//						drawPlot(flaresList.get(i), i);
//					}
//					//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());
//                    getFlares.getException().printStackTrace();
//				}
//			}
//		});
//
//		new Thread(getFlux).start();
//		waitCursor();
//	}
//	/**
//  * Create plot series at index position from a FlareFlux list
//	 * Version compatible since Java 8  
//  */
//	private void drawPlot(FlareFluxWrapper flux, int index) {
//		if ((flux != null) && (flux.getFlares() != null) && (flux.getFlares().size() > 0)) {
//			// create chart plot series
//	        fluxChart.getData().set(index, createSeries(flux.getFlares()));
//			// resize axis
//			resizeXAxis(minX, maxX);
//	        // show legend
//			if (index < fluxLegend.getChildren().size()) {
//				fluxLegend.setNodeOpacity(fluxLegend.getChildren().get(index), true);
//			}
//		}
//	}
    private void resizeXAxis(double tMin, double tMax) {
        xAxis.setLowerBound(tMin);
        xAxis.setUpperBound(tMax);
        xAxis.setTickUnit((tMax - tMin) / FluxPlotModel.NB_TICS);
    }

    /**
     * Create series
     *
     * @param flares
     */
    private List<Series<Number, Number>> createSeries(List<FlareFlux> fluxList) {
        //cut in series depending on gaps
        List<Series<Number, Number>> allSeries = new ArrayList<Series<Number, Number>>();
        Series<Number, Number> series = new Series<Number, Number>();
        
        fluxChart.getStylesheets().clear();
        if (FluxType.GAMMA.equals(curFluxType)) {
            //special treatment for gamma because of too many 0 causing bad display.
            long lGapMillisec = 20 * 60 * 1000l; //20mn
            long lastTime = -1;
            fluxChart.getStylesheets().add("FluxPlotComponent_GAMMA.css");
            
            for (FlareFlux flux : fluxList) {
                //System.out.println("CreateSeries: "+flux.getEventTime().getTime().toString()+" "+flux.getFlux());
                //if(flux.getFlux()>0) test for gamma horizontal line bug 10486
//            series.getData().add(new Data<Number, Number>(flux.getEventTime().getTimeInMillis(), flux.getFlux()));
//        }
                if (flux.getFlux() <= 0) {
                    continue;
                }
                //System.out.println("Checking value at time " + solarSpeed.getEventTime().getTime() + " val= " + solarSpeed.getSpeed());
                if (lastTime != -1 && (flux.getEventTime().getTimeInMillis() - lastTime) > lGapMillisec) {
                    //System.out.println("Adding series at " + flux.getEventTime().getTime());
                    if (!series.getData().isEmpty()) {
                        allSeries.add(series);
                    }
                    series = new Series<Number, Number>();
                }
                series.getData().add(new Data<Number, Number>(flux.getEventTime().getTimeInMillis(), flux.getFlux()));
                //System.out.println("Adding " + solarSpeed.getSpeed() + " at " + solarSpeed.getEventTime().getTime());
                //currentReadSpeeds.add(solarSpeed);
                lastTime = flux.getEventTime().getTimeInMillis();
            }
            if (!series.getData().isEmpty()) {
                allSeries.add(series);
            }

        } else {
            fluxChart.getStylesheets().add(getClass().getResource("FluxPlotComponent.css").toString());
            for (FlareFlux flux : fluxList) {
                series.getData().add(new Data<Number, Number>(flux.getEventTime().getTimeInMillis(), flux.getFlux()));
            }
            if (!series.getData().isEmpty()) {
                allSeries.add(series);
            }
        }
        
        
        
        
        return allSeries;
    }

    public FluxPlotModel getFluxPlotModel() {
        return fluxPlotModel;
    }

    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;
    }

}
