/* ----------------------------------------
 * 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 : 06/11/2017 : SCA : V2.2.1 
 *  Redmine #5403 - DMPT3 
 * Version : 29/06/2022 : LBE : V2.5
 * DM-ID : Redmine#9229 : 29/06/2022 : PFSS mode
 * Version : 30/11/2022 : LBE : V2.6
 * DM-ID : Redmine #l0481 : 30/11/2022 : HCS and PFSS FL added to carrington map
 * Version : 05/08/2024 : LBE : V2.11
 * DM-ID : Redmine#l0763 : 05/08/2024 : PFSS grid lines
 * ----------------------------------------
 */
package fr.tlse.irap.pt.client.controller.tools.carrington;

import java.util.ArrayList;
import java.util.List;

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.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.drawingPlane.DrawHelper.DrawingCmeType;
import fr.tlse.irap.fieldLine.FieldLine;
import fr.tlse.irap.flare.Flare;
import fr.tlse.irap.map.CarringtonMap;
import fr.tlse.irap.map.CarringtonMapType;
import fr.tlse.irap.map.HCSLine;
import fr.tlse.irap.spaceElement.Comet;
import fr.tlse.irap.spaceElement.Planet;
import fr.tlse.irap.spaceElement.Probe;
import fr.tlse.irap.spaceElement.SpaceElement;
import fr.tlse.irap.tool.corotation.Corotation;
import fr.tlse.irap.tool.radial.Radial;
import fr.tlse.irap.tool.sep.Sep;
import java.util.Date;
import javafx.beans.property.BooleanProperty;
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.Group;
import javafx.scene.control.ChoiceBox;
import javafx.scene.control.Label;
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.AnchorPane;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.GridPane;
import javafx.scene.layout.HBox;
import javafx.scene.layout.VBox;

public class CarringtonComponent extends GridPane implements ICustomComponent {

    @FXML
    protected CarringtonComponent carringtonComponent;

    @FXML
    protected Label latLongLbl;
    @FXML
    protected ImageView carringtonImg;
    @FXML
    protected ScrollPane scrollItems;
    @FXML
    protected Label startLabel;
    @FXML
    protected Label crnLabel;
    @FXML
    protected Label measureLabel;
    @FXML
    protected Label measAltLabel;
    @FXML
    protected ChoiceBox<String> altCBox;
    @FXML
    protected Label altLabel;
    @FXML
    protected Label obsLabel;
    @FXML
    protected Label instrLabel;
    @FXML
    protected HBox topHBox;
    @FXML
    protected VBox bottomVBox;
    @FXML
    protected AnchorPane leftPane;

    protected Group groupCarringtonMap;
    ScrollPane scrollPane = new ScrollPane();

    final MapMenu carringtonMenu = new MapMenu();
    MenuItem clickSourceItem, clickExtentItem;

    CarringtonCME CME = new CarringtonCME();
    CarringtonShocks carringtonShocks = new CarringtonShocks();
    CarringtonFlares flarePoints = new CarringtonFlares();
    CarringtonHCS hcsLine = new CarringtonHCS();
    CarringtonFieldLines fieldLines = new CarringtonFieldLines();
    CarringtonGridFieldLines gridFieldLines = new CarringtonGridFieldLines();

    CarringtonElements carringtonElements = new CarringtonElements();

    CarringtonMap carringtonMap;

    boolean isCmeSourceInProgress;
    boolean isCmeRadiusInProgress;

    BooleanProperty hasChanged = new SimpleBooleanProperty();

    public CarringtonComponent() {
        super();
        CustomComponent.initCustomComponent(this);
        initComponent();
    }

    public void initComponent() {
        CarringtonHelper.imgWidth = carringtonImg.getFitWidth();
        CarringtonHelper.imgHeight = carringtonImg.getFitHeight();

        groupCarringtonMap = new Group(carringtonImg);
        BorderPane borderPane = new BorderPane();
        borderPane.getChildren().add(groupCarringtonMap);

        scrollPane.setContent(borderPane);
        GridPane.setRowIndex(scrollPane, 1);
        GridPane.setColumnIndex(scrollPane, 1);
        this.getChildren().add(scrollPane);

        groupCarringtonMap.getChildren().add(CarringtonHelper.createXAxis());
        groupCarringtonMap.getChildren().addAll(CME.getNodes());
        groupCarringtonMap.getChildren().add(flarePoints.getNodes());
        groupCarringtonMap.getChildren().add(hcsLine.getNodes());
        groupCarringtonMap.getChildren().add(fieldLines.getNodes());
        groupCarringtonMap.getChildren().add(gridFieldLines.getNodes());
        groupCarringtonMap.getChildren().add(carringtonShocks.getNodes());
        groupCarringtonMap.getChildren().add(carringtonElements.planetNodes);
        groupCarringtonMap.getChildren().add(carringtonElements.probeNodes);
        groupCarringtonMap.getChildren().add(carringtonElements.cometNodes);
        scrollItems.setContent(carringtonElements.mapLegend);

        // Add mouse events
        carringtonImg.setOnMouseMoved(mouseHandler);
        carringtonImg.setOnMouseClicked(mouseHandler);

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

    }

    public void setModel(CarringtonModel carringtonModel) {
        // bind properties
        carringtonModel.getStartLabel().bindBidirectional(startLabel.textProperty());
        carringtonModel.getCarringtonImg().bindBidirectional(carringtonImg.imageProperty());
        carringtonModel.getRotationNumber().bindBidirectional(crnLabel.textProperty());
        carringtonModel.getObserver().bindBidirectional(obsLabel.textProperty());
        carringtonModel.getInstrument().bindBidirectional(instrLabel.textProperty());
        carringtonModel.getMeasurement().bindBidirectional(measureLabel.textProperty());
        carringtonModel.getAltitude().bindBidirectional(altCBox.valueProperty());

        // Set default values
        carringtonModel.reset();
    }

    public void setCmeModel(CarringtonCMEModel cmeModel) {
        this.CME.setModel(cmeModel);
    }

    public void setShockModel(CarringtonShockModel shockModel) {
        this.carringtonShocks.setModel(shockModel);
    }

    /**
     * Initialise elements from Radial model
     *
     * @param
     */
    public void initCarringtonElements(Radial radial) {
        carringtonElements.initialize(radial);
    }

    public void initCarringtonElements(Corotation corotation) {
        carringtonElements.initialize(corotation);
    }

    public void initCarringtonElements(Sep sep) {
        carringtonElements.initialize(sep);
    }

    public void initCarringtonElements() {
        carringtonElements.initialize();
    }

    public void initCme(DrawingCmeType cmeType) {
        CME.setDrawingType(cmeType);
    }

    /**
     * Reset combo box and remove listeners on it
     */
    public void resetAltCBox(ChangeListener<String> valuePropertyChange) {
        try {
            altCBox.getSelectionModel().selectedItemProperty().removeListener(valuePropertyChange);
        } catch (Exception e) {
        }
        altCBox.getItems().clear();
        altCBox.setVisible(false);
        altLabel.setVisible(false);
        measAltLabel.setVisible(false);
    }

    /**
     * Init combo box from the CMT Reset first if needed
     */
    public void initAltCBox(CarringtonMapType cmt, ChangeListener<String> valuePropertyChange) {
        if (altCBox.isVisible()) {
            resetAltCBox(valuePropertyChange);
        }
        if (cmt.getRadialDistances().size() > 0) {
            MTreeNode node = CarringtonModel.getTreeCMaps().getNode(cmt.getInstrument(), cmt.getMeasurement(), 4);
            if (node != null) {
                for (MTreeNode child : node.getChildren()) {
                    altCBox.getItems().add(child.getName());
                }
                if (altCBox.getItems().size() > 0) {
                    altCBox.getSelectionModel().select(cmt.getRadialDistances().get(0));
                }
            }

            altCBox.getSelectionModel().selectedItemProperty().addListener(valuePropertyChange);
            altCBox.setVisible(true);
            altLabel.setVisible(true);
            measAltLabel.setVisible(true);
        }
    }

    public BooleanProperty getHasChanged() {
        return hasChanged;
    }

    /**
     * Load contextual menu of Carrington map component
     *
     * @param iCarrington Utility for building menues
     * @param startSource Changes which menu can be displayed
     */
    protected void loadMenu(ICarringtonMenu iCarrington, SpaceElement startSource) {

        // create menu
        List<Item> menus = new ArrayList<Item>();
        menus.add(CarringtonItemBuilder.getCarringtonMapItem(carringtonMenu.getMap(), iCarrington));
        if (CME.getDrawingType() == DrawingCmeType.CME) {
            menus.add(CarringtonItemBuilder.getDefineCMEItem(carringtonMenu.getMap(), iCarrington));
        } else {
//            if (startSource == null || startSource.getId() == 10) {
//                menus.add(CarringtonItemBuilder.getFieldLinesItem(carringtonMenu.getMap(), iCarrington));
//            }
            menus.add(CarringtonItemBuilder.getDefineSpiralItem(carringtonMenu.getMap(), iCarrington));
        }
        menus.add(CarringtonItemBuilder.getShowPlanetsItem(carringtonMenu.getMap(), iCarrington));
        menus.add(CarringtonItemBuilder.getShowProbesItem(carringtonMenu.getMap(), iCarrington));
        menus.add(CarringtonItemBuilder.getShowCometsItem(carringtonMenu.getMap(), iCarrington));
        menus.add(CarringtonItemBuilder.getShowStartEndItem(carringtonMenu.getMap(), iCarrington));
        menus.add(CarringtonItemBuilder.getShowFlaresItem(carringtonMenu.getMap(), iCarrington));
        menus.add(CarringtonItemBuilder.getShowShocksItem(carringtonMenu.getMap(), iCarrington));
        menus.add(CarringtonItemBuilder.getShowHcsItem(carringtonMenu.getMap(), iCarrington));
        menus.add(CarringtonItemBuilder.getResetObjectsItem(carringtonMenu.getMap(), iCarrington));

        carringtonMenu.getItems().clear();
        carringtonMenu.addAll(menus);
        if (CME.getDrawingType() == DrawingCmeType.CME) {
            clickSourceItem = carringtonMenu.getMenu(CarringtonItemBuilder.DEFINE_CME).getItems().get(0);
            clickExtentItem = carringtonMenu.getMenu(CarringtonItemBuilder.DEFINE_CME).getItems().get(1);
        } else {
            clickSourceItem = carringtonMenu.getMenu(CarringtonItemBuilder.DEFINE_SPIRAL).getItems().get(0);
            clickExtentItem = carringtonMenu.getMenu(CarringtonItemBuilder.DEFINE_SPIRAL).getItems().get(1);
        }
    }

    /**
     * Update menu items according to startElement
     *
     * @param spaceElement
     */
    public void setDisableSource(boolean isDisable) {
        if (clickSourceItem != null) {
            clickSourceItem.setDisable(isDisable);
        }
    }

    public void setDisableExtent(boolean isDisable) {
        if (clickExtentItem != null) {
            clickExtentItem.setDisable(isDisable);
        }
    }

    /**
     * this is must be dynamic. the variables are the width and the height of the image.
     */
    private EventHandler<MouseEvent> mouseHandler = new EventHandler<MouseEvent>() {

        @Override
        public void handle(MouseEvent mouseEvent) {

            double longitude = CarringtonHelper.xToLong(mouseEvent.getX());
            double latitude = CarringtonHelper.yToLat(mouseEvent.getY());

            if (mouseEvent.getEventType() == MouseEvent.MOUSE_MOVED) {
                latLongLbl.setText("Longitude : " + Formatter.getFormatter1D().format(longitude) + "° - "
                        + "Latitude : " + Formatter.getFormatter1D().format(latitude) + "°");
                Float pixelValue = CarringtonHelper.extractValue(carringtonMap, mouseEvent.getX(), mouseEvent.getY());
                if (pixelValue != null) {
                    String unit = CarringtonHelper.getValueUnit(carringtonMap);
                    latLongLbl.setText(latLongLbl.getText() + " - Value : " + Formatter.getFormatter().format(pixelValue) + " " + unit);
                }
            }
            if (mouseEvent.getEventType() == MouseEvent.MOUSE_CLICKED) {
                if (mouseEvent.getButton() == MouseButton.SECONDARY) {
                    if (carringtonMenu != null) {
                        carringtonMenu.show(groupCarringtonMap, mouseEvent.getScreenX(), mouseEvent.getScreenY());
                    }
                    mouseEvent.consume();
                } else if (mouseEvent.getButton() == MouseButton.PRIMARY) {
                    if (carringtonMenu != null) {
                        carringtonMenu.hide();
                    }
                    if (isCmeSourceInProgress) {
                        // Move long ext before setting long source
                        CME.getModel().setLongExt(CME.getModel().getLongExt() + longitude - CME.getModel().getSrcLong());
                        CME.getModel().setLongSource(longitude);
                        CME.getModel().setLatSource(0.0);
                        setCmeSourceInProgress(false);
                        // notify change
                        getHasChanged().set(true);
                        getHasChanged().set(false);
                        return;
                    } else if (isCmeRadiusInProgress) {
                        CME.getModel().setLongExt(longitude);
                        CME.getModel().setLatExt(latitude);
                        setCmeRadiusInProgress(false);
                        // notify change
                        getHasChanged().set(true);
                        getHasChanged().set(false);
                        return;
                    }
                }
            }
        }
    };

    public void setFlares(List<Flare> flares) {
        flarePoints.setFlares(flares);
    }

    public CarringtonFlares getFlares() {
        return flarePoints;
    }

    public void setHCSLine(HCSLine pLine) {
        hcsLine.setLine(pLine);
    }

    public CarringtonHCS getHCSLine() {
        return hcsLine;
    }

    public void setFieldLines(List<FieldLine> pLines) {
        fieldLines.setFieldLines(pLines);
    }

    public CarringtonFieldLines getFieldLines() {
        return fieldLines;
    }
    
    public void setGridFieldLines(List<Date> pTimes, List<List<FieldLine>> pLines, float pRadius) {
        gridFieldLines.setFieldLines(pTimes, pLines);
        gridFieldLines.setStartRadius(pRadius);
    }
    public CarringtonGridFieldLines getGridFieldLines() {
        return gridFieldLines;
    }
    
    public CarringtonMap getCarringtonMap() {
        return carringtonMap;
    }

    public void setCarringtonMap(CarringtonMap map) {
        carringtonMap = map;
    }

    public void setCmeSourceInProgress(boolean isInProgress) {
        this.isCmeSourceInProgress = isInProgress;
        if (isCmeSourceInProgress) {
            setCursor(Cursor.CROSSHAIR);
        } else {
            setCursor(null);
        }
    }

    public void setCmeRadiusInProgress(boolean isInProgress) {
        this.isCmeRadiusInProgress = isInProgress;
        if (isCmeRadiusInProgress) {
            setCursor(Cursor.CROSSHAIR);
        } else {
            setCursor(null);
        }
    }

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

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

    /**
     * Show all planets
     */
    public void showAllPlanets() {
        showAllPlanets(!carringtonElements.isVisibleAllPlanet());
    }

    /**
     * Show all planets
     */
    public void showAllPlanets(final boolean visibility) {
        Task<Object> worker = showAllPlanetWorker(visibility);

        worker.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) {
                    carringtonElements.showHidePlanets(visibility);
                    endCursor();
                } else if (newState == Worker.State.FAILED || newState == Worker.State.CANCELLED) {
                    endCursor();
                }
            }
        });

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

    private Task<Object> showAllPlanetWorker(final boolean visibility) {
        return new Task<Object>() {
            @Override
            protected Object call() throws Exception {
                if (visibility) {
                    carringtonElements.updatePlanets();
                }
                return true;
            }
        };
    }

    /**
     * Show planet
     */
    public void showPlanet(final Planet planet) {
        Task<Object> worker = showPlanetWorker(planet);
        worker.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) {
                    carringtonElements.showHide(planet);
                    endCursor();
                } else if (newState == Worker.State.FAILED || newState == Worker.State.CANCELLED) {
                    endCursor();
                }
            }
        });

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

    private Task<Object> showPlanetWorker(final Planet planet) {
        return new Task<Object>() {
            @Override
            protected Object call() throws Exception {
                carringtonElements.update(planet);
                return true;
            }
        };
    }

    /**
     * Show all probes
     */
    public void showAllProbes() {
        showAllProbes(!carringtonElements.isVisibleAllProbe());
    }

    /**
     * Show all probes
     */
    public void showAllProbes(final boolean visibility) {
        Task<Object> worker = showAllProbeWorker(visibility);

        worker.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) {
                    carringtonElements.showHideProbes(visibility);
                    endCursor();
                } else if (newState == Worker.State.FAILED || newState == Worker.State.CANCELLED) {
                    endCursor();
                }
            }
        });

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

    private Task<Object> showAllProbeWorker(final boolean visibility) {
        return new Task<Object>() {
            @Override
            protected Object call() throws Exception {
                if (visibility) {
                    carringtonElements.updateProbes();
                }
                return true;
            }
        };
    }

    /**
     * Show probe
     */
    public void showProbe(final Probe probe) {
        Task<Object> worker = showProbeWorker(probe);
        worker.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) {
                    carringtonElements.showHide(probe);
                    endCursor();
                } else if (newState == Worker.State.FAILED || newState == Worker.State.CANCELLED) {
                    endCursor();
                }
            }
        });

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

    private Task<Object> showProbeWorker(final Probe probe) {
        return new Task<Object>() {
            @Override
            protected Object call() throws Exception {
                carringtonElements.update(probe);
                return true;
            }
        };
    }

    /**
     * Show all comets
     */
    public void showAllComets() {
        showAllComets(!carringtonElements.isVisibleAllComet());
    }

    /**
     * Show all comets
     */
    public void showAllComets(final boolean visibility) {
        Task<Object> worker = showAllCometWorker(visibility);

        worker.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) {
                    carringtonElements.showHideComets(visibility);
                    endCursor();
                } else if (newState == Worker.State.FAILED || newState == Worker.State.CANCELLED) {
                    endCursor();
                }
            }
        });

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

    private Task<Object> showAllCometWorker(final boolean visibility) {
        return new Task<Object>() {
            @Override
            protected Object call() throws Exception {
                if (visibility) {
                    carringtonElements.updateComets();
                }
                return true;
            }
        };
    }

    /**
     * Show Comet
     */
    public void showComet(final Comet comet) {
        Task<Object> worker = showCometWorker(comet);
        worker.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) {
                    carringtonElements.showHide(comet);
                    endCursor();
                } else if (newState == Worker.State.FAILED || newState == Worker.State.CANCELLED) {
                    endCursor();
                }
            }
        });

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

    private Task<Object> showCometWorker(final Comet comet) {
        return new Task<Object>() {
            @Override
            protected Object call() throws Exception {
                carringtonElements.update(comet);
                return true;
            }
        };
    }

    /**
     * Show start/end
     */
    public void showStartEnd(SpaceElement startElement, SpaceElement endElement) {
        showHideStartEnd(startElement, endElement, !carringtonElements.isVisibleStartEnd());
    }

    /**
     * Show start/end
     */
    public void showStartEnd(SpaceElement startElement, SpaceElement endElement, boolean visibility) {
        carringtonElements.setVisibilityStartEnd(!visibility);
        showStartEnd(startElement, endElement);
    }

    private void showHideStartEnd(final SpaceElement startElement, final SpaceElement endElement, final boolean visibility) {
        Task<Object> worker = showStartEndWorker(startElement, endElement, visibility);

        worker.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) {
                    carringtonElements.showHideStartEnd(startElement, endElement, visibility);
                    endCursor();
                } else if (newState == Worker.State.FAILED || newState == Worker.State.CANCELLED) {
                    endCursor();
                }
            }
        });

        new Thread(worker).start();
        waitCursor();

    }

    private Task<Object> showStartEndWorker(final SpaceElement startElement, final SpaceElement endElement, final boolean visibility) {
        return new Task<Object>() {
            @Override
            protected Object call() throws Exception {
                if (visibility) {
                    carringtonElements.update(startElement);
                    if (endElement != null) {
                        carringtonElements.update(endElement);
                    }
                }
                return true;
            }
        };
    }

}
