// Path: src/main/java/org/terasology/launcher/ui/FxTimer.java
// public final class FxTimer {
//     private final Duration actionTime;
//     private final Timeline timeline;
//     private final Runnable action;
// 
//     private long seq;
// 
// 
//     private FxTimer(java.time.Duration actionTime, java.time.Duration period, Runnable action, int cycles) {
//         this.actionTime = Duration.millis(actionTime.toMillis());
//         this.timeline = new Timeline();
//         this.action = action;
// 
//         timeline.getKeyFrames().add(new KeyFrame(this.actionTime)); // used as placeholder
//         if (period != actionTime) {
//             timeline.getKeyFrames().add(new KeyFrame(Duration.millis(period.toMillis())));
//         }
// 
//         timeline.setCycleCount(cycles);
//     }
// 
//     /**
//      * Prepares a (stopped) timer that lasts for {@code delay} and whose action runs when timer <em>ends</em>.
//      */
//     public static FxTimer create(java.time.Duration delay, Runnable action) {
//         return new FxTimer(delay, delay, action, 1);
//     }
// 
//     /**
//      * Equivalent to {@code create(delay, action).restart()}.
//      */
//     public static FxTimer runLater(java.time.Duration delay, Runnable action) {
//         FxTimer timer = create(delay, action);
//         timer.restart();
//         return timer;
//     }
// 
//     public void restart() {
//         stop();
//         long expected = seq;
//         timeline.getKeyFrames().set(0, new KeyFrame(actionTime, ae -> {
//             if (seq == expected) {
//                 action.run();
//             }
//         }));
//         timeline.play();
//     }
// 
//     public void stop() {
//         timeline.stop();
//         ++seq;
//     }
// }


import com.google.common.base.MoreObjects;
import javafx.concurrent.Task;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.slf4j.event.Level;
import org.terasology.launcher.ui.FxTimer;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.time.Duration;
import java.util.concurrent.Callable;
import java.util.function.Predicate;
import java.util.regex.Pattern;
import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.base.Verify.verify;
import static com.google.common.base.Verify.verifyNotNull;
// Copyright 2020 The Terasology Foundation
// SPDX-License-Identifier: Apache-2.0

package org.terasology.launcher.game;




/**
 * Starts and manages a game process.
 * <p>
 * Many of the characteristics of this task are described in {@link GameService}, as it's the expected access point to
 * this.
 * <p>
 * (An individual {@link Task} lasts as long as the process does, the {@link javafx.concurrent.Service Service}
 * provides a stable reference that doesn't have to be updated with each new task.)
 * <p>
 * Beware javafx's treatment of exceptions that come up in {@link Task Tasks}. An uncaught exception will be stored
 * in its {@link #exceptionProperty()} and never see its way to an executor's uncaught exception handler. It won't be
 * until you call this object's {@link #get()} that it will be thrown again.
 */
class RunGameTask extends Task<Boolean> {
    static final int EXIT_CODE_OK = 0;

    /**
     * The output of the process is tested against this for a sign that it launched successfully.
     * <p>
     * (We don't yet have a formal protocol about this with Terasology, so it won't show up if its minimum log level is
     * higher than {@link Level#INFO INFO}. But the default is to include {@link Level#INFO INFO}, so this should often
     * work.)
     */
    static final Predicate<String> START_MATCH = Pattern.compile("TerasologyEngine.+Initialization completed")
            .asPredicate();

    /**
     * How long a process has to live in order for us to call it successful.
     * <p>
     * If there's no output with {@link #START_MATCH} and it hasn't crashed after being up this long, it's probably
     * okay.
     */
    static final Duration SURVIVAL_THRESHOLD = Duration.ofSeconds(10);

    private static final Logger logger = LoggerFactory.getLogger(RunGameTask.class);

    protected final Callable<Process> starter;

    /**
     * Indicates whether we have set the {@link Task#updateValue value} of this Task yet.
     * <p>
     * The value is stored in a {@link javafx.beans.property.SimpleObjectProperty property} we can't directly
     * access from this task's thread, so it remembers it here.
     */
    private boolean valueSet;

// Path: src/main/java/org/terasology/launcher/ui/FxTimer.java
// public final class FxTimer {
//     private final Duration actionTime;
//     private final Timeline timeline;
//     private final Runnable action;
// 
//     private long seq;
// 
// 
//     private FxTimer(java.time.Duration actionTime, java.time.Duration period, Runnable action, int cycles) {
//         this.actionTime = Duration.millis(actionTime.toMillis());
//         this.timeline = new Timeline();
//         this.action = action;
// 
//         timeline.getKeyFrames().add(new KeyFrame(this.actionTime)); // used as placeholder
//         if (period != actionTime) {
//             timeline.getKeyFrames().add(new KeyFrame(Duration.millis(period.toMillis())));
//         }
// 
//         timeline.setCycleCount(cycles);
//     }
// 
//     /**
//      * Prepares a (stopped) timer that lasts for {@code delay} and whose action runs when timer <em>ends</em>.
//      */
//     public static FxTimer create(java.time.Duration delay, Runnable action) {
//         return new FxTimer(delay, delay, action, 1);
//     }
// 
//     /**
//      * Equivalent to {@code create(delay, action).restart()}.
//      */
//     public static FxTimer runLater(java.time.Duration delay, Runnable action) {
//         FxTimer timer = create(delay, action);
//         timer.restart();
//         return timer;
//     }
// 
//     public void restart() {
//         stop();
//         long expected = seq;
//         timeline.getKeyFrames().set(0, new KeyFrame(actionTime, ae -> {
//             if (seq == expected) {
//                 action.run();
//             }
//         }));
//         timeline.play();
//     }
// 
//     public void stop() {
//         timeline.stop();
//         ++seq;
//     }
// }


// Path: src/main/java/org/terasology/launcher/game/RunGameTask.java
import com.google.common.base.MoreObjects;
import javafx.concurrent.Task;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.slf4j.event.Level;
import org.terasology.launcher.ui.FxTimer;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.time.Duration;
import java.util.concurrent.Callable;
import java.util.function.Predicate;
import java.util.regex.Pattern;
import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.base.Verify.verify;
import static com.google.common.base.Verify.verifyNotNull;

// Copyright 2020 The Terasology Foundation
// SPDX-License-Identifier: Apache-2.0

package org.terasology.launcher.game;




/**
 * Starts and manages a game process.
 * <p>
 * Many of the characteristics of this task are described in {@link GameService}, as it's the expected access point to
 * this.
 * <p>
 * (An individual {@link Task} lasts as long as the process does, the {@link javafx.concurrent.Service Service}
 * provides a stable reference that doesn't have to be updated with each new task.)
 * <p>
 * Beware javafx's treatment of exceptions that come up in {@link Task Tasks}. An uncaught exception will be stored
 * in its {@link #exceptionProperty()} and never see its way to an executor's uncaught exception handler. It won't be
 * until you call this object's {@link #get()} that it will be thrown again.
 */
class RunGameTask extends Task<Boolean> {
    static final int EXIT_CODE_OK = 0;

    /**
     * The output of the process is tested against this for a sign that it launched successfully.
     * <p>
     * (We don't yet have a formal protocol about this with Terasology, so it won't show up if its minimum log level is
     * higher than {@link Level#INFO INFO}. But the default is to include {@link Level#INFO INFO}, so this should often
     * work.)
     */
    static final Predicate<String> START_MATCH = Pattern.compile("TerasologyEngine.+Initialization completed")
            .asPredicate();

    /**
     * How long a process has to live in order for us to call it successful.
     * <p>
     * If there's no output with {@link #START_MATCH} and it hasn't crashed after being up this long, it's probably
     * okay.
     */
    static final Duration SURVIVAL_THRESHOLD = Duration.ofSeconds(10);

    private static final Logger logger = LoggerFactory.getLogger(RunGameTask.class);

    protected final Callable<Process> starter;

    /**
     * Indicates whether we have set the {@link Task#updateValue value} of this Task yet.
     * <p>
     * The value is stored in a {@link javafx.beans.property.SimpleObjectProperty property} we can't directly
     * access from this task's thread, so it remembers it here.
     */
    private boolean valueSet;

    private FxTimer successTimer;