diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml
new file mode 100644
index 0000000000..c6b7ee2b1e
--- /dev/null
+++ b/.github/workflows/tests.yml
@@ -0,0 +1,50 @@
+name: Java CI
+
+on: [push, pull_request]
+
+jobs:
+ build:
+ strategy:
+ matrix:
+ platform: [ubuntu-latest, macos-latest, windows-latest]
+ runs-on: ${{ matrix.platform }}
+
+ steps:
+ - name: Set up repository
+ uses: actions/checkout@master
+
+ - name: Set up repository
+ uses: actions/checkout@master
+ with:
+ ref: master
+
+ - name: Merge to master
+ run: git checkout --progress --force ${{ github.sha }}
+
+ - name: Validate Gradle Wrapper
+ uses: gradle/wrapper-validation-action@v1
+
+ - name: Setup JDK 11
+ uses: actions/setup-java@v1
+ with:
+ java-version: '11'
+ java-package: jdk+fx
+
+ - name: Build and check with Gradle
+ run: ./gradlew check
+
+ - name: Perform IO redirection test (*NIX)
+ if: runner.os == 'Linux'
+ working-directory: ${{ github.workspace }}/text-ui-test
+ run: ./runtest.sh
+
+ - name: Perform IO redirection test (MacOS)
+ if: always() && runner.os == 'macOS'
+ working-directory: ${{ github.workspace }}/text-ui-test
+ run: ./runtest.sh
+
+ - name: Perform IO redirection test (Windows)
+ if: always() && runner.os == 'Windows'
+ working-directory: ${{ github.workspace }}/text-ui-test
+ shell: cmd
+ run: runtest.bat
diff --git a/.gitignore b/.gitignore
index f69985ef1f..0aad6ee8af 100644
--- a/.gitignore
+++ b/.gitignore
@@ -13,5 +13,11 @@ src/main/resources/docs/
*.iml
bin/
+# Testing files
+anthea.txt
+antheaNotes.txt
/text-ui-test/ACTUAL.txt
+/text-ui-test/ACTUAL1.txt
text-ui-test/EXPECTED-UNIX.TXT
+/text-ui-test/anthea.txt
+/text-ui-test/antheaNotes.txt
\ No newline at end of file
diff --git a/README.md b/README.md
index 8715d4d915..d2b690e1f9 100644
--- a/README.md
+++ b/README.md
@@ -1,24 +1,3 @@
-# Duke project template
+# Anthea - Chatbot Task Organiser
-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.
-
-## Setting up in Intellij
-
-Prerequisites: JDK 11, update Intellij to the most recent version.
-
-1. Open Intellij (if you are not in the welcome screen, click `File` > `Close Project` to close the existing project first)
-1. Open the project into Intellij as follows:
- 1. Click `Open`.
- 1. Select the project directory, and click `OK`.
- 1. If there are any further prompts, accept the defaults.
-1. Configure the project to use **JDK 11** (not other versions) as explained in [here](https://www.jetbrains.com/help/idea/sdk.html#set-up-jdk).
- In the same dialog, set the **Project language level** field to the `SDK default` option.
-3. After that, locate the `src/main/java/Duke.java` file, right-click it, and choose `Run Duke.main()` (if the code editor is showing compile errors, try restarting the IDE). If the setup is correct, you should see something like the below as the output:
- ```
- Hello from
- ____ _
- | _ \ _ _| | _____
- | | | | | | | |/ / _ \
- | |_| | |_| | < __/
- |____/ \__,_|_|\_\___|
- ```
+This is an implementation extending a project template for a greenfield Java project. It's named after the Java mascot _Duke_.
\ No newline at end of file
diff --git a/build.gradle b/build.gradle
new file mode 100644
index 0000000000..b0d88e2e39
--- /dev/null
+++ b/build.gradle
@@ -0,0 +1,63 @@
+plugins {
+ id 'java'
+ id 'application'
+ id 'com.github.johnrengelman.shadow' version '5.1.0'
+ id 'checkstyle'
+ id 'org.openjfx.javafxplugin' version '0.0.10'
+}
+
+checkstyle {
+ toolVersion = '10.2'
+}
+
+repositories {
+ mavenCentral()
+}
+
+dependencies {
+ 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'
+
+ 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'
+}
+
+test {
+ useJUnitPlatform()
+
+ testLogging {
+ events "passed", "skipped", "failed"
+
+ showExceptions true
+ exceptionFormat "full"
+ showCauses true
+ showStackTraces true
+ showStandardStreams = false
+ }
+}
+
+application {
+ mainClassName = "anthea.gui.Launcher"
+}
+
+shadowJar {
+ archiveBaseName = "Anthea"
+ archiveClassifier = null
+}
+
+run {
+ standardInput = System.in
+ enableAssertions = true
+}
diff --git a/config/checkstyle/checkstyle.xml b/config/checkstyle/checkstyle.xml
new file mode 100644
index 0000000000..870acebdda
--- /dev/null
+++ b/config/checkstyle/checkstyle.xml
@@ -0,0 +1,429 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/config/checkstyle/suppressions.xml b/config/checkstyle/suppressions.xml
new file mode 100644
index 0000000000..dcaa1af3c3
--- /dev/null
+++ b/config/checkstyle/suppressions.xml
@@ -0,0 +1,10 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/docs/README.md b/docs/README.md
index 8077118ebe..f3c77494ff 100644
--- a/docs/README.md
+++ b/docs/README.md
@@ -1,29 +1,290 @@
# User Guide
-## Features
+## Quick Start
-### Feature-ABC
+Get the JAR file, and place it in the directory of your choice. Run the JAR and you are set! On Windows, you may double-click the JAR to run it. On Mac, you can run `java -jar ./Anthea.jar` to avoid issues with saving data.
-Description of the feature.
+Console mode is also provided. You may run `java -jar ./Anthea.jar console` in Windows if you prefer using the console.
-### Feature-XYZ
+## Features
-Description of the feature.
+### Chatbot
+
+Anthea is chatbot-styled, where the commands are with specified words.
+
+### Tasks/Notes
+
+You can track your tasks, be them events, todos or deadlines. You can add notes too!
## Usage
-### `Keyword` - Describe action
+### Task Commands
+
+#### Making tasks
+
+##### `deadline DESCRIPTION [/by TIME]`
+
+Adds a deadline to track that has to be done by a certain `TIME`.
+
+**Example of usage:** `deadline Rush the holiday homework... /by 8-9 2018`
+
+This would make the deadline:
+
+```
+Good luck with the deadline, here's the task:
+[D][ ] Rush the holiday homework... (by: 08 Sep 2018 2359)
+```
+
+##### `event DESCRIPTION [/at TIME]`
+
+Adds an event to track that happens at a certain `TIME`.
+
+**Example of usage:** `event Prepare for the holiday season dinner /at 21st dec 2012`
+
+This would make the event:
+```
+That's going to happen at some time later:
+[E][ ] Prepare for the holiday season dinner (at: 21 Dec 2012 2359)
+```
+
+##### `todo DESCRIPTION`
+
+Adds a todo to track.
-Describe the action and its outcome.
+**Example of usage:** `todo Plan holiday dinner`
-Example of usage:
+This would make the todo:
+```
+I've recorded this thing you need to do:
+[T][ ] Plan holiday dinner
+```
-`keyword (optional arguments)`
+#### Modifying tasks
-Expected outcome:
+##### `delete INDEX`
-Description of the outcome.
+Delete task with index `INDEX`.
+**Example of usage:**
```
-expected output
+event Prepare for the holiday season dinner /at 21st dec 2012
+delete 1
+```
+
+This would delete the task:
```
+It seems you didn't need this task anymore, so I removed it:
+[E][ ] Prepare for the holiday season dinner (at: 21 Dec 2012 2359)
+You have 0 tasks left.
+```
+
+##### `mark INDEX`
+
+Mark the task with index `INDEX` as completed.
+
+**Example of usage:**
+```
+event Prepare for the holiday season dinner /at 21st dec 2012
+mark 1
+```
+
+This will mark the task as done:
+```
+Marked your task as done:
+[E][X] Prepare for the holiday season dinner (at: 21 Dec 2012 2359)
+```
+
+##### `reschedule INDEX [/at TIME] [/by TIME]`
+
+Reschedule the task. If the task is a deadline, `/by TIME` is used to reschedule. If the task is an event, `/at TIME` is used to reschedule. If the task is a todo, it doesn't have a time, so it cannot be rescheduled. You cannot reschedule your notes.
+
+**Example of usage:**
+```
+event Prepare for the holiday season dinner /at 21st dec 2012
+reschedule 1 /at 2012 20/12
+```
+
+This would reschedule the task:
+```
+I have rescheduled your task!
+[E][ ] Prepare for the holiday season dinner (at: 20 Dec 2012 2359)
+```
+
+##### `unmark INDEX`
+
+Unmark the task with index `INDEX`.
+
+**Example of usage:**
+```
+event Prepare for the holiday season dinner /at 21st dec 2012
+unmark 1
+```
+
+This would unmark the task:
+```
+Aw... it's not done yet:
+| [E][ ] Prepare for the holiday season dinne. (at: 21 Dec 2012 2359)
+```
+
+#### Viewing tasks
+
+##### `find DESCRIPTION`
+
+Find all tasks matching `DESCRIPTION`. The number next to the task is preserved.
+
+**Example of usage:**
+```
+event Prepare for the holiday season dinner /at 21st dec 2012
+find holiday
+```
+
+This would find these tasks:
+```
+Here are the tasks that you might be looking for:
+1.[E][X] Prepare for the holiday season dinner (at: 21 Dec 2012 2359)
+```
+
+##### `list`
+
+List all tasks.
+
+**Example of usage:**
+```
+event Prepare for the holiday season dinner /at 21st dec 2012
+list
+```
+
+This would list all tasks:
+```
+Here, your tasks:
+1.[E][X] Prepare for the holiday season dinner (at: 20 Dec 2012 2359)
+```
+
+### Note commands
+
+##### `delete note INDEX`
+
+Delete note with index `INDEX`.
+
+**Example of usage:**
+```
+note Delete this /content Something to forget.
+delete note 1
+```
+
+This would delete the note:
+```
+I removed this note:
+Delete this
+```
+
+##### `find notes DESCRIPTION`
+
+Find all notes where the title matches `DESCSRIPTION`. The number next to the note is preserved.
+
+**Example of usage:**
+```
+note Find this /content Something to find.
+find notes Find
+```
+
+This would find the note:
+```
+These notes match your query:
+1.Find this
+```
+
+##### `list notes`
+
+List all notes.
+
+**Example of usage:**
+```
+note Find this /content Something to find.
+list notes
+```
+
+This would list your notes:
+```
+Here, your notes:
+1.Find this
+```
+
+##### `note DESCRIPTION [/content CONTENT]`
+
+Adds a note titled `DESCRIPTION` with content `CONTENT`.
+
+**Example of usage:**
+```
+note Find this /content Something to find.
+```
+
+This would add a note:
+```
+Added your note about Find this.
+```
+
+##### `view note INDEX`
+
+View note with index `INDEX`. This shows the title and content.
+
+**Example of usage:**
+```
+note Find this /content Something to find.
+view note 1
+```
+
+This would view the note:
+```
+Here's the note:
+Find this
+Something to find.
+```
+
+### Other commands
+
+##### `bye`
+Close the application.
+
+### Command Glossary
+
+`CONTENT`/`DESCRIPTION`: Any ASCII string works, but ensure none of the words start with a forward slash, else it would be interpreted as a command modifier. Example: `grocery list` is fine but `groceries meat /fish` is not.
+
+`INDEX`: Any number works, as long it is the index of a task/note. Deleting a task/note will not preserve the index of other tasks/note behind it.
+
+`TIME`: Any string which contains the following keywords would be interpreted as a time if Anthea can resolve the time.
+* A time written as HH:MM (24 hours) or HH:MMam or HH:MMpm, or with a period instead of a colon.
+* A day of the week, full or abbreviated.
+* An ordinal, interpreted as the day of a month (e.g. 1st, 23rd).
+* A month name, full or abbreviated.
+* A date, written as D/M/Y or D-M-Y.
+* A year, written as Y.
+
+Time resolution proceeds as follows:
+* A time at or after the present is attempted to be found which matches all the keywords
+* If no such time exists, a time before the present but after 1st Jan 1AD 00:00
+* If no such time exists, the string is left intact for the benefit of the user.
+
+### Command Summary
+
+|Command|Description|
+|-|-|
+|`bye`|Close the application.|
+|`deadline DESCRIPTION [/by TIME]`|Adds a deadline.|
+|`delete INDEX`|Delete task.|
+|`delete note INDEX`|Delete note.|
+|`event DESCRIPTION [/at TIME]`|Adds an event.|
+|`find DESCRIPTION`|Find tasks.|
+|`find notes DESCRIPTION`|Find notes.|
+|`list`|List all tasks.|
+|`list notes`|List all notes.|
+|`todo DESCRIPTION`|Adds a todo to track.|
+|`mark INDEX`|Mark task as completed.|
+|`note DESCRIPTION [/content CONTENT]`|Adds a note.|
+|`reschedule INDEX [/at TIME] [/by TIME]`|Reschedule the task.|
+|`unmark INDEX`|Unmark task.|
+|`view note INDEX`|View note.|
+
+
+### Data
+The data for the application is stored in `anthea.txt` and `antheaNotes.txt` in CSV using base64. They may be deleted to purge data, or backups may be made of them.
diff --git a/docs/Ui.png b/docs/Ui.png
new file mode 100644
index 0000000000..d6293dc2e6
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..c14115d7e0
--- /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
\ No newline at end of file
diff --git a/gradlew b/gradlew
new file mode 100755
index 0000000000..645f6ca315
--- /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" "$@"
\ No newline at end of file
diff --git a/gradlew.bat b/gradlew.bat
new file mode 100644
index 0000000000..a14a3d051d
--- /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
\ 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/anthea/Anthea.java b/src/main/java/anthea/Anthea.java
new file mode 100644
index 0000000000..051dae291c
--- /dev/null
+++ b/src/main/java/anthea/Anthea.java
@@ -0,0 +1,83 @@
+package anthea;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Optional;
+
+import anthea.note.NoteList;
+import anthea.task.TaskList;
+
+/**
+ * The main method of the chatbot, as well as its startup and teardown.
+ */
+public class Anthea {
+ /** List of commands */
+ private static ArrayList commands;
+ private static UiInterface ui = new ConsoleUi();
+
+ /**
+ * Sets the current UI.
+ *
+ * @param ui The current UI to use.
+ */
+ public static void setUi(UiInterface ui) {
+ assert ui != null;
+ Anthea.ui = ui;
+ }
+
+ /**
+ * Gets the current UI to interact with.
+ *
+ * @return UiInterface that helps display text to screen.
+ */
+ public static UiInterface getUi() {
+ assert ui != null;
+ return ui;
+ }
+
+ private static ChatbotResponse handleCommand(String command) {
+ Optional response = Optional.empty();
+ for (CommandMatcher matcher : commands) {
+ response = response.or(() -> matcher.run(command));
+ }
+ return response.orElse(new ChatbotResponse("I cannot figure out this command..."));
+ }
+
+ /**
+ * Runs the chatbot execution.
+ *
+ * @param args Command line args which are not used.
+ */
+ public static void main(String[] args) {
+ // initialization
+ ui.greet();
+ TaskList.initializeTaskList();
+ NoteList.initializeNoteList();
+ commands = Parser.getCommands();
+ BufferedReader input = new BufferedReader(ui.getReader());
+
+ // main application logic
+ boolean isStillRunning = true;
+ while (isStillRunning) {
+ String command;
+ try {
+ command = input.readLine();
+ } catch (IOException ex) {
+ System.out.println("IOException in application logic - terminating");
+ throw new RuntimeException(ex);
+ }
+ if (command.equals("bye")) {
+ isStillRunning = false;
+ } else {
+ ChatbotResponse response = handleCommand(command);
+ response.print(ui); // ensure response is printed
+ }
+ }
+
+ // finalization
+ TaskList.finalizeTaskList();
+ NoteList.finalizeNoteList();
+ ui.leave();
+ }
+}
diff --git a/src/main/java/anthea/ChatbotResponse.java b/src/main/java/anthea/ChatbotResponse.java
new file mode 100644
index 0000000000..3201d7844e
--- /dev/null
+++ b/src/main/java/anthea/ChatbotResponse.java
@@ -0,0 +1,46 @@
+package anthea;
+
+import java.util.function.Consumer;
+
+/**
+ * Stores an output to print once.
+ */
+public class ChatbotResponse {
+ private Consumer responsePrinter;
+ private boolean hasPrinted = false;
+
+ /**
+ * Constructs a wrapper for the output,
+ * so it prints only once.
+ *
+ * @param output Output to print only once.
+ */
+ public ChatbotResponse(String... output) {
+ responsePrinter = (ui) -> ui.printStyledMessage(output);
+ }
+
+ /**
+ * Constructs a wrapper for the output,
+ * so it prints only once.
+ *
+ * @param printer Method to print only once.
+ */
+ public ChatbotResponse(Consumer printer) {
+ assert printer != null;
+ this.responsePrinter = printer;
+ }
+
+ /**
+ * Prints the output if not printed yet.
+ *
+ * @param ui User interface to use.
+ */
+ public void print(UiInterface ui) {
+ assert ui != null;
+ if (hasPrinted) {
+ return;
+ }
+ responsePrinter.accept(ui);
+ hasPrinted = true;
+ }
+}
diff --git a/src/main/java/anthea/CommandMatcher.java b/src/main/java/anthea/CommandMatcher.java
new file mode 100644
index 0000000000..68e34e9720
--- /dev/null
+++ b/src/main/java/anthea/CommandMatcher.java
@@ -0,0 +1,50 @@
+package anthea;
+
+import java.util.function.Function;
+import java.util.function.Predicate;
+
+import anthea.exception.ChatbotExceptionFunction;
+
+/**
+ * This class serves as a way to abstract the idea of making a command
+ * as a matching process and an action.
+ */
+public class CommandMatcher extends StringMatcher {
+
+ /**
+ * Constructs an object that handles checking and executing a command.
+ *
+ * @param shouldRunAction Predicate to check if the command should be run.
+ * @param action Action to run.
+ */
+ public CommandMatcher(Predicate shouldRunAction, Function action) {
+ super(shouldRunAction, action);
+ assert shouldRunAction != null;
+ assert action != null;
+ }
+
+ /**
+ * Constructs an object that handles checking and executing a command.
+ *
+ * @param shouldRunAction Predicate to check if the command should be run.
+ * @param action Action to run.
+ * @return Constructed CommandMatcher.
+ */
+ public static CommandMatcher of(Predicate shouldRunAction, ChatbotExceptionFunction action) {
+ return new CommandMatcher(shouldRunAction, ChatbotExceptionFunction.toFunction(action));
+ }
+
+ /**
+ * Constructs an object that handles checking and executing a command.
+ *
+ * @param prefix Prefix of the command which is checked.
+ * @param action Action to run.
+ * @return Constructed CommandMatcher.
+ */
+ public static CommandMatcher of(String prefix, ChatbotExceptionFunction action) {
+ assert prefix != null;
+ assert action != null;
+ return new CommandMatcher((cmd) -> cmd.strip().startsWith(prefix),
+ ChatbotExceptionFunction.toFunction(action));
+ }
+}
diff --git a/src/main/java/anthea/Commands.java b/src/main/java/anthea/Commands.java
new file mode 100644
index 0000000000..1d5a2cb6a6
--- /dev/null
+++ b/src/main/java/anthea/Commands.java
@@ -0,0 +1,244 @@
+package anthea;
+
+import java.util.List;
+
+import anthea.note.Note;
+import anthea.note.NoteList;
+import anthea.task.Deadline;
+import anthea.task.Event;
+import anthea.task.Task;
+import anthea.task.TaskList;
+import anthea.task.ToDo;
+
+/**
+ * Creates commands.
+ */
+public class Commands {
+ /**
+ * Creates the command to add deadlines.
+ * @return Command for adding deadlines.
+ */
+ public static CommandMatcher getAddDeadlineCommand() {
+ return new PrefixCommandMatcher("deadline", (str, map) -> {
+ assert str != null;
+ assert map != null;
+ Task task = new Deadline(str, map.getOrDefault("by", "[unknown]"));
+ TaskList.getTaskList().add(task);
+ return new ChatbotResponse(
+ "Good luck with the deadline, here's the task:",
+ task.toString());
+ });
+ }
+
+ /**
+ * Creates the command to add events.
+ * @return Command for adding events.
+ */
+ public static CommandMatcher getAddEventCommand() {
+ return new PrefixCommandMatcher("event", (str, map) -> {
+ assert str != null;
+ assert map != null;
+ Task task = new Event(str, map.getOrDefault("at", "[unknown]"));
+ TaskList.getTaskList().add(task);
+ return new ChatbotResponse(
+ "That's going to happen at some time later:",
+ task.toString());
+ });
+ }
+
+ /**
+ * Creates the command to add tasks.
+ * @return Command for adding tasks.
+ */
+ public static CommandMatcher getAddToDoCommand() {
+ return new PrefixCommandMatcher("todo", (str, map) -> {
+ assert str != null;
+ assert map != null;
+ Task task = new ToDo(str);
+ TaskList.getTaskList().add(task);
+ return new ChatbotResponse(
+ "I've recorded this thing you need to do:",
+ task.toString());
+ });
+ }
+
+ /**
+ * Creates the command to mark tasks as done.
+ * @return Command for marking tasks as done.
+ */
+ public static CommandMatcher getMarkCommand() {
+ return PrefixCommandMatcher.of("mark", (str, map) -> {
+ assert str != null;
+ assert map != null;
+ Task task = TaskList.getTask(str);
+ task.markAsDone();
+ return new ChatbotResponse(
+ "Marked your task as done:",
+ task.toString());
+ });
+ }
+
+ /**
+ * Creates the command to unmark tasks as done.
+ * @return Command for unmarking tasks as done.
+ */
+ public static CommandMatcher getUnmarkCommand() {
+ return PrefixCommandMatcher.of("unmark", (str, map) -> {
+ assert str != null;
+ assert map != null;
+ Task task = TaskList.getTask(str);
+ task.markAsNotDone();
+ return new ChatbotResponse(
+ "Aw... it's not done yet:",
+ task.toString());
+ });
+ }
+
+ /**
+ * Creates the command to delete tasks.
+ * @return Command for deleting tasks.
+ */
+ public static CommandMatcher getDeleteCommand() {
+ return PrefixCommandMatcher.of("delete", (str, map) -> {
+ assert str != null;
+ assert map != null;
+ Task task = TaskList.getTask(str);
+ TaskList.getTaskList().remove(task);
+ return new ChatbotResponse(
+ "It seems you didn't need this task anymore, so I removed it:",
+ task.toString(),
+ String.format("You have %d tasks left.", TaskList.getTaskList().size()));
+ });
+ }
+
+ /**
+ * Creates the command to reschedule.
+ * @return Command for rescheduling.
+ */
+ public static CommandMatcher getRescheduleCommand() {
+ return PrefixCommandMatcher.of("reschedule", (str, map) -> {
+ assert str != null;
+ assert map != null;
+ Task task = TaskList.getTask(str);
+ if (task instanceof ToDo) {
+ return new ChatbotResponse("That's a todo, it doesn't have a date.");
+ }
+ Task newTask;
+ if (task instanceof Event) {
+ if (!map.containsKey("at")) {
+ return new ChatbotResponse("Do specify /at for events.");
+ }
+ newTask = new Event(task.getDescription(), map.get("at"), task.isTaskDone());
+ } else if (task instanceof Deadline) {
+ if (!map.containsKey("by")) {
+ return new ChatbotResponse("Do specify /by for deadlines.");
+ }
+ newTask = new Deadline(task.getDescription(), map.get("by"), task.isTaskDone());
+ } else {
+ return new ChatbotResponse("This is a strange task - I don't recognise it.");
+ }
+ List tasks = TaskList.getTaskList();
+ tasks.set(tasks.indexOf(task), newTask);
+ return new ChatbotResponse(
+ "I have rescheduled your task!",
+ newTask.toString());
+ });
+ }
+
+ /**
+ * Creates the command to list tasks.
+ * @return Command for listing tasks.
+ */
+ public static CommandMatcher getListTasksCommand() {
+ return new CommandMatcher(str -> str.equals("list"), (str) -> {
+ assert str != null;
+ List tasks = TaskList.getTaskList();
+ return new ChatbotResponse(
+ Parser.listObjects("Here, your tasks:", tasks));
+ });
+ }
+
+ /**
+ * Creates the command to find tasks.
+ * @return Command for finding tasks.
+ */
+ public static CommandMatcher getFindTasksCommand() {
+ return new PrefixCommandMatcher("find", (str, map) -> {
+ assert str != null;
+ assert map != null;
+ List> tasks = TaskList.filterTasks(str);
+ return new ChatbotResponse(
+ Parser.listNumberedObjects("Here are the tasks that you might be looking for:", tasks));
+ });
+ }
+
+ /**
+ * Creates the command to list notes.
+ * @return Command for listing notes.
+ */
+ public static CommandMatcher getListNotesCommand() {
+ return new CommandMatcher(str -> str.equals("list notes"), (str) -> {
+ assert str != null;
+ List notes = NoteList.getNoteList();
+ return new ChatbotResponse(
+ Parser.listObjects("Here, your notes:", notes));
+ });
+ }
+
+ /**
+ * Creates the command to find notes.
+ * @return Command for finding notes.
+ */
+ public static CommandMatcher getFindNotesCommand() {
+ return new PrefixCommandMatcher("find notes", (str, map) -> {
+ assert str != null;
+ assert map != null;
+ List> notes = NoteList.filterNotes(str);
+ return new ChatbotResponse(
+ Parser.listNumberedObjects("These notes match your query:", notes));
+ });
+ }
+
+ /**
+ * Creates the command to view notes.
+ * @return Command for viewing notes.
+ */
+ public static CommandMatcher getViewNoteCommand() {
+ return PrefixCommandMatcher.of("view note", (str, map) -> {
+ assert str != null;
+ assert map != null;
+ Note note = NoteList.getNote(str);
+ return new ChatbotResponse("Here's the note:", note.getTitle(), note.getContent());
+ });
+ }
+
+ /**
+ * Creates the command to delete notes.
+ * @return Command for deleting notes.
+ */
+ public static CommandMatcher getDeleteNoteCommand() {
+ return PrefixCommandMatcher.of("delete note", (str, map) -> {
+ assert str != null;
+ assert map != null;
+ Note note = NoteList.getNote(str);
+ NoteList.getNoteList().remove(note);
+ return new ChatbotResponse(
+ "I removed this note:",
+ note.toString());
+ });
+ }
+
+ /**
+ * Creates the command to add notes.
+ * @return Command for add notes.
+ */
+ public static CommandMatcher getAddNoteCommand() {
+ return PrefixCommandMatcher.of("note", (str, map) -> {
+ assert str != null;
+ assert map != null;
+ Note note = new Note(str, map.getOrDefault("content", "[EMPTY]"));
+ NoteList.getNoteList().add(note);
+ return new ChatbotResponse(String.format("Added your note about %s.", str));
+ });
+ }
+}
diff --git a/src/main/java/anthea/ConsoleUi.java b/src/main/java/anthea/ConsoleUi.java
new file mode 100644
index 0000000000..d7e3bbb792
--- /dev/null
+++ b/src/main/java/anthea/ConsoleUi.java
@@ -0,0 +1,53 @@
+package anthea;
+
+import java.io.InputStreamReader;
+import java.io.Reader;
+
+/**
+ * Handles console-based user interface. Used for sanity checks.
+ */
+public class ConsoleUi implements UiInterface {
+ private static final String UPPER_BAR = ",----------------------------------------------------------------";
+ private static final String LOWER_BAR = "'----------------------------------------------------------------";
+ private static final String LEFT_BAR = "| ";
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public Reader getReader() {
+ return new InputStreamReader(System.in);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void printStyledMessage(String... lines) {
+ System.out.println(UPPER_BAR);
+ for (String str : lines) {
+ assert str != null;
+ System.out.print(LEFT_BAR);
+ System.out.println(str);
+ }
+ System.out.println(LOWER_BAR);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void greet() {
+ printStyledMessage("...where is this again?",
+ "Oh, hello, I didn't see you there - I'm Anthea, a chatbot...",
+ "...or at least that's what they told me.");
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void leave() {
+ printStyledMessage("It was nice to have you around, I'm going back to sleep...");
+ }
+}
diff --git a/src/main/java/anthea/Pair.java b/src/main/java/anthea/Pair.java
new file mode 100644
index 0000000000..aa4110923e
--- /dev/null
+++ b/src/main/java/anthea/Pair.java
@@ -0,0 +1,41 @@
+package anthea;
+
+/**
+ * Stores a pair.
+ *
+ * @param Type of first member of pair.
+ * @param Type of second member of pair.
+ */
+public class Pair {
+ private U u;
+ private V v;
+
+ /**
+ * Constructs a pair.
+ *
+ * @param u First member of the pair.
+ * @param v Second member of the pair.
+ */
+ public Pair(U u, V v) {
+ this.u = u;
+ this.v = v;
+ }
+
+ /**
+ * Gets the first member of the pair.
+ *
+ * @return First member of the pair.
+ */
+ public U getFirst() {
+ return this.u;
+ }
+
+ /**
+ * Gets the second member of the pair.
+ *
+ * @return Second member of the pair.
+ */
+ public V getSecond() {
+ return this.v;
+ }
+}
diff --git a/src/main/java/anthea/ParsedDateTime.java b/src/main/java/anthea/ParsedDateTime.java
new file mode 100644
index 0000000000..a7ade9d55e
--- /dev/null
+++ b/src/main/java/anthea/ParsedDateTime.java
@@ -0,0 +1,722 @@
+package anthea;
+
+import java.time.DateTimeException;
+import java.time.DayOfWeek;
+import java.time.LocalDateTime;
+import java.time.Month;
+import java.time.format.DateTimeFormatter;
+import java.time.format.DateTimeParseException;
+import java.time.temporal.ChronoField;
+import java.time.temporal.ChronoUnit;
+import java.time.temporal.Temporal;
+import java.time.temporal.TemporalAdjuster;
+import java.time.temporal.TemporalAdjusters;
+import java.util.ArrayList;
+import java.util.Optional;
+import java.util.function.Predicate;
+
+/**
+ * Helper class to parse date/time strings
+ */
+public class ParsedDateTime {
+ private static final DateTimeFormatter[] formatters = {
+ DateTimeFormatter.ofPattern("dd MMM yyyy HHmm"),
+ DateTimeFormatter.ofPattern("yyyy-MM-dd"), DateTimeFormatter.ofPattern("d/M/yyyy HHmm"),
+ DateTimeFormatter.BASIC_ISO_DATE, DateTimeFormatter.ISO_LOCAL_DATE, DateTimeFormatter.ISO_OFFSET_DATE,
+ DateTimeFormatter.ISO_DATE, DateTimeFormatter.ISO_LOCAL_TIME, DateTimeFormatter.ISO_OFFSET_TIME,
+ DateTimeFormatter.ISO_TIME, DateTimeFormatter.ISO_LOCAL_DATE_TIME, DateTimeFormatter.ISO_OFFSET_DATE_TIME,
+ DateTimeFormatter.ISO_ZONED_DATE_TIME, DateTimeFormatter.ISO_DATE_TIME, DateTimeFormatter.ISO_ORDINAL_DATE,
+ DateTimeFormatter.ISO_WEEK_DATE, DateTimeFormatter.ISO_INSTANT, DateTimeFormatter.RFC_1123_DATE_TIME };
+ private static final ArrayList> naturalDateParsers;
+ private static final ArrayList> naturalDateLatestTimeParsers;
+
+ private static final int BIG_NUMBER_OF_ITERATIONS = 1024;
+ private static final int HOURS_PER_DAY = 24;
+ private static final int MINUTES_PER_HOUR = 60;
+ private static final int DATE_29 = 29;
+ private static final int MONTH_FEB = 2;
+
+ static {
+ naturalDateParsers = new ArrayList<>();
+ naturalDateParsers.add(new StringMatcher<>(
+ StringMatcher.getCaseInsensitiveMatcher(new String[]{ "mon", "monday" }),
+ s -> getDayOfWeekTemporalAdjuster(DayOfWeek.MONDAY)));
+ naturalDateParsers.add(new StringMatcher<>(
+ StringMatcher.getCaseInsensitiveMatcher(new String[]{ "tue", "tuesday" }),
+ s -> getDayOfWeekTemporalAdjuster(DayOfWeek.TUESDAY)));
+ naturalDateParsers.add(new StringMatcher<>(
+ StringMatcher.getCaseInsensitiveMatcher(new String[]{ "wed", "wednesday" }),
+ s -> getDayOfWeekTemporalAdjuster(DayOfWeek.WEDNESDAY)));
+ naturalDateParsers.add(new StringMatcher<>(
+ StringMatcher.getCaseInsensitiveMatcher(new String[]{ "thur", "thursday" }),
+ s -> getDayOfWeekTemporalAdjuster(DayOfWeek.THURSDAY)));
+ naturalDateParsers.add(new StringMatcher<>(
+ StringMatcher.getCaseInsensitiveMatcher(new String[]{ "fri", "friday" }),
+ s -> getDayOfWeekTemporalAdjuster(DayOfWeek.FRIDAY)));
+ naturalDateParsers.add(new StringMatcher<>(
+ StringMatcher.getCaseInsensitiveMatcher(new String[]{ "sat", "saturday" }),
+ s -> getDayOfWeekTemporalAdjuster(DayOfWeek.SATURDAY)));
+ naturalDateParsers.add(new StringMatcher<>(
+ StringMatcher.getCaseInsensitiveMatcher(new String[]{ "sun", "sunday" }),
+ s -> getDayOfWeekTemporalAdjuster(DayOfWeek.SUNDAY)));
+ naturalDateParsers.add(new StringMatcher<>(
+ StringMatcher.getCaseInsensitiveMatcher(new String[]{ "jan", "january" }),
+ s -> getMonthTemporalAdjuster(Month.JANUARY)));
+ naturalDateParsers.add(new StringMatcher<>(
+ StringMatcher.getCaseInsensitiveMatcher(new String[]{ "feb", "february" }),
+ s -> getMonthTemporalAdjuster(Month.FEBRUARY)));
+ naturalDateParsers.add(new StringMatcher<>(
+ StringMatcher.getCaseInsensitiveMatcher(new String[]{ "mar", "march" }),
+ s -> getMonthTemporalAdjuster(Month.MARCH)));
+ naturalDateParsers.add(new StringMatcher<>(
+ StringMatcher.getCaseInsensitiveMatcher(new String[]{ "apr", "april" }),
+ s -> getMonthTemporalAdjuster(Month.APRIL)));
+ naturalDateParsers.add(new StringMatcher<>(
+ StringMatcher.getCaseInsensitiveMatcher(new String[]{ "may" }),
+ s -> getMonthTemporalAdjuster(Month.MAY)));
+ naturalDateParsers.add(new StringMatcher<>(
+ StringMatcher.getCaseInsensitiveMatcher(new String[]{ "jun", "june" }),
+ s -> getMonthTemporalAdjuster(Month.JUNE)));
+ naturalDateParsers.add(new StringMatcher<>(
+ StringMatcher.getCaseInsensitiveMatcher(new String[]{ "jul", "july" }),
+ s -> getMonthTemporalAdjuster(Month.JULY)));
+ naturalDateParsers.add(new StringMatcher<>(
+ StringMatcher.getCaseInsensitiveMatcher(new String[]{ "aug", "august" }),
+ s -> getMonthTemporalAdjuster(Month.AUGUST)));
+ naturalDateParsers.add(new StringMatcher<>(
+ StringMatcher.getCaseInsensitiveMatcher(new String[]{ "sep", "september" }),
+ s -> getMonthTemporalAdjuster(Month.SEPTEMBER)));
+ naturalDateParsers.add(new StringMatcher<>(
+ StringMatcher.getCaseInsensitiveMatcher(new String[]{ "oct", "october" }),
+ s -> getMonthTemporalAdjuster(Month.OCTOBER)));
+ naturalDateParsers.add(new StringMatcher<>(
+ StringMatcher.getCaseInsensitiveMatcher(new String[]{ "nov", "november" }),
+ s -> getMonthTemporalAdjuster(Month.NOVEMBER)));
+ naturalDateParsers.add(new StringMatcher<>(
+ StringMatcher.getCaseInsensitiveMatcher(new String[]{ "dec", "december" }),
+ s -> getMonthTemporalAdjuster(Month.DECEMBER)));
+ String[] dates = new String[38];
+ for (int i = 1; i <= 31; i++) {
+ // 1th included, this is a feature
+ dates[i - 1] = String.format("%dth", i);
+ }
+ // custom ordinals
+ dates[31] = "1st";
+ dates[32] = "2nd";
+ dates[33] = "3rd";
+ dates[34] = "21st";
+ dates[35] = "22nd";
+ dates[36] = "23rd";
+ dates[37] = "31st";
+ naturalDateParsers.add(new StringMatcher<>(
+ StringMatcher.getCaseInsensitiveMatcher(dates),
+ s -> {
+ boolean isTwoDigit = '0' <= s.charAt(1) && s.charAt(1) <= '9';
+ int date = Integer.parseInt(s.substring(0, 1));
+ if (isTwoDigit) {
+ date = Integer.parseInt(s.substring(0, 2));
+ }
+ return getDateTemporalAdjuster(date);
+ }));
+ Predicate isDate = s -> {
+ String[] parts = s.split("/");
+ if (parts.length == 2 || parts.length == 3) {
+ for (String part : parts) {
+ if (!isNumeric(part)) {
+ return false;
+ }
+ }
+ return true;
+ }
+ parts = s.split("-");
+ if (parts.length == 2 || parts.length == 3) {
+ for (String part : parts) {
+ if (!isNumeric(part)) {
+ return false;
+ }
+ }
+ return true;
+ }
+ return false;
+ };
+ naturalDateParsers.add(new StringMatcher<>(
+ isDate,
+ s -> {
+ try {
+ int date = -1;
+ int month = -1;
+ int year = -1;
+ String[] parts = s.split("/");
+ if (parts.length == 2 || parts.length == 3) {
+ date = Integer.parseInt(parts[0]);
+ month = Integer.parseInt(parts[1]);
+ if (parts.length == 3) {
+ year = Integer.parseInt(parts[2]);
+ }
+ }
+ parts = s.split("-");
+ if (parts.length == 2 || parts.length == 3) {
+ date = Integer.parseInt(parts[0]);
+ month = Integer.parseInt(parts[1]);
+ if (parts.length == 3) {
+ year = Integer.parseInt(parts[2]);
+ }
+ }
+ if (year == -1) {
+ return getDateMonthTemporalAdjuster(date, month);
+ }
+ return getDateMonthYearTemporalAdjuster(date, month, year);
+ } catch (NumberFormatException ex) {
+ return temporal -> {
+ throw new DateTimeException("Parse error for date");
+ };
+ }
+ }));
+ naturalDateParsers.add(new StringMatcher<>(
+ ParsedDateTime::isNumeric,
+ s -> {
+ try {
+ int year = Integer.parseInt(s);
+ if (year < 1) {
+ return temporal -> {
+ throw new DateTimeException("No negative years");
+ };
+ }
+ return getYearTemporalAdjuster(year);
+ } catch (NumberFormatException ex) {
+ return temporal -> {
+ throw new DateTimeException("Parse error for year");
+ };
+ }
+ }));
+ Predicate isTime = s -> {
+ String[] parts = s.split("[:.]");
+ if (parts.length != 2) {
+ return false;
+ }
+ if (!isNumeric(parts[0])) {
+ return false;
+ }
+ if (isNumeric(parts[1])) {
+ return true;
+ }
+ if (parts[1].length() < 3) {
+ return false;
+ }
+ if (!isNumeric(parts[1].substring(0, parts[1].length() - 2))) {
+ return false;
+ }
+ String suffix = parts[1].substring(parts[1].length() - 2)
+ .toLowerCase();
+ return suffix.equals("am") || suffix.equals("pm");
+ };
+ naturalDateParsers.add(new StringMatcher<>(
+ isTime,
+ s -> {
+ try {
+ String[] parts = s.split("[:.]");
+ int hour = Integer.parseInt(parts[0]);
+ int minute;
+ boolean isInvalidHour;
+ if (isNumeric(parts[1])) {
+ minute = Integer.parseInt(parts[1]);
+ isInvalidHour = hour < 0 || hour >= HOURS_PER_DAY;
+ } else {
+ minute = Integer.parseInt(parts[1].substring(0, parts[1].length() - 2));
+ String suffix = parts[1].substring(parts[1].length() - 2)
+ .toLowerCase();
+ isInvalidHour = hour <= 0 || hour > 12;
+ if (suffix.equals("am")) {
+ if (hour == 12) {
+ hour = 0;
+ }
+ } else if (suffix.equals("pm")) {
+ if (hour != 12) {
+ hour += 12;
+ }
+ }
+ }
+ boolean isInvalidMinute = minute < 0 || minute >= MINUTES_PER_HOUR;
+ if (isInvalidHour || isInvalidMinute) {
+ return temporal -> {
+ throw new DateTimeException("Invalid hour/minute for time");
+ };
+ }
+ return getTimeTemporalAdjuster(hour, minute);
+ } catch (NumberFormatException ex) {
+ return temporal -> {
+ throw new DateTimeException("Parse error for time");
+ };
+ }
+ }));
+ TemporalAdjuster lastTimeOfDay = temporal ->
+ LocalDateTime.from(temporal).withHour(23).withMinute(59).withSecond(0).withNano(0);
+ TemporalAdjuster lastTimeOfMonth = temporal ->
+ LocalDateTime.from(temporal).with(TemporalAdjusters.lastDayOfMonth()).with(lastTimeOfDay);
+ TemporalAdjuster lastTimeOfYear = temporal ->
+ LocalDateTime.from(temporal).with(TemporalAdjusters.lastDayOfYear()).with(lastTimeOfDay);
+ naturalDateLatestTimeParsers = new ArrayList<>();
+ String[] weekdays = new String[]{
+ "mon", "monday", "tue", "tuesday", "wed", "wednesday", "thur", "thursday", "fri", "friday",
+ "sat", "saturday", "sun", "sunday" };
+ String[] months = new String[]{
+ "jan", "january", "feb", "february", "mar", "march",
+ "apr", "april", "may", "jun", "june",
+ "jul", "july", "aug", "august", "sep", "september",
+ "oct", "october", "nov", "november", "dec", "december" };
+ naturalDateLatestTimeParsers.add(new StringMatcher<>(
+ StringMatcher.getCaseInsensitiveMatcher(weekdays),
+ s -> lastTimeOfDay));
+ naturalDateLatestTimeParsers.add(new StringMatcher<>(
+ StringMatcher.getCaseInsensitiveMatcher(months),
+ s -> lastTimeOfMonth));
+ naturalDateLatestTimeParsers.add(new StringMatcher<>(
+ StringMatcher.getCaseInsensitiveMatcher(dates),
+ s -> lastTimeOfDay));
+ naturalDateLatestTimeParsers.add(new StringMatcher<>(
+ isDate, s -> lastTimeOfDay));
+ naturalDateLatestTimeParsers.add(new StringMatcher<>(
+ ParsedDateTime::isNumeric, s -> lastTimeOfYear));
+ naturalDateLatestTimeParsers.add(new StringMatcher<>(
+ isTime, s -> t -> t));
+ }
+
+ private Optional parsedDateTime;
+ private String input;
+
+ /**
+ * Constructs an object to handle if the date/time can be parsed.
+ *
+ * @param input String that may represent date/time.
+ */
+ public ParsedDateTime(String input) {
+ assert input != null;
+ this.input = input;
+ parsedDateTime = Optional.empty();
+ for (DateTimeFormatter formatter : formatters) {
+ try {
+ parsedDateTime = Optional.of(LocalDateTime.parse(input, formatter));
+ break;
+ } catch (DateTimeParseException ex) {
+ // Just try another one
+ }
+ }
+ }
+
+ /**
+ * Constructs an object that handles an already parsed date/time.
+ *
+ * @param parsed Already parsed date/time.
+ */
+ public ParsedDateTime(LocalDateTime parsed) {
+ input = "";
+ parsedDateTime = Optional.of(parsed);
+ }
+
+ /**
+ * Finds the earliest time after now that matches the description of input. If no such
+ * time exists, it falls back on the earliest time before now. If no such time still
+ * exists, it just gives the input as a ParsedDateTime which stores a String.
+ *
+ * @param input Human-readable description of the time, which only contains valid
+ * descriptors. Valid descriptors include:
+ * * case-insensitive Mon-Sun/Monday-Sunday,
+ * * an ordinal which specifies a date of the month, e.g. 3rd or 31st,
+ * * two or three numbers separated by a slashes or dashes which denote
+ * a date, e.g. 3/7 for the next 3rd of July, 3/7/2012 for the 3rd
+ * of July on 2012, and 1-2 for the next first of February,
+ * * a single number, for a year, e.g. 2024 for year 2024,
+ * * case-insensitive month name, or the abbreviation of a month name, e.g. Jan, July
+ * * two numbers separated by colons or periods (with optional AM/am/PM/pm
+ * after it) for a time, e.g. 23:45, 03.16pm.
+ * @param isLatestTime If true, gives the latest time in the first block of time which matches
+ * the input description. Example: If today is not Monday, "Mon" gives the
+ * time as 23:59 on Monday if isLatestTime, else 00:00.
+ * @return ParsedDateTime of the input. If input is not readable as a time, it
+ * a ParsedDateTime instance that acts like a String.
+ */
+ public static ParsedDateTime of(String input, boolean isLatestTime) {
+ String[] tokens = input.split(" ");
+ try {
+ ArrayList adjusters = getDateTimeAdjusters(naturalDateParsers, tokens);
+ ArrayList latestTimeAdjusters = getDateTimeAdjusters(
+ naturalDateLatestTimeParsers, tokens);
+ Optional result = Optional.empty();
+ Temporal now = LocalDateTime.now().truncatedTo(ChronoUnit.MINUTES);
+ result = result.or(() -> findAfterTimeMatching(now, adjusters));
+ result = result.or(() -> findBeforeTimeMatching(now, adjusters));
+ return result.map(t -> {
+ if (!isLatestTime) {
+ return t;
+ }
+ return applyLatestTimeAdjusters(latestTimeAdjusters, t);
+ })
+ .map(LocalDateTime::from)
+ .map(t -> new ParsedDateTime(t))
+ .orElseGet(() -> new ParsedDateTime(input));
+ } catch (IllegalArgumentException ex) {
+ // thrown by getNaturalDateParsers if not all tokens are valid
+ return new ParsedDateTime(input);
+ }
+ }
+
+ /**
+ * Applies the latest time adjusters. Each adjuster gives the latest time the token would consider as part
+ * of the same length of time specified by the token, so this method finds the earliest time all the tokens
+ * would still consider as part of the same length of time.
+ *
+ * @param latestTimeAdjusters List of time adjusters which give the latest time considered.
+ * @param t The time which is specified by all tokens.
+ * @return Latest time considered by all tokens.
+ */
+ private static LocalDateTime applyLatestTimeAdjusters(ArrayList latestTimeAdjusters, Temporal t) {
+ return latestTimeAdjusters.stream()
+ .map(adjuster -> LocalDateTime.from(t.with(adjuster)))
+ .min(LocalDateTime::compareTo)
+ .orElse(LocalDateTime.from(t));
+ }
+
+ /**
+ * Gets a time adjuster to set to the earliest time at or after the given time
+ * which is of the day of week specified.
+ *
+ * @param day Day of week.
+ * @return Time adjuster which sets to next or same time.
+ */
+ private static TemporalAdjuster getDayOfWeekTemporalAdjuster(DayOfWeek day) {
+ return temporal -> {
+ if (DayOfWeek.from(LocalDateTime.from(temporal)).equals(day)) {
+ return temporal;
+ }
+ Temporal result = TemporalAdjusters.next(day).adjustInto(temporal);
+ return LocalDateTime.from(result).truncatedTo(ChronoUnit.DAYS);
+ };
+ }
+
+ /**
+ * Checks if a string consists of digits.
+ *
+ * @param s A String.
+ * @return true if consisting only of ASCII digits, false otherwise.
+ */
+ private static boolean isNumeric(String s) {
+ for (int i = 0; i < s.length(); i++) {
+ if (s.charAt(i) < '0' || '9' < s.charAt(i)) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ /**
+ * Gets a time adjuster to set to the earliest time at or after the given time
+ * which is of the month specified.
+ *
+ * @param month Month.
+ * @return Time adjuster which sets to next or same time.
+ */
+ private static TemporalAdjuster getMonthTemporalAdjuster(Month month) {
+ return temporal -> {
+ if (Month.from(temporal).equals(month)) {
+ return temporal;
+ }
+ Temporal result = temporal;
+ while (!Month.from(result).equals(month)) {
+ result = TemporalAdjusters.firstDayOfNextMonth().adjustInto(result);
+ }
+ return LocalDateTime.from(result).truncatedTo(ChronoUnit.DAYS);
+ };
+ }
+
+ /**
+ * Gets a time adjuster to set to the earliest time at or after the given time
+ * which is of the date of the month specified.
+ *
+ * @param date Date of the month.
+ * @return Time adjuster which sets to next or same time.
+ */
+ private static TemporalAdjuster getDateTemporalAdjuster(int date) {
+ assert 1 <= date && date <= 31;
+ return temporal -> {
+ int temporalDayOfMonth = temporal.get(ChronoField.DAY_OF_MONTH);
+ if (temporalDayOfMonth == date) {
+ return temporal;
+ }
+ Temporal result = temporal;
+ while (true) {
+ int resultDayOfMonth = result.get(ChronoField.DAY_OF_MONTH);
+ if (resultDayOfMonth == date) {
+ break;
+ }
+ if (resultDayOfMonth > date) {
+ result = result.with(TemporalAdjusters.firstDayOfNextMonth());
+ resultDayOfMonth = 1;
+ }
+ result = LocalDateTime.from(result).plusDays(date - resultDayOfMonth);
+ }
+ LocalDateTime startOfDay = LocalDateTime.from(result).truncatedTo(ChronoUnit.DAYS);
+ return startOfDay;
+ };
+ }
+
+ /**
+ * Gets a time adjuster to set to the earliest time at or after the given time
+ * which is of the time specified.
+ *
+ * @param hour Hour of day.
+ * @param minute Minute of hour.
+ * @return Time adjuster which sets to next or same time.
+ */
+ private static TemporalAdjuster getTimeTemporalAdjuster(int hour, int minute) {
+ boolean isValidHour = 0 <= hour && hour < HOURS_PER_DAY;
+ boolean isValidMinute = 0 <= minute && minute < MINUTES_PER_HOUR;
+ if (!isValidHour || !isValidMinute) {
+ return temporal -> {
+ throw new DateTimeException(
+ String.format("Invalid time: Hour %d, Minute %d", hour, minute));
+ };
+ }
+ int minuteOfDay = minute + hour * MINUTES_PER_HOUR;
+ return temporal -> {
+ if (temporal.get(ChronoField.MINUTE_OF_DAY) <= minuteOfDay) {
+ return temporal.with(ChronoField.MINUTE_OF_DAY, minuteOfDay);
+ }
+ return temporal.plus(1, ChronoUnit.DAYS)
+ .with(ChronoField.MINUTE_OF_DAY, minuteOfDay);
+ };
+ }
+
+ /**
+ * Gets a time adjuster to set to the earliest time at or after the given time
+ * which is of the date/month specified.
+ *
+ * @param date Date of month.
+ * @param month Month.
+ * @return Time adjuster which sets to next or same time.
+ */
+ private static TemporalAdjuster getDateMonthTemporalAdjuster(int date, int month) {
+ boolean isValidDate = 1 <= date && date <= 31;
+ boolean isValidMonth = 1 <= month && month <= 12;
+ int[] daysInMonth = new int[]{ 0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };
+ boolean isValidCombination = isValidDate && isValidMonth && date <= daysInMonth[month];
+ if (!isValidCombination) {
+ return temporal -> {
+ throw new DateTimeException(
+ String.format("Date does not exist: Date %d, Month %d", date, month));
+ };
+ }
+ return temporal -> {
+ int temporalDate = temporal.get(ChronoField.DAY_OF_MONTH);
+ int temporalMonth = temporal.get(ChronoField.MONTH_OF_YEAR);
+ boolean isDate = temporalDate == date;
+ boolean isMonth = temporalMonth == month;
+ if (isDate && isMonth) {
+ return temporal;
+ }
+ boolean isLaterOnSameMonth = temporalMonth == month && temporalDate > date;
+ int year = LocalDateTime.from(temporal).getYear();
+ if (temporalMonth > month || isLaterOnSameMonth) {
+ year++;
+ }
+ if (date == DATE_29 && month == MONTH_FEB) {
+ year = leapYearNextOrSame(year);
+ }
+ return LocalDateTime.of(year, month, date, 0, 0, 0);
+ };
+ }
+
+ /**
+ * Gets a time adjuster to set to the earliest time at or after the given time
+ * which is of the date/month/year specified.
+ *
+ * @param date Date of month.
+ * @param month Month.
+ * @param year Year.
+ * @return Time adjuster which sets to next or same time.
+ */
+ private static TemporalAdjuster getDateMonthYearTemporalAdjuster(int date, int month, int year) {
+ boolean isValidDate = 1 <= date && date <= 31;
+ boolean isValidMonth = 1 <= month && month <= 12;
+ boolean isValidYear = 1 <= year;
+ int[] daysInMonth = new int[]{
+ 0, 31, isLeapYear(year) ? 29 : 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };
+ boolean isValidCombination = isValidYear && isValidDate && isValidMonth && date <= daysInMonth[month];
+ if (!isValidCombination) {
+ return temporal -> {
+ throw new DateTimeException(String.format(
+ "Date does not exist: Date %d, Month %d, Year %d", date, month, year));
+ };
+ }
+ return temporal -> {
+ int temporalDate = temporal.get(ChronoField.DAY_OF_MONTH);
+ int temporalMonth = temporal.get(ChronoField.MONTH_OF_YEAR);
+ int temporalYear = temporal.get(ChronoField.YEAR);
+ boolean isDate = temporalDate == date;
+ boolean isMonth = temporalMonth == month;
+ boolean isYear = temporalYear == year;
+ if (isDate && isMonth && isYear) {
+ return temporal;
+ }
+ boolean isLaterOnSameYear = isYear && temporalMonth > month;
+ boolean isLaterOnSameMonth = isYear && isMonth && temporalDate > date;
+ if (temporalYear > year || isLaterOnSameYear || isLaterOnSameMonth) {
+ throw new DateTimeException(String.format(
+ "Later date does not exist: Date %d, Month %d, Year %d", date, month, year));
+ }
+ return LocalDateTime.of(year, month, date, 0, 0, 0);
+ };
+ }
+
+ /**
+ * Gets a time adjuster to set to the earliest time at or after the given time
+ * which is of the year specified.
+ *
+ * @param year Year.
+ * @return Time adjuster which sets to next or same time.
+ */
+ private static TemporalAdjuster getYearTemporalAdjuster(int year) {
+ boolean isValidYear = 1 <= year;
+ if (!isValidYear) {
+ return temporal -> {
+ throw new DateTimeException(String.format(
+ "Date does not exist:Year %d", year));
+ };
+ }
+ return temporal -> {
+ int temporalYear = temporal.get(ChronoField.YEAR);
+ boolean isYear = temporalYear == year;
+ if (isYear) {
+ return temporal;
+ }
+ if (temporalYear > year) {
+ throw new DateTimeException(String.format(
+ "Later date does not exist: Year %d", year));
+ }
+ return LocalDateTime.of(year, 1, 1, 0, 0, 0);
+ };
+ }
+
+ /**
+ * Gets the next or same year which is a leap year.
+ *
+ * @param year Year.
+ * @return Next or same year which is a leap year.
+ */
+ private static int leapYearNextOrSame(int year) {
+ while (!isLeapYear(year)) {
+ year++;
+ }
+ return year;
+ }
+
+ /**
+ * Checks if the input year is a leap year.
+ *
+ * @param year Year.
+ * @return true if it is a leap year, false otherwise.
+ */
+ private static boolean isLeapYear(int year) {
+ return year % 400 == 0 || (year % 4 == 0 && year % 100 != 0);
+ }
+
+ /**
+ * Gets TemporalAdjusters which match the tokens available from the matchers.
+ *
+ * @param matchers StringMatchers which check the tokens to give TemporalAdjusters.
+ * @param tokens Array of String tokens.
+ * @return ArrayList of TemporalAdjusters.
+ * @throws IllegalArgumentException If some token is not recognized by the StringMatchers.
+ */
+ private static ArrayList getDateTimeAdjusters(
+ ArrayList> matchers, String[] tokens) {
+ ArrayList adjusters = new ArrayList<>();
+ for (String token : tokens) {
+ if (token.equals("")) {
+ continue;
+ }
+ adjusters.add(parseToken(matchers, token));
+ }
+ return adjusters;
+ }
+
+ /**
+ * Gets TemporalAdjusters which match the token available from the matchers.
+ *
+ * @param matchers StringMatchers which check the tokens to give TemporalAdjusters.
+ * @param token Array of String tokens.
+ * @return TemporalAdjuster determined by StringMatchers.
+ * @throws IllegalArgumentException If the token is not recognized by the StringMatchers.
+ */
+ private static TemporalAdjuster parseToken(ArrayList> matchers, String token) {
+ Optional adjuster = Optional.empty();
+ for (StringMatcher matcher : matchers) {
+ adjuster = adjuster.or(() -> matcher.run(token));
+ }
+ return adjuster.orElseThrow(() -> new IllegalArgumentException("Unrecognised token"));
+ }
+
+ /**
+ * Finds the time after the given time which is a fixed point of all the adjusters.
+ * Warning: Imperfect practical implementation which stops an infinite loop.
+ *
+ * @param time Starting time.
+ * @param adjusters Adjusters which find the next time that works according to their description.
+ * @return Time which satisfies all adjusters' descriptions.
+ */
+ private static Optional findAfterTimeMatching(Temporal time, ArrayList adjusters) {
+ try {
+ Temporal result = time;
+ for (int i = 0; i < BIG_NUMBER_OF_ITERATIONS; i++) {
+ Temporal originalTime = result;
+ for (TemporalAdjuster adjuster : adjusters) {
+ result = adjuster.adjustInto(result);
+ }
+ if (originalTime.equals(result)) {
+ return Optional.of(result);
+ }
+ }
+ return Optional.empty();
+ } catch (DateTimeException ex) {
+ return Optional.empty();
+ }
+ }
+
+ /**
+ * Finds the time before the given time which is a fixed point of all the adjusters.
+ *
+ * @param time Ending time.
+ * @param adjusters Adjusters which find the next time that works according to their description.
+ * @return Time which satisfies all adjusters' descriptions.
+ */
+ private static Optional findBeforeTimeMatching(Temporal time, ArrayList adjusters) {
+ try {
+ Temporal result = LocalDateTime.of(1, 1, 1, 0, 0, 0);
+ LocalDateTime endingTime = LocalDateTime.from(time);
+ while (LocalDateTime.from(result).compareTo(endingTime) <= 0) {
+ Temporal originalTime = result;
+ for (TemporalAdjuster adjuster : adjusters) {
+ result = adjuster.adjustInto(result);
+ }
+ if (originalTime.equals(result)) {
+ return Optional.of(result);
+ }
+ }
+ return Optional.empty();
+ } catch (DateTimeException ex) {
+ return Optional.empty();
+ }
+ }
+
+ /**
+ * Gets a nicely-formatted date.
+ *
+ * @return The date if it parses, else the original string.
+ */
+ @Override
+ public String toString() {
+ return parsedDateTime.map((dateTime) -> {
+ assert dateTime != null;
+ return dateTime.format(DateTimeFormatter.ofPattern("dd MMM yyyy HHmm"));
+ }).orElse(input);
+ }
+}
diff --git a/src/main/java/anthea/Parser.java b/src/main/java/anthea/Parser.java
new file mode 100644
index 0000000000..6c42239f35
--- /dev/null
+++ b/src/main/java/anthea/Parser.java
@@ -0,0 +1,140 @@
+package anthea;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Handles gathering commands.
+ */
+public class Parser {
+
+ /**
+ * Lists objects in a String[].
+ *
+ * @param response String that describes the objects.
+ * @param objects List of objects.
+ * @param Type of objects.
+ * @return String[] of objects.
+ */
+ public static String[] listObjects(String response, List objects) {
+ assert response != null;
+ assert objects != null;
+ String[] output = new String[objects.size() + 1];
+ output[0] = response;
+ for (int i = 0; i < objects.size(); i++) {
+ assert objects.get(i) != null;
+ output[i + 1] = (i + 1) + "." + objects.get(i).toString();
+ }
+ return output;
+ }
+
+ /**
+ * Lists numbered objects in a String[].
+ *
+ * @param response String that describes the objects.
+ * @param objects List of numbered objects.
+ * @param Type of objects.
+ * @return String[] of objects.
+ */
+ public static String[] listNumberedObjects(String response, List> objects) {
+ assert response != null;
+ assert objects != null;
+ String[] output = new String[objects.size() + 1];
+ output[0] = response;
+ for (int i = 0; i < objects.size(); i++) {
+ Pair pair = objects.get(i);
+ assert pair != null;
+ assert pair.getFirst() != null;
+ assert pair.getSecond() != null;
+ output[i + 1] = (pair.getFirst() + 1) + "." + pair.getSecond().toString();
+ }
+ return output;
+ }
+
+ /**
+ * Gets the chatbot commands in an ArrayList.
+ *
+ * @return ArrayList of chatbot commands.
+ */
+ public static ArrayList getCommands() {
+ ArrayList commands = new ArrayList<>();
+
+ // This has to be before since the commands are more specific.
+ commands = addNoteCommands(commands);
+
+ commands = addTaskCommands(commands);
+ commands = addTaskModificationCommands(commands);
+ commands = addTaskViewingCommands(commands);
+
+ commands = addCatchAllCommand(commands);
+
+ return commands;
+ }
+
+ /**
+ * Adds adding task commands to an ArrayList.
+ *
+ * @return ArrayList of chatbot commands.
+ */
+ public static ArrayList addTaskCommands(ArrayList commands) {
+ assert commands != null;
+ commands.add(Commands.getAddDeadlineCommand());
+ commands.add(Commands.getAddEventCommand());
+ commands.add(Commands.getAddToDoCommand());
+ return commands;
+ }
+
+ /**
+ * Adds task modification commands to an ArrayList.
+ *
+ * @return ArrayList of chatbot commands.
+ */
+ public static ArrayList addTaskModificationCommands(ArrayList commands) {
+ assert commands != null;
+ commands.add(Commands.getMarkCommand());
+ commands.add(Commands.getUnmarkCommand());
+ commands.add(Commands.getDeleteCommand());
+ commands.add(Commands.getRescheduleCommand());
+ return commands;
+ }
+
+ /**
+ * Adds task viewing commands to an ArrayList.
+ *
+ * @return ArrayList of chatbot commands.
+ */
+ public static ArrayList addTaskViewingCommands(ArrayList commands) {
+ assert commands != null;
+ commands.add(Commands.getListTasksCommand());
+ commands.add(Commands.getFindTasksCommand());
+ return commands;
+ }
+
+ /**
+ * Adds note commands to an ArrayList.
+ *
+ * @return ArrayList of chatbot commands.
+ */
+ public static ArrayList addNoteCommands(ArrayList commands) {
+ assert commands != null;
+ commands.add(Commands.getListNotesCommand());
+ commands.add(Commands.getFindNotesCommand());
+ commands.add(Commands.getViewNoteCommand());
+ commands.add(Commands.getDeleteNoteCommand());
+ commands.add(Commands.getAddNoteCommand());
+ return commands;
+ }
+
+ /**
+ * Adds the command that runs otherwise
+ * to an ArrayList.
+ *
+ * @return ArrayList of chatbot commands.
+ */
+ public static ArrayList addCatchAllCommand(ArrayList commands) {
+ commands.add(
+ new CommandMatcher((str) -> true, (str) -> new ChatbotResponse(
+ "(>.<') I'm sorry, I don't really know what that means.")));
+ return commands;
+ }
+}
diff --git a/src/main/java/anthea/PrefixCommandMatcher.java b/src/main/java/anthea/PrefixCommandMatcher.java
new file mode 100644
index 0000000000..fd2e4ecdbe
--- /dev/null
+++ b/src/main/java/anthea/PrefixCommandMatcher.java
@@ -0,0 +1,80 @@
+package anthea;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.function.BiFunction;
+import java.util.function.Predicate;
+
+import anthea.exception.ChatbotException;
+import anthea.exception.ChatbotExceptionBiFunction;
+import anthea.exception.ChatbotExceptionFunction;
+
+/**
+ * Makes a command matcher based on prefix.
+ * It splits the slash options "/by /at" and other parts as a Map<String, String>
+ * and trims the string involved.
+ * The action takes the String and a map containing the options.
+ */
+public class PrefixCommandMatcher extends CommandMatcher {
+ /**
+ * Creates a command matcher that tries to match a prefix.
+ *
+ * @param prefix Prefix to match.
+ * @param action Action to do.
+ */
+ public PrefixCommandMatcher(String prefix, BiFunction, ChatbotResponse> action) {
+ super(makePrefixMatcher(prefix),
+ ChatbotExceptionFunction.toFunction((cmd) -> {
+ // preprocessing
+ cmd = cmd.strip();
+
+ // corner case
+ if (cmd.equals(prefix)) {
+ throw new ChatbotException(new ChatbotResponse(
+ "(>.<') Add a description to your " + prefix + "."));
+ }
+
+ // map processing
+ String withoutPrefix = cmd.substring(prefix.length() + 1);
+ String[] commandParts = withoutPrefix.split(" /");
+ Map map = new HashMap<>();
+ for (int i = 1; i < commandParts.length; i++) {
+ String[] keyAndValue = commandParts[i].split(" ", 2);
+ if (keyAndValue.length == 2) {
+ map.put(keyAndValue[0].strip(), keyAndValue[1]);
+ } else {
+ map.put(keyAndValue[0].strip(), "");
+ }
+ }
+
+ // another corner case
+ if (commandParts[0].equals("")) {
+ throw new ChatbotException(new ChatbotResponse(
+ "(>.<') The description for " + prefix + " shouldn't be empty."));
+ }
+
+ // accept
+ return action.apply(commandParts[0], map);
+ }));
+ assert prefix != null;
+ assert action != null;
+ }
+
+ private static Predicate makePrefixMatcher(String prefix) {
+ return (cmd) -> cmd.strip().startsWith(prefix + " ") || cmd.strip().equals(prefix);
+ }
+
+ /**
+ * Creates a command matcher that tries to match a prefix.
+ *
+ * @param prefix Prefix to match.
+ * @param action Action to do.
+ */
+ public static PrefixCommandMatcher of(String prefix, ChatbotExceptionBiFunction> action) {
+ assert prefix != null;
+ assert action != null;
+ return new PrefixCommandMatcher(prefix,
+ ChatbotExceptionBiFunction.toBiFunction(action));
+ }
+}
diff --git a/src/main/java/anthea/Storage.java b/src/main/java/anthea/Storage.java
new file mode 100644
index 0000000000..2e0bc45f9c
--- /dev/null
+++ b/src/main/java/anthea/Storage.java
@@ -0,0 +1,102 @@
+package anthea;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.Base64;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Scanner;
+
+
+/**
+ * Handles file state.
+ */
+public class Storage {
+ private static Map fileStates = new HashMap<>();
+ private String contents = null;
+ private String fileName;
+
+ private Storage(String fileName) {
+ this.fileName = fileName;
+ }
+
+ /**
+ * Gets a singleton object that manages a particular file.
+ *
+ * @param fileName File name to identify file.
+ * @return Object managing the file.
+ */
+ public static Storage getFileState(String fileName) {
+ if (!fileStates.containsKey(fileName)) {
+ fileStates.put(fileName, new Storage(fileName));
+ }
+ return fileStates.get(fileName);
+ }
+
+ private static String convertToBase64(String input) {
+ return Base64.getEncoder().encodeToString(input.getBytes(StandardCharsets.UTF_16));
+ }
+
+ private static String convertFromBase64(String input) {
+ return StandardCharsets.UTF_16.decode(ByteBuffer.wrap(Base64.getDecoder().decode(input))).toString();
+ }
+
+ /**
+ * Gets lines from file as a String[][].
+ *
+ * @return Array of String[], each storing comma-separated parts of a line.
+ */
+ public String[][] getLines() {
+ ArrayList lines = new ArrayList<>();
+ try {
+ File f = new File(fileName);
+ Scanner sc = new Scanner(f);
+ while (sc.hasNext()) {
+ ArrayList curLine = new ArrayList<>();
+ for (String str : sc.nextLine().split(",")) {
+ curLine.add(convertFromBase64(str));
+ }
+ lines.add(curLine.toArray(new String[]{}));
+ }
+ return (String[][]) lines.toArray(new String[][]{});
+ } catch (FileNotFoundException ex) {
+ // file not found or error
+ return new String[][] {};
+ }
+ }
+
+ /**
+ * Saves the string to the file on disk.
+ *
+ * @param strings Strings to save.
+ */
+ public void saveLines(String[][] strings) {
+ assert strings != null;
+ try {
+ FileWriter writer = new FileWriter(fileName, false);
+ for (int i = 0; i < strings.length; i++) {
+ assert strings[i] != null;
+ if (i > 0) {
+ writer.append('\n');
+ }
+ StringBuilder builder = new StringBuilder();
+ for (String s : strings[i]) {
+ builder.append(',').append(convertToBase64(s));
+ }
+ writer.append(builder.substring(1));
+ }
+ writer.close();
+ } catch (IOException ex) {
+ /*throw new ChatbotException(new ChatbotResponse(
+ "(>.<') I was unable to record your tasks...")));*/
+ Anthea.getUi().printStyledMessage(
+ "(>.<') I was unable to record your tasks...");
+ ex.printStackTrace();
+ }
+ }
+}
diff --git a/src/main/java/anthea/StringMatcher.java b/src/main/java/anthea/StringMatcher.java
new file mode 100644
index 0000000000..0e98fa035d
--- /dev/null
+++ b/src/main/java/anthea/StringMatcher.java
@@ -0,0 +1,58 @@
+package anthea;
+
+import java.util.Optional;
+import java.util.function.Function;
+import java.util.function.Predicate;
+
+/**
+ * This class serves as a way to abstract the idea of getting a result
+ * from a string.
+ */
+public class StringMatcher {
+ private Predicate isMatch;
+ private Function getResult;
+
+ /**
+ * Constructs an object that calculates a particular case.
+ *
+ * @param isMatch Checks if this function handles this case.
+ * @param getResult Function that calculates result.
+ */
+ public StringMatcher(Predicate isMatch, Function getResult) {
+ assert isMatch != null;
+ assert getResult != null;
+ this.isMatch = isMatch;
+ this.getResult = getResult;
+ }
+
+ public static Predicate getCaseInsensitiveMatcher(String[] matches) {
+ String[] copy = new String[matches.length];
+ for (int i = 0; i < matches.length; i++) {
+ copy[i] = matches[i].toLowerCase();
+ }
+ return s -> {
+ String lowercase = s.toLowerCase();
+ for (String candidate : copy) {
+ if (lowercase.equals(candidate)) {
+ return true;
+ }
+ }
+ return false;
+ };
+ }
+
+ /**
+ * Checks if the string matches.
+ * If it does, it would give the result.
+ *
+ * @param input String to check if it is for this command.
+ * @return An Optional of class T if the string matches.
+ */
+ public Optional run(String input) {
+ assert input != null;
+ if (isMatch.test(input)) {
+ return Optional.of(getResult.apply(input));
+ }
+ return Optional.empty();
+ }
+}
diff --git a/src/main/java/anthea/UiInterface.java b/src/main/java/anthea/UiInterface.java
new file mode 100644
index 0000000000..479519d75e
--- /dev/null
+++ b/src/main/java/anthea/UiInterface.java
@@ -0,0 +1,32 @@
+package anthea;
+
+import java.io.Reader;
+
+/**
+ * Functions that a UI interface needs to implement.
+ */
+public interface UiInterface {
+ /**
+ * Gives you the input stream to scan to interact with this interface.
+ *
+ * @return InputStream to scan.
+ */
+ Reader getReader();
+
+ /**
+ * Styles and prints lines with a border.
+ *
+ * @param lines Lines to be printed
+ */
+ void printStyledMessage(String... lines);
+
+ /**
+ * Greets user.
+ */
+ void greet();
+
+ /**
+ * Leaves the user.
+ */
+ void leave();
+}
diff --git a/src/main/java/anthea/exception/ChatbotException.java b/src/main/java/anthea/exception/ChatbotException.java
new file mode 100644
index 0000000000..e38a6c4cd8
--- /dev/null
+++ b/src/main/java/anthea/exception/ChatbotException.java
@@ -0,0 +1,32 @@
+package anthea.exception;
+
+import anthea.ChatbotResponse;
+
+/**
+ * An exception class that comes with a ChatbotResponse
+ * so that the chatbot can show the message to the screen.
+ */
+public class ChatbotException extends Exception {
+ private ChatbotResponse response;
+
+ /**
+ * Constructs a ChatbotException such that it
+ * holds a ChatbotResponse.
+ *
+ * @param response Response which can be shown.
+ */
+ public ChatbotException(ChatbotResponse response) {
+ super("[Exception]");
+ assert response != null;
+ this.response = response;
+ }
+
+ /**
+ * Gets the available response.
+ *
+ * @return Single-use response.
+ */
+ public ChatbotResponse getResponse() {
+ return response;
+ }
+}
diff --git a/src/main/java/anthea/exception/ChatbotExceptionBiFunction.java b/src/main/java/anthea/exception/ChatbotExceptionBiFunction.java
new file mode 100644
index 0000000000..3cbd1a0378
--- /dev/null
+++ b/src/main/java/anthea/exception/ChatbotExceptionBiFunction.java
@@ -0,0 +1,52 @@
+package anthea.exception;
+
+import java.util.function.BiFunction;
+
+import anthea.ChatbotResponse;
+
+/**
+ * Defines an interface for functions that throw ChatbotException.
+ *
+ * @param Input type.
+ * @param Input type.
+ */
+public interface ChatbotExceptionBiFunction {
+ /**
+ * Constructs ChatbotExceptionBiFunction from BiFunction.
+ *
+ * @param lambda BiFunction.
+ * @param Input type.
+ * @param Input type.
+ * @return Constructed ChatbotExceptionBiFunction.
+ */
+ public static ChatbotExceptionBiFunction of(BiFunction lambda) {
+ assert lambda != null;
+ return lambda::apply;
+ }
+
+ /**
+ * Constructs BiFunction from ChatbotExceptionBiFunction.
+ *
+ * @param lambda ChatbotExceptionFunction.
+ * @param Input type.
+ * @return Constructed Function.
+ */
+ public static BiFunction toBiFunction(ChatbotExceptionBiFunction lambda) {
+ assert lambda != null;
+ return (a, b) -> {
+ try {
+ return lambda.apply(a, b);
+ } catch (ChatbotException exception) {
+ return exception.getResponse();
+ }
+ };
+ }
+
+ /**
+ * Applies on T, U such that it allows for throwing of ChatbotException.
+ *
+ * @return Single-use output ChatbotResponse.
+ * @throws ChatbotException Single-use output ChatbotException.
+ */
+ public ChatbotResponse apply(T t, U u) throws ChatbotException;
+}
diff --git a/src/main/java/anthea/exception/ChatbotExceptionFunction.java b/src/main/java/anthea/exception/ChatbotExceptionFunction.java
new file mode 100644
index 0000000000..0f5a6fdc63
--- /dev/null
+++ b/src/main/java/anthea/exception/ChatbotExceptionFunction.java
@@ -0,0 +1,50 @@
+package anthea.exception;
+
+import java.util.function.Function;
+
+import anthea.ChatbotResponse;
+
+/**
+ * Defines an interface for functions that throw ChatbotException.
+ *
+ * @param Input type.
+ */
+public interface ChatbotExceptionFunction {
+ /**
+ * Constructs ChatbotExceptionFunction from Function.
+ *
+ * @param lambda Function.
+ * @param Input type.
+ * @return Constructed ChatbotExceptionFunction.
+ */
+ public static ChatbotExceptionFunction of(Function lambda) {
+ assert lambda != null;
+ return a -> lambda.apply(a);
+ }
+
+ /**
+ * Constructs Function from ChatbotExceptionFunction.
+ *
+ * @param lambda ChatbotExceptionFunction.
+ * @param Input type.
+ * @return Constructed Function.
+ */
+ public static Function toFunction(ChatbotExceptionFunction lambda) {
+ assert lambda != null;
+ return a -> {
+ try {
+ return lambda.apply(a);
+ } catch (ChatbotException exception) {
+ return exception.getResponse();
+ }
+ };
+ }
+
+ /**
+ * Applies on T such that it allows for throwing of ChatbotException.
+ *
+ * @return Single-use output ChatbotResponse.
+ * @throws ChatbotException Single-use output ChatbotException.
+ */
+ public ChatbotResponse apply(T t) throws ChatbotException;
+}
diff --git a/src/main/java/anthea/gui/DialogBox.java b/src/main/java/anthea/gui/DialogBox.java
new file mode 100644
index 0000000000..6e26f97951
--- /dev/null
+++ b/src/main/java/anthea/gui/DialogBox.java
@@ -0,0 +1,85 @@
+package anthea.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;
+
+//@@author clarence-chew-reused
+// Reused from this tutorial
+// https://se-education.org/guides/tutorials/javaFx.html
+// with minor modifications at most
+
+/**
+ * An example of a custom control using FXML.
+ * This control represents a dialog box consisting of an ImageView to represent the speaker's face and a label
+ * containing text from the speaker.
+ */
+public class DialogBox extends HBox {
+ @FXML
+ private Label dialog;
+ @FXML
+ private ImageView displayPicture;
+
+ private DialogBox(String text, Image img) {
+ try {
+ FXMLLoader fxmlLoader = new FXMLLoader(MainWindow.class.getResource("/view/DialogBox.fxml"));
+ fxmlLoader.setController(this);
+ fxmlLoader.setRoot(this);
+ fxmlLoader.load();
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+
+ dialog.setText(text);
+ displayPicture.setImage(img);
+ setMinHeight(dialog.getPrefHeight());
+ }
+
+ /**
+ * 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);
+ }
+
+ /**
+ * Gets a dialog box for the user.
+ *
+ * @param text User's text.
+ * @param img User's image.
+ * @return Dialog box.
+ */
+ public static DialogBox getUserDialog(String text, Image img) {
+ assert text != null;
+ return new DialogBox(text, img);
+ }
+
+ /**
+ * Gets a dialog box for Anthea.
+ *
+ * @param text Anthea's text.
+ * @param img Anthea's image.
+ * @return Dialog box.
+ */
+ public static DialogBox getChatbotDialog(String text, Image img) {
+ assert text != null;
+ var db = new DialogBox(text, img);
+ db.flip();
+ return db;
+ }
+}
+
+//@@author
diff --git a/src/main/java/anthea/gui/GraphicUi.java b/src/main/java/anthea/gui/GraphicUi.java
new file mode 100644
index 0000000000..cb52a81524
--- /dev/null
+++ b/src/main/java/anthea/gui/GraphicUi.java
@@ -0,0 +1,89 @@
+package anthea.gui;
+
+import java.io.BufferedWriter;
+import java.io.IOException;
+import java.io.PipedReader;
+import java.io.PipedWriter;
+import java.io.Reader;
+
+import anthea.UiInterface;
+
+//@@author clarence-chew-reused
+// Reused from this tutorial
+// https://se-education.org/guides/tutorials/javaFx.html
+// with minor modifications at most
+
+/**
+ * Used for GUI.
+ */
+public class GraphicUi implements UiInterface {
+ private static final PipedWriter writer = new PipedWriter();
+ private static final BufferedWriter bufferedWriter = new BufferedWriter(writer);
+
+ /**
+ * Gets writer that writes out of application logic into GUI.
+ *
+ * @return Writer from application logic.
+ */
+ public static PipedWriter getWriter() {
+ return writer;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public Reader getReader() {
+ try {
+ return new PipedReader(MainWindow.getWriter());
+ } catch (IOException e) {
+ System.out.println("Did not obtain GUI reader - terminating");
+ throw new RuntimeException(e);
+ }
+ }
+
+ /**
+ * Prints lines.
+ *
+ * @param lines Line to be printed
+ */
+ @Override
+ public void printStyledMessage(String... lines) {
+ StringBuilder result = new StringBuilder();
+ for (String line : lines) {
+ assert line != null;
+ result.append(line);
+ result.append('\n');
+ }
+ result.append('\n');
+ try {
+ assert bufferedWriter != null;
+ bufferedWriter.write(result.toString(), 0, result.length());
+ bufferedWriter.flush();
+ } catch (IOException e) {
+ System.out.println("Cannot write output - terminating");
+ throw new RuntimeException(e);
+ }
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void greet() {
+ printStyledMessage("...where is this again?",
+ "Oh, hello, I didn't see you there - I'm Anthea, a chatbot...",
+ "...or at least that's what they told me.");
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void leave() {
+ printStyledMessage("It was nice to have you around, I'm going back to sleep...",
+ "(close the window yourself)");
+ }
+}
+
+//@@author
diff --git a/src/main/java/anthea/gui/Launcher.java b/src/main/java/anthea/gui/Launcher.java
new file mode 100644
index 0000000000..26ddb7e783
--- /dev/null
+++ b/src/main/java/anthea/gui/Launcher.java
@@ -0,0 +1,29 @@
+package anthea.gui;
+
+import anthea.Anthea;
+import javafx.application.Application;
+
+//@@author clarence-chew-reused
+// Reused from this tutorial
+// https://se-education.org/guides/tutorials/javaFx.html
+// with minor modifications at most
+
+/**
+ * A launcher class to workaround classpath issues.
+ */
+public class Launcher {
+ /**
+ * Launches the GUI application.
+ *
+ * @param args Ignored arguments.
+ */
+ public static void main(String[] args) {
+ if (args.length > 0 && args[0].equals("console")) {
+ Anthea.main(new String[]{});
+ return;
+ }
+ Application.launch(Main.class, args);
+ }
+}
+
+//@@author
diff --git a/src/main/java/anthea/gui/Main.java b/src/main/java/anthea/gui/Main.java
new file mode 100644
index 0000000000..cc570c4f98
--- /dev/null
+++ b/src/main/java/anthea/gui/Main.java
@@ -0,0 +1,50 @@
+package anthea.gui;
+
+import java.io.IOException;
+
+import anthea.Anthea;
+import javafx.application.Application;
+import javafx.fxml.FXMLLoader;
+import javafx.scene.Scene;
+import javafx.scene.layout.AnchorPane;
+import javafx.stage.Stage;
+
+//@@author clarence-chew-reused
+// Reused from this tutorial
+// https://se-education.org/guides/tutorials/javaFx.html
+// with minor modifications at most
+
+/**
+ * A GUI for Anthea using FXML.
+ */
+public class Main extends Application {
+ /**
+ * Start the application and Anthea logic.
+ *
+ * @param stage JavaFX stage.
+ */
+ @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);
+ // Start application logic in thread
+ Thread appLogic = new Thread("appLogic") {
+ @Override
+ public void run() {
+ Anthea.setUi(new GraphicUi());
+ Anthea.main(new String[]{});
+ }
+ };
+ appLogic.start();
+ fxmlLoader.getController().getOutput();
+ stage.show();
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ }
+}
+
+//@@author
diff --git a/src/main/java/anthea/gui/MainWindow.java b/src/main/java/anthea/gui/MainWindow.java
new file mode 100644
index 0000000000..26ec6a3b91
--- /dev/null
+++ b/src/main/java/anthea/gui/MainWindow.java
@@ -0,0 +1,124 @@
+package anthea.gui;
+
+import java.io.BufferedReader;
+import java.io.BufferedWriter;
+import java.io.IOException;
+import java.io.PipedReader;
+import java.io.PipedWriter;
+
+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;
+
+//@@author clarence-chew-reused
+// Reused from this tutorial
+// https://se-education.org/guides/tutorials/javaFx.html
+// with minor modifications at most
+
+/**
+ * Controller for MainWindow. Provides the layout for the other controls.
+ */
+public class MainWindow extends AnchorPane {
+ /**
+ * GUI writes to here.
+ */
+ private static final PipedWriter writer = new PipedWriter();
+
+ private static final BufferedWriter bufferedWriter = new BufferedWriter(writer);
+ /**
+ * GUI reads from here.
+ */
+ private static BufferedReader reader;
+
+ @FXML
+ private ScrollPane scrollPane;
+ @FXML
+ private VBox dialogContainer;
+ @FXML
+ private TextField userInput;
+ @FXML
+ private Button sendButton;
+
+ private final Image userImage = new Image(
+ this.getClass().getResourceAsStream("/images/userImage.png"),
+ 96, 96, false, true);
+ private final Image antheaImage = new Image(
+ this.getClass().getResourceAsStream("/images/antheaImage.png"),
+ 96, 96, false, true);
+
+ /**
+ * Gets the writer that writes out GUI interactions.
+ *
+ * @return The writer that writes out GUI interactions.
+ */
+ public static PipedWriter getWriter() {
+ return writer;
+ }
+
+ @FXML
+ private void initialize() {
+ try {
+ reader = new BufferedReader(new PipedReader(GraphicUi.getWriter()));
+ } catch (IOException e) {
+ System.out.println("Cannot initialize GUI output reader - terminating");
+ throw new RuntimeException(e);
+ }
+ scrollPane.vvalueProperty().bind(dialogContainer.heightProperty());
+ }
+
+ /**
+ * Creates two dialog boxes, one echoing user input and the other containing Anthea's reply and then appends them to
+ * the dialog container. Clears the user input after processing.
+ */
+ @FXML
+ private void handleUserInput() {
+ String input = userInput.getText();
+ dialogContainer.getChildren().addAll(
+ DialogBox.getUserDialog(input, userImage)
+ );
+ userInput.clear();
+ try {
+ bufferedWriter.write(input);
+ bufferedWriter.newLine();
+ bufferedWriter.flush();
+ } catch (IOException ex) {
+ dialogContainer.getChildren().addAll(
+ DialogBox.getChatbotDialog(String.format(
+ "I did not manage to send that to application logic:\n%s",
+ input), antheaImage)
+ );
+ }
+ getOutput();
+ }
+
+ /**
+ * Gets output. Used for first message too.
+ */
+ public void getOutput() {
+ try {
+ StringBuilder result = new StringBuilder();
+ while (true) {
+ String line = reader.readLine();
+ if (line.length() == 0) {
+ break;
+ }
+ result.append(line);
+ result.append('\n');
+ }
+ String responseWithoutTrailingNewline = result.substring(0, result.length() - 1);
+ dialogContainer.getChildren().addAll(
+ DialogBox.getChatbotDialog(responseWithoutTrailingNewline, antheaImage)
+ );
+ } catch (IOException ex) {
+ dialogContainer.getChildren().addAll(
+ DialogBox.getChatbotDialog("I did not manage to read it...", antheaImage)
+ );
+ }
+ }
+}
+
+//@@author
diff --git a/src/main/java/anthea/note/Note.java b/src/main/java/anthea/note/Note.java
new file mode 100644
index 0000000000..977162ac79
--- /dev/null
+++ b/src/main/java/anthea/note/Note.java
@@ -0,0 +1,67 @@
+package anthea.note;
+
+/**
+ * Stores a note which has title and content.
+ */
+public class Note {
+ private String title;
+ private String content;
+
+ /**
+ * Constructs a note.
+ *
+ * @param title Title of note.
+ * @param content Content of note.
+ */
+ public Note(String title, String content) {
+ this.title = title;
+ this.content = content;
+ }
+
+ /**
+ * Gets the title of the note.
+ *
+ * @return Title of the note.
+ */
+ public String getTitle() {
+ return title;
+ }
+
+ /**
+ * Gets the content of the note.
+ *
+ * @return Content of the note.
+ */
+ public String getContent() {
+ return content;
+ }
+
+ /**
+ * Checks if the note title matches the query.
+ *
+ * @param query Query for the note to check.
+ * @return True if the note description matches the query.
+ */
+ public boolean isMatchingQuery(String query) {
+ return title.contains(query);
+ }
+
+ /**
+ * Gets a String array format of the note to save to file.
+ *
+ * @return String array.
+ */
+ public String[] getAsStringArray() {
+ return new String[]{ title, content };
+ }
+
+ /**
+ * Get String for printing to the screen.
+ *
+ * @return String for printing.
+ */
+ @Override
+ public String toString() {
+ return title;
+ }
+}
diff --git a/src/main/java/anthea/note/NoteList.java b/src/main/java/anthea/note/NoteList.java
new file mode 100644
index 0000000000..5454d95655
--- /dev/null
+++ b/src/main/java/anthea/note/NoteList.java
@@ -0,0 +1,76 @@
+package anthea.note;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import anthea.ChatbotResponse;
+import anthea.Pair;
+import anthea.exception.ChatbotException;
+
+/**
+ * Holds the list of tasks.
+ */
+public class NoteList {
+ /** List of tasks to remember */
+ private static ArrayList noteList = new ArrayList<>();
+
+ /**
+ * Initializes the task list.
+ */
+ public static void initializeNoteList() {
+ noteList = NoteStorage.getNotes();
+ }
+
+ /**
+ * Finalizes the note list.
+ */
+ public static void finalizeNoteList() {
+ NoteStorage.saveNotes(noteList);
+ }
+
+ /**
+ * Gets note from index as string.
+ *
+ * @param index Index as a string.
+ * @return Note if successful.
+ * @throws ChatbotException if not successful.
+ */
+ public static Note getNote(String index) throws ChatbotException {
+ try {
+ int idx = Integer.parseInt(index);
+ return noteList.get(idx - 1);
+ } catch (NumberFormatException ex) {
+ throw new ChatbotException(new ChatbotResponse(
+ "Sorry, I didn't understand " + index + ", please give me a number."));
+ } catch (IndexOutOfBoundsException ex) {
+ throw new ChatbotException(new ChatbotResponse(
+ "Sorry, the number " + index + ", wasn't in the range."));
+ }
+ }
+
+ /**
+ * Gets notes that match the search term.
+ *
+ * @param query Search term.
+ * @return List of indices and notes.
+ */
+ public static ArrayList> filterNotes(String query) {
+ ArrayList> result = new ArrayList<>();
+ for (int i = 0; i < noteList.size(); i++) {
+ Note note = noteList.get(i);
+ if (note.isMatchingQuery(query)) {
+ result.add(new Pair<>(i, note));
+ }
+ }
+ return result;
+ }
+
+ /**
+ * Gets the task list for other classes to work on.
+ *
+ * @return The task list.
+ */
+ public static List getNoteList() {
+ return noteList;
+ }
+}
diff --git a/src/main/java/anthea/note/NoteStorage.java b/src/main/java/anthea/note/NoteStorage.java
new file mode 100644
index 0000000000..99dc3d4679
--- /dev/null
+++ b/src/main/java/anthea/note/NoteStorage.java
@@ -0,0 +1,41 @@
+package anthea.note;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.stream.Collectors;
+
+import anthea.Storage;
+
+/**
+ * Accesses a file for notes.
+ */
+public class NoteStorage {
+
+ private static String noteStorageFileName = "./antheaNotes.txt";
+
+ /**
+ * Gets ArrayList of previously saved notes.
+ *
+ * @return ArrayList of notes.
+ */
+ public static ArrayList getNotes() {
+ Storage storage = Storage.getFileState(noteStorageFileName);
+ ArrayList notes = new ArrayList<>();
+ for (String[] line : storage.getLines()) {
+ assert line != null;
+ notes.add(new Note(line[0], line[1]));
+ }
+ return notes;
+ }
+
+ /**
+ * Saves a list of notes to the default file.
+ *
+ * @param notes List of notes.
+ */
+ public static void saveNotes(List notes) {
+ List lines = notes.stream().map(Note::getAsStringArray).collect(Collectors.toList());
+ Storage storage = Storage.getFileState(noteStorageFileName);
+ storage.saveLines((String[][]) lines.toArray(new String[][]{}));
+ }
+}
diff --git a/src/main/java/anthea/task/Deadline.java b/src/main/java/anthea/task/Deadline.java
new file mode 100644
index 0000000000..3cbf43f0c4
--- /dev/null
+++ b/src/main/java/anthea/task/Deadline.java
@@ -0,0 +1,51 @@
+package anthea.task;
+
+import anthea.ParsedDateTime;
+
+/**
+ * Handles a task with a deadline.
+ */
+public class Deadline extends Task {
+ protected ParsedDateTime datetime;
+
+ /**
+ * Constructs a Deadline object.
+ *
+ * @param description Description of deadline.
+ * @param by Time of deadline.
+ */
+ public Deadline(String description, String by) {
+ this(description, by, false);
+ }
+
+ /**
+ * Constructs a Deadline object.
+ *
+ * @param description Description of deadline.
+ * @param by Time of deadline.
+ * @param isDone If the task is done.
+ */
+ public Deadline(String description, String by, boolean isDone) {
+ super(description, isDone);
+ datetime = ParsedDateTime.of(by, true);
+ assert description != null;
+ assert by != null;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public String toString() {
+ return String.format("[D]%s (by: %s)", super.toString(), datetime.toString());
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public String[] getAsStringArray() {
+ String[] data = super.getAsStringArray();
+ return new String[]{ "Deadline", data[1], data[2], datetime.toString() };
+ }
+}
diff --git a/src/main/java/anthea/task/Event.java b/src/main/java/anthea/task/Event.java
new file mode 100644
index 0000000000..68fad3d8ac
--- /dev/null
+++ b/src/main/java/anthea/task/Event.java
@@ -0,0 +1,51 @@
+package anthea.task;
+
+import anthea.ParsedDateTime;
+
+/**
+ * Handles an event.
+ */
+public class Event extends Task {
+ protected ParsedDateTime datetime;
+
+ /**
+ * Creates an event.
+ *
+ * @param description Description of event.
+ * @param at Time of event.
+ */
+ public Event(String description, String at) {
+ this(description, at, false);
+ }
+
+ /**
+ * Creates an event.
+ *
+ * @param description Description of event.
+ * @param at Time of event.
+ * @param isDone If the task is done.
+ */
+ public Event(String description, String at, boolean isDone) {
+ super(description, isDone);
+ assert description != null;
+ assert at != null;
+ datetime = ParsedDateTime.of(at, true);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public String toString() {
+ return String.format("[E]%s (at: %s)", super.toString(), datetime.toString());
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public String[] getAsStringArray() {
+ String[] data = super.getAsStringArray();
+ return new String[]{ "Event", data[1], data[2], datetime.toString() };
+ }
+}
diff --git a/src/main/java/anthea/task/Task.java b/src/main/java/anthea/task/Task.java
new file mode 100644
index 0000000000..223363d762
--- /dev/null
+++ b/src/main/java/anthea/task/Task.java
@@ -0,0 +1,98 @@
+package anthea.task;
+
+/**
+ * A task stores its state and description.
+ */
+public class Task {
+ private static final char MARKER_DONE = 'X';
+ private static final char MARKER_NOT_DONE = ' ';
+ private String name;
+ private boolean isDone;
+
+ /**
+ * Constructs a task which is not done.
+ *
+ * @param name Name of task.
+ */
+ public Task(String name) {
+ this(name, false);
+ assert name != null;
+ }
+
+ /**
+ * Constructs a task.
+ *
+ * @param name Name of task.
+ * @param isDone True if and only if the task is done.
+ */
+ public Task(String name, boolean isDone) {
+ assert name != null;
+ this.name = name;
+ this.isDone = isDone;
+ }
+
+ /**
+ * Marks the task as done.
+ */
+ public void markAsDone() {
+ isDone = true;
+ }
+
+ /**
+ * Marks the task as not done.
+ */
+ public void markAsNotDone() {
+ isDone = false;
+ }
+
+ /**
+ * Checks if the task description matches the query.
+ *
+ * @param query Query for the task to check.
+ * @return True if the task description matches the query.
+ */
+ public boolean isMatchingQuery(String query) {
+ return name.contains(query);
+ }
+
+ private char getDoneMarker() {
+ return isDone ? MARKER_DONE : MARKER_NOT_DONE;
+ }
+
+ /**
+ * Gets the task description.
+ *
+ * @return Task description.
+ */
+ public String getDescription() {
+ return name;
+ }
+
+ /**
+ * Gets if the task is done.
+ *
+ * @return true if the task is done, false otherwise.
+ */
+ public boolean isTaskDone() {
+ return isDone;
+ }
+
+ /**
+ * Creates a string representation suitable for printing to screen.
+ *
+ * @return String representation of task.
+ */
+ @Override
+ public String toString() {
+ return String.format("[%c] %s", getDoneMarker(), name);
+ }
+
+ /**
+ * Creates a string array representation suitable for printing to files.
+ *
+ * @return String array representation.
+ */
+ public String[] getAsStringArray() {
+ return new String[]{ "Task", name, String.valueOf(isDone) };
+ }
+}
diff --git a/src/main/java/anthea/task/TaskFactory.java b/src/main/java/anthea/task/TaskFactory.java
new file mode 100644
index 0000000000..0f859ccdd5
--- /dev/null
+++ b/src/main/java/anthea/task/TaskFactory.java
@@ -0,0 +1,62 @@
+package anthea.task;
+
+import java.util.Optional;
+
+import anthea.Anthea;
+
+/**
+ * Constructs tasks from strings
+ */
+public class TaskFactory {
+ private static boolean isTrue(String str) {
+ return str.equals("true");
+ }
+
+ /**
+ * Constructs the task.
+ *
+ * @param taskData Data for the task.
+ * @return Task according to taskData.
+ * @throws IllegalArgumentException If taskData does not conform to the format.
+ */
+ public static Task constructTask(String[] taskData) throws IllegalArgumentException {
+ if (taskData == null || taskData.length < 1) {
+ throw new IllegalArgumentException("taskData cannot be null or of length 1");
+ }
+ boolean isTask = taskData[0].equals("Task") && taskData.length >= 3;
+ if (isTask) {
+ return new Task(taskData[1], isTrue(taskData[2]));
+ }
+ boolean isToDo = taskData[0].equals("ToDo") && taskData.length >= 3;
+ if (isToDo) {
+ return new ToDo(taskData[1], isTrue(taskData[2]));
+ }
+ boolean isDeadline = taskData[0].equals("Deadline") && taskData.length >= 4;
+ if (isDeadline) {
+ return new Deadline(taskData[1], taskData[3], isTrue(taskData[2]));
+ }
+ boolean isEvent = taskData[0].equals("Event") && taskData.length >= 4;
+ if (isEvent) {
+ return new Event(taskData[1], taskData[3], isTrue(taskData[2]));
+ }
+ throw new IllegalArgumentException("Unsupported task type or incorrect task data length");
+ }
+
+ /**
+ * Constructs the task.
+ *
+ * @param taskData Data for the task.
+ * @return Optional of Task according to taskData, Optional.empty() if cannot construct.
+ */
+ public static Optional constructOptionalTask(String[] taskData) {
+ assert taskData != null;
+ try {
+ return Optional.of(constructTask(taskData));
+ } catch (IllegalArgumentException ex) {
+ Anthea.getUi().printStyledMessage(
+ "(>.<') did not understand this task - dropping it",
+ String.join(", ", taskData));
+ return Optional.empty();
+ }
+ }
+}
diff --git a/src/main/java/anthea/task/TaskList.java b/src/main/java/anthea/task/TaskList.java
new file mode 100644
index 0000000000..ac3c9864d4
--- /dev/null
+++ b/src/main/java/anthea/task/TaskList.java
@@ -0,0 +1,76 @@
+package anthea.task;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import anthea.ChatbotResponse;
+import anthea.Pair;
+import anthea.exception.ChatbotException;
+
+/**
+ * Holds the list of tasks
+ */
+public class TaskList {
+ /** List of tasks to remember */
+ private static ArrayList taskList = new ArrayList<>();
+
+ /**
+ * Initializes the task list.
+ */
+ public static void initializeTaskList() {
+ taskList = TaskStorage.getTasks();
+ }
+
+ /**
+ * Finalizes the task list.
+ */
+ public static void finalizeTaskList() {
+ TaskStorage.saveTasks(taskList);
+ }
+
+ /**
+ * Gets task from index as string.
+ *
+ * @param index Index as a string.
+ * @return Task if successful.
+ * @throws ChatbotException if not successful.
+ */
+ public static Task getTask(String index) throws ChatbotException {
+ try {
+ int idx = Integer.parseInt(index);
+ return taskList.get(idx - 1);
+ } catch (NumberFormatException ex) {
+ throw new ChatbotException(new ChatbotResponse(
+ "Sorry, I didn't understand " + index + ", please give me a number."));
+ } catch (IndexOutOfBoundsException ex) {
+ throw new ChatbotException(new ChatbotResponse(
+ "Sorry, the number " + index + ", wasn't in the range."));
+ }
+ }
+
+ /**
+ * Gets tasks that match the search term.
+ *
+ * @param query Search term.
+ * @return List of indices and tasks.
+ */
+ public static ArrayList> filterTasks(String query) {
+ ArrayList> result = new ArrayList<>();
+ for (int i = 0; i < taskList.size(); i++) {
+ Task task = taskList.get(i);
+ if (task.isMatchingQuery(query)) {
+ result.add(new Pair<>(i, task));
+ }
+ }
+ return result;
+ }
+
+ /**
+ * Gets the task list for other classes to work on.
+ *
+ * @return The task list.
+ */
+ public static List getTaskList() {
+ return taskList;
+ }
+}
diff --git a/src/main/java/anthea/task/TaskStorage.java b/src/main/java/anthea/task/TaskStorage.java
new file mode 100644
index 0000000000..843488c515
--- /dev/null
+++ b/src/main/java/anthea/task/TaskStorage.java
@@ -0,0 +1,41 @@
+package anthea.task;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.stream.Collectors;
+
+import anthea.Storage;
+
+/**
+ * Accesses a file for tasks.
+ */
+public class TaskStorage {
+
+ private static String taskStorageFileName = "./anthea.txt";
+
+ /**
+ * Gets ArrayList of previously saved tasks.
+ *
+ * @return ArrayList of tasks.
+ */
+ public static ArrayList getTasks() {
+ Storage storage = Storage.getFileState(taskStorageFileName);
+ ArrayList tasks = new ArrayList<>();
+ for (String[] line : storage.getLines()) {
+ assert line != null;
+ TaskFactory.constructOptionalTask(line).ifPresent((task) -> tasks.add(task));
+ }
+ return tasks;
+ }
+
+ /**
+ * Saves a list of tasks to the default file.
+ *
+ * @param tasks List of tasks.
+ */
+ public static void saveTasks(List tasks) {
+ List lines = tasks.stream().map(Task::getAsStringArray).collect(Collectors.toList());
+ Storage storage = Storage.getFileState(taskStorageFileName);
+ storage.saveLines((String[][]) lines.toArray(new String[][]{}));
+ }
+}
diff --git a/src/main/java/anthea/task/ToDo.java b/src/main/java/anthea/task/ToDo.java
new file mode 100644
index 0000000000..32fd5940d5
--- /dev/null
+++ b/src/main/java/anthea/task/ToDo.java
@@ -0,0 +1,45 @@
+package anthea.task;
+
+/**
+ * A class that stores something to do.
+ */
+public class ToDo extends Task {
+
+ /**
+ * Creates a task item.
+ *
+ * @param description Description of task.
+ */
+ public ToDo(String description) {
+ this(description, false);
+ assert description != null;
+ }
+
+ /**
+ * Creates a task item.
+ *
+ * @param description Description of task.
+ * @param isDone If the task is done.
+ */
+ public ToDo(String description, boolean isDone) {
+ super(description, isDone);
+ assert description != null;
+ }
+
+ @Override
+ public String toString() {
+ return String.format("[T]%s", super.toString());
+ }
+
+ /**
+ * Gets a string array representation suitable for printing to files.
+ *
+ * @return String array representation.
+ */
+ @Override
+ public String[] getAsStringArray() {
+ String[] data = super.getAsStringArray();
+ assert data != null;
+ return new String[]{ "ToDo", data[1], data[2] };
+ }
+}
diff --git a/src/main/resources/images/antheaImage.png b/src/main/resources/images/antheaImage.png
new file mode 100644
index 0000000000..771ef82dce
Binary files /dev/null and b/src/main/resources/images/antheaImage.png differ
diff --git a/src/main/resources/images/userImage.png b/src/main/resources/images/userImage.png
new file mode 100644
index 0000000000..7f7edd8f1d
Binary files /dev/null and b/src/main/resources/images/userImage.png differ
diff --git a/src/main/resources/view/DialogBox.fxml b/src/main/resources/view/DialogBox.fxml
new file mode 100644
index 0000000000..02cac458ee
--- /dev/null
+++ b/src/main/resources/view/DialogBox.fxml
@@ -0,0 +1,16 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/main/resources/view/MainWindow.fxml b/src/main/resources/view/MainWindow.fxml
new file mode 100644
index 0000000000..15c7040985
--- /dev/null
+++ b/src/main/resources/view/MainWindow.fxml
@@ -0,0 +1,19 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/test/java/anthea/ParsedDateTimeTest.java b/src/test/java/anthea/ParsedDateTimeTest.java
new file mode 100644
index 0000000000..0e0c250fd6
--- /dev/null
+++ b/src/test/java/anthea/ParsedDateTimeTest.java
@@ -0,0 +1,51 @@
+package anthea;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.Timeout;
+
+public class ParsedDateTimeTest {
+ @Test
+ @Timeout(1)
+ public void of_makingObjects_normalBehavior() {
+ assertEquals(ParsedDateTime.of("345", true).toString(), "31 Dec 0345 2359");
+ assertEquals(ParsedDateTime.of("345", false).toString(), "01 Jan 0345 0000");
+ assertEquals(ParsedDateTime.of("29/2/1984", false).toString(), "29 Feb 1984 0000");
+ assertEquals(ParsedDateTime.of("29-2-1984", true).toString(), "29 Feb 1984 2359");
+ assertEquals(ParsedDateTime.of("Friday 13th 1985", true).toString(), "13 Sep 1985 2359");
+ assertEquals(ParsedDateTime.of("Friday 13th 1985", false).toString(), "13 Sep 1985 0000");
+ assertEquals(ParsedDateTime.of("January 1984", true).toString(), "31 Jan 1984 2359");
+ assertEquals(ParsedDateTime.of("Jan 1984", false).toString(), "01 Jan 1984 0000");
+ assertEquals(ParsedDateTime.of("Friday MAY 1984", true).toString(), "04 May 1984 2359");
+ assertEquals(ParsedDateTime.of("Friday MAY 1984", false).toString(), "04 May 1984 0000");
+ assertEquals(ParsedDateTime.of("12:34", true).toString(),
+ ParsedDateTime.of("12:34", false).toString());
+ assertEquals(ParsedDateTime.of("12:34pm", true).toString(),
+ ParsedDateTime.of("12:34", false).toString());
+ assertEquals(ParsedDateTime.of("11:34am", true).toString(),
+ ParsedDateTime.of("11:34", false).toString());
+ assertEquals(ParsedDateTime.of("11:34pm", true).toString(),
+ ParsedDateTime.of("23:34", false).toString());
+ assertEquals(ParsedDateTime.of("12:34am", true).toString(),
+ ParsedDateTime.of("00:34", false).toString());
+ assertEquals(ParsedDateTime.of("12:34Am", true).toString(),
+ ParsedDateTime.of("12:34aM", false).toString());
+ assertEquals(ParsedDateTime.of("1st 1th 1St 1/1/1950 January Jan jan 1950", true).toString(),
+ "01 Jan 1950 2359");
+ }
+
+ @Test
+ @Timeout(1)
+ public void of_makingObjects_cannotConstruct() {
+ String[] testCases = new String[]{
+ "2022 fri 31st", "29/2 2100", "29-2-2100", "12/12-12", "23rd 14/5", "34/56", "-3",
+ "Jan Jul", "2030 2040", "Monday Tuesday", "1st 2nd",
+ "13.00am", "0.00am", "13.0pm", "0.0pm", "-3:-3", "26.26"
+ };
+ for (String s : testCases) {
+ assertEquals(ParsedDateTime.of(s, true).toString(), s);
+ assertEquals(ParsedDateTime.of(s, false).toString(), s);
+ }
+ }
+}
diff --git a/src/test/java/anthea/task/DeadlineTest.java b/src/test/java/anthea/task/DeadlineTest.java
new file mode 100644
index 0000000000..863e70bf49
--- /dev/null
+++ b/src/test/java/anthea/task/DeadlineTest.java
@@ -0,0 +1,23 @@
+package anthea.task;
+
+import static org.junit.jupiter.api.Assertions.assertArrayEquals;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+import org.junit.jupiter.api.Test;
+
+public class DeadlineTest {
+ @Test
+ public void getAsStringArray_makingObjects_normalBehavior() {
+ Task task1 = new Deadline("task one", "not-date-1", false);
+ Task task2 = new Deadline("task two", "not-date-2", true);
+ Task task3 = new Deadline("task three", "not-date-3");
+
+ assertEquals("[D][ ] task one (by: not-date-1)", task1.toString());
+ assertEquals("[D][X] task two (by: not-date-2)", task2.toString());
+ assertEquals("[D][ ] task three (by: not-date-3)", task3.toString());
+
+ assertArrayEquals(new String[] { "Deadline", "task one", "false", "not-date-1" }, task1.getAsStringArray());
+ assertArrayEquals(new String[] { "Deadline", "task two", "true", "not-date-2" }, task2.getAsStringArray());
+ assertArrayEquals(new String[] { "Deadline", "task three", "false", "not-date-3" }, task3.getAsStringArray());
+ }
+}
diff --git a/src/test/java/anthea/task/EventTest.java b/src/test/java/anthea/task/EventTest.java
new file mode 100644
index 0000000000..8bc5ac81e7
--- /dev/null
+++ b/src/test/java/anthea/task/EventTest.java
@@ -0,0 +1,23 @@
+package anthea.task;
+
+import static org.junit.jupiter.api.Assertions.assertArrayEquals;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+import org.junit.jupiter.api.Test;
+
+public class EventTest {
+ @Test
+ public void getAsStringArray_makingObjects_normalBehavior() {
+ Task task1 = new Event("task one", "not-date-1", false);
+ Task task2 = new Event("task two", "not-date-2", true);
+ Task task3 = new Event("task three", "not-date-3");
+
+ assertEquals("[E][ ] task one (at: not-date-1)", task1.toString());
+ assertEquals("[E][X] task two (at: not-date-2)", task2.toString());
+ assertEquals("[E][ ] task three (at: not-date-3)", task3.toString());
+
+ assertArrayEquals(new String[] { "Event", "task one", "false", "not-date-1" }, task1.getAsStringArray());
+ assertArrayEquals(new String[] { "Event", "task two", "true", "not-date-2" }, task2.getAsStringArray());
+ assertArrayEquals(new String[] { "Event", "task three", "false", "not-date-3" }, task3.getAsStringArray());
+ }
+}
diff --git a/src/test/java/anthea/task/TaskTest.java b/src/test/java/anthea/task/TaskTest.java
new file mode 100644
index 0000000000..a8e79beae8
--- /dev/null
+++ b/src/test/java/anthea/task/TaskTest.java
@@ -0,0 +1,54 @@
+package anthea.task;
+
+import static org.junit.jupiter.api.Assertions.assertArrayEquals;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+import org.junit.jupiter.api.Test;
+
+public class TaskTest {
+ @Test
+ public void markAsDone_togglingDoneAndNotDone_normalBehavior() {
+ Task task1 = new Task("task one", false);
+ Task task2 = new Task("task two", true);
+ Task task3 = new Task("task three");
+
+ assertEquals("[ ] task one", task1.toString());
+ assertEquals("[X] task two", task2.toString());
+ assertEquals("[ ] task three", task3.toString());
+
+ task1.markAsDone();
+ task2.markAsNotDone();
+ task3.markAsNotDone();
+
+ assertEquals("[X] task one", task1.toString());
+ assertEquals("[ ] task two", task2.toString());
+ assertEquals("[ ] task three", task3.toString());
+
+ task1.markAsDone();
+ task2.markAsNotDone();
+ task3.markAsNotDone();
+
+ assertEquals("[X] task one", task1.toString());
+ assertEquals("[ ] task two", task2.toString());
+ assertEquals("[ ] task three", task3.toString());
+
+ task1.markAsNotDone();
+ task2.markAsDone();
+ task3.markAsDone();
+
+ assertEquals("[ ] task one", task1.toString());
+ assertEquals("[X] task two", task2.toString());
+ assertEquals("[X] task three", task3.toString());
+ }
+
+ @Test
+ public void getAsStringArray_makingObjects_normalBehavior() {
+ Task task1 = new Task("task one", false);
+ Task task2 = new Task("task two", true);
+ Task task3 = new Task("task three");
+
+ assertArrayEquals(new String[] { "Task", "task one", "false" }, task1.getAsStringArray());
+ assertArrayEquals(new String[] { "Task", "task two", "true" }, task2.getAsStringArray());
+ assertArrayEquals(new String[] { "Task", "task three", "false" }, task3.getAsStringArray());
+ }
+}
diff --git a/src/test/java/anthea/task/ToDoTest.java b/src/test/java/anthea/task/ToDoTest.java
new file mode 100644
index 0000000000..ec25316334
--- /dev/null
+++ b/src/test/java/anthea/task/ToDoTest.java
@@ -0,0 +1,23 @@
+package anthea.task;
+
+import static org.junit.jupiter.api.Assertions.assertArrayEquals;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+import org.junit.jupiter.api.Test;
+
+public class ToDoTest {
+ @Test
+ public void getAsStringArray_makingObjects_normalBehavior() {
+ Task task1 = new ToDo("task one", false);
+ Task task2 = new ToDo("task two", true);
+ Task task3 = new ToDo("task three");
+
+ assertEquals("[T][ ] task one", task1.toString());
+ assertEquals("[T][X] task two", task2.toString());
+ assertEquals("[T][ ] task three", task3.toString());
+
+ assertArrayEquals(new String[] { "ToDo", "task one", "false" }, task1.getAsStringArray());
+ assertArrayEquals(new String[] { "ToDo", "task two", "true" }, task2.getAsStringArray());
+ assertArrayEquals(new String[] { "ToDo", "task three", "false" }, task3.getAsStringArray());
+ }
+}
diff --git a/text-ui-test/ACTUAL1.TXT b/text-ui-test/ACTUAL1.TXT
new file mode 100644
index 0000000000..b84ba99a9b
--- /dev/null
+++ b/text-ui-test/ACTUAL1.TXT
@@ -0,0 +1,541 @@
+,----------------------------------------------------------------
+| ...where is this again?
+| Oh, hello, I didn't see you there - I'm Anthea, a chatbot...
+| ...or at least that's what they told me.
+'----------------------------------------------------------------
+,----------------------------------------------------------------
+| (>.<') I'm sorry, I don't really know what that means.
+'----------------------------------------------------------------
+,----------------------------------------------------------------
+| (>.<') I'm sorry, I don't really know what that means.
+'----------------------------------------------------------------
+,----------------------------------------------------------------
+| (>.<') Add a description to your todo.
+'----------------------------------------------------------------
+,----------------------------------------------------------------
+| (>.<') Add a description to your deadline.
+'----------------------------------------------------------------
+,----------------------------------------------------------------
+| (>.<') Add a description to your event.
+'----------------------------------------------------------------
+,----------------------------------------------------------------
+| (>.<') Add a description to your mark.
+'----------------------------------------------------------------
+,----------------------------------------------------------------
+| (>.<') Add a description to your unmark.
+'----------------------------------------------------------------
+,----------------------------------------------------------------
+| Here, your tasks:
+| 1.[T][ ] homework
+| 2.[E][ ] meet friends (at: 31 Dec 1950 2359)
+| 3.[E][ ] meet people at dinner (at: 31 Dec 1200 2359)
+| 4.[E][ ] meet people at function x/y/z whatever (at: 31 Dec 1200 2359)
+| 5.[D][ ] /at mysterious (by: 31 Dec 1960 2359)
+| 6.[D][ ] /by mysterious number 2 (by: 31 Dec 1000 2359)
+| 7.[D][ ] rush this please (by: when?)
+| 8.[E][ ] wow much space (at: 10am)
+'----------------------------------------------------------------
+,----------------------------------------------------------------
+| I've recorded this thing you need to do:
+| [T][ ] homework
+'----------------------------------------------------------------
+,----------------------------------------------------------------
+| I've recorded this thing you need to do:
+| [T][ ] other homework
+'----------------------------------------------------------------
+,----------------------------------------------------------------
+| I've recorded this thing you need to do:
+| [T][ ] project 12
+'----------------------------------------------------------------
+,----------------------------------------------------------------
+| Marked your task as done:
+| [T][X] homework
+'----------------------------------------------------------------
+,----------------------------------------------------------------
+| Marked your task as done:
+| [E][X] meet friends (at: 31 Dec 1950 2359)
+'----------------------------------------------------------------
+,----------------------------------------------------------------
+| Sorry, the number 0, wasn't in the range.
+'----------------------------------------------------------------
+,----------------------------------------------------------------
+| Sorry, the number -1, wasn't in the range.
+'----------------------------------------------------------------
+,----------------------------------------------------------------
+| Sorry, I didn't understand not a number, please give me a number.
+'----------------------------------------------------------------
+,----------------------------------------------------------------
+| Sorry, I didn't understand not a number, please give me a number.
+'----------------------------------------------------------------
+,----------------------------------------------------------------
+| Here, your tasks:
+| 1.[T][X] homework
+| 2.[E][X] meet friends (at: 31 Dec 1950 2359)
+| 3.[E][ ] meet people at dinner (at: 31 Dec 1200 2359)
+| 4.[E][ ] meet people at function x/y/z whatever (at: 31 Dec 1200 2359)
+| 5.[D][ ] /at mysterious (by: 31 Dec 1960 2359)
+| 6.[D][ ] /by mysterious number 2 (by: 31 Dec 1000 2359)
+| 7.[D][ ] rush this please (by: when?)
+| 8.[E][ ] wow much space (at: 10am)
+| 9.[T][ ] homework
+| 10.[T][ ] other homework
+| 11.[T][ ] project 12
+'----------------------------------------------------------------
+,----------------------------------------------------------------
+| Aw... it's not done yet:
+| [T][ ] homework
+'----------------------------------------------------------------
+,----------------------------------------------------------------
+| Aw... it's not done yet:
+| [E][ ] meet friends (at: 31 Dec 1950 2359)
+'----------------------------------------------------------------
+,----------------------------------------------------------------
+| Here, your tasks:
+| 1.[T][ ] homework
+| 2.[E][ ] meet friends (at: 31 Dec 1950 2359)
+| 3.[E][ ] meet people at dinner (at: 31 Dec 1200 2359)
+| 4.[E][ ] meet people at function x/y/z whatever (at: 31 Dec 1200 2359)
+| 5.[D][ ] /at mysterious (by: 31 Dec 1960 2359)
+| 6.[D][ ] /by mysterious number 2 (by: 31 Dec 1000 2359)
+| 7.[D][ ] rush this please (by: when?)
+| 8.[E][ ] wow much space (at: 10am)
+| 9.[T][ ] homework
+| 10.[T][ ] other homework
+| 11.[T][ ] project 12
+'----------------------------------------------------------------
+,----------------------------------------------------------------
+| Marked your task as done:
+| [E][X] meet people at dinner (at: 31 Dec 1200 2359)
+'----------------------------------------------------------------
+,----------------------------------------------------------------
+| Here, your tasks:
+| 1.[T][ ] homework
+| 2.[E][ ] meet friends (at: 31 Dec 1950 2359)
+| 3.[E][X] meet people at dinner (at: 31 Dec 1200 2359)
+| 4.[E][ ] meet people at function x/y/z whatever (at: 31 Dec 1200 2359)
+| 5.[D][ ] /at mysterious (by: 31 Dec 1960 2359)
+| 6.[D][ ] /by mysterious number 2 (by: 31 Dec 1000 2359)
+| 7.[D][ ] rush this please (by: when?)
+| 8.[E][ ] wow much space (at: 10am)
+| 9.[T][ ] homework
+| 10.[T][ ] other homework
+| 11.[T][ ] project 12
+'----------------------------------------------------------------
+,----------------------------------------------------------------
+| Marked your task as done:
+| [E][X] meet people at function x/y/z whatever (at: 31 Dec 1200 2359)
+'----------------------------------------------------------------
+,----------------------------------------------------------------
+| Here, your tasks:
+| 1.[T][ ] homework
+| 2.[E][ ] meet friends (at: 31 Dec 1950 2359)
+| 3.[E][X] meet people at dinner (at: 31 Dec 1200 2359)
+| 4.[E][X] meet people at function x/y/z whatever (at: 31 Dec 1200 2359)
+| 5.[D][ ] /at mysterious (by: 31 Dec 1960 2359)
+| 6.[D][ ] /by mysterious number 2 (by: 31 Dec 1000 2359)
+| 7.[D][ ] rush this please (by: when?)
+| 8.[E][ ] wow much space (at: 10am)
+| 9.[T][ ] homework
+| 10.[T][ ] other homework
+| 11.[T][ ] project 12
+'----------------------------------------------------------------
+,----------------------------------------------------------------
+| Marked your task as done:
+| [D][X] /at mysterious (by: 31 Dec 1960 2359)
+'----------------------------------------------------------------
+,----------------------------------------------------------------
+| Here, your tasks:
+| 1.[T][ ] homework
+| 2.[E][ ] meet friends (at: 31 Dec 1950 2359)
+| 3.[E][X] meet people at dinner (at: 31 Dec 1200 2359)
+| 4.[E][X] meet people at function x/y/z whatever (at: 31 Dec 1200 2359)
+| 5.[D][X] /at mysterious (by: 31 Dec 1960 2359)
+| 6.[D][ ] /by mysterious number 2 (by: 31 Dec 1000 2359)
+| 7.[D][ ] rush this please (by: when?)
+| 8.[E][ ] wow much space (at: 10am)
+| 9.[T][ ] homework
+| 10.[T][ ] other homework
+| 11.[T][ ] project 12
+'----------------------------------------------------------------
+,----------------------------------------------------------------
+| Marked your task as done:
+| [D][X] /by mysterious number 2 (by: 31 Dec 1000 2359)
+'----------------------------------------------------------------
+,----------------------------------------------------------------
+| Here, your tasks:
+| 1.[T][ ] homework
+| 2.[E][ ] meet friends (at: 31 Dec 1950 2359)
+| 3.[E][X] meet people at dinner (at: 31 Dec 1200 2359)
+| 4.[E][X] meet people at function x/y/z whatever (at: 31 Dec 1200 2359)
+| 5.[D][X] /at mysterious (by: 31 Dec 1960 2359)
+| 6.[D][X] /by mysterious number 2 (by: 31 Dec 1000 2359)
+| 7.[D][ ] rush this please (by: when?)
+| 8.[E][ ] wow much space (at: 10am)
+| 9.[T][ ] homework
+| 10.[T][ ] other homework
+| 11.[T][ ] project 12
+'----------------------------------------------------------------
+,----------------------------------------------------------------
+| That's going to happen at some time later:
+| [E][ ] meet friends (at: [unknown])
+'----------------------------------------------------------------
+,----------------------------------------------------------------
+| That's going to happen at some time later:
+| [E][ ] meet people at dinner (at: 6:25 pm)
+'----------------------------------------------------------------
+,----------------------------------------------------------------
+| That's going to happen at some time later:
+| [E][ ] meet people at function x/y/z whatever (at: 07 Nov 1524 0712)
+'----------------------------------------------------------------
+,----------------------------------------------------------------
+| Here, your tasks:
+| 1.[T][ ] homework
+| 2.[E][ ] meet friends (at: 31 Dec 1950 2359)
+| 3.[E][X] meet people at dinner (at: 31 Dec 1200 2359)
+| 4.[E][X] meet people at function x/y/z whatever (at: 31 Dec 1200 2359)
+| 5.[D][X] /at mysterious (by: 31 Dec 1960 2359)
+| 6.[D][X] /by mysterious number 2 (by: 31 Dec 1000 2359)
+| 7.[D][ ] rush this please (by: when?)
+| 8.[E][ ] wow much space (at: 10am)
+| 9.[T][ ] homework
+| 10.[T][ ] other homework
+| 11.[T][ ] project 12
+| 12.[E][ ] meet friends (at: [unknown])
+| 13.[E][ ] meet people at dinner (at: 6:25 pm)
+| 14.[E][ ] meet people at function x/y/z whatever (at: 07 Nov 1524 0712)
+'----------------------------------------------------------------
+,----------------------------------------------------------------
+| Good luck with the deadline, here's the task:
+| [D][ ] /at mysterious (by: [unknown])
+'----------------------------------------------------------------
+,----------------------------------------------------------------
+| Good luck with the deadline, here's the task:
+| [D][ ] /by mysterious number 2 (by: [unknown])
+'----------------------------------------------------------------
+,----------------------------------------------------------------
+| Good luck with the deadline, here's the task:
+| [D][ ] rush this please (by: when?)
+'----------------------------------------------------------------
+,----------------------------------------------------------------
+| Here, your tasks:
+| 1.[T][ ] homework
+| 2.[E][ ] meet friends (at: 31 Dec 1950 2359)
+| 3.[E][X] meet people at dinner (at: 31 Dec 1200 2359)
+| 4.[E][X] meet people at function x/y/z whatever (at: 31 Dec 1200 2359)
+| 5.[D][X] /at mysterious (by: 31 Dec 1960 2359)
+| 6.[D][X] /by mysterious number 2 (by: 31 Dec 1000 2359)
+| 7.[D][ ] rush this please (by: when?)
+| 8.[E][ ] wow much space (at: 10am)
+| 9.[T][ ] homework
+| 10.[T][ ] other homework
+| 11.[T][ ] project 12
+| 12.[E][ ] meet friends (at: [unknown])
+| 13.[E][ ] meet people at dinner (at: 6:25 pm)
+| 14.[E][ ] meet people at function x/y/z whatever (at: 07 Nov 1524 0712)
+| 15.[D][ ] /at mysterious (by: [unknown])
+| 16.[D][ ] /by mysterious number 2 (by: [unknown])
+| 17.[D][ ] rush this please (by: when?)
+'----------------------------------------------------------------
+,----------------------------------------------------------------
+| It seems you didn't need this task anymore, so I removed it:
+| [E][ ] meet friends (at: 31 Dec 1950 2359)
+| You have 16 tasks left.
+'----------------------------------------------------------------
+,----------------------------------------------------------------
+| It seems you didn't need this task anymore, so I removed it:
+| [E][X] meet people at dinner (at: 31 Dec 1200 2359)
+| You have 15 tasks left.
+'----------------------------------------------------------------
+,----------------------------------------------------------------
+| Here, your tasks:
+| 1.[T][ ] homework
+| 2.[E][X] meet people at function x/y/z whatever (at: 31 Dec 1200 2359)
+| 3.[D][X] /at mysterious (by: 31 Dec 1960 2359)
+| 4.[D][X] /by mysterious number 2 (by: 31 Dec 1000 2359)
+| 5.[D][ ] rush this please (by: when?)
+| 6.[E][ ] wow much space (at: 10am)
+| 7.[T][ ] homework
+| 8.[T][ ] other homework
+| 9.[T][ ] project 12
+| 10.[E][ ] meet friends (at: [unknown])
+| 11.[E][ ] meet people at dinner (at: 6:25 pm)
+| 12.[E][ ] meet people at function x/y/z whatever (at: 07 Nov 1524 0712)
+| 13.[D][ ] /at mysterious (by: [unknown])
+| 14.[D][ ] /by mysterious number 2 (by: [unknown])
+| 15.[D][ ] rush this please (by: when?)
+'----------------------------------------------------------------
+,----------------------------------------------------------------
+| Sorry, the number 0, wasn't in the range.
+'----------------------------------------------------------------
+,----------------------------------------------------------------
+| (>.<') Add a description to your delete.
+'----------------------------------------------------------------
+,----------------------------------------------------------------
+| Sorry, I didn't understand nan, please give me a number.
+'----------------------------------------------------------------
+,----------------------------------------------------------------
+| Sorry, the number 24, wasn't in the range.
+'----------------------------------------------------------------
+,----------------------------------------------------------------
+| That's going to happen at some time later:
+| [E][ ] wow much space (at: 10am)
+'----------------------------------------------------------------
+,----------------------------------------------------------------
+| Here, your tasks:
+| 1.[T][ ] homework
+| 2.[E][X] meet people at function x/y/z whatever (at: 31 Dec 1200 2359)
+| 3.[D][X] /at mysterious (by: 31 Dec 1960 2359)
+| 4.[D][X] /by mysterious number 2 (by: 31 Dec 1000 2359)
+| 5.[D][ ] rush this please (by: when?)
+| 6.[E][ ] wow much space (at: 10am)
+| 7.[T][ ] homework
+| 8.[T][ ] other homework
+| 9.[T][ ] project 12
+| 10.[E][ ] meet friends (at: [unknown])
+| 11.[E][ ] meet people at dinner (at: 6:25 pm)
+| 12.[E][ ] meet people at function x/y/z whatever (at: 07 Nov 1524 0712)
+| 13.[D][ ] /at mysterious (by: [unknown])
+| 14.[D][ ] /by mysterious number 2 (by: [unknown])
+| 15.[D][ ] rush this please (by: when?)
+| 16.[E][ ] wow much space (at: 10am)
+'----------------------------------------------------------------
+,----------------------------------------------------------------
+| (>.<') Add a description to your find.
+'----------------------------------------------------------------
+,----------------------------------------------------------------
+| (>.<') Add a description to your find.
+'----------------------------------------------------------------
+,----------------------------------------------------------------
+| Here are the tasks that you might be looking for:
+'----------------------------------------------------------------
+,----------------------------------------------------------------
+| Here are the tasks that you might be looking for:
+'----------------------------------------------------------------
+,----------------------------------------------------------------
+| Here are the tasks that you might be looking for:
+| 1.[T][ ] homework
+| 2.[E][X] meet people at function x/y/z whatever (at: 31 Dec 1200 2359)
+| 7.[T][ ] homework
+| 8.[T][ ] other homework
+| 10.[E][ ] meet friends (at: [unknown])
+| 11.[E][ ] meet people at dinner (at: 6:25 pm)
+| 12.[E][ ] meet people at function x/y/z whatever (at: 07 Nov 1524 0712)
+'----------------------------------------------------------------
+,----------------------------------------------------------------
+| Here are the tasks that you might be looking for:
+| 2.[E][X] meet people at function x/y/z whatever (at: 31 Dec 1200 2359)
+| 3.[D][X] /at mysterious (by: 31 Dec 1960 2359)
+| 4.[D][X] /by mysterious number 2 (by: 31 Dec 1000 2359)
+| 5.[D][ ] rush this please (by: when?)
+| 8.[T][ ] other homework
+| 9.[T][ ] project 12
+| 10.[E][ ] meet friends (at: [unknown])
+| 11.[E][ ] meet people at dinner (at: 6:25 pm)
+| 12.[E][ ] meet people at function x/y/z whatever (at: 07 Nov 1524 0712)
+| 13.[D][ ] /at mysterious (by: [unknown])
+| 14.[D][ ] /by mysterious number 2 (by: [unknown])
+| 15.[D][ ] rush this please (by: when?)
+'----------------------------------------------------------------
+,----------------------------------------------------------------
+| Here, your tasks:
+| 1.[T][ ] homework
+| 2.[E][X] meet people at function x/y/z whatever (at: 31 Dec 1200 2359)
+| 3.[D][X] /at mysterious (by: 31 Dec 1960 2359)
+| 4.[D][X] /by mysterious number 2 (by: 31 Dec 1000 2359)
+| 5.[D][ ] rush this please (by: when?)
+| 6.[E][ ] wow much space (at: 10am)
+| 7.[T][ ] homework
+| 8.[T][ ] other homework
+| 9.[T][ ] project 12
+| 10.[E][ ] meet friends (at: [unknown])
+| 11.[E][ ] meet people at dinner (at: 6:25 pm)
+| 12.[E][ ] meet people at function x/y/z whatever (at: 07 Nov 1524 0712)
+| 13.[D][ ] /at mysterious (by: [unknown])
+| 14.[D][ ] /by mysterious number 2 (by: [unknown])
+| 15.[D][ ] rush this please (by: when?)
+| 16.[E][ ] wow much space (at: 10am)
+'----------------------------------------------------------------
+,----------------------------------------------------------------
+| Sorry, the number -1, wasn't in the range.
+'----------------------------------------------------------------
+,----------------------------------------------------------------
+| Sorry, the number -1, wasn't in the range.
+'----------------------------------------------------------------
+,----------------------------------------------------------------
+| Sorry, the number -1, wasn't in the range.
+'----------------------------------------------------------------
+,----------------------------------------------------------------
+| Sorry, the number -1, wasn't in the range.
+'----------------------------------------------------------------
+,----------------------------------------------------------------
+| Sorry, I didn't understand a, please give me a number.
+'----------------------------------------------------------------
+,----------------------------------------------------------------
+| Sorry, I didn't understand a, please give me a number.
+'----------------------------------------------------------------
+,----------------------------------------------------------------
+| Sorry, I didn't understand a, please give me a number.
+'----------------------------------------------------------------
+,----------------------------------------------------------------
+| Sorry, I didn't understand a, please give me a number.
+'----------------------------------------------------------------
+,----------------------------------------------------------------
+| Sorry, the number 0, wasn't in the range.
+'----------------------------------------------------------------
+,----------------------------------------------------------------
+| Sorry, the number 0, wasn't in the range.
+'----------------------------------------------------------------
+,----------------------------------------------------------------
+| Sorry, the number 0, wasn't in the range.
+'----------------------------------------------------------------
+,----------------------------------------------------------------
+| Sorry, the number 0, wasn't in the range.
+'----------------------------------------------------------------
+,----------------------------------------------------------------
+| That's a todo, it doesn't have a date.
+'----------------------------------------------------------------
+,----------------------------------------------------------------
+| That's a todo, it doesn't have a date.
+'----------------------------------------------------------------
+,----------------------------------------------------------------
+| That's a todo, it doesn't have a date.
+'----------------------------------------------------------------
+,----------------------------------------------------------------
+| That's a todo, it doesn't have a date.
+'----------------------------------------------------------------
+,----------------------------------------------------------------
+| I have rescheduled your task!
+| [E][X] meet people at function x/y/z whatever (at: 31 Dec 1200 2359)
+'----------------------------------------------------------------
+,----------------------------------------------------------------
+| Do specify /at for events.
+'----------------------------------------------------------------
+,----------------------------------------------------------------
+| I have rescheduled your task!
+| [E][X] meet people at function x/y/z whatever (at: 31 Dec 1200 2359)
+'----------------------------------------------------------------
+,----------------------------------------------------------------
+| I have rescheduled your task!
+| [E][X] meet people at function x/y/z whatever (at: 31 Dec 1200 2359)
+'----------------------------------------------------------------
+,----------------------------------------------------------------
+| I have rescheduled your task!
+| [E][X] meet people at function x/y/z whatever (at: 31 Dec 1200 2359)
+'----------------------------------------------------------------
+,----------------------------------------------------------------
+| Do specify /at for events.
+'----------------------------------------------------------------
+,----------------------------------------------------------------
+| I have rescheduled your task!
+| [E][X] meet people at function x/y/z whatever (at: 31 Dec 1200 2359)
+'----------------------------------------------------------------
+,----------------------------------------------------------------
+| I have rescheduled your task!
+| [E][X] meet people at function x/y/z whatever (at: 31 Dec 1200 2359)
+'----------------------------------------------------------------
+,----------------------------------------------------------------
+| Do specify /by for deadlines.
+'----------------------------------------------------------------
+,----------------------------------------------------------------
+| I have rescheduled your task!
+| [D][X] /at mysterious (by: 31 Dec 1000 2359)
+'----------------------------------------------------------------
+,----------------------------------------------------------------
+| Do specify /by for deadlines.
+'----------------------------------------------------------------
+,----------------------------------------------------------------
+| I have rescheduled your task!
+| [D][X] /by mysterious number 2 (by: 31 Dec 1000 2359)
+'----------------------------------------------------------------
+,----------------------------------------------------------------
+| Do specify /by for deadlines.
+'----------------------------------------------------------------
+,----------------------------------------------------------------
+| I have rescheduled your task!
+| [D][ ] rush this please (by: 31 Dec 1000 2359)
+'----------------------------------------------------------------
+,----------------------------------------------------------------
+| I have rescheduled your task!
+| [E][ ] wow much space (at: 31 Dec 1200 2359)
+'----------------------------------------------------------------
+,----------------------------------------------------------------
+| Do specify /at for events.
+'----------------------------------------------------------------
+,----------------------------------------------------------------
+| Added your note about hello.
+'----------------------------------------------------------------
+,----------------------------------------------------------------
+| Added your note about pi.
+'----------------------------------------------------------------
+,----------------------------------------------------------------
+| Added your note about hello.
+'----------------------------------------------------------------
+,----------------------------------------------------------------
+| Here, your notes:
+| 1.hello
+| 2.pi
+| 3.hello
+| 4.pi
+| 5.hello
+'----------------------------------------------------------------
+,----------------------------------------------------------------
+| Sorry, I didn't understand fails, please give me a number.
+'----------------------------------------------------------------
+,----------------------------------------------------------------
+| These notes match your query:
+| 1.hello
+| 3.hello
+| 5.hello
+'----------------------------------------------------------------
+,----------------------------------------------------------------
+| Here's the note:
+| hello
+| this is a great note
+'----------------------------------------------------------------
+,----------------------------------------------------------------
+| Here's the note:
+| pi
+| 3.14159265358979323
+'----------------------------------------------------------------
+,----------------------------------------------------------------
+| Here's the note:
+| hello
+| this is a great note
+'----------------------------------------------------------------
+,----------------------------------------------------------------
+| Sorry, the number 0, wasn't in the range.
+'----------------------------------------------------------------
+,----------------------------------------------------------------
+| Sorry, the number -1, wasn't in the range.
+'----------------------------------------------------------------
+,----------------------------------------------------------------
+| Sorry, the number 12389, wasn't in the range.
+'----------------------------------------------------------------
+,----------------------------------------------------------------
+| Sorry, I didn't understand okay, please give me a number.
+'----------------------------------------------------------------
+,----------------------------------------------------------------
+| That's a todo, it doesn't have a date.
+'----------------------------------------------------------------
+,----------------------------------------------------------------
+| I have rescheduled your task!
+| [E][X] meet people at function x/y/z whatever (at: 31 Dec 1950 2359)
+'----------------------------------------------------------------
+,----------------------------------------------------------------
+| I have rescheduled your task!
+| [D][ ] rush this please (by: 31 Dec 1960 2359)
+'----------------------------------------------------------------
+,----------------------------------------------------------------
+| I removed this note:
+| hello
+'----------------------------------------------------------------
+,----------------------------------------------------------------
+| Here, your notes:
+| 1.hello
+| 2.pi
+| 3.pi
+| 4.hello
+'----------------------------------------------------------------
+,----------------------------------------------------------------
+| It was nice to have you around, I'm going back to sleep...
+'----------------------------------------------------------------
diff --git a/text-ui-test/EXPECTED.TXT b/text-ui-test/EXPECTED.TXT
index 657e74f6e7..7c2761df4b 100644
--- a/text-ui-test/EXPECTED.TXT
+++ b/text-ui-test/EXPECTED.TXT
@@ -1,7 +1,428 @@
-Hello from
- ____ _
-| _ \ _ _| | _____
-| | | | | | | |/ / _ \
-| |_| | |_| | < __/
-|____/ \__,_|_|\_\___|
-
+,----------------------------------------------------------------
+| ...where is this again?
+| Oh, hello, I didn't see you there - I'm Anthea, a chatbot...
+| ...or at least that's what they told me.
+'----------------------------------------------------------------
+,----------------------------------------------------------------
+| (>.<') I'm sorry, I don't really know what that means.
+'----------------------------------------------------------------
+,----------------------------------------------------------------
+| (>.<') I'm sorry, I don't really know what that means.
+'----------------------------------------------------------------
+,----------------------------------------------------------------
+| (>.<') Add a description to your todo.
+'----------------------------------------------------------------
+,----------------------------------------------------------------
+| (>.<') Add a description to your deadline.
+'----------------------------------------------------------------
+,----------------------------------------------------------------
+| (>.<') Add a description to your event.
+'----------------------------------------------------------------
+,----------------------------------------------------------------
+| (>.<') Add a description to your mark.
+'----------------------------------------------------------------
+,----------------------------------------------------------------
+| (>.<') Add a description to your unmark.
+'----------------------------------------------------------------
+,----------------------------------------------------------------
+| Here, your tasks:
+'----------------------------------------------------------------
+,----------------------------------------------------------------
+| I've recorded this thing you need to do:
+| [T][ ] homework
+'----------------------------------------------------------------
+,----------------------------------------------------------------
+| I've recorded this thing you need to do:
+| [T][ ] other homework
+'----------------------------------------------------------------
+,----------------------------------------------------------------
+| I've recorded this thing you need to do:
+| [T][ ] project 12
+'----------------------------------------------------------------
+,----------------------------------------------------------------
+| Marked your task as done:
+| [T][X] homework
+'----------------------------------------------------------------
+,----------------------------------------------------------------
+| Marked your task as done:
+| [T][X] other homework
+'----------------------------------------------------------------
+,----------------------------------------------------------------
+| Sorry, the number 0, wasn't in the range.
+'----------------------------------------------------------------
+,----------------------------------------------------------------
+| Sorry, the number -1, wasn't in the range.
+'----------------------------------------------------------------
+,----------------------------------------------------------------
+| Sorry, I didn't understand not a number, please give me a number.
+'----------------------------------------------------------------
+,----------------------------------------------------------------
+| Sorry, I didn't understand not a number, please give me a number.
+'----------------------------------------------------------------
+,----------------------------------------------------------------
+| Here, your tasks:
+| 1.[T][X] homework
+| 2.[T][X] other homework
+| 3.[T][ ] project 12
+'----------------------------------------------------------------
+,----------------------------------------------------------------
+| Aw... it's not done yet:
+| [T][ ] homework
+'----------------------------------------------------------------
+,----------------------------------------------------------------
+| Aw... it's not done yet:
+| [T][ ] other homework
+'----------------------------------------------------------------
+,----------------------------------------------------------------
+| Here, your tasks:
+| 1.[T][ ] homework
+| 2.[T][ ] other homework
+| 3.[T][ ] project 12
+'----------------------------------------------------------------
+,----------------------------------------------------------------
+| Marked your task as done:
+| [T][X] project 12
+'----------------------------------------------------------------
+,----------------------------------------------------------------
+| Here, your tasks:
+| 1.[T][ ] homework
+| 2.[T][ ] other homework
+| 3.[T][X] project 12
+'----------------------------------------------------------------
+,----------------------------------------------------------------
+| Sorry, the number 4, wasn't in the range.
+'----------------------------------------------------------------
+,----------------------------------------------------------------
+| Here, your tasks:
+| 1.[T][ ] homework
+| 2.[T][ ] other homework
+| 3.[T][X] project 12
+'----------------------------------------------------------------
+,----------------------------------------------------------------
+| Sorry, the number 5, wasn't in the range.
+'----------------------------------------------------------------
+,----------------------------------------------------------------
+| Here, your tasks:
+| 1.[T][ ] homework
+| 2.[T][ ] other homework
+| 3.[T][X] project 12
+'----------------------------------------------------------------
+,----------------------------------------------------------------
+| Sorry, the number 6, wasn't in the range.
+'----------------------------------------------------------------
+,----------------------------------------------------------------
+| Here, your tasks:
+| 1.[T][ ] homework
+| 2.[T][ ] other homework
+| 3.[T][X] project 12
+'----------------------------------------------------------------
+,----------------------------------------------------------------
+| That's going to happen at some time later:
+| [E][ ] meet friends (at: [unknown])
+'----------------------------------------------------------------
+,----------------------------------------------------------------
+| That's going to happen at some time later:
+| [E][ ] meet people at dinner (at: 6:25 pm)
+'----------------------------------------------------------------
+,----------------------------------------------------------------
+| That's going to happen at some time later:
+| [E][ ] meet people at function x/y/z whatever (at: 07 Nov 1524 0712)
+'----------------------------------------------------------------
+,----------------------------------------------------------------
+| Here, your tasks:
+| 1.[T][ ] homework
+| 2.[T][ ] other homework
+| 3.[T][X] project 12
+| 4.[E][ ] meet friends (at: [unknown])
+| 5.[E][ ] meet people at dinner (at: 6:25 pm)
+| 6.[E][ ] meet people at function x/y/z whatever (at: 07 Nov 1524 0712)
+'----------------------------------------------------------------
+,----------------------------------------------------------------
+| Good luck with the deadline, here's the task:
+| [D][ ] /at mysterious (by: [unknown])
+'----------------------------------------------------------------
+,----------------------------------------------------------------
+| Good luck with the deadline, here's the task:
+| [D][ ] /by mysterious number 2 (by: [unknown])
+'----------------------------------------------------------------
+,----------------------------------------------------------------
+| Good luck with the deadline, here's the task:
+| [D][ ] rush this please (by: when?)
+'----------------------------------------------------------------
+,----------------------------------------------------------------
+| Here, your tasks:
+| 1.[T][ ] homework
+| 2.[T][ ] other homework
+| 3.[T][X] project 12
+| 4.[E][ ] meet friends (at: [unknown])
+| 5.[E][ ] meet people at dinner (at: 6:25 pm)
+| 6.[E][ ] meet people at function x/y/z whatever (at: 07 Nov 1524 0712)
+| 7.[D][ ] /at mysterious (by: [unknown])
+| 8.[D][ ] /by mysterious number 2 (by: [unknown])
+| 9.[D][ ] rush this please (by: when?)
+'----------------------------------------------------------------
+,----------------------------------------------------------------
+| It seems you didn't need this task anymore, so I removed it:
+| [T][ ] other homework
+| You have 8 tasks left.
+'----------------------------------------------------------------
+,----------------------------------------------------------------
+| It seems you didn't need this task anymore, so I removed it:
+| [T][X] project 12
+| You have 7 tasks left.
+'----------------------------------------------------------------
+,----------------------------------------------------------------
+| Here, your tasks:
+| 1.[T][ ] homework
+| 2.[E][ ] meet friends (at: [unknown])
+| 3.[E][ ] meet people at dinner (at: 6:25 pm)
+| 4.[E][ ] meet people at function x/y/z whatever (at: 07 Nov 1524 0712)
+| 5.[D][ ] /at mysterious (by: [unknown])
+| 6.[D][ ] /by mysterious number 2 (by: [unknown])
+| 7.[D][ ] rush this please (by: when?)
+'----------------------------------------------------------------
+,----------------------------------------------------------------
+| Sorry, the number 0, wasn't in the range.
+'----------------------------------------------------------------
+,----------------------------------------------------------------
+| (>.<') Add a description to your delete.
+'----------------------------------------------------------------
+,----------------------------------------------------------------
+| Sorry, I didn't understand nan, please give me a number.
+'----------------------------------------------------------------
+,----------------------------------------------------------------
+| Sorry, the number 24, wasn't in the range.
+'----------------------------------------------------------------
+,----------------------------------------------------------------
+| That's going to happen at some time later:
+| [E][ ] wow much space (at: 10am)
+'----------------------------------------------------------------
+,----------------------------------------------------------------
+| Here, your tasks:
+| 1.[T][ ] homework
+| 2.[E][ ] meet friends (at: [unknown])
+| 3.[E][ ] meet people at dinner (at: 6:25 pm)
+| 4.[E][ ] meet people at function x/y/z whatever (at: 07 Nov 1524 0712)
+| 5.[D][ ] /at mysterious (by: [unknown])
+| 6.[D][ ] /by mysterious number 2 (by: [unknown])
+| 7.[D][ ] rush this please (by: when?)
+| 8.[E][ ] wow much space (at: 10am)
+'----------------------------------------------------------------
+,----------------------------------------------------------------
+| (>.<') Add a description to your find.
+'----------------------------------------------------------------
+,----------------------------------------------------------------
+| (>.<') Add a description to your find.
+'----------------------------------------------------------------
+,----------------------------------------------------------------
+| Here are the tasks that you might be looking for:
+'----------------------------------------------------------------
+,----------------------------------------------------------------
+| Here are the tasks that you might be looking for:
+'----------------------------------------------------------------
+,----------------------------------------------------------------
+| Here are the tasks that you might be looking for:
+| 1.[T][ ] homework
+| 2.[E][ ] meet friends (at: [unknown])
+| 3.[E][ ] meet people at dinner (at: 6:25 pm)
+| 4.[E][ ] meet people at function x/y/z whatever (at: 07 Nov 1524 0712)
+'----------------------------------------------------------------
+,----------------------------------------------------------------
+| Here are the tasks that you might be looking for:
+| 2.[E][ ] meet friends (at: [unknown])
+| 3.[E][ ] meet people at dinner (at: 6:25 pm)
+| 4.[E][ ] meet people at function x/y/z whatever (at: 07 Nov 1524 0712)
+| 5.[D][ ] /at mysterious (by: [unknown])
+| 6.[D][ ] /by mysterious number 2 (by: [unknown])
+| 7.[D][ ] rush this please (by: when?)
+'----------------------------------------------------------------
+,----------------------------------------------------------------
+| Here, your tasks:
+| 1.[T][ ] homework
+| 2.[E][ ] meet friends (at: [unknown])
+| 3.[E][ ] meet people at dinner (at: 6:25 pm)
+| 4.[E][ ] meet people at function x/y/z whatever (at: 07 Nov 1524 0712)
+| 5.[D][ ] /at mysterious (by: [unknown])
+| 6.[D][ ] /by mysterious number 2 (by: [unknown])
+| 7.[D][ ] rush this please (by: when?)
+| 8.[E][ ] wow much space (at: 10am)
+'----------------------------------------------------------------
+,----------------------------------------------------------------
+| Sorry, the number -1, wasn't in the range.
+'----------------------------------------------------------------
+,----------------------------------------------------------------
+| Sorry, the number -1, wasn't in the range.
+'----------------------------------------------------------------
+,----------------------------------------------------------------
+| Sorry, the number -1, wasn't in the range.
+'----------------------------------------------------------------
+,----------------------------------------------------------------
+| Sorry, the number -1, wasn't in the range.
+'----------------------------------------------------------------
+,----------------------------------------------------------------
+| Sorry, I didn't understand a, please give me a number.
+'----------------------------------------------------------------
+,----------------------------------------------------------------
+| Sorry, I didn't understand a, please give me a number.
+'----------------------------------------------------------------
+,----------------------------------------------------------------
+| Sorry, I didn't understand a, please give me a number.
+'----------------------------------------------------------------
+,----------------------------------------------------------------
+| Sorry, I didn't understand a, please give me a number.
+'----------------------------------------------------------------
+,----------------------------------------------------------------
+| Sorry, the number 0, wasn't in the range.
+'----------------------------------------------------------------
+,----------------------------------------------------------------
+| Sorry, the number 0, wasn't in the range.
+'----------------------------------------------------------------
+,----------------------------------------------------------------
+| Sorry, the number 0, wasn't in the range.
+'----------------------------------------------------------------
+,----------------------------------------------------------------
+| Sorry, the number 0, wasn't in the range.
+'----------------------------------------------------------------
+,----------------------------------------------------------------
+| That's a todo, it doesn't have a date.
+'----------------------------------------------------------------
+,----------------------------------------------------------------
+| That's a todo, it doesn't have a date.
+'----------------------------------------------------------------
+,----------------------------------------------------------------
+| That's a todo, it doesn't have a date.
+'----------------------------------------------------------------
+,----------------------------------------------------------------
+| That's a todo, it doesn't have a date.
+'----------------------------------------------------------------
+,----------------------------------------------------------------
+| I have rescheduled your task!
+| [E][ ] meet friends (at: 31 Dec 1200 2359)
+'----------------------------------------------------------------
+,----------------------------------------------------------------
+| Do specify /at for events.
+'----------------------------------------------------------------
+,----------------------------------------------------------------
+| I have rescheduled your task!
+| [E][ ] meet friends (at: 31 Dec 1200 2359)
+'----------------------------------------------------------------
+,----------------------------------------------------------------
+| I have rescheduled your task!
+| [E][ ] meet friends (at: 31 Dec 1200 2359)
+'----------------------------------------------------------------
+,----------------------------------------------------------------
+| I have rescheduled your task!
+| [E][ ] meet friends (at: 31 Dec 1200 2359)
+'----------------------------------------------------------------
+,----------------------------------------------------------------
+| Do specify /at for events.
+'----------------------------------------------------------------
+,----------------------------------------------------------------
+| I have rescheduled your task!
+| [E][ ] meet friends (at: 31 Dec 1200 2359)
+'----------------------------------------------------------------
+,----------------------------------------------------------------
+| I have rescheduled your task!
+| [E][ ] meet friends (at: 31 Dec 1200 2359)
+'----------------------------------------------------------------
+,----------------------------------------------------------------
+| I have rescheduled your task!
+| [E][ ] meet people at dinner (at: 31 Dec 1200 2359)
+'----------------------------------------------------------------
+,----------------------------------------------------------------
+| Do specify /at for events.
+'----------------------------------------------------------------
+,----------------------------------------------------------------
+| I have rescheduled your task!
+| [E][ ] meet people at function x/y/z whatever (at: 31 Dec 1200 2359)
+'----------------------------------------------------------------
+,----------------------------------------------------------------
+| Do specify /at for events.
+'----------------------------------------------------------------
+,----------------------------------------------------------------
+| Do specify /by for deadlines.
+'----------------------------------------------------------------
+,----------------------------------------------------------------
+| I have rescheduled your task!
+| [D][ ] /at mysterious (by: 31 Dec 1000 2359)
+'----------------------------------------------------------------
+,----------------------------------------------------------------
+| Do specify /by for deadlines.
+'----------------------------------------------------------------
+,----------------------------------------------------------------
+| I have rescheduled your task!
+| [D][ ] /by mysterious number 2 (by: 31 Dec 1000 2359)
+'----------------------------------------------------------------
+,----------------------------------------------------------------
+| Added your note about hello.
+'----------------------------------------------------------------
+,----------------------------------------------------------------
+| Added your note about pi.
+'----------------------------------------------------------------
+,----------------------------------------------------------------
+| Added your note about hello.
+'----------------------------------------------------------------
+,----------------------------------------------------------------
+| Here, your notes:
+| 1.hello
+| 2.pi
+| 3.hello
+'----------------------------------------------------------------
+,----------------------------------------------------------------
+| Sorry, I didn't understand fails, please give me a number.
+'----------------------------------------------------------------
+,----------------------------------------------------------------
+| These notes match your query:
+| 1.hello
+| 3.hello
+'----------------------------------------------------------------
+,----------------------------------------------------------------
+| Here's the note:
+| hello
+| this is a great note
+'----------------------------------------------------------------
+,----------------------------------------------------------------
+| Here's the note:
+| pi
+| 3.14159265358979323
+'----------------------------------------------------------------
+,----------------------------------------------------------------
+| Here's the note:
+| hello
+| repeated title
+'----------------------------------------------------------------
+,----------------------------------------------------------------
+| Sorry, the number 0, wasn't in the range.
+'----------------------------------------------------------------
+,----------------------------------------------------------------
+| Sorry, the number -1, wasn't in the range.
+'----------------------------------------------------------------
+,----------------------------------------------------------------
+| Sorry, the number 12389, wasn't in the range.
+'----------------------------------------------------------------
+,----------------------------------------------------------------
+| Sorry, I didn't understand okay, please give me a number.
+'----------------------------------------------------------------
+,----------------------------------------------------------------
+| That's a todo, it doesn't have a date.
+'----------------------------------------------------------------
+,----------------------------------------------------------------
+| I have rescheduled your task!
+| [E][ ] meet friends (at: 31 Dec 1950 2359)
+'----------------------------------------------------------------
+,----------------------------------------------------------------
+| I have rescheduled your task!
+| [D][ ] /at mysterious (by: 31 Dec 1960 2359)
+'----------------------------------------------------------------
+,----------------------------------------------------------------
+| I removed this note:
+| hello
+'----------------------------------------------------------------
+,----------------------------------------------------------------
+| Here, your notes:
+| 1.hello
+| 2.pi
+'----------------------------------------------------------------
+,----------------------------------------------------------------
+| It was nice to have you around, I'm going back to sleep...
+'----------------------------------------------------------------
diff --git a/text-ui-test/EXPECTED1.TXT b/text-ui-test/EXPECTED1.TXT
new file mode 100644
index 0000000000..b84ba99a9b
--- /dev/null
+++ b/text-ui-test/EXPECTED1.TXT
@@ -0,0 +1,541 @@
+,----------------------------------------------------------------
+| ...where is this again?
+| Oh, hello, I didn't see you there - I'm Anthea, a chatbot...
+| ...or at least that's what they told me.
+'----------------------------------------------------------------
+,----------------------------------------------------------------
+| (>.<') I'm sorry, I don't really know what that means.
+'----------------------------------------------------------------
+,----------------------------------------------------------------
+| (>.<') I'm sorry, I don't really know what that means.
+'----------------------------------------------------------------
+,----------------------------------------------------------------
+| (>.<') Add a description to your todo.
+'----------------------------------------------------------------
+,----------------------------------------------------------------
+| (>.<') Add a description to your deadline.
+'----------------------------------------------------------------
+,----------------------------------------------------------------
+| (>.<') Add a description to your event.
+'----------------------------------------------------------------
+,----------------------------------------------------------------
+| (>.<') Add a description to your mark.
+'----------------------------------------------------------------
+,----------------------------------------------------------------
+| (>.<') Add a description to your unmark.
+'----------------------------------------------------------------
+,----------------------------------------------------------------
+| Here, your tasks:
+| 1.[T][ ] homework
+| 2.[E][ ] meet friends (at: 31 Dec 1950 2359)
+| 3.[E][ ] meet people at dinner (at: 31 Dec 1200 2359)
+| 4.[E][ ] meet people at function x/y/z whatever (at: 31 Dec 1200 2359)
+| 5.[D][ ] /at mysterious (by: 31 Dec 1960 2359)
+| 6.[D][ ] /by mysterious number 2 (by: 31 Dec 1000 2359)
+| 7.[D][ ] rush this please (by: when?)
+| 8.[E][ ] wow much space (at: 10am)
+'----------------------------------------------------------------
+,----------------------------------------------------------------
+| I've recorded this thing you need to do:
+| [T][ ] homework
+'----------------------------------------------------------------
+,----------------------------------------------------------------
+| I've recorded this thing you need to do:
+| [T][ ] other homework
+'----------------------------------------------------------------
+,----------------------------------------------------------------
+| I've recorded this thing you need to do:
+| [T][ ] project 12
+'----------------------------------------------------------------
+,----------------------------------------------------------------
+| Marked your task as done:
+| [T][X] homework
+'----------------------------------------------------------------
+,----------------------------------------------------------------
+| Marked your task as done:
+| [E][X] meet friends (at: 31 Dec 1950 2359)
+'----------------------------------------------------------------
+,----------------------------------------------------------------
+| Sorry, the number 0, wasn't in the range.
+'----------------------------------------------------------------
+,----------------------------------------------------------------
+| Sorry, the number -1, wasn't in the range.
+'----------------------------------------------------------------
+,----------------------------------------------------------------
+| Sorry, I didn't understand not a number, please give me a number.
+'----------------------------------------------------------------
+,----------------------------------------------------------------
+| Sorry, I didn't understand not a number, please give me a number.
+'----------------------------------------------------------------
+,----------------------------------------------------------------
+| Here, your tasks:
+| 1.[T][X] homework
+| 2.[E][X] meet friends (at: 31 Dec 1950 2359)
+| 3.[E][ ] meet people at dinner (at: 31 Dec 1200 2359)
+| 4.[E][ ] meet people at function x/y/z whatever (at: 31 Dec 1200 2359)
+| 5.[D][ ] /at mysterious (by: 31 Dec 1960 2359)
+| 6.[D][ ] /by mysterious number 2 (by: 31 Dec 1000 2359)
+| 7.[D][ ] rush this please (by: when?)
+| 8.[E][ ] wow much space (at: 10am)
+| 9.[T][ ] homework
+| 10.[T][ ] other homework
+| 11.[T][ ] project 12
+'----------------------------------------------------------------
+,----------------------------------------------------------------
+| Aw... it's not done yet:
+| [T][ ] homework
+'----------------------------------------------------------------
+,----------------------------------------------------------------
+| Aw... it's not done yet:
+| [E][ ] meet friends (at: 31 Dec 1950 2359)
+'----------------------------------------------------------------
+,----------------------------------------------------------------
+| Here, your tasks:
+| 1.[T][ ] homework
+| 2.[E][ ] meet friends (at: 31 Dec 1950 2359)
+| 3.[E][ ] meet people at dinner (at: 31 Dec 1200 2359)
+| 4.[E][ ] meet people at function x/y/z whatever (at: 31 Dec 1200 2359)
+| 5.[D][ ] /at mysterious (by: 31 Dec 1960 2359)
+| 6.[D][ ] /by mysterious number 2 (by: 31 Dec 1000 2359)
+| 7.[D][ ] rush this please (by: when?)
+| 8.[E][ ] wow much space (at: 10am)
+| 9.[T][ ] homework
+| 10.[T][ ] other homework
+| 11.[T][ ] project 12
+'----------------------------------------------------------------
+,----------------------------------------------------------------
+| Marked your task as done:
+| [E][X] meet people at dinner (at: 31 Dec 1200 2359)
+'----------------------------------------------------------------
+,----------------------------------------------------------------
+| Here, your tasks:
+| 1.[T][ ] homework
+| 2.[E][ ] meet friends (at: 31 Dec 1950 2359)
+| 3.[E][X] meet people at dinner (at: 31 Dec 1200 2359)
+| 4.[E][ ] meet people at function x/y/z whatever (at: 31 Dec 1200 2359)
+| 5.[D][ ] /at mysterious (by: 31 Dec 1960 2359)
+| 6.[D][ ] /by mysterious number 2 (by: 31 Dec 1000 2359)
+| 7.[D][ ] rush this please (by: when?)
+| 8.[E][ ] wow much space (at: 10am)
+| 9.[T][ ] homework
+| 10.[T][ ] other homework
+| 11.[T][ ] project 12
+'----------------------------------------------------------------
+,----------------------------------------------------------------
+| Marked your task as done:
+| [E][X] meet people at function x/y/z whatever (at: 31 Dec 1200 2359)
+'----------------------------------------------------------------
+,----------------------------------------------------------------
+| Here, your tasks:
+| 1.[T][ ] homework
+| 2.[E][ ] meet friends (at: 31 Dec 1950 2359)
+| 3.[E][X] meet people at dinner (at: 31 Dec 1200 2359)
+| 4.[E][X] meet people at function x/y/z whatever (at: 31 Dec 1200 2359)
+| 5.[D][ ] /at mysterious (by: 31 Dec 1960 2359)
+| 6.[D][ ] /by mysterious number 2 (by: 31 Dec 1000 2359)
+| 7.[D][ ] rush this please (by: when?)
+| 8.[E][ ] wow much space (at: 10am)
+| 9.[T][ ] homework
+| 10.[T][ ] other homework
+| 11.[T][ ] project 12
+'----------------------------------------------------------------
+,----------------------------------------------------------------
+| Marked your task as done:
+| [D][X] /at mysterious (by: 31 Dec 1960 2359)
+'----------------------------------------------------------------
+,----------------------------------------------------------------
+| Here, your tasks:
+| 1.[T][ ] homework
+| 2.[E][ ] meet friends (at: 31 Dec 1950 2359)
+| 3.[E][X] meet people at dinner (at: 31 Dec 1200 2359)
+| 4.[E][X] meet people at function x/y/z whatever (at: 31 Dec 1200 2359)
+| 5.[D][X] /at mysterious (by: 31 Dec 1960 2359)
+| 6.[D][ ] /by mysterious number 2 (by: 31 Dec 1000 2359)
+| 7.[D][ ] rush this please (by: when?)
+| 8.[E][ ] wow much space (at: 10am)
+| 9.[T][ ] homework
+| 10.[T][ ] other homework
+| 11.[T][ ] project 12
+'----------------------------------------------------------------
+,----------------------------------------------------------------
+| Marked your task as done:
+| [D][X] /by mysterious number 2 (by: 31 Dec 1000 2359)
+'----------------------------------------------------------------
+,----------------------------------------------------------------
+| Here, your tasks:
+| 1.[T][ ] homework
+| 2.[E][ ] meet friends (at: 31 Dec 1950 2359)
+| 3.[E][X] meet people at dinner (at: 31 Dec 1200 2359)
+| 4.[E][X] meet people at function x/y/z whatever (at: 31 Dec 1200 2359)
+| 5.[D][X] /at mysterious (by: 31 Dec 1960 2359)
+| 6.[D][X] /by mysterious number 2 (by: 31 Dec 1000 2359)
+| 7.[D][ ] rush this please (by: when?)
+| 8.[E][ ] wow much space (at: 10am)
+| 9.[T][ ] homework
+| 10.[T][ ] other homework
+| 11.[T][ ] project 12
+'----------------------------------------------------------------
+,----------------------------------------------------------------
+| That's going to happen at some time later:
+| [E][ ] meet friends (at: [unknown])
+'----------------------------------------------------------------
+,----------------------------------------------------------------
+| That's going to happen at some time later:
+| [E][ ] meet people at dinner (at: 6:25 pm)
+'----------------------------------------------------------------
+,----------------------------------------------------------------
+| That's going to happen at some time later:
+| [E][ ] meet people at function x/y/z whatever (at: 07 Nov 1524 0712)
+'----------------------------------------------------------------
+,----------------------------------------------------------------
+| Here, your tasks:
+| 1.[T][ ] homework
+| 2.[E][ ] meet friends (at: 31 Dec 1950 2359)
+| 3.[E][X] meet people at dinner (at: 31 Dec 1200 2359)
+| 4.[E][X] meet people at function x/y/z whatever (at: 31 Dec 1200 2359)
+| 5.[D][X] /at mysterious (by: 31 Dec 1960 2359)
+| 6.[D][X] /by mysterious number 2 (by: 31 Dec 1000 2359)
+| 7.[D][ ] rush this please (by: when?)
+| 8.[E][ ] wow much space (at: 10am)
+| 9.[T][ ] homework
+| 10.[T][ ] other homework
+| 11.[T][ ] project 12
+| 12.[E][ ] meet friends (at: [unknown])
+| 13.[E][ ] meet people at dinner (at: 6:25 pm)
+| 14.[E][ ] meet people at function x/y/z whatever (at: 07 Nov 1524 0712)
+'----------------------------------------------------------------
+,----------------------------------------------------------------
+| Good luck with the deadline, here's the task:
+| [D][ ] /at mysterious (by: [unknown])
+'----------------------------------------------------------------
+,----------------------------------------------------------------
+| Good luck with the deadline, here's the task:
+| [D][ ] /by mysterious number 2 (by: [unknown])
+'----------------------------------------------------------------
+,----------------------------------------------------------------
+| Good luck with the deadline, here's the task:
+| [D][ ] rush this please (by: when?)
+'----------------------------------------------------------------
+,----------------------------------------------------------------
+| Here, your tasks:
+| 1.[T][ ] homework
+| 2.[E][ ] meet friends (at: 31 Dec 1950 2359)
+| 3.[E][X] meet people at dinner (at: 31 Dec 1200 2359)
+| 4.[E][X] meet people at function x/y/z whatever (at: 31 Dec 1200 2359)
+| 5.[D][X] /at mysterious (by: 31 Dec 1960 2359)
+| 6.[D][X] /by mysterious number 2 (by: 31 Dec 1000 2359)
+| 7.[D][ ] rush this please (by: when?)
+| 8.[E][ ] wow much space (at: 10am)
+| 9.[T][ ] homework
+| 10.[T][ ] other homework
+| 11.[T][ ] project 12
+| 12.[E][ ] meet friends (at: [unknown])
+| 13.[E][ ] meet people at dinner (at: 6:25 pm)
+| 14.[E][ ] meet people at function x/y/z whatever (at: 07 Nov 1524 0712)
+| 15.[D][ ] /at mysterious (by: [unknown])
+| 16.[D][ ] /by mysterious number 2 (by: [unknown])
+| 17.[D][ ] rush this please (by: when?)
+'----------------------------------------------------------------
+,----------------------------------------------------------------
+| It seems you didn't need this task anymore, so I removed it:
+| [E][ ] meet friends (at: 31 Dec 1950 2359)
+| You have 16 tasks left.
+'----------------------------------------------------------------
+,----------------------------------------------------------------
+| It seems you didn't need this task anymore, so I removed it:
+| [E][X] meet people at dinner (at: 31 Dec 1200 2359)
+| You have 15 tasks left.
+'----------------------------------------------------------------
+,----------------------------------------------------------------
+| Here, your tasks:
+| 1.[T][ ] homework
+| 2.[E][X] meet people at function x/y/z whatever (at: 31 Dec 1200 2359)
+| 3.[D][X] /at mysterious (by: 31 Dec 1960 2359)
+| 4.[D][X] /by mysterious number 2 (by: 31 Dec 1000 2359)
+| 5.[D][ ] rush this please (by: when?)
+| 6.[E][ ] wow much space (at: 10am)
+| 7.[T][ ] homework
+| 8.[T][ ] other homework
+| 9.[T][ ] project 12
+| 10.[E][ ] meet friends (at: [unknown])
+| 11.[E][ ] meet people at dinner (at: 6:25 pm)
+| 12.[E][ ] meet people at function x/y/z whatever (at: 07 Nov 1524 0712)
+| 13.[D][ ] /at mysterious (by: [unknown])
+| 14.[D][ ] /by mysterious number 2 (by: [unknown])
+| 15.[D][ ] rush this please (by: when?)
+'----------------------------------------------------------------
+,----------------------------------------------------------------
+| Sorry, the number 0, wasn't in the range.
+'----------------------------------------------------------------
+,----------------------------------------------------------------
+| (>.<') Add a description to your delete.
+'----------------------------------------------------------------
+,----------------------------------------------------------------
+| Sorry, I didn't understand nan, please give me a number.
+'----------------------------------------------------------------
+,----------------------------------------------------------------
+| Sorry, the number 24, wasn't in the range.
+'----------------------------------------------------------------
+,----------------------------------------------------------------
+| That's going to happen at some time later:
+| [E][ ] wow much space (at: 10am)
+'----------------------------------------------------------------
+,----------------------------------------------------------------
+| Here, your tasks:
+| 1.[T][ ] homework
+| 2.[E][X] meet people at function x/y/z whatever (at: 31 Dec 1200 2359)
+| 3.[D][X] /at mysterious (by: 31 Dec 1960 2359)
+| 4.[D][X] /by mysterious number 2 (by: 31 Dec 1000 2359)
+| 5.[D][ ] rush this please (by: when?)
+| 6.[E][ ] wow much space (at: 10am)
+| 7.[T][ ] homework
+| 8.[T][ ] other homework
+| 9.[T][ ] project 12
+| 10.[E][ ] meet friends (at: [unknown])
+| 11.[E][ ] meet people at dinner (at: 6:25 pm)
+| 12.[E][ ] meet people at function x/y/z whatever (at: 07 Nov 1524 0712)
+| 13.[D][ ] /at mysterious (by: [unknown])
+| 14.[D][ ] /by mysterious number 2 (by: [unknown])
+| 15.[D][ ] rush this please (by: when?)
+| 16.[E][ ] wow much space (at: 10am)
+'----------------------------------------------------------------
+,----------------------------------------------------------------
+| (>.<') Add a description to your find.
+'----------------------------------------------------------------
+,----------------------------------------------------------------
+| (>.<') Add a description to your find.
+'----------------------------------------------------------------
+,----------------------------------------------------------------
+| Here are the tasks that you might be looking for:
+'----------------------------------------------------------------
+,----------------------------------------------------------------
+| Here are the tasks that you might be looking for:
+'----------------------------------------------------------------
+,----------------------------------------------------------------
+| Here are the tasks that you might be looking for:
+| 1.[T][ ] homework
+| 2.[E][X] meet people at function x/y/z whatever (at: 31 Dec 1200 2359)
+| 7.[T][ ] homework
+| 8.[T][ ] other homework
+| 10.[E][ ] meet friends (at: [unknown])
+| 11.[E][ ] meet people at dinner (at: 6:25 pm)
+| 12.[E][ ] meet people at function x/y/z whatever (at: 07 Nov 1524 0712)
+'----------------------------------------------------------------
+,----------------------------------------------------------------
+| Here are the tasks that you might be looking for:
+| 2.[E][X] meet people at function x/y/z whatever (at: 31 Dec 1200 2359)
+| 3.[D][X] /at mysterious (by: 31 Dec 1960 2359)
+| 4.[D][X] /by mysterious number 2 (by: 31 Dec 1000 2359)
+| 5.[D][ ] rush this please (by: when?)
+| 8.[T][ ] other homework
+| 9.[T][ ] project 12
+| 10.[E][ ] meet friends (at: [unknown])
+| 11.[E][ ] meet people at dinner (at: 6:25 pm)
+| 12.[E][ ] meet people at function x/y/z whatever (at: 07 Nov 1524 0712)
+| 13.[D][ ] /at mysterious (by: [unknown])
+| 14.[D][ ] /by mysterious number 2 (by: [unknown])
+| 15.[D][ ] rush this please (by: when?)
+'----------------------------------------------------------------
+,----------------------------------------------------------------
+| Here, your tasks:
+| 1.[T][ ] homework
+| 2.[E][X] meet people at function x/y/z whatever (at: 31 Dec 1200 2359)
+| 3.[D][X] /at mysterious (by: 31 Dec 1960 2359)
+| 4.[D][X] /by mysterious number 2 (by: 31 Dec 1000 2359)
+| 5.[D][ ] rush this please (by: when?)
+| 6.[E][ ] wow much space (at: 10am)
+| 7.[T][ ] homework
+| 8.[T][ ] other homework
+| 9.[T][ ] project 12
+| 10.[E][ ] meet friends (at: [unknown])
+| 11.[E][ ] meet people at dinner (at: 6:25 pm)
+| 12.[E][ ] meet people at function x/y/z whatever (at: 07 Nov 1524 0712)
+| 13.[D][ ] /at mysterious (by: [unknown])
+| 14.[D][ ] /by mysterious number 2 (by: [unknown])
+| 15.[D][ ] rush this please (by: when?)
+| 16.[E][ ] wow much space (at: 10am)
+'----------------------------------------------------------------
+,----------------------------------------------------------------
+| Sorry, the number -1, wasn't in the range.
+'----------------------------------------------------------------
+,----------------------------------------------------------------
+| Sorry, the number -1, wasn't in the range.
+'----------------------------------------------------------------
+,----------------------------------------------------------------
+| Sorry, the number -1, wasn't in the range.
+'----------------------------------------------------------------
+,----------------------------------------------------------------
+| Sorry, the number -1, wasn't in the range.
+'----------------------------------------------------------------
+,----------------------------------------------------------------
+| Sorry, I didn't understand a, please give me a number.
+'----------------------------------------------------------------
+,----------------------------------------------------------------
+| Sorry, I didn't understand a, please give me a number.
+'----------------------------------------------------------------
+,----------------------------------------------------------------
+| Sorry, I didn't understand a, please give me a number.
+'----------------------------------------------------------------
+,----------------------------------------------------------------
+| Sorry, I didn't understand a, please give me a number.
+'----------------------------------------------------------------
+,----------------------------------------------------------------
+| Sorry, the number 0, wasn't in the range.
+'----------------------------------------------------------------
+,----------------------------------------------------------------
+| Sorry, the number 0, wasn't in the range.
+'----------------------------------------------------------------
+,----------------------------------------------------------------
+| Sorry, the number 0, wasn't in the range.
+'----------------------------------------------------------------
+,----------------------------------------------------------------
+| Sorry, the number 0, wasn't in the range.
+'----------------------------------------------------------------
+,----------------------------------------------------------------
+| That's a todo, it doesn't have a date.
+'----------------------------------------------------------------
+,----------------------------------------------------------------
+| That's a todo, it doesn't have a date.
+'----------------------------------------------------------------
+,----------------------------------------------------------------
+| That's a todo, it doesn't have a date.
+'----------------------------------------------------------------
+,----------------------------------------------------------------
+| That's a todo, it doesn't have a date.
+'----------------------------------------------------------------
+,----------------------------------------------------------------
+| I have rescheduled your task!
+| [E][X] meet people at function x/y/z whatever (at: 31 Dec 1200 2359)
+'----------------------------------------------------------------
+,----------------------------------------------------------------
+| Do specify /at for events.
+'----------------------------------------------------------------
+,----------------------------------------------------------------
+| I have rescheduled your task!
+| [E][X] meet people at function x/y/z whatever (at: 31 Dec 1200 2359)
+'----------------------------------------------------------------
+,----------------------------------------------------------------
+| I have rescheduled your task!
+| [E][X] meet people at function x/y/z whatever (at: 31 Dec 1200 2359)
+'----------------------------------------------------------------
+,----------------------------------------------------------------
+| I have rescheduled your task!
+| [E][X] meet people at function x/y/z whatever (at: 31 Dec 1200 2359)
+'----------------------------------------------------------------
+,----------------------------------------------------------------
+| Do specify /at for events.
+'----------------------------------------------------------------
+,----------------------------------------------------------------
+| I have rescheduled your task!
+| [E][X] meet people at function x/y/z whatever (at: 31 Dec 1200 2359)
+'----------------------------------------------------------------
+,----------------------------------------------------------------
+| I have rescheduled your task!
+| [E][X] meet people at function x/y/z whatever (at: 31 Dec 1200 2359)
+'----------------------------------------------------------------
+,----------------------------------------------------------------
+| Do specify /by for deadlines.
+'----------------------------------------------------------------
+,----------------------------------------------------------------
+| I have rescheduled your task!
+| [D][X] /at mysterious (by: 31 Dec 1000 2359)
+'----------------------------------------------------------------
+,----------------------------------------------------------------
+| Do specify /by for deadlines.
+'----------------------------------------------------------------
+,----------------------------------------------------------------
+| I have rescheduled your task!
+| [D][X] /by mysterious number 2 (by: 31 Dec 1000 2359)
+'----------------------------------------------------------------
+,----------------------------------------------------------------
+| Do specify /by for deadlines.
+'----------------------------------------------------------------
+,----------------------------------------------------------------
+| I have rescheduled your task!
+| [D][ ] rush this please (by: 31 Dec 1000 2359)
+'----------------------------------------------------------------
+,----------------------------------------------------------------
+| I have rescheduled your task!
+| [E][ ] wow much space (at: 31 Dec 1200 2359)
+'----------------------------------------------------------------
+,----------------------------------------------------------------
+| Do specify /at for events.
+'----------------------------------------------------------------
+,----------------------------------------------------------------
+| Added your note about hello.
+'----------------------------------------------------------------
+,----------------------------------------------------------------
+| Added your note about pi.
+'----------------------------------------------------------------
+,----------------------------------------------------------------
+| Added your note about hello.
+'----------------------------------------------------------------
+,----------------------------------------------------------------
+| Here, your notes:
+| 1.hello
+| 2.pi
+| 3.hello
+| 4.pi
+| 5.hello
+'----------------------------------------------------------------
+,----------------------------------------------------------------
+| Sorry, I didn't understand fails, please give me a number.
+'----------------------------------------------------------------
+,----------------------------------------------------------------
+| These notes match your query:
+| 1.hello
+| 3.hello
+| 5.hello
+'----------------------------------------------------------------
+,----------------------------------------------------------------
+| Here's the note:
+| hello
+| this is a great note
+'----------------------------------------------------------------
+,----------------------------------------------------------------
+| Here's the note:
+| pi
+| 3.14159265358979323
+'----------------------------------------------------------------
+,----------------------------------------------------------------
+| Here's the note:
+| hello
+| this is a great note
+'----------------------------------------------------------------
+,----------------------------------------------------------------
+| Sorry, the number 0, wasn't in the range.
+'----------------------------------------------------------------
+,----------------------------------------------------------------
+| Sorry, the number -1, wasn't in the range.
+'----------------------------------------------------------------
+,----------------------------------------------------------------
+| Sorry, the number 12389, wasn't in the range.
+'----------------------------------------------------------------
+,----------------------------------------------------------------
+| Sorry, I didn't understand okay, please give me a number.
+'----------------------------------------------------------------
+,----------------------------------------------------------------
+| That's a todo, it doesn't have a date.
+'----------------------------------------------------------------
+,----------------------------------------------------------------
+| I have rescheduled your task!
+| [E][X] meet people at function x/y/z whatever (at: 31 Dec 1950 2359)
+'----------------------------------------------------------------
+,----------------------------------------------------------------
+| I have rescheduled your task!
+| [D][ ] rush this please (by: 31 Dec 1960 2359)
+'----------------------------------------------------------------
+,----------------------------------------------------------------
+| I removed this note:
+| hello
+'----------------------------------------------------------------
+,----------------------------------------------------------------
+| Here, your notes:
+| 1.hello
+| 2.pi
+| 3.pi
+| 4.hello
+'----------------------------------------------------------------
+,----------------------------------------------------------------
+| It was nice to have you around, I'm going back to sleep...
+'----------------------------------------------------------------
diff --git a/text-ui-test/input.txt b/text-ui-test/input.txt
index e69de29bb2..0a24fcb230 100644
--- a/text-ui-test/input.txt
+++ b/text-ui-test/input.txt
@@ -0,0 +1,104 @@
+random
+
+todo
+deadline
+event
+mark
+unmark
+list
+todo homework /at /by 1:23
+todo other homework /options ignored
+todo project 12
+mark 1
+mark 2
+mark 0
+mark -1
+mark not a number
+unmark not a number
+list
+unmark 1
+unmark 2
+list
+mark 3
+list
+mark 4
+list
+mark 5
+list
+mark 6
+list
+event meet friends
+event meet people at dinner /at 6:25 pm
+event meet people at function x/y/z whatever /at oh no not this /by /at 7:12am 1524 Fri NOV
+list
+deadline /at mysterious
+deadline /by mysterious number 2
+deadline rush this please /by when? /option is ignored
+list
+delete 2
+delete 2
+list
+delete 0
+delete
+delete nan
+delete 24
+ event wow much space / very /wow /at 10am
+list
+find
+find
+find meet
+find me
+find me
+find t
+list
+reschedule -1 /at 1200
+reschedule -1 /by 1000
+reschedule -1 /at 1200 /by 1000
+reschedule -1 /by 1000 /at 1200
+reschedule a /at 1200
+reschedule a /by 1000
+reschedule a /at 1200 /by 1000
+reschedule a /by 1000 /at 1200
+reschedule 0 /at 1200
+reschedule 0 /by 1000
+reschedule 0 /at 1200 /by 1000
+reschedule 0 /by 1000 /at 1200
+reschedule 01 /at 1200
+reschedule 01 /by 1000
+reschedule 01 /at 1200 /by 1000
+reschedule 01 /by 1000 /at 1200
+reschedule 2 /at 1200
+reschedule 2 /by 1000
+reschedule 2 /at 1200 /by 1000
+reschedule 2 /by 1000 /at 1200
+reschedule 02 /at 1200
+reschedule 02 /by 1000
+reschedule 02 /at 1200 /by 1000
+reschedule 02 /by 1000 /at 1200
+reschedule 3 /at 1200
+reschedule 3 /by 1000
+reschedule 4 /at 1200
+reschedule 4 /by 1000
+reschedule 5 /at 1200
+reschedule 5 /by 1000
+reschedule 6 /at 1200
+reschedule 6 /by 1000
+note hello /content this is a great note
+note pi /content 3.14159265358979323
+note hello /content repeated title
+list notes
+delete note fails
+find notes he
+view note 1
+view note 2
+view note 3
+view note 0
+view note -1
+view note 12389
+view note okay
+reschedule 1 /at hi1 /by hi2
+reschedule 2 /at 1950 /by 1960
+reschedule 5 /at 1950 /by 1960
+delete note 3
+list notes
+bye
diff --git a/text-ui-test/runtest.bat b/text-ui-test/runtest.bat
index 0873744649..5792655923 100644
--- a/text-ui-test/runtest.bat
+++ b/text-ui-test/runtest.bat
@@ -7,15 +7,28 @@ REM delete output from previous run
if exist ACTUAL.TXT del ACTUAL.TXT
REM compile the code into the bin folder
-javac -cp ..\src\main\java -Xlint:none -d ..\bin ..\src\main\java\*.java
+javac -cp ..\src\main\java -Xlint:none -d ..\bin ..\src\main\java\anthea\*.java ..\src\main\java\anthea\exception\*.java ..\src\main\java\anthea\note\*.java ..\src\main\java\anthea\task\*.java
IF ERRORLEVEL 1 (
echo ********** BUILD FAILURE **********
exit /b 1
)
REM no error here, errorlevel == 0
+REM delete data
+if exist anthea.txt del anthea.txt
+if exist antheaNotes.txt del antheaNotes.txt
+
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 anthea.Anthea < input.txt > ACTUAL.TXT
REM compare the output to the expected output
FC ACTUAL.TXT EXPECTED.TXT
+
+REM delete output from previous run
+if exist ACTUAL1.TXT del ACTUAL1.TXT
+
+REM run the program, feed commands from input.txt file and redirect the output to the ACTUAL1.TXT
+java -classpath ..\bin anthea.Anthea < input.txt > ACTUAL1.TXT
+
+REM compare the output to the expected output
+FC ACTUAL1.TXT EXPECTED1.TXT
diff --git a/text-ui-test/runtest.sh b/text-ui-test/runtest.sh
old mode 100644
new mode 100755
index c9ec870033..a01717f0af
--- a/text-ui-test/runtest.sh
+++ b/text-ui-test/runtest.sh
@@ -13,25 +13,56 @@ then
fi
# compile the code into the bin folder, terminates if error occurred
-if ! javac -cp ../src/main/java -Xlint:none -d ../bin ../src/main/java/*.java
+if ! javac -cp ../src/main/java -Xlint:none -d ../bin ../src/main/java/anthea/*.java ../src/main/java/anthea/exception/*.java ../src/main/java/anthea/note/*.java ../src/main/java/anthea/task/*.java
then
echo "********** BUILD FAILURE **********"
exit 1
fi
+# delete data
+if [ -e "./anthea.txt" ]
+then
+ rm anthea.txt
+fi
+if [ -e "./antheaNotes.txt" ]
+then
+ rm antheaNotes.txt
+fi
+
# run the program, feed commands from input.txt file and redirect the output to the ACTUAL.TXT
-java -classpath ../bin Duke < input.txt > ACTUAL.TXT
+java -classpath ../bin anthea.Anthea < input.txt > ACTUAL.TXT
# convert to UNIX format
cp EXPECTED.TXT EXPECTED-UNIX.TXT
dos2unix ACTUAL.TXT EXPECTED-UNIX.TXT
+# delete output from previous run
+if [ -e "./ACTUAL1.TXT" ]
+then
+ rm ACTUAL1.TXT
+fi
+
+# run the program, feed commands from input.txt file and redirect the output to the ACTUAL1.TXT
+java -classpath ../bin anthea.Anthea < input.txt > ACTUAL1.TXT
+
+# convert to UNIX format
+cp EXPECTED1.TXT EXPECTED1-UNIX.TXT
+dos2unix ACTUAL1.TXT EXPECTED1-UNIX.TXT
+
# compare the output to the expected output
diff ACTUAL.TXT EXPECTED-UNIX.TXT
if [ $? -eq 0 ]
then
- echo "Test result: PASSED"
- exit 0
+ # compare the output to the expected output
+ diff ACTUAL1.TXT EXPECTED1-UNIX.TXT
+ if [ $? -eq 0 ]
+ then
+ echo "Test result: PASSED"
+ exit 0
+ else
+ echo "Test result: FAILED SECOND"
+ exit 1
+ fi
else
echo "Test result: FAILED"
exit 1