/* ----------------------------------------
 * 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)
 * Add comets
 * Version : 29/06/2022 : LBE : V2.5
 * DM-ID : Redmine 9229: PFSS mode
 * Version : 12/09/2023 : LBE : V2.8
 * DM-ID : Redmine#8799 : 12/09/2023 : new carrington maps
 * Version : 05/08/2024 : LBE : V2.11
 * DM-ID : Redmine#l0763 : 05/08/2024 : Access to probe visibility
 * ----------------------------------------
 */
package fr.tlse.irap.pt.client.controller.tools.carrington;

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

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import fr.tlse.irap.pt.client.controller.common.SpaceElementCollection;
import fr.tlse.irap.pt.client.controller.drawingPlane.DrawHelper;
import fr.tlse.irap.pt.client.controller.tools.carrington.CarringtonElement.ElementType;
import fr.tlse.irap.pt.client.controller.tools.common.MapLegend;
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 javafx.beans.property.BooleanProperty;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.scene.Group;
import javafx.scene.Node;
import javafx.scene.shape.Circle;
import javafx.scene.shape.Rectangle;

public class CarringtonElements {

    protected static final Logger log = LoggerFactory.getLogger(CarringtonElements.class);

    private List<CarringtonElement> carringtonPlanets = new ArrayList<CarringtonElement>();
    private List<CarringtonElement> carringtonProbes = new ArrayList<CarringtonElement>();
    private List<CarringtonElement> carringtonComets = new ArrayList<CarringtonElement>();

    private Planet currentPlanet;
    private Probe currentProbe;
    private Comet currentComet;
    private SpaceElement startElement;
    private SpaceElement endElement;

    private BooleanProperty hasChanged = new SimpleBooleanProperty();
    private BooleanProperty visibilityPlanet = new SimpleBooleanProperty();
    private BooleanProperty visibilityProbe = new SimpleBooleanProperty();
    private BooleanProperty visibilityComet = new SimpleBooleanProperty();
    private BooleanProperty visibilityAllPlanets = new SimpleBooleanProperty();
    private BooleanProperty visibilityAllProbes = new SimpleBooleanProperty();
    private BooleanProperty visibilityAllComets = new SimpleBooleanProperty();
    private BooleanProperty visibilityStartEnd = new SimpleBooleanProperty();

    public Group planetNodes;
    public Group probeNodes;
    public Group cometNodes;
    public MapLegend mapLegend;

    public CarringtonElements() {

        planetNodes = new Group();
        probeNodes = new Group();
        cometNodes = new Group();
        mapLegend = new MapLegend();

        // Listen for property changed => init drawing elements 
        hasChanged.addListener(new ChangeListener<Boolean>() {
            @Override
            public void changed(ObservableValue<? extends Boolean> observedBoolean, Boolean oldValue, Boolean newValue) {
                // init planet nodes
                planetNodes.getChildren().clear();
                for (Planet planet : SpaceElementCollection.getPlanets()) {
                    Node elementNode = CarringtonHelper.createNodeFromSpaceElement(planet);
                    planetNodes.getChildren().add(elementNode);
                    elementNode.setVisible(false);
                }
                // init probe nodes
                probeNodes.getChildren().clear();
                for (CarringtonElement carrProbe : getCarringtonProbes()) {
                    Node elementNode = CarringtonHelper.createNodeFromSpaceElement(carrProbe.getElement());
                    probeNodes.getChildren().add(elementNode);
                    elementNode.setVisible(false);
                }
                // init comet nodes
                cometNodes.getChildren().clear();
                for (Comet comet : SpaceElementCollection.getComets()) {
                    Node elementNode = CarringtonHelper.createNodeFromSpaceElement(comet);
                    cometNodes.getChildren().add(elementNode);
                    elementNode.setVisible(false);
                }
                // init legend nodes
                mapLegend.getChildren().clear();
                mapLegend.getChildren().addAll(CarringtonHelper.createSpaceElementsLabels());
                for (Node node : mapLegend.getChildren()) {
                    mapLegend.setNodeOpacity(node, false);
                }

            }
        });

        // Listen for property visibility changed => swap visibility 
        visibilityPlanet.addListener(new ChangeListener<Boolean>() {
            @Override
            public void changed(ObservableValue<? extends Boolean> observedBoolean, Boolean oldValue, Boolean newValue) {
                Node node = DrawHelper.findByName(planetNodes.getChildren(), currentPlanet.getName() + Planet.class.getSimpleName());
                if (!node.isVisible()) {
                    Circle planetNode = (Circle) node;
                    CarringtonElement element = findByName(getCarringtonPlanets(), currentPlanet.getName());
                    planetNode.setCenterX(CarringtonHelper.longToX(element.getLongitude()));
                    planetNode.setCenterY(CarringtonHelper.latToY(element.getLatitude()));
                }
                node.setVisible(!node.isVisible());
                // Update legend visibility
                node = DrawHelper.findByName(mapLegend.getChildren(), currentPlanet.getName() + Planet.class.getSimpleName());
                if (node != null) {
                    mapLegend.toggleNodeOpacity(node);
                }
            }
        });

        // Listen for property visibility changed => swap visibility 
        visibilityAllPlanets.addListener(new ChangeListener<Boolean>() {
            @Override
            public void changed(ObservableValue<? extends Boolean> observedBoolean, Boolean oldValue, Boolean newValue) {
                //log.debug("All planets visibility changed: "+ newValue);
                for (Node node : planetNodes.getChildren()) {
                    if (newValue) {
                        for (CarringtonElement carringtonPlanet : getCarringtonPlanets()) {
                            if (node.getId().contains(carringtonPlanet.getName())) {
                                Circle planetNode = (Circle) node;
                                planetNode.setCenterX(CarringtonHelper.longToX(carringtonPlanet.getLongitude()));
                                planetNode.setCenterY(CarringtonHelper.latToY(carringtonPlanet.getLatitude()));
                            }
                        }
                    }
                    node.setVisible(newValue);
                }

                for (Node node : mapLegend.getChildren()) {
                    if (node.getId().contains(Planet.class.getSimpleName())) {
                        mapLegend.setNodeOpacity(node, newValue);
                    }
                }
            }
        });

        // Listen for property visibility changed => swap visibility 
        visibilityProbe.addListener(new ChangeListener<Boolean>() {
            @Override
            public void changed(ObservableValue<? extends Boolean> observedBoolean, Boolean oldValue, Boolean newValue) {
                Node node = DrawHelper.findByName(probeNodes.getChildren(), currentProbe.getName() + Probe.class.getSimpleName());
                boolean visibleChanged = true;
                if (!node.isVisible()) {
                    CarringtonElement element = findByName(getCarringtonProbes(), currentProbe.getName());
                    if (element.isComputed()) {
                        Rectangle probeNode = (Rectangle) node;
                        probeNode.setX(CarringtonHelper.longToX(element.getLongitude()) - probeNode.getWidth() / 2);
                        probeNode.setY(CarringtonHelper.latToY(element.getLatitude()) - probeNode.getHeight() / 2);
                    } else {
                        visibleChanged = false;
                    }
                }

                if (visibleChanged) {
                    node.setVisible(!node.isVisible());

                    // Update legend visibility
                    node = DrawHelper.findByName(mapLegend.getChildren(), currentProbe.getName() + Probe.class.getSimpleName());
                    if (node != null) {
                        mapLegend.toggleNodeOpacity(node);
                    }
                }
            }
        });

        // Listen for property visibility changed => swap visibility 
        visibilityAllProbes.addListener(new ChangeListener<Boolean>() {
            @Override
            public void changed(ObservableValue<? extends Boolean> observedBoolean, Boolean oldValue, Boolean newValue) {
                for (Node node : probeNodes.getChildren()) {
                    boolean visibleChanged = true;
                    if (newValue) {
                        for (CarringtonElement element : getCarringtonProbes()) {
                            if (node.getId().contains(element.getName())) {
                                if (element.isComputed()) {
                                    Rectangle probeNode = (Rectangle) node;
                                    probeNode.setX(CarringtonHelper.longToX(element.getLongitude()) - probeNode.getWidth() / 2);
                                    probeNode.setY(CarringtonHelper.latToY(element.getLatitude()) - probeNode.getHeight() / 2);
                                } else {
                                    visibleChanged = false;
                                }
                            }
                        }
                    }
                    if (visibleChanged) {
                        node.setVisible(newValue);

                        // Update legend visibility
                        Node probeNode = DrawHelper.findByName(mapLegend.getChildren(), node.getId());
                        if (probeNode != null) {
                            mapLegend.setNodeOpacity(probeNode, newValue);
                        }
                    }
                }
            }
        });

        // Listen for property visibility changed => swap visibility 
        visibilityComet.addListener(new ChangeListener<Boolean>() {
            @Override
            public void changed(ObservableValue<? extends Boolean> observedBoolean, Boolean oldValue, Boolean newValue) {
                Node node = DrawHelper.findByName(cometNodes.getChildren(), currentComet.getName() + Comet.class.getSimpleName());
                boolean visibleChanged = true;
                if (!node.isVisible()) {
                    CarringtonElement element = findByName(getCarringtonComets(), currentComet.getName());
                    if (element.isComputed()) {
                        Circle cometNode = (Circle) node;
                        cometNode.setCenterX(CarringtonHelper.longToX(element.getLongitude()));
                        cometNode.setCenterY(CarringtonHelper.latToY(element.getLatitude()));
                    } else {
                        visibleChanged = false;
                    }
                }

                if (visibleChanged) {
                    node.setVisible(!node.isVisible());

                    // Update legend visibility
                    node = DrawHelper.findByName(mapLegend.getChildren(), currentComet.getName() + Comet.class.getSimpleName());
                    if (node != null) {
                        mapLegend.toggleNodeOpacity(node);
                    }
                }
            }
        });

        // Listen for property visibility changed => swap visibility 
        visibilityAllComets.addListener(new ChangeListener<Boolean>() {
            @Override
            public void changed(ObservableValue<? extends Boolean> observedBoolean, Boolean oldValue, Boolean newValue) {
                for (Node node : cometNodes.getChildren()) {
                    boolean visibleChanged = true;
                    if (newValue) {
                        for (CarringtonElement element : getCarringtonComets()) {
                            if (node.getId().contains(element.getName())) {
                                if (element.isComputed()) {
                                    Circle cometNode = (Circle) node;
                                    cometNode.setCenterX(CarringtonHelper.longToX(element.getLongitude()));
                                    cometNode.setCenterY(CarringtonHelper.latToY(element.getLatitude()));
                                } else {
                                    visibleChanged = false;
                                }
                            }
                        }
                    }
                    if (visibleChanged) {
                        node.setVisible(newValue);

                        // Update legend visibility
                        Node cometNode = DrawHelper.findByName(mapLegend.getChildren(), node.getId());
                        if (cometNode != null) {
                            mapLegend.setNodeOpacity(cometNode, newValue);
                        }
                    }
                }
            }
        });

        // Listen for property visibility changed => swap visibility 
        visibilityStartEnd.addListener(new ChangeListener<Boolean>() {
            @Override
            public void changed(ObservableValue<? extends Boolean> observedBoolean, Boolean oldValue, Boolean visibility) {
                //log.debug("Start end visibility changed: "+ visibility);
                if (visibility) {
                    // hide all elements
                    for (Node node : planetNodes.getChildren()) {
                        node.setVisible(false);
                    }
                    for (Node node : probeNodes.getChildren()) {
                        node.setVisible(false);
                    }
                    for (Node node : cometNodes.getChildren()) {
                        node.setVisible(false);
                    }
                    for (Node node : mapLegend.getChildren()) {
                        mapLegend.setNodeOpacity(node, false);
                    }
                }
                if (startElement instanceof Probe) {
                    boolean visibleChanged = true;
                    Node node = DrawHelper.findByName(probeNodes.getChildren(), startElement.getName() + Probe.class.getSimpleName());
                    if (visibility) {
                        CarringtonElement element = findByName(getCarringtonProbes(), startElement.getName());
                        if (element!=null && element.isComputed()) {
                            Rectangle probeNode = (Rectangle) node;
                            probeNode.setX(CarringtonHelper.longToX(element.getLongitude()) - probeNode.getWidth() / 2);
                            probeNode.setY(CarringtonHelper.latToY(element.getLatitude()) - probeNode.getHeight() / 2);
                        } else {
                            visibleChanged = false;
                        }
                    }

                    if (visibleChanged) {
                        if (node != null) {
                            node.setVisible(visibility);
                        }
                        // Update legend visibility
                        node = DrawHelper.findByName(mapLegend.getChildren(), startElement.getName() + Probe.class.getSimpleName());
                        if (node != null) {
                            mapLegend.setNodeOpacity(node, visibility);
                        }
                    }
                } else if (startElement instanceof Planet) {
                    Node node = DrawHelper.findByName(planetNodes.getChildren(), startElement.getName() + Planet.class.getSimpleName());
                    if (visibility) {
                        Circle planetNode = (Circle) node;
                        CarringtonElement element = findByName(getCarringtonPlanets(), startElement.getName());
                        planetNode.setCenterX(CarringtonHelper.longToX(element.getLongitude()));
                        planetNode.setCenterY(CarringtonHelper.latToY(element.getLatitude()));
                    }
                    node.setVisible(visibility);
                    // Update legend visibility
                    node = DrawHelper.findByName(mapLegend.getChildren(), startElement.getName() + Planet.class.getSimpleName());
                    if (node != null) {
                        mapLegend.setNodeOpacity(node, visibility);
                    }
                } else if (startElement instanceof Comet) {
                    Node node = DrawHelper.findByName(cometNodes.getChildren(), startElement.getName() + Comet.class.getSimpleName());
                    if (visibility) {
                        Circle cometNode = (Circle) node;
                        CarringtonElement element = findByName(getCarringtonComets(), startElement.getName());
                        cometNode.setCenterX(CarringtonHelper.longToX(element.getLongitude()));
                        cometNode.setCenterY(CarringtonHelper.latToY(element.getLatitude()));
                    }
                    node.setVisible(visibility);
                    // Update legend visibility
                    node = DrawHelper.findByName(mapLegend.getChildren(), startElement.getName() + Comet.class.getSimpleName());
                    if (node != null) {
                        mapLegend.setNodeOpacity(node, visibility);
                    }
                }

                if (endElement != null) {
                    if (endElement instanceof Probe) {
                        boolean visibleChanged = true;
                        Node node = DrawHelper.findByName(probeNodes.getChildren(), endElement.getName() + Probe.class.getSimpleName());
                        if (visibility) {
                            CarringtonElement element = findByName(getCarringtonProbes(), endElement.getName());
                            if (element.isComputed()) {
                                Rectangle probeNode = (Rectangle) node;
                                probeNode.setX(CarringtonHelper.longToX(element.getLongitude()) - probeNode.getWidth() / 2);
                                probeNode.setY(CarringtonHelper.latToY(element.getLatitude()) - probeNode.getHeight() / 2);
                            } else {
                                visibleChanged = false;
                            }
                        }

                        if (visibleChanged) {
                            node.setVisible(visibility);
                            // Update legend visibility
                            node = DrawHelper.findByName(mapLegend.getChildren(), endElement.getName() + Probe.class.getSimpleName());
                            if (node != null) {
                                mapLegend.setNodeOpacity(node, visibility);
                            }
                        }
                    } else if (endElement instanceof Planet) {
                        Node node = DrawHelper.findByName(planetNodes.getChildren(), endElement.getName() + Planet.class.getSimpleName());
                        if (visibility) {
                            Circle planetNode = (Circle) node;
                            CarringtonElement element = findByName(getCarringtonPlanets(), endElement.getName());
                            planetNode.setCenterX(CarringtonHelper.longToX(element.getLongitude()));
                            planetNode.setCenterY(CarringtonHelper.latToY(element.getLatitude()));
                        }
                        node.setVisible(visibility);
                        // Update legend visibility
                        node = DrawHelper.findByName(mapLegend.getChildren(), endElement.getName() + Planet.class.getSimpleName());
                        if (node != null) {
                            mapLegend.setNodeOpacity(node, visibility);
                        }
                    } else if (endElement instanceof Comet) {
                        Node node = DrawHelper.findByName(cometNodes.getChildren(), endElement.getName() + Comet.class.getSimpleName());
                        if (visibility) {
                            Circle cometNode = (Circle) node;
                            CarringtonElement element = findByName(getCarringtonComets(), endElement.getName());
                            cometNode.setCenterX(CarringtonHelper.longToX(element.getLongitude()));
                            cometNode.setCenterY(CarringtonHelper.latToY(element.getLatitude()));
                        }
                        node.setVisible(visibility);
                        // Update legend visibility
                        node = DrawHelper.findByName(mapLegend.getChildren(), endElement.getName() + Comet.class.getSimpleName());
                        if (node != null) {
                            mapLegend.setNodeOpacity(node, visibility);
                        }
                    }
                }

            }
        });

    }

    /**
     * Init Drawing Carrington elements from Radial model
     *
     * @param radial
     */
    public void initialize(Radial radial) {
        CarringtonElement.setRadialModel(radial);
        initialize(ElementType.RADIAL);
    }

    /**
     * Init Drawing Carrington elements from Corotation model and available list
     * of space elements
     *
     * @param corotation
     */
    public void initialize(Corotation corotation) {
        CarringtonElement.setCorotationModel(corotation);
        initialize(ElementType.COROTATION);
    }

    /**
     * Init Drawing Carrington elements from SEP model and the available list of
     * space elements
     *
     * @param sep
     */
    public void initialize(Sep sep) {
        CarringtonElement.setSepModel(sep);
        initialize(ElementType.SEP);
    }

    /**
     * Reinit Drawing Carrington elements from the available list of space
     * elements
     */
    public void initialize() {
        if (carringtonPlanets.size() > 0) {
            initialize(carringtonPlanets.get(0).getType());
        }
    }

    private void initialize(ElementType model) {
        carringtonPlanets.clear();
        carringtonProbes.clear();
        carringtonComets.clear();
        // init common model
        for (SpaceElement planet : SpaceElementCollection.getPlanets()) {
            carringtonPlanets.add(new CarringtonElement(planet, model));
        }
        for (SpaceElement probe : SpaceElementCollection.getSpaceElementWrapper().getProbes()) {
            if (probe.isDefined()) {
                carringtonProbes.add(new CarringtonElement(probe, model));
            }
        }
        for (SpaceElement comet : SpaceElementCollection.getComets()) {
            carringtonComets.add(new CarringtonElement(comet, model));
        }
        // init drawing elements
        hasChanged.set(!hasChanged.get());
    }

    /**
     * Compute Carrington coordinates of all planets
     */
    public void updatePlanets() {
        for (CarringtonElement carringtonPlanet : carringtonPlanets) {
            carringtonPlanet.compute();
        }
    }

    /**
     * Compute Carrington coordinates of all probes
     */
    public void updateProbes() {
        for (CarringtonElement carringtonProbe : carringtonProbes) {
            carringtonProbe.compute();
        }
    }

    public void updateComets() {
        for (CarringtonElement carringtonComet : carringtonComets) {
            carringtonComet.compute();
        }
    }

    /**
     * Compute Carrington coordinates of a planet
     */
    public void update(Planet planet) {
        for (CarringtonElement carringtonPlanet : carringtonPlanets) {
            if (carringtonPlanet.getName().contains(planet.getName())) {
                //log.debug("Update Carrington planet position");
                carringtonPlanet.compute();
            }
        }
    }

    /**
     * Compute Carrington coordinates of a probe
     */
    public void update(Probe probe) {
        for (CarringtonElement carringtonProbe : carringtonProbes) {
            if (carringtonProbe.getName().contains(probe.getName())) {
                //log.debug("Update Carrington probe position");
                carringtonProbe.compute();
            }
        }
    }

    /**
     * Compute Carrington coordinates of a comet
     */
    public void update(Comet comet) {
        for (CarringtonElement carringtonComet : carringtonComets) {
            if (carringtonComet.getName().contains(comet.getName())) {
                //log.debug("Update Carrington comet position");
                carringtonComet.compute();
            }
        }
    }

    /**
     * Compute Carrington coordinates of a Space Element
     */
    public void update(SpaceElement element) {
        if (element instanceof Probe) {
            update((Probe) element);
        } else if (element instanceof Planet) {
            update((Planet) element);
        } else if (element instanceof Comet) {
            update((Comet) element);
        }
    }

    public void showHideStartEnd(SpaceElement startElt, SpaceElement endElt, boolean visibility) {
        //log.debug("SHOW HIDE START END: " + visibility);
        visibilityAllPlanets.set(false);
        visibilityAllProbes.set(false);
        visibilityAllComets.set(false);
        startElement = startElt;
        endElement = endElt;
        visibilityStartEnd.set(visibility);
    }

    public void showHide(Planet planet) {
        //log.debug("SHOW HIDE PLANET:"+ planet);
        currentPlanet = planet;
        visibilityPlanet.set(!visibilityPlanet.get());
    }

    public void showHidePlanets(boolean visibility) {
        //log.debug(" ===> SHOW HIDE ALL PLANETS:" + visibility);
        visibilityAllPlanets.set(visibility);
    }

    public boolean isVisibleAllPlanet() {
        return visibilityAllPlanets.get();
    }

    public void showHide(Probe probe) {
        //log.debug("SHOW HIDE PROBE:"+ probe);
        currentProbe = probe;
        visibilityProbe.set(!visibilityProbe.get());
    }

    public void refreshProbe(CarringtonElement pCe) {
        log.info("REFRESH:" + pCe.getName());
        Probe lProbe = (Probe) pCe.getElement();
        Node node = DrawHelper.findByName(probeNodes.getChildren(), lProbe.getName() + Probe.class.getSimpleName());
        Rectangle probeNode = (Rectangle) node;
        probeNode.setX(CarringtonHelper.longToX(pCe.getLongitude()) - probeNode.getWidth() / 2);
        probeNode.setY(CarringtonHelper.latToY(pCe.getLatitude()) - probeNode.getHeight() / 2);
    }

    public void showHideProbes(boolean visibility) {
        //log.debug("SHOW HIDE ALL PROBES:" + visibility);
        visibilityAllProbes.set(visibility);
    }

    public boolean isVisibleAllProbe() {
        return visibilityAllProbes.get();
    }

    public void showHide(Comet comet) {
        //log.debug("SHOW HIDE COMET:"+ comet);
        currentComet = comet;
        visibilityComet.set(!visibilityComet.get());
    }

    public void showHideComets(boolean visibility) {
        //log.debug("SHOW HIDE ALL COMETS:" + visibility);
        visibilityAllComets.set(visibility);
    }

    public boolean isVisibleAllComet() {
        return visibilityAllComets.get();
    }

    public boolean isVisibleStartEnd() {
        return visibilityStartEnd.get();
    }

    public void setVisibilityStartEnd(boolean visibility) {
        visibilityStartEnd.set(visibility);
    }

    public CarringtonElement findByName(List<CarringtonElement> elements, String name) {
        for (CarringtonElement element : elements) {
            if (element.getName() != null) {
                if (element.getName().contains(name)) {
                    return element;
                }
            }
        }
        return null;
    }

    /**
     * @return the carringtonPlanets
     */
    public List<CarringtonElement> getCarringtonPlanets() {
        return carringtonPlanets;
    }

    /**
     * @return the carringtonProbes
     */
    public List<CarringtonElement> getCarringtonProbes() {
        return carringtonProbes;
    }

    /**
     * @return the carringtonComets
     */
    public List<CarringtonElement> getCarringtonComets() {
        return carringtonComets;
    }

    /**
     * @return the visibilityProbe
     */
    public BooleanProperty getVisibilityProbe() {
        return visibilityProbe;
    }

    /**
     * @return the visibilityAllProbes
     */
    public BooleanProperty getVisibilityAllProbes() {
        return visibilityAllProbes;
    }
}
