/*
 * Copyright 2014-2016 Arnaud Nouard. All rights reserved.
 *
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 * * Redistributions of source code must retain the above copyright notice, this
 *   list of conditions and the following disclaimer.
 *
 * * Redistributions in binary form must reproduce the above copyright notice,
 *   this list of conditions and the following disclaimer in the documentation
 *   and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
 * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */
package insidefx.undecorator;

import java.net.URL;
import java.util.Locale;
import java.util.Properties;
import java.util.ResourceBundle;
import java.util.logging.Level;
import java.util.logging.Logger;
import javafx.animation.FadeTransition;
import javafx.animation.ParallelTransition;
import javafx.animation.TranslateTransition;
import javafx.application.Platform;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.collections.ObservableList;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.fxml.FXML;
import javafx.fxml.FXMLLoader;
import javafx.geometry.Bounds;
import javafx.geometry.Side;
import javafx.scene.CacheHint;
import javafx.scene.Node;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.CheckMenuItem;
import javafx.scene.control.ContextMenu;
import javafx.scene.control.Label;
import javafx.scene.control.MenuItem;
import javafx.scene.control.SeparatorMenuItem;
import javafx.scene.control.Tooltip;
import javafx.scene.effect.BlurType;
import javafx.scene.effect.DropShadow;
import javafx.scene.effect.Effect;
import javafx.scene.input.KeyCode;
import javafx.scene.input.KeyCodeCombination;
import javafx.scene.input.KeyCombination;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.AnchorPane;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.Pane;
import javafx.scene.layout.Region;
import javafx.scene.layout.StackPane;
import javafx.scene.paint.Color;
import javafx.scene.shape.Rectangle;
import javafx.scene.shape.Shape;
import javafx.stage.Stage;
import javafx.stage.StageStyle;
import javafx.util.Duration;

/**
 * This class, with the UndecoratorController, is the central class for the
 * decoration of Transparent Stages. The Stage Undecorator TODO: Themes, manage
 * Quit (main stage)
 *
 * Bugs (Mac only?): Accelerators + Fullscreen crashes JVM KeyCombination does
 * not respect keyboard's locale. Multi screen: On second screen JFX returns
 * wrong value for MinY (300)
 */
public class Undecorator extends StackPane {

    public int SHADOW_WIDTH = 15;
    public int SAVED_SHADOW_WIDTH = 15;
    static public int RESIZE_PADDING = 7;
    static public int FEEDBACK_STROKE = 4;
    static public double ROUNDED_DELTA = 5;
    public static final Logger LOGGER = Logger.getLogger("Undecorator");
    public static final ResourceBundle LOC;
    StageStyle stageStyle;
    @FXML
    private Button menu;
    @FXML
    private Button close;
    @FXML
    private Button maximize;
    @FXML
    private Button minimize;
    @FXML
    private Button resize;
    @FXML
    private Button fullscreen;
    @FXML
    private Label title;
    @FXML
    private Pane decorationRoot;
    @FXML
    private ContextMenu contextMenu;

    MenuItem maximizeMenuItem;
    CheckMenuItem fullScreenMenuItem;
    Region clientArea;
    Pane stageDecoration = null;
    Rectangle shadowRectangle;
    Pane glassPane;
    Rectangle dockFeedback;
    FadeTransition dockFadeTransition;
    Stage dockFeedbackPopup;
    ParallelTransition parallelTransition;
    Effect dsFocused;
    Effect dsNotFocused;
    UndecoratorController undecoratorController;
    Stage stage;
    Rectangle backgroundRect;
    SimpleBooleanProperty maximizeProperty;
    SimpleBooleanProperty minimizeProperty;
    SimpleBooleanProperty closeProperty;
    SimpleBooleanProperty fullscreenProperty;
    String shadowBackgroundStyleClass = "decoration-shadow";
    String decorationBackgroundStyle = "decoration-background";
    TranslateTransition fullscreenButtonTransition;
    final Rectangle internal = new Rectangle();
    final Rectangle external = new Rectangle();

    static {
        LOC = ResourceBundle.getBundle("resources/localization", Locale.getDefault());
    }

    public SimpleBooleanProperty maximizeProperty() {
        return maximizeProperty;
    }

    public SimpleBooleanProperty minimizeProperty() {
        return minimizeProperty;
    }

    public SimpleBooleanProperty closeProperty() {
        return closeProperty;
    }

    public SimpleBooleanProperty fullscreenProperty() {
        return fullscreenProperty;
    }

    public Undecorator(Stage stage, Region root) {
        this(stage, root, "stagedecoration.fxml", StageStyle.UNDECORATED);
    }

    public Undecorator(Stage stag, Region clientArea, String stageDecorationFxml, StageStyle st) {
        create(stag, clientArea, getClass().getResource(stageDecorationFxml), st);
    }

    public Undecorator(Stage stag, Region clientArea, URL stageDecorationFxmlAsURL, StageStyle st) {
        create(stag, clientArea, stageDecorationFxmlAsURL, st);
    }

    public void create(Stage stag, Region clientArea, URL stageDecorationFxmlAsURL, StageStyle st) {
        this.stage = stag;
        this.clientArea = clientArea;

        setStageStyle(st);
        loadConfig();

        // Properties
        maximizeProperty = new SimpleBooleanProperty(false);
        maximizeProperty.addListener((ObservableValue<? extends Boolean> ov, Boolean t, Boolean t1) -> {
            getController().maximizeOrRestore();
        });
        minimizeProperty = new SimpleBooleanProperty(false);
        minimizeProperty.addListener((ObservableValue<? extends Boolean> ov, Boolean t, Boolean t1) -> {
            getController().minimize();
        });

        closeProperty = new SimpleBooleanProperty(false);
        closeProperty.addListener((ObservableValue<? extends Boolean> ov, Boolean t, Boolean t1) -> {
            getController().close();
        });
        fullscreenProperty = new SimpleBooleanProperty(false);
        fullscreenProperty.addListener((ObservableValue<? extends Boolean> ov, Boolean t, Boolean t1) -> {
            getController().setFullScreen(!stage.isFullScreen());
        });

        // The controller
        undecoratorController = new UndecoratorController(this);
        if (stage.getUserData() != null) {
            undecoratorController.setAsStageDraggable(stage, (Node) stage.getUserData());
        } else {
            undecoratorController.setAsStageDraggable(stage, clientArea);
        }

        // Focus drop shadows: radius, spread, offsets
        dsFocused = new DropShadow(BlurType.THREE_PASS_BOX, Color.BLACK, SHADOW_WIDTH, 0.1, 0, 0);
        dsNotFocused = new DropShadow(BlurType.THREE_PASS_BOX, Color.DARKGREY, SHADOW_WIDTH, 0, 0, 0);

        shadowRectangle = new Rectangle();
        shadowRectangle.layoutBoundsProperty().addListener((ObservableValue<? extends Bounds> observable, Bounds oldBounds, Bounds newBounds) -> {
            if (SHADOW_WIDTH != 0) {
                shadowRectangle.setVisible(true);
                setShadowClip(newBounds);
            } else {
                shadowRectangle.setVisible(false);
            }
        });

        // UI part of the decoration
        try {
            FXMLLoader fxmlLoader = new FXMLLoader(stageDecorationFxmlAsURL);
            fxmlLoader.setController(this);
            stageDecoration = (Pane) fxmlLoader.load();
        } catch (Exception ex) {
            LOGGER.log(Level.SEVERE, "Decorations not found", ex);
        }

        initDecoration();

        /*
         * Resize rectangle
         */
        undecoratorController.setStageResizableWith(stage, decorationRoot, RESIZE_PADDING, SHADOW_WIDTH);

        // If not resizable (quick fix)
        if (fullscreen != null) {
            fullscreen.setVisible(stage.isResizable());
        }
        if (resize != null) {
            resize.setVisible(stage.isResizable());
        }
        if (minimize != null) {
            minimize.setVisible(stage.isResizable());
        }
        if (maximize != null) {
            maximize.setVisible(stage.isResizable());
        }
        if (menu != null) {
            menu.setVisible(stage.isResizable());
        }
        if (minimize != null && !stage.isResizable()) {
            AnchorPane.setRightAnchor(minimize, 34d);
        }

        // Glass Pane
        glassPane = new Pane();

        glassPane.setMouseTransparent(true);
        buildDockFeedbackStage();

        title.getStyleClass().add("undecorator-label-titlebar");
        shadowRectangle.getStyleClass().add(shadowBackgroundStyleClass);
//        resizeRect.getStyleClass().add(resizeStyleClass);
        // Do not intercept mouse events on stage's shadow
        shadowRectangle.setMouseTransparent(true);

        // Is it possible to apply an effect without affecting decendent?
        super.setStyle("-fx-background-color:transparent;");
        // Or this:
//        super.setStyle("-fx-background-color:transparent;-fx-border-color:white;-fx-border-radius:30;-fx-border-width:1;-fx-border-insets:"+SHADOW_WIDTH+";");
//        super.setEffect(dsFocused);
//          super.getChildren().addAll(clientArea,stageDecoration, glassPane);

        backgroundRect = new Rectangle();
        backgroundRect.getStyleClass().add(decorationBackgroundStyle);
        backgroundRect.setMouseTransparent(true);

        // Add all layers
        super.getChildren().addAll(shadowRectangle, backgroundRect, clientArea, stageDecoration, glassPane);
//        super.getChildren().addAll(shadowRectangle, backgroundRect);

        /*
         * Focused stage
         */
        stage.focusedProperty().addListener((ObservableValue<? extends Boolean> ov, Boolean t, Boolean t1) -> {
            setShadowFocused(t1);
        });
        /*
         * Fullscreen
         */
        if (fullscreen != null) {

            stage.fullScreenProperty().addListener((ObservableValue<? extends Boolean> ov, Boolean t, Boolean fullscreenState) -> {
                setShadow(!fullscreenState);
                fullScreenMenuItem.setSelected(fullscreenState);
                maximize.setVisible(!fullscreenState);
                minimize.setVisible(!fullscreenState);
                if (resize != null) {
                    resize.setVisible(!fullscreenState);
                }
                if (fullscreenState) {
                    // String and icon
                    fullscreen.getStyleClass().add("decoration-button-unfullscreen");
                    fullscreen.setTooltip(new Tooltip(LOC.getString("Restore")));

                    undecoratorController.saveFullScreenBounds();
                    if (fullscreenButtonTransition != null) {
                        fullscreenButtonTransition.stop();
                    }
                    // Animate the fullscreen button
                    fullscreenButtonTransition = new TranslateTransition();
                    fullscreenButtonTransition.setDuration(Duration.millis(2000));
                    fullscreenButtonTransition.setToX(80);
                    fullscreenButtonTransition.setNode(fullscreen);
                    fullscreenButtonTransition.setOnFinished(new EventHandler<ActionEvent>() {
                        @Override
                        public void handle(ActionEvent t) {
                            fullscreenButtonTransition = null;
                        }
                    });
                    fullscreenButtonTransition.play();
                    //  fullscreen.setOpacity(0.2);
                } else {
                    // String and icon
                    fullscreen.getStyleClass().remove("decoration-button-unfullscreen");
                    fullscreen.setTooltip(new Tooltip(LOC.getString("FullScreen")));

                    undecoratorController.restoreFullScreenSavedBounds(stage);
                    //  fullscreen.setOpacity(1);
                    if (fullscreenButtonTransition != null) {
                        fullscreenButtonTransition.stop();
                    }
                    // Animate the change
                    fullscreenButtonTransition = new TranslateTransition();
                    fullscreenButtonTransition.setDuration(Duration.millis(1000));
                    fullscreenButtonTransition.setToX(0);
                    fullscreenButtonTransition.setNode(fullscreen);
                    fullscreenButtonTransition.setOnFinished((ActionEvent t1) -> {
                        fullscreenButtonTransition = null;
                    });

                    fullscreenButtonTransition.play();
                }
            });
        }

        computeAllSizes();
    }

    /**
     * Compute the needed clip for stage's shadow border
     *
     * @param newBounds
     * @param shadowVisible
     */
    void setShadowClip(Bounds newBounds) {
        external.relocate(
                newBounds.getMinX() - SHADOW_WIDTH,
                newBounds.getMinY() - SHADOW_WIDTH
        );
        internal.setX(SHADOW_WIDTH);
        internal.setY(SHADOW_WIDTH);
        internal.setWidth(newBounds.getWidth());
        internal.setHeight(newBounds.getHeight());
        internal.setArcWidth(shadowRectangle.getArcWidth());    // shadowRectangle CSS cannot be applied on this
        internal.setArcHeight(shadowRectangle.getArcHeight());

        external.setWidth(newBounds.getWidth() + SHADOW_WIDTH * 2);
        external.setHeight(newBounds.getHeight() + SHADOW_WIDTH * 2);
        Shape clip = Shape.subtract(external, internal);
        shadowRectangle.setClip(clip);

    }

    /**
     * Install default accelerators
     *
     * @param scene
     */
    public void installAccelerators(Scene scene) {
        // Accelerators
        if (stage.isResizable()) {
            scene.getAccelerators().put(new KeyCodeCombination(KeyCode.F, KeyCombination.CONTROL_DOWN, KeyCombination.SHORTCUT_DOWN), (Runnable) () -> {
                switchFullscreen();
            });
        }
        scene.getAccelerators().put(new KeyCodeCombination(KeyCode.M, KeyCombination.SHORTCUT_DOWN), (Runnable) () -> {
            switchMinimize();
        });
        scene.getAccelerators().put(new KeyCodeCombination(KeyCode.W, KeyCombination.SHORTCUT_DOWN), (Runnable) () -> {
            switchClose();
        });
    }

    /**
     * Init the minimum/pref/max sizes in order to be reflected in the primary
     * stage
     */
    private void computeAllSizes() {
        double minWidth = minWidth(getWidth());
        setMinWidth(minWidth);
        double minHeight = minHeight(getHeight());
        setMinHeight(minHeight);

        double prefWidth = prefWidth(getWidth());
        setPrefWidth(prefWidth);
        setWidth(prefWidth);

        double prefHeight = prefHeight(getHeight());
        setPrefHeight(prefHeight);
        setHeight(prefHeight);

        double maxWidth = maxWidth(getWidth());
        if (maxWidth > minWidth) {
            setMaxWidth(maxWidth);
        }
        double maxHeight = maxHeight(getHeight());
        if (maxHeight > minHeight) {
            setMaxHeight(maxHeight);
        }
    }

    /*
     * The sizing is based on client area's bounds.
     */
    @Override
    protected double computePrefWidth(double d) {
        return clientArea.getPrefWidth() + SHADOW_WIDTH * 2 + RESIZE_PADDING * 2;
    }

    @Override
    protected double computePrefHeight(double d) {
        return clientArea.getPrefHeight() + SHADOW_WIDTH * 2 + RESIZE_PADDING * 2;
    }

    @Override
    protected double computeMaxHeight(double d) {
        double maxHeight = clientArea.getMaxHeight();
        if (maxHeight > 0) {
            return maxHeight + SHADOW_WIDTH * 2 + RESIZE_PADDING * 2;
        }
        return maxHeight;
    }

    @Override
    protected double computeMinHeight(double d) {
        double d2 = super.computeMinHeight(d);
        d2 += SHADOW_WIDTH * 2 + RESIZE_PADDING * 2;
        return d2;
    }

    @Override
    protected double computeMaxWidth(double d) {
        double maxWidth = clientArea.getMaxWidth();
        if (maxWidth > 0) {
            return maxWidth + SHADOW_WIDTH * 2 + RESIZE_PADDING * 2;
        }
        return maxWidth;
    }

    @Override
    protected double computeMinWidth(double d) {
        double d2 = super.computeMinWidth(d);
        d2 += SHADOW_WIDTH * 2 + RESIZE_PADDING * 2;
        return d2;
    }

    public void setStageStyle(StageStyle st) {
        stageStyle = st;
    }

    public StageStyle getStageStyle() {
        return stageStyle;
    }

    /**
     * Activate fade in transition on showing event
     */
    public void setFadeInTransition() {
        super.setOpacity(0);
        stage.showingProperty().addListener((ObservableValue<? extends Boolean> ov, Boolean t, Boolean t1) -> {
            if (t1) {
                FadeTransition fadeTransition = new FadeTransition(Duration.seconds(2), Undecorator.this);
                fadeTransition.setToValue(1);
                fadeTransition.play();
            }
        });
    }

    /**
     * Launch the fade out transition. Must be invoked when the
     * application/window is supposed to be closed
     */
    public void setFadeOutTransition() {
        FadeTransition fadeTransition = new FadeTransition(Duration.seconds(1), Undecorator.this);
        fadeTransition.setToValue(0);
        fadeTransition.play();
        fadeTransition.setOnFinished((ActionEvent t) -> {
            stage.hide();
            if (dockFeedbackPopup != null && dockFeedbackPopup.isShowing()) {
                dockFeedbackPopup.hide();
            }
        });
    }

    public void removeDefaultBackgroundStyleClass() {
        shadowRectangle.getStyleClass().remove(shadowBackgroundStyleClass);
    }

    public Rectangle getShadowNode() {
        return shadowRectangle;
    }

    public Rectangle getBackgroundRectangle() {
        return backgroundRect;
    }

    /**
     * Background opacity
     *
     * @param opacity
     */
    public void setBackgroundOpacity(double opacity) {
        shadowRectangle.setOpacity(opacity);
    }

    /**
     * Manage buttons and menu items
     */
    public void initDecoration() {
        MenuItem minimizeMenuItem = null;
        // Menu
        contextMenu.setAutoHide(true);
        if (minimize != null) { // Utility Stage
            minimizeMenuItem = new MenuItem(LOC.getString("Minimize"));
            minimizeMenuItem.setAccelerator(new KeyCodeCombination(KeyCode.M, KeyCombination.SHORTCUT_DOWN));

            minimizeMenuItem.setOnAction((ActionEvent e) -> {
                switchMinimize();
            });
            contextMenu.getItems().add(minimizeMenuItem);
        }
        if (maximize != null && stage.isResizable()) { // Utility Stage type
            maximizeMenuItem = new MenuItem(LOC.getString("Maximize"));
            maximizeMenuItem.setOnAction((ActionEvent e) -> {
                switchMaximize();
                contextMenu.hide(); // Stay stuck on screen
            });
            contextMenu.getItems().addAll(maximizeMenuItem, new SeparatorMenuItem());
        }

        // Fullscreen
        if (stageStyle != StageStyle.UTILITY && stage.isResizable()) {
            fullScreenMenuItem = new CheckMenuItem(LOC.getString("FullScreen"));
            fullScreenMenuItem.setOnAction((ActionEvent e) -> {
                // fake
                //maximizeProperty().set(!maximizeProperty().get());
                switchFullscreen();
            });
            fullScreenMenuItem.setAccelerator(new KeyCodeCombination(KeyCode.F, KeyCombination.CONTROL_DOWN, KeyCombination.SHORTCUT_DOWN));

            contextMenu.getItems().addAll(fullScreenMenuItem, new SeparatorMenuItem());
        }

        // Close
        MenuItem closeMenuItem = new MenuItem(LOC.getString("Close"));
        closeMenuItem.setOnAction((ActionEvent e) -> {
            switchClose();
        });
        closeMenuItem.setAccelerator(new KeyCodeCombination(KeyCode.W, KeyCombination.SHORTCUT_DOWN));

        contextMenu.getItems().add(closeMenuItem);

        menu.setOnMousePressed((MouseEvent t) -> {
            if (contextMenu.isShowing()) {
                contextMenu.hide();
            } else {
                contextMenu.show(menu, Side.BOTTOM, 0, 0);
            }
        });

        // Close button
        close.setTooltip(new Tooltip(LOC.getString("Close")));
        close.setOnAction((ActionEvent t) -> {
            switchClose();
        });

        // Maximize button
        // If changed via contextual menu
        maximizeProperty().addListener((ObservableValue<? extends Boolean> ov, Boolean t, Boolean t1) -> {
            if (!stage.isResizable()) {
                return;
            }
            Tooltip tooltip = maximize.getTooltip();
            if (tooltip.getText().equals(LOC.getString("Maximize"))) {
                tooltip.setText(LOC.getString("Restore"));
                maximizeMenuItem.setText(LOC.getString("Restore"));
                maximize.getStyleClass().add("decoration-button-restore");
                if (resize != null) {
                    resize.setVisible(false);
                }
            } else {
                tooltip.setText(LOC.getString("Maximize"));
                maximizeMenuItem.setText(LOC.getString("Maximize"));
                maximize.getStyleClass().remove("decoration-button-restore");
                if (resize != null) {
                    resize.setVisible(true);
                }
            }
        });

        if (maximize != null) { // Utility Stage
            maximize.setTooltip(new Tooltip(LOC.getString("Maximize")));
            maximize.setOnAction((ActionEvent t) -> {
                switchMaximize();
            });
        }
        if (fullscreen != null) { // Utility Stage
            fullscreen.setTooltip(new Tooltip(LOC.getString("FullScreen")));
            fullscreen.setOnAction((ActionEvent t) -> {
                switchFullscreen();
            });
        }

        // Minimize button
        if (minimize != null) { // Utility Stage
            minimize.setTooltip(new Tooltip(LOC.getString("Minimize")));
            minimize.setOnAction((ActionEvent t) -> {
                switchMinimize();
            });
        }
        // Transfer stage title to undecorator tiltle label
        stage.titleProperty().addListener(new ChangeListener<String>() {
            @Override
            public void changed(ObservableValue<? extends String> observable, String oldValue, String newValue) {
                title.setText(stage.getTitle());
            }
        });
        title.setText(stage.getTitle());
    }

    public void switchFullscreen() {
        // Invoke runLater even if it's on EDT: Crash apps on Mac
        Platform.runLater(() -> {
            undecoratorController.setFullScreen(!stage.isFullScreen());
        });
    }

    public void switchMinimize() {
        minimizeProperty().set(!minimizeProperty().get());
    }

    public void switchMaximize() {
        maximizeProperty().set(!maximizeProperty().get());
    }

    public void switchClose() {
        closeProperty().set(!closeProperty().get());
    }

    /**
     * Bridge to the controller to enable the specified node to drag the stage
     *
     * @param stage
     * @param node
     */
    public void setAsStageDraggable(Stage stage, Node node) {
        undecoratorController.setAsStageDraggable(stage, node);
    }

    /**
     * Switch the visibility of the window's drop shadow
     *
     * @param shadow
     */
    protected void setShadow(boolean shadow) {
        // Already removed?
        if (!shadow && shadowRectangle.getEffect() == null) {
            return;
        }
        // From fullscreen to maximize case
        if (shadow && maximizeProperty.get()) {
            return;
        }
        if (!shadow) {
            shadowRectangle.setEffect(null);
            SAVED_SHADOW_WIDTH = SHADOW_WIDTH;
            SHADOW_WIDTH = 0;
        } else {
            shadowRectangle.setEffect(dsFocused);
            SHADOW_WIDTH = SAVED_SHADOW_WIDTH;
        }
    }

    /**
     * Set on/off the stage shadow effect
     *
     * @param b
     */
    protected void setShadowFocused(boolean b) {
        // Do not change anything while maximized (in case of dialog closing for instance)
        if (stage.isFullScreen()) {
            return;
        }
        if (maximizeProperty().get()) {
            return;
        }
        if (b) {
            shadowRectangle.setEffect(dsFocused);
        } else {
            shadowRectangle.setEffect(dsNotFocused);
        }
    }

    /**
     * Set the layout of different layers of the stage
     */
    @Override
    public void layoutChildren() {
        Bounds b = super.getLayoutBounds();
        double w = b.getWidth();
        double h = b.getHeight();
        ObservableList<Node> list = super.getChildren();
//        ROUNDED_DELTA=shadowRectangle.getArcWidth()/4;
        ROUNDED_DELTA = 0;
        for (Node node : list) {
            if (node == shadowRectangle) {
                shadowRectangle.setWidth(w - SHADOW_WIDTH * 2);
                shadowRectangle.setHeight(h - SHADOW_WIDTH * 2);
                shadowRectangle.setX(SHADOW_WIDTH);
                shadowRectangle.setY(SHADOW_WIDTH);
            } else if (node == backgroundRect) {
                backgroundRect.setWidth(w - SHADOW_WIDTH * 2);
                backgroundRect.setHeight(h - SHADOW_WIDTH * 2);
                backgroundRect.setX(SHADOW_WIDTH);
                backgroundRect.setY(SHADOW_WIDTH);
            } else if (node == stageDecoration) {
                stageDecoration.resize(w - SHADOW_WIDTH * 2 - ROUNDED_DELTA * 2, h - SHADOW_WIDTH * 2 - ROUNDED_DELTA * 2);
                stageDecoration.setLayoutX(SHADOW_WIDTH + ROUNDED_DELTA);
                stageDecoration.setLayoutY(SHADOW_WIDTH + ROUNDED_DELTA);
            } //            else if (node == resizeRect) {
            //                resizeRect.setWidth(w - SHADOW_WIDTH * 2);
            //                resizeRect.setHeight(h - SHADOW_WIDTH * 2);
            //                resizeRect.setLayoutX(SHADOW_WIDTH);
            //                resizeRect.setLayoutY(SHADOW_WIDTH);
            //            }
            else {
                node.resize(w - SHADOW_WIDTH * 2 - ROUNDED_DELTA * 2, h - SHADOW_WIDTH * 2 - ROUNDED_DELTA * 2);
                node.setLayoutX(SHADOW_WIDTH + ROUNDED_DELTA);
                node.setLayoutY(SHADOW_WIDTH + ROUNDED_DELTA);
//                node.resize(w - SHADOW_WIDTH * 2 - RESIZE_PADDING * 2, h - SHADOW_WIDTH * 2 - RESIZE_PADDING * 2);
//                node.setLayoutX(SHADOW_WIDTH + RESIZE_PADDING);
//                node.setLayoutY(SHADOW_WIDTH + RESIZE_PADDING);
            }
        }
    }

    public int getShadowBorderSize() {
        return SHADOW_WIDTH * 2 + RESIZE_PADDING * 2;
    }

    public UndecoratorController getController() {
        return undecoratorController;
    }

    public Stage getStage() {
        return stage;
    }

    protected Pane getGlassPane() {
        return glassPane;
    }

    public void addGlassPane(Node node) {
        glassPane.getChildren().add(node);
    }

    public void removeGlassPane(Node node) {
        glassPane.getChildren().remove(node);
    }

    /**
     * Returns the decoration (buttons...)
     *
     * @return
     */
    public Pane getStageDecorationNode() {
        return stageDecoration;
    }

    /**
     * Prepare Stage for dock feedback display
     */
    void buildDockFeedbackStage() {
        dockFeedbackPopup = new Stage(StageStyle.TRANSPARENT);
        dockFeedback = new Rectangle(0, 0, 100, 100);
        dockFeedback.setArcHeight(10);
        dockFeedback.setArcWidth(10);
        dockFeedback.setFill(Color.TRANSPARENT);
        dockFeedback.setStroke(Color.BLACK);
        dockFeedback.setStrokeWidth(2);
        dockFeedback.setCache(true);
        dockFeedback.setCacheHint(CacheHint.SPEED);
        dockFeedback.setEffect(new DropShadow(BlurType.TWO_PASS_BOX, Color.BLACK, 10, 0.2, 3, 3));
        dockFeedback.setMouseTransparent(true);
        BorderPane borderpane = new BorderPane();
        borderpane.setStyle("-fx-background-color:transparent"); //J8
        borderpane.setCenter(dockFeedback);
        Scene scene = new Scene(borderpane);
        scene.setFill(Color.TRANSPARENT);
        dockFeedbackPopup.setScene(scene);
        dockFeedbackPopup.sizeToScene();
    }

    /**
     * Activate dock feedback on screen's bounds
     *
     * @param x
     * @param y
     * @param width
     * @param height
     */
    public void setDockFeedbackVisible(double x, double y, double width, double height) {
        dockFeedbackPopup.setX(x);
        dockFeedbackPopup.setY(y);

        dockFeedback.setX(SHADOW_WIDTH);
        dockFeedback.setY(SHADOW_WIDTH);
        dockFeedback.setHeight(height - SHADOW_WIDTH * 2);
        dockFeedback.setWidth(width - SHADOW_WIDTH * 2);

        dockFeedbackPopup.setWidth(width);
        dockFeedbackPopup.setHeight(height);

        dockFeedback.setOpacity(1);
        dockFeedbackPopup.show();

        dockFadeTransition = new FadeTransition();
        dockFadeTransition.setDuration(Duration.millis(200));
        dockFadeTransition.setNode(dockFeedback);
        dockFadeTransition.setFromValue(0);
        dockFadeTransition.setToValue(1);
        dockFadeTransition.setAutoReverse(true);
        dockFadeTransition.setCycleCount(3);

        dockFadeTransition.play();

    }

    public void setDockFeedbackInvisible() {
        if (dockFeedbackPopup.isShowing()) {
            dockFeedbackPopup.hide();
            if (dockFadeTransition != null) {
                dockFadeTransition.stop();
            }
        }
    }

    void loadConfig() {
        Properties prop = new Properties();
        try {
            prop.load(Undecorator.class.getClassLoader().getResourceAsStream("skin/undecorator.properties"));
            SHADOW_WIDTH = Integer.parseInt(prop.getProperty("window-shadow-width"));
            RESIZE_PADDING = Integer.parseInt(prop.getProperty("window-resize-padding"));
        } catch (Exception ex) {
            LOGGER.log(Level.SEVERE, "Error while loading confguration flie", ex);
        }
    }
}