diff --git a/README.md b/README.md
index 8715d4d915..48ba0c52c4 100644
--- a/README.md
+++ b/README.md
@@ -21,4 +21,4 @@ Prerequisites: JDK 11, update Intellij to the most recent version.
| | | | | | | |/ / _ \
| |_| | |_| | < __/
|____/ \__,_|_|\_\___|
- ```
+ ```
\ No newline at end of file
diff --git a/build.gradle b/build.gradle
new file mode 100644
index 0000000000..46b9671e45
--- /dev/null
+++ b/build.gradle
@@ -0,0 +1,60 @@
+plugins {
+ id 'java'
+ id 'application'
+ id 'checkstyle'
+ id 'com.github.johnrengelman.shadow' version '5.1.0'
+}
+
+repositories {
+ mavenCentral()
+}
+
+dependencies {
+ 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'
+ testImplementation group: 'org.junit.jupiter', name: 'junit-jupiter-api', version: '5.5.0'
+ testRuntimeOnly group: 'org.junit.jupiter', name: 'junit-jupiter-engine', version: '5.5.0'
+}
+
+test {
+ useJUnitPlatform()
+
+ testLogging {
+ events "passed", "skipped", "failed"
+
+ showExceptions true
+ exceptionFormat "full"
+ showCauses true
+ showStackTraces true
+ showStandardStreams = false
+ }
+}
+
+application {
+ mainClassName = "duke.Launcher"
+}
+
+shadowJar {
+ archiveBaseName = "duke"
+ archiveClassifier = null
+}
+
+checkstyle {
+ toolVersion = '8.29'
+}
+
+run{
+ standardInput = System.in
+}
diff --git a/docs/README.md b/docs/README.md
index 8077118ebe..640fb2f19b 100644
--- a/docs/README.md
+++ b/docs/README.md
@@ -1,29 +1,126 @@
# User Guide
+Duke is a **desktop app for managing tasks, optimized via a Command Line Interface** (CLI).
+
+A Graphical User Interface (GUI) has also been added which mimics a chatbot you can interact with.
+
+- [Quick Start](#quick-start)
+- [Features](#features)
+ - [Adding a task](#adding-a-task)
+ - [Listing all tasks](#listing-all-tasks-list)
+ - [Sorting all tasks by time](#sorting-all-tasks-by-time-sort)
+ - [Finding tasks by keyword](#finding-tasks-by-keyword-find)
+ - [Marking a task as done](#marking-a-task-as-done-mark)
+ - [Unmarking a task](#unmarking-a-task-unmark)
+ - [Deleting a task](#deleting-a-task-delete)
+ - [Exiting the program](#exiting-the-program-bye)
+- [Command Summary](#command-summary)
+- [References](#references)
+
+## Quick Start
+1. Ensure that you have Java `11` or above installed on your Computer.
+2. Download the latest `duke.jar` from [here](https://github.com/Decaxical/ip/releases).
+3. Copy the file to the folder you want to use as the *home folder* for Duke.
+4. Double-click the file to start the app.
+5. Type the command in the command box and press `Enter` key or click the `Send` button to execute it.
+6. Refer to the [Features](#features) below for details of each command.
## Features
+**Notes about the command format:**
+- Parameters to be supplied by the user are in `UPPER_CASE`.
+
+ e.g. `todo TASK_NAME`, `TASK_NAME` is a parameter to be supplied such as `todo homework`.
+
+---
+### Adding a task:
+Duke supports 3 types of tasks: `todo`, `deadline` and `event`
+### Adding a todo task: `todo`
+Adds a task that has no date and time parameters.
+
+Format: `todo TASK_NAME`
+
+Example: `todo return book`
+
+### Adding a deadline task: `deadline`
+Adds a task that needs to be done before a specific date/time.
+
+Format: `deadline TASK_NAME /by DD/MM/YYYY HH:MM`
+
+Example: `deadline submit project /by 22/02/2022 22:22`
+
+### Adding an event task: `event`
+Adds a task that starts at a specific date/time.
+
+Format: `event TASK_NAME /at DD/MM/YYYY HH:MM`
+
+Example: `event volleyball training /at 24/02/2022 21:00`
+
+---
+### Listing all tasks: `list`
+
+Shows a numbered list of all tasks in the task list.
+
+Format: `list`
+
+---
+### Sorting all tasks by time: `sort`
+
+Separates all tasks by type before sorting them by time. Upcoming tasks are displayed first.
+
+Format: `sort`
+
+---
+### Finding tasks by keyword: `find`
+Find tasks whose name contains the keyword supplied.
+
+Format: `find KEYWORD`
+
+Example: `find meeting`
+
+---
+### Marking a task as done: `mark`
+Marks task at given index as done.
+
+Format: `mark INDEX`
+
+Example: `mark 4`
-### Feature-ABC
+---
+### Unmarking a task: `unmark`
+Unmarks task at given index.
-Description of the feature.
+Format: `unmark INDEX`
-### Feature-XYZ
+Example: `unmark 4`
-Description of the feature.
+---
+### Deleting a task: `delete`
+Deletes task at given index.
-## Usage
+Format: `delete INDEX`
-### `Keyword` - Describe action
+Example: `delete 3`
-Describe the action and its outcome.
+---
+### Exiting the program: `bye`
-Example of usage:
+Exits the program.
-`keyword (optional arguments)`
+Format: `bye`
-Expected outcome:
+## Command Summary
-Description of the outcome.
+ Action | Format, Examples
+ ---| ---
+ Todo | `todo TASK_NAME` e.g., `todo return book`
+ Deadline| `deadline TASK_NAME /by DD/MM/YYYY HH:MM`
e.g., `deadline submit project /by 22/02/2022 22:22`
+ Event | `event TASK_NAME /at DD/MM/YYYY HH:MM`
e.g., `event volleyball training /at 24/02/2022 21:00`
+ List | `list`
+ Sort | `sort`
+ Find | `find KEYWORD`
e.g., `find meeting`
+ Mark | `mark INDEX`
e.g., `mark 4`
+ Unmark | `unmark INDEX`
e.g., `unmark 4`
+ Delete | `delete INDEX`
e.g., `delete 3`
+ Exit | `bye`
-```
-expected output
-```
+## References
+[AddressBook-Level3](https://github.com/j-lum/addressbook-level3)
diff --git a/docs/Ui.png b/docs/Ui.png
new file mode 100644
index 0000000000..e0338d4af7
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..18854876c6
--- /dev/null
+++ b/docs/_config.yml
@@ -0,0 +1 @@
+theme: jekyll-theme-midnight
\ No newline at end of file
diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar
new file mode 100644
index 0000000000..f3d88b1c2f
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..b7c8c5dbf5
--- /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-6.2-bin.zip
+zipStoreBase=GRADLE_USER_HOME
+zipStorePath=wrapper/dists
diff --git a/gradlew b/gradlew
new file mode 100755
index 0000000000..2fe81a7d95
--- /dev/null
+++ b/gradlew
@@ -0,0 +1,183 @@
+#!/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..62bd9b9cce
--- /dev/null
+++ b/gradlew.bat
@@ -0,0 +1,103 @@
+@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/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/java/META-INF/MANIFEST.MF b/src/main/java/META-INF/MANIFEST.MF
new file mode 100644
index 0000000000..2c9a9745c5
--- /dev/null
+++ b/src/main/java/META-INF/MANIFEST.MF
@@ -0,0 +1,3 @@
+Manifest-Version: 1.0
+Main-Class: duke.Duke
+
diff --git a/src/main/java/duke/Duke.java b/src/main/java/duke/Duke.java
new file mode 100644
index 0000000000..21d0a36625
--- /dev/null
+++ b/src/main/java/duke/Duke.java
@@ -0,0 +1,89 @@
+package duke;
+
+import duke.commands.Command;
+import duke.commands.ExitCommand;
+import duke.exceptions.DukeException;
+import duke.parser.Parser;
+import duke.storage.Storage;
+import duke.tasks.*;
+import duke.ui.TextUI;
+import duke.ui.gui.DialogBox;
+import javafx.application.Application;
+import javafx.scene.Scene;
+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.Region;
+import javafx.scene.layout.VBox;
+import javafx.stage.Stage;
+
+
+
+/**
+ * Entry point of DUke.
+ * Initializes the application and initiates contact with user.
+ */
+public class Duke {
+
+ private static TaskList taskList;
+ private static Storage storage;
+ private static TextUI textUI;
+ private static Parser parser;
+
+ public Duke() {
+ initialize();
+ }
+
+ public static void main(String[] args) {
+ initialize();
+ run();
+ }
+
+ private static void initialize() {
+ textUI = new TextUI();
+ storage = new Storage();
+ parser = new Parser();
+ try {
+ taskList = Storage.loadTasklist();
+ } catch (DukeException e) {
+ taskList = new TaskList();
+ System.out.println(e.getMessage());
+ }
+ Command.defineTaskList(taskList);
+ }
+
+ private static void run() {
+ assert taskList != null : "TaskList failed to initialize";
+ textUI.printWelcomeMessage();
+ do {
+ String userInputCommand = textUI.getUserCommand();
+ try {
+ Command currCommand = parser.parseCommands(userInputCommand);
+ textUI.printMessage(currCommand.execute());
+ storage.saveTasklist(taskList);
+ } catch (DukeException e) {
+ textUI.printMessage(e.getMessage());
+ }
+ } while (ExitCommand.isRunning());
+ }
+
+ /**
+ * You should have your own function to generate a response to user input.
+ * Replace this stub with your completed method.
+ */
+ public String getResponse(String input) {
+ assert taskList != null : "TaskList failed to initialize";
+ try {
+ Command currCommand = parser.parseCommands(input);
+ String outputToUser = currCommand.execute();
+ storage.saveTasklist(taskList);
+ return outputToUser;
+ } catch (DukeException e) {
+ return e.getMessage();
+ }
+ }
+}
diff --git a/src/main/java/duke/Launcher.java b/src/main/java/duke/Launcher.java
new file mode 100644
index 0000000000..d4b928a05e
--- /dev/null
+++ b/src/main/java/duke/Launcher.java
@@ -0,0 +1,12 @@
+package duke;
+
+import javafx.application.Application;
+
+/**
+ * A launcher class to workaround classpath issues.
+ */
+public class Launcher {
+ public static void main(String[] args) {
+ Application.launch(duke.ui.gui.Main.class, args);
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/duke/commands/AddCommand.java b/src/main/java/duke/commands/AddCommand.java
new file mode 100644
index 0000000000..499937115c
--- /dev/null
+++ b/src/main/java/duke/commands/AddCommand.java
@@ -0,0 +1,55 @@
+package duke.commands;
+
+import duke.tasks.Task;
+import duke.tasks.Deadline;
+import duke.tasks.Event;
+import duke.tasks.Todo;
+import java.time.LocalDateTime;
+
+/**
+ * Command that adds a Task to the TaskList.
+ */
+
+public class AddCommand extends Command{
+
+ private String taskType;
+ private String content;
+ private LocalDateTime datetime;
+
+ /**
+ * Constructor for AddCommand.
+ *
+ * @param taskType string representing subclass of Task to add.
+ * @param content description of Task to add.
+ * @param datetime date and time of task to add.
+ */
+ public AddCommand(String taskType, String content, LocalDateTime datetime) {
+ this.taskType = taskType;
+ this.content = content;
+ this.datetime = datetime;
+ }
+
+ /**
+ * Creates respective Task depending on taskType which is then added to the TaskList.
+ *
+ * @return Message for completing the command which is displayed to user.
+ */
+ @Override
+ public String execute() {
+ Task newTask = null;
+ switch (taskType.toLowerCase()) {
+ case "todo":
+ newTask = new Todo(content);
+ break;
+ case "deadline":
+ newTask = new Deadline(content, datetime);
+ break;
+ case "event":
+ newTask = new Event(content, datetime);
+ break;
+ }
+ taskList.addTask(newTask);
+ return String.format("This task has been added as requested:\n" +
+ "%s\n" + "You now have %d item(s) in your list", newTask, taskList.size());
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/duke/commands/Command.java b/src/main/java/duke/commands/Command.java
new file mode 100644
index 0000000000..ca67429e36
--- /dev/null
+++ b/src/main/java/duke/commands/Command.java
@@ -0,0 +1,32 @@
+package duke.commands;
+
+import duke.exceptions.DukeException;
+import duke.exceptions.DukeUnsupportedOperationException;
+import duke.tasks.TaskList;
+
+/**
+ * Parent class of all duke commands.
+ */
+public class Command {
+
+ static TaskList taskList;
+
+ /**
+ * Defines the TaskList shared with all commands.
+ *
+ * @param taskList TaskList defined and used by all commands.
+ */
+ public static void defineTaskList(TaskList taskList) {
+ Command.taskList = taskList;
+ }
+
+ /**
+ * Carries out the command.
+ *
+ * @return Message for completing the command which is displayed to user.
+ * @throws DukeException If arguments passed to the commands are invalid.
+ */
+ public String execute() throws DukeException{
+ throw new DukeUnsupportedOperationException("This method is to be implemented by child classes");
+ }
+}
diff --git a/src/main/java/duke/commands/DeleteCommand.java b/src/main/java/duke/commands/DeleteCommand.java
new file mode 100644
index 0000000000..2774e7e92d
--- /dev/null
+++ b/src/main/java/duke/commands/DeleteCommand.java
@@ -0,0 +1,38 @@
+package duke.commands;
+
+import duke.exceptions.DukeInvalidArgumentException;
+import duke.tasks.Task;
+
+/**
+ * Command that deletes a Task from TaskList.
+ */
+public class DeleteCommand extends Command {
+
+ private int index;
+
+ /**
+ * Constructor for DeleteCommand.
+ *
+ * @param index index of Task to delete.
+ */
+ public DeleteCommand(int index) {
+ this.index = index;
+ }
+
+ /**
+ * Deletes Task from TaskList
+ *
+ * @return Message for completing the command which is displayed to user.
+ * @throws DukeInvalidArgumentException If index is not within TaskList.
+ */
+ @Override
+ public String execute() throws DukeInvalidArgumentException {
+ if (index > taskList.size() - 1 || index < 0) {
+ throw new DukeInvalidArgumentException("I am afraid that's an invalid task!" +
+ "Please check your task number");
+ }
+ Task deletedTask = taskList.deleteTask(index);
+ return String.format("As you wish. The following task has been removed:\n" +
+ "%s\n" + "You now have %d item(s) in your list", deletedTask, taskList.size());
+ }
+}
diff --git a/src/main/java/duke/commands/ExitCommand.java b/src/main/java/duke/commands/ExitCommand.java
new file mode 100644
index 0000000000..162d2fd68e
--- /dev/null
+++ b/src/main/java/duke/commands/ExitCommand.java
@@ -0,0 +1,29 @@
+package duke.commands;
+
+/**
+ * Command that indicates exit of program.
+ */
+public class ExitCommand extends Command{
+
+ private static boolean isProgramRunning = true;
+
+ /**
+ * returns true if the program should be running and false otherwise.
+ *
+ * @return whether program should be running.
+ */
+ public static boolean isRunning() {
+ return isProgramRunning;
+ }
+
+ /**
+ * Sets isProgramRunning to false.
+ *
+ * @return Message for completing the command which is displayed to user.
+ */
+ @Override
+ public String execute() {
+ isProgramRunning = false;
+ return "Till we meet again";
+ }
+}
diff --git a/src/main/java/duke/commands/FindCommand.java b/src/main/java/duke/commands/FindCommand.java
new file mode 100644
index 0000000000..ec36de50c5
--- /dev/null
+++ b/src/main/java/duke/commands/FindCommand.java
@@ -0,0 +1,39 @@
+package duke.commands;
+
+import duke.tasks.TaskList;
+
+/**
+ * Command that finds a string in Tasks in TaskList.
+ */
+public class FindCommand extends Command {
+
+ private String searchStr;
+
+ /**
+ * Constructor for FindCommand.
+ *
+ * @param searchStr string to be searched in Task content.
+ */
+ public FindCommand(String searchStr) {
+ this.searchStr = searchStr.toLowerCase();
+ }
+
+ /**
+ * Iterates through each Task in TaskList and searches for given string.
+ *
+ * @return List of Task found according to search.
+ */
+ @Override
+ public String execute() {
+ TaskList foundTasks = new TaskList();
+ for (int i = 0; i < taskList.size(); i++) {
+ if (taskList.getTaskContent(i).toLowerCase().contains(searchStr)) {
+ foundTasks.addTask(taskList.getTask(i));
+ }
+ }
+ if (foundTasks.size() == 0) {
+ return "Apologies, no task matches what you are looking for";
+ }
+ return String.format("These are the results of my search:\n%s", foundTasks.toString());
+ }
+}
diff --git a/src/main/java/duke/commands/InvalidCommand.java b/src/main/java/duke/commands/InvalidCommand.java
new file mode 100644
index 0000000000..3a5ec1a083
--- /dev/null
+++ b/src/main/java/duke/commands/InvalidCommand.java
@@ -0,0 +1,17 @@
+package duke.commands;
+
+/**
+ * Command for any unknown commands.
+ */
+public class InvalidCommand extends Command{
+
+ /**
+ * Returns String to tell user command is unknown.
+ *
+ * @return Message for completing the command which is displayed to user.
+ */
+ @Override
+ public String execute(){
+ return "I am unable to comprehend your request. Please try again";
+ }
+}
diff --git a/src/main/java/duke/commands/ListCommand.java b/src/main/java/duke/commands/ListCommand.java
new file mode 100644
index 0000000000..a783c0c5a3
--- /dev/null
+++ b/src/main/java/duke/commands/ListCommand.java
@@ -0,0 +1,17 @@
+package duke.commands;
+
+/**
+ * Command that displays all Task in TaskList.
+ */
+public class ListCommand extends Command {
+
+ /**
+ * Checks if TaskList is empty, otherwise displays all Task.
+ *
+ * @return All Task in TaskList and message for completing the command which is displayed to user.
+ */
+ @Override
+ public String execute() {
+ return taskList.toString();
+ }
+}
diff --git a/src/main/java/duke/commands/MarkCommand.java b/src/main/java/duke/commands/MarkCommand.java
new file mode 100644
index 0000000000..851f2e0262
--- /dev/null
+++ b/src/main/java/duke/commands/MarkCommand.java
@@ -0,0 +1,38 @@
+package duke.commands;
+
+import duke.exceptions.DukeInvalidArgumentException;
+import duke.tasks.Task;
+
+/**
+ * Command that marks Task from TaskList as done.
+ */
+public class MarkCommand extends Command {
+
+ private int index;
+
+ /**
+ * Constructor for MarkCommand.
+ *
+ * @param index index of Task to mark.
+ */
+ public MarkCommand(int index) {
+ this.index = index;
+ }
+
+ /**
+ * Marks Task from TaskList as done.
+ *
+ * @return Message for completing the command which is displayed to user.
+ * @throws DukeInvalidArgumentException If index is not within TaskList.
+ */
+ @Override
+ public String execute() throws DukeInvalidArgumentException {
+ if (index > taskList.size() - 1 || index < 0) {
+ throw new DukeInvalidArgumentException("I am afraid that's an invalid task!" +
+ " Please check your task number");
+ }
+ Task markedTask = taskList.markTask(index);
+ return String.format("Duly noted. The following task has been marked as done:\n" +
+ "%s", markedTask);
+ }
+}
diff --git a/src/main/java/duke/commands/SortCommand.java b/src/main/java/duke/commands/SortCommand.java
new file mode 100644
index 0000000000..fe3be2e84b
--- /dev/null
+++ b/src/main/java/duke/commands/SortCommand.java
@@ -0,0 +1,50 @@
+package duke.commands;
+
+import duke.tasks.*;
+
+import java.util.PriorityQueue;
+
+/**
+ * Command that sorts and displays all Task in TaskList.
+ */
+public class SortCommand extends Command {
+ private PriorityQueue deadlineList = new PriorityQueue<>(new TaskDateComparator());
+ private PriorityQueue eventList = new PriorityQueue<>(new TaskDateComparator());
+ private PriorityQueue todoList = new PriorityQueue<>(new TaskDateComparator());
+
+ @Override
+ public String execute() {
+ initializeQueues();
+ String sortedDeadlines = "These are your upcoming Deadlines: \n" + priorityQueueStringFormat(deadlineList);
+ String sortedEvents = "These are your upcoming Events: \n" + priorityQueueStringFormat(eventList);
+ String sortedTodos = "These are your upcoming Todos: \n" + priorityQueueStringFormat(todoList);
+ return sortedDeadlines + "\n" + sortedEvents + "\n" + sortedTodos;
+ }
+
+ private static String priorityQueueStringFormat(PriorityQueue extends Task> pq) {
+ if(pq.isEmpty()) {
+ return "You have no such tasks\n";
+ }
+
+ int index = 1;
+ StringBuilder numberedPriorityQueue = new StringBuilder();
+ while (!pq.isEmpty()) {
+ numberedPriorityQueue.append(String.format("%d: %s\n", index, pq.poll().toString()));
+ index++;
+ }
+ return numberedPriorityQueue.toString();
+ }
+
+ private void initializeQueues() {
+ for (int i = 0; i < taskList.size(); i++) {
+ Task currTask = taskList.getTask(i);
+ if (currTask instanceof Deadline) {
+ deadlineList.add((Deadline) currTask);
+ } else if (currTask instanceof Event) {
+ eventList.add((Event) currTask);
+ } else {
+ todoList.add((Todo) currTask);
+ }
+ }
+ }
+}
diff --git a/src/main/java/duke/commands/UnmarkCommand.java b/src/main/java/duke/commands/UnmarkCommand.java
new file mode 100644
index 0000000000..ad2cf99bd5
--- /dev/null
+++ b/src/main/java/duke/commands/UnmarkCommand.java
@@ -0,0 +1,38 @@
+package duke.commands;
+
+import duke.exceptions.DukeInvalidArgumentException;
+import duke.tasks.Task;
+
+/**
+ * Command that unmarks a Task from TaskList
+ */
+public class UnmarkCommand extends Command {
+
+ private int index;
+
+ /**
+ * Constructor for UnmarkCommand.
+ *
+ * @param index index of Task to unmark.
+ */
+ public UnmarkCommand(int index) {
+ this.index = index;
+ }
+
+ /**
+ * Unmarks Task from TaskList.
+ *
+ * @return Message for completing the command which is displayed to user.
+ * @throws DukeInvalidArgumentException If index is not within TaskList.
+ */
+ @Override
+ public String execute() throws DukeInvalidArgumentException {
+ if (index > taskList.size() - 1 || index < 0) {
+ throw new DukeInvalidArgumentException("I am afraid that's an invalid task!" +
+ " Please check your task number");
+ }
+ Task unmarkedTask = taskList.unmarkTask(index);
+ return String.format("Very well. The following task has been marked as not done:" +
+ "%s", unmarkedTask);
+ }
+}
diff --git a/src/main/java/duke/exceptions/DukeException.java b/src/main/java/duke/exceptions/DukeException.java
new file mode 100644
index 0000000000..e9ec37397e
--- /dev/null
+++ b/src/main/java/duke/exceptions/DukeException.java
@@ -0,0 +1,16 @@
+package duke.exceptions;
+
+/**
+ * Parent class of all duke exceptions.
+ */
+public class DukeException extends Exception {
+
+ /**
+ * Constructor for DukeException.
+ *
+ * @param message Contains information of the exception.
+ */
+ public DukeException(String message) {
+ super(message);
+ }
+}
diff --git a/src/main/java/duke/exceptions/DukeInvalidArgumentException.java b/src/main/java/duke/exceptions/DukeInvalidArgumentException.java
new file mode 100644
index 0000000000..7ca7ff703a
--- /dev/null
+++ b/src/main/java/duke/exceptions/DukeInvalidArgumentException.java
@@ -0,0 +1,16 @@
+package duke.exceptions;
+
+/**
+ * DukeException for invalid arguments inputted.
+ */
+public class DukeInvalidArgumentException extends DukeException{
+
+ /**
+ * Constructor for DukeInvalidArgumentException.
+ *
+ * @param message Contains information of the exception.
+ */
+ public DukeInvalidArgumentException(String message) {
+ super(message);
+ }
+}
diff --git a/src/main/java/duke/exceptions/DukeUnsupportedOperationException.java b/src/main/java/duke/exceptions/DukeUnsupportedOperationException.java
new file mode 100644
index 0000000000..336b1b3c59
--- /dev/null
+++ b/src/main/java/duke/exceptions/DukeUnsupportedOperationException.java
@@ -0,0 +1,16 @@
+package duke.exceptions;
+
+/**
+ * DukeException for operations that cannot be carried out.
+ */
+public class DukeUnsupportedOperationException extends DukeException{
+
+ /**
+ * Constructor for DukeUnsupportedOperationException.
+ *
+ * @param message Contains information of the exception.
+ */
+ public DukeUnsupportedOperationException(String message) {
+ super(message);
+ }
+}
diff --git a/src/main/java/duke/parser/Parser.java b/src/main/java/duke/parser/Parser.java
new file mode 100644
index 0000000000..4ba5b74ef6
--- /dev/null
+++ b/src/main/java/duke/parser/Parser.java
@@ -0,0 +1,112 @@
+package duke.parser;
+
+import duke.commands.*;
+import duke.exceptions.DukeInvalidArgumentException;
+import duke.tasks.Deadline;
+import duke.tasks.Event;
+import duke.tasks.Task;
+import duke.tasks.Todo;
+
+import java.time.LocalDateTime;
+import java.time.format.DateTimeFormatter;
+import java.time.format.DateTimeParseException;
+
+/**
+ * Parses user inputs for commands.
+ */
+public class Parser {
+
+ /**
+ * Parses user input for specified commands.
+ *
+ * @param userInput raw input from user.
+ * @return Command parsed from user input.
+ * @throws DukeInvalidArgumentException If user input invalid arguments.
+ */
+ public Command parseCommands(String userInput) throws DukeInvalidArgumentException {
+ String[] parsedUserInput = userInput.split(" ", 2);
+ String commandType = parsedUserInput[0].toLowerCase();
+ switch (commandType) {
+ case "bye":
+ return new ExitCommand();
+ case "list":
+ return new ListCommand();
+ case "sort":
+ return new SortCommand();
+ case "mark":
+ return new MarkCommand(parseIndex(parsedUserInput[1]));
+ case "unmark":
+ return new UnmarkCommand(parseIndex(parsedUserInput[1]));
+ case "delete":
+ return new DeleteCommand(parseIndex(parsedUserInput[1]));
+ case "find":
+ return new FindCommand(parsedUserInput[1].trim());
+ case "todo":
+ String[] todoParsedArguments = parseArguments(parsedUserInput);
+ String content = todoParsedArguments[0];
+ return new AddCommand(commandType, content, null);
+ case "deadline":
+ case "event":
+ String[] parsedArguments = parseArguments(parsedUserInput);
+ content = parsedArguments[0];
+ LocalDateTime datetime = parseDateTime(parsedArguments[1]);
+ return new AddCommand(commandType, content, datetime);
+ default:
+ return new InvalidCommand();
+ }
+ }
+
+ private int parseIndex(String strIndex) throws DukeInvalidArgumentException {
+ try {
+ return Integer.parseInt(strIndex.trim()) - 1;
+ } catch (NumberFormatException e) {
+ throw new DukeInvalidArgumentException("Please input an integer as index");
+ }
+ }
+
+ private String[] parseArguments(String[] arguments) throws DukeInvalidArgumentException {
+ if (arguments.length < 2) {
+ throw new DukeInvalidArgumentException("There appears to be insufficient arguments");
+ }
+ return arguments[1].split(" /([Aa][Tt]|[Bb][Yy]) ", 2);
+ }
+
+ private LocalDateTime parseDateTime(String datetime) throws DukeInvalidArgumentException {
+ try{
+ DateTimeFormatter datetimePattern = DateTimeFormatter.ofPattern("dd/MM/yyyy HH:mm");
+ return LocalDateTime.parse(datetime, datetimePattern);
+ } catch (DateTimeParseException e) {
+ throw new DukeInvalidArgumentException("Pardon me. Please input date in the format DD/MM/YYYY HH:MM");
+ }
+ }
+
+ /**
+ * Parses date and time from data in savefile.
+ *
+ * @param datetime date and time to be parsed.
+ * @return LocalDateTime object based on given date time.
+ */
+ public LocalDateTime parseSaveDateTime(String datetime) {
+ DateTimeFormatter datetimePattern = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm");
+ return LocalDateTime.parse(datetime, datetimePattern);
+ }
+
+ public Task parseSavedTask(String currSaveLine) {
+ Task currentTask = null;
+ switch (currSaveLine.charAt(0)) {
+ case 'T' : {
+ currentTask = Todo.createFromData(currSaveLine);
+ break;
+ }
+ case 'E' : {
+ currentTask = Event.createFromData(currSaveLine);
+ break;
+ }
+ case 'D' : {
+ currentTask = Deadline.createFromData(currSaveLine);
+ break;
+ }
+ }
+ return currentTask;
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/duke/storage/Storage.java b/src/main/java/duke/storage/Storage.java
new file mode 100644
index 0000000000..e4ca4da538
--- /dev/null
+++ b/src/main/java/duke/storage/Storage.java
@@ -0,0 +1,78 @@
+package duke.storage;
+
+import duke.exceptions.DukeException;
+import duke.parser.Parser;
+import duke.tasks.*;
+import java.io.BufferedReader;
+import java.io.FileReader;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Paths;
+import java.nio.file.Path;
+
+
+/**
+ * This class saves and loads data from save file.
+ */
+public class Storage {
+
+ private static final Path DATA_PATH = Paths.get("data", "duke.txt");
+
+ /**
+ * Writes TaskList data to save file.
+ *
+ * @param taskList TaskList to write to save file.
+ * @throws DukeException If there is an error writing to save file.
+ */
+ public void saveTasklist(TaskList taskList) throws DukeException {
+ assert taskList != null : "Null tasklist to save";
+ initialiseSaveFile();
+ String dataToWrite = taskList.toSaveData();
+ try {
+ FileWriter saveFileWriter = new FileWriter(DATA_PATH.toString(), false);
+ saveFileWriter.write(dataToWrite);
+ saveFileWriter.close();
+ } catch (IOException e) {
+ throw new DukeException("Unable to write into save file");
+ }
+ }
+
+ /**
+ * Loads TaskList data from save file.
+ *
+ * @return new TaskList recreated from data.
+ * @throws DukeException If data cannot be loaded from save file.
+ */
+ public static TaskList loadTasklist() throws DukeException{
+ initialiseSaveFile();
+ String strCurrentLine;
+ Parser parser = new Parser();
+ TaskList taskList = new TaskList();
+ try {
+ BufferedReader saveFilereader = new BufferedReader(new FileReader(DATA_PATH.toString()));
+ while ((strCurrentLine = saveFilereader.readLine()) != null) {
+ Task currentTask = parser.parseSavedTask(strCurrentLine);
+ taskList.addTask(currentTask);
+ }
+ saveFilereader.close();
+ } catch (IOException e) {
+ throw new DukeException("Unable to load save file");
+ }
+ return taskList;
+ }
+
+ private static void initialiseSaveFile() throws DukeException {
+ try {
+ if (Files.notExists(DATA_PATH)) {
+ if(Files.notExists(DATA_PATH.getParent())) {
+ Files.createDirectory(DATA_PATH.getParent());
+ }
+ Files.createFile(DATA_PATH);
+ }
+ } catch (IOException e) {
+ throw new DukeException("Unable to create a save file");
+ }
+ }
+}
+
diff --git a/src/main/java/duke/tasks/Deadline.java b/src/main/java/duke/tasks/Deadline.java
new file mode 100644
index 0000000000..7f746887b1
--- /dev/null
+++ b/src/main/java/duke/tasks/Deadline.java
@@ -0,0 +1,64 @@
+package duke.tasks;
+
+import duke.parser.Parser;
+
+import java.time.LocalDateTime;
+
+/**
+ * Deadlines are tasks due on a certain date.
+ */
+public class Deadline extends Task{
+
+ private LocalDateTime date;
+
+ /**
+ * Constructor for Deadline.
+ *
+ * @param content Description of Deadline.
+ * @param date date and time Deadline is due.
+ */
+ public Deadline(String content, LocalDateTime date) {
+ super(content);
+ this.date = date;
+ }
+
+ @Override
+ public LocalDateTime getDate() {
+ return this.date;
+ }
+
+ /**
+ * Adds Deadline indicator at the front and the date time to the back of Task string.
+ *
+ * @return Formatted string representation of Deadline.
+ */
+ @Override
+ public String toString() {
+ return String.format("[D]%s (by: %s)", super.toString(), this.date.toString().replace("T", " "));
+ }
+
+ /**
+ * Adds Deadline indicator and date time to save data.
+ *
+ * @return Data to write into save file.
+ */
+ public String toSaveData() {
+ return String.format("D|%s|%s\n", super.toSaveData(), this.date.toString().replace("T", " "));
+ }
+
+ /**
+ * Recreate Deadline from save data.
+ *
+ * @param savedData Deadline Data in save file.
+ * @return Deadline that was saved.
+ */
+ public static Deadline createFromData(String savedData) {
+ Parser parser = new Parser();
+ String[] parsedSavedData = savedData.split("\\|");
+ Deadline newDeadline = new Deadline(parsedSavedData[2], parser.parseSaveDateTime(parsedSavedData[3]));
+ if (parsedSavedData[1].equals("1")) {
+ newDeadline.markAsDone();
+ }
+ return newDeadline;
+ }
+}
diff --git a/src/main/java/duke/tasks/Event.java b/src/main/java/duke/tasks/Event.java
new file mode 100644
index 0000000000..ff6afcf2c2
--- /dev/null
+++ b/src/main/java/duke/tasks/Event.java
@@ -0,0 +1,64 @@
+package duke.tasks;
+
+import duke.parser.Parser;
+
+import java.time.LocalDateTime;
+
+/**
+ * Events are tasks happening at a point in time.
+ */
+public class Event extends Task {
+
+ private LocalDateTime date;
+
+ /**
+ * Constructor for Event.
+ *
+ * @param content Description of Event.
+ * @param date date and time of Event.
+ */
+ public Event(String content, LocalDateTime date) {
+ super(content);
+ this.date = date;
+ }
+
+ @Override
+ public LocalDateTime getDate() {
+ return this.date;
+ }
+
+ /**
+ * Adds Event indicator at the front and the date time to the back of Task string.
+ *
+ * @return Formatted string representation of Event.
+ */
+ @Override
+ public String toString() {
+ return String.format("[E]%s (at: %s)", super.toString(), this.date.toString().replace("T", " "));
+ }
+
+ /**
+ * Adds Event indicator and date time to save data.
+ *
+ * @return Data to write into save file.
+ */
+ public String toSaveData() {
+ return String.format("E|%s|%s\n", super.toSaveData(), this.date.toString().replace("T", " "));
+ }
+
+ /**
+ * Recreate Event from save data.
+ *
+ * @param savedData Event Data in save file.
+ * @return Event that was saved.
+ */
+ public static Event createFromData(String savedData) {
+ Parser parser = new Parser();
+ String[] parsedSavedData = savedData.split("\\|");
+ Event newEvent = new Event(parsedSavedData[2], parser.parseSaveDateTime(parsedSavedData[3]));
+ if (parsedSavedData[1].equals("1")) {
+ newEvent.markAsDone();
+ }
+ return newEvent;
+ }
+}
diff --git a/src/main/java/duke/tasks/Task.java b/src/main/java/duke/tasks/Task.java
new file mode 100644
index 0000000000..665a5ae982
--- /dev/null
+++ b/src/main/java/duke/tasks/Task.java
@@ -0,0 +1,73 @@
+package duke.tasks;
+
+import duke.exceptions.DukeUnsupportedOperationException;
+
+import java.time.LocalDateTime;
+
+/**
+ * Parent Class of all duke tasks.
+ */
+public class Task {
+
+ private String content;
+ private boolean isDone = false;
+
+ /**
+ * Constructor for Task.
+ *
+ * @param content Description of Task.
+ */
+ public Task(String content) {
+ this.content = content;
+ }
+
+ /**
+ * Marks task as done.
+ */
+ public void markAsDone() {
+ this.isDone = true;
+ }
+
+ /**
+ * Unmarks task as not done.
+ */
+ public void markAsNotDone() {
+ this.isDone = false;
+ }
+
+ /**
+ * Returns content of the Task.
+ *
+ * @return content of the Task.
+ */
+ public String getContent() {
+ return content;
+ }
+
+ public LocalDateTime getDate() {
+ return LocalDateTime.MIN;
+ }
+
+ /**
+ * Formats Task indicating done tasks with an X followed by content of task.
+ *
+ * @return Formatted string representation of task.
+ */
+ @Override
+ public String toString() {
+ String markedDoneIndicator = this.isDone ? "X" : " ";
+ return String.format("[%s] %s", markedDoneIndicator, this.content);
+ }
+
+ /**
+ * Formats Task to write into save file.
+ * Marked done tasks are denoted by 1 and 0 otherwise.
+ *
+ * @return Data to write into save file.
+ */
+ public String toSaveData() {
+ String markedDoneIndicator = this.isDone ? "1" : "0";
+ return String.format("%s|%s", markedDoneIndicator, this.content);
+ }
+
+}
diff --git a/src/main/java/duke/tasks/TaskDateComparator.java b/src/main/java/duke/tasks/TaskDateComparator.java
new file mode 100644
index 0000000000..e45a19ff49
--- /dev/null
+++ b/src/main/java/duke/tasks/TaskDateComparator.java
@@ -0,0 +1,22 @@
+package duke.tasks;
+
+import duke.exceptions.DukeUnsupportedOperationException;
+
+import java.time.LocalDateTime;
+import java.util.Comparator;
+
+public class TaskDateComparator implements Comparator {
+
+ public int compare(Task task1, Task task2) {
+ LocalDateTime task1Date = task1.getDate();
+ LocalDateTime task2Date = task2.getDate();
+
+ if (task1Date.isEqual(task2Date)) {
+ return 0;
+ } else if (task1Date.isBefore(task2Date)) {
+ return -1;
+ } else {
+ return 1;
+ }
+ }
+}
diff --git a/src/main/java/duke/tasks/TaskList.java b/src/main/java/duke/tasks/TaskList.java
new file mode 100644
index 0000000000..95cdf5f440
--- /dev/null
+++ b/src/main/java/duke/tasks/TaskList.java
@@ -0,0 +1,126 @@
+package duke.tasks;
+
+import java.util.ArrayList;
+
+/**
+ * ArrayList of Task objects.
+ */
+public class TaskList {
+
+ private final ArrayList taskList;
+
+ /**
+ * Constructor for TaskList.
+ */
+ public TaskList() {
+ this.taskList = new ArrayList<>();
+ }
+
+ /**
+ * Get task at given index.
+ *
+ * @param index Index of task to get.
+ * @return Task at given index.
+ */
+ public Task getTask(int index) {
+ assert index < taskList.size() && index >= 0 : "TaskList out of bounds";
+ return taskList.get(index);
+ }
+
+ /**
+ * Get content of task at given index.
+ *
+ * @param index Index of task to get content.
+ * @return Content of task at given index.
+ */
+ public String getTaskContent(int index) {
+ assert index < taskList.size() && index >= 0 : "TaskList out of bounds";
+ return taskList.get(index).getContent();
+ }
+
+ /**
+ * Adds Task to TaskList.
+ *
+ * @param taskToAdd Task to add to TaskList.
+ */
+ public void addTask(Task taskToAdd) {
+ taskList.add(taskToAdd);
+ }
+
+ /**
+ * Deletes Task from Tasklist specified by given index.
+ *
+ * @param indexTaskToDelete Index of Task to delete.
+ * @return Deleted task.
+ */
+ public Task deleteTask(int indexTaskToDelete) {
+ assert indexTaskToDelete < taskList.size() && indexTaskToDelete >= 0 : "TaskList out of bounds";
+ return taskList.remove(indexTaskToDelete);
+ }
+
+ /**
+ * Marks Task from TaskList specified by given index.
+ *
+ * @param indexTaskToMark Index of Task to mark.
+ * @return Marked task
+ */
+ public Task markTask(int indexTaskToMark) {
+ assert indexTaskToMark < taskList.size() && indexTaskToMark >= 0 : "TaskList out of bounds";
+ Task taskToMark = taskList.get(indexTaskToMark);
+ taskToMark.markAsDone();
+ return taskToMark;
+ }
+
+ /**
+ * Unmarks Task from TaskList specified by given index.
+ *
+ * @param indexTaskToUnmark Index of Task to Unmark.
+ * @return Unmarked task
+ */
+ public Task unmarkTask(int indexTaskToUnmark) {
+ assert indexTaskToUnmark < taskList.size() && indexTaskToUnmark >= 0 : "TaskList out of bounds";
+ Task taskToUnmark = taskList.get(indexTaskToUnmark);
+ taskToUnmark.markAsNotDone();
+ return taskToUnmark;
+ }
+
+ /**
+ * Returns number of tasks in TaskList.
+ *
+ * @return size of TaskList.
+ */
+ public int size() {
+ return taskList.size();
+ }
+
+ /**
+ * Formats each Task in TaskList to write into save file.
+ *
+ * @return Data to write into save file.
+ */
+ public String toSaveData() {
+ StringBuilder dataToWrite = new StringBuilder();
+ for (Task task : taskList) {
+ dataToWrite.append(task.toSaveData());
+ }
+ return dataToWrite.toString();
+ }
+
+ /**
+ * Returns string indicating empty TaskList if empty.
+ * Else return numbered list of task.
+ *
+ * @return String representation of TaskList.
+ */
+ @Override
+ public String toString() {
+ if (taskList.size() == 0) {
+ return "Your current task list is empty";
+ }
+ StringBuilder numberedTaskList = new StringBuilder();
+ for (int i = 0; i < taskList.size(); i++) {
+ numberedTaskList.append(String.format("%d. %s \n", i + 1, taskList.get(i)));
+ }
+ return numberedTaskList.toString().trim();
+ }
+}
diff --git a/src/main/java/duke/tasks/Todo.java b/src/main/java/duke/tasks/Todo.java
new file mode 100644
index 0000000000..ca8148d67a
--- /dev/null
+++ b/src/main/java/duke/tasks/Todo.java
@@ -0,0 +1,57 @@
+package duke.tasks;
+
+import java.time.LocalDateTime;
+
+/**
+ * Todos are tasks that have no dates involved.
+ */
+public class Todo extends Task{
+
+ /**
+ * Constructor for Todo.
+ *
+ * @param content Description of Todo.
+ */
+ public Todo(String content) {
+ super(content);
+ }
+
+ @Override
+ public LocalDateTime getDate() {
+ return LocalDateTime.MIN;
+ }
+
+ /**
+ * Adds Todo indicator to the front of Task string.
+ *
+ * @return Formatted string representation of Event.
+ */
+ @Override
+ public String toString() {
+ return String.format("[T]%s", super.toString());
+ }
+
+ /**
+ * Adds Todo indicator and date time to save data.
+ *
+ * @return Data to write into save file.
+ */
+ public String toSaveData() {
+ return String.format("T|%s\n",super.toSaveData());
+ }
+
+ /**
+ * Recreate Todo from save data.
+ *
+ * @param savedData Todo Data in save file.
+ * @return Todo that was saved.
+ */
+ public static Todo createFromData(String savedData) {
+ String[] parsedSavedData = savedData.split("\\|");
+ Todo newTodo = new Todo(parsedSavedData[2]);
+ if (parsedSavedData[1].equals("1")) {
+ newTodo.markAsDone();
+ }
+ return newTodo;
+ }
+}
diff --git a/src/main/java/duke/ui/TextUI.java b/src/main/java/duke/ui/TextUI.java
new file mode 100644
index 0000000000..8eea1aab43
--- /dev/null
+++ b/src/main/java/duke/ui/TextUI.java
@@ -0,0 +1,86 @@
+package duke.ui;
+
+import java.io.InputStream;
+import java.io.PrintStream;
+import java.util.Scanner;
+
+/**
+ * Class that handles input and output with user.
+ */
+public class TextUI {
+
+ private final Scanner in;
+ private final PrintStream out;
+
+ /**
+ * Constructor for TextUI.
+ *
+ * @param in To receive user input by scanning.
+ * @param out output to print to.
+ */
+ public TextUI(InputStream in, PrintStream out){
+ this.in = new Scanner(in);
+ this.out = out;
+ }
+
+ /**
+ * Constructor for TextUI with default System in and out.
+ */
+ public TextUI() {
+ this(System.in, System.out);
+ }
+
+ /**
+ * Gets next line entered by user.
+ * Ignores empty lines.
+ *
+ * @return raw user input
+ */
+ public String getUserCommand() {
+ String currInput = in.nextLine().trim();
+ while (shouldIgnore(currInput)) {
+ currInput = in.nextLine().trim();
+ }
+ return currInput;
+ }
+
+ private boolean shouldIgnore(String rawInputLine) {
+ return rawInputLine.trim().isEmpty();
+ }
+
+ /**
+ * Formats the message with borders and prints it.
+ *
+ * @param message Message to be printed.
+ */
+ public void printMessage(String message) {
+ assert !message.isEmpty() : "No message to print";
+ String textBorder = "+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++\n";
+ String textToPrint = "\n" + textBorder + message + "\n" + textBorder + "\n";
+ out.print(textToPrint);
+ }
+
+ /**
+ * Generates and print the welcome message upon the start of the application.
+ */
+ public void printWelcomeMessage() {
+ String logo = " ____ _ \n"
+ + "| _ \\ _ _| | _____ \n"
+ + "| | | | | | | |/ / _ \\\n"
+ + "| |_| | |_| | < __/\n"
+ + "|____/ \\__,_|_|\\_\\___|\n";
+ out.print("\n+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++");
+ out.println("\nGreetings from\n" + logo);
+ out.println("How may I assist you?");
+ out.print("+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++\n\n");
+ }
+
+ /**
+ * Generates the welcome message upon the start of the application.
+ *
+ * @return String of welcome message.
+ */
+ public String getWelcomeMessage() {
+ return "Greetings from Duke. \nHow may I assist you today?";
+ }
+}
diff --git a/src/main/java/duke/ui/gui/DialogBox.java b/src/main/java/duke/ui/gui/DialogBox.java
new file mode 100644
index 0000000000..47aebe3a88
--- /dev/null
+++ b/src/main/java/duke/ui/gui/DialogBox.java
@@ -0,0 +1,64 @@
+package duke.ui.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;
+import javafx.scene.layout.Region;
+
+/**
+ * This control represents a dialog box consisting of an Image to represent the speaker's face and a String
+ * 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);
+ }
+
+ public static DialogBox getUserDialog(String text, Image img) {
+ DialogBox newdb = new DialogBox(text, img);
+ newdb.setMinHeight(Region.USE_PREF_SIZE);
+ return newdb;
+ }
+
+ public static DialogBox getDukeDialog(String text, Image img) {
+ var db = new DialogBox(text, img);
+ db.flip();
+ db.setMinHeight(Region.USE_PREF_SIZE);
+ return db;
+ }
+
+ /**
+ * Flips the dialog box such that the Image 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);
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/duke/ui/gui/Main.java b/src/main/java/duke/ui/gui/Main.java
new file mode 100644
index 0000000000..6560506836
--- /dev/null
+++ b/src/main/java/duke/ui/gui/Main.java
@@ -0,0 +1,35 @@
+package duke.ui.gui;
+
+import java.io.IOException;
+
+import duke.Duke;
+import javafx.application.Application;
+import javafx.fxml.FXMLLoader;
+import javafx.scene.Scene;
+import javafx.scene.image.Image;
+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();
+
+ @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");
+ stage.getIcons().add(new Image("/images/DaDuke.png"));
+ fxmlLoader.getController().setDuke(duke);
+ stage.show();
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/duke/ui/gui/MainWindow.java b/src/main/java/duke/ui/gui/MainWindow.java
new file mode 100644
index 0000000000..5ffcfb6258
--- /dev/null
+++ b/src/main/java/duke/ui/gui/MainWindow.java
@@ -0,0 +1,68 @@
+package duke.ui.gui;
+
+import duke.Duke;
+import duke.ui.TextUI;
+import javafx.fxml.FXML;
+import javafx.scene.control.Button;
+import javafx.scene.control.ScrollPane;
+import javafx.scene.control.TextField;
+import javafx.scene.image.Image;
+import javafx.scene.layout.AnchorPane;
+import javafx.scene.layout.VBox;
+
+import java.util.concurrent.TimeUnit;
+
+/**
+ * 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.png"));
+ private Image dukeImage = new Image(this.getClass().getResourceAsStream("/images/DaDuke.png"));
+
+ @FXML
+ public void initialize() {
+ TextUI tempTextUI = new TextUI();
+ scrollPane.vvalueProperty().bind(dialogContainer.heightProperty());
+ dialogContainer.getChildren().addAll(
+ DialogBox.getDukeDialog(tempTextUI.getWelcomeMessage(), dukeImage)
+ );
+ }
+
+ 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() {
+ assert duke != null : "duke is null";
+ String input = userInput.getText();
+ if(input.isBlank()) {
+ return;
+ }
+ String response = duke.getResponse(input);
+ dialogContainer.getChildren().addAll(
+ DialogBox.getUserDialog(input, userImage),
+ DialogBox.getDukeDialog(response, dukeImage)
+ );
+ userInput.clear();
+ if (input.equals("bye")) {
+ System.exit(0);
+ }
+ }
+}
+
diff --git a/src/main/resources/images/DaDuke.png b/src/main/resources/images/DaDuke.png
new file mode 100644
index 0000000000..e27af8f651
Binary files /dev/null and b/src/main/resources/images/DaDuke.png differ
diff --git a/src/main/resources/images/DaUser.png b/src/main/resources/images/DaUser.png
new file mode 100644
index 0000000000..b76dca863e
Binary files /dev/null and b/src/main/resources/images/DaUser.png differ
diff --git a/src/main/resources/view/DialogBox.fxml b/src/main/resources/view/DialogBox.fxml
new file mode 100644
index 0000000000..a504886ee9
--- /dev/null
+++ b/src/main/resources/view/DialogBox.fxml
@@ -0,0 +1,16 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ 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..5538336da4
--- /dev/null
+++ b/src/main/resources/view/MainWindow.fxml
@@ -0,0 +1,19 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/main/resources/view/style.css b/src/main/resources/view/style.css
new file mode 100644
index 0000000000..b7f53324b6
--- /dev/null
+++ b/src/main/resources/view/style.css
@@ -0,0 +1,6 @@
+.dialog-box {
+ -fx-background-color: #4fc3f7;
+ -fx-background-radius: 10px;
+ -fx-background-insets: 0 5;
+ -fx-label-padding: 12px;
+}
\ No newline at end of file
diff --git a/src/test/java/duke/parser/ParserTest.java b/src/test/java/duke/parser/ParserTest.java
new file mode 100644
index 0000000000..e7e4833890
--- /dev/null
+++ b/src/test/java/duke/parser/ParserTest.java
@@ -0,0 +1,51 @@
+package duke.parser;
+
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import duke.commands.*;
+import duke.exceptions.DukeInvalidArgumentException;
+import org.junit.jupiter.api.Test;
+
+public class ParserTest {
+ private Parser parser = new Parser();
+
+ @Test
+ public void parseExitCommandsTest() throws DukeInvalidArgumentException{
+ assertTrue(parser.parseCommands("bye") instanceof ExitCommand);
+ }
+
+ @Test
+ public void parseListCommandsTest() throws DukeInvalidArgumentException{
+ assertTrue(parser.parseCommands("list") instanceof ListCommand);
+ }
+
+ @Test
+ public void parseMarkCommandsTest() throws DukeInvalidArgumentException{
+ assertTrue(parser.parseCommands("mark 1") instanceof MarkCommand);
+ }
+
+ @Test
+ public void parseUnmarkCommandsTest() throws DukeInvalidArgumentException{
+ assertTrue(parser.parseCommands("unmark 1") instanceof UnmarkCommand);
+ }
+
+ @Test
+ public void parseDeleteCommandsTest() throws DukeInvalidArgumentException{
+ assertTrue(parser.parseCommands("delete 1") instanceof DeleteCommand);
+ }
+
+ @Test
+ public void parseTodoCommandsTest() throws DukeInvalidArgumentException{
+ assertTrue(parser.parseCommands("TODO return book") instanceof AddCommand);
+ }
+
+ @Test
+ public void parseDeadlineCommandsTest() throws DukeInvalidArgumentException{
+ assertTrue(parser.parseCommands("DeADLINE return book /by 01/01/2000 10:10") instanceof AddCommand);
+ }
+
+ @Test
+ public void parseEventCommandsTest() throws DukeInvalidArgumentException{
+ assertTrue(parser.parseCommands("EvEnt return book /at 01/01/2000 10:10") instanceof AddCommand);
+ }
+}
diff --git a/src/test/java/duke/tasks/TaskTest.java b/src/test/java/duke/tasks/TaskTest.java
new file mode 100644
index 0000000000..cb65efec13
--- /dev/null
+++ b/src/test/java/duke/tasks/TaskTest.java
@@ -0,0 +1,41 @@
+package duke.tasks;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+import org.junit.jupiter.api.Test;
+
+public class TaskTest {
+ private Task task = new Task("testing task");
+
+ @Test
+ void toStringTest() {
+
+ String expected = "[ ] testing task";
+ assertEquals(expected, task.toString());
+ }
+
+ @Test
+ void markAsDoneTest() {
+ task.markAsDone();
+ String expectedMarked = "[X] testing task";
+ assertEquals(expectedMarked, task.toString());
+ }
+
+ @Test
+ void markAsNotDoneTest() {
+ task.markAsDone();
+ task.markAsNotDone();
+ String expectedUnmarked = "[ ] testing task";
+ assertEquals(expectedUnmarked, task.toString());
+ }
+
+ @Test
+ void toSaveDataTest() {
+ task.markAsDone();
+ String expectedMarked = "1|testing task";
+ assertEquals(expectedMarked, task.toSaveData());
+ task.markAsNotDone();
+ String expectedUnmarked = "0|testing task";
+ assertEquals(expectedUnmarked, task.toSaveData());
+ }
+}
diff --git a/text-ui-test/EXPECTED.TXT b/text-ui-test/EXPECTED.TXT
index 657e74f6e7..a0d1d5f171 100644
--- a/text-ui-test/EXPECTED.TXT
+++ b/text-ui-test/EXPECTED.TXT
@@ -5,3 +5,15 @@ Hello from
| |_| | |_| | < __/
|____/ \__,_|_|\_\___|
+How may I assist you?
+Your current task list is empty
+This task has been added as requested:
+[T][ ] borrow book
+You now have 1 item(s) in your list
+This task has been added as requested:
+[D][ ] return book (by: sunday)
+You now have 2 item(s) in your list
+This task has been added as requested:
+[E][ ] project meeting (at: Mon 2-4pm)
+You now have 3 item(s) in your list
+Till we meet again
diff --git a/text-ui-test/input.txt b/text-ui-test/input.txt
index e69de29bb2..f6953e22d8 100644
--- a/text-ui-test/input.txt
+++ b/text-ui-test/input.txt
@@ -0,0 +1,5 @@
+list
+todo borrow book
+deadline return book /by sunday
+event project meeting /at Mon 2-4pm
+bye
diff --git a/text-ui-test/runtest.sh b/text-ui-test/runtest.sh
old mode 100644
new mode 100755