diff --git a/build.gradle b/build.gradle
new file mode 100644
index 0000000000..c35c614fad
--- /dev/null
+++ b/build.gradle
@@ -0,0 +1,66 @@
+plugins {
+ id 'java'
+ id 'application'
+ id 'checkstyle'
+ id "com.github.johnrengelman.shadow" version "6.0.0"
+}
+
+group 'org.example'
+version '1.0-SNAPSHOT'
+
+repositories {
+ mavenCentral()
+}
+
+sourceSets {
+ main {
+ java {
+ srcDirs = ['src']
+ }
+ }
+
+}
+
+dependencies {
+ compile 'junit:junit:4.12'
+}
+
+run {
+ enableAssertions = true
+}
+
+test {
+ useJUnitPlatform()
+}
+
+dependencies {
+ implementation 'org.jetbrains:annotations:20.1.0'
+ testImplementation group: 'org.junit.jupiter', name: 'junit-jupiter-api', version: '5.4.0'
+ testRuntimeOnly group: 'org.junit.jupiter', name: 'junit-jupiter-engine', version: '5.4.0'
+ implementation 'org.junit.jupiter:junit-jupiter-api:5.5.1'
+ String javaFxVersion = '11'
+
+ implementation group: 'org.openjfx', name: 'javafx-base', version: javaFxVersion, classifier: 'win'
+ implementation group: 'org.openjfx', name: 'javafx-base', version: javaFxVersion, classifier: 'mac'
+ implementation group: 'org.openjfx', name: 'javafx-base', version: javaFxVersion, classifier: 'linux'
+ implementation group: 'org.openjfx', name: 'javafx-controls', version: javaFxVersion, classifier: 'win'
+ implementation group: 'org.openjfx', name: 'javafx-controls', version: javaFxVersion, classifier: 'mac'
+ implementation group: 'org.openjfx', name: 'javafx-controls', version: javaFxVersion, classifier: 'linux'
+ implementation group: 'org.openjfx', name: 'javafx-fxml', version: javaFxVersion, classifier: 'win'
+ implementation group: 'org.openjfx', name: 'javafx-fxml', version: javaFxVersion, classifier: 'mac'
+ implementation group: 'org.openjfx', name: 'javafx-fxml', version: javaFxVersion, classifier: 'linux'
+ implementation group: 'org.openjfx', name: 'javafx-graphics', version: javaFxVersion, classifier: 'win'
+ implementation group: 'org.openjfx', name: 'javafx-graphics', version: javaFxVersion, classifier: 'mac'
+ implementation group: 'org.openjfx', name: 'javafx-graphics', version: javaFxVersion, classifier: 'linux'
+ compile group: 'org.testng', name: 'testng', version: '6.11'
+}
+
+checkstyle {
+ toolVersion = '8.23'
+}
+
+run{
+ standardInput = System.in
+}
+
+mainClassName ="main.duke.gui.Launcher"
\ No newline at end of file
diff --git a/data/duke.txt b/data/duke.txt
new file mode 100644
index 0000000000..55d4dea944
--- /dev/null
+++ b/data/duke.txt
@@ -0,0 +1,4 @@
+D~1~d1~1111-11-11 1111
+T~1~t1
+E~0~e1~1111-11-11 1212
+D~0~d2~1111-11-11 1111
diff --git a/docs/README.md b/docs/README.md
index 8077118ebe..c2e49ba001 100644
--- a/docs/README.md
+++ b/docs/README.md
@@ -1,29 +1,136 @@
# User Guide
+Duke bot is a chat bot designed to be used via a command line interface(CLI) with the benefits of using a graphical user interface(GUI).
-## Features
+## Features
+>list : prints the list of stored tasks
+bye : terminates the program
+todo [your task input] : adds a todo type task
+event [your task input] /at [YYYY-MM-DD hhmm]: adds an event type task
+deadline [your task input] /by [YYYY-MM-DD hhmm]: adds a deadline type task
+delete[number] : deletes the selected task
+mark[number] : mark the selected task as done
+unmark[number] : mark the selected task as not done
+find [search string] : finds tasks that match the string description
+undo : undo the previous command
-### Feature-ABC
+### `list` - prints the list of stored tasks
-Description of the feature.
-### Feature-XYZ
+Example of usage:
-Description of the feature.
+`list`
-## Usage
+Expected outcome:
+
+>1.[T] [X] sample_todo
+2.[D] [ ] sample_deadline 2020-11-11 1200
+3.[T] [ ] sample_todo_2
+
+### `bye` - terminates the program
+
+
+Example of usage:
+
+`bye`
+
+Expected outcome:
+
+`Bye. Hope to see you again soon!`
+
+### `todo` - adds a todo type task
+
+Example of usage:
+
+`todo sample_todo`
+
+Expected outcome:
+
+>Got it. I've added this task:
+[T][X] sample_todo
+You now have 1 tasks in your list
+
+### `event` - adds an event type task
+
+Example of usage:
+
+`event sample_event /at 2020-01-01 1111`
+
+Expected outcome:
+
+>Got it. I've added this task:
+[E][X] sample_event 2020-11-11 1200
+You now have 1 tasks in your list
+
+### `deadline` - adds a deadline type task
+
+Example of usage:
+
+`deadline wakeup /by 2020-01-01 1111`
+
+Expected outcome:
-### `Keyword` - Describe action
+>Got it. I've added this task:
+> [D][X] sample_deadline 2020-11-11 1200
+>You now have 1 tasks in your list
-Describe the action and its outcome.
+### `delete` - deletes the selected task
-Example of usage:
+Example of usage:
-`keyword (optional arguments)`
+`delete 1`
Expected outcome:
-Description of the outcome.
+>Noted. I've removed this task:
+[T][X] sample_todo
+You now have 0 tasks in your list
+
+### `mark` - mark the selected task as done
+
+Describe action and its outcome.
+
+Example of usage:
+
+`mark 1`
+
+Expected outcome:
+
+>Nice! I've marked this task as done:
+[T][X] sample
+
+### `unmark` - mark the selected task as not done
+
+Describe action and its outcome.
+
+Example of usage:
+
+`unmark 1`
+
+Expected outcome:
+
+>Nice! I've marked this task as not done:
+[T][ ] sample
+
+### `find` - finds tasks that match the string description
+
+Example of usage:
+
+`find sample`
+
+Expected outcome:
+
+>Here are the matching tasks in your list:
+> 1. [T][X] sample_todo
+> 2. [T][X] sample_todo_2
+
+### `undo` - undo the previous command
+
+Example of usage:
+
+`undo`
+
+Expected outcome:
-```
-expected output
-```
+>Undo task successfully!
+> 1. [T][X] sample_todo
+> 2. [T][X] sample_todo_2
diff --git a/docs/Ui.png b/docs/Ui.png
new file mode 100644
index 0000000000..1c1c145be7
Binary files /dev/null and b/docs/Ui.png differ
diff --git a/docs/_config.yml b/docs/_config.yml
new file mode 100644
index 0000000000..9da9a0291e
--- /dev/null
+++ b/docs/_config.yml
@@ -0,0 +1 @@
+theme: jekyll-theme-dinky
\ No newline at end of file
diff --git a/duke.txt b/duke.txt
new file mode 100644
index 0000000000..055934e2c0
--- /dev/null
+++ b/duke.txt
@@ -0,0 +1 @@
+T|0|1
diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar
new file mode 100644
index 0000000000..62d4c05355
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..7dcdbdde07
--- /dev/null
+++ b/gradle/wrapper/gradle-wrapper.properties
@@ -0,0 +1,6 @@
+#Tue Feb 23 16:47:37 SGT 2021
+distributionUrl=https\://services.gradle.org/distributions/gradle-6.5-all.zip
+distributionBase=GRADLE_USER_HOME
+distributionPath=wrapper/dists
+zipStorePath=wrapper/dists
+zipStoreBase=GRADLE_USER_HOME
diff --git a/gradlew b/gradlew
new file mode 100644
index 0000000000..fbd7c51583
--- /dev/null
+++ b/gradlew
@@ -0,0 +1,185 @@
+#!/usr/bin/env sh
+
+#
+# Copyright 2015 the original author or authors.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# https://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+##############################################################################
+##
+## 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" "-Xms64m"'
+
+# 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 or MSYS, switch paths to Windows format before running java
+if [ "$cygwin" = "true" -o "$msys" = "true" ] ; 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=`expr $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"
+
+exec "$JAVACMD" "$@"
diff --git a/gradlew.bat b/gradlew.bat
new file mode 100644
index 0000000000..5093609d51
--- /dev/null
+++ b/gradlew.bat
@@ -0,0 +1,104 @@
+@rem
+@rem Copyright 2015 the original author or authors.
+@rem
+@rem Licensed under the Apache License, Version 2.0 (the "License");
+@rem you may not use this file except in compliance with the License.
+@rem You may obtain a copy of the License at
+@rem
+@rem https://www.apache.org/licenses/LICENSE-2.0
+@rem
+@rem Unless required by applicable law or agreed to in writing, software
+@rem distributed under the License is distributed on an "AS IS" BASIS,
+@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+@rem See the License for the specific language governing permissions and
+@rem limitations under the License.
+@rem
+
+@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 Resolve any "." and ".." in APP_HOME to make it shorter.
+for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
+
+@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" "-Xms64m"
+
+@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/src/main/MANIFEST.MF b/src/main/MANIFEST.MF
new file mode 100644
index 0000000000..58202def9f
--- /dev/null
+++ b/src/main/MANIFEST.MF
@@ -0,0 +1,3 @@
+Manifest-Version: 1.0
+Main-Class: main.duke.Duke
+
diff --git a/src/main/META-INF/MANIFEST.MF b/src/main/META-INF/MANIFEST.MF
new file mode 100644
index 0000000000..58202def9f
--- /dev/null
+++ b/src/main/META-INF/MANIFEST.MF
@@ -0,0 +1,3 @@
+Manifest-Version: 1.0
+Main-Class: main.duke.Duke
+
diff --git a/src/main/duke/Duke.java b/src/main/duke/Duke.java
new file mode 100644
index 0000000000..dcbcfbae1a
--- /dev/null
+++ b/src/main/duke/Duke.java
@@ -0,0 +1,34 @@
+package main.duke;
+
+import main.duke.commands.Command;
+import main.duke.io.Parser;
+import main.duke.io.Storage;
+
+public class Duke {
+
+ private Storage storage;
+ private TaskList taskList;
+ private Ui ui;
+ private Parser parser;
+
+ public Duke(String dirname, String filename) {
+ this.storage = new Storage(dirname, filename);
+ this.taskList = new TaskList();
+ this.ui = new Ui();
+ this.parser = new Parser();
+ this.storage.readFile(this.taskList);
+ }
+
+ public String getResponse(String input){
+ try {
+ Command command = this.parser.parse(input);
+ String reply = command.runCommand(this.ui, this.taskList);
+ this.storage.writeFile(this.taskList);
+ assert (reply != null);
+ return reply;
+ }
+ catch (DukeException e) {
+ return e.getMessage();
+ }
+ }
+}
diff --git a/src/main/duke/DukeException.java b/src/main/duke/DukeException.java
new file mode 100644
index 0000000000..35e9a6ebc4
--- /dev/null
+++ b/src/main/duke/DukeException.java
@@ -0,0 +1,7 @@
+package main.duke;
+
+public class DukeException extends Exception{
+ public DukeException (String errorMessage){
+ super(errorMessage);
+ }
+}
diff --git a/src/main/duke/TaskList.java b/src/main/duke/TaskList.java
new file mode 100644
index 0000000000..f052edc77a
--- /dev/null
+++ b/src/main/duke/TaskList.java
@@ -0,0 +1,61 @@
+package main.duke;
+
+import main.duke.tasks.Task;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Stack;
+
+public class TaskList {
+ private Stack> previousTasks;
+ private ArrayList Tasks;
+
+ public TaskList() {
+ this.Tasks = new ArrayList<>();
+ this.previousTasks = new Stack<>();
+ }
+
+ public Task getTask(int taskIndex) {
+ return this.Tasks.get(taskIndex);
+ }
+
+ public void addTask(Task newTask) {
+ this.Tasks.add(newTask);
+ }
+
+ public void deleteTask(int taskIndex) {
+ this.Tasks.remove(taskIndex);
+ }
+
+ public int getTasksCount() {
+ return this.Tasks.size();
+ }
+
+ public void undo() throws DukeException {
+ if (previousTasks.size() == 1) {
+ throw new DukeException("There is nothing to undo.");
+ }
+ this.previousTasks.pop();
+ if (previousTasks.size() != 1) {
+ this.Tasks = this.previousTasks.pop();
+ } else {
+ this.Tasks = this.previousTasks.peek();
+ }
+ }
+
+ public void updateStack() {
+ System.out.println(previousTasks.size());
+ ArrayList oldTasks = new ArrayList<>();
+ for (int i = 0; i < this.getTasksCount(); i++) {
+ oldTasks.add(this.getTask(i).clone());
+ }
+ this.previousTasks.add(oldTasks);
+ for (ArrayList al : previousTasks) {
+ System.out.println(Arrays.toString(al.toArray()));
+ }
+ }
+
+ public String taskCountToString() {
+ return String.format("Now you have %d task(s) in the list.", this.getTasksCount());
+ }
+}
diff --git a/src/main/duke/Ui.java b/src/main/duke/Ui.java
new file mode 100644
index 0000000000..7d9a4b0f98
--- /dev/null
+++ b/src/main/duke/Ui.java
@@ -0,0 +1,99 @@
+package main.duke;
+
+import main.duke.tasks.Task;
+
+import java.util.ArrayList;
+
+public class Ui {
+ private static final String GOODBYE_MESSAGE = "Bye. Hope to see you again soon!";
+
+ public String respondBye() {
+ return Ui.GOODBYE_MESSAGE;
+ }
+
+ /**
+ * prints out all the tasks in the current list
+ *
+ * @param taskList the current list of tasks
+ */
+ public String respondList(TaskList taskList) {
+ int n = taskList.getTasksCount();
+ if (n == 0) {
+ return "The list is currently empty.";
+ } else {
+ StringBuilder response = new StringBuilder();
+ for (int i = 0; i < n; i++) {
+ response.append(String.format("%d.%s%n", i + 1, taskList.getTask(i)));
+ }
+ return response.toString();
+ }
+ }
+
+ /**
+ * prints out the task that has been marked
+ *
+ * @param markTask the targeted task to mark
+ */
+ public String respondMark(Task markTask) {
+ return String.format("Nice! I've marked this task as done: \n"
+ + " %s\n", markTask);
+ }
+
+ /**
+ * prints out the task that has been unmarked
+ *
+ * @param unmarkTask the targeted task to unmark
+ */
+ public String respondUnmark(Task unmarkTask) {
+ return String.format("Nice! I've marked this task as not done: \n"
+ + " %s\n", unmarkTask);
+ }
+
+ /**
+ * prints out the task that has been added as well as the current number of tasks after adding
+ *
+ * @param newTask the targeted task to add
+ * @param taskList the current list of tasks
+ */
+ public String respondAddTask(Task newTask, TaskList taskList) {
+ return String.format("Got it. I've added this task:\n" + "%s\n" + "%s\n",
+ newTask, taskList.taskCountToString());
+ }
+
+ /**
+ * prints out the task that has been added as well as the current number of tasks after removing
+ *
+ * @param deleteTask the targeted task to add
+ * @param taskList the current list of tasks
+ */
+ public String respondDeleteTask(Task deleteTask, TaskList taskList) {
+ return String.format("Noted. I've removed this task: \n" + " %s\n"
+ + "%s\n", deleteTask, taskList.taskCountToString());
+ }
+
+ /**
+ * prints out the task that was filtered by the user
+ *
+ * @param foundTasks the targeted task to add
+ */
+ public String respondFindTask(ArrayList foundTasks) {
+ int n = foundTasks.size();
+ if (n == 0) {
+ return "Cannot find any related tasks.";
+ } else {
+ StringBuilder response = new StringBuilder("Here are the matching tasks in your list:");
+ for (int i = 0; i < n; i++) {
+ response.append(String.format("%d.%s%n", i + 1, foundTasks.get(i)));
+ }
+ return response.toString();
+ }
+ }
+ /**
+ * undo the previous command
+ *
+ * @param taskList the new taskList after undo
+ */
+ public String respondUndo(TaskList taskList) {
+ return "Undo task successfully!\n" + this.respondList(taskList);
+ }
+}
diff --git a/src/main/duke/commands/CBye.java b/src/main/duke/commands/CBye.java
new file mode 100644
index 0000000000..e7e1e86445
--- /dev/null
+++ b/src/main/duke/commands/CBye.java
@@ -0,0 +1,19 @@
+package main.duke.commands;
+
+import main.duke.DukeException;
+import main.duke.TaskList;
+import main.duke.Ui;
+import main.duke.enums.CommandType;
+
+public class CBye extends Command {
+
+ public CBye() {
+ super(CommandType.BYE);
+ }
+
+ @Override
+ public String runCommand(Ui ui, TaskList taskList) throws DukeException {
+ Command.exitDuke();
+ return ui.respondBye();
+ }
+}
diff --git a/src/main/duke/commands/CDeadline.java b/src/main/duke/commands/CDeadline.java
new file mode 100644
index 0000000000..b7f51ab95e
--- /dev/null
+++ b/src/main/duke/commands/CDeadline.java
@@ -0,0 +1,37 @@
+package main.duke.commands;
+
+import main.duke.DukeException;
+import main.duke.TaskList;
+import main.duke.Ui;
+import main.duke.enums.CommandType;
+import main.duke.tasks.Deadline;
+import main.duke.tasks.Task;
+
+import java.time.LocalDateTime;
+
+public class CDeadline extends Command {
+ protected String description;
+ protected LocalDateTime dueDate;
+
+ public CDeadline(String description, LocalDateTime dueDate) {
+ super(CommandType.DEADLINE);
+ this.description = description;
+ this.dueDate = dueDate;
+ }
+
+ public LocalDateTime getDueDate() {
+ return this.dueDate;
+ }
+
+ public String getDescription() {
+ return this.description;
+ }
+
+ @Override
+ public String runCommand(Ui ui, TaskList taskList) throws DukeException {
+ Task newDeadline = new Deadline(this.getDescription(), this.getDueDate());
+ taskList.addTask(newDeadline);
+ super.runCommand(ui, taskList);
+ return ui.respondAddTask(newDeadline, taskList);
+ }
+}
diff --git a/src/main/duke/commands/CDelete.java b/src/main/duke/commands/CDelete.java
new file mode 100644
index 0000000000..eb4f0712ac
--- /dev/null
+++ b/src/main/duke/commands/CDelete.java
@@ -0,0 +1,32 @@
+package main.duke.commands;
+
+import main.duke.DukeException;
+import main.duke.TaskList;
+import main.duke.Ui;
+import main.duke.enums.CommandType;
+import main.duke.tasks.Task;
+
+public class CDelete extends Command{
+ protected int deleteIndex;
+
+ public CDelete(int deleteIndex) {
+ super(CommandType.DELETE);
+ this.deleteIndex = deleteIndex;
+ }
+
+ public int getDeleteIndex() {
+ return this.deleteIndex;
+ }
+
+ @Override
+ public String runCommand(Ui ui, TaskList taskList) throws DukeException {
+ try {
+ Task deleteTask = taskList.getTask(this.getDeleteIndex());
+ taskList.deleteTask(this.getDeleteIndex());
+ super.runCommand(ui, taskList);
+ return ui.respondDeleteTask(deleteTask, taskList);
+ } catch (IndexOutOfBoundsException e) {
+ throw new DukeException("Please check that you have entered the correct index.");
+ }
+ }
+}
diff --git a/src/main/duke/commands/CEvent.java b/src/main/duke/commands/CEvent.java
new file mode 100644
index 0000000000..56ef2b07bc
--- /dev/null
+++ b/src/main/duke/commands/CEvent.java
@@ -0,0 +1,38 @@
+package main.duke.commands;
+
+import main.duke.DukeException;
+import main.duke.TaskList;
+import main.duke.Ui;
+import main.duke.enums.CommandType;
+import main.duke.tasks.Event;
+import main.duke.tasks.Task;
+
+import java.time.LocalDateTime;
+
+public class CEvent extends Command {
+ protected String description;
+ protected LocalDateTime dateTime;
+
+ public CEvent(String description, LocalDateTime dateTime) {
+ super(CommandType.EVENT);
+ this.description = description;
+ this.dateTime = dateTime;
+ }
+
+ public LocalDateTime getDateTime() {
+ return this.dateTime;
+ }
+
+ public String getDescription() {
+ return this.description;
+ }
+
+
+ @Override
+ public String runCommand(Ui ui, TaskList taskList) throws DukeException {
+ Task newEvent = new Event(this.getDescription(), this.getDateTime());
+ taskList.addTask(newEvent);
+ super.runCommand(ui, taskList);
+ return ui.respondAddTask(newEvent, taskList);
+ }
+}
diff --git a/src/main/duke/commands/CFind.java b/src/main/duke/commands/CFind.java
new file mode 100644
index 0000000000..a4e9c66361
--- /dev/null
+++ b/src/main/duke/commands/CFind.java
@@ -0,0 +1,34 @@
+package main.duke.commands;
+
+import main.duke.DukeException;
+import main.duke.TaskList;
+import main.duke.Ui;
+import main.duke.enums.CommandType;
+import main.duke.tasks.Task;
+
+import java.util.ArrayList;
+
+public class CFind extends Command{
+ protected String findString;
+
+ public CFind(String findString) {
+ super(CommandType.FIND);
+ this.findString = findString;
+ }
+
+ public String getFindString() {
+ return this.findString;
+ }
+
+ @Override
+ public String runCommand(Ui ui, TaskList taskList) throws DukeException {
+ ArrayList foundTasks = new ArrayList<>();
+ for (int i = 0; i < taskList.getTasksCount(); i++) {
+ Task curTask = taskList.getTask(i);
+ if (curTask.getDescription().contains(this.getFindString())) {
+ foundTasks.add(curTask);
+ }
+ }
+ return ui.respondFindTask(foundTasks);
+ }
+}
diff --git a/src/main/duke/commands/CList.java b/src/main/duke/commands/CList.java
new file mode 100644
index 0000000000..676ea94554
--- /dev/null
+++ b/src/main/duke/commands/CList.java
@@ -0,0 +1,16 @@
+package main.duke.commands;
+
+import main.duke.TaskList;
+import main.duke.Ui;
+import main.duke.enums.CommandType;
+
+public class CList extends Command {
+ public CList() {
+ super(CommandType.LIST);
+ }
+
+ @Override
+ public String runCommand(Ui ui, TaskList taskList) {
+ return ui.respondList(taskList);
+ }
+}
diff --git a/src/main/duke/commands/CMark.java b/src/main/duke/commands/CMark.java
new file mode 100644
index 0000000000..a3321a828d
--- /dev/null
+++ b/src/main/duke/commands/CMark.java
@@ -0,0 +1,32 @@
+package main.duke.commands;
+
+import main.duke.DukeException;
+import main.duke.TaskList;
+import main.duke.Ui;
+import main.duke.enums.CommandType;
+import main.duke.tasks.Task;
+
+public class CMark extends Command{
+ protected int markIndex;
+
+ public CMark(int markIndex) {
+ super(CommandType.MARK);
+ this.markIndex = markIndex;
+ }
+
+ public int getMarkIndex() {
+ return this.markIndex;
+ }
+
+ @Override
+ public String runCommand(Ui ui, TaskList taskList) throws DukeException {
+ try {
+ Task markTask = taskList.getTask(this.getMarkIndex());
+ markTask.setIsDone(true);
+ super.runCommand(ui, taskList);
+ return ui.respondMark(markTask);
+ } catch (IndexOutOfBoundsException e) {
+ throw new DukeException("Please check that you have entered the correct index.");
+ }
+ }
+}
diff --git a/src/main/duke/commands/CTodo.java b/src/main/duke/commands/CTodo.java
new file mode 100644
index 0000000000..c7ed048f3d
--- /dev/null
+++ b/src/main/duke/commands/CTodo.java
@@ -0,0 +1,29 @@
+package main.duke.commands;
+
+import main.duke.DukeException;
+import main.duke.TaskList;
+import main.duke.Ui;
+import main.duke.enums.CommandType;
+import main.duke.tasks.Task;
+import main.duke.tasks.ToDo;
+
+public class CTodo extends Command{
+ protected String description;
+
+ public CTodo(String description) {
+ super(CommandType.TODO);
+ this.description = description;
+ }
+
+ public String getDescription() {
+ return this.description;
+ }
+
+ @Override
+ public String runCommand(Ui ui, TaskList taskList) throws DukeException {
+ Task newToDo = new ToDo(this.getDescription());
+ taskList.addTask(newToDo);
+ super.runCommand(ui, taskList);
+ return ui.respondAddTask(newToDo, taskList);
+ }
+}
diff --git a/src/main/duke/commands/CUndo.java b/src/main/duke/commands/CUndo.java
new file mode 100644
index 0000000000..26a98d81fd
--- /dev/null
+++ b/src/main/duke/commands/CUndo.java
@@ -0,0 +1,19 @@
+package main.duke.commands;
+
+import main.duke.DukeException;
+import main.duke.TaskList;
+import main.duke.Ui;
+import main.duke.enums.CommandType;
+
+public class CUndo extends Command {
+
+ public CUndo() {
+ super(CommandType.UNDO);
+ }
+
+ @Override
+ public String runCommand(Ui ui, TaskList taskList) throws DukeException {
+ taskList.undo();
+ return ui.respondUndo(taskList);
+ }
+}
diff --git a/src/main/duke/commands/CUnmark.java b/src/main/duke/commands/CUnmark.java
new file mode 100644
index 0000000000..8f381dff05
--- /dev/null
+++ b/src/main/duke/commands/CUnmark.java
@@ -0,0 +1,32 @@
+package main.duke.commands;
+
+import main.duke.DukeException;
+import main.duke.TaskList;
+import main.duke.Ui;
+import main.duke.enums.CommandType;
+import main.duke.tasks.Task;
+
+public class CUnmark extends Command{
+ protected int unmarkIndex;
+
+ public CUnmark(int unmarkIndex) {
+ super(CommandType.UNMARK);
+ this.unmarkIndex = unmarkIndex;
+ }
+
+ public int getUnmarkIndex() {
+ return this.unmarkIndex;
+ }
+
+ @Override
+ public String runCommand(Ui ui, TaskList taskList) throws DukeException {
+ try {
+ Task unmarkTask = taskList.getTask(this.getUnmarkIndex());
+ unmarkTask.setIsDone(false);
+ super.runCommand(ui, taskList);
+ return ui.respondUnmark(unmarkTask);
+ } catch (IndexOutOfBoundsException e) {
+ throw new DukeException("Please check that you have entered the correct index.");
+ }
+ }
+}
diff --git a/src/main/duke/commands/Command.java b/src/main/duke/commands/Command.java
new file mode 100644
index 0000000000..0aa5877481
--- /dev/null
+++ b/src/main/duke/commands/Command.java
@@ -0,0 +1,33 @@
+package main.duke.commands;
+
+import main.duke.DukeException;
+import main.duke.TaskList;
+import main.duke.Ui;
+import main.duke.enums.CommandType;
+
+public abstract class Command {
+ protected CommandType commandType;
+
+ protected static boolean isExit = false;
+
+ public Command(CommandType commandType) {
+ this.commandType = commandType;
+ }
+
+ public static boolean getIsExit() {
+ return Command.isExit;
+ }
+
+ public static void exitDuke() {
+ Command.isExit = true;
+ }
+
+ public CommandType getCommandType() {
+ return this.commandType;
+ }
+
+ public String runCommand(Ui ui, TaskList taskList) throws DukeException {
+ taskList.updateStack();
+ return "";
+ }
+}
diff --git a/src/main/duke/enums/CommandType.java b/src/main/duke/enums/CommandType.java
new file mode 100644
index 0000000000..d0f0198280
--- /dev/null
+++ b/src/main/duke/enums/CommandType.java
@@ -0,0 +1,14 @@
+package main.duke.enums;
+
+public enum CommandType {
+ BYE,
+ DEADLINE,
+ DELETE,
+ EVENT,
+ LIST,
+ MARK,
+ TODO,
+ UNMARK,
+ FIND,
+ UNDO,
+}
diff --git a/src/main/duke/enums/TaskType.java b/src/main/duke/enums/TaskType.java
new file mode 100644
index 0000000000..e9fb6f1def
--- /dev/null
+++ b/src/main/duke/enums/TaskType.java
@@ -0,0 +1,18 @@
+package main.duke.enums;
+
+public enum TaskType {
+ TODO("T"),
+ DEADLINE("D"),
+ Event("E");
+
+ private String icon;
+
+ private TaskType(String icon) {
+ this.icon = icon;
+ }
+
+ @Override
+ public String toString() {
+ return icon;
+ }
+}
diff --git a/src/main/duke/gui/DialogBox.java b/src/main/duke/gui/DialogBox.java
new file mode 100644
index 0000000000..f4dddc2426
--- /dev/null
+++ b/src/main/duke/gui/DialogBox.java
@@ -0,0 +1,61 @@
+package main.duke.gui;
+
+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.Pos;
+import javafx.scene.Node;
+import javafx.scene.control.Label;
+import javafx.scene.image.Image;
+import javafx.scene.image.ImageView;
+import javafx.scene.layout.HBox;
+
+/**
+ * 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 Label dialog;
+ @FXML
+ private ImageView displayPicture;
+
+ private DialogBox(String text, Image img) {
+ 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);
+ displayPicture.setImage(img);
+ }
+
+ /**
+ * 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) {
+ return new DialogBox(text, img);
+ }
+
+ public static DialogBox getDukeDialog(String text, Image img) {
+ var db = new DialogBox(text, img);
+ db.flip();
+ return db;
+ }
+}
diff --git a/src/main/duke/gui/Launcher.java b/src/main/duke/gui/Launcher.java
new file mode 100644
index 0000000000..0d59cc8c66
--- /dev/null
+++ b/src/main/duke/gui/Launcher.java
@@ -0,0 +1,9 @@
+package main.duke.gui;
+
+import javafx.application.Application;
+
+public class Launcher {
+ public static void main(String[] args) {
+ Application.launch(Main.class, args);
+ }
+}
diff --git a/src/main/duke/gui/Main.java b/src/main/duke/gui/Main.java
new file mode 100644
index 0000000000..3e7b62273d
--- /dev/null
+++ b/src/main/duke/gui/Main.java
@@ -0,0 +1,34 @@
+package main.duke.gui;
+
+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;
+
+import main.duke.Duke;
+
+/**
+ * A GUI for Duke using FXML.
+ */
+public class Main extends Application {
+
+ private Duke duke = new Duke("data", "duke.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);
+ stage.setTitle("Duke bot");
+ fxmlLoader.getController().setDuke(duke);
+ stage.show();
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ }
+}
diff --git a/src/main/duke/gui/MainWindow.java b/src/main/duke/gui/MainWindow.java
new file mode 100644
index 0000000000..412296dfac
--- /dev/null
+++ b/src/main/duke/gui/MainWindow.java
@@ -0,0 +1,63 @@
+package main.duke.gui;
+
+import javafx.fxml.FXML;
+import main.duke.Duke;
+
+import javafx.scene.control.ScrollPane;
+import javafx.scene.control.TextField;
+import javafx.scene.layout.AnchorPane;
+import javafx.scene.layout.VBox;
+import javafx.scene.image.Image;
+import main.duke.DukeException;
+import main.duke.commands.Command;
+
+import java.util.concurrent.CompletableFuture;
+
+public class MainWindow extends AnchorPane{
+
+ @FXML
+ private ScrollPane scrollPane;
+ @FXML
+ private VBox dialogContainer;
+ @FXML
+ private TextField userInput;
+ @FXML
+
+ private Image userImage = new Image(this.getClass().getResourceAsStream("/images/user.png"));
+ private Image dukeImage = new Image(this.getClass().getResourceAsStream("/images/duke.jpg"));
+
+ private Duke duke = new Duke("data", "duke.txt");
+
+ @FXML
+ public void initialize() {
+ scrollPane.vvalueProperty().bind(dialogContainer.heightProperty());
+ }
+
+ public void setDuke(Duke d) {
+ duke = d;
+ }
+
+ /**
+ * Clears the user input after processing.
+ */
+ @FXML
+ private void handleUserInput() throws DukeException {
+ String input = userInput.getText();
+ String response = duke.getResponse(input);
+ dialogContainer.getChildren().addAll(
+ DialogBox.getUserDialog(input, userImage),
+ DialogBox.getDukeDialog(response, dukeImage)
+ );
+ userInput.clear();
+ CompletableFuture.runAsync(() -> {
+ try {
+ if (Command.getIsExit()) {
+ Thread.sleep(200);
+ System.exit(0);
+ }
+ } catch (InterruptedException e) {
+ e.printStackTrace();
+ }});
+ assert (userInput.equals(null));
+ }
+}
diff --git a/src/main/duke/io/Parser.java b/src/main/duke/io/Parser.java
new file mode 100644
index 0000000000..0bf090fb59
--- /dev/null
+++ b/src/main/duke/io/Parser.java
@@ -0,0 +1,152 @@
+package main.duke.io;
+
+import main.duke.commands.*;
+import main.duke.DukeException;
+
+import java.util.Arrays;
+
+import java.time.LocalDateTime;
+import java.time.format.DateTimeFormatter;
+import java.time.format.DateTimeParseException;
+
+public class Parser {
+
+ /**
+ * @param userInput the whole console command line from the user
+ *
+ * @return Command to be executed
+ *
+ * @throws DukeException when invalid input detected
+ */
+ public Command parse(String userInput) throws DukeException {
+ assert (userInput.length() != 0);
+ String[] inputArray = userInput.split(" ");
+ String userCommand = inputArray[0];
+ Command newCommand;
+ switch (userCommand) {
+ case "bye":
+ newCommand = new CBye();
+ break;
+ case "list":
+ newCommand = new CList();
+ break;
+ case "mark":
+ newCommand = createNewMark(inputArray);
+ break;
+ case "unmark":
+ newCommand = createNewUnmark(inputArray);
+ break;
+ case "delete":
+ newCommand = createNewDelete(inputArray);
+ break;
+ case "todo":
+ newCommand = createNewToDo(inputArray);
+ break;
+ case "deadline":
+ newCommand = createNewDeadline(userInput, inputArray);
+ break;
+ case "event":
+ newCommand = createNewEvent(userInput, inputArray);
+ break;
+ case "find":
+ String findString = String.join(" ",
+ Arrays.copyOfRange(inputArray, 1, inputArray.length));
+ newCommand = new CFind(findString);
+ break;
+ case "undo":
+ newCommand = new CUndo();
+ break;
+ default:
+ throw new DukeException("Sorry. I do not understand your input.");
+ }
+ return newCommand;
+ }
+
+ private Command createNewMark(String[] inputArray) throws DukeException{
+ try {
+ int markIndex = Integer.parseInt(inputArray[1]) - 1;
+ return new CMark(markIndex);
+ } catch (IndexOutOfBoundsException e) {
+ throw new DukeException("Please specify the task you wish to mark.");
+ } catch (NumberFormatException e) {
+ throw new DukeException("Invalid index format.");
+ }
+ }
+
+ private Command createNewUnmark(String[] inputArray) throws DukeException {
+ try {
+ int unmarkIndex = Integer.parseInt(inputArray[1]) - 1;
+ return new CUnmark(unmarkIndex);
+ } catch (IndexOutOfBoundsException e) {
+ throw new DukeException("Please specify the task you wish to unmark.");
+ } catch (NumberFormatException e) {
+ throw new DukeException("Invalid index format.");
+ }
+ }
+
+ private Command createNewDelete(String[] inputArray) throws DukeException {
+ try {
+ int deleteIndex = Integer.parseInt(inputArray[1]) - 1;
+ return new CDelete(deleteIndex);
+ } catch (IndexOutOfBoundsException e) {
+ throw new DukeException("Please specify the task you wish to delete.");
+ } catch (NumberFormatException e) {
+ throw new DukeException("Invalid index format.");
+ }
+ }
+
+ private Command createNewToDo(String[] inputArray) throws DukeException {
+ String todoDescription = String.join(" ",
+ Arrays.copyOfRange(inputArray, 1, inputArray.length));
+ if (todoDescription.equals("")) {
+ throw new DukeException("Please specify the description of the todo task.");
+ }
+ return new CTodo(todoDescription);
+ }
+
+ private Command createNewDeadline(String userInput, String[] inputArray) throws DukeException {
+ if (!userInput.contains("/by")) {
+ throw new DukeException("Please specify the due date using the /by keyword.");
+ } else {
+ try {
+ int byIndex = Arrays.asList(inputArray).indexOf("/by");
+ String deadlineDescription = String.join(" ",
+ Arrays.copyOfRange(inputArray, 1, byIndex));
+ String dueDate = String.join(" ",
+ Arrays.copyOfRange(inputArray, byIndex + 1, inputArray.length));
+ LocalDateTime newDueDate = LocalDateTime.parse(dueDate,
+ DateTimeFormatter.ofPattern("yyyy-MM-dd kkmm"));
+ // check if the date and time input is in the right format
+ if (deadlineDescription.equals("") || dueDate.equals("")) {
+ throw new DukeException("Please specify the description/due date of the deadline task.");
+ }
+ return new CDeadline(deadlineDescription, newDueDate);
+ } catch (DateTimeParseException e){
+ throw new DukeException("Please provide the date time in this format YYYY-MM-DD 0000");
+ }
+ }
+ }
+
+ private Command createNewEvent(String userInput, String[] inputArray) throws DukeException {
+ if (!userInput.contains("/at")) {
+ throw new DukeException("Please specify the date time using the /at keyword.");
+ } else {
+ try {
+ int byIndex = Arrays.asList(inputArray).indexOf("/at");
+ String eventDescription = String.join(" ",
+ Arrays.copyOfRange(inputArray, 1, byIndex));
+ String dateTime = String.join(" ",
+ Arrays.copyOfRange(inputArray, byIndex + 1, inputArray.length));
+ LocalDateTime newDateTime = LocalDateTime.parse(dateTime,
+ DateTimeFormatter.ofPattern("yyyy-MM-dd kkmm"));
+ // check if the date and time input is in the right format
+ if (eventDescription.equals("") || dateTime.equals("")) {
+ throw new DukeException("Please specify the description/date time of the event task.");
+ }
+ return new CEvent(eventDescription, newDateTime);
+ } catch (DateTimeParseException e){
+ throw new DukeException("Please provide the date time in this format YYYY-MM-DD 0000");
+ }
+ }
+ }
+}
diff --git a/src/main/duke/io/Storage.java b/src/main/duke/io/Storage.java
new file mode 100644
index 0000000000..bada947d28
--- /dev/null
+++ b/src/main/duke/io/Storage.java
@@ -0,0 +1,143 @@
+package main.duke.io;
+
+import main.duke.DukeException;
+import main.duke.TaskList;
+import main.duke.tasks.Deadline;
+import main.duke.tasks.Event;
+import main.duke.tasks.ToDo;
+
+import java.io.File;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.time.LocalDateTime;
+import java.time.format.DateTimeFormatter;
+import java.time.format.DateTimeParseException;
+import java.util.Scanner;
+
+public class Storage {
+ protected String filename;
+ protected String dirname;
+
+ public Storage(String dirname, String filename) {
+ this.filename = filename;
+ this.dirname = dirname;
+ this.createFile();
+ }
+
+ private String getCurrentDirectory() {
+ return System.getProperty("user.dir");
+ }
+
+ private String getDirname() {
+ return this.dirname;
+ }
+
+ private String getFilename() {
+ return this.filename;
+ }
+
+ private Path getDirPath() {
+ return Paths.get(this.getCurrentDirectory(), this.getDirname());
+ }
+
+ private Path getFilePath() {
+ return Paths.get(this.getCurrentDirectory(),
+ String.format("%s/%s", this.getDirname(), this.getFilename()));
+ }
+
+ /**
+ * create the required directory and file to store the list if it doesn't exist
+ */
+ private void createFile(){
+ try {
+ if (!Files.exists(this.getDirPath())) {
+ Files.createDirectories(this.getDirPath());
+ }
+ if (!Files.exists(this.getFilePath())) {
+ Files.createFile(this.getFilePath());
+ }
+ } catch (IOException e) {
+ System.out.printf("Error while trying to create save file: ", e.getMessage());
+ }
+ }
+
+ /**
+ * read the file for the saved list and update the list of tasks
+ *
+ * @param taskList empty list of task
+ */
+ public void readFile(TaskList taskList) {
+ try {
+ File dukeFile = new File(String.format("%s/%s", this.getDirname(), this.getFilename()));
+ Scanner dukeReader = new Scanner(dukeFile);
+ while (dukeReader.hasNextLine()) {
+ String taskString = dukeReader.nextLine();
+ String[] taskStringArray = taskString.split("~");
+ this.addToTasks(taskStringArray, taskList);
+ }
+ taskList.updateStack();
+ dukeReader.close();
+ } catch (IOException e) {
+ System.out.printf("Error while trying to read save file: ", e.getMessage());
+ } catch (DukeException e) {
+ System.out.println(e.getMessage());
+ }
+ }
+
+ /**
+ * decipher the array and add the appropriate task to the list
+ *
+ * @param taskStringArray array of strings representing a task
+ * @param taskList current list of tasks
+ */
+ public void addToTasks(String[] taskStringArray, TaskList taskList) throws DukeException {
+ String type = taskStringArray[0];
+ boolean isDone = taskStringArray[1].equals("1");
+ String description = taskStringArray[2];
+ switch (type) {
+ case "T":
+ taskList.addTask(new ToDo(description, isDone));
+ break;
+ case "D":
+ try {
+ LocalDateTime newDueDate = LocalDateTime.parse(taskStringArray[3],
+ DateTimeFormatter.ofPattern("yyyy-MM-dd kkmm"));
+ taskList.addTask(new Deadline(description, newDueDate, isDone));
+ } catch (DateTimeParseException e){
+ throw new DukeException("Save file date time not in this format YYYY-MM-DD 0000");
+ }
+ break;
+ case "E":
+ try {
+ LocalDateTime newDateTime = LocalDateTime.parse(taskStringArray[3],
+ DateTimeFormatter.ofPattern("yyyy-MM-dd kkmm"));
+ taskList.addTask(new Event(description, newDateTime, isDone));
+ } catch (DateTimeParseException e){
+ throw new DukeException("Save file date time not in this format YYYY-MM-DD 0000");
+ }
+ break;
+ }
+ }
+
+ /**
+ * save the list back into the save file
+ *
+ * @param taskList current list of tasks
+ */
+ public void writeFile(TaskList taskList) {
+ try {
+ FileWriter dukeWriter = new FileWriter(String.format("%s/%s", this.getDirname(), this.getFilename()));
+ for (int i = 0; i < taskList.getTasksCount(); i++) {
+ if (i != taskList.getTasksCount()) {
+ dukeWriter.write(taskList.getTask(i).toStoreString() + "\n");
+ }
+ }
+ dukeWriter.close();
+ } catch (IOException e) {
+ System.out.printf("Error while trying to write save file: ", e.getMessage());
+ }
+ }
+}
diff --git a/src/main/duke/tasks/Deadline.java b/src/main/duke/tasks/Deadline.java
new file mode 100644
index 0000000000..1878dccf8d
--- /dev/null
+++ b/src/main/duke/tasks/Deadline.java
@@ -0,0 +1,40 @@
+package main.duke.tasks;
+
+import main.duke.enums.TaskType;
+
+import java.time.LocalDateTime;
+import java.time.format.DateTimeFormatter;
+
+public class Deadline extends Task{
+ protected LocalDateTime dueDate;
+
+ public Deadline(String description, LocalDateTime dueDate) {
+ super(description, TaskType.DEADLINE);
+ this.dueDate = dueDate;
+ }
+
+ public Deadline(String description, LocalDateTime dueDate, boolean isDone) {
+ super(description, TaskType.DEADLINE, isDone);
+ this.dueDate = dueDate;
+ }
+
+ public LocalDateTime getDueDate() {
+ return this.dueDate;
+ }
+
+ @Override
+ public Task clone() {
+ return new Deadline(this.getDescription(), this.getDueDate(), this.getIsDone());
+ }
+
+ @Override
+ public String toStoreString() {
+ return super.toStoreString() + "~" + this.getDueDate().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HHmm"));
+ }
+
+ @Override
+ public String toString() {
+ return super.toString() + String.format(" (by: %s)",
+ this.getDueDate().format(DateTimeFormatter.ofPattern("dd/MMM/yyyy HH:mm")));
+ }
+}
diff --git a/src/main/duke/tasks/Event.java b/src/main/duke/tasks/Event.java
new file mode 100644
index 0000000000..a0bb5b3583
--- /dev/null
+++ b/src/main/duke/tasks/Event.java
@@ -0,0 +1,39 @@
+package main.duke.tasks;
+
+import main.duke.enums.TaskType;
+
+import java.time.LocalDateTime;
+import java.time.format.DateTimeFormatter;
+
+public class Event extends Task{
+ protected LocalDateTime eventTime;
+
+ public Event(String description, LocalDateTime eventTime) {
+ super(description, TaskType.Event);
+ this.eventTime = eventTime;
+ }
+
+ public Event(String description, LocalDateTime eventTime, boolean isDone) {
+ super(description, TaskType.Event, isDone);
+ this.eventTime = eventTime;
+ }
+
+ public LocalDateTime getEventTime() {
+ return this.eventTime;
+ }
+
+ public Task clone() {
+ return new Event(this.getDescription(), this.getEventTime(), this.getIsDone());
+ }
+
+ @Override
+ public String toStoreString() {
+ return super.toStoreString() + "~" + this.getEventTime().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HHmm"));
+ }
+
+ @Override
+ public String toString() {
+ return super.toString() + String.format(" (at: %s)",
+ this.getEventTime().format(DateTimeFormatter.ofPattern("dd/MMM/yyyy HH:mm")));
+ }
+}
diff --git a/src/main/duke/tasks/Task.java b/src/main/duke/tasks/Task.java
new file mode 100644
index 0000000000..ce5a43a446
--- /dev/null
+++ b/src/main/duke/tasks/Task.java
@@ -0,0 +1,60 @@
+package main.duke.tasks;
+
+import main.duke.DukeException;
+import main.duke.enums.TaskType;
+
+public abstract class Task {
+ protected String description;
+ protected TaskType taskType;
+ protected boolean isDone;
+
+ public Task(String description, TaskType taskType) {
+ this.description = description;
+ this.taskType = taskType;
+ this.isDone = false;
+ }
+
+ public Task(String description, TaskType taskType, boolean isDone) {
+ this.description = description;
+ this.taskType = taskType;
+ this.isDone = isDone;
+ }
+
+ public String getDescription() {
+ return this.description;
+ }
+
+ public TaskType getTaskType() { return this.taskType; }
+
+ public String getStatusIcon() {
+ return (isDone ? "X" : " ");
+ }
+
+ public void setIsDone(boolean isDone) throws DukeException {
+ if (this.isDone == isDone) {
+ throw new DukeException("This task is already marked/unmarked.");
+ }
+ this.isDone = isDone;
+ }
+
+ public boolean getIsDone() {
+ return this.isDone;
+ }
+
+ /**
+ * format the task into a string to be stored in the save file
+ */
+ public String toStoreString() {
+ return String.format("%s~%d~%s",
+ this.getTaskType(),
+ this.getStatusIcon() == "X" ? 1 : 0,
+ this.getDescription());
+ }
+
+ public abstract Task clone();
+
+ @Override
+ public String toString() {
+ return String.format("[%s][%s] %s", this.getTaskType(), this.getStatusIcon(), this.getDescription());
+ }
+}
\ No newline at end of file
diff --git a/src/main/duke/tasks/ToDo.java b/src/main/duke/tasks/ToDo.java
new file mode 100644
index 0000000000..25b308bd1d
--- /dev/null
+++ b/src/main/duke/tasks/ToDo.java
@@ -0,0 +1,17 @@
+package main.duke.tasks;
+
+import main.duke.enums.TaskType;
+
+public class ToDo extends Task{
+ public ToDo(String description) {
+ super(description, TaskType.TODO);
+ }
+
+ public Task clone() {
+ return new ToDo(this.getDescription(), this.getIsDone());
+ }
+
+ public ToDo(String description, boolean isDone) {
+ super(description, TaskType.TODO, isDone);
+ }
+}
diff --git a/src/main/java/Duke.java b/src/main/java/Duke.java
deleted file mode 100644
index 5d313334cc..0000000000
--- a/src/main/java/Duke.java
+++ /dev/null
@@ -1,10 +0,0 @@
-public class Duke {
- public static void main(String[] args) {
- String logo = " ____ _ \n"
- + "| _ \\ _ _| | _____ \n"
- + "| | | | | | | |/ / _ \\\n"
- + "| |_| | |_| | < __/\n"
- + "|____/ \\__,_|_|\\_\\___|\n";
- System.out.println("Hello from\n" + logo);
- }
-}
diff --git a/src/main/resources/images/duke.jpg b/src/main/resources/images/duke.jpg
new file mode 100644
index 0000000000..e0186fa50f
Binary files /dev/null and b/src/main/resources/images/duke.jpg differ
diff --git a/src/main/resources/images/user.png b/src/main/resources/images/user.png
new file mode 100644
index 0000000000..89c2e2d5ea
Binary files /dev/null and b/src/main/resources/images/user.png differ
diff --git a/src/main/resources/view/DialogBox.fxml b/src/main/resources/view/DialogBox.fxml
new file mode 100644
index 0000000000..59d16ac91a
--- /dev/null
+++ b/src/main/resources/view/DialogBox.fxml
@@ -0,0 +1,16 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/main/resources/view/MainWindow.fxml b/src/main/resources/view/MainWindow.fxml
new file mode 100644
index 0000000000..f8c5457847
--- /dev/null
+++ b/src/main/resources/view/MainWindow.fxml
@@ -0,0 +1,19 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/test/duke/DukeTest.java b/src/test/duke/DukeTest.java
new file mode 100644
index 0000000000..3e81211b8a
--- /dev/null
+++ b/src/test/duke/DukeTest.java
@@ -0,0 +1,34 @@
+import main.duke.DukeException;
+import main.duke.commands.CBye;
+import main.duke.commands.CDeadline;
+import main.duke.commands.Command;
+import main.duke.io.Parser;
+import org.testng.annotations.Test;
+
+import static org.testng.AssertJUnit.assertEquals;
+
+public class DukeTest {
+ private static Parser parser = new Parser();
+
+ @Test
+ public void parseByeTest() {
+ Command bye = new CBye();
+ try{
+ Command parseBye = parser.parse("bye");
+ assertEquals(bye.getClass(), parseBye.getClass());
+ }
+ catch(DukeException e) {
+ System.out.println(e.getMessage());
+ }
+ }
+
+ @Test
+ public void parseDeadlineTest() {
+ try {
+ Command parseDeadline = parser.parse("deadline test /by 1111-11-11 1111");
+ assertEquals(parseDeadline.getClass(), CDeadline.class);
+ } catch (DukeException dukeException) {
+ System.out.println(dukeException.getMessage());
+ }
+ }
+}
\ No newline at end of file
diff --git a/text-ui-test/EXPECTED.TXT b/text-ui-test/EXPECTED.TXT
index 657e74f6e7..6fc3058184 100644
--- a/text-ui-test/EXPECTED.TXT
+++ b/text-ui-test/EXPECTED.TXT
@@ -1,7 +1,27 @@
-Hello from
- ____ _
-| _ \ _ _| | _____
-| | | | | | | |/ / _ \
-| |_| | |_| | < __/
-|____/ \__,_|_|\_\___|
+Hello! I'm Duke
+What can I do for you
+Got it. I've added this tasks:
+[T][ ] borrow book
+Now you have 1 tasks(s) in the list.
+Got it. I've added this tasks:
+[D][ ] return book (by:Sunday)
+Now you have 2 tasks(s) in the list.
+Got it. I've added this tasks:
+[E][ ] project meeting (at:Mon 2-4pm)
+Now you have 3 tasks(s) in the list.
+1.[T][ ] borrow book
+2.[D][ ] return book (by:Sunday)
+3.[E][ ] project meeting (at:Mon 2-4pm)
+Nice! I've marked this tasks as done:
+ [D][X] return book (by:Sunday)
+1.[T][ ] borrow book
+2.[D][X] return book (by:Sunday)
+3.[E][ ] project meeting (at:Mon 2-4pm)
+Ok, I've marked this tasks as not done yet:
+ [D][ ] return book (by:Sunday)
+1.[T][ ] borrow book
+2.[D][ ] return book (by:Sunday)
+3.[E][ ] project meeting (at:Mon 2-4pm)
+Bye. Hope to see you again soon!
+
diff --git a/text-ui-test/input.txt b/text-ui-test/input.txt
index e69de29bb2..00f7a31730 100644
--- a/text-ui-test/input.txt
+++ b/text-ui-test/input.txt
@@ -0,0 +1,9 @@
+todo borrow book
+deadline return book /by Sunday
+event project meeting /at Mon 2-4pm
+list
+mark 2
+list
+unmark 2
+list
+bye
\ No newline at end of file