/*
 * Copyright (c) 2017, 2018, Oracle and/or its affiliates. All rights reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * This code is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 only, as
 * published by the Free Software Foundation.  Oracle designates this
 * particular file as subject to the "Classpath" exception as provided
 * by Oracle in the LICENSE file that accompanied this code.
 *
 * This code is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * version 2 for more details (a copy is included in the LICENSE file that
 * accompanied this code).
 *
 * You should have received a copy of the GNU General Public License version
 * 2 along with this work; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 * or visit www.oracle.com if you need additional information or have any
 * questions.
 */

package test.robot.javafx.scene;

import javafx.application.Application;
import javafx.application.Platform;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.input.MouseButton;
import javafx.scene.layout.HBox;
import javafx.scene.robot.Robot;
import javafx.stage.Stage;
import javafx.stage.StageStyle;
import javafx.stage.WindowEvent;

import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;

import org.junit.AfterClass;
import org.junit.Assert;
import org.junit.BeforeClass;
import org.junit.Test;
import static org.junit.Assert.fail;

/*
 * Test to verify the events when scene of stage is changed.
 * Steps of Test:
 * 1. Create a stage with a scene with a button.
 * 2. Add MOUSE_EXITED listener & WindowProperty Listener to the same scene.
 * 3. In onAction of the button change stage's scene to a new scene.
 * 4. Verify that MOUSE_EXITED & WindowPropery change listener are called in
 *    sequence.
 */

public class SceneChangeEventsTest {
    static CountDownLatch startupLatch;
    static Robot robot;
    static volatile Stage stage;
    boolean mouseExited = false;
    boolean mouseWindowEventOrder = false;
    boolean windowChanged = false;

    public static void main(String[] args) {
        SceneChangeEventsTest test = new SceneChangeEventsTest();
        test.testSceneChange();
        exit();
    }

    @Test
    public void testSceneChange() {

        Button button = new Button("onAction");
        CountDownLatch onActionLatch = new CountDownLatch(1);
        button.setOnAction(event -> {
            stage.setScene(new Scene(new HBox()));
            onActionLatch.countDown();
        });
        HBox root = new HBox();
        root.getChildren().add(button);
        Scene scene = new Scene(root);
        CountDownLatch setSceneLatch = new CountDownLatch(1);
        stage.sceneProperty().addListener(observable -> setSceneLatch.countDown());
        Platform.runLater(() -> {
            stage.setScene(scene);
        });
        waitForLatch(setSceneLatch, 5, "Timeout while waiting for scene to be set on stage.");

        scene.setOnMouseExited(event -> {
            mouseExited = true;
        });
        scene.windowProperty().addListener(observable -> {
            mouseWindowEventOrder = mouseExited;
            windowChanged = true;
        });
        Platform.runLater(() -> {
            robot.mouseMove((int)(scene.getWindow().getX() + scene.getX() + button.getLayoutX() + button.getLayoutBounds().getWidth() / 2),
                    (int)(scene.getWindow().getY() + scene.getY() +  button.getLayoutY() + button.getLayoutBounds().getHeight() / 2));
            robot.mousePress(MouseButton.PRIMARY);
            robot.mouseRelease(MouseButton.PRIMARY);
        });
        waitForLatch(onActionLatch, 5, "Timeout while waiting for button.onAction().");

        Assert.assertTrue("MOUSE_EXITED should be received when scene is " +
            " changed.", mouseExited);
        Assert.assertTrue("scene.windowProperty() listener should be received" +
            "on scene change.", windowChanged);
        Assert.assertTrue("MOUSE_EXITED should have been received before " +
            "scene.windowProperty().", mouseWindowEventOrder);
    }

    public static class TestApp extends Application {
        @Override
        public void start(Stage primaryStage) {
            robot = new Robot();
            stage = primaryStage;
            stage.initStyle(StageStyle.UNDECORATED);
            stage.addEventHandler(WindowEvent.WINDOW_SHOWN, e ->
                    Platform.runLater(startupLatch::countDown));
            stage.setAlwaysOnTop(true);
            stage.show();
        }
    }

    @BeforeClass
    public static void initFX() {
        startupLatch = new CountDownLatch(1);
        new Thread(() -> Application.launch(TestApp.class, (String[])null)).start();
        waitForLatch(startupLatch, 10, "Timeout waiting for FX runtime to start");
    }

    @AfterClass
    public static void exit() {
        Platform.runLater(() -> {
            stage.hide();
        });
        Platform.exit();
    }

    public static void waitForLatch(CountDownLatch latch, int seconds, String msg) {
        try {
            if (!latch.await(seconds, TimeUnit.SECONDS)) {
                fail(msg);
            }
        } catch (Exception ex) {
            fail("Unexpected exception: " + ex);
        }
    }
}
