diff --git a/README.md b/README.md
index 9d95025bce..b9e11953ef 100644
--- a/README.md
+++ b/README.md
@@ -1,4 +1,4 @@
-# Duke project template
+# duke.Duke project template
This is a project template for a greenfield Java project. It's named after the Java mascot _Duke_. Given below are instructions on how to use it.
@@ -15,7 +15,7 @@ Prerequisites: JDK 11, update Intellij to the most recent version.
1. Click `Open or Import`.
1. Select the project directory, and click `OK`
1. If there are any further prompts, accept the defaults.
-1. After the importing is complete, locate the `src/main/java/Duke.java` file, right-click it, and choose `Run Duke.main()`. If the setup is correct, you should see something like the below:
+1. After the importing is complete, locate the `src/main/java/duke.Duke.java` file, right-click it, and choose `Run duke.Duke.main()`. If the setup is correct, you should see something like the below:
```
Hello from
____ _
diff --git a/build.gradle b/build.gradle
new file mode 100644
index 0000000000..e7d24dbafa
--- /dev/null
+++ b/build.gradle
@@ -0,0 +1,61 @@
+plugins {
+ id 'java'
+ id 'application'
+ id 'checkstyle'
+ id 'com.github.johnrengelman.shadow' version '5.1.0'
+}
+
+repositories {
+ mavenCentral()
+}
+
+dependencies {
+ String javaFxVersion = '11'
+
+ 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'
+
+ 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'
+}
+
+test {
+ useJUnitPlatform()
+
+ testLogging {
+ events "passed", "skipped", "failed"
+
+ showExceptions true
+ exceptionFormat "full"
+ showCauses true
+ showStackTraces true
+ showStandardStreams = false
+ }
+}
+
+application {
+ mainClassName = "Launcher"
+}
+
+shadowJar {
+ archiveBaseName = "DukeLUL"
+ archiveClassifier = null
+}
+
+checkstyle {
+ toolVersion = '8.23'
+}
+
+run{
+ standardInput = System.in
+}
diff --git a/data/tasks.txt b/data/tasks.txt
new file mode 100644
index 0000000000..ca84b69d41
--- /dev/null
+++ b/data/tasks.txt
@@ -0,0 +1,7 @@
+duke.task.Task list (Last updated Sep 18 2020, 22:56:14 PM):
+1. [T][✗] homewok
+2. [T][✗] test1
+3. [T][✗] test2
+4. [T][✗] test3
+5. [T][✗] test4
+6. [T][✗] test6
diff --git a/docs/README.md b/docs/README.md
index fd44069597..dc3c78bd7d 100644
--- a/docs/README.md
+++ b/docs/README.md
@@ -1,20 +1,218 @@
-# User Guide
+# DukeLUL User Guide
+
+DukeLUL is a desktop app for managing things that you need to get done.
+Chat with an incredibly annoying LULCAT and get so irritated that you'll
+complete your tasks in record time and close the app ASAP.
+
+* [Quick Start](#quick-start)
+* [Features](#features)
+ * [Viewing help: `help`](#viewing-help-help)
+ * [Adding a todo task: `todo`](#adding-a-todo-task-todo)
+ * [Adding a task with a deadline: `deadline`](#adding-a-task-with-a-deadline-deadline)
+ * [Adding an event task: `event`](#adding-an-event-task-event)
+ * [Listing all tasks: `list`](#listing-all-tasks-list)
+ * [Finding tasks: `find`](#finding-tasks-find)
+ * [Marking a task as done: `done`](#marking-a-task-as-done-done)
+ * [Deleting a task: `delete`](#deleting-a-task-delete)
+ * [Updating a task: `update`](#updating-a-task-update)
+ * [Exiting the program: `bye`](#exiting-the-program-bye)
+ * [Saving the data](#saving-the-data)
+* [FAQ](#FAQ)
+* [Command summary](#command-summary)
+
+
+---
+
+## Quick Start
+
+1. Ensure you have Java 11 or above installed on your computer.
+
+2. Download the latest `DukeLUL.jar` from [here](https://github.com/kaitlynng/ip/releases/tag/v0.1).
+
+3. Copy the file to the folder you want to use as the home folder of your DukeLUL app.
+
+4. Double-click the file to start the app. The GUI similar to the one below should appear in a few seconds.
+
+5. Chat with the LULCAT by typing into the chat box and pressing Enter. Typing `help` and pressing Enter will bring up
+a list of commands you can try.
+
+6. Refer to the Features below for details of available commands.
+
## Features
-### Feature 1
-Description of feature.
+This section describes a list of commands that you can use when chatting with LULCAT.
+
+| :memo: Take note! |
+|:------------------------------------------------------------------------------------------------------|
+| Words in `UPPER_CASE` are parameters to be supplied by you. |
+| * e.g. In `todo TASK_DESCRIPTION`, `TASK_DESCRIPTION` is a parameter which you can supply, like so: `todo Homework` |
+| |
+| Items in square brackets are optional. |
+| * e.g. `list [\by DATE_TIME]` can be used as `list` or `list \by 2012-02-02` |
+
+
+### Viewing help: `help`
+
+
+ Provides a link to the user guide for additional help.
+
+ Format: `help`
+
+
+### Adding a todo task: `todo`
+
+
+ Adds a todo task to the tasklist
+
+ Format: `todo TASK_DESCRIPTION`
+ Examples:
+ * `todo Finish homework`
+ * `todo Clean the house`
+
+
+### Adding a task with a deadline: `deadline`
+
+
+ Adds a task to the tasklist with a deadline.
+
+ Format: `deadline TASK_DESCRIPTION \by DATE_TIME`
+ * Note that the `DATE_TIME`has to be entered with a `YYYY-MM-dd` or `YYYY-MM-ddThh:mm:ss` format to be
+ recognised as a date / datetime by the app. Otherwise, no filtering operations
+ can be performed on it.
+
+ Examples:
+ * `deadline Project 3 \by 2020-10-05`
+ * `deadline Buy grocevires \by 2020-09-16T11:02`
+
+
+### Adding an event task: `event`
+
+ Adds an event to the tasklist happening at a given date/time.
+
+ Format: `event EVENT_DESCRIPTION \at DATE_TIME`
+ * Note that the `DATE_TIME`has to be entered with a `YYYY-MM-dd` or `YYYY-MM-ddThh:mm:ss` format to be
+ recognised as a date / datetime by the app. Otherwise, no filtering operations
+ can be performed on it.
+
+ Examples:
+ * `event Night cycling \at 2020-09-19T02:00`
+ * `event Dad's birthday \at 2020-10-25`
+
+
+### Listing all tasks: `list`
+
+
+ Shows a list of all tasks in the tasklist.
+
+ Format: `list [\by DATE_TIME]`
+ * You can optionally specify a `DATE_TIME`to list all the deadlines and events that are due / happening before the given datetime.
+ The `DATE_TIME` specified has to be of the format `YYYY-MM-dd` or `YYYY-MM-ddThh:mm:ss`
+ to be valid.
+
+ Examples:
+ * `list \by 2020-09-19T02:00`
+ * `list \by 2020-10-25`
+
+
+### Finding tasks: `find`
+
+
+ Finds tasks that contains the given keyphrase.
+
+ Format: `find KEYPHRASE`
+ * The search is not case-sensitive. e.g. `Cats` will match `cats`
+ * Only the description of tasks / events is searched.
+ * Only the whole phrase would be matched. e.g. `Happy` or `days` will not match `Happy days`
+
+ Examples:
+ * `find birthday` returns `John's birthday` and `Weiming's birthday`
+
+
+### Marking a task as done: `done`
+
+
+ Marks a task in the current tasklist as done.
+
+ Format: `done TASK_INDEX`
+ * `TASK_INDEX` refers to the index of the task you wish to mark as done in the tasklist.
+ To find the index of the task you would like to mark as done, use the `list` command.
+
+ Examples:
+ * `done 3` marks the task at index 3 as done
+
+
+### Deleting a task: `delete`
+
+
+ Deletes a task from the current tasklist.
+
+ Format: `delete TASK_INDEX`
+ * `TASK_INDEX` refers to the index of the task you wish to delete in the tasklist.
+ To find the index of the task you would like to delete, use the `list` command.
+
+ Examples:
+ * `delete 3` deletes the task at index 3 in the tasklist
+
+
+### Updating a task: `update`
+
+
+ Updates the description or datetime of a task currently in the tasklist.
+
+ Format: `update TASK_INDEX [\date] NEW_VALUE`
+ * `TASK_INDEX` refers to the index of the task you wish to update in the tasklist.
+ To find the index of the task you would like to update, use the `list` command.
+ * If `\date` is specified, the date of the task will be updated to `NEW_VALUE`, otherwise
+ the description of the task will be updated to `NEW_VALUE`. If the task at the given index
+ does not have a specified datetime, an error will be thrown.
+
+ Examples:
+ * `update 3 Chinese homework` updates the description of the task at index 3 to "Chinese homework"
+ * `update 3 \date 2020-09-02T12:00:05` updates the datetime of the task at index 3 to "Sep 2 2020, 12:00:05 PM"
+
+
+### Exiting the program: `bye`
+
+
+ Exits the program.
+
+ Format: `bye`
+
+
+### Saving the data
+
+DukeLUL automatically saves the current tasklist to the hard drive after any command that
+modifies the tasklist. There is no need to save manually.
+
+
+---
+
+## FAQ
+
+ How can I inform the developers of bugs in the program?
-## Usage
+ **A:** You can open a new issue in this project's Github repository. These issues will be
+ reviewed on a regular basis. Click [here](https://docs.github.com/en/enterprise/2.15/user/articles/creating-an-issue)
+ to find out how to create a Github issue.
+
-### `Keyword` - Describe action
+---
-Describe action and its outcome.
+## Command Summary
-Example of usage:
+| **Action** | **Format** |
+|---------------| ---------------------------------------------|
+| Help | `help` |
+| Add todo | `todo TASK_DESCRIPTION` |
+| Add deadline | `deadline TASK_DESCRIPTION \by DATE_TIME` |
+| Add event | `event EVENT_DESCRIPTION \at DATE_TIME` |
+| List | `list [\by DATE_TIME]` |
+| Find | `find KEYPHRASE` |
+| Mark done | `done TASK_INDEX` |
+| Delete | `delete TASK_INDEX` |
+| Update | `update TASK_INDEX [\date] NEW_VALUE` |
+| Exit | `bye` |
-`keyword (optional arguments)`
-Expected outcome:
-`outcome`
diff --git a/docs/Ui.png b/docs/Ui.png
new file mode 100644
index 0000000000..c1dc7075c5
Binary files /dev/null and b/docs/Ui.png differ
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/DialogBox.java b/src/main/java/DialogBox.java
new file mode 100644
index 0000000000..213e0e8424
--- /dev/null
+++ b/src/main/java/DialogBox.java
@@ -0,0 +1,66 @@
+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, boolean isDuke) {
+ String fxmlUrl;
+ if (isDuke) {
+ fxmlUrl = "/view/DukeDialogBox.fxml";
+ } else {
+ fxmlUrl = "/view/UserDialogBox.fxml";
+ }
+
+ try {
+ FXMLLoader fxmlLoader = new FXMLLoader(MainWindow.class.getResource(fxmlUrl));
+ 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, false);
+ }
+
+ public static DialogBox getDukeDialog(String text, Image img) {
+ var db = new DialogBox(text, img, true);
+ db.flip();
+ return db;
+ }
+}
\ No newline at end of file
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/Launcher.java b/src/main/java/Launcher.java
new file mode 100644
index 0000000000..43d64c26fb
--- /dev/null
+++ b/src/main/java/Launcher.java
@@ -0,0 +1,10 @@
+import javafx.application.Application;
+
+/**
+ * A launcher class to workaround classpath issues.
+ */
+public class Launcher {
+ public static void main(String[] args) {
+ Application.launch(Main.class, args);
+ }
+}
diff --git a/src/main/java/META-INF/MANIFEST.MF b/src/main/java/META-INF/MANIFEST.MF
new file mode 100644
index 0000000000..6e864153e8
--- /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/Main.java b/src/main/java/Main.java
new file mode 100644
index 0000000000..37c92bb7aa
--- /dev/null
+++ b/src/main/java/Main.java
@@ -0,0 +1,31 @@
+import duke.Duke;
+
+import java.io.IOException;
+
+import javafx.application.Application;
+import javafx.fxml.FXMLLoader;
+import javafx.scene.Scene;
+import javafx.scene.layout.AnchorPane;
+import javafx.stage.Stage;
+
+/**
+ * A GUI for Duke using FXML.
+ */
+public class Main extends Application {
+
+ private Duke duke = new Duke();
+
+ @Override
+ public void start(Stage stage) {
+ try {
+ FXMLLoader fxmlLoader = new FXMLLoader(Main.class.getResource("/view/MainWindow.fxml"));
+ AnchorPane ap = fxmlLoader.load();
+ Scene scene = new Scene(ap);
+ stage.setScene(scene);
+ fxmlLoader.getController().setDuke(duke);
+ stage.show();
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/MainWindow.java b/src/main/java/MainWindow.java
new file mode 100644
index 0000000000..8b94606507
--- /dev/null
+++ b/src/main/java/MainWindow.java
@@ -0,0 +1,62 @@
+import duke.Duke;
+
+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 javafx.scene.layout.Region;
+
+/**
+ * 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"));
+
+ private String welcomeMessage = "Oh hai kittehs!";
+ @FXML
+ public void initialize() {
+ scrollPane.vvalueProperty().bind(dialogContainer.heightProperty());
+ dialogContainer.getChildren().add(DialogBox.getDukeDialog(welcomeMessage, 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() {
+ String input = userInput.getText();
+ String response = duke.getResponse(input);
+ DialogBox userDialog = DialogBox.getUserDialog(input, userImage);
+ DialogBox dukeDialog = DialogBox.getDukeDialog(response, dukeImage);
+ dukeDialog.setMinHeight(Region.USE_PREF_SIZE);
+ dialogContainer.getChildren().addAll(
+ userDialog, dukeDialog
+ );
+ userInput.clear();
+
+ if (!duke.getRunState()) {
+ System.out.println("good");
+ System.exit(0);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/duke/Duke.java b/src/main/java/duke/Duke.java
new file mode 100644
index 0000000000..fcacf6918d
--- /dev/null
+++ b/src/main/java/duke/Duke.java
@@ -0,0 +1,71 @@
+package duke;
+
+import duke.command.Command;
+import duke.parser.Parser;
+import duke.storage.Storage;
+import duke.task.TaskList;
+import duke.ui.Ui;
+
+
+public class Duke {
+ private static final String LOG_DIRPATH = "data";
+ private static final String LOG_FILENAME = "tasks.txt";
+ private Storage storage;
+ private TaskList tasks;
+ private Ui ui;
+
+ private boolean isRunning = true;
+
+ public Duke() {
+ this.ui = new Ui();
+ this.storage = new Storage(LOG_DIRPATH, LOG_FILENAME);
+ try {
+ tasks = new TaskList(storage.load());
+ ui.showLoadedTasks(tasks);
+ } catch (DukeException e) {
+ ui.showLoadingError(e.getMessage());
+ tasks = new TaskList();
+ }
+ }
+
+ public boolean getRunState() {
+ return this.isRunning;
+ }
+
+ public String getResponse(String input) {
+ assert !input.isEmpty() : "input cannot be empty";
+
+ try {
+ Command c = Parser.parse(input);
+ this.isRunning = !c.isExit();
+ return c.getResponse(tasks, storage);
+ } catch (DukeException e) {
+ return e.getMessage();
+ }
+ }
+
+ public void run() {
+ ui.showWelcome();
+ boolean isExit = false;
+ while (!isExit) {
+ try {
+ String fullCommand = ui.readCommand();
+ ui.showLine();
+ Command c = Parser.parse(fullCommand);
+ c.execute(tasks, ui, storage);
+ isExit = c.isExit();
+ } catch (DukeException e) {
+ ui.showError(e.getMessage());
+ } finally {
+ ui.showLine();
+ }
+ }
+ }
+
+ public static void main(String[] args) {
+ new Duke().run();
+ }
+
+
+
+}
diff --git a/src/main/java/duke/DukeException.java b/src/main/java/duke/DukeException.java
new file mode 100644
index 0000000000..3fcd0f5ea8
--- /dev/null
+++ b/src/main/java/duke/DukeException.java
@@ -0,0 +1,7 @@
+package duke;
+
+public class DukeException extends Exception {
+ public DukeException(String message) {
+ super(message);
+ }
+}
diff --git a/src/main/java/duke/command/ByeCommand.java b/src/main/java/duke/command/ByeCommand.java
new file mode 100644
index 0000000000..43abd5579d
--- /dev/null
+++ b/src/main/java/duke/command/ByeCommand.java
@@ -0,0 +1,25 @@
+package duke.command;
+
+import duke.*;
+import duke.storage.Storage;
+import duke.task.TaskList;
+import duke.ui.Ui;
+
+public class ByeCommand extends Command {
+
+ public ByeCommand() {
+ super();
+ this.cmd = CMD.BYE;
+ this.isExit = true;
+ }
+
+ @Override
+ public String getResponse(TaskList tasklist, Storage storage) throws DukeException {
+ return "OKAIS I IZ GOIN 2 NOM BYEEEEE C U !!!1!1!!";
+ }
+
+ @Override
+ public String toString() {
+ return this.cmd.toString();
+ }
+}
diff --git a/src/main/java/duke/command/CMD.java b/src/main/java/duke/command/CMD.java
new file mode 100644
index 0000000000..b4f2cb098c
--- /dev/null
+++ b/src/main/java/duke/command/CMD.java
@@ -0,0 +1,15 @@
+package duke.command;
+
+public enum CMD {
+ HELP,
+ BYE,
+ LIST,
+ TODO,
+ DEADLINE,
+ EVENT,
+ DONE,
+ DELETE,
+ FIND,
+ UPDATE,
+ DEFAULT
+}
\ No newline at end of file
diff --git a/src/main/java/duke/command/Command.java b/src/main/java/duke/command/Command.java
new file mode 100644
index 0000000000..6c6fa11435
--- /dev/null
+++ b/src/main/java/duke/command/Command.java
@@ -0,0 +1,42 @@
+package duke.command;
+
+import duke.*;
+import duke.storage.Storage;
+import duke.task.TaskList;
+import duke.ui.Ui;
+
+public class Command {
+ protected CMD cmd;
+ protected boolean isExit;
+
+ public Command() {
+ this.cmd = CMD.DEFAULT;
+ this.isExit = false;
+ }
+
+ public boolean isExit() {
+ return this.isExit;
+ }
+
+ public String getResponse(TaskList tasklist, Storage storage) throws DukeException {
+ return "CAN I HAZ CHEEZBURGER?";
+ }
+
+ /**
+ * Executes the default command, which displays a standard string regardless
+ * of input.
+ *
+ * @param taskList
+ * @param ui
+ * @param storage
+ * @throws DukeException
+ */
+ public void execute(TaskList taskList, Ui ui, Storage storage) throws DukeException {
+ ui.display(getResponse(taskList, storage));
+ }
+
+ @Override
+ public String toString() {
+ return this.cmd.toString();
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/duke/command/DeadlineCommand.java b/src/main/java/duke/command/DeadlineCommand.java
new file mode 100644
index 0000000000..bc931e6f8d
--- /dev/null
+++ b/src/main/java/duke/command/DeadlineCommand.java
@@ -0,0 +1,31 @@
+package duke.command;
+
+import duke.*;
+import duke.storage.Storage;
+import duke.task.TaskList;
+import duke.ui.Ui;
+
+public class DeadlineCommand extends Command {
+ private String taskName;
+ private String by;
+
+ public DeadlineCommand(String taskName, String by) {
+ super();
+ this.cmd = CMD.DEADLINE;
+ this.taskName = taskName;
+ this.by = by;
+ }
+
+ @Override
+ public String getResponse(TaskList tasklist, Storage storage) throws DukeException {
+ String msg = "I PUT NEW TING IN DA LIST\n " + tasklist.addDeadline(this.taskName, this.by)
+ + "\nNAO U HAS " + tasklist.getNumberOfTasks() + " FINGS IN DA LIST LULZIES";
+ storage.save(tasklist);
+ return msg;
+ }
+
+ @Override
+ public String toString() {
+ return this.cmd.toString() + ": " + this.taskName + "(" + this.by + ")";
+ }
+}
diff --git a/src/main/java/duke/command/DeleteCommand.java b/src/main/java/duke/command/DeleteCommand.java
new file mode 100644
index 0000000000..b00600fcd1
--- /dev/null
+++ b/src/main/java/duke/command/DeleteCommand.java
@@ -0,0 +1,29 @@
+package duke.command;
+
+import duke.*;
+import duke.storage.Storage;
+import duke.task.TaskList;
+import duke.ui.Ui;
+
+public class DeleteCommand extends Command {
+ private int taskIdx;
+ public DeleteCommand(int idx) {
+ super();
+ this.cmd = CMD.DONE;
+ this.taskIdx = idx;
+ }
+
+ @Override
+ public String getResponse(TaskList taskList, Storage storage) throws DukeException {
+ String msg = "TASK IZ NAO DELETZ!!!!1!11!\n" + " "
+ + taskList.popTask(this.taskIdx)
+ + "\nNAO U HAS " + taskList.getNumberOfTasks() + " FINGS IN DA LIST LULZIES";
+ storage.save(taskList);
+ return msg;
+ }
+
+ @Override
+ public String toString() {
+ return this.cmd.toString() + ": " + this.taskIdx;
+ }
+}
diff --git a/src/main/java/duke/command/DoneCommand.java b/src/main/java/duke/command/DoneCommand.java
new file mode 100644
index 0000000000..ea325c2944
--- /dev/null
+++ b/src/main/java/duke/command/DoneCommand.java
@@ -0,0 +1,28 @@
+package duke.command;
+
+import duke.*;
+import duke.storage.Storage;
+import duke.task.TaskList;
+import duke.ui.Ui;
+
+public class DoneCommand extends Command {
+ private int idx;
+ public DoneCommand(int idx) {
+ super();
+ this.cmd = CMD.DONE;
+ this.idx = idx;
+ }
+
+ @Override
+ public String getResponse(TaskList tasklist, Storage storage) throws DukeException {
+ tasklist.markTaskAsDone(this.idx);
+ storage.save(tasklist);
+ return "TASK IZ NAO DUNZ!!!!1!11!\n" + " " + tasklist.getTaskByIdx(this.idx);
+ }
+
+ @Override
+ public String toString() {
+ return this.cmd.toString() + ": " + this.idx;
+ }
+}
+
diff --git a/src/main/java/duke/command/EventCommand.java b/src/main/java/duke/command/EventCommand.java
new file mode 100644
index 0000000000..599f291018
--- /dev/null
+++ b/src/main/java/duke/command/EventCommand.java
@@ -0,0 +1,31 @@
+package duke.command;
+
+import duke.*;
+import duke.storage.Storage;
+import duke.task.TaskList;
+import duke.ui.Ui;
+
+public class EventCommand extends Command {
+ private String taskName;
+ private String by;
+
+ public EventCommand(String taskName, String by) {
+ super();
+ this.cmd = CMD.EVENT;
+ this.taskName = taskName;
+ this.by = by;
+ }
+
+ @Override
+ public String getResponse(TaskList tasklist, Storage storage) throws DukeException {
+ String msg = "I PUT NEW TING IN DA LIST\n " + tasklist.addEvent(this.taskName, this.by)
+ + "\nNAO U HAS " + tasklist.getNumberOfTasks() + " FINGS IN DA LIST LULZIES";
+ storage.save(tasklist);
+ return msg;
+ }
+
+ @Override
+ public String toString() {
+ return this.cmd.toString() + ": " + this.taskName + "(" + this.by + ")";
+ }
+}
diff --git a/src/main/java/duke/command/FindCommand.java b/src/main/java/duke/command/FindCommand.java
new file mode 100644
index 0000000000..5f00353ad5
--- /dev/null
+++ b/src/main/java/duke/command/FindCommand.java
@@ -0,0 +1,29 @@
+package duke.command;
+
+import duke.DukeException;
+import duke.datetime.DateTimeUtility;
+import duke.storage.Storage;
+import duke.task.TaskList;
+
+public class FindCommand extends Command {
+ private String filter;
+
+ public FindCommand(String filter) {
+ super();
+ this.cmd = CMD.FIND;
+ this.filter = filter;
+ }
+
+ @Override
+ public String getResponse(TaskList tasklist, Storage storage) throws DukeException {
+ String ret = tasklist.filterTasksByDescription(this.filter);
+ if (ret.isEmpty()) {
+ return "U HAZ NUTHIN DAT GOT "
+ + this.filter + " INSIDE LULZIES";
+ } else {
+ return "U HAS DEES TINGS IN UR LIST DAT MATCH "
+ + this.filter + ": \n"
+ + ret;
+ }
+ }
+}
diff --git a/src/main/java/duke/command/HelpCommand.java b/src/main/java/duke/command/HelpCommand.java
new file mode 100644
index 0000000000..becafb562e
--- /dev/null
+++ b/src/main/java/duke/command/HelpCommand.java
@@ -0,0 +1,42 @@
+package duke.command;
+
+import duke.*;
+import duke.storage.Storage;
+import duke.task.TaskList;
+
+import java.awt.Desktop;
+import java.io.IOException;
+import java.net.URI;
+import java.net.URISyntaxException;
+
+public class HelpCommand extends Command {
+
+ public HelpCommand() {
+ super();
+ this.cmd = CMD.HELP;
+ }
+
+ @Override
+ public String getResponse(TaskList tasklist, Storage storage) throws DukeException {
+ String url = "https://kaitlynng.github.io/ip/";
+ try {
+ if (System.getProperty("os.name").contains("Windows")) {
+ Desktop desktop = java.awt.Desktop.getDesktop();
+ URI userGuideSite = new URI(url);
+ desktop.browse(userGuideSite);
+ } else {
+ Runtime runtime = Runtime.getRuntime();
+ runtime.exec("xdg-open " + url);
+ }
+ } catch (IOException | URISyntaxException e) {
+ return "Bark. (Sorry, we couldn't open the website.)";
+ }
+
+ return "GOES HERE 4 MOAR INFO!!!!11!!\n" + url;
+ }
+
+ @Override
+ public String toString() {
+ return this.cmd.toString();
+ }
+}
diff --git a/src/main/java/duke/command/ListCommand.java b/src/main/java/duke/command/ListCommand.java
new file mode 100644
index 0000000000..9fe2b4b8a4
--- /dev/null
+++ b/src/main/java/duke/command/ListCommand.java
@@ -0,0 +1,55 @@
+package duke.command;
+
+import duke.*;
+import duke.datetime.DateTimeUtility;
+import duke.storage.Storage;
+import duke.task.TaskList;
+import duke.ui.Ui;
+
+public class ListCommand extends Command {
+ private String by;
+
+ public ListCommand() {
+ super();
+ this.by = "";
+ this.cmd = CMD.LIST;
+ }
+
+ /**
+ * Overloaded class constructor if a deadline is spceified for filtering out
+ * tasks after the deadline.
+ *
+ * @param by
+ */
+
+ public ListCommand(String by) {
+ this();
+ this.by = DateTimeUtility.formatString(by);
+ }
+
+ @Override
+ public String getResponse(TaskList tasklist, Storage storage) throws DukeException {
+ if (tasklist.isEmpty()) {
+ return "UR LIST HAZ NUTHIN LOLOL";
+ } else {
+ if (this.by.isEmpty()) {
+ return ("U HAS DEES TINGS IN UR LIST.\n" + tasklist.toString());
+ } else {
+ String ret = tasklist.filterTasksByDate(this.by);
+ if (ret.isEmpty()) {
+ return "U HAZ NUTHIN DUE/HAPPENIN BY "
+ + DateTimeUtility.formatString(this.by) + "!! LULZIES";
+ } else {
+ return "U HAS DEES TINGS IN UR LIST DAT R DUE/HAPPENIN BY "
+ + DateTimeUtility.formatString(this.by) + ": \n"
+ + ret;
+ }
+ }
+ }
+ }
+
+ @Override
+ public String toString() {
+ return this.cmd.toString() + (this.by.isEmpty() ? "" : " (" + this.by + ")");
+ }
+}
diff --git a/src/main/java/duke/command/TodoCommand.java b/src/main/java/duke/command/TodoCommand.java
new file mode 100644
index 0000000000..79c3385388
--- /dev/null
+++ b/src/main/java/duke/command/TodoCommand.java
@@ -0,0 +1,28 @@
+package duke.command;
+
+import duke.*;
+import duke.storage.Storage;
+import duke.task.TaskList;
+
+public class TodoCommand extends Command {
+ private String taskName;
+
+ public TodoCommand(String taskName) {
+ super();
+ this.cmd = CMD.TODO;
+ this.taskName = taskName;
+ }
+
+ @Override
+ public String getResponse(TaskList tasklist, Storage storage) throws DukeException {
+ String msg = "I PUT NEW TING IN DA LIST\n " + tasklist.addTodo(this.taskName)
+ + "\nNAO U HAS " + tasklist.getNumberOfTasks() + " FINGS IN DA LIST LULZIES";
+ storage.save(tasklist);
+ return msg;
+ }
+
+ @Override
+ public String toString() {
+ return this.cmd.toString() + ": " + this.taskName;
+ }
+}
diff --git a/src/main/java/duke/command/UpdateCommand.java b/src/main/java/duke/command/UpdateCommand.java
new file mode 100644
index 0000000000..45dc8d229d
--- /dev/null
+++ b/src/main/java/duke/command/UpdateCommand.java
@@ -0,0 +1,38 @@
+package duke.command;
+
+import duke.DukeException;
+import duke.storage.Storage;
+import duke.task.TaskList;
+
+public class UpdateCommand extends Command {
+ private int taskIdx;
+ private String newValue;
+ private boolean isUpdateDate;
+
+ public UpdateCommand(int idx, String newValue, boolean isUpdateDate) {
+ super();
+ this.cmd = CMD.UPDATE;
+ this.taskIdx = idx;
+ this.newValue = newValue;
+ this.isUpdateDate = isUpdateDate;
+ }
+
+ @Override
+ public String getResponse(TaskList tasklist, Storage storage) throws DukeException {
+ String msg = "TASK IZ NAO UPDATEZ!!1!11!!\n" + " ";
+
+ if (this.isUpdateDate) {
+ msg += tasklist.updateTaskDate(this.taskIdx, this.newValue);
+ } else {
+ msg += tasklist.updateTaskDescription(this.taskIdx, this.newValue);
+ }
+
+ storage.save(tasklist);
+ return msg;
+ }
+
+ @Override
+ public String toString() {
+ return this.cmd.toString() + ": " + this.taskIdx;
+ }
+}
diff --git a/src/main/java/duke/datetime/DateTimeFormat.java b/src/main/java/duke/datetime/DateTimeFormat.java
new file mode 100644
index 0000000000..3ad07591df
--- /dev/null
+++ b/src/main/java/duke/datetime/DateTimeFormat.java
@@ -0,0 +1,7 @@
+package duke.datetime;
+
+public enum DateTimeFormat {
+ String,
+ Date,
+ DateTime,
+}
\ No newline at end of file
diff --git a/src/main/java/duke/datetime/DateTimeUtility.java b/src/main/java/duke/datetime/DateTimeUtility.java
new file mode 100644
index 0000000000..28c8fdc88d
--- /dev/null
+++ b/src/main/java/duke/datetime/DateTimeUtility.java
@@ -0,0 +1,156 @@
+package duke.datetime;
+import java.util.List;
+import java.util.ArrayList;
+
+import duke.DukeException;
+
+import java.time.DateTimeException;
+import java.time.LocalDate;
+import java.time.LocalDateTime;
+import java.time.format.DateTimeFormatter;
+
+public class DateTimeUtility {
+ private static DateTimeFormatter OUTPUT_DATE_FMT = DateTimeFormatter.ofPattern("MMM d yyyy");
+ private static DateTimeFormatter OUTPUT_DATETIME_FMT = DateTimeFormatter.ofPattern("MMM d yyyy, HH:mm:ss a");
+
+ /**
+ * Checks whether the input dataString is of the correct Date or DateTime format, or is just a String.
+ *
+ * @param dateStr
+ * @return the format of the input dataString (whether it's of a Date, DateTime or normal String format)
+ */
+ public static DateTimeFormat checkDateTimeType(String dateStr) {
+ assert !dateStr.isEmpty() : "dateStr should not be empty";
+ try {
+ LocalDateTime.parse(dateStr);
+ return DateTimeFormat.DateTime;
+ } catch (DateTimeException e) {}
+ try {
+ LocalDateTime.parse(dateStr, OUTPUT_DATETIME_FMT);
+ return DateTimeFormat.DateTime;
+ } catch (DateTimeException e) {}
+ try {
+ LocalDate.parse(dateStr);
+ return DateTimeFormat.Date;
+ } catch (DateTimeException e) {}
+ try {
+ LocalDate.parse(dateStr, OUTPUT_DATE_FMT);
+ return DateTimeFormat.Date;
+ } catch (DateTimeException e) {}
+
+ return DateTimeFormat.String;
+ }
+
+ /**
+ * Formats the input string according to the DateTimeFormat specified. If it is of a Date or DateTime
+ * format, output a String that changes the input string to a specified output format. If it is of a
+ * String format, formatString returns the same string.
+ *
+ * @param dateStr
+ * @param format format of the input string that can be obtained with checkDateTimeType()
+ * @return Another string of the correct standardised output format
+ */
+
+ public static String formatString(String dateStr, DateTimeFormat format) throws DukeException {
+ assert !dateStr.isEmpty() : "dateStr should not be empty";
+ assert format != null : "format cannot be null";
+
+ switch(format) {
+ case DateTime:
+ try {
+ return LocalDateTime.parse(dateStr).format(OUTPUT_DATETIME_FMT);
+ } catch (DateTimeException e) {
+ throw new DukeException("Datetime format " + format
+ + " does not match that of input string " + dateStr);
+ }
+ case Date:
+ try {
+ return LocalDate.parse(dateStr).format(OUTPUT_DATE_FMT);
+ } catch (DateTimeException e) {
+ throw new DukeException("Datetime format " + format
+ + " does not match that of input string " + dateStr);
+ }
+
+ default:
+ return dateStr;
+ }
+ }
+
+ /**
+ * Formats the input string according to the DateTimeFormat that is Automatically detected
+ * using checkDateTimeType().
+ *
+ * @param dateStr
+ * @return
+ */
+ public static String formatString(String dateStr) {
+ try {
+ return DateTimeUtility.formatString(dateStr, DateTimeUtility.checkDateTimeType(dateStr));
+ } catch (DukeException e) {
+ return dateStr;
+ }
+
+ }
+
+ public static int compare(LocalDate a, LocalDate b) {
+ assert a != null : "a cannot be null";
+ assert b != null : "b cannot be null";
+ return a.compareTo(b);
+ }
+
+ public static int compare(LocalDateTime a, LocalDateTime b) {
+ assert a != null : "a cannot be null";
+ assert b != null : "b cannot be null";
+ return a.compareTo(b);
+ }
+
+ public static int compare(LocalDate a, LocalDateTime b) {
+ assert a != null : "a cannot be null";
+ assert b != null : "b cannot be null";
+ return a.compareTo(b.toLocalDate());
+ }
+
+ public static int compare(LocalDateTime a, LocalDate b) {
+ assert a != null : "a cannot be null";
+ assert b != null : "b cannot be null";
+ return a.toLocalDate().compareTo(b);
+ }
+
+ /**
+ * Overloaded comparator between two input DateTime strings. Checks the DateTimeFormat for each
+ * string and makes the right comparisons between Date and DateTime. If either one of the strings
+ * cannot be parsed as a Date or DateTime object, throws an exception.
+ *
+ * @param a
+ * @param b
+ * @return
+ * @throws DateTimeException
+ */
+ public static int compare(String a, String b) throws DateTimeException {
+ assert !a.isEmpty() : "a cannot be empty";
+ assert !b.isEmpty() : "b cannot be empty";
+
+ DateTimeFormat aType = DateTimeUtility.checkDateTimeType(a);
+ DateTimeFormat bType = DateTimeUtility.checkDateTimeType(b);
+
+ if (aType == DateTimeFormat.Date) {
+ if (bType == DateTimeFormat.Date) {
+ return DateTimeUtility.compare(LocalDate.parse(a, OUTPUT_DATE_FMT), LocalDate.parse(b, OUTPUT_DATE_FMT));
+ } else if (bType == DateTimeFormat.DateTime) {
+ return DateTimeUtility.compare(LocalDate.parse(a, OUTPUT_DATE_FMT), LocalDateTime.parse(b, OUTPUT_DATETIME_FMT));
+ } else {
+ throw new DateTimeException("Cannot compare Datetime with string!");
+ }
+ } else if (aType == DateTimeFormat.DateTime) {
+ if (bType == DateTimeFormat.Date) {
+ return DateTimeUtility.compare(LocalDateTime.parse(a, OUTPUT_DATE_FMT), LocalDate.parse(b, OUTPUT_DATE_FMT));
+ } else if (bType == DateTimeFormat.DateTime) {
+ return DateTimeUtility.compare(LocalDateTime.parse(a, OUTPUT_DATETIME_FMT), LocalDateTime.parse(b, OUTPUT_DATETIME_FMT));
+ } else {
+ throw new DateTimeException("Cannot compare Datetime with string!");
+ }
+ } else {
+ throw new DateTimeException("Cannot compare Datetime with string!");
+ }
+ }
+}
diff --git a/src/main/java/duke/parser/Parser.java b/src/main/java/duke/parser/Parser.java
new file mode 100644
index 0000000000..24fdaab7b4
--- /dev/null
+++ b/src/main/java/duke/parser/Parser.java
@@ -0,0 +1,166 @@
+package duke.parser;
+
+import duke.Duke;
+import duke.DukeException;
+import duke.command.*;
+import duke.datetime.DateTimeFormat;
+import duke.datetime.DateTimeUtility;
+
+public class Parser {
+
+ /**
+ * Parses a user input and returns the right command with other relevant arguments for the command.
+ * Performs input validation and throws a DukeException if input is not valid.
+ *
+ * @param userInput
+ * @return
+ * @throws DukeException
+ */
+ public static Command parse(String userInput) throws DukeException {
+ assert !userInput.isEmpty() : "userInput cannot be empty";
+
+ int space_idx = userInput.indexOf(' ');
+ CMD cmd;
+
+ try {
+ cmd = CMD.valueOf((space_idx == -1 ? userInput : userInput.substring(0, space_idx)).toUpperCase());
+ } catch (IllegalArgumentException e) {
+ cmd = CMD.DEFAULT;
+ }
+
+ String rest = space_idx == -1 ? "" : userInput.substring(space_idx + 1).trim();
+ int dateStrIdx;
+ String dateStr;
+ String item;
+
+ switch(cmd) {
+
+ case HELP:
+ return new HelpCommand();
+
+ case BYE:
+ return new ByeCommand();
+
+ case LIST:
+ dateStrIdx = rest.indexOf("/by");
+ if (dateStrIdx == -1) {
+ return new ListCommand();
+ }
+
+ dateStr = rest.substring(dateStrIdx + 3).trim();
+ if (DateTimeUtility.checkDateTimeType(dateStr) != DateTimeFormat.String) {
+ return new ListCommand(dateStr);
+ } else {
+ throw new DukeException("U NID 2 GIV CORRECT DATE FOMAT!");
+ }
+
+ case TODO:
+ if (!rest.isEmpty()) {
+ return new TodoCommand(rest);
+ } else {
+ throw new DukeException("ME FINKZ DAT U NED 2 ENTR NAYM 4 UR TODO ITEM LULZ");
+ }
+
+ case DEADLINE:
+ if (rest.isEmpty()) {
+ throw new DukeException("ME FINKZ DAT U NED 2 ENTR NAYM 4 UR DEDLINE ITEM LULZ");
+ }
+
+ if (rest.indexOf("/by") == -1) {
+ throw new DukeException("ME FINKZ U NED 2 GIV DATE 4 TIEM 4 DA DEDLINE USIN /by");
+ }
+
+ dateStrIdx = rest.indexOf("/by");
+ item = rest.substring(0, dateStrIdx).trim();
+ dateStr = rest.substring(dateStrIdx + 3).trim();
+
+ if (!item.isEmpty() && !dateStr.isEmpty()) {
+ return new DeadlineCommand(item, dateStr);
+ } else if (item.isEmpty()) {
+ throw new DukeException("ME FINKZ U NED 2 GIV DA DEDLINE A NAEM");
+ } else {
+ throw new DukeException("ME FINKZ U NED 2 PUT SUMTHIN 4 DA DATE OR TIEM");
+ }
+
+
+ case EVENT:
+ if (rest.isEmpty()) {
+ throw new DukeException("ME FINKZ DAT U NED 2 ENTR NAYM 4 UR EVENT ITEM LULZ");
+ }
+
+ if (rest.indexOf("/at") == -1) {
+ throw new DukeException("ME FINKZ U NED 2 GIV DATE 4 TIEM 4 DA EVENT USIN /at");
+ }
+
+ dateStrIdx = rest.indexOf("/at");
+ item = rest.substring(0, dateStrIdx).trim();
+ dateStr = rest.substring(dateStrIdx + 3).trim();
+
+ if (!item.isEmpty() && !dateStr.isEmpty()) {
+ return new EventCommand(item, dateStr);
+ } else if (item.isEmpty()) {
+ throw new DukeException("ME FINKZ U NED 2 GIV DA EVENT A NAEM");
+ } else {
+ throw new DukeException("ME FINKZ U NED 2 PUT SUMTHIN 4 DA DATE OR TIEM");
+ }
+
+
+ case DONE:
+ try {
+ int idx = Integer.parseInt(rest) - 1;
+ return new DoneCommand(idx);
+ } catch (NumberFormatException e) {
+ throw new DukeException("U MUST ONLY PUT INDEX OV TASK LULS");
+ }
+
+ case DELETE:
+ try {
+ int idx = Integer.parseInt(rest) - 1;
+ return new DeleteCommand(idx);
+ } catch (NumberFormatException e) {
+ throw new DukeException("U MUST ONLY PUT INDEX OV TASK LULS");
+ }
+
+ case FIND:
+ if (!rest.isEmpty()) {
+ return new FindCommand(rest);
+ } else {
+ throw new DukeException("ME FINKZ DAT U NED 2 ENTR NAYM 4 UR TODO ITEM LULZ");
+
+ }
+
+ case UPDATE:
+ if (rest.isEmpty()) {
+ throw new DukeException("U MUST PUT DA INDEX OV TASK AN' NEW NAME LULZ");
+ }
+
+ int taskIdx;
+ String newInput;
+
+ try {
+ String parts[] = rest.split(" ", 2);
+ taskIdx = Integer.parseInt(parts[0]) - 1;
+ newInput = parts[1];
+ } catch (NumberFormatException e) {
+ throw new DukeException("U MUST PUT INDEX OV TASK LULS");
+ }
+
+ dateStrIdx = newInput.indexOf("/date");
+
+ if (dateStrIdx == -1) {
+ return new UpdateCommand(taskIdx, newInput, false);
+ }
+
+ dateStr = newInput.substring(dateStrIdx + 5).trim();
+ if (!dateStr.isEmpty()) {
+ return new UpdateCommand(taskIdx, dateStr, true);
+ } else {
+ throw new DukeException("U CANNOT GIV EMPTY DATE!");
+ }
+
+ case DEFAULT:
+ return new Command();
+ }
+ return new Command();
+ }
+}
diff --git a/src/main/java/duke/storage/Storage.java b/src/main/java/duke/storage/Storage.java
new file mode 100644
index 0000000000..556d1827b9
--- /dev/null
+++ b/src/main/java/duke/storage/Storage.java
@@ -0,0 +1,83 @@
+package duke.storage;
+
+import duke.DukeException;
+import duke.task.TaskList;
+
+import java.io.BufferedWriter;
+import java.io.BufferedReader;
+import java.io.File;
+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.util.ArrayList;
+
+public class Storage {
+
+ private Path dirPath;
+ private Path filePath;
+
+ public Storage(String dirPath, String fileName) {
+ this.dirPath = Paths.get(dirPath);
+ this.filePath = Paths.get(dirPath, fileName);
+ File dataDir = this.dirPath.toAbsolutePath().toFile();
+ File dataFile = this.filePath.toAbsolutePath().toFile();
+
+ try {
+ if (!dataDir.exists()) {
+ dataDir.mkdirs();
+ }
+ if (!dataFile.exists()) {
+ dataFile.createNewFile();
+ }
+ } catch (IOException e) {
+ e.printStackTrace();;
+ }
+
+ }
+
+ /**
+ * Saves the existing tasklist to a log file specified with filePath in the class constructor along
+ * with a timestamp. Creates the necessary directories and files if they do not exist.
+ *
+ * @param taskList
+ * @throws DukeException
+ */
+ public void save(TaskList taskList) throws DukeException {
+ try (BufferedWriter writer = Files.newBufferedWriter(this.filePath)) {
+ LocalDateTime now = LocalDateTime.now();
+ String msg = "duke.task.Task list (Last updated "
+ + now.format(DateTimeFormatter.ofPattern("MMM d yyyy, HH:mm:ss a"))
+ + "):\n";
+ writer.write(msg + taskList.toString());
+ } catch (IOException e) {
+ throw new DukeException("CANNOT SAVE TASKLIST TO FILE: " + e.getMessage());
+ }
+ }
+
+ /**
+ * loads a saved tasklist from a log file and parses it into an arrayList of Strings that describe
+ * the task.
+ *
+ * @return an arrayList of Strings that describe the tasks saved in the tasklist
+ * @throws DukeException
+ */
+ public ArrayList load() throws DukeException {
+ try (BufferedReader reader = Files.newBufferedReader(this.filePath)) {
+ ArrayList tasksStr = new ArrayList<>();
+ String str;
+ reader.readLine();
+ while((str = reader.readLine()) != null) {
+ tasksStr.add(str.substring(str.indexOf(".") + 1).trim());
+ }
+
+ return tasksStr;
+ } catch (IOException e) {
+ throw new DukeException("GOT ERROR LOADING TASKS! " + e.getMessage());
+ }
+ }
+
+
+}
diff --git a/src/main/java/duke/task/Deadline.java b/src/main/java/duke/task/Deadline.java
new file mode 100644
index 0000000000..8f8cdd3e1c
--- /dev/null
+++ b/src/main/java/duke/task/Deadline.java
@@ -0,0 +1,15 @@
+package duke.task;
+
+import duke.datetime.DateTimeUtility;
+
+public class Deadline extends TimedTask {
+ public static final String TASK_ICON = "D";
+ public Deadline(String description, String by) {
+ super(description, DateTimeUtility.formatString(by));
+ }
+
+ @Override
+ public String toString() {
+ return "["+ this.TASK_ICON +"]" + super.toString() + " (by: " + super.formatBy() + ")";
+ }
+}
diff --git a/src/main/java/duke/task/Event.java b/src/main/java/duke/task/Event.java
new file mode 100644
index 0000000000..1d006c3751
--- /dev/null
+++ b/src/main/java/duke/task/Event.java
@@ -0,0 +1,15 @@
+package duke.task;
+
+import duke.datetime.DateTimeUtility;
+
+public class Event extends TimedTask {
+ public static final String TASK_ICON = "E";
+ public Event(String description, String by) {
+ super(description, DateTimeUtility.formatString(by));
+ }
+
+ @Override
+ public String toString() {
+ return "["+ this.TASK_ICON +"]" + super.toString() + " (at: " + super.formatBy() + ")";
+ }
+}
diff --git a/src/main/java/duke/task/Task.java b/src/main/java/duke/task/Task.java
new file mode 100644
index 0000000000..7ea8fc33a2
--- /dev/null
+++ b/src/main/java/duke/task/Task.java
@@ -0,0 +1,31 @@
+package duke.task;
+
+public class Task {
+ public static final String ICON_TICK = "✓";
+ public static final String ICON_CROSS = "✗";
+
+ protected String description;
+ protected boolean isDone;
+
+ public Task(String description) {
+ this.description = description;
+ this.isDone = false;
+ }
+
+ public String getDescription() { return this.description; };
+
+ public void setDescription(String description) { this.description = description; }
+
+ public void setDone(boolean status) {
+ this.isDone = status;
+ }
+
+ public String getStatusIcon() {
+ return (isDone ? ICON_TICK : ICON_CROSS); //return tick or X symbols
+ }
+
+ @Override
+ public String toString() {
+ return "[" + getStatusIcon() + "] " + description;
+ }
+}
diff --git a/src/main/java/duke/task/TaskList.java b/src/main/java/duke/task/TaskList.java
new file mode 100644
index 0000000000..9207a3920a
--- /dev/null
+++ b/src/main/java/duke/task/TaskList.java
@@ -0,0 +1,265 @@
+package duke.task;
+
+import duke.Duke;
+import duke.DukeException;
+import duke.datetime.DateTimeFormat;
+import duke.datetime.DateTimeUtility;
+
+import java.time.DateTimeException;
+import java.util.ArrayList;
+
+public class TaskList {
+ private ArrayList tasks;
+
+ public TaskList() {
+ this.tasks = new ArrayList<>();
+ }
+
+ /**
+ * Overloaded constructor to initialise taskList with tasks loaded from an existing log file.
+ *
+ * @param tasksStr
+ * @throws DukeException
+ */
+ public TaskList(ArrayList tasksStr) throws DukeException {
+ assert !tasksStr.isEmpty() : "tasksStr cannot be empty";
+
+ this.tasks = new ArrayList<>();
+ for (String taskStr : tasksStr) {
+ this.tasks.add(TaskList.parseTaskFromString(taskStr));
+ }
+ }
+
+ public boolean isEmpty() {
+ return tasks.isEmpty();
+ }
+
+ public int getNumberOfTasks() {
+ return tasks.size();
+ }
+
+ public String getTaskByIdx(int idx) {
+ return tasks.get(idx).toString();
+ }
+
+ /**
+ * Returns a string describing all the tasks in the taskList with a valid
+ * Date or DateTime that is before the Date or DateTime specified.
+ *
+ * Performs input validation on the input Date or DateTime string and throws a
+ * DukeException if it is of an invalid format.
+ *
+ * @param by
+ * @return
+ * @throws DukeException
+ */
+ public String filterTasksByDate(String by) throws DukeException {
+ assert !by.isEmpty() : "by cannot be empty";
+
+ if (DateTimeUtility.checkDateTimeType(by) != DateTimeFormat.String) {
+ ArrayList filtered = new ArrayList<>();
+
+ for (int i = 0; i < tasks.size(); i++) {
+ if (tasks.get(i) instanceof TimedTask) {
+ String taskBy = ((TimedTask)tasks.get(i)).getByString();
+ try {
+ if (DateTimeUtility.compare(by, taskBy) >= 0) {
+ filtered.add(tasks.get(i));
+ }
+ } catch (DateTimeException e) {}
+ }
+ }
+
+ return tasks2String(filtered);
+
+ } else {
+ throw new DukeException("U NID 2 GIV CORRECT DATE FOMAT!");
+ }
+ }
+
+ public String filterTasksByDescription(String description) {
+ assert !description.isEmpty() : "description cannot be empty";
+ ArrayList filtered = new ArrayList<>();
+ String descriptionLower = description.toLowerCase();
+
+ for (int i = 0; i < tasks.size(); i++) {
+ if (tasks.get(i).getDescription().toLowerCase().contains(descriptionLower)) {
+ filtered.add(tasks.get(i));
+ }
+ }
+
+ return tasks2String(filtered);
+ }
+
+ /**
+ * Adds a TODO task to the existing taskList. The task is marked as undone by default.
+ * Returns a string representation of the added task.
+ *
+ * @param name
+ * @return
+ */
+ public String addTodo(String name) {
+ assert !name.isEmpty() : "name cannot be empty";
+
+ Todo newTask = new Todo(name);
+ tasks.add(newTask);
+ return newTask.toString();
+ }
+
+ /**
+ * Adds a Deadline task to the existing taskList. The task is marked as undone by default.
+ * Returns a string representation of the added task.
+ *
+ * @param name
+ * @return
+ */
+ public String addDeadline(String name, String by) {
+ assert !name.isEmpty() : "name cannot be empty";
+ assert !by.isEmpty() : "by cannot be empty";
+
+ Deadline newTask = new Deadline(name, by);
+ tasks.add(newTask);
+ return newTask.toString();
+ }
+
+ /**
+ * Adds an Event task to the existing taskList. The task is marked as undone by default.
+ * Returns a string representation of the added task.
+ *
+ * @param name
+ * @return
+ */
+ public String addEvent(String name, String by) {
+ assert !name.isEmpty() : "name cannot be empty";
+ assert !by.isEmpty() : "by cannot be empty";
+
+ Event newTask = new Event(name, by);
+ tasks.add(newTask);
+ return newTask.toString();
+ }
+
+ /**
+ * Marks an existing task in the taskList as done, accessed by index. Performs input
+ * validation on the task index and throws a DukeException if the input is invalid.
+ *
+ * @param idx
+ * @throws DukeException
+ */
+ public void markTaskAsDone(int idx) throws DukeException {
+ if (idx < 0 || idx >= tasks.size()) {
+ throw new DukeException("U DOAN HAS TASK WIF DIS LABEL");
+ } else {
+ tasks.get(idx).setDone(true);
+ }
+ }
+
+ /**
+ * Updates an existing task in the taskList with a new description, accessed by index. Performs input
+ * validation on the task index and throws a DukeException if the input is invalid.
+ *
+ * @param idx
+ * @param newDescription
+ * @throws DukeException
+ */
+ public String updateTaskDescription(int idx, String newDescription) throws DukeException {
+ if (idx < 0 || idx >= tasks.size()) {
+ throw new DukeException("U DOAN HAS TASK WIF DIS LABEL");
+ } else {
+ tasks.get(idx).setDescription(newDescription);
+ return tasks.get(idx).toString();
+ }
+ }
+
+ /**
+ * Updates an existing task in the tasklist with a new date, accessed by index. Performs input
+ * validation on the task index and date and throws a DukeException if the input index is invalid,
+ * or if the task does not take in a date.
+ *
+ * @param idx
+ * @param newDate
+ * @throws DukeException
+ */
+
+ public String updateTaskDate(int idx, String newDate) throws DukeException {
+ if (idx < 0 || idx >= tasks.size()) {
+ throw new DukeException("U DOAN HAS TASK WIF DIS LABEL");
+ } else if (!(tasks.get(idx) instanceof TimedTask)) {
+ throw new DukeException("DIS TASK GOTS NO DATE FER UPDATE LULZ");
+ } else {
+ ((TimedTask)tasks.get(idx)).setByString(newDate);
+ return tasks.get(idx).toString();
+ }
+ }
+
+
+ /**
+ * Removes an existing task from the taskList by index and returns its string representation.
+ * Performs input validation on the specified index and throws a DukeException when index
+ * is invalid.
+ *
+ * @param idx
+ * @return
+ * @throws DukeException
+ */
+ public String popTask(int idx) throws DukeException {
+ if (tasks.isEmpty()) {
+ throw new DukeException("U CANT DELET ANYTHIN COZ U HAS NO TASKZ NAO LOLOL");
+ }
+ if (idx < 0 || idx >= tasks.size()) {
+ throw new DukeException("U DOAN HAS TASK WIF DIS LABEL");
+ } else {
+ return tasks.remove(idx).toString();
+ }
+ }
+
+ /**
+ * Parses an input string and returns a Task of the correct type, name, status and
+ * deadline (if available), otherwise throws a DukeException if input string is
+ * of the wrong format.
+ *
+ * @param str
+ * @return
+ * @throws DukeException
+ */
+ public static Task parseTaskFromString(String str) throws DukeException {
+ assert !str.isEmpty() : "str cannot be empty";
+
+ Task task;
+ String[] temp = str.split("]");
+ String taskType = String.valueOf(temp[0].charAt(1));
+ boolean isDone = (String.valueOf(temp[1].charAt(1)).equals(Task.ICON_TICK));
+
+ if (taskType.equals(Todo.TASK_ICON)) {
+ task = new Todo(temp[2].trim());
+ } else if (taskType.equals(Deadline.TASK_ICON)) {
+ String taskName = temp[2].split("\\(by:")[0].trim();
+ String by = temp[2].split(" \\(by:")[1].trim();
+ by = by.substring(0, by.length()-1);
+ task = new Deadline(taskName, by);
+ } else if (taskType.equals(Event.TASK_ICON)) {
+ String taskName = temp[2].split(" \\(at:")[0].trim();
+ String by = temp[2].split(" \\(at:")[1].trim();
+ by = by.substring(0, by.length()-1);
+ task = new Event(taskName, by);
+ } else {
+ throw new DukeException("TASK ICON NOT RECOGNISED");
+ }
+
+ task.setDone(isDone);
+ return task;
+ }
+
+ private static String tasks2String(ArrayList tasks) {
+ String ret = "";
+ for (int i = 0; i < tasks.size(); i++) {
+ ret += (i + 1) + ". " + tasks.get(i) + "\n";
+ }
+ return ret;
+ }
+
+ @Override
+ public String toString() {
+ return TaskList.tasks2String(this.tasks);
+
+ }
+}
diff --git a/src/main/java/duke/task/TimedTask.java b/src/main/java/duke/task/TimedTask.java
new file mode 100644
index 0000000000..eb2046b0bf
--- /dev/null
+++ b/src/main/java/duke/task/TimedTask.java
@@ -0,0 +1,32 @@
+package duke.task;
+
+
+import duke.datetime.DateTimeFormat;
+import duke.datetime.DateTimeUtility;
+
+public class TimedTask extends Task {
+ protected DateTimeFormat format;
+ protected String byString;
+
+ public TimedTask(String description, String by) {
+ super(description);
+ this.byString = by;
+ }
+
+ public void setByString(String byString) {
+ this.byString = byString;
+ }
+
+ public String formatBy() {
+ return DateTimeUtility.formatString(this.byString);
+ }
+
+ public String getByString() {
+ return byString;
+ }
+
+ @Override
+ public String toString() {
+ return super.toString();
+ }
+}
diff --git a/src/main/java/duke/task/Todo.java b/src/main/java/duke/task/Todo.java
new file mode 100644
index 0000000000..8d8474e2ec
--- /dev/null
+++ b/src/main/java/duke/task/Todo.java
@@ -0,0 +1,14 @@
+package duke.task;
+
+public class Todo extends Task {
+ public static final String TASK_ICON = "T";
+
+ public Todo(String description) {
+ super(description);
+ }
+
+ @Override
+ public String toString() {
+ return "[" + this.TASK_ICON + "]" + super.toString();
+ }
+}
diff --git a/src/main/java/duke/ui/Ui.java b/src/main/java/duke/ui/Ui.java
new file mode 100644
index 0000000000..663ad128ef
--- /dev/null
+++ b/src/main/java/duke/ui/Ui.java
@@ -0,0 +1,139 @@
+package duke.ui;
+
+import duke.task.TaskList;
+
+import java.util.Scanner;
+
+public class Ui {
+ private static final String LOGO =
+ " ,\n" +
+ " \\`-._ __\n" +
+ " \\\\ `-..____,.' `.\n" +
+ " :`. / \\`.\n" +
+ " : ) : : \\\n" +
+ " ;' ' ; | :\n" +
+ " ).. .. .:.`.; :\n" +
+ " /::... .:::... ` ;\n" +
+ " ; _ ' __ /:\\\n" +
+ " `:o> /\\o_> ;:. `.\n" +
+ " `-`.__ ; __..--- /:. \\\n" +
+ " === \\_/ ;=====_.':. ;\n" +
+ " ,/'`--'...`--.... ;\n" +
+ " ; ;\n" +
+ " .' ;\n" +
+ " .' ;\n" +
+ " .' .. , . ;\n" +
+ " : ::.. / ;::. |\n" +
+ " / `.;::. | ;:.. ;\n" +
+ " : |:. : ;:. ;\n" +
+ " : :: ;:.. |. ;\n" +
+ " : :; :::....| |\n" +
+ " /\\ ,/ \\ ;:::::; ;\n" +
+ " .:. \\:..| : ; '.--| ;\n" +
+ " ::. :'' `-.,,; ;' ; ;\n" +
+ ".-'. _.'\\ / `; \\,__: \\\n" +
+ "`---' `----' ; / \\,.,,,/\n" +
+ " `----` ";
+
+ private static final String LINE = "-------------------------------------------------------------------------------";
+
+ private String botName = "duke.Duke";
+ private String userName = "You";
+
+ private Scanner scanner;
+
+ public Ui() {
+ this.scanner = new Scanner(System.in);
+ }
+
+ /**
+ * Overloaded constructor for specifying name of the bot and user.
+ *
+ * @param botName
+ * @param userName
+ */
+ public Ui(String botName, String userName) {
+ this();
+ this.botName = botName;
+ this.userName = userName;
+ }
+
+ /**
+ * Prints the tasks currently existing in the taskList that is loaded from
+ * a log file.
+ *
+ * @param taskList
+ */
+ public void showLoadedTasks(TaskList taskList) {
+ System.out.println("\nTASKS LOADED FROM DATA FILE!!");
+ System.out.println(taskList);
+ this.showLine();
+ }
+
+ /**
+ * Prints a welcome message for the chat application.
+ *
+ */
+ public void showWelcome() {
+ System.out.println("\nOh hai kittehs! I r lolcatus. reziztents is fu... fut...\n"
+ + this.LOGO + "\n\n" + "reziztents dun werk.\n" + this.LINE);
+ }
+
+ /**
+ * Prompts the user to enter a command and returns a string for the user's input.
+ *
+ * @return
+ */
+ public String readCommand() {
+ System.out.print(this.userName + " sed: ");
+ return scanner.nextLine();
+ }
+
+ /**
+ * Prints a horizontal line.
+ */
+ public void showLine() {
+ System.out.println(this.LINE);
+ }
+
+ /**
+ * Formats and prints an error in the chat application.
+ *
+ * @param e
+ */
+ public void showError(String e) {
+ System.out.println(this.fmtMsg(botName + " HAZ FINDED ERRRR!\n" + "=> " + e));
+ }
+
+ /**
+ * Formats and displays desired output to the chat application.
+ *
+ * @param msg
+ */
+ public void display(String msg) {
+ System.out.println(this.fmtMsg(msg));
+ }
+
+ /**
+ * Displays error when tasks cannot be read from log file.
+ *
+ * @param e
+ */
+ public void showLoadingError(String e) {
+ System.out.println(this.fmtMsg("CANNOT LOAD TASKS FROM FILE uwu owo \n" + " " + e));
+ }
+
+ /**
+ * Formats messages to be sent to the chat application.
+ *
+ * @param msg
+ * @return
+ */
+ public String fmtMsg(String msg) {
+ return this.botName + " sed: " + msg;
+ }
+
+
+
+
+}
diff --git a/src/main/resources/images/DaDuke.jpeg b/src/main/resources/images/DaDuke.jpeg
new file mode 100644
index 0000000000..8c640b4526
Binary files /dev/null and b/src/main/resources/images/DaDuke.jpeg differ
diff --git a/src/main/resources/images/DaDuke.png b/src/main/resources/images/DaDuke.png
new file mode 100644
index 0000000000..ec536ad73b
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..3c82f45461
Binary files /dev/null and b/src/main/resources/images/DaUser.png differ
diff --git a/src/main/resources/view/DukeDialogBox.fxml b/src/main/resources/view/DukeDialogBox.fxml
new file mode 100644
index 0000000000..fe05f81e35
--- /dev/null
+++ b/src/main/resources/view/DukeDialogBox.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..1941abc15e
--- /dev/null
+++ b/src/main/resources/view/MainWindow.fxml
@@ -0,0 +1,19 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/main/resources/view/UserDialogBox.fxml b/src/main/resources/view/UserDialogBox.fxml
new file mode 100644
index 0000000000..57d5a57ec7
--- /dev/null
+++ b/src/main/resources/view/UserDialogBox.fxml
@@ -0,0 +1,16 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/test/java/datetime/DateTimeUtilityTest.java b/src/test/java/datetime/DateTimeUtilityTest.java
new file mode 100644
index 0000000000..e37f069e7c
--- /dev/null
+++ b/src/test/java/datetime/DateTimeUtilityTest.java
@@ -0,0 +1,15 @@
+package datetime;
+
+import duke.datetime.DateTimeUtility;
+import duke.datetime.DateTimeFormat;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+public class DateTimeUtilityTest {
+ @Test
+ public void checkDateTimeTypeTest() {
+ assertEquals(DateTimeUtility.checkDateTimeType("2020-04-05"),
+ DateTimeFormat.Date);
+ }
+}
diff --git a/src/test/java/duke/DukeTest.java b/src/test/java/duke/DukeTest.java
new file mode 100644
index 0000000000..44c39e03af
--- /dev/null
+++ b/src/test/java/duke/DukeTest.java
@@ -0,0 +1,11 @@
+package duke;
+
+import org.junit.jupiter.api.Test;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+public class DukeTest {
+ @Test
+ public void dummyTest(){
+ assertEquals(2, 2);
+ }
+}
diff --git a/src/test/java/parser/ParserTest.java b/src/test/java/parser/ParserTest.java
new file mode 100644
index 0000000000..aff8afcc3c
--- /dev/null
+++ b/src/test/java/parser/ParserTest.java
@@ -0,0 +1,23 @@
+package parser;
+
+import duke.DukeException;
+import duke.parser.Parser;
+import duke.command.Command;
+import duke.command.ListCommand;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.fail;
+
+public class ParserTest {
+ @Test
+ public void parseTest() {
+ try {
+ Command command = Parser.parse("list /by 2020-04-04");
+ Command actual = new ListCommand("2020-04-04");
+ assertEquals(command.toString(), actual.toString());
+ } catch (DukeException e) {
+ fail("Exception thrown! " + e);
+ }
+ }
+}
diff --git a/src/test/java/task/TaskListTest.java b/src/test/java/task/TaskListTest.java
new file mode 100644
index 0000000000..8aa89a3033
--- /dev/null
+++ b/src/test/java/task/TaskListTest.java
@@ -0,0 +1,25 @@
+package task;
+
+import duke.DukeException;
+import duke.task.Deadline;
+import duke.task.TaskList;
+import duke.task.Task;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.fail;
+
+public class TaskListTest {
+ @Test
+ public void parseTaskFromStringTest() {
+ try {
+ Task ret = TaskList.parseTaskFromString("[D][✓] hello (by: Apr 20 2020, 12:00:59 PM)");
+ Task actual = new Deadline("hello", "2020-04-20T12:00:59");
+ actual.setDone(true);
+ assertEquals(ret.toString(), actual.toString());
+ } catch (DukeException e) {
+ fail("Exception thrown! " + e);
+ }
+ }
+
+}
diff --git a/text-ui-test/ACTUAL.TXT b/text-ui-test/ACTUAL.TXT
new file mode 100644
index 0000000000..1681efc641
--- /dev/null
+++ b/text-ui-test/ACTUAL.TXT
@@ -0,0 +1,60 @@
+Hi, my nmae is
+ ____ _
+| _ \ _ _| | _____
+| | | | | | | |/ / _ \
+| |_| | |_| | < __/
+|____/ \__,_|_|\_\___|
+
+
+How can I hlep you taody?
+------------------------------------------------------------------
+You said: ------------------------------------------------------------------
+duke.Duke said: I hvae adedd a new tsak:
+ [T][✗] read book
+You now hvae 1 tskas in yuor lsit.
+------------------------------------------------------------------
+You said: ------------------------------------------------------------------
+duke.Duke said: I hvae adedd a new tsak:
+ [T][✗] eat lunch
+You now hvae 2 tskas in yuor lsit.
+------------------------------------------------------------------
+You said: ------------------------------------------------------------------
+duke.Duke said: I hvae adedd a new tsak:
+ [D][✗] finish book (by: Saturday)
+You now hvae 3 tskas in yuor lsit.
+------------------------------------------------------------------
+You said: ------------------------------------------------------------------
+duke.Duke said: I hvae adedd a new tsak:
+ [E][✗] project meeting (at: June 6th)
+You now hvae 4 tskas in yuor lsit.
+------------------------------------------------------------------
+You said: ------------------------------------------------------------------
+duke.Duke said:
+Hree are yuor tkass:
+1. [T][✗] read book
+2. [T][✗] eat lunch
+3. [D][✗] finish book (by: Saturday)
+4. [E][✗] project meeting (at: June 6th)
+------------------------------------------------------------------
+You said: ------------------------------------------------------------------
+duke.Duke said: Mkread tsak as cteopmle.
+ [T][✓] eat lunch
+------------------------------------------------------------------
+You said: ------------------------------------------------------------------
+duke.Duke said: Mkread tsak as cteopmle.
+ [D][✓] finish book (by: Saturday)
+------------------------------------------------------------------
+You said: ------------------------------------------------------------------
+duke.Duke said:
+Hree are yuor tkass:
+1. [T][✗] read book
+2. [T][✓] eat lunch
+3. [D][✓] finish book (by: Saturday)
+4. [E][✗] project meeting (at: June 6th)
+------------------------------------------------------------------
+You said: ------------------------------------------------------------------
+duke.Duke said: Nteod wtih tnakhs.
+------------------------------------------------------------------
+You said: ------------------------------------------------------------------
+duke.Duke said: Bye, hpoe to nveer see you aiagn.
+------------------------------------------------------------------
diff --git a/text-ui-test/EXPECTED.TXT b/text-ui-test/EXPECTED.TXT
index 657e74f6e7..1681efc641 100644
--- a/text-ui-test/EXPECTED.TXT
+++ b/text-ui-test/EXPECTED.TXT
@@ -1,7 +1,60 @@
-Hello from
- ____ _
+Hi, my nmae is
+ ____ _
| _ \ _ _| | _____
| | | | | | | |/ / _ \
| |_| | |_| | < __/
|____/ \__,_|_|\_\___|
+
+How can I hlep you taody?
+------------------------------------------------------------------
+You said: ------------------------------------------------------------------
+duke.Duke said: I hvae adedd a new tsak:
+ [T][✗] read book
+You now hvae 1 tskas in yuor lsit.
+------------------------------------------------------------------
+You said: ------------------------------------------------------------------
+duke.Duke said: I hvae adedd a new tsak:
+ [T][✗] eat lunch
+You now hvae 2 tskas in yuor lsit.
+------------------------------------------------------------------
+You said: ------------------------------------------------------------------
+duke.Duke said: I hvae adedd a new tsak:
+ [D][✗] finish book (by: Saturday)
+You now hvae 3 tskas in yuor lsit.
+------------------------------------------------------------------
+You said: ------------------------------------------------------------------
+duke.Duke said: I hvae adedd a new tsak:
+ [E][✗] project meeting (at: June 6th)
+You now hvae 4 tskas in yuor lsit.
+------------------------------------------------------------------
+You said: ------------------------------------------------------------------
+duke.Duke said:
+Hree are yuor tkass:
+1. [T][✗] read book
+2. [T][✗] eat lunch
+3. [D][✗] finish book (by: Saturday)
+4. [E][✗] project meeting (at: June 6th)
+------------------------------------------------------------------
+You said: ------------------------------------------------------------------
+duke.Duke said: Mkread tsak as cteopmle.
+ [T][✓] eat lunch
+------------------------------------------------------------------
+You said: ------------------------------------------------------------------
+duke.Duke said: Mkread tsak as cteopmle.
+ [D][✓] finish book (by: Saturday)
+------------------------------------------------------------------
+You said: ------------------------------------------------------------------
+duke.Duke said:
+Hree are yuor tkass:
+1. [T][✗] read book
+2. [T][✓] eat lunch
+3. [D][✓] finish book (by: Saturday)
+4. [E][✗] project meeting (at: June 6th)
+------------------------------------------------------------------
+You said: ------------------------------------------------------------------
+duke.Duke said: Nteod wtih tnakhs.
+------------------------------------------------------------------
+You said: ------------------------------------------------------------------
+duke.Duke said: Bye, hpoe to nveer see you aiagn.
+------------------------------------------------------------------
diff --git a/text-ui-test/input.txt b/text-ui-test/input.txt
index e69de29bb2..fbd639d50c 100644
--- a/text-ui-test/input.txt
+++ b/text-ui-test/input.txt
@@ -0,0 +1,10 @@
+todo read book
+todo eat lunch
+deadline finish book /by Saturday
+event project meeting /at June 6th
+list
+done 2
+done 3
+list
+hello
+bye
\ No newline at end of file
diff --git a/text-ui-test/runtest.bat b/text-ui-test/runtest.bat
index d0facc6310..00ed6a04cd 100644
--- a/text-ui-test/runtest.bat
+++ b/text-ui-test/runtest.bat
@@ -7,7 +7,7 @@ REM delete output from previous run
del ACTUAL.TXT
REM compile the code into the bin folder
-javac -cp ..\src -Xlint:none -d ..\bin ..\src\main\java\Duke.java
+javac -cp ..\src -Xlint:none -d ..\bin ..\src\main\java\duke.Duke.java
IF ERRORLEVEL 1 (
echo ********** BUILD FAILURE **********
exit /b 1
@@ -15,7 +15,7 @@ IF ERRORLEVEL 1 (
REM no error here, errorlevel == 0
REM run the program, feed commands from input.txt file and redirect the output to the ACTUAL.TXT
-java -classpath ..\bin Duke < input.txt > ACTUAL.TXT
+java -classpath ..\bin duke.Duke < input.txt > ACTUAL.TXT
REM compare the output to the expected output
FC ACTUAL.TXT EXPECTED.TXT
diff --git a/text-ui-test/runtest.sh b/text-ui-test/runtest.sh
old mode 100644
new mode 100755
index e169618a34..8433cd0b75
--- a/text-ui-test/runtest.sh
+++ b/text-ui-test/runtest.sh
@@ -13,7 +13,7 @@ then
fi
# compile the code into the bin folder, terminates if error occurred
-if ! javac -cp ../src -Xlint:none -d ../bin ../src/main/java/Duke.java
+if ! javac -cp ../src/main/java -Xlint:none -d ../bin ../src/main/java/Duke.java
then
echo "********** BUILD FAILURE **********"
exit 1