import javafx.application.Application;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.Node;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.GridPane;
import javafx.scene.layout.HBox;
import javafx.scene.layout.Pane;
import javafx.stage.Stage;

/**
 * Event capturing and bubbling. Everything prints to the console.
 *
 * The scene graph:   scene root (a GridPane)
 *                      +-- tableau (an HBox)       three of them, side by side
 *                            +-- pile (a Pane)
 *                                  +-- card (a Label)
 *
 * Click a card. A click travels in two passes:
 *   1. CAPTURE, from the scene root DOWN to the card: each node's event FILTERS run.
 *   2. BUBBLE, from the card back UP to the scene root: each node's event HANDLERS run.
 *
 * The scene root, and every tableau, pile and card, has a filter and a handler.
 *   Tableau 1: nothing else. The click goes all the way down and all the way back up.
 *   Tableau 2: its tableau FILTER consumes the click on the way down, so the pile and the
 *              card never see it, and no handler below the scene root runs.
 *   Tableau 3: its pile HANDLER consumes the click on the way up, so the tableau's
 *              handler never runs (the card's handler already has).
 *
 * Whether the scene root's handler runs is worth watching in each case.
 */
public class SimpleCaptureBubble extends Application {

    private void log(String phase, String name) {
        System.out.println("    " + phase + "   " + name);
    }

    // A filter (capture): runs on the way down, scene root first.
    private void addFilter(Node node, String name, boolean consume) {
        node.addEventFilter(MouseEvent.MOUSE_CLICKED, e -> {
            log("filter ", name);
            if (consume) {
                System.out.println("    -- " + name + " filter consumes the click --");
                e.consume();
            }
        });
    }

    // A handler (bubble): runs on the way back up, target first.
    private void addHandler(Node node, String name, boolean consume) {
        node.addEventHandler(MouseEvent.MOUSE_CLICKED, e -> {
            log("handler", name);
            if (consume) {
                System.out.println("    -- " + name + " handler consumes the click --");
                e.consume();
            }
        });
    }

    private Label card(String name) {
        Label card = new Label(name);
        card.setPrefSize(90, 70);
        card.setAlignment(Pos.TOP_CENTER);
        card.setStyle("-fx-background-color: white; -fx-border-color: #333;");
        return card;
    }

    // A pile: a Pane of overlapping cards, each one lower than the last.
    private Pane pile(int number, boolean consumeInHandler) {
        Pane pile = new Pane();
        pile.setPrefSize(90, 70 + 24 * 2);
        pile.setStyle("-fx-background-color: #e8d0f5; -fx-border-color: #6c3a9c;");
        for (int i = 0; i < 3; i++) {
            String name = "card " + number + "." + (i + 1);
            Label card = card(name);
            card.relocate(0, i * 24);
            addFilter(card, name, false);
            addHandler(card, name, false);
            pile.getChildren().add(card);
        }
        addFilter(pile, "pile " + number, false);
        addHandler(pile, "pile " + number, consumeInHandler);
        return pile;
    }

    // A tableau: an HBox holding one pile.
    private HBox tableau(int number, boolean consumeInFilter, boolean consumeInPileHandler) {
        HBox tableau = new HBox();
        tableau.setPadding(new Insets(10));
        tableau.setStyle("-fx-background-color: #cfe8ff; -fx-border-color: #1a4fa0;");
        tableau.getChildren().add(pile(number, consumeInPileHandler));
        addFilter(tableau, "tableau " + number, consumeInFilter);
        addHandler(tableau, "tableau " + number, false);
        return tableau;
    }

    @Override
    public void start(Stage stage) {
        GridPane root = new GridPane();
        root.setHgap(20);
        root.setVgap(10);
        root.setPadding(new Insets(15));

        root.add(new Label("1: no consuming"), 0, 0);
        root.add(new Label("2: filter consumes"), 1, 0);
        root.add(new Label("3: handler consumes"), 2, 0);
        root.add(tableau(1, false, false), 0, 1);
        root.add(tableau(2, true, false), 1, 1);
        root.add(tableau(3, false, true), 2, 1);

        // The scene root: its filter runs first, its handler last.
        root.addEventFilter(MouseEvent.MOUSE_CLICKED, e -> {
            System.out.println();
            System.out.println("click on " + describe(e.getTarget()));
        });
        addFilter(root, "scene root", false);
        addHandler(root, "scene root", false);

        stage.setScene(new Scene(root));
        stage.setTitle("Capturing and bubbling");
        stage.show();
    }

    // The name of the node that was clicked (the event's target). A Label's text is drawn by a
    // Text node inside it, so that Text is the real target: walk up to the Label.
    private String describe(Object target) {
        Node node = (Node) target;
        while (node != null && !(node instanceof Label) && !(node instanceof Button)) {
            node = node.getParent();
        }
        if (node instanceof Label) {
            return ((Label) node).getText() + " (the target is its Text child)";
        }
        return String.valueOf(target);
    }

    public static void main(String[] args) {
        launch(args);
    }
}