/* ----------------------------------------
 * Origin  : GFI INFORMATIQUE 
 * Project : CDPP Propagation Tool 
 * ----------------------------------------
 * History 
 * Version : 01/05/2015 : SCA : V2.1
 * Creation 
 * Version : 24/03/2016 : SCA : V2.2 (PSWS-H2020)
 * Version : 05/05/2021 : LBE
 * DM-ID : Redmine#7059 : 05/05/2021 : n point instead of 3 for trajectories drawing
 * Version : V2.13
 * DM-ID : redmine#12357 : 24/10/2025 : LBE : Fixed fit data clear openjfx bug
 * ----------------------------------------
 */
package fr.tlse.irap.pt.client.controller.tools.jmap;

import java.io.File;
import java.util.ArrayList;
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.Item;
import fr.tlse.irap.pt.client.controller.common.MapMenu;
import fr.tlse.irap.pt.client.controller.common.SpaceElementCollection;
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.drawingPlane.DrawHelper;
import fr.tlse.irap.pt.client.controller.tools.IToolController.JMapTool;
import fr.tlse.irap.pt.client.controller.tools.IToolController.Tool;
import fr.tlse.irap.pt.client.controller.tools.common.MapLegend;
import fr.tlse.irap.fit.FitPoint;
import fr.tlse.irap.map.JMap;
import fr.tlse.irap.map.JMapHeader;
import fr.tlse.irap.spaceElement.Comet;
import fr.tlse.irap.spaceElement.Planet;
import fr.tlse.irap.spaceElement.Probe;
import fr.tlse.irap.spaceElement.SpaceElement;
import javafx.beans.property.BooleanProperty;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.collections.ObservableList;
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.Series;
import javafx.scene.control.Label;
import javafx.scene.control.Menu;
import javafx.scene.control.MenuItem;
import javafx.scene.control.ScrollPane;
import javafx.scene.control.Tooltip;
import javafx.scene.image.ImageView;
import javafx.scene.input.MouseButton;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.GridPane;
import javafx.stage.FileChooser;

public class JMapComponent extends GridPane implements ICustomComponent {

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

	public JMapModel jMapModel = new JMapModel();
	private Tooltip tooltip = new Tooltip();

	@FXML protected GridPane jMapComponent;    
	@FXML protected Label startLabel;
	@FXML protected Label fitTypeLabel;
    @FXML protected Label paLabel;
    @FXML protected Label sourceLabel;
    @FXML protected Label typeLabel;
    @FXML protected ScrollPane scrollItems;
    @FXML protected ScrollPane scrollImg;
    @FXML protected ImageView jmapImg;
    @FXML protected LineChart<Number, Number> blobsChart;
    @FXML protected LineChart<Number, Number> probeChart;
    @FXML protected LineChart<Number, Number> planetChart;
    @FXML protected LineChart<Number, Number> cometChart;
    @FXML protected LineChart<Number, Number> catalogChart;
    @FXML protected LineChart<Number, Number> fitChart;
    
    protected MapLegend jMapLegend;
	
    JMap jMap;
    JMapPosition jMapPosition; 
    
	NumberAxis xAxis;
	NumberAxis yAxis;
	Double minX, maxX;
	Double minY, maxY;

	boolean isVisibleAllPlanets = true;
    boolean isVisibleAllProbes = true;
    boolean isVisibleAllComets = true;
    boolean isVisibleStartEnd = true;

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

	/* Constants */
	public static final long NB_TICS = 10;

	/* Context menu */
	IJMapMenu iJMap;
	MapMenu jMapMenu;
	MenuItem p1Item, p2Item, resetItem;
	MenuItem savePointsItem, removePointItem, fitPointsItem;

	/* Selection properties */
	boolean isP1Selected = false;
	FitPoint selectedP = new FitPoint();
	FitPoint selectedP1 = new FitPoint();
	FitPoint selectedP2 = new FitPoint();

	boolean isSelectFitInProgress = false;
	int selectedFit = -1;
	boolean isClickPointsInProgress = false;

	BooleanProperty hasChanged = new SimpleBooleanProperty();

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

        // bind view fields to model properties
        jMapModel.startLabel().bindBidirectional(startLabel.textProperty());
        jMapModel.fitTypeLabel().bindBidirectional(fitTypeLabel.textProperty());
        jMapModel.posAngleLabel().bindBidirectional(paLabel.textProperty());
        jMapModel.sourceLabel().bindBidirectional(sourceLabel.textProperty());
        jMapModel.typeLabel().bindBidirectional(typeLabel.textProperty());
    
        // x-axis label format
        xAxis = (NumberAxis)blobsChart.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)blobsChart.getYAxis();
		minY = yAxis.getLowerBound();
		maxY = yAxis.getUpperBound();
		resizeYAxis(minY, maxY);

        // bind other charts axis 
		bindAxisChart(planetChart, xAxisFormatter);
		bindAxisChart(probeChart, xAxisFormatter);
		bindAxisChart(cometChart, xAxisFormatter);
		bindAxisChart(catalogChart, xAxisFormatter);
		bindAxisChart(fitChart, xAxisFormatter);

		// set charts CSS style  
		blobsChart.getStylesheets().add(getClass().getResource("BlobsChart.css").toString());
		planetChart.getStylesheets().add(getClass().getResource("PlanetChart.css").toString());
		probeChart.getStylesheets().add(getClass().getResource("ProbeChart.css").toString());
		cometChart.getStylesheets().add(getClass().getResource("CometChart.css").toString());
		catalogChart.getStylesheets().add(getClass().getResource("CatalogChart.css").toString());
		fitChart.getStylesheets().add(getClass().getResource("FitChart.css").toString());

		fitChart.setOnMouseMoved(mouseHandler);
		fitChart.setOnMouseClicked(mouseHandler);

		// add click and fit insitu series in fit chart 
		seriesClick.setName("ClickPoints");
		seriesFit.setName("InSitu");
		fitChart.getData().add(seriesClick);
		fitChart.getData().add(seriesFit);
		
		scrollImg.setContent(jmapImg);
    }
        
    /**
     * dynamic init of component
     */
	public void initComponent(JMapPosition jMapPosition) {
		
		this.jMapPosition = jMapPosition;
		// Initialise tooltips except for Mac OS
		if (!TOOLTIP.isDisable()) {
			tooltip.setText(TOOLTIP.JMAP_RIGHT_CLICK);
			Tooltip.install(jMapComponent, tooltip);
		}
		// Init x-axis according to start date
		minX = Math.floor((double) (jMapPosition.getT1().getTime()));
		maxX = Math.floor((double) (jMapPosition.getT2().getTime()));
		resizeXAxis(minX, maxX);
	}

	/**
	 * Load contextual menu and legend
	 * @param iJMap
	 */
	public void initMenu(IJMapMenu iJMap, JMapTool jMapTool, Tool tool) {
		this.iJMap = iJMap;
		// load contextual menu
		loadMenu(jMapTool, tool);
        // load J-map legend from Space Elements
        jMapLegend = new MapLegend(JMapHelper.createSpaceElementsLabels());
		scrollItems.setContent(jMapLegend);
	}

	/**
	 * configure component according to jMapTool
	 * @param jMapTool
	 * @param tool
	 */
	public void setJMapTool(JMapTool jMapTool, Tool tool) {
		
		if (!TOOLTIP.isDisable()) {
			// configure tooltip
			String txt = TOOLTIP.JMAP_RIGHT_CLICK;
			if (jMapTool == JMapTool.CLICK) {
				txt += TOOLTIP.JMAP_ADD_FIT;
			} else if (jMapTool == JMapTool.FIT) {
				txt += TOOLTIP.JMAP_SELECT_FIT;
			}
			tooltip.setText(txt);
		}

		if (jMapMenu != null) {
			// configure menu
			loadMenu(jMapTool, tool);
			// reset initial state image
			resetZoom();
		}
		// configure fit chart
		clearFitSeries();
		clearCatalogSeries();
		clearBlobsSeries();
	}

	public void setJMapPosition(JMapPosition jMapPosition) {
		this.jMapPosition = jMapPosition;
	}

	/**
	 * Create contextual j-map menu
	 * @param iJMap
	 * @param jMapTool
	 * @param tool
	 */
	protected void loadMenu(JMapTool jMapTool, Tool tool) {
		if (jMapMenu == null) {
			jMapMenu = new MapMenu();
		}
		List<Item> menus = new ArrayList<Item>();
		menus.add(JMapItemBuilder.getChooseMapItem(jMapMenu.getMap(), iJMap));
		if (jMapTool == JMapTool.FIT) {
			if (tool.equals(Tool.RADIAL)) {
				menus.add(JMapItemBuilder.getChooseCatalogItem(jMapMenu.getMap(), iJMap));
			}
			menus.add(JMapItemBuilder.getSelectFitItem(jMapMenu.getMap(), iJMap));
		} 
		else if (jMapTool == JMapTool.CLICK) {
			menus.add(JMapItemBuilder.getAddFitItem(jMapMenu.getMap(), iJMap));
		}
		menus.add(JMapItemBuilder.getShowPlanetsItem(jMapMenu.getMap(), iJMap));
		menus.add(JMapItemBuilder.getShowProbesItem(jMapMenu.getMap(), iJMap));
		menus.add(JMapItemBuilder.getShowCometsItem(jMapMenu.getMap(), iJMap));
		menus.add(JMapItemBuilder.getShowStartEndItem(jMapMenu.getMap(), iJMap));
		menus.add(JMapItemBuilder.getZoomItem(jMapMenu.getMap(), iJMap));
		menus.add(JMapItemBuilder.getShowResetObjectsItem(jMapMenu.getMap(), iJMap));
		jMapMenu.getItems().clear();
		jMapMenu.addAll(menus);

		/* Init zoom menu */
		Menu zoomMenu = jMapMenu.getMenu(JMapItemBuilder.ZOOM);
		p1Item = zoomMenu.getItems().get(0);
		p2Item = zoomMenu.getItems().get(1);
		resetItem = zoomMenu.getItems().get(2);
		p2Item.setDisable(true);
		resetItem.setDisable(true);
		
		if (jMapTool == JMapTool.CLICK) {
			/* Init addFit menu */
			Menu addFitMenu = jMapMenu.getMenu(JMapItemBuilder.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);
		}
	}

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

		NumberAxis yAxisBind = (NumberAxis) lChart.getYAxis();
		yAxisBind.lowerBoundProperty().bind(yAxis.lowerBoundProperty());
		yAxisBind.upperBoundProperty().bind(yAxis.upperBoundProperty());
		yAxisBind.tickUnitProperty().bind(yAxis.tickUnitProperty());
	}
	
	/***************************************************************
	 ******************** Image view methods ***********************
	 ***************************************************************/
	/**
	 * Update J-map image between 2 dates 
	 */
	public boolean updateImage(JMap jMap) {
		// save the original J-Map
		this.jMap = jMap;
		// create image and fit in the panel
		if (setUpImage()) {
			// update model
			jMapModel.update(jMap.getHeader());
			// set x axis limits
			minX = Math.floor((double) (jMap.getHeader().getStartDate().getTime()));
			maxX = Math.floor((double) (jMap.getHeader().getEndDate().getTime()));
			resizeXAxis(minX, maxX);
			// set the real y axis limits
			minY = jMap.getHeader().getMinElongation();
			maxY = jMap.getHeader().getMaxElongation();
			resizeYAxis(minY, maxY);
			
			return true;
		} else {
			return false;
		}
	}

	/**
	 * Check if image has been set up
	 * 
	 */
	public boolean hasImage() {
		return (jmapImg.getImage() != null);
	}

	/**
	 * Clear J-map image
	 */
	public void clearImage() {
		if (hasImage()) {
			this.jMap = null;
			jmapImg.setImage(null);
		}
	}

	/**
	 * Fit image
	 */
	public void fitImage() {
		//log.debug("Fit image into scroll pane. Height="+scrollImg.getHeight()+", Witdh="+scrollImg.getWidth());
		jmapImg.setFitHeight(scrollImg.getHeight());
		jmapImg.setFitWidth(scrollImg.getWidth());
	}

	public Node getView() {
		return this;
	}

	public JMapHeader getJmapHeader(){
		if (jMap != null){
			return jMap.getHeader();
		}
		return null;
	}

	/**
	 * Create a bitmap of JMap
	 */
	private boolean setUpImage() {
		try {
			// Create a bitmap from the JMap and fits it in the panel
			jmapImg.setImage(JMapHelper.createImage(this.jMap));
			//log.debug("Image has been created");
			// fit image in the scrolled panel
			fitImage();

		} catch (Exception e) {
			//log.debug("Image not available - " + e.getStackTrace());
			DialogBox.warn("Image not available !");
			return false;
		}
		return true;
	}

	/**
	 * Create a zoomed bitmap of JMap from p1 to p2
	 */
	private boolean setUpImageFrom(FitPoint p1, FitPoint p2) {
		try {
			// Create a bitmap from the JMap and fits it in the panel
			jmapImg.setImage(JMapHelper.createImage(this.jMap, p1, p2));
			//log.debug("Image has been created");
			// fit image in the scrolled panel
			fitImage();

		} catch (Exception e) {
			e.printStackTrace();
			//log.debug("Image not available - " + e.getMessage());
			DialogBox.warn("Image not available !");
			return false;
		}
		return true;
	}

	/*****************************************************************
	 ******************** Chart series methods ***********************
	 *****************************************************************/

	/**
	 * Add a catalogue series from a CME fit points 
	 */
	public void addCatalogSeries(List<FitPoint> fit) {
		if (fit != null){
			XYChart.Series<Number, Number> series = new XYChart.Series<Number, Number>();
			for (FitPoint fitPoint : fit) {
				series.getData().add(new XYChart.Data<Number, Number>(fitPoint.getTime(), fitPoint.getElongation()));
			}
			catalogChart.getData().add(series);
		}
	}

	public void addBlobSeries(List<FitPoint> fit) {
		if (fit != null){
			XYChart.Series<Number, Number> series = new XYChart.Series<Number, Number>();
			for (FitPoint fitPoint : fit) {
				series.getData().add(new XYChart.Data<Number, Number>(fitPoint.getTime(), fitPoint.getElongation()));
			}
			blobsChart.getData().add(series);
		}
	}

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

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

	/**
	 * Add an element series from a SpaceElement fit points 
	 */
	public void addElementSeries(SpaceElement spaceElement, FitPoint alpha[]) {
		addElementSeries(spaceElement, (Date) null, alpha);
	}

	/**
	 * Add an element series from a SpaceElement fit points 
	 */
	public void addElementSeries(SpaceElement spaceElement, Date time, FitPoint alpha[]) {
		String name = spaceElement.getName() + spaceElement.getClass().getSimpleName();
		String color = spaceElement.getColor();
		addElementSeries(name, color, time, alpha);
	}

	/**
	 * Add an element series with name and color
	 */
	public void addElementSeries(String name, String color, FitPoint alpha[]) {
		addElementSeries(name, color, (Date) null, alpha);
	}

	/**
	 * Add an element series with name and color
	 */
	public void addElementSeries(String name, String color, Date time, FitPoint alpha[]) {
		// Create horizontal series from SpaceElement
		XYChart.Series<Number, Number> series = new XYChart.Series<Number, Number>();
		series.setName(name);
//		series.getData().add(new XYChart.Data<Number, Number>(minX, alpha[0].getElongation()));
//		series.getData().add(new XYChart.Data<Number, Number>(alpha[1].getTime(), alpha[1].getElongation()));
//		if (time != null) {
//			series.getData().add(new XYChart.Data<Number, Number>(alpha[1].getTime(), 0.0));
//			series.getData().add(new XYChart.Data<Number, Number>(alpha[1].getTime(), alpha[1].getElongation()));
//		}

                for(FitPoint lFp:alpha) {
                    //log.debug("draw point "+name+" "+lFp.getTime()+" "+lFp.getElongation());
                    series.getData().add(new XYChart.Data<Number, Number>(lFp.getTime(), lFp.getElongation()));
                    if(time != null && lFp.equals(alpha[alpha.length-1])) {
                        //if impact vertical line to add, it is in last position
			series.getData().add(new XYChart.Data<Number, Number>(alpha[alpha.length-1].getTime(), 0.0));
			series.getData().add(new XYChart.Data<Number, Number>(alpha[alpha.length-1].getTime(), alpha[alpha.length-1].getElongation()));
                    }
                }

		//series.getData().add(new XYChart.Data<Number, Number>(maxX, alpha[2].getElongation()));
log.debug("*** Add serie of" +  alpha.length+" values for " + name);
		if (name.contains(Probe.class.getSimpleName())) {
			// Add series to Probe line Chart
			int index = probeChart.getData().size();
			//log.debug("size=" + index + " color=" + color + " alpha=[" + alpha[0].getElongation() + ", " + alpha[1].getElongation() + ", " + alpha[2].getElongation() + "]");
			probeChart.getData().add(series);
			// Set up series color of the SpaceElement
			for (Node n : probeChart.lookupAll(".default-color" + index + ".chart-series-line")) {
				n.setStyle("-fx-stroke: " + color + ";");
			}
//			for (Node mark : probeChart.lookupAll(".chart-line-symbol")) {
//				   Tooltip.install(mark, new Tooltip(name));
//			}
		} else if (name.contains(Planet.class.getSimpleName())) {
			// Add series to Planet line Chart
			int index = planetChart.getData().size();
			//log.debug("size=" + index + " color=" + color + " alpha=[" + alpha[0].getElongation() + ", " + alpha[1].getElongation() + ", " + alpha[2].getElongation() + "]");
			planetChart.getData().add(series);
			// Set up series color of the SpaceElement
			for (Node n : planetChart.lookupAll(".default-color" + index + ".chart-series-line")) {
				n.setStyle("-fx-stroke: " + color + ";");
			}
		} else if (name.contains(Comet.class.getSimpleName())) {
			// Add series to Comet line Chart
			int index = cometChart.getData().size();
			//log.debug("size=" + index + " color=" + color + " alpha=[" + alpha[0].getElongation() + ", " + alpha[1].getElongation() + ", " + alpha[2].getElongation() + "]");
			cometChart.getData().add(series);
			// Set up series color of the SpaceElement
			for (Node n : cometChart.lookupAll(".default-color" + index + ".chart-series-line")) {
				n.setStyle("-fx-stroke: " + color + ";");
			}
		}
	}

	/**
	 * 
	 */
	public List<FitPoint> getClickPoints() {
		if (seriesClick.getData().size() > 0) {
			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;
		}
	}

	public void clearProbeSeries() {
		while (probeChart.getData().size() > 0) {
			Series<Number, Number> series = probeChart.getData().remove(0);
			series.getData().clear();
		}
	}

	public void clearPlanetSeries() {
		while (planetChart.getData().size() > 0) {
			Series<Number, Number> series = planetChart.getData().remove(0);
			series.getData().clear();
		}
	}

	public void clearCometSeries() {
		while (cometChart.getData().size() > 0) {
			Series<Number, Number> series = cometChart.getData().remove(0);
			series.getData().clear();
		}
	}

	public void clearBlobsSeries() {
		while (blobsChart.getData().size() > 0) {
			Series<Number, Number> series = blobsChart.getData().remove(0);
			series.getData().clear();
		}
	}

	public void clearCatalogSeries() {
		while (catalogChart.getData().size() > 0) {
			Series<Number, Number> series = catalogChart.getData().remove(0);
			series.getData().clear();
		}
	}

	public void clearFitSeries() {
		seriesClick.getData().clear();
		seriesFit.getData().clear();
                fitChart.requestLayout();
                //force data reset for a openjfx(<23) bug
                fitChart.getData().remove(seriesFit);
       		fitChart.getData().add(seriesFit);
	}

	/*****************************************************/
	/**************** Private methods ********************/
	/*****************************************************/

	private void resizeXAxis(Double lowerTime1, Double upperTime2) {
		xAxis.setLowerBound(lowerTime1);
		xAxis.setUpperBound(upperTime2);
		xAxis.setTickUnit((upperTime2 - lowerTime1) / NB_TICS);
	}

	private void resizeYAxis(Double lowerLong1, Double upperLong2) {
		//log.debug("Reset y axis - ymin=" + lowerLong1 + ", ymax=" + upperLong2);
		yAxis.setLowerBound(lowerLong1);
		yAxis.setUpperBound(upperLong2);
		yAxis.setTickUnit(computeYAxisTickUnit(lowerLong1, upperLong2));
	}

	private double computeYAxisTickUnit(double lowerLong1, double upperLong2) {
		/* first sol - trivial */
		// yAxis.setTickUnit((upperLong2 - lowerLong1) / NB_TICS);
		/* second sol - always one digit */
//		double tickUnit = 0.1 + 1.0 * Math.round(upperLong2 - lowerLong1)
//				/ NB_TICS;
		/* third sol - without digits */
		double tickUnit = Math.floor((Math.ceil(upperLong2) - Math.floor(lowerLong1)) / NB_TICS);
		if (tickUnit > 0.0) {
			return tickUnit;
		} else {
			return 1.0;
		}
	}

	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 = getSceneShift(xAxis);
				double yAxisShift = getSceneShiftY(yAxis);

				// Treat only if mouse is inside axis
				if (mouseEvent.getSceneY() > yAxisShift
						&& mouseEvent.getSceneY() < yAxisShift + yAxis.getHeight()
						&& mouseEvent.getSceneX() > xAxisShift
						&& mouseEvent.getSceneX() < xAxisShift + xAxis.getWidth()) {

					double xAxisStep = xAxis.getWidth() / (xAxis.getUpperBound() - xAxis.getLowerBound());
					double yAxisStep = yAxis.getHeight() / (yAxis.getUpperBound() - yAxis.getLowerBound());

					Double XX = ((mouseEvent.getSceneX() - xAxisShift) / xAxisStep) + xAxis.getLowerBound();
					Double YY = yAxis.getUpperBound() - ((mouseEvent.getSceneY() - yAxisShift) / yAxisStep);

					if (mouseEvent.getEventType() == MouseEvent.MOUSE_CLICKED) {

						if (mouseEvent.getButton() == MouseButton.PRIMARY) {
							/* treat select/add Fit actions */
							selectedP.setTime(XX.longValue());
							selectedP.setElongation(YY);
							if (isSelectFitInProgress) {
								setSelectFitInProgress(false);
								/* check if the cursor is near from a Fit */
								selectedFit = findSeriesInCatalogue(selectedP);
								if (selectedFit != -1) {
									setFitInSitu(selectedFit);
									/* prevent the caller to be refreshed */
									getHasChanged().set(true);
									getHasChanged().set(false);
								}
							} else if (isClickPointsInProgress) {
								/* add point in the click series */
								addClickPoint(selectedP);
							}

						} else if (mouseEvent.getButton() == MouseButton.SECONDARY) {
							/* Treat zoom actions */
							if (!isP1Selected) {
								selectedP1.setTime(XX.longValue());
								selectedP1.setElongation((double) YY.longValue());
								p1Item.setText(JMapItemBuilder.ZOOM_P1 + "("
										+ Formatter.getSdf().format(selectedP1.getTime()) + ", "
										+ selectedP1.getElongation() + ")");
							} else {
								selectedP2.setTime(XX.longValue());
								selectedP2.setElongation((double) YY.longValue());
								p2Item.setText(JMapItemBuilder.ZOOM_P2 + "("
										+ Formatter.getSdf().format(selectedP2.getTime()) + ", "
										+ selectedP2.getElongation() + ")");
							}

							/* Show context menu */
							jMapMenu.show(scrollImg, mouseEvent.getScreenX(), mouseEvent.getScreenY());
						}
					}
				} // End test mouse inside axis
			}

		} // End override handle

	};

	private static double getSceneShift(Node node) {
		double shift = 0;
		do {
			shift += node.getLayoutX();
			node = node.getParent();
		} while (node != null);
		return shift;
	}

	private static double getSceneShiftY(Node node) {
		double shift = 0;
		do {
			shift += node.getLayoutY();
			node = node.getParent();
		} while (node != null);
		return shift;
	}

	/**
	 * Find series in catalog
	 * @param p
	 * @return found index
	 */
	private int findSeriesInCatalogue(FitPoint p) {
		//log.debug("find series from point : ("+ Formatter.getSdf().format(p.getTime()) + "," + p.getElongation() + ")");

		/* delta error axis */
		Double dxx = (maxX - minX) / 50;
		long dx = dxx.longValue();
		double dy = (maxY - minY) / 50;

		ObservableList<XYChart.Series<Number, Number>> fits = catalogChart.getData();
		for (int i = 0; i < catalogChart.getData().size(); i++) {
			ObservableList<XYChart.Data<Number, Number>> fit = fits.get(i).getData();
			for (int j = 0; j < fit.size(); j++) {
				if (Math.abs(fit.get(j).getXValue().longValue() - p.getTime()) < dx) {
					if (Math.abs(fit.get(j).getYValue().doubleValue() - p.getElongation()) < dy) {
						// found => return Fit number
						return i;
					}
				}
			}
		}
		// not found
		return -1;
	}

	/**
	 * set the fit in situ from selected fit in catalog
	 * => selected fit will then be identified by red color 
	 * @param fitId
	 */
	private void setFitInSitu(int fitId) {
		Series<Number, Number> series = catalogChart.getData().get(fitId);
		seriesFit.getData().clear();
		seriesFit.getData().addAll(series.getData());
	}

	/**
	 * 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 : (" + Formatter.getSdf().format(p.getTime()) + "," + p.getElongation() + ")");
		//log.debug("Size = " + seriesClick.getData().size());
	}

	/*************************************************************************
	 ***************************** zoom actions ******************************
	 *************************************************************************/
	public void zoomP1() {
		// disable P2
		p2Item.setDisable(false);
		isP1Selected = true;
	}

	public void zoomP2() {
		if (selectedP2.getTime() > selectedP1.getTime()) {
			if (setUpImageFrom(selectedP1, selectedP2)) {
				/* resize axis */
				resizeXAxis((double) selectedP1.getTime(), (double) selectedP2.getTime());
				if (selectedP2.getElongation() > selectedP1.getElongation()) {
					resizeYAxis(selectedP1.getElongation(), selectedP2.getElongation());
				} else {
					resizeYAxis(selectedP2.getElongation(), selectedP1.getElongation());
				}
				resetItem.setDisable(false);
				resetFields();
			}
		}
	}

	/**
	 * 
	 */
	public void resetZoom() {
		if (!resetItem.isDisable()) {
			p2Item.setDisable(true);
			if (setUpImage()) {
				resizeXAxis(minX, maxX);
				resizeYAxis(minY, maxY);
				resetItem.setDisable(true);
				resetFields();
			}
        }
	}

	private void resetFields() {
		p1Item.setText(JMapItemBuilder.ZOOM_P1);
		p2Item.setText(JMapItemBuilder.ZOOM_P2);
		isP1Selected = false;
		p2Item.setDisable(true);
	}

	/**
	 * @param isSelectFitInProgress
	 */
	public void setSelectFitInProgress(boolean isSelectFitInProgress) {
		this.isSelectFitInProgress = isSelectFitInProgress;
		if (isSelectFitInProgress) {
			super.setCursor(Cursor.OPEN_HAND);
		}
		else {
			super.setCursor(null);
		}
	}

	/*************************************************************************
	 ************************* add click actions *****************************
	 *************************************************************************/
	public void startClickPoints() {
		// clear click series and blobs
		clearFitSeries();
		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);
		}
	}

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

	/**
	 * 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 fitPoints() {
		setClickPointsInProgress(false);
        removePointItem.setDisable(true);
		fitPointsItem.setDisable(true);

		// create a fit series from the current click points series
		getHasChanged().set(true);
		getHasChanged().set(false);
	}

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

	public int getSelectedFit() {
		return selectedFit;
	}

	public BooleanProperty getHasChanged() {
		return hasChanged;
	}

	public void setStartSource(SpaceElement startSource) {
		jMapModel.startLabel().set("START : " + startSource.getLongName());
	}

    /**
     * Reset default view 
     * => Compute Start/End data and legend   
     */
	public void resetView() {
		// reset legend
        for (Node node : jMapLegend.getChildren()) {
            node.setUserData(null);
            jMapLegend.setNodeOpacity(node, false);
        }
        // compute Start/End
        Node startNode = DrawHelper.findByName(jMapLegend.getChildren(), jMapPosition.getStartSource().getName());
        Node endNode = null;
		if (jMapPosition.getEndSource() != null) {
			endNode = DrawHelper.findByName(jMapLegend.getChildren(), jMapPosition.getEndSource().getName());
		}
		computeStartEnd(startNode, endNode);
    }

	private void computeStartEnd(Node startNode, Node endNode) {
	     final Task<Object> computeStartEndWorker = this.computeStartEndWorker(startNode, endNode);
	     computeStartEndWorker.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) {
	            	 // show start end view
	            	 resetStartEnd();
	            	 endCursor();
	
	             } else if (newState == Worker.State.FAILED || newState == Worker.State.CANCELLED) {
	            	 log.warn("Position is not found while reset Start/End");
	                 endCursor();
	             }
	         }
	     });
	     new Thread(computeStartEndWorker).start();
	     waitCursor();
	 }
	
	 private Task<Object> computeStartEndWorker(final Node startNode, final Node endNode) {
		return new Task<Object>() {
	         @Override
	         protected Object call() throws Exception {
	        	 if (startNode != null) {
	        		 startNode.setUserData(jMapPosition.getElongations(jMapPosition.getStartSource()));
	        	 }
	        	 if (endNode != null) {
	        		 endNode.setUserData(jMapPosition.getElongations(jMapPosition.getEndSource()));
	        	 }
	             return true;
	         }
	     };
	 }

	/*************************************************************************
     **************************** planets actions ****************************
     *************************************************************************/
	public void showAllPlanets() {
		showAllPlanets(!isVisibleAllPlanets);		
	}

	public void showAllPlanets(boolean visibility) {
        if (visibility) {
            for (final Planet planet : SpaceElementCollection.getPlanets()) {
    			try {
                    showElement(planet);
    				Thread.sleep(100);
    			} catch (InterruptedException e) {
    				e.printStackTrace();
    			}
            }
        } else {
            // restore planets opacity
            for (Node node : jMapLegend.getChildren()) {
                if (node.getId().contains(Planet.class.getSimpleName())) {
                	jMapLegend.setNodeOpacity(node, visibility);
                }
            }
            drawPlanetFromLegend();
        }
        isVisibleAllPlanets = visibility;
    }

    /*************************************************************************
     **************************** probes actions *****************************
     *************************************************************************/
	public void showAllProbes() {
        showAllProbes(!isVisibleAllProbes);
	}

	public void showAllProbes(boolean visibility) {
        if (visibility) {
            //for (final Probe probe : SpaceElementCollection.getProbes()) {
            for (final Probe probe : SpaceElementCollection.getSpaceElementWrapper().getProbes()) {
            	if (probe.isDefined()) {
	    			try {
	                    showElement(probe);
	    				Thread.sleep(100);
	    			} catch (InterruptedException e) {
	    				e.printStackTrace();
	    			}
            	}
            }
        } else {
            // restore probes opacity
            for (Node node : jMapLegend.getChildren()) {
                if (node.getId().contains(Probe.class.getSimpleName())) {
                	jMapLegend.setNodeOpacity(node, visibility);
                }
            }
            drawProbeFromLegend();
        }
        isVisibleAllProbes = visibility;
    }

    /*************************************************************************
     **************************** comets actions *****************************
     *************************************************************************/
	public void showAllComets() {
        showAllComets(!isVisibleAllComets);
	}

	public void showAllComets(boolean visibility) {
        if (visibility) {
            for (final Comet comet : SpaceElementCollection.getComets()) {
    			try {
                    showElement(comet);
    				Thread.sleep(100);
    			} catch (InterruptedException e) {
    				e.printStackTrace();
    			}
            }
        } else {
            // restore comets opacity
            for (Node node : jMapLegend.getChildren()) {
                if (node.getId().contains(Comet.class.getSimpleName())) {
                	jMapLegend.setNodeOpacity(node, visibility);
                }
            }
            drawCometFromLegend();
        }
        isVisibleAllComets = visibility;
    }

    /**
     * Toggle Show/Hide element
     * @param spaceElement
     */
    public void showHideElement(SpaceElement spaceElement) {
        Node node = DrawHelper.findByName(jMapLegend.getChildren(), (spaceElement.getName()));
        if (node != null) {
            if (jMapLegend.isVisibleNode(node)) {
                hideElement(node);
            } else {
                showElement(spaceElement, node);
            }
        }
    }

    /**
     * Show element
     * @param spaceElement
     */
    private void showElement(SpaceElement spaceElement) {
        Node node = DrawHelper.findByName(jMapLegend.getChildren(), (spaceElement.getName()));
        log.debug("Node found "+node+" for "+spaceElement.getName());
        if (node != null) {
            if (!jMapLegend.isVisibleNode(node)) {
                showElement(spaceElement, node);
            }
        }
    }

    /**
     * Reset Start/End view
     */
    public void resetStartEnd() {
        /* reset Start/End */
    	showAllProbes(false);
    	showAllPlanets(false);
    	showAllComets(false);
    	showStartEnd(true);
    }


	public void showStartEnd() {
        showStartEnd(!isVisibleStartEnd);
	}

    /**
     * Show/Hide Start/end
     * @param visibility
     */
    public void showStartEnd(boolean visibility) {
        FitPoint alpha[];
        for (Node node : jMapLegend.getChildren()) {
            if (node.getId().contains(jMapPosition.getStartSource().getName())) {
                if (visibility) {
                    if (!jMapLegend.isVisibleNode(node)) {
                    	alpha = (FitPoint[]) node.getUserData();
                        if (JMapHelper.isValidFit(alpha)) {
                            // add series in lineChart
                            addElementSeries(jMapPosition.getStartSource(), jMapPosition.getStartTime(), alpha);
                        }
                    }
                }
                jMapLegend.setNodeOpacity(node, visibility);

            } else if (jMapPosition.getEndSource() != null && node.getId().contains(jMapPosition.getEndSource().getName())) {
                if (visibility) {
                    if (!jMapLegend.isVisibleNode(node)) {
                    	alpha = (FitPoint[]) node.getUserData();
                        if (JMapHelper.isValidFit(alpha)) {
                            // add series in lineChart
                            if (jMapPosition.getImpactTime() == null) {
                                addElementSeries(jMapPosition.getEndSource(), alpha);
                            } else {
                                addElementSeries(jMapPosition.getEndSource(), jMapPosition.getImpactTime(), alpha);
                            }
                        }
                    }
                }
                jMapLegend.setNodeOpacity(node, visibility);
            }
        }
        if (!visibility) {
            drawPlanetFromLegend();
            drawProbeFromLegend();
            drawCometFromLegend();
        }
        isVisibleStartEnd = visibility;
    }

    /**
     * Show one space element on the J-map 
     */
    public void showElement(final SpaceElement spaceElement, final Node node) {
        final Task<Object> showElementWorker = this.showElementWorker(spaceElement, node);
        showElementWorker.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) {
                    // successful ended
                    log.warn("*** show element "+spaceElement.getName()+" alpha? "+ (((FitPoint[]) node.getUserData())!=null) );
                    FitPoint alpha[] = (FitPoint[]) node.getUserData();
                    if (alpha != null) {
                    	jMapLegend.setNodeOpacity(node, true);
                        if (JMapHelper.isValidFit(alpha)) {
	                        // add series in lineChart
	                        if (spaceElement.getName().contains(jMapPosition.getStartSource().getName())) {
	                            addElementSeries(spaceElement, jMapPosition.getStartTime(), alpha);
	
	                        } else if (jMapPosition.getEndSource() != null && spaceElement.getName().contains(jMapPosition.getEndSource().getName())) {
	                            if (jMapPosition.getImpactTime() == null) {
	                                addElementSeries(jMapPosition.getEndSource(), alpha);
	                            } else {
	                                addElementSeries(jMapPosition.getEndSource(), jMapPosition.getImpactTime(), alpha);
	                            }
	                        } else {
	                            addElementSeries(spaceElement, alpha);
	                        }
	                    }
                    }
                    endCursor();

                } else if (newState == Worker.State.FAILED || newState == Worker.State.CANCELLED) {
                    log.warn(showElementWorker.getException().getMessage());
                    showElementWorker.getException().printStackTrace();
                    DialogBox.error("Web service error while getting " + spaceElement.getName() + " position! Try to show it again.");
                    endCursor();
                }
            }
        });
        new Thread(showElementWorker).start();
        waitCursor();
    }

    private Task<Object> showElementWorker(final SpaceElement e, final Node node) {
        return new Task<Object>() {
            @Override
            protected Object call() {
                if (node.getUserData() == null) {
	        		try {
                                    log.debug("call setUserData with "+e.getName());
                                    node.setUserData(jMapPosition.getElongations(e));
	        		}
	        		catch (Exception ex) {
	        			log.warn("Error on getElongations for " + e.getName() + ". " + ex.getMessage());
                                        ex.printStackTrace();
	        		}
                }
                return true;
            }
        };
    }

    private void hideElement(Node node) {
    	jMapLegend.setNodeOpacity(node, false);
        FitPoint alpha[] = (FitPoint[]) node.getUserData();
        if (JMapHelper.isValidFit(alpha)) {
            if (node.getId().contains(Planet.class.getSimpleName())) {
                drawPlanetFromLegend();
            } else if (node.getId().contains(Probe.class.getSimpleName())) {
                drawProbeFromLegend();
	        } else if (node.getId().contains(Comet.class.getSimpleName())) {
	            drawCometFromLegend();
	        }
        }
    }

    private void drawPlanetFromLegend() {
        // Clear Planets lineChart series
        clearPlanetSeries();

        // Rebuild planet series lineChart from legend
        for (Node node : jMapLegend.getChildren()) {
            if (node.getId().contains(Planet.class.getSimpleName())) {
                if (jMapLegend.isVisibleNode(node)) {
                    drawNodeFromLegend(node);
                }
            }
        }
    }

    private void drawProbeFromLegend() {
        // Clear Probes lineChart series
        clearProbeSeries();

        // Rebuild probes series lineChart from legend
        for (Node node : jMapLegend.getChildren()) {
            if (node.getId().contains(Probe.class.getSimpleName())) {
                if (jMapLegend.isVisibleNode(node)) {
                    drawNodeFromLegend(node);
                }
            }
        }
    }

    private void drawCometFromLegend() {
        // Clear Comets lineChart series
        clearCometSeries();

        // Rebuild comets series lineChart from legend
        for (Node node : jMapLegend.getChildren()) {
            if (node.getId().contains(Comet.class.getSimpleName())) {
                if (jMapLegend.isVisibleNode(node)) {
                    drawNodeFromLegend(node);
                }
            }
        }
    }

    private void drawNodeFromLegend(Node node) {
        FitPoint alpha[] = (FitPoint[]) node.getUserData();

        if (JMapHelper.isValidFit(alpha)) {
            // add series in lineChart
            if (node.getId().contains(jMapPosition.getStartSource().getName())) {
                addElementSeries(node.getId(), jMapPosition.getStartSource().getColor(), jMapPosition.getStartTime(), alpha);

            } else if (jMapPosition.getEndSource() != null && node.getId().contains(jMapPosition.getEndSource().getName())) {
                addElementSeries(node.getId(), jMapPosition.getEndSource().getColor(), jMapPosition.getImpactTime(), alpha);

            } else {
                String color = JMapHelper.findById(node.getId()).getColor();
                addElementSeries(node.getId(), color, alpha);
            }
        }

    }

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

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