import java.util.Scanner;
import javafx.application.Application;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.control.TextField;
import javafx.scene.layout.GridPane;
import javafx.stage.Stage;

/**
 * Suppose a spaceship races away from the earth at constant velocity v for time t
 * according to a clock on the spaceship. Einstein's Special Theory of Relativity tells 
 * us how much time u will have elapsed on earth: t^2 / u^2 = 1 - (v^2 / c^2).
 *
 * This version handles errors in user input and styles the text. 
 *
 * @author Albert Einstein 
 */
public class TimeDilator2 extends Application {

    @Override
    public void start(Stage primaryStage) {
        GridPane root = new GridPane();
        root.setAlignment(Pos.CENTER);
        root.setVgap(10);
        root.setHgap(10);
        Scene scene = new Scene(root, 450, 125);

        Label vLabel = new Label("Velocity as a fraction of light speed");
        Label tLabel = new Label("Travel time in years.");
        final double defaultVelocity = 0.5;
        final double defaultTime = 10;
        TextField vTextField = new TextField("" + defaultVelocity);
        TextField tTextField = new TextField("" + defaultTime);
        Button button = new Button("Elapsed time on Earth");
        String initLabelText = getElapsedTime(defaultVelocity, defaultTime);
        Label resultLabel = new Label(initLabelText);

        // Style strings for text in labels.
        String baseStyle = "-fx-font-size: 12px; -fx-font-weight:bold; ";
        String normalStyle = baseStyle + "-fx-text-fill: #2f4f4f";
        String errorStyle = baseStyle + "-fx-text-fill: #aa0000";

        class ButtonHandler implements EventHandler<ActionEvent> {

            @Override
            public void handle(ActionEvent event) {
                
                String vText = vTextField.getText();
                String tText = tTextField.getText();

                /* The following code would be shorter and simpler if we used exception
                 * handling techniques. Also, the error checking could be improved with
                 * more specific messages concerning the nature of the error, and the 
                 * error message could be displayed in the relevant text field instead 
                 * of the result label.*/
                 
                // Check for empty fields.
                if (vText.isEmpty() || tText.isEmpty()) {
                    setLabel(resultLabel, errorStyle, "MISSING INPUT");
                    return;
                }

                // Check for non-empty non-numeric input.
                if (!canBeParsedAsDouble(vText) || !canBeParsedAsDouble(tText)) {
                    setLabel(resultLabel, errorStyle, "NON-NUMERIC INPUT");
                    return;
                }
                
                // Parse the strings as double values. 
                double v = Double.parseDouble(vText);
                double t = Double.parseDouble(tText);

                // Check for invalid numbers.
                if (t < 0 || v < 0 || v >= 1) {
                    setLabel(resultLabel, errorStyle, "INVALID INPUT");
                    return;
                }

                // Input is valid, so update result label.
                setLabel(resultLabel, normalStyle, getElapsedTime(v, t));                
            }
        }
        button.setOnAction(new ButtonHandler());

        // set styles for all nodes
        vTextField.setStyle(normalStyle);
        tTextField.setStyle(normalStyle);
        vLabel.setStyle(normalStyle);
        tLabel.setStyle(normalStyle);
        button.setStyle(normalStyle);
        resultLabel.setStyle(normalStyle);

        // place nodes in the grid pane
        root.add(vLabel, 0, 0);
        root.add(tLabel, 0, 1);
        root.add(vTextField, 1, 0);
        root.add(tTextField, 1, 1);
        root.add(button, 0, 2);
        root.add(resultLabel, 1, 2);

        primaryStage.setTitle("Time Dilator");
        primaryStage.setScene(scene);
        primaryStage.show();
    }

    /**
     * Returns true if the given string can be parsed as a double.
     */
    private boolean canBeParsedAsDouble(String str) {
        Scanner in = new Scanner(str);
        if (in.hasNextDouble()) {
            
            // Extract the double from the stream and then check to see if any
            // extraneous characters remain.
            in.nextDouble(); 
            return !in.hasNext();
        }
        return false;
    }
    
    /**
     * Sets the text on a given label and applies a given style.
     */
    private void setLabel(Label label, String style, String text) {
        label.setStyle(style);
        label.setText(text);
    }

    /**
     * Calculates the time dilation for an object moving at constant velocity v (given as
     * a fraction of light speed) for time t (in years).
     */
    private static String getElapsedTime(double v, double t) {
        double u = Math.sqrt(t * t / (1 - v * v));
        return String.format("%.2f years", u);
    }

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