package primegui;

import javafx.application.Application;
import javafx.geometry.Insets;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.control.ProgressBar;
import javafx.scene.control.TextField;
import javafx.scene.layout.HBox;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;

/**
 * Counts the primes below a limit without freezing the window.
 */
public class PrimeCounterApp extends Application {

	private TextField limitField_;
	private Button countButton_;
	private Button cancelButton_;
	private ProgressBar countProgress_;
	private Label statusLabel_;
	private Label resultLabel_;
	private PrimeCountTask task_;     // the running count, or null; used only on the FX thread

	@Override
	public void start ( Stage stage ) {
		Label limitLabel = new Label("Count primes below:");
		limitField_ = new TextField("20000000");

		countButton_ = new Button("Count");
		countButton_.setOnAction(e -> startCount(false));

		cancelButton_ = new Button("Cancel");
		cancelButton_.setDisable(true);
		cancelButton_.setOnAction(e -> cancelCount());

		// an edit to the limit while counting makes the count stale: restart it
		limitField_.textProperty().addListener((obs,oldText,newText) -> restartCount());

		countProgress_ = new ProgressBar(0);
		statusLabel_ = new Label();
		resultLabel_ = new Label();

		HBox inputRow = new HBox(10,limitLabel,limitField_,countButton_,cancelButton_);
		VBox root = new VBox(10,inputRow,countProgress_,statusLabel_,resultLabel_);
		root.setPadding(new Insets(15));

		stage.setScene(new Scene(root));
		stage.setTitle("Prime Counter");
		stage.show();
	}

	/**
	 * @param quiet if true, an invalid limit is ignored without a message
	 *          (used while the user is still typing)
	 */
	private void startCount ( boolean quiet ) {
		int limit = readLimit(quiet);
		if ( limit < 0 ) {
			return;
		}

		PrimeCountTask task = new PrimeCountTask(limit);
		task_ = task;

		countButton_.setDisable(true);            // one count at a time
		cancelButton_.setDisable(false);
		resultLabel_.setText("");
		countProgress_.progressProperty().bind(task.progressProperty());
		statusLabel_.textProperty().bind(task.messageProperty());

		// each handler first checks that this task is still the current one,
		// so a task that has been replaced can't change the UI
		task.setOnSucceeded(e -> {
			if ( task == task_ ) {
				endCount();
				statusLabel_.setText("");
				showCount(limit,task.getValue());
			}
		});
		task.setOnFailed(e -> {
			if ( task == task_ ) {
				endCount();
				statusLabel_.setText("Counting failed: " + task.getException().getMessage());
			}
		});
		task.setOnCancelled(e -> {
			if ( task == task_ ) {
				endCount();
				statusLabel_.setText("Cancelled");
			}
		});

		Thread thread = new Thread(task,"prime-count");
		thread.setDaemon(true);                   // closing the window ends the program
		thread.start();
	}

	/** If a count is running, cancels it and starts one for the new limit. */
	private void restartCount () {
		if ( task_ != null && task_.isRunning() ) {
			task_.cancel();
			startCount(true);
		}
	}

	private void cancelCount () {
		if ( task_ != null && task_.isRunning() ) {
			task_.cancel();
		}
	}

	/** Called by every ending handler, on the FX thread. */
	private void endCount () {
		countProgress_.progressProperty().unbind();
		countProgress_.setProgress(0);
		statusLabel_.textProperty().unbind();
		countButton_.setDisable(false);
		cancelButton_.setDisable(true);
		task_ = null;
	}

	private void showCount ( int limit, int count ) {
		resultLabel_.setText("Primes below " + limit + ": " + count);
	}

	/**
	 * @param quiet if true, don't show a message for an invalid limit
	 * @return the limit from the text field, or -1 if it isn't a whole number
	 *         of at least 2
	 */
	private int readLimit ( boolean quiet ) {
		try {
			int limit = Integer.parseInt(limitField_.getText().trim());
			if ( limit >= 2 ) {
				return limit;
			}
		} catch ( NumberFormatException e ) {}
		if ( !quiet ) {
			statusLabel_.setText("The limit must be a whole number of at least 2, e.g. 20000000");
		}
		return -1;
	}

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

}
