diff --git a/build.gradle b/build.gradle
new file mode 100644
index 0000000000..3984c94e5d
--- /dev/null
+++ b/build.gradle
@@ -0,0 +1,37 @@
+plugins {
+ id 'java'
+ id 'application'
+ id 'checkstyle'
+ id 'org.openjfx.javafxplugin' version '0.0.7'
+}
+
+checkstyle {
+ toolVersion = '8.23'
+}
+
+group 'seedu.duke'
+version '0.1.0'
+
+repositories {
+ mavenCentral()
+}
+
+javafx {
+ version = "11.0.2"
+ modules = [ 'javafx.controls', 'javafx.fxml' ]
+}
+
+application {
+ // Change this to your main class.
+ mainClassName = "Duke"
+}
+
+run {
+ standardInput = System.in
+}
+dependencies {
+ testImplementation 'org.junit.jupiter:junit-jupiter:5.5.0'
+}
+test {
+ useJUnitPlatform()
+}
\ No newline at end of file
diff --git a/config/checkstyle/checkstyle.xml b/config/checkstyle/checkstyle.xml
new file mode 100644
index 0000000000..b1a57ba6c0
--- /dev/null
+++ b/config/checkstyle/checkstyle.xml
@@ -0,0 +1,257 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/data/tasks.txt b/data/tasks.txt
new file mode 100644
index 0000000000..c300589777
--- /dev/null
+++ b/data/tasks.txt
@@ -0,0 +1,5 @@
+T}-}true}-}x}-}
+D}-}true}-}asd}-}12/12/1234 1234
+D}-}false}-}adfadg}-}13/1/2312 1222
+T}-}true}-}x}-}
+
diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar
new file mode 100644
index 0000000000..87b738cbd0
Binary files /dev/null and b/gradle/wrapper/gradle-wrapper.jar differ
diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties
new file mode 100644
index 0000000000..4b7e1f3d38
--- /dev/null
+++ b/gradle/wrapper/gradle-wrapper.properties
@@ -0,0 +1,5 @@
+distributionBase=GRADLE_USER_HOME
+distributionPath=wrapper/dists
+distributionUrl=https\://services.gradle.org/distributions/gradle-5.5.1-bin.zip
+zipStoreBase=GRADLE_USER_HOME
+zipStorePath=wrapper/dists
diff --git a/gradlew b/gradlew
new file mode 100644
index 0000000000..af6708ff22
--- /dev/null
+++ b/gradlew
@@ -0,0 +1,172 @@
+#!/usr/bin/env sh
+
+##############################################################################
+##
+## Gradle start up script for UN*X
+##
+##############################################################################
+
+# Attempt to set APP_HOME
+# Resolve links: $0 may be a link
+PRG="$0"
+# Need this for relative symlinks.
+while [ -h "$PRG" ] ; do
+ ls=`ls -ld "$PRG"`
+ link=`expr "$ls" : '.*-> \(.*\)$'`
+ if expr "$link" : '/.*' > /dev/null; then
+ PRG="$link"
+ else
+ PRG=`dirname "$PRG"`"/$link"
+ fi
+done
+SAVED="`pwd`"
+cd "`dirname \"$PRG\"`/" >/dev/null
+APP_HOME="`pwd -P`"
+cd "$SAVED" >/dev/null
+
+APP_NAME="Gradle"
+APP_BASE_NAME=`basename "$0"`
+
+# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+DEFAULT_JVM_OPTS='"-Xmx64m"'
+
+# Use the maximum available, or set MAX_FD != -1 to use that value.
+MAX_FD="maximum"
+
+warn () {
+ echo "$*"
+}
+
+die () {
+ echo
+ echo "$*"
+ echo
+ exit 1
+}
+
+# OS specific support (must be 'true' or 'false').
+cygwin=false
+msys=false
+darwin=false
+nonstop=false
+case "`uname`" in
+ CYGWIN* )
+ cygwin=true
+ ;;
+ Darwin* )
+ darwin=true
+ ;;
+ MINGW* )
+ msys=true
+ ;;
+ NONSTOP* )
+ nonstop=true
+ ;;
+esac
+
+CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
+
+# Determine the Java command to use to start the JVM.
+if [ -n "$JAVA_HOME" ] ; then
+ if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
+ # IBM's JDK on AIX uses strange locations for the executables
+ JAVACMD="$JAVA_HOME/jre/sh/java"
+ else
+ JAVACMD="$JAVA_HOME/bin/java"
+ fi
+ if [ ! -x "$JAVACMD" ] ; then
+ die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+ fi
+else
+ JAVACMD="java"
+ which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+fi
+
+# Increase the maximum file descriptors if we can.
+if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
+ MAX_FD_LIMIT=`ulimit -H -n`
+ if [ $? -eq 0 ] ; then
+ if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
+ MAX_FD="$MAX_FD_LIMIT"
+ fi
+ ulimit -n $MAX_FD
+ if [ $? -ne 0 ] ; then
+ warn "Could not set maximum file descriptor limit: $MAX_FD"
+ fi
+ else
+ warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
+ fi
+fi
+
+# For Darwin, add options to specify how the application appears in the dock
+if $darwin; then
+ GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
+fi
+
+# For Cygwin, switch paths to Windows format before running java
+if $cygwin ; then
+ APP_HOME=`cygpath --path --mixed "$APP_HOME"`
+ CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
+ JAVACMD=`cygpath --unix "$JAVACMD"`
+
+ # We build the pattern for arguments to be converted via cygpath
+ ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
+ SEP=""
+ for dir in $ROOTDIRSRAW ; do
+ ROOTDIRS="$ROOTDIRS$SEP$dir"
+ SEP="|"
+ done
+ OURCYGPATTERN="(^($ROOTDIRS))"
+ # Add a user-defined pattern to the cygpath arguments
+ if [ "$GRADLE_CYGPATTERN" != "" ] ; then
+ OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
+ fi
+ # Now convert the arguments - kludge to limit ourselves to /bin/sh
+ i=0
+ for arg in "$@" ; do
+ CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
+ CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
+
+ if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
+ eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
+ else
+ eval `echo args$i`="\"$arg\""
+ fi
+ i=$((i+1))
+ done
+ case $i in
+ (0) set -- ;;
+ (1) set -- "$args0" ;;
+ (2) set -- "$args0" "$args1" ;;
+ (3) set -- "$args0" "$args1" "$args2" ;;
+ (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
+ (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
+ (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
+ (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
+ (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
+ (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
+ esac
+fi
+
+# Escape application args
+save () {
+ for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
+ echo " "
+}
+APP_ARGS=$(save "$@")
+
+# Collect all arguments for the java command, following the shell quoting and substitution rules
+eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
+
+# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong
+if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then
+ cd "$(dirname "$0")"
+fi
+
+exec "$JAVACMD" "$@"
diff --git a/gradlew.bat b/gradlew.bat
new file mode 100644
index 0000000000..6d57edc706
--- /dev/null
+++ b/gradlew.bat
@@ -0,0 +1,84 @@
+@if "%DEBUG%" == "" @echo off
+@rem ##########################################################################
+@rem
+@rem Gradle startup script for Windows
+@rem
+@rem ##########################################################################
+
+@rem Set local scope for the variables with windows NT shell
+if "%OS%"=="Windows_NT" setlocal
+
+set DIRNAME=%~dp0
+if "%DIRNAME%" == "" set DIRNAME=.
+set APP_BASE_NAME=%~n0
+set APP_HOME=%DIRNAME%
+
+@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+set DEFAULT_JVM_OPTS="-Xmx64m"
+
+@rem Find java.exe
+if defined JAVA_HOME goto findJavaFromJavaHome
+
+set JAVA_EXE=java.exe
+%JAVA_EXE% -version >NUL 2>&1
+if "%ERRORLEVEL%" == "0" goto init
+
+echo.
+echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:findJavaFromJavaHome
+set JAVA_HOME=%JAVA_HOME:"=%
+set JAVA_EXE=%JAVA_HOME%/bin/java.exe
+
+if exist "%JAVA_EXE%" goto init
+
+echo.
+echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:init
+@rem Get command-line arguments, handling Windows variants
+
+if not "%OS%" == "Windows_NT" goto win9xME_args
+
+:win9xME_args
+@rem Slurp the command line arguments.
+set CMD_LINE_ARGS=
+set _SKIP=2
+
+:win9xME_args_slurp
+if "x%~1" == "x" goto execute
+
+set CMD_LINE_ARGS=%*
+
+:execute
+@rem Setup the command line
+
+set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
+
+@rem Execute Gradle
+"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
+
+:end
+@rem End local scope for the variables with windows NT shell
+if "%ERRORLEVEL%"=="0" goto mainEnd
+
+:fail
+rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
+rem the _cmd.exe /c_ return code!
+if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
+exit /b 1
+
+:mainEnd
+if "%OS%"=="Windows_NT" endlocal
+
+:omega
diff --git a/settings.gradle b/settings.gradle
new file mode 100644
index 0000000000..d1e92fe5db
--- /dev/null
+++ b/settings.gradle
@@ -0,0 +1 @@
+rootProject.name = 'duke'
diff --git a/src/main/java/DialogBox.java b/src/main/java/DialogBox.java
new file mode 100644
index 0000000000..2ded97e5c1
--- /dev/null
+++ b/src/main/java/DialogBox.java
@@ -0,0 +1,82 @@
+import java.io.IOException;
+import java.util.Collections;
+
+import javafx.collections.FXCollections;
+import javafx.collections.ObservableList;
+import javafx.fxml.FXML;
+import javafx.fxml.FXMLLoader;
+import javafx.geometry.Insets;
+import javafx.geometry.Pos;
+import javafx.scene.Node;
+import javafx.scene.control.Label;
+import javafx.scene.image.Image;
+import javafx.scene.image.ImageView;
+import javafx.scene.layout.Background;
+import javafx.scene.layout.BackgroundFill;
+import javafx.scene.layout.CornerRadii;
+import javafx.scene.layout.HBox;
+import javafx.scene.paint.Color;
+import javafx.scene.shape.Circle;
+import javafx.scene.shape.Rectangle;
+import javafx.scene.text.Text;
+
+/**
+ * An example of a custom control using FXML.
+ * This control represents a dialog box consisting of an ImageView to represent the speaker's face and a label
+ * containing text from the speaker.
+ */
+public class DialogBox extends HBox {
+ @FXML
+ private Text dialog;
+ @FXML
+ private ImageView displayPicture;
+ @FXML
+ private Background background;
+
+ private DialogBox(String text, Image img, BackgroundFill background_fill) {
+ try {
+ FXMLLoader fxmlLoader = new FXMLLoader(MainWindow.class.getResource("/view/DialogBox.fxml"));
+ fxmlLoader.setController(this);
+ fxmlLoader.setRoot(this);
+ fxmlLoader.load();
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+
+ dialog.setText(text);
+ dialog.setWrappingWidth(250.0);
+ displayPicture.setImage(img);
+ final Circle clip = new Circle(49.5, 49.5, 49.5);
+ displayPicture.setClip(clip);
+ background = new Background(background_fill);
+ Rectangle rect = new Rectangle(385,120);
+ rect.widthProperty().bind(this.widthProperty());
+ rect.heightProperty().bind(this.heightProperty().subtract(10));
+ rect.setArcHeight(60.0);
+ rect.setArcWidth(60.0);
+ this.setClip(rect);
+ this.setBackground(background);
+ }
+
+ /**
+ * Flips the dialog box such that the ImageView is on the left and text on the right.
+ */
+ private void flip() {
+ ObservableList tmp = FXCollections.observableArrayList(this.getChildren());
+ Collections.reverse(tmp);
+ getChildren().setAll(tmp);
+ setAlignment(Pos.TOP_LEFT);
+ }
+
+ public static DialogBox getUserDialog(String text, Image img) {
+ BackgroundFill background_fill = new BackgroundFill(Color.LIGHTBLUE, CornerRadii.EMPTY, Insets.EMPTY);
+ return new DialogBox(text, img, background_fill);
+ }
+
+ public static DialogBox getDukeDialog(String text, Image img) {
+ BackgroundFill background_fill = new BackgroundFill(Color.PINK, CornerRadii.EMPTY, Insets.EMPTY);
+ var db = new DialogBox(text, img, background_fill);
+ db.flip();
+ return db;
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/Duke.java b/src/main/java/Duke.java
index d62a957f62..3c6e7c5abe 100644
--- a/src/main/java/Duke.java
+++ b/src/main/java/Duke.java
@@ -1,12 +1,205 @@
-public class Duke {
- public static void main(String[] args) {
- String logo = " ____ _ \n"
- + "| _ \\ _ _| | _____ \n"
- + "| | | | | | | |/ / _ \\\n"
- + "| |_| | |_| | < __/\n"
- + "|____/ \\__,_|_|\\_\\___|\n";
- System.out.println("Hello from\n" + logo);
- }
-}
-
-//change
+import java.io.*;
+
+import command.Command;
+import javafx.application.Application;
+import javafx.scene.Scene;
+import javafx.scene.control.Label;
+import javafx.stage.Stage;
+
+
+import javafx.scene.control.Button;
+import javafx.scene.control.ScrollPane;
+import javafx.scene.control.TextField;
+import javafx.scene.layout.AnchorPane;
+import javafx.scene.layout.VBox;
+import javafx.scene.layout.Region;
+import javafx.scene.image.Image;
+import javafx.scene.image.ImageView;
+import process.DukeException;
+import process.Parser;
+import process.Storage;
+import process.Ui;
+import task.TaskList;
+
+import static java.lang.System.exit;
+
+/**
+ * Represents the task manager
+ */
+public class Duke extends Application {
+ private ScrollPane scrollPane;
+ private VBox dialogContainer;
+ private TextField userInput;
+ private Button sendButton;
+ private Scene scene;
+ private Storage storage;
+ private TaskList tasks;
+ private Ui ui;
+
+ /**
+ * Create a task manager with a task list from an existing file
+ * @param file to create a task list from
+ */
+ public Duke(String file) {
+ String directory = System.getProperty("user.home");
+ directory += "\\documents\\duke\\data";
+ String savefile = file;
+ String absolutePath = directory + File.separator + savefile;
+ storage = new Storage(absolutePath);
+ ui = new Ui();
+ try {
+ tasks = new TaskList(storage.load());
+ } catch (DukeException e) {
+ tasks = new TaskList();
+ ui.showLoadingError();
+ }
+ }
+
+ /**
+ * Execute the CLI task manager
+ */
+ public void run() {
+ ui.showWelcome();
+ boolean isExit = false;
+ while (!isExit) {
+ try {
+ String fullCommand = ui.readCommand();
+ ui.showLine();
+ Command c = Parser.parse(fullCommand);
+ c.execute(tasks, ui, storage);
+ isExit = c.isExit();
+ } catch (DukeException e) {
+ ui.showError(e.getMessage());
+ }
+ finally {
+ ui.showLine();
+ }
+ }
+ }
+
+ /**
+ * Da shit
+ * @param args i have no idea what this is for
+ */
+ public static void main(String[] args) {
+ new Duke("tasks.txt").run();
+ exit(0);
+ }
+
+ @Override
+ public void start(Stage stage) {
+ //Step 1. Setting up reqoutput += uired components
+
+ //The container for the content of the chat to scroll.
+ scrollPane = new ScrollPane();
+ dialogContainer = new VBox();
+ scrollPane.setContent(dialogContainer);
+
+ userInput = new TextField();
+ sendButton = new Button("Send");
+
+ AnchorPane mainLayout = new AnchorPane();
+ mainLayout.getChildren().addAll(scrollPane, userInput, sendButton);
+
+ scene = new Scene(mainLayout);
+
+ stage.setScene(scene);
+ stage.show();
+
+ //Step 2. Formatting the window to look as expected
+ stage.setTitle("Duke");
+ stage.setResizable(false);
+ stage.setMinHeight(600.0);
+ stage.setMinWidth(400.0);
+
+ mainLayout.setPrefSize(400.0, 600.0);
+
+ scrollPane.setPrefSize(385, 535);
+ scrollPane.setHbarPolicy(ScrollPane.ScrollBarPolicy.NEVER);
+ scrollPane.setVbarPolicy(ScrollPane.ScrollBarPolicy.ALWAYS);
+
+ scrollPane.setVvalue(1.0);
+ scrollPane.setFitToWidth(true);
+
+ // You will need to import `javafx.scene.layout.Region` for this.
+ dialogContainer.setPrefHeight(Region.USE_COMPUTED_SIZE);
+
+ userInput.setPrefWidth(325.0);
+
+ sendButton.setPrefWidth(55.0);
+
+ AnchorPane.setTopAnchor(scrollPane, 1.0);
+
+ AnchorPane.setBottomAnchor(sendButton, 1.0);
+ AnchorPane.setRightAnchor(sendButton, 1.0);
+
+ AnchorPane.setLeftAnchor(userInput , 1.0);
+ AnchorPane.setBottomAnchor(userInput, 1.0);
+
+ //Step 3. Add functionality to handle user input.
+ sendButton.setOnMouseClicked((event) -> {
+ dialogContainer.getChildren().add(getDialogLabel(userInput.getText()));
+ userInput.clear();
+ });
+
+ userInput.setOnAction((event) -> {
+ dialogContainer.getChildren().add(getDialogLabel(userInput.getText()));
+ userInput.clear();
+ });
+ //Scroll down to the end every time dialogContainer's height changes.
+ dialogContainer.heightProperty().addListener((observable) -> scrollPane.setVvalue(1.0));
+ // more code to be added here later
+ //Part 3. Add functionality to handle user input.
+ sendButton.setOnMouseClicked((event) -> {
+ handleUserInput();
+ });
+
+ userInput.setOnAction((event) -> {
+ handleUserInput();
+ });
+ }
+ private Image user = new Image(this.getClass().getResourceAsStream("/images/DaUser.jpg"));
+ private Image duke = new Image(this.getClass().getResourceAsStream("/images/DaDuke.jpg"));
+ /**
+ * Iteration 1:
+ * Creates a label with the specified text and adds it to the dialog container.
+ * @param text String containing text to add
+ * @return a label with the specified text that has word wrap enabled.
+ */
+ private Label getDialogLabel(String text) {
+ // You will need to import `javafx.scene.control.Label`.
+ Label textToAdd = new Label(text);
+ textToAdd.setWrapText(true);
+
+ return textToAdd;
+ }
+ /**
+ * Iteration 2:
+ * Creates two dialog boxes, one echoing user input and the other containing Duke's reply and then appends them to
+ * the dialog container. Clears the user input after processing.
+ */
+ private void handleUserInput() {
+ Label userText = new Label(userInput.getText());
+ Label dukeText = new Label(getResponse(userInput.getText()));
+ dialogContainer.getChildren().addAll(
+ DialogBox.getUserDialog(userText.getText(), new ImageView(user).getImage()),
+ DialogBox.getDukeDialog(dukeText.getText(), new ImageView(duke).getImage())
+ );
+ userInput.clear();
+ }
+
+ /**
+ * The response function for the GUI
+ * @param input user input
+ * @return the response
+ */
+ String getResponse(String input) {
+ try {
+ Command c = Parser.parse(input);
+ String toGui = c.execute(tasks, ui, storage);
+ return toGui;
+ } catch (DukeException e) {
+ return ui.showError(e.getMessage());
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/Launcher.java b/src/main/java/Launcher.java
new file mode 100644
index 0000000000..6f9230b9d6
--- /dev/null
+++ b/src/main/java/Launcher.java
@@ -0,0 +1,11 @@
+import javafx.application.Application;
+
+/**
+ * A launcher class to workaround classpath issues.
+ */
+public class Launcher {
+ public static void main(String[] args) {
+// Application.launch(Duke.class, args);
+ Application.launch(Main.class, args);
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/Main.java b/src/main/java/Main.java
new file mode 100644
index 0000000000..74c683e4e3
--- /dev/null
+++ b/src/main/java/Main.java
@@ -0,0 +1,29 @@
+//@Override
+import java.io.IOException;
+import javafx.application.Application;
+import javafx.fxml.FXMLLoader;
+import javafx.scene.Scene;
+import javafx.scene.layout.AnchorPane;
+import javafx.stage.Stage;
+
+/**
+ * A GUI for Duke using FXML.
+ */
+public class Main extends Application {
+
+ private Duke duke = new Duke("tasks.txt");
+
+ @Override
+ public void start(Stage stage) {
+ try {
+ FXMLLoader fxmlLoader = new FXMLLoader(Main.class.getResource("/view/MainWindow.fxml"));
+ AnchorPane ap = fxmlLoader.load();
+ Scene scene = new Scene(ap);
+ stage.setScene(scene);
+ fxmlLoader.getController().setDuke(duke);
+ stage.show();
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/MainWindow.java b/src/main/java/MainWindow.java
new file mode 100644
index 0000000000..4bb9f4d28a
--- /dev/null
+++ b/src/main/java/MainWindow.java
@@ -0,0 +1,52 @@
+import javafx.fxml.FXML;
+import javafx.scene.control.Button;
+import javafx.scene.control.Label;
+import javafx.scene.control.ScrollPane;
+import javafx.scene.control.TextField;
+import javafx.scene.image.Image;
+import javafx.scene.image.ImageView;
+import javafx.scene.layout.AnchorPane;
+import javafx.scene.layout.VBox;
+/**
+ * Controller for MainWindow. Provides the layout for the other controls.
+ */
+public class MainWindow extends AnchorPane {
+ @FXML
+ private ScrollPane scrollPane;
+ @FXML
+ private VBox dialogContainer;
+ @FXML
+ private TextField userInput;
+ @FXML
+ private Button sendButton;
+
+ private Duke duke;
+
+ private Image userImage = new Image(this.getClass().getResourceAsStream("/images/DaUser.jpg"));
+ private Image dukeImage = new Image(this.getClass().getResourceAsStream("/images/DaDuke.jpg"));
+
+ @FXML
+ public void initialize() {
+ scrollPane.vvalueProperty().bind(dialogContainer.heightProperty());
+ }
+
+ public void setDuke(Duke d) {
+ duke = d;
+ }
+
+ /**
+ * Creates two dialog boxes, one echoing user input and the other containing Duke's reply and then appends them to
+ * the dialog container. Clears the user input after processing.
+ */
+ @FXML
+ private void handleUserInput() {
+ String input = userInput.getText();
+ String response = duke.getResponse(input);
+ dialogContainer.getChildren().addAll(
+ DialogBox.getUserDialog(input, userImage),
+ DialogBox.getDukeDialog(response, dukeImage)
+ );
+ userInput.clear();
+ }
+
+}
\ No newline at end of file
diff --git a/src/main/java/command/AddCommand.java b/src/main/java/command/AddCommand.java
new file mode 100644
index 0000000000..4597b20280
--- /dev/null
+++ b/src/main/java/command/AddCommand.java
@@ -0,0 +1,52 @@
+package command;
+import process.*;
+
+import process.DukeException;
+import task.Deadline;
+import task.Event;
+import task.TaskList;
+import task.Todo;
+
+/**
+ * Represents a command that adds an item to tasks
+ */
+public class AddCommand extends Command {
+ private String description;
+ private String tasktype;
+ private String datetime;
+ /**
+ * Creates a new AddCommand object with the given type of task and description
+ * @param tasktype The task type
+ * @param description of the task
+ */
+ public AddCommand(String tasktype, String description) {
+ this.description = description;
+ this.tasktype = tasktype;
+ }
+ /**
+ * Creates a new AddCommand object with the given type of task, description and date time
+ * @param tasktype The task type
+ * @param description of the task
+ * @param datetime the date and time
+ */
+ public AddCommand(String tasktype, String description, String datetime) {
+ this.description = description;
+ this.tasktype = tasktype;
+ this.datetime = datetime;
+ }
+ /**
+ * Executes the AddCommand and saves changes to storage
+ * @param tasks the task list
+ * @param storage the storage file
+ * @param ui the user interface object
+ * @return ui response as a string
+ */
+ public String execute(TaskList tasks, Ui ui, Storage storage) throws DukeException {
+ if (tasktype.equals("todo")) tasks.add(new Todo(description, false));
+ else if (tasktype.equals("deadline")) tasks.add(new Deadline(description, datetime, false));
+ else if (tasktype.equals("event")) tasks.add(new Event(description, datetime, false));
+ else throw new DukeException("add error");
+ storage.save(tasks);
+ return ui.showTaskAdded(tasks.get(tasks.size()-1).toString(), tasks.size());
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/command/Command.java b/src/main/java/command/Command.java
new file mode 100644
index 0000000000..7b9c7243eb
--- /dev/null
+++ b/src/main/java/command/Command.java
@@ -0,0 +1,26 @@
+package command;
+import process.*;
+import task.TaskList;
+
+/**
+ * Represents a command registered in Duke
+ */
+public abstract class Command {
+ protected boolean is_exit = false;
+ /**
+ * Executes the command
+ * @param tasks The task list that you want to execute the command on
+ * @param ui The user interface object used to respond to the user
+ * @param storage The storage object used to save the changes made by the executed command
+ * @return the response as a string from the user interface
+ * @throws DukeException if command cannot be executed
+ */
+ public abstract String execute(TaskList tasks, Ui ui, Storage storage) throws DukeException;
+ /**
+ * Checks if the programme should exit
+ * @return True if the programme should exit
+ */
+ public boolean isExit() {
+ return is_exit;
+ };
+}
diff --git a/src/main/java/command/DeleteCommand.java b/src/main/java/command/DeleteCommand.java
new file mode 100644
index 0000000000..c184da032d
--- /dev/null
+++ b/src/main/java/command/DeleteCommand.java
@@ -0,0 +1,29 @@
+package command;
+import process.*;
+
+import process.DukeException;
+import task.TaskList;
+
+/**
+ * Represents a command that deletes an item from tasks
+ */
+public class DeleteCommand extends Command {
+ private int index;
+ /**
+ * Creates a new DeleteCommand object with the given index
+ * @param index of the task to be deleted
+ */
+ public DeleteCommand(int index) {
+ this.index = index;
+ }
+ /**
+ * Executes the DeleteCommand and saves changes to storage
+ */
+ public String execute(TaskList tasks, Ui ui, Storage storage) throws DukeException {
+ if (this.index >= tasks.size()) throw new DukeException("index error");
+ String output = ui.showTaskDelete(tasks.get(this.index).toString(),tasks.size()-1);
+ tasks.deleteTask(index);
+ storage.save(tasks);
+ return output;
+ }
+}
diff --git a/src/main/java/command/DoneCommand.java b/src/main/java/command/DoneCommand.java
new file mode 100644
index 0000000000..002ecdd7f4
--- /dev/null
+++ b/src/main/java/command/DoneCommand.java
@@ -0,0 +1,24 @@
+package command;
+import process.*;
+import process.DukeException;
+import task.TaskList;
+
+/**
+ * Represents a command that checks items as done in tasks
+ */
+public class DoneCommand extends Command {
+ private int index;
+ public DoneCommand(int index) {
+ this.index = index;
+ }
+ /**
+ * Executes the DoneCommand
+ */
+ public String execute(TaskList tasks, Ui ui, Storage storage) throws DukeException {
+ if (this.index >= tasks.size()) throw new DukeException("index error");
+ tasks.doneTask(this.index);
+ String output = ui.showTaskDone(tasks.get(this.index).toString());
+ storage.save(tasks);
+ return output;
+ }
+}
diff --git a/src/main/java/command/ExitCommand.java b/src/main/java/command/ExitCommand.java
new file mode 100644
index 0000000000..16aad530cc
--- /dev/null
+++ b/src/main/java/command/ExitCommand.java
@@ -0,0 +1,17 @@
+package command;
+import process.*;
+import task.TaskList;
+
+/**
+ * Represents a command that exits from the programme
+ */
+public class ExitCommand extends Command {
+ /**
+ * Executes the ExitCommand
+ */
+ public String execute(TaskList tasks, Ui ui, Storage storage) {
+ super.is_exit = true;
+ ui.close();
+ return "";
+ }
+}
diff --git a/src/main/java/command/FindCommand.java b/src/main/java/command/FindCommand.java
new file mode 100644
index 0000000000..1e323bf6cd
--- /dev/null
+++ b/src/main/java/command/FindCommand.java
@@ -0,0 +1,24 @@
+package command;
+import process.*;
+import process.DukeException;
+import task.TaskList;
+
+/**
+ * Represents a command that finds items from tasks
+ */
+public class FindCommand extends Command {
+ private String keyword;
+ /**
+ * Creates a new FindCommand object with the given keyword
+ * @param keyword used to find task
+ */
+ public FindCommand(String keyword) {
+ this.keyword = keyword;
+ }
+ /**
+ * Executes the FindCommand
+ */
+ public String execute(TaskList tasks, Ui ui, Storage storage) throws DukeException {
+ return ui.print_this(tasks.find(keyword));
+ }
+}
diff --git a/src/main/java/command/ListCommand.java b/src/main/java/command/ListCommand.java
new file mode 100644
index 0000000000..6e7e35583c
--- /dev/null
+++ b/src/main/java/command/ListCommand.java
@@ -0,0 +1,15 @@
+package command;
+import process.*;
+import task.TaskList;
+
+/**
+ * Represents a command that list items from tasks
+ */
+public class ListCommand extends Command{
+ /**
+ * Executes the ListCommand
+ */
+ public String execute(TaskList tasks, Ui ui, Storage storage) throws DukeException {
+ return ui.print_this(tasks.print_list());
+ }
+}
diff --git a/src/main/java/process/DatetimeFormatter.java b/src/main/java/process/DatetimeFormatter.java
new file mode 100644
index 0000000000..e5ab32a406
--- /dev/null
+++ b/src/main/java/process/DatetimeFormatter.java
@@ -0,0 +1,76 @@
+package process;
+
+import process.DukeException;
+
+/**
+ * Represents a datetime formatter
+ */
+public class DatetimeFormatter {
+ /**
+ * Validates that the datetime is valid
+ * @param str1 the datetime in the form of a String to be checked
+ * @return str1 back if it is a valid datetime
+ * @throws DukeException if str1 is not a valid datetime
+ */
+ public static String check(String str1) throws DukeException {
+ try {
+ str1 = str1.trim();
+ String[] datetime = str1.split(" ");
+ String[] ddmmyyyy = datetime[0].split("/");
+ String day = ddmmyyyy[0];
+ String month = ddmmyyyy[1];
+ String year = ddmmyyyy[2];
+ String time = datetime[1];
+ int[] daysinmonth = {31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
+ int month_i = Integer.parseInt(month);
+ int year_i = Integer.parseInt(year);
+ Integer day_i = Integer.parseInt(day);
+ String min = time.substring(2);
+ String hr = time.substring(0, 2);
+ Integer min_i = Integer.parseInt(min);
+ Integer hr_i = Integer.parseInt(hr);
+ if (hr_i > 23 || min_i > 59 || month_i > 12 || year_i < 0 || day_i > daysinmonth[month_i - 1])
+ throw new DukeException("datetime");
+ return str1;
+ } catch (ArrayIndexOutOfBoundsException e) {
+ throw new DukeException("datetime");
+ } catch (NumberFormatException e) {
+ throw new DukeException("datetime");
+ }
+ }
+ /**
+ * Formats the datetime object to match the exact format required
+ * @param str1 the datetime in the form of a String to be formatted
+ * @return the datetime in the exact format required
+ */
+ public static String view(String str1) {
+ try {
+ str1 = str1.trim();
+ String[] datetime = str1.split(" ");
+ String day = datetime[0];
+ String[] stndrd = {"st", "nd", "rd"};
+ Integer day_i = Integer.parseInt(day);
+ String day_x;
+ if (day_i >= 10 && day_i <= 20 || day_i % 10 > 3 || day_i % 10 == 0) {
+ day_x = day_i.toString() + "th";
+ } else {
+ day_x = day_i.toString() + stndrd[day_i % 10 - 1];
+ }
+ str1 = day_x + str1.substring(2);
+ int min_start = str1.indexOf(':');
+ String min = "" + str1.charAt(min_start + 1) + str1.charAt(min_start + 2);
+ String ampm = str1.substring(str1.length() - 2).toLowerCase();
+ if (Integer.parseInt(min) == 0) {
+ str1 = str1.substring(0, min_start) + ampm;
+ }
+ else {
+ str1 = str1.substring(0, str1.length() - 3) + ampm;
+ }
+ } catch (NumberFormatException e) {
+ e.printStackTrace();
+ } catch (ArrayIndexOutOfBoundsException e) {
+ e.printStackTrace();
+ }
+ return str1;
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/process/DukeException.java b/src/main/java/process/DukeException.java
new file mode 100644
index 0000000000..8bb0774283
--- /dev/null
+++ b/src/main/java/process/DukeException.java
@@ -0,0 +1,28 @@
+package process;
+
+/**
+ * Represents an exception thrown by Duke that may be specific to its application
+ */
+public class DukeException extends Exception {
+ private String error;
+ /**
+ * Creates a new DukeException of e
+ * @param e The error message
+ */
+ public DukeException(String e) {
+ this.error = e;
+ }
+ /**
+ * Represents the exception in a String
+ */
+ public String toString() {
+ return "DukeException[" + error + "]";
+ }
+ @Override
+ /**
+ * Retrieve the message from the error
+ */
+ public String getMessage() {
+ return this.error;
+ }
+}
diff --git a/src/main/java/process/Parser.java b/src/main/java/process/Parser.java
new file mode 100644
index 0000000000..23eaff76a4
--- /dev/null
+++ b/src/main/java/process/Parser.java
@@ -0,0 +1,73 @@
+package process;
+
+import command.*;
+import process.DukeException;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+/**
+ * Represents a parser to make sense of user input and translate it to commands for Duke
+ */
+public class Parser {
+ /**
+ * Interprets user input
+ * @param input to be interpreted
+ * @return Duke commands based on user input
+ * @throws DukeException if Duke cannot make sense of the input
+ */
+ public static Command parse(String input) throws DukeException { //input validation
+ ArrayList command_list = new ArrayList(Arrays.asList("bye", "list", "find", "delete", "done", "todo", "deadline", "event"));
+ String operation;
+ String date;
+ int index =-1;
+ String arg1;
+ int command_status = -1;
+ if (!input.isBlank()) command_status = command_list.indexOf(input.split(" ")[0]);
+ else {
+ throw new DukeException("unknown");
+ }
+ if (command_status > -1) { //keyword 1 is accepted
+ String[] operation_list = input.split(" ");
+ operation = operation_list[0];
+ if ((operation.equals("delete") || operation.equals("done"))) {
+ if (operation_list.length != 2) throw new DukeException("index error" + input.length());
+ try {
+ index = Integer.parseInt(operation_list[1]) -1;
+ if (index < 0) throw new DukeException("index error");
+ } catch (NumberFormatException e) {
+ throw new DukeException("index error");
+ }
+ if (operation.equals("delete")) return new DeleteCommand(index);
+ else return new DoneCommand(index); //done
+ } else if (operation.equals("find") || operation.equals("todo")) {
+ if (operation_list.length == 1) throw new DukeException("arg1 error "+ operation);
+ arg1 = input.substring(5);
+ if (arg1.isBlank()) throw new DukeException("arg1 error "+ operation);
+ arg1.trim();
+ if (operation.equals("find")) return new FindCommand(arg1);
+ else return new AddCommand("todo", arg1);
+ } else if (operation.equals("deadline")) {
+ int by_index = input.indexOf(" /by ");
+ if (by_index == -1) throw new DukeException("datetime");
+ arg1 = input.substring(8, by_index).trim();
+ if (arg1.isBlank()) throw new DukeException("arg1 error "+ operation);
+ date = input.substring(by_index + 5).trim();
+ if (date.isBlank()) throw new DukeException("datetime");
+ return new AddCommand("deadline", arg1, date);
+ } else if (operation.equals("event")) {
+ int at_index = input.indexOf(" /at ");
+ if (at_index == -1) throw new DukeException("datetime");
+ arg1 = input.substring(5, at_index).trim();
+ if (arg1.isBlank()) throw new DukeException("arg1 error "+ operation);
+ date = input.substring(at_index + 5).trim();
+ if (date.isBlank()) throw new DukeException("datetime");
+ return new AddCommand("event", arg1, date);
+ } else if (operation.equals("bye")) {
+ return new ExitCommand();
+ } else if (operation.equals("list")) {
+ return new ListCommand();
+ }
+ }
+ throw new DukeException("unknown");
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/process/Storage.java b/src/main/java/process/Storage.java
new file mode 100644
index 0000000000..09ae7d83d2
--- /dev/null
+++ b/src/main/java/process/Storage.java
@@ -0,0 +1,57 @@
+package process;
+
+import process.DukeException;
+import task.Task;
+import task.TaskList;
+
+import java.io.*;
+/**
+ * Represents a storage file inside data
+ */
+public class Storage {
+ private String file_content;
+ private String file_path;
+ /**
+ * Create a storage file found at filepath
+ * @param filePath to be used for storage
+ */
+ public Storage (String filePath){
+ file_path = filePath;
+ file_content = "";
+ try (BufferedReader br = new BufferedReader(new FileReader(filePath))) {
+ String line_X;
+ while ((line_X = br.readLine()) != null) {
+ file_content += line_X + "\n";
+ }
+ } catch (IOException e) {
+ System.out.println(filePath + " file not found, creating file...");
+ }
+ }
+ /**
+ * Laod the file from the filepath
+ * @return the file in the form of a string
+ * @throws DukeException if the file cannot be found or if there is no task in the list
+ */
+ public String load () throws DukeException {
+ if (file_content.isBlank()) {
+ throw new DukeException("file_not_found");
+ }
+ return file_content;
+ }
+ /**
+ * Save the tasklist given to the filepath
+ * @param tasks the tasklist to be saved
+ */
+ public void save(TaskList tasks) {
+ String sep = "}-}";
+ String output = "";
+ for (Task i : tasks) {
+ output += i.save_as(sep);
+ }
+ try (PrintWriter out = new PrintWriter(file_path)) {
+ out.println(output);
+ } catch (FileNotFoundException e) {
+ e.printStackTrace();
+ }
+ }
+}
diff --git a/src/main/java/process/Ui.java b/src/main/java/process/Ui.java
new file mode 100644
index 0000000000..28c2f96ebe
--- /dev/null
+++ b/src/main/java/process/Ui.java
@@ -0,0 +1,129 @@
+package process;
+
+import java.util.Scanner;
+
+import static java.lang.System.*;
+/**
+ * Represents the user interface
+ */
+public class Ui {
+ Scanner input = new Scanner(System.in);
+ private String line = " ____________________________________________________________";
+ private String logo = " ____ _ \n"
+ + "| _ \\ _ _| | _____ \n"
+ + "| | | | | | | |/ / _ \\\n"
+ + "| |_| | |_| | < __/\n"
+ + "|____/ \\__,_|_|\\_\\___|\n";
+ /**
+ * Shows the welcome message
+ */
+ public void showWelcome() {
+ System.out.println(line);
+ System.out.print(logo);
+ System.out.println(line);
+ System.out.println("Hello! I'm Duke");
+ System.out.println("What can I do for you?");
+ System.out.println(line);
+ }
+ /**
+ * Shows a line
+ */
+ public void showLine() {
+ System.out.println(line);
+ }
+ /**
+ * Shows and
+ * @return whatever string is passed into the function
+ * @param thingy to be printed
+ */
+ public String print_this(String thingy) {
+ System.out.println(thingy);
+ return thingy;
+ }
+ /**
+ * Reads and
+ * @return user input from keyboard
+ */
+ public String readCommand() {
+ return input.nextLine();
+ }
+ /**
+ * Shows the goodbye message
+ */
+ public void close() {
+ System.out.println("Bye. Hope to see you again soon!");
+ }
+ /**
+ * Shows and returns the error message
+ * @param error_msg the error message to be formatted and printed
+ * @return the formatted error message
+ */
+ public String showError(String error_msg) {
+ String out_ = "";
+ if(error_msg.equals("datetime")) {
+ out_ = "OOPS!!! Please enter the datetime in this format: dd/mm/yyyy HHMM";
+ } else if(error_msg.equals("unknown")) {
+ out_ = ("OOPS!!! I'm sorry, but I don't know what that means :-(");
+ } else if (error_msg.equals("arg1 error find")) {
+ out_ = "OOPS!!! Please enter a keyword or phrase for your search";
+ } else if (error_msg.equals("arg1 error todo")) {
+ out_ = "OOPS!!! The description of a todo cannot be blank";
+ } else if (error_msg.equals("arg1 error deadline")) {
+ out_ = "OOPS!!! The description of a deadline cannot be blank";
+ } else if (error_msg.equals("arg1 error event")) {
+ out_ = "OOPS!!! The description of an event cannot be blank";
+ } else if (error_msg.equals("index error")) {
+ out_ = "OOPS!!! Please enter a valid task index number";
+ } else if (error_msg.equals("empty list")) {
+ out_ = "You have no tasks in your list";
+ } else if (error_msg.equals("empty task")) {
+ out_ ="OOPS!!! The description of a task cannot be empty.";
+ }
+ else out_ = ("OOPS!!! " + error_msg);
+ System.out.println(out_);
+ return out_;
+ }
+ /**
+ * Shows and returns the loading error
+ * @return loading error
+ */
+ public String showLoadingError() {
+ String out_ = "OOPS!!! I'm sorry, but your saved file cannot be found";
+ System.out.println(out_);
+ return out_;
+ }
+ /**
+ * Shows and
+ * @return the given taska as done
+ * @param task to be displayed as done
+ */
+ public String showTaskDone(String task) {
+// String out_ = "Nice! I've marked this task as done:\n" + "[✓] " + task;
+ String out_ = "Nice! I've marked this task as done:\n" + task;
+ System.out.println(out_);
+ return out_;
+ }
+ /**
+ * Shows and
+ * @return the given task as deleted
+ * @param task to be shown as deleted
+ * @param size of the task list
+ */
+ public String showTaskDelete(String task, int size) {
+ String out_ = "Noted. I've removed this task:" + "\n" + "\t" + task + "\n" + "Now you have " + Integer.toString(size) + " tasks in the list.";
+ System.out.println(out_);
+ return out_;
+ }
+ /**
+ * Shows and
+ * @return the given task as added
+ * @param task to be shown as added
+ * @param size of the task list
+ */
+ public String showTaskAdded(String task, int size) {
+ String out_ = "Got it. I've added this task:" + "\n" + "\t" + task +"\n" + "Now you have " + Integer.toString(size) + " tasks in the list.";
+ System.out.println(out_);
+ return out_;
+ }
+ //☹
+}
diff --git a/src/main/java/task/Deadline.java b/src/main/java/task/Deadline.java
new file mode 100644
index 0000000000..1f6f097e08
--- /dev/null
+++ b/src/main/java/task/Deadline.java
@@ -0,0 +1,47 @@
+package task;
+import process.*;
+import java.text.DateFormat;
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+import java.util.Locale;
+
+/**
+ * Represents a Deadline-typed-task
+ */
+public class Deadline extends Task {
+ protected Date by;
+ DateFormat fmt = new SimpleDateFormat("dd MMMM yyyy, h:mm a", Locale.US);
+ /**
+ * Creates a Deadline object with the given description, datetime and whether it is done
+ * @param description of the task
+ * @param by the due datetime of the deadline
+ * @param b if the task is checked
+ * @throws DukeException if the deadline is not valid
+ */
+ public Deadline(String description, String by,boolean b) throws DukeException {
+ super(description, b);
+ if (by.isBlank()) {
+ throw new DukeException("blank by");
+ } try {
+ DatetimeFormatter.check(by);
+ SimpleDateFormat sdf = new SimpleDateFormat("dd/M/yyyy hhmm");
+ by = by.trim();
+ Date date = sdf.parse(by);
+ this.by = date;
+ super.tt = "D";
+ super.extra = by;
+ } catch (ParseException e) {
+ throw new DukeException("datetime");
+ }
+ }
+
+ /**
+ * Represents the object of this class as a string
+ * @return that string
+ */
+ @Override
+ public String toString() {
+ return "[D]" + super.toString() + " (by: " + DatetimeFormatter.view(this.fmt.format(by)) + ")";
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/task/Event.java b/src/main/java/task/Event.java
new file mode 100644
index 0000000000..2161428454
--- /dev/null
+++ b/src/main/java/task/Event.java
@@ -0,0 +1,46 @@
+package task;
+import process.*;
+import java.text.DateFormat;
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+import java.util.Locale;
+
+/**
+ * Represents an Event-typed-task
+ */
+public class Event extends Task{
+ protected Date at;
+ DateFormat fmt = new SimpleDateFormat("dd MMMM yyyy, h:mm a", Locale.US);
+ /**
+ * Creates an Event object with the given description, datetime and whether it is done
+ * @param description of the Event
+ * @param b if the task is checked or not
+ * @param at the date and time of the event
+ * @throws DukeException if there is an error in creating the event
+ */
+ public Event(String description, String at, boolean b) throws DukeException {
+ super(description, b);
+ if (at.isBlank()) {
+ throw new DukeException("blank at");
+ }
+ try {
+ DatetimeFormatter.check(at);
+ SimpleDateFormat sdf = new SimpleDateFormat("dd/M/yyyy hhmm");
+ Date date = sdf.parse(at);
+ this.at = date;
+ super.tt = "D";
+ super.extra = at;
+ } catch (ParseException e){
+ throw new DukeException("datetime");
+ }
+ }
+ /**
+ * Represents the object of this class as a string
+ * @return that string
+ */
+ @Override
+ public String toString() {
+ return "[E]" + super.toString() + " (at: " + DatetimeFormatter.view(this.fmt.format(at)) + ")";
+ }
+}
diff --git a/src/main/java/task/Task.java b/src/main/java/task/Task.java
new file mode 100644
index 0000000000..4f5135798f
--- /dev/null
+++ b/src/main/java/task/Task.java
@@ -0,0 +1,48 @@
+package task;
+import process.*;
+/**
+ * Represents a Task recorded by Duke task manager
+ */
+public class Task {
+ protected String description;
+ protected boolean isDone;
+ protected String tt;
+ protected String extra;
+ /**
+ * Creates a Task object with the given description and whether it is done
+ * @param description of the task
+ * @param b if a task is checked
+ * @throws DukeException if an error has occured in constructing the task
+ */
+ public Task(String description, boolean b) throws DukeException {
+ this.tt = "";
+ this.extra = "";
+ this.description = description;
+ this.isDone = b;
+ if (description.isBlank()) {
+ throw new DukeException("empty task");
+ }
+ }
+ /**
+ * Pack the Task in the form of a string with the given separator for writing to a file
+ * @param sep the separator used
+ * @return the save form of a task
+ */
+ public String save_as(String sep) {
+ return tt + sep + isDone + sep + description + sep + extra + "\n";
+ }
+ /**
+ * Mark a Task as b
+ * @param b checked or not checked
+ */
+ public void done(boolean b) {
+ this.isDone = b;
+ }
+ /**
+ * Represents the object of this class as a string
+ * @return that string
+ */
+ public String toString() {
+ return (isDone ? "[Done] " : "[X] ") + this.description;
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/task/TaskList.java b/src/main/java/task/TaskList.java
new file mode 100644
index 0000000000..b69db837de
--- /dev/null
+++ b/src/main/java/task/TaskList.java
@@ -0,0 +1,101 @@
+package task;
+import process.*;
+import java.util.ArrayList;
+import java.util.Iterator;
+
+/**
+ * Represents the tasklist that duke is managing
+ */
+public class TaskList extends ArrayList{
+ String sep = "}-}";
+ /**
+ * Creates an empty task list
+ */
+ public TaskList() {
+ }
+ /**
+ * Creates a task list from file
+ * @param file the file in the form of a string to be converted to the task list
+ */
+ public TaskList(String file) {
+ if (file.isBlank()) {
+ return;
+ }
+ String[] itemlist = file.split("\n");
+ int line = 0;
+ for (String item: itemlist) {
+ line ++;
+ String[] attributes = item.split(sep);
+ try {
+ if (attributes[0].equals("T")) {
+ this.add(new Todo(attributes[2], attributes[1].equals("true")));
+ } else if (attributes[0].equals("D")) {
+ this.add(new Deadline(attributes[2], DatetimeFormatter.check(attributes[3]), attributes[1].equals("true")));
+ } else if (attributes[0].equals("E")) {
+ this.add(new Event(attributes[2], DatetimeFormatter.check(attributes[3]), attributes[1].equals("true")));
+ } else {
+ System.out.println("☹ OOPS!!! Line " + Integer.toString(line) + " in duke.txt is corrupted" + ", skipping...");
+ }
+ } catch (DukeException e) {
+ System.out.println("☹ OOPS!!! Line " + Integer.toString(line) + " in duke.txt is corrupted[" + e + "], skipping...");
+ } catch (ArrayIndexOutOfBoundsException e) {
+ System.out.println("☹ OOPS!!! Line " + Integer.toString(line) + " in duke.txt is corrupted[" + e + "], skipping...");
+ }
+ }
+ }
+ /**
+ * Formats the task list in a readable format
+ * @return a task list in the form of a string
+ * @throws DukeException if the list is empty
+ */
+ public String print_list() throws DukeException {
+ if (this.size()== 0) throw new DukeException("empty list");
+ String output ="Here are the tasks in your list:";
+ int x = 0;
+ Iterator itr=this.iterator();
+ while(itr.hasNext()){
+ output += "\n" + (Integer.toString(x + 1) + ".");
+ output += (itr.next().toString());
+ x ++;
+ }
+ return output;
+ }
+ /**
+ * Deletes the task at given index
+ * @param index of the task to be deleted
+ */
+ public void deleteTask(int index) {
+ this.remove(index);
+ }
+
+ /**
+ * Marks the task at given index as done
+ * @param index of the task to be marked as done
+ */
+ public void doneTask(int index) {
+ this.get(index).done(true);
+ }
+
+ /**
+ * Searches for a task with the given keyword
+ * @param keyword to be found
+ * @return The list of task with the keyword
+ */
+ public String find(String keyword) {
+ int x = 0;
+ String output = "Here are the matching tasks in your list:";
+ boolean have_result= false;
+ for (Task task: this) {
+ if (task.toString().indexOf(keyword) != -1) {
+ output += "\n" + (Integer.toString(x + 1) + ".");
+ output += (task.toString());
+ x ++;
+ have_result = true;
+ }
+ }
+ if (have_result == false) {
+ return ("Sorry, no results found");
+ }
+ return output;
+ }
+}
diff --git a/src/main/java/task/Todo.java b/src/main/java/task/Todo.java
new file mode 100644
index 0000000000..ac013c52c1
--- /dev/null
+++ b/src/main/java/task/Todo.java
@@ -0,0 +1,25 @@
+package task;
+import process.*;
+/**
+ * Represents a Todo-typed-task
+ */
+public class Todo extends Task{
+ /**
+ * Creates a Todo object with the given description and whether it is done
+ * @param description of the todo task
+ * @param b if the task is checked or not
+ * @throws DukeException if the Todo task is invalid
+ */
+ public Todo(String description, boolean b) throws DukeException {
+ super(description, b);
+ super.tt = "T";
+ }
+ /**
+ * Represents the object of this class as a string
+ * @return that string
+ */
+ @Override
+ public String toString() {
+ return "[T]" + super.toString();
+ }
+}
diff --git a/src/main/resources/images/DaDuke.jpg b/src/main/resources/images/DaDuke.jpg
new file mode 100644
index 0000000000..f68cbb617e
Binary files /dev/null and b/src/main/resources/images/DaDuke.jpg differ
diff --git a/src/main/resources/images/DaUser.jpg b/src/main/resources/images/DaUser.jpg
new file mode 100644
index 0000000000..6b0a4c0f08
Binary files /dev/null and b/src/main/resources/images/DaUser.jpg differ
diff --git a/src/main/resources/view/DialogBox.fxml b/src/main/resources/view/DialogBox.fxml
new file mode 100644
index 0000000000..9a7d91eecc
--- /dev/null
+++ b/src/main/resources/view/DialogBox.fxml
@@ -0,0 +1,25 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/main/resources/view/MainWindow.fxml b/src/main/resources/view/MainWindow.fxml
new file mode 100644
index 0000000000..1f1f505c03
--- /dev/null
+++ b/src/main/resources/view/MainWindow.fxml
@@ -0,0 +1,19 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/test/java/DeadlineTest.java b/src/test/java/DeadlineTest.java
new file mode 100644
index 0000000000..c6127a02c8
--- /dev/null
+++ b/src/test/java/DeadlineTest.java
@@ -0,0 +1,36 @@
+import org.junit.jupiter.api.Test;
+import process.DukeException;
+import task.Deadline;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+public class DeadlineTest {
+ @Test
+ public void dummyTest(){
+ assertEquals(2, 2);
+ }
+ @Test
+ public void Test() {
+ try {
+ new Deadline("asd 234567 xcvbm !", "12/12/12 1234", false);
+ new Deadline("asfvfs 134", "12/12/12 2300", false);
+ } catch (DukeException e) {
+ assert false;
+ }
+ try {
+ new Deadline("asfvfs 134", "12/12/12 9900", false);
+ assert false;
+ } catch (DukeException e) { }
+
+ try {
+ new Deadline("asfvfs 134", "12/13/0 1200", false);
+ assert false;
+ } catch (DukeException e) { }
+
+ try {
+ new Deadline("asfvfs 134", "30/2/12 2300", false);
+ assert false;
+ } catch (DukeException e) { }
+ assert true;
+ }
+}
\ No newline at end of file
diff --git a/src/test/java/DeleteTest.java b/src/test/java/DeleteTest.java
new file mode 100644
index 0000000000..154f8ab611
--- /dev/null
+++ b/src/test/java/DeleteTest.java
@@ -0,0 +1,28 @@
+import org.junit.jupiter.api.Test;
+import process.DukeException;
+import task.TaskList;
+import task.Todo;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+public class DeleteTest {
+ @Test
+ public void dummyTest(){
+ assertEquals(2, 2);
+ }
+ @Test
+ public void Test() {
+ try {
+ TaskList tasks = new TaskList();
+ tasks.add(new Todo("x",false));
+ tasks.deleteTask(0);
+ if (tasks.size() == 0)
+ assert true;
+ else assert false;
+ } catch (DukeException e) {
+ assert false;
+ } catch (Exception e) {
+ assert false;
+ }
+ }
+}
diff --git a/src/test/java/DoneTest.java b/src/test/java/DoneTest.java
new file mode 100644
index 0000000000..89ec127083
--- /dev/null
+++ b/src/test/java/DoneTest.java
@@ -0,0 +1,28 @@
+import org.junit.jupiter.api.Test;
+import process.DukeException;
+import task.TaskList;
+import task.Todo;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+public class DoneTest {
+ @Test
+ public void dummyTest(){
+ assertEquals(2, 2);
+ }
+ @Test
+ public void Test() {
+ try {
+ TaskList tasks = new TaskList();
+ tasks.add(new Todo("asd",false));
+ tasks.doneTask(0);
+ if (tasks.get(0).toString().indexOf("Done") > -1)
+ assert true;
+ else assert false;
+ } catch (DukeException e) {
+ assert false;
+ } catch (Exception e) {
+ assert false;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/test/java/DukeTest.java b/src/test/java/DukeTest.java
new file mode 100644
index 0000000000..2fec0b4905
--- /dev/null
+++ b/src/test/java/DukeTest.java
@@ -0,0 +1,42 @@
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+public class DukeTest {
+ @Test
+ public void dummyTest(){
+ assertEquals(2, 2);
+ }
+ @Test
+ public void DeadlineTest() {
+ new DeadlineTest().Test();
+ }
+ @Test
+ public void DeleteTest() {
+ new DeleteTest().Test();
+ }
+ @Test
+ public void DoneTest() {
+ new DoneTest().Test();
+ }
+ @Test
+ public void EventTest() {
+ new EventTest().Test();
+ }
+ @Test
+ public void FindTest() {
+ new FindTest().Test();
+ }
+ @Test
+ public void ListTest() {
+ new ListTest().Test();
+ }
+ @Test
+ public void ParserTest() {
+ new ParserTest().Test();
+ }
+ @Test
+ public void Todo() {
+ new TodoTest().Test();
+ }
+}
\ No newline at end of file
diff --git a/src/test/java/EventTest.java b/src/test/java/EventTest.java
new file mode 100644
index 0000000000..8c1f7f9dd6
--- /dev/null
+++ b/src/test/java/EventTest.java
@@ -0,0 +1,35 @@
+import org.junit.jupiter.api.Test;
+import process.DukeException;
+import task.Event;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+public class EventTest {
+ @Test
+ public void dummyTest(){
+ assertEquals(2, 2);
+ }
+ @Test
+ public void Test() {
+ try {
+ new Event("asd 234567 xcvbm !", "12/12/12 1234", false);
+ new Event("asfvfs 134", "12/12/12 2300", false);
+ } catch (DukeException e) {
+ assert false;
+ }
+ try {
+ new Event("asfvfs 134", "12/12/12 9900", false);
+ assert false;
+ } catch (DukeException e) { }
+
+ try {
+ new Event("asfvfs 134", "12/13/0 1200", false);
+ assert false;
+ } catch (DukeException e) { }
+
+ try {
+ new Event("asfvfs 134", "30/2/12 2300", false);
+ assert false;
+ } catch (DukeException e) { }
+ }
+}
\ No newline at end of file
diff --git a/src/test/java/FindTest.java b/src/test/java/FindTest.java
new file mode 100644
index 0000000000..76ad68dc3b
--- /dev/null
+++ b/src/test/java/FindTest.java
@@ -0,0 +1,28 @@
+import org.junit.jupiter.api.Test;
+import process.DukeException;
+import task.TaskList;
+import task.Todo;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+public class FindTest {
+ @Test
+ public void dummyTest(){
+ assertEquals(2, 2);
+ }
+ @Test
+ public void Test() {
+ try {
+ TaskList tasks = new TaskList();
+ tasks.add(new Todo("abcdef", false));
+ tasks.add(new Todo("zxcv", false));
+ if (tasks.find("abcdef").indexOf("abcdef") > -1 && tasks.find("abcdef").indexOf("zxcv") == -1 && tasks.find("abczxc").indexOf("Sorry") > -1)
+ assert true;
+ else assert false;
+ } catch (DukeException e) {
+ assert false;
+ } catch (Exception e) {
+ assert false;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/test/java/ListTest.java b/src/test/java/ListTest.java
new file mode 100644
index 0000000000..c8d2177d1e
--- /dev/null
+++ b/src/test/java/ListTest.java
@@ -0,0 +1,37 @@
+import org.junit.jupiter.api.Test;
+import process.DukeException;
+import task.TaskList;
+import task.Todo;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+public class ListTest {
+ @Test
+ public void dummyTest(){
+ assertEquals(2, 2);
+ }
+ @Test
+ public void Test() {
+ try {
+ TaskList tasks = new TaskList();
+ tasks.add(new Todo("abcdef", false));
+ tasks.add(new Todo("zxcv", false));
+ if (tasks.print_list().indexOf("Here are the tasks in your list:") > -1)
+ assert true;
+ else assert false;
+ } catch (DukeException e) {
+ assert false;
+ } catch (Exception e) {
+ assert false;
+ }
+ try {
+ TaskList tasksEmpty = new TaskList();
+ tasksEmpty.print_list();
+ assert false;
+ } catch (DukeException e) {
+ assert true;
+ } catch (Exception e) {
+ assert false;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/test/java/ParserTest.java b/src/test/java/ParserTest.java
new file mode 100644
index 0000000000..cbb59381a4
--- /dev/null
+++ b/src/test/java/ParserTest.java
@@ -0,0 +1,34 @@
+import command.Command;
+import org.junit.jupiter.api.Test;
+import process.DukeException;
+import process.Parser;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+public class ParserTest {
+ @Test
+ public void dummyTest(){
+ assertEquals(2, 2);
+ }
+ @Test
+ public void Test() {
+ try {
+ Command a = Parser.parse("todo x");
+ Command b = Parser.parse("deadline x /by 12/12/12 1234 ");
+ Command c = Parser.parse("event x /at 12/12/12 1234");
+ Command d = Parser.parse("find x");
+ Command e = Parser.parse("delete 1");
+ Command f = Parser.parse("list");
+ Command g = Parser.parse("bye");
+ Command h = Parser.parse("done 1");
+ } catch (DukeException e) {
+ assert false;
+ }
+ try {
+ Command j = Parser.parse("x");
+ assert false;
+ } catch (DukeException e) {
+ assert true;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/test/java/TodoTest.java b/src/test/java/TodoTest.java
new file mode 100644
index 0000000000..bb796d09eb
--- /dev/null
+++ b/src/test/java/TodoTest.java
@@ -0,0 +1,27 @@
+import org.junit.jupiter.api.Test;
+import process.DukeException;
+import task.Todo;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+public class TodoTest {
+ @Test
+ public void dummyTest(){
+ assertEquals(2, 2);
+ }
+ @Test
+ public void Test() {
+ try {
+ new Todo("a a a /by 12/13/12 9934)", false).toString();
+ new Todo("a a a /at 12/13/12 9934)", false).toString();
+ } catch (DukeException e) {
+ assert false;
+ }
+ try {
+ new Todo("", false).toString();
+ assert false;
+ } catch (DukeException e) {
+ assert true;
+ }
+ }
+}
\ No newline at end of file
diff --git a/text-ui-test/ACTUAL.TXT b/text-ui-test/ACTUAL.TXT
new file mode 100644
index 0000000000..590416cb6a
--- /dev/null
+++ b/text-ui-test/ACTUAL.TXT
@@ -0,0 +1,42 @@
+OOPS!!! I'm sorry, but your saved file cannot be found
+ ____________________________________________________________
+ ____ _
+| _ \ _ _| | _____
+| | | | | | | |/ / _ \
+| |_| | |_| | < __/
+|____/ \__,_|_|\_\___|
+ ____________________________________________________________
+Hello! I'm Duke
+What can I do for you?
+ ____________________________________________________________
+ ____________________________________________________________
+OOPS!!! Please enter a valid task index number
+ ____________________________________________________________
+ ____________________________________________________________
+Got it. I've added this task:
+ [T][X] x
+Now you have 1 tasks in the list.
+ ____________________________________________________________
+ ____________________________________________________________
+Here are the tasks in your list:
+1.[T][X] x
+ ____________________________________________________________
+ ____________________________________________________________
+Nice! I've marked this task as done:
+[Done] x
+ ____________________________________________________________
+ ____________________________________________________________
+Here are the tasks in your list:
+1.[T][Done] x
+ ____________________________________________________________
+ ____________________________________________________________
+Noted. I've removed this task:
+ [T][Done] x
+Now you have 0 tasks in the list.
+ ____________________________________________________________
+ ____________________________________________________________
+You have no tasks in your list
+ ____________________________________________________________
+ ____________________________________________________________
+Bye. Hope to see you again soon!
+ ____________________________________________________________
diff --git a/text-ui-test/EXPECTED.txt b/text-ui-test/EXPECTED.txt
new file mode 100644
index 0000000000..bae9e98c80
--- /dev/null
+++ b/text-ui-test/EXPECTED.txt
@@ -0,0 +1,42 @@
+OOPS!!! I'm sorry, but your saved file cannot be found
+ ____________________________________________________________
+ ____ _
+| _ \ _ _| | _____
+| | | | | | | |/ / _ \
+| |_| | |_| | < __/
+|____/ \__,_|_|\_\___|
+ ____________________________________________________________
+Hello! I'm Duke
+What can I do for you?
+ ____________________________________________________________
+ ____________________________________________________________
+OOPS!!! Please enter a valid task index number
+ ____________________________________________________________
+ ____________________________________________________________
+Got it. I've added this task:
+ [T][X] x
+Now you have 1 tasks in the list.
+ ____________________________________________________________
+ ____________________________________________________________
+Here are the tasks in your list:
+1.[T][X] x
+ ____________________________________________________________
+ ____________________________________________________________
+Nice! I've marked this task as done:
+[Done] x
+ ____________________________________________________________
+ ____________________________________________________________
+Here are the tasks in your list:
+1.[T][Done] x
+ ____________________________________________________________
+ ____________________________________________________________
+Noted. I've removed this task:
+ [T][Done] x
+Now you have 0 tasks in the list.
+ ____________________________________________________________
+ ____________________________________________________________
+You have no tasks in your list
+ ____________________________________________________________
+ ____________________________________________________________
+Bye. Hope to see you again soon!
+ ____________________________________________________________
diff --git a/text-ui-test/input.txt b/text-ui-test/input.txt
new file mode 100644
index 0000000000..b2ebd33de7
--- /dev/null
+++ b/text-ui-test/input.txt
@@ -0,0 +1,8 @@
+delete 1
+todo x
+list
+done 1
+list
+delete 1
+list
+bye
\ No newline at end of file
diff --git a/text-ui-test/runtest - Copy.bat b/text-ui-test/runtest - Copy.bat
new file mode 100644
index 0000000000..9be4261d78
--- /dev/null
+++ b/text-ui-test/runtest - Copy.bat
@@ -0,0 +1,23 @@
+@ECHO OFF
+
+REM create bin directory if it doesn't exist
+if not exist ..\bin mkdir ..\bin
+
+REM delete output from previous run
+if exist ACTUAL.TXT del ACTUAL.TXT
+
+REM compile the code into the bin folder
+SET MY_FOLDER = C:\Users\Zheng Wen\Documents\duke\src\main\java
+SET DUKE = C:\Users\Zheng Wen\Documents\duke\src\main\java\Duke.java
+javac -cp %MY_FOLDER%/*.java -Xlint:none -d ..\bin -sourcepath ..\src\main\java\Duke.java
+IF ERRORLEVEL 1 (
+ echo ********** BUILD FAILURE **********
+ exit /b 1
+)
+REM no error here, errorlevel == 0
+
+REM run the program, feed commands from input.txt file and redirect the output to the ACTUAL.TXT
+java -classpath ..\bin Duke < input.txt > ACTUAL.TXT
+
+REM compare the output to the expected output
+FC ACTUAL.TXT EXPECTED.TXT
\ No newline at end of file
diff --git a/text-ui-test/runtest.bat b/text-ui-test/runtest.bat
new file mode 100644
index 0000000000..735f3ecb16
--- /dev/null
+++ b/text-ui-test/runtest.bat
@@ -0,0 +1,23 @@
+@ECHO OFF
+
+REM create bin directory if it doesn't exist
+if not exist ..\bin mkdir ..\bin
+if exist ACTUAL.TXT del ACTUAL.TXT
+
+REM delete output from previous run
+
+REM compile the code into the bin folder
+javac -cp ..\src\main\java -Xlint:none -d ..\bin ..\src\main\java\Duke.java
+IF ERRORLEVEL 1 (
+ echo ********** BUILD FAILURE **********
+ exit /b 1
+)
+REM no error here, errorlevel == 0
+
+REM run the program, feed commands from input.txt file and redirect the output to the ACTUAL.TXT
+java -classpath ..\bin Duke < input.txt > ACTUAL.TXT
+
+REM compare the output to the expected output
+FC ACTUAL.TXT EXPECTED.TXT
+
+pause
\ No newline at end of file
diff --git a/text-ui-test/runtest.sh b/text-ui-test/runtest.sh
new file mode 100644
index 0000000000..c26ecef87c
--- /dev/null
+++ b/text-ui-test/runtest.sh
@@ -0,0 +1,34 @@
+#!/usr/bin/env bash
+
+# create bin directory if it doesn't exist
+if [ ! -d "../bin" ]
+then
+ mkdir ../bin
+fi
+
+# delete output from previous run
+if [ -e "./ACTUAL.TXT" ]
+then
+ rm ACTUAL.TXT
+fi
+
+# compile the code into the bin folder, terminates if error occurred
+if ! javac -cp ../src -Xlint:none -d ../bin ../src/main/java/Duke.java
+then
+ echo "********** BUILD FAILURE **********"
+ exit 1
+fi
+
+# run the program, feed commands from input.txt file and redirect the output to the ACTUAL.TXT
+java -classpath ../bin Duke < input.txt > ACTUAL.TXT
+
+# compare the output to the expected output
+diff ACTUAL.TXT EXPECTED.TXT
+if [ $? -eq 0 ]
+then
+ echo "Test result: PASSED"
+ exit 0
+else
+ echo "Test result: FAILED"
+ exit 1
+fi
\ No newline at end of file