/* ----------------------------------------
 * 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 : 12/09/2023 : LBE : V2.8
 * DM-ID : Redmine#10500 : 12/09/2023 : new END points
 * Version : 05/08/2024 : LBE : V2.11
 * DM-ID : Redmine#l0763 : 05/08/2024 : zoom rectangle added
 * ----------------------------------------
 */
package fr.tlse.irap.pt.client.controller.drawingPlane;

import java.util.List;

import fr.tlse.irap.pos.Position;
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.spaceElement.Sun;
import fr.tlse.irap.utils.CoordConverter;
import javafx.scene.Cursor;
import javafx.scene.Group;
import javafx.scene.Node;
import javafx.scene.control.Label;
import javafx.scene.control.Tooltip;
import javafx.scene.effect.InnerShadow;
import javafx.scene.paint.Color;
import javafx.scene.paint.CycleMethod;
import javafx.scene.paint.RadialGradient;
import javafx.scene.paint.Stop;
import javafx.scene.shape.Arc;
import javafx.scene.shape.Circle;
import javafx.scene.shape.Ellipse;
import javafx.scene.shape.LineTo;
import javafx.scene.shape.MoveTo;
import javafx.scene.shape.Path;
import javafx.scene.shape.PathElement;
import javafx.scene.shape.Polygon;
import javafx.scene.shape.QuadCurveTo;
import javafx.scene.shape.Rectangle;

public class DrawHelper {

    final static String ORBIT = "Orbit";

    public final static double INIT_SCALE = 1.0;
    private static Double scale = 1.0;

    public static enum DrawingCmeType {
        CME, SPIRAL
    };

    public static enum ViewType {
        XY, XZ
    };

    /**
     * Create graphical node corresponding to the kind of Space Element (sun or
     * planet or probe)
     *
     * @param spaceElement
     * @return
     */
    public static Node createNodeFrom(SpaceElement spaceElement, ViewType viewType) {
        if (spaceElement instanceof Sun) {
            return createCircle(spaceElement, viewType);
        } else if (spaceElement instanceof Probe) {
            return createProbe(spaceElement, viewType);
        } else if (spaceElement instanceof Planet) {
            return createCircle(spaceElement, viewType);
        } else if (spaceElement instanceof Comet) {
            return createCircle(spaceElement, viewType);
        } else {
            return null;
        }
    }

    /**
     *
     * @param elementNode
     * @param spaceElement
     * @return
     */
    public static Node createLabelFrom(Node elementNode, SpaceElement spaceElement) {
        if (spaceElement instanceof Probe) {
            return createProbeLabel(elementNode, spaceElement);
        } else if (spaceElement instanceof Planet) {
            return createCircleLabel(elementNode, spaceElement);
        } else if (spaceElement instanceof Comet) {
            return createCircleLabel(elementNode, spaceElement);
        } else {
            return null;
        }
    }

    private static Node createProbeLabel(Node n, SpaceElement spaceElement) {
        Label label = new Label(spaceElement.getLongName());
        label.setLayoutX(n.getLayoutX());
        label.setLayoutY(n.getLayoutY());
        label.setVisible(false);
        label.setId("Label");

        return label;
    }

    private static Node createCircleLabel(Node n, SpaceElement spaceElement) {
        Label label = new Label(spaceElement.getLongName());
        label.setLayoutX(((Circle) n).getCenterX());
        label.setLayoutY(((Circle) n).getCenterY());
        label.setVisible(false);
        label.setId("Label");

        return label;
    }

    /**
     * Create a planet
     *
     * @param spaceElement
     * @return Circle
     */
    private static Circle createCircle(SpaceElement spaceElement, ViewType viewType) {
        Color c;
        if (spaceElement.getColor() == null) {
            c = Color.WHITE;
        } else {
            c = Color.web(spaceElement.getColor());
        }
        final Circle node = new Circle(spaceElement.getSize(),
                new RadialGradient(0, 0, 0.2, 0.3, 1, true, CycleMethod.NO_CYCLE, new Stop[]{
            new Stop(0, Color.rgb(250, 250, 255)), new Stop(1, c)}));

        node.setCenterX(spaceElement.getDisplayX());
        if (viewType.equals(ViewType.XY)) {
            node.setCenterY(spaceElement.getDisplayY());
        } else {
            node.setCenterY(spaceElement.getDisplayZ());
        }

        buildNode(node, spaceElement, c);
        return node;
    }

    /**
     * Create probe
     *
     * @param spaceElement
     * @return Group of nodes
     */
    private static Group createProbe(SpaceElement spaceElement, ViewType viewType) {
        Color c;
        if (spaceElement.getColor() == null) {
            c = Color.BLUE;
        } else {
            c = Color.web(spaceElement.getColor());
        }
        final Circle circleNode = new Circle(spaceElement.getSize(),
                new RadialGradient(0, 0, 0.2, 0.3, 1, true, CycleMethod.NO_CYCLE, new Stop[]{
            new Stop(0, Color.rgb(250, 250, 255)), new Stop(1, c)}));

        double xCenter = spaceElement.getDisplayX();
        double yCenter;
        if (viewType.equals(ViewType.XY)) {
            yCenter = spaceElement.getDisplayY();
        } else {
            yCenter = spaceElement.getDisplayZ();
        }

        circleNode.setCenterX(xCenter);
        circleNode.setCenterY(yCenter);

        final Path pathProbe = new Path();
        MoveTo moveTo;
        QuadCurveTo quadCurveTo;
        double xPos, yPos;

        xPos = xCenter + spaceElement.getSize();
        yPos = yCenter + spaceElement.getSize() * 2 / 3;
        moveTo = new MoveTo(xPos, yPos);
        pathProbe.getElements().add(moveTo);
        xPos = xPos + 3 * spaceElement.getSize();
        yPos = yCenter + spaceElement.getSize();
        quadCurveTo = new QuadCurveTo(xPos, yPos, xPos, yPos);
        pathProbe.getElements().add(quadCurveTo);
        yPos = yPos - 2 * spaceElement.getSize();
        quadCurveTo = new QuadCurveTo(xPos, yPos, xPos, yPos);
        pathProbe.getElements().add(quadCurveTo);
        xPos = xPos - 3 * spaceElement.getSize();
        yPos = yPos + spaceElement.getSize() / 3;
        quadCurveTo = new QuadCurveTo(xPos, yPos, xPos, yPos);
        pathProbe.getElements().add(quadCurveTo);

        xPos = xCenter - spaceElement.getSize();
        yPos = yCenter + spaceElement.getSize() * 2 / 3;
        moveTo = new MoveTo(xPos, yPos);
        pathProbe.getElements().add(moveTo);
        xPos = xPos - 3 * spaceElement.getSize();
        yPos = yCenter + spaceElement.getSize();
        quadCurveTo = new QuadCurveTo(xPos, yPos, xPos, yPos);
        pathProbe.getElements().add(quadCurveTo);
        yPos = yPos - 2 * spaceElement.getSize();
        quadCurveTo = new QuadCurveTo(xPos, yPos, xPos, yPos);
        pathProbe.getElements().add(quadCurveTo);
        xPos = xPos + 3 * spaceElement.getSize();
        yPos = yPos + spaceElement.getSize() / 3;
        quadCurveTo = new QuadCurveTo(xPos, yPos, xPos, yPos);
        pathProbe.getElements().add(quadCurveTo);

        pathProbe.setStroke(c);

        final Group node = new Group();
        node.getChildren().add(circleNode);
        node.getChildren().add(pathProbe);

        buildNode(node, spaceElement, c);
        return node;
    }

    /**
     * Buid node. - set id - set up the hand cursor. - link the tool tip to the
     * node.
     *
     * @param node
     * @param spaceElement
     * @param c
     */
    private static void buildNode(Node node, SpaceElement element, Color c) {
        node.setId(element.getName() + element.getClass().getSimpleName());

        node.setUserData(element);
        if (node instanceof Group) {
            for (Node lNode : ((Group) node).getChildren()) {
                lNode.setUserData(element);
            }
        }
        node.setCursor(Cursor.HAND);
        node.setEffect(new InnerShadow(7, c.darker().darker()));
        // create tool tip
        Tooltip tooltip = new Tooltip(element.getLongName());
        Tooltip.install(node, tooltip);
    }

    /**
     * Create elliptic representation of a space element
     *
     * @param space element
     * @return
     */
    public static Path createEllipse(SpaceElement element, ViewType viewType, Color color) {
        final Path ellipse = new Path();
        MoveTo moveTo;
        QuadCurveTo quadCurveTo;
        boolean lineFrom = false;

        for (Position pos : element.getDisplayPositions()) {
            lineFrom = !lineFrom;
            if (lineFrom) {
                if (viewType.equals(ViewType.XY)) {
                    moveTo = new MoveTo(pos.getX(), pos.getY());
                } else {
                    moveTo = new MoveTo(pos.getX(), pos.getZ());
                }
                ellipse.getElements().add(moveTo);
            } else {
                if (viewType.equals(ViewType.XY)) {
                    quadCurveTo = new QuadCurveTo(pos.getX(), pos.getY(), pos.getX(), pos.getY());
                } else {
                    quadCurveTo = new QuadCurveTo(pos.getX(), pos.getZ(), pos.getX(), pos.getZ());
                }
                ellipse.getElements().add(quadCurveTo);
            }
        }
        ellipse.setId(element.getName() + ORBIT + element.getClass().getSimpleName());
        ellipse.setFill(color);
        ellipse.setStroke(color);

        return ellipse;
    }

    public static Path createEllipse(SpaceElement element, ViewType viewType) {
        return createEllipse(element, viewType, Color.GREY);
    }

    /**
     * Apply zoom on a space element
     */
    public static void zoomNode(Node node, double zoomFactor) {
        if (node instanceof Circle) {
            zoomNode((Circle) node, zoomFactor);
        } else if (node instanceof Rectangle) {
            zoomNode((Rectangle) node, zoomFactor);
        } else if (node instanceof Ellipse) {
            zoomNode((Ellipse) node, zoomFactor);
        } else if (node instanceof Arc) {
            zoomNode((Arc) node, zoomFactor);
        } else if (node instanceof Polygon) {
            zoomNode((Polygon) node, zoomFactor);
        } else if (node instanceof Path) {
            zoomNode((Path) node, zoomFactor);
        } else if (node instanceof Group) {
            zoomNode((Group) node, zoomFactor);
        }
    }

    public static void zoomNode(Group group, double zoomFactor) {
        for (Node node : group.getChildren()) {
            zoomNode(node, zoomFactor);
        }
    }

    public static void zoomNode(Polygon polygon, double zoomFactor) {
        Double point;
        for (int i = 0; i < polygon.getPoints().size(); i++) {
            point = polygon.getPoints().get(i);
            point = point * zoomFactor;
            polygon.getPoints().set(i, point);
        }
    }

    public static void zoomNode(Circle circle, double zoomFactor) {
        circle.setCenterX(circle.getCenterX() * zoomFactor);
        circle.setCenterY(circle.getCenterY() * zoomFactor);
        if ((circle.getId() != null && circle.getId().equals("CME"))
                || (circle.getUserData() != null && circle.getUserData() instanceof SpaceElement
                && ((SpaceElement) circle.getUserData()).getId() == 10) //zoom affect only Sun
                && ((zoomFactor > 1 && scale > 0.25)
                || (zoomFactor < 1 && scale > 0.50))) {
            circle.setRadius(circle.getRadius() * zoomFactor);
        }
    }

    public static void zoomNode(Rectangle shape, double zoomFactor) {
        //shape.setX(shape.getX()* zoomFactor-shape.getWidth()/2);
        //shape.setY(shape.getY()* zoomFactor-shape.getHeight()/2);
        shape.setX((shape.getX()+shape.getWidth()/2)* zoomFactor-shape.getWidth()/2);
        shape.setY((shape.getY()+shape.getHeight()/2)* zoomFactor-shape.getHeight()/2);
    }

    public static void zoomNode(Ellipse ellipse, double zoomFactor) {
        ellipse.setCenterX(ellipse.getCenterX() * zoomFactor);
        ellipse.setCenterY(ellipse.getCenterY() * zoomFactor);
        ellipse.setRadiusX(ellipse.getRadiusX() * zoomFactor);
        ellipse.setRadiusY(ellipse.getRadiusY() * zoomFactor);
    }

    public static void zoomNode(Arc arc, double zoomFactor) {
        arc.setCenterX(arc.getCenterX() * zoomFactor);
        arc.setCenterY(arc.getCenterY() * zoomFactor);
        arc.setRadiusX(arc.getRadiusX() * zoomFactor);
        arc.setRadiusY(arc.getRadiusY() * zoomFactor);
    }

    public static void zoomNode(Path path, double zoomFactor) {
        //for probe drawing, just move quadcuve points from MoveTo values
        if (path.getUserData() != null && path.getUserData() instanceof Probe) {
            //Move solar arrays without resize
            Circle lCircle = (Circle) ((Group) path.getParent()).getChildren().get(0);
            //retreive move beetween old position and new
            double lX = lCircle.getCenterX() - lCircle.getCenterX() / zoomFactor;
            double lY = lCircle.getCenterY() - lCircle.getCenterY() / zoomFactor;
            path.setTranslateX(path.getTranslateX() + lX);
            path.setTranslateY(path.getTranslateY() + lY);
            return;
        }
        for (PathElement element : path.getElements()) {
            if (element instanceof MoveTo) {
                MoveTo moveTo = (MoveTo) element;
                moveTo.setX(moveTo.getX() * zoomFactor);
                moveTo.setY(moveTo.getY() * zoomFactor);
            } else if (element instanceof QuadCurveTo) {
                QuadCurveTo line = (QuadCurveTo) element;
                line.setX(line.getX() * zoomFactor);
                line.setY(line.getY() * zoomFactor);
                line.setControlX(line.getControlX() * zoomFactor);
                line.setControlY(line.getControlY() * zoomFactor);
            } else if (element instanceof LineTo) {
                LineTo line = (LineTo) element;
                line.setX(line.getX() * zoomFactor);
                line.setY(line.getY() * zoomFactor);
            }
        }
    }

    /**
     * Test if a space element is inside a rectangle
     *
     * @param circle
     * @param rec
     * @return
     */
    public static boolean isInside(SpaceElement element, double width, double height) {
        return (Math.abs(element.getDisplayX()) <= width / 2 && Math.abs(element.getDisplayY()) <= height / 2);
    }

    public static boolean isInside(Node node, double width, double height) {
        if (node instanceof Circle) {
            return isInside((Circle) node, width, height);
        } else if (node instanceof Path) {
            return isInside((Path) node, width, height);
        } else {
            return true;
        }
    }

    public static boolean isInside(Circle circle, double width, double height) {
        return isInside(circle.getCenterX(), circle.getCenterY(), width, height);
    }

    public static boolean isInside(Group group, double width, double height) {
        if (group.getChildren().size() > 0) {
            return isInside((Circle) group.getChildren().get(0), width, height);
        } else {
            return false;
        }
    }

    public static boolean isInside(Path path, double width, double height) {
        if (path.getElements().size() > 0) {
            PathElement e = path.getElements().get(0);
            if (e instanceof MoveTo) {
                return isInside(((MoveTo) e).getX(), ((MoveTo) e).getY(), width, height);
            } else if (e instanceof QuadCurveTo) {
                return isInside(((QuadCurveTo) e).getX(), ((QuadCurveTo) e).getY(), width, height);
            } else if (e instanceof LineTo) {
                return isInside(((LineTo) e).getX(), ((LineTo) e).getY(), width, height);
            } else {
                return false;
            }
        } else {
            return false;
        }
    }

    private static boolean isInside(double x, double y, double width, double height) {
        return (Math.abs(x) <= width / 2 && Math.abs(y) <= height / 2);
    }

    /**
     * @return the scale of the PathElement.
     */
    public static Double getScale() {
        return DrawHelper.scale;
    }

    /**
     * set up the scale.
     */
    public static void setScale(Double scale) {
        DrawHelper.scale = scale;
    }

    public static void showAllNodes(Group group) {
        for (Node node : group.getChildren()) {
            if (!node.isDisable()) {
                node.setVisible(true);
            }
        }

    }

    /**
     * @param nodes
     * @param id
     * @return
     */
    public static Node findByName(List<Node> nodes, String id) {
        for (Node node : nodes) {
            if (node.getId() != null) {
                if (node.getId().contains(id)) {
                    return node;
                }
            }
        }
        return null;
    }

    /**
     * transform HAE longitude in HEE system coordinates
     */
    public static double HAEToHEE(double longitude) {
        return CoordConverter.normalizeLongitude(longitude + Math.toDegrees(SpaceElement.getRotate()));
    }

}
