/* ----------------------------------------
 * Origin  : GFI INFORMATIQUE 
 * Project : CDPP Propagation Tool 
 * ----------------------------------------
 * History 
 * Version : 01/05/2015 : SCA : V2.1
 * Version : 30/06/2022 : LBE : V2.5
 * DM-ID : Redmine#9582 : 30/06/2022 : data range is made by selecting a date from catalogue in menu
 * DM-ID : Redmine#7278 : 05/08/2022 : manage data gaps display
 * Version : 25/11/2022 : LBE : V2.6
 * DM-ID : Redmine#10630 : AMDA zoom type
 * ----------------------------------------
 */
package fr.tlse.irap.pt.client.controller.tools.plot;

import java.util.ArrayList;
import java.util.Calendar;
import java.util.Collections;
import java.util.Date;
import java.util.GregorianCalendar;
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.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 static fr.tlse.irap.pt.client.controller.tools.plot.VPlotItemBuilder.getCatalogTypes;
import fr.tlse.irap.pt.client.controller.tools.plot.VPlotModel.Catalog;
import fr.tlse.irap.pt.client.controller.tools.plot.VPlotModel.DataSource;
import fr.tlse.irap.pt.client.services.ServiceFactory;
import fr.tlse.irap.spaceElement.Planet;
import fr.tlse.irap.spaceElement.SpaceElement;
import fr.tlse.irap.vplot.DateRange;
import fr.tlse.irap.vplot.DateRangeWrapper;
import fr.tlse.irap.vplot.SolarSpeed;
import fr.tlse.irap.vplot.SolarSpeedWrapper;
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.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.shape.Line;

public class VPlotComponent extends GridPane implements ICustomComponent, IVPlotMenu {

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

    private VPlotModel vPlotModel = new VPlotModel();
    private BooleanProperty hasChanged = new SimpleBooleanProperty();

    private ObjectProperty<Date> startDate;
    private SpaceElement startElement;

    private SolarSpeedWrapper speedsWrapper;
    private DateRangeWrapper dateRangesWrapper;
    private List<DateRangeWrapper> menuDateRangesWrappers;

    @FXML
    protected GridPane root;
    @FXML
    protected BorderPane pane;
    @FXML
    protected LineChart<Number, Number> speedChart;
    @FXML
    protected LineChart<Number, Number> startDateChart; //Used to draw start date vertical line
    @FXML
    protected LineChart<Number, Number> dateRangeChart;
    @FXML
    protected TextField legend;

    private Line hLine;
    private Line vLine;
    private NumberAxis xAxis;
    private NumberAxis yAxis;
    private Double minX, maxX;
    private Double minY, maxY;

    //private VPlotMenu vPlotMenu;
    private MapMenu vPlotMenu;
    //private MenuItem t1Item, t2Item, resetItem, simuItem;
    private Calendar calendar = new GregorianCalendar();

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

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

        // Init x-axis
        xAxis = (NumberAxis) speedChart.getXAxis();
        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 = (NumberAxis) speedChart.getYAxis();
        minY = yAxis.getLowerBound();
        maxY = yAxis.getUpperBound();
        //resizeAxis(yAxis, minY, maxY);
        resizeYAxis(minY, maxY);

        // bind other charts axis 
        bindAxisChart(dateRangeChart, xAxisFormatter);
        bindAxisChart(startDateChart, 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
        speedChart.getStylesheets().add(getClass().getResource("VPlotComponent.css").toString());
        startDateChart.getStylesheets().add(getClass().getResource("VPlotComponentStartDateLineChart.css").toString());
        dateRangeChart.getStylesheets().add(getClass().getResource("VPlotCatalog.css").toString());
        speedChart.setLegendVisible(false);
        // Init context menu
        loadMenu();
        // Create tooltips
        if (!TOOLTIP.isDisable()) {
            // Initialise tooltips except for Mac OS
            Tooltip.install(pane, new Tooltip(TOOLTIP.VPLOT_RIGHT_CLICK));
        }
        pane.setOnMouseClicked(mouseHandler);
        pane.setOnMouseMoved(mouseHandler);
    }

    /**
     * Bind start date to the vPlot 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
                vPlotModel.setDates(startDate.get());
                minX = Math.floor((double) (vPlotModel.getT1()));
                maxX = Math.ceil((double) (vPlotModel.getT2()));
                resizeAxis(xAxis, minX, maxX);
                // redraw chart from Solar speeds
                drawSolarSpeeds(null);
            }
        });
        // init the VPlotComponent from the selected start date
        vPlotModel.setDates(startDate.get());
        minX = Math.floor((double) (vPlotModel.getT1()));
        maxX = Math.ceil((double) (vPlotModel.getT2()));
        resizeAxis(xAxis, minX, maxX);

    }

    /**
     * Bind Start Element to the vPlot 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(VPlotItemBuilder.DATA_SOURCE).setDisable(!(startElement instanceof Planet));
                    drawSolarSpeeds(null);
                }
            }
        });
        // first init 
        startElement = startSourceProperty.get();
        vPlotMenu.getMenu(VPlotItemBuilder.DATA_SOURCE).setDisable(!(startElement instanceof Planet));
        drawSolarSpeeds(null);
    }

    private EventHandler<MouseEvent> mouseHandler = new EventHandler<MouseEvent>() {
        @Override
        public void handle(MouseEvent mouseEvent) {
            if (mouseEvent.getEventType() == MouseEvent.MOUSE_CLICKED || mouseEvent.getEventType() == MouseEvent.MOUSE_MOVED) {
                double xAxisShift = xAxis.getLayoutX();
                double yAxisShift = yAxis.getLayoutY() + ((Node) yAxis.getParent()).getLayoutY();
                // Treat only if mouse is inside axis
                if (mouseEvent.getY() > yAxisShift
                        && mouseEvent.getY() < yAxisShift + yAxis.getHeight()
                        && mouseEvent.getX() > xAxisShift
                        && mouseEvent.getX() < xAxisShift + xAxis.getWidth()) {
                    double xAxisStep = xAxis.getWidth() / (xAxis.getUpperBound() - xAxis.getLowerBound());
                    double yAxisStep = yAxis.getHeight() / (yAxis.getUpperBound() - yAxis.getLowerBound());

                    long XX = (long) (((mouseEvent.getX() - xAxisShift - 1) / xAxisStep) + xAxis.getLowerBound());
                    double YY = yAxis.getUpperBound() - ((mouseEvent.getY() - yAxisShift - 1) / yAxisStep);

                    if (mouseEvent.getEventType() == MouseEvent.MOUSE_MOVED) {
                        hLine.setStartX(xAxisShift);
                        hLine.setEndX(xAxisShift + xAxis.getWidth());
                        vLine.setStartY(yAxisShift);
                        vLine.setEndY(yAxisShift + yAxis.getHeight());

                        if (vPlotModel.isValid()) {
                            calendar.setTimeInMillis(XX);
                            SolarSpeed nearest = speedsWrapper.getNearestFromTime(calendar);
                            long nearestXX = nearest.getEventTime().getTimeInMillis();
                            double nearestYY = nearest.getSpeed().doubleValue();
                            // Force HLine to the Y value of the nearest measurement
                            double yHL = (yAxis.getUpperBound() - nearestYY) * yAxisStep + yAxisShift + 1;
                            hLine.setStartY(yHL);
                            hLine.setEndY(yHL);
                            // Force vLine to the X value of the nearest measurement
                            double xVL = ((nearestXX - xAxis.getLowerBound()) * xAxisStep) + xAxisShift + 1;
                            vLine.setStartX(xVL);
                            vLine.setEndX(xVL);
                            // Display the "nearest" values from curve
                            //setLegend("Time : " + Formatter.getSdf().format(nearestXX) + " , Speed : " + Formatter.getFormatter().format(nearestYY));
                            legend.setText("Time : " + Formatter.getSdf().format(nearestXX) + " , Speed : " + Formatter.getFormatter().format(nearestYY));
                        } else {
                            hLine.setStartY(mouseEvent.getY());
                            hLine.setEndY(mouseEvent.getY());

                            vLine.setStartX(mouseEvent.getX());
                            vLine.setEndX(mouseEvent.getX());
                            // Display mouse coordinates
                            //setLegend("Time : " + Formatter.getSdf().format(XX) + " , Speed : " + Formatter.getFormatter().format(YY));
                            legend.setText("Time : " + Formatter.getSdf().format(XX) + " , Speed : " + Formatter.getFormatter().format(YY));
                        }
                    } else if (mouseEvent.getEventType() == MouseEvent.MOUSE_CLICKED) {

                        if (mouseEvent.getButton() == MouseButton.PRIMARY) {
                            vPlotMenu.hide();
                        } else if (mouseEvent.getButton() == MouseButton.SECONDARY) {
                            System.out.println("Show menu id " + vPlotMenu);

                            vPlotMenu.show(pane, mouseEvent.getScreenX(), mouseEvent.getScreenY());
                        }
                        if (!vPlotModel.isValid()) {
                            vPlotMenu.getMenu(VPlotItemBuilder.ZOOM).setVisible(false);
                        } else {
                            vPlotMenu.getMenu(VPlotItemBuilder.ZOOM).setVisible(true);
                            calendar.setTimeInMillis(XX);
                            SolarSpeed nearest = speedsWrapper.getNearestFromTime(calendar);
                            long nearestXX = nearest.getEventTime().getTimeInMillis();
                            double nearestYY = nearest.getSpeed().doubleValue();

                            if (mouseEvent.getButton() == MouseButton.PRIMARY) {
                                if (zoomSelection) {
                                    if (!isT1Selected) {
                                        selectedT1 = nearestXX;
                                        isT1Selected = true;
                                        //t1Item.setText(VPlotItemBuilder.ZOOM_T1 + Formatter.getSdf().format(selectedT1));
                                    } else {
                                        selectedT2 = nearestXX;
                                        //t2Item.setText(VPlotItemBuilder.ZOOM_T2 + Formatter.getSdf().format(selectedT2));
                                        if (selectedT2 > selectedT1) {
                                            resizeAxis(xAxis, selectedT1, selectedT2);
                                            resetZoomFields();
                                            // allow reset
                                            // resetItem.setDisable(false);
                                        }
                                        isT1Selected = false;
                                        zoomSelection = false;
                                    }
                                } else {
                                    // set current data
                                    vPlotModel.setTime(nearestXX);
                                    vPlotModel.setSpeed(nearestYY);
                                    // notify change
                                    getHasChanged().set(true);
                                    getHasChanged().set(false);
                                }
                                //} else if (mouseEvent.getButton() == MouseButton.SECONDARY) {
                            }
                        }
                    }
                }
            }

        }
    };

    /**
     * Create series
     *
     * @param solarSpeeds
     */
    private List<Series<Number, Number>> createSeries(List<SolarSpeed> solarSpeeds, int pSampling) {
        //manage gaps by adding NaN to avoid join in plot.
//        long lGapMillisec = 5 * 60 * 1000l;
//        List<SolarSpeed> currentReadSpeeds = new ArrayList<SolarSpeed>();
//        long lastTime = -1;
//        for (SolarSpeed solarSpeed : solarSpeeds) {
//            if (lastTime != -1 && (solarSpeed.getEventTime().getTimeInMillis() - lastTime) > lGapMillisec) {
//                SolarSpeed lNaNSp = new SolarSpeed();
//                lNaNSp.setSpeed(Float.NaN);
//                Calendar lNaNCal = Calendar.getInstance(TimeZone.getTimeZone("UTC"));
//                lNaNCal.setTimeInMillis(solarSpeed.getEventTime().getTimeInMillis() - 1);
//                lNaNSp.setEventTime(lNaNCal);
//                currentReadSpeeds.add(lNaNSp);
//                System.out.println("Adding NaN at " + lNaNSp.getEventTime().getTime());
//            }
//            System.out.println("Adding " + solarSpeed.getSpeed() + " at " + solarSpeed.getEventTime().getTime());
//            currentReadSpeeds.add(solarSpeed);
//            lastTime = solarSpeed.getEventTime().getTimeInMillis();
//        }
        //cut in series depending on gaps
        List<Series<Number, Number>> allSeries = new ArrayList<Series<Number, Number>>();
        long lGapMillisec = 5 * pSampling * 1000l;
        long lastTime = -1;
        Series<Number, Number> series = new Series<Number, Number>();
        for (SolarSpeed solarSpeed : solarSpeeds) {
            //System.out.println("Checking value at time " + solarSpeed.getEventTime().getTime() + " val= " + solarSpeed.getSpeed());
            if (lastTime != -1 && (solarSpeed.getEventTime().getTimeInMillis() - lastTime) > lGapMillisec) {
                //System.out.println("Adding series at " + solarSpeed.getEventTime().getTime());
                if (!series.getData().isEmpty()) {
                    allSeries.add(series);
                }
                series = new Series<Number, Number>();
            }
            series.getData().add(new Data<Number, Number>(solarSpeed.getEventTime().getTimeInMillis(), solarSpeed.getSpeed()));
            //System.out.println("Adding " + solarSpeed.getSpeed() + " at " + solarSpeed.getEventTime().getTime());
            //currentReadSpeeds.add(solarSpeed);
            lastTime = solarSpeed.getEventTime().getTimeInMillis();
        }
        if (!series.getData().isEmpty()) {
            allSeries.add(series);
        }


        /*        List<Series<Number, Number>> allSeries = new ArrayList<Series<Number, Number>>();
        Series<Number, Number> series = null;
        if (solarSpeeds.size() > 0) {
            //series = new Series<Number, Number>();
            for (SolarSpeed solarSpeed : solarSpeeds) {
                series = new Series<Number, Number>();
                series.getData().add(new Data<Number, Number>(solarSpeed.getEventTime().getTimeInMillis(), solarSpeed.getSpeed()));
                allSeries.add(series);
                
            }
            //allSeries.add(series);
        }
         */
        return allSeries;
    }

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

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

    /**
     * Add the items to the menu of vplot.
     */
    private void loadMenu() {
        // load contextual menu
//        if(vPlotMenu==null)
//            vPlotMenu = new MapMenu();
//        vPlotMenu.removeAll();
//        List<Item> menus = new ArrayList<Item>();
//        menus.add(VPlotItemBuilder.getChooseDataSourceItem(vPlotMenu.getMap(), this));
//        menus.add(VPlotItemBuilder.getInSituCatalogItem(vPlotMenu.getMap(), this));
//        menus.add(VPlotItemBuilder.getZoomItem(vPlotMenu.getMap(), this));
//        vPlotMenu.addAll(menus);
//        System.out.println("Updated menu id "+vPlotMenu);
//
//        /* Init zoom menu */
//        Menu zoomMenu = vPlotMenu.getMenu(VPlotItemBuilder.ZOOM);
//        t1Item = zoomMenu.getItems().get(0);
//        t2Item = zoomMenu.getItems().get(1);
//        resetItem = zoomMenu.getItems().get(2);
//
//        zoomMenu.setVisible(false);
        getInsituRanges();
    }

    @Override
    public void runChooseData(DataSource type) {
        // redraw chart from Solar speeds
        drawSolarSpeeds(type);
    }

    @Override
    public void runInSituCatalog(String catalog, String catType) {
        vPlotModel.setCatalog(catalog);
        vPlotModel.setCatType(catType);
        drawInsitu();
    }

    /**
     * Get all catalog ranges
     *
     * @param catMenu Menu to add ranges
     * @param catalog
     * @param catType
     */
    @Override
    public void runInSituCatalogDataRange(Item catMenu, final List<Catalog> catalogs) {
        //vPlotModel.setCatalog(catalog);
        //vPlotModel.setCatType(catType);
        //getInsituRanges(catMenu,catalogs);
    }

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

//    @Override
//    public void runZoomT2() {
//        if (selectedT2 > selectedT1) {
//            resizeAxis(xAxis, selectedT1, selectedT2);
//            resetFields();
//            // allow reset
//            resetItem.setDisable(false);
//        }
//    }
    @Override
    public void runResetZoom() {
        resizeAxis(xAxis, minX, maxX);
        resetZoomFields();
    }

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

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

    private void resizeYAxis(double vMin, double vMax) {
        minY = Math.floor(vMin * 0.99 / VPlotModel.DELTA_V) * VPlotModel.DELTA_V;
        maxY = Math.ceil(vMax * 1.01 / VPlotModel.DELTA_V) * VPlotModel.DELTA_V;
        yAxis.setLowerBound(minY);
        yAxis.setUpperBound(maxY);
        yAxis.setTickUnit(VPlotModel.DELTA_V);
    }

    /**
     * Draw chart series from Solar speeds
     */
    private void drawSolarSpeeds(final DataSource ds) {
        final Task<Object> getSolarSpeeds = this.getSolarSpeeds(ds);
        getSolarSpeeds.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
                    //dateRangeChart.getData().clear();
                    speedChart.getData().clear();
                    startDateChart.getData().clear();
                    if ((speedsWrapper != null) && (speedsWrapper.getSolarSpeeds() != null) && (speedsWrapper.getSolarSpeeds().size() > 0)) {
                        // resize axis
                        resizeAxis(xAxis, minX, maxX);
                        double vMin = Collections.min(speedsWrapper.getSolarSpeeds()).getSpeed().doubleValue();
                        double vMax = Collections.max(speedsWrapper.getSolarSpeeds()).getSpeed().doubleValue();
                        resizeYAxis(vMin, vMax);

                        NumberAxis yAxisRange = (NumberAxis) dateRangeChart.getYAxis();
                        // add start time series (vertical bar line)
                        Series<Number, Number> series = new Series<Number, Number>();
                        series.getData().add(new Data<Number, Number>(startDate.get().getTime(), yAxisRange.getLowerBound()));
                        series.getData().add(new Data<Number, Number>(startDate.get().getTime(), yAxisRange.getUpperBound()));
                        startDateChart.getData().add(series);

                        //speedChart.setCreateSymbols(true);
                        // create chart series
                        speedChart.getData().addAll(createSeries(speedsWrapper.getSolarSpeeds(), speedsWrapper.getSampling()));

                        // initialize speed with the nearest measurement data
                        Calendar calendar = new GregorianCalendar();
                        calendar.setTime(vPlotModel.getDate());
                        vPlotModel.setSpeed(speedsWrapper.getNearestFromTime(calendar).getSpeed());
                        vPlotModel.setValid(true);
                    } else {
                        vPlotModel.setValid(false);
                    }
                    vPlotModel.updateView();
                    speedChart.setTitle(vPlotModel.getSourceLabel());
                    endCursor();
                    loadMenu();
                    // notify change 
                    getHasChanged().set(true);
                    getHasChanged().set(false);

                } else if (newState == Worker.State.FAILED || newState == Worker.State.CANCELLED) {
                    // aborted
                    dateRangeChart.getData().clear();
                    speedChart.getData().clear();
                    startDateChart.getData().clear();
                    vPlotModel.setValid(false);
                    vPlotModel.updateView();
                    speedChart.setTitle(vPlotModel.getSourceLabel());
                    endCursor(true);
                    log.info("get Solar Speeds failed - " + getSolarSpeeds.getException().getMessage());
                }
            }
        });

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

    /**
     * Get solar wind speeds from StartElement between start and End Dates (WS)
     */
    private Task<Object> getSolarSpeeds(final DataSource type) {
        return new Task<Object>() {
            @Override
            protected Object call() throws Exception {
                speedsWrapper = null;
                if (type != null) {
                    vPlotModel.update(startElement, type);
                    if (vPlotModel.getSource() != null) {
                        // get solar speed data of selected data source type
                        speedsWrapper = ServiceFactory.getVPlotService().fetchSolarSpeeds(vPlotModel.getStartDate(), vPlotModel.getEndDate(), vPlotModel.getSource());
                    }
                } else {
                    vPlotModel.update(startElement, DataSource.REAL);
                    if (vPlotModel.getSource() != null) {
                        // get real solar speed data
                        speedsWrapper = ServiceFactory.getVPlotService().fetchSolarSpeeds(vPlotModel.getStartDate(), vPlotModel.getEndDate(), vPlotModel.getSource());
                    }
                    if (startElement instanceof Planet) {
                        if ((speedsWrapper == null) || (speedsWrapper.getSolarSpeeds() == null) || (speedsWrapper.getSolarSpeeds().size() == 0)) {
                            vPlotModel.update(startElement, DataSource.SIMULATED);
                            if (vPlotModel.getSource() != null) {
                                // get simulated solar speed data of planet
                                speedsWrapper = ServiceFactory.getVPlotService().fetchSolarSpeeds(vPlotModel.getStartDate(), vPlotModel.getEndDate(), vPlotModel.getSource());
                            }
                        }
                    }
                }
                //log.info("Number of found SolarSpeeds=" + speedsWrapper.getSolarSpeeds().size());
                return true;
            }
        };
    }

    private void drawDateRangeBars(final DateRange pDr) {
        dateRangeChart.getData().clear();
        NumberAxis yAxisRange = (NumberAxis) dateRangeChart.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()));
        dateRangeChart.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()));
        dateRangeChart.getData().add(series);
    }

    /**
     * Update InSitu from catalogue Contains menu building too
     *
     * @param source
     * @param type
     */
    public void getInsituRanges() {
        final Task<Object> getInsituRanges = this.getInsituRangesTask();
        final VPlotComponent lThis = this;
        getInsituRanges.stateProperty().addListener(new ChangeListener<Worker.State>() {
            @Override
            public void changed(ObservableValue<? extends Worker.State> observableValue, Worker.State oldState, Worker.State newState) {
                vPlotMenu = new MapMenu();
                List<Item> menus = new ArrayList<Item>();
                menus.add(VPlotItemBuilder.getChooseDataSourceItem(vPlotMenu.getMap(), lThis));
                Item lInSituMenuItem = new Item("In situ catalogue");
                if (newState == Worker.State.SUCCEEDED) {
                    if ((menuDateRangesWrappers != null) && (menuDateRangesWrappers.size() > 0)) {
                        //System.out.println("==>>> ************ VPlotComponent got " + menuDateRangesWrappers.size() + " ranges ");
                        int lItemIdx = 0;
                        for (Catalog catalog : Catalog.values()) {
                            Item lCatItem = new Item(catalog.getName());
                            for (final VPlotModel.CatalogType catType : getCatalogTypes(catalog)) {
                                Item lCatTypeItem = new Item(catType.getName());
                                //System.out.println("Adding to menu "+menuDateRangesWrappers.get(lItemIdx).getDateRanges().size()+" ranges");
                                for (final DateRange lDr : menuDateRangesWrappers.get(lItemIdx++).getDateRanges()) {
                                    Item lDateItem = new Item("" + lDr.getStartTime());
                                    vPlotMenu.getMap().put(lDateItem, new ISelectedItem() {
                                        @Override
                                        public void run() {
                                            //vPlotMenu.runInSituCatalog(catalog.name(), catType.name());
                                            long lCenterTime = (lDr.getStartTime().getTime() + lDr.getEndTime().getTime()) / 2l;
                                            //12/08/2022 meeting: set Time to start
                                            vPlotModel.setTime(lDr.getStartTime().getTime());
                                            // notify change
                                            getHasChanged().set(true);
                                            getHasChanged().set(false);
                                            //runInSituCatalog(catalog.name(), catType.name());
                                            drawDateRangeBars(lDr);
                                        }
                                    });
                                    lCatTypeItem.add(lDateItem);
                                }
                                if (lCatTypeItem.hasChild()) {
                                    lCatItem.add(lCatTypeItem);
                                }
                            }
                            if (lCatItem.hasChild()) {
                                lInSituMenuItem.add(lCatItem);
                            }
                        }
                    }
                    if (lInSituMenuItem.hasChild()) {
                        menus.add(lInSituMenuItem);
                    }
                    menus.add(VPlotItemBuilder.getZoomItem(vPlotMenu.getMap(), lThis));

                    vPlotMenu.addAll(menus);

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

                    zoomMenu.setVisible(false);

                    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("==>>> ************ VPlotComponent fetchDateRange till " + new Date(200 * 365 * 24 * 60 * 60 * 1000l)
                        //+ " with " + vPlotModel.getSource() + " " + vPlotModel.getType());
                menuDateRangesWrappers = new ArrayList<DateRangeWrapper>();
                if (vPlotModel.getSource() != null && vPlotModel.getType() == DataSource.REAL) {
                    for (Catalog catalog : Catalog.values()) {
                        for (final VPlotModel.CatalogType catType : getCatalogTypes(catalog)) {
                            //System.out.println("==>>> ************ VPlotComponent fetchDateRange for " + catalog + "/" + catType + " till " + new Date(200 * 365 * 24 * 60 * 60 * 1000l));
                            DateRangeWrapper lDrw = ServiceFactory.getVPlotService().fetchDateRanges(
                                    new Date(0l),//1970
                                    new Date(200 * 365 * 24 * 60 * 60 * 1000l),//2070
                                    catalog.name(), vPlotModel.getSource(), catType.name());
                            //System.out.println("Adding for " + catalog.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;
            }
        };
    }

    /**
     * Update InSitu from catalogue
     *
     * @param source
     * @param type
     */
    public void drawInsitu() {
        final Task<Object> getInsitu = this.getInsitu();
        getInsitu.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) {
                    dateRangeChart.getData().clear();
                    if ((dateRangesWrapper != null) && (dateRangesWrapper.getDateRanges() != null) && (dateRangesWrapper.getDateRanges().size() > 0)) {
                        NumberAxis yAxisRange = (NumberAxis) dateRangeChart.getYAxis();
                        // create chart series
                        for (DateRange dateRange : dateRangesWrapper.getDateRanges()) {
                            Series<Number, Number> series = new Series<Number, Number>();
                            series.getData().add(new Data<Number, Number>(dateRange.getStartTime().getTime(), yAxisRange.getLowerBound()));
                            series.getData().add(new Data<Number, Number>(dateRange.getStartTime().getTime(), yAxisRange.getUpperBound()));
                            dateRangeChart.getData().add(series);
                            series = new Series<Number, Number>();
                            series.getData().add(new Data<Number, Number>(dateRange.getEndTime().getTime(), yAxisRange.getLowerBound()));
                            series.getData().add(new Data<Number, Number>(dateRange.getEndTime().getTime(), yAxisRange.getUpperBound()));
                            dateRangeChart.getData().add(series);
                        }
                    }
                    endCursor();

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

    private Task<Object> getInsitu() {
        return new Task<Object>() {
            @Override
            protected Object call() {
                if (vPlotModel.getSource() != null && vPlotModel.getType() == DataSource.REAL) {
                    dateRangesWrapper = ServiceFactory.getVPlotService().fetchDateRanges(vPlotModel.getStartDate(), vPlotModel.getEndDate(), vPlotModel.getCatalog(), vPlotModel.getSource(), vPlotModel.getCatType());
                    //log.info("Number of found DateRanges=" + dateRangesWrapper.getDateRanges().size());
                } else {
                    dateRangesWrapper = null;
                    //log.info("No data");
                }
                return true;
            }
        };
    }

    public VPlotModel getVPlotModel() {
        return vPlotModel;
    }

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

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

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

    public BooleanProperty getHasChanged() {
        return hasChanged;
    }

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

}
