diff --git a/.github/workflows/gradle.yml b/.github/workflows/gradle.yml
new file mode 100644
index 0000000000..391c46b4fe
--- /dev/null
+++ b/.github/workflows/gradle.yml
@@ -0,0 +1,34 @@
+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
diff --git a/.gitignore b/.gitignore
index f69985ef1f..7a60ce093a 100644
--- a/.gitignore
+++ b/.gitignore
@@ -15,3 +15,6 @@ bin/
/text-ui-test/ACTUAL.txt
text-ui-test/EXPECTED-UNIX.TXT
+duke.txt
+tasks.txt
+gui-test.txt
diff --git a/README.md b/README.md
index 9d95025bce..0015389bc2 100644
--- a/README.md
+++ b/README.md
@@ -1,4 +1,4 @@
-# Duke project template
+# meimei.MeimeiBot project template
This is a project template for a greenfield Java project. It's named after the Java mascot _Duke_. Given below are instructions on how to use it.
@@ -15,7 +15,7 @@ Prerequisites: JDK 11, update Intellij to the most recent version.
1. Click `Open or Import`.
1. Select the project directory, and click `OK`
1. If there are any further prompts, accept the defaults.
-1. After the importing is complete, locate the `src/main/java/Duke.java` file, right-click it, and choose `Run Duke.main()`. If the setup is correct, you should see something like the below:
+1. After the importing is complete, locate the `src/main/java/meimei.MeimeiBot.java` file, right-click it, and choose `Run meimei.MeimeiBot.main()`. If the setup is correct, you should see something like the below:
```
Hello from
____ _
diff --git a/build.gradle b/build.gradle
new file mode 100644
index 0000000000..61cc2f03f0
--- /dev/null
+++ b/build.gradle
@@ -0,0 +1,61 @@
+plugins {
+ id 'java'
+ id 'application'
+ id 'checkstyle'
+ id 'com.github.johnrengelman.shadow' version '5.1.0'
+}
+
+repositories {
+ mavenCentral()
+}
+
+dependencies {
+ 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 = "meimei.Launcher"
+}
+
+shadowJar {
+ archiveBaseName = "meimei"
+ archiveClassifier = null
+}
+
+checkstyle {
+ toolVersion = '8.29'
+}
+
+run{
+ standardInput = System.in
+}
diff --git a/config/checkstyle/checkstyle.xml b/config/checkstyle/checkstyle.xml
new file mode 100644
index 0000000000..4c001417ae
--- /dev/null
+++ b/config/checkstyle/checkstyle.xml
@@ -0,0 +1,403 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/config/checkstyle/suppressions.xml b/config/checkstyle/suppressions.xml
new file mode 100644
index 0000000000..39efb6e4ac
--- /dev/null
+++ b/config/checkstyle/suppressions.xml
@@ -0,0 +1,10 @@
+
+
+
+
+
+
+
+
diff --git a/docs/README.md b/docs/README.md
index fd44069597..13523dc3d2 100644
--- a/docs/README.md
+++ b/docs/README.md
@@ -1,20 +1,86 @@
-# User Guide
+# User Guide For Meimei Bot
+
+ Meimei Bot is an interactive bot that offers commands to help the user keep track of a mutable list of tasks that can
+ be of 3 types:
+ * `Todo`,
+ * `Deadline` or
+ * `Event`.
+
+ These tasks can also be marked as done and will be saved in the hard disk.
+
+ After the execution of any command, the bot will return a reply to help the user along or confirm their request.
+
+## Getting Started
+Type in the text box and press Enter or click the `Send` button to execute a command.
## Features
-### Feature 1
-Description of feature.
+### Adding a task: `todo` `deadline` `event`
+Adds a task that is one of three types (`Todo`, `Deadline` or `Event`.) to the list of tasks stored by the bot.
+
+#### Usage
+Use one of the following commands:
+##### `todo` - For simple tasks that need to be done.
+##### `deadline` - For tasks that need to be completed by a specific deadline.
+##### `event` - For events scheduled at a specific date and time.
+
+Type the commands listed above followed by the description of the task you want to add.
+
+Format (for each type of task):
+* `todo {name/description of the task}`
+* `deadline {name/description of the task} /by {date and time in this format: dd/MM/yyyy HH:mm}`
+* `event {name/description of the task} /at {date and time in this format: dd/MM/yyyy HH:mm}`
+
+Examples:
+* `todo homework`
+* `deadline essay /by 21/09/2020 23:59`
+* `event workshop /at 25/09/2020 17:00`
+
+### Listing all tasks: `list`
+Shows a list of all currently saved tasks.
+
+#### Usage
+Format: `list`
+
+Example reply from bot:
+
+`Na, here is your list lah:`
+
+`1.[T][✓] Spring cleaning`
+
+`2.[D][✓] CCA video by: 2 Oct 2020, 11.59 PM`
+
+`3.[E][✘] Dance workshop at: 21 Sep 2020, 7.00 PM`
+
+### Deleting a task: `delete`
+Deletes a task based on the position of the tasks in the whole list of current tasks.
+
+#### Usage
+Format: `delete {number corresponding to the task to be deleted}`
+
+Example:
+* `delete 1` Deletes the first task in the list.
+
+### Marking a task as done: `done`
+Marks a task as done based on the position of the tasks in the whole list of current tasks.
-## Usage
+#### Usage
+Format: `done {number corresponding to the task to be marked}`
-### `Keyword` - Describe action
+Example:
+* `done 1` Marks the first task in the list as done.
-Describe action and its outcome.
+### Finding tasks: `find`
+Asks the bot to find and return tasks that fully or partially match a user given string.
-Example of usage:
+#### Usage
+Format: `find {keyword(s) that the user is searching for}`
-`keyword (optional arguments)`
+Examples:
+* `find home work` Asks the bot to look for tasks that include the strings "home" or "work" in their descriptions/names.
-Expected outcome:
+### Exiting the bot: `bye`
+Terminates the bot and quits the application, closing the windows running the application.
-`outcome`
+#### Usage
+Format: `bye`
\ No newline at end of file
diff --git a/docs/Ui.png b/docs/Ui.png
new file mode 100644
index 0000000000..3c4b61c17c
Binary files /dev/null and b/docs/Ui.png differ
diff --git a/docs/_config.yml b/docs/_config.yml
new file mode 100644
index 0000000000..3397c9a492
--- /dev/null
+++ b/docs/_config.yml
@@ -0,0 +1 @@
+theme: jekyll-theme-architect
\ No newline at end of file
diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar
new file mode 100644
index 0000000000..f3d88b1c2f
Binary files /dev/null and b/gradle/wrapper/gradle-wrapper.jar differ
diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties
new file mode 100644
index 0000000000..b7c8c5dbf5
--- /dev/null
+++ b/gradle/wrapper/gradle-wrapper.properties
@@ -0,0 +1,5 @@
+distributionBase=GRADLE_USER_HOME
+distributionPath=wrapper/dists
+distributionUrl=https\://services.gradle.org/distributions/gradle-6.2-bin.zip
+zipStoreBase=GRADLE_USER_HOME
+zipStorePath=wrapper/dists
diff --git a/gradlew b/gradlew
new file mode 100755
index 0000000000..2fe81a7d95
--- /dev/null
+++ b/gradlew
@@ -0,0 +1,183 @@
+#!/usr/bin/env sh
+
+#
+# Copyright 2015 the original author or authors.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# https://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+##############################################################################
+##
+## Gradle start up script for UN*X
+##
+##############################################################################
+
+# Attempt to set APP_HOME
+# Resolve links: $0 may be a link
+PRG="$0"
+# Need this for relative symlinks.
+while [ -h "$PRG" ] ; do
+ ls=`ls -ld "$PRG"`
+ link=`expr "$ls" : '.*-> \(.*\)$'`
+ if expr "$link" : '/.*' > /dev/null; then
+ PRG="$link"
+ else
+ PRG=`dirname "$PRG"`"/$link"
+ fi
+done
+SAVED="`pwd`"
+cd "`dirname \"$PRG\"`/" >/dev/null
+APP_HOME="`pwd -P`"
+cd "$SAVED" >/dev/null
+
+APP_NAME="Gradle"
+APP_BASE_NAME=`basename "$0"`
+
+# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
+
+# Use the maximum available, or set MAX_FD != -1 to use that value.
+MAX_FD="maximum"
+
+warn () {
+ echo "$*"
+}
+
+die () {
+ echo
+ echo "$*"
+ echo
+ exit 1
+}
+
+# OS specific support (must be 'true' or 'false').
+cygwin=false
+msys=false
+darwin=false
+nonstop=false
+case "`uname`" in
+ CYGWIN* )
+ cygwin=true
+ ;;
+ Darwin* )
+ darwin=true
+ ;;
+ MINGW* )
+ msys=true
+ ;;
+ NONSTOP* )
+ nonstop=true
+ ;;
+esac
+
+CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
+
+# Determine the Java command to use to start the JVM.
+if [ -n "$JAVA_HOME" ] ; then
+ if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
+ # IBM's JDK on AIX uses strange locations for the executables
+ JAVACMD="$JAVA_HOME/jre/sh/java"
+ else
+ JAVACMD="$JAVA_HOME/bin/java"
+ fi
+ if [ ! -x "$JAVACMD" ] ; then
+ die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+ fi
+else
+ JAVACMD="java"
+ which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+fi
+
+# Increase the maximum file descriptors if we can.
+if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
+ MAX_FD_LIMIT=`ulimit -H -n`
+ if [ $? -eq 0 ] ; then
+ if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
+ MAX_FD="$MAX_FD_LIMIT"
+ fi
+ ulimit -n $MAX_FD
+ if [ $? -ne 0 ] ; then
+ warn "Could not set maximum file descriptor limit: $MAX_FD"
+ fi
+ else
+ warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
+ fi
+fi
+
+# For Darwin, add options to specify how the application appears in the dock
+if $darwin; then
+ GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
+fi
+
+# For Cygwin or MSYS, switch paths to Windows format before running java
+if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
+ APP_HOME=`cygpath --path --mixed "$APP_HOME"`
+ CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
+ JAVACMD=`cygpath --unix "$JAVACMD"`
+
+ # We build the pattern for arguments to be converted via cygpath
+ ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
+ SEP=""
+ for dir in $ROOTDIRSRAW ; do
+ ROOTDIRS="$ROOTDIRS$SEP$dir"
+ SEP="|"
+ done
+ OURCYGPATTERN="(^($ROOTDIRS))"
+ # Add a user-defined pattern to the cygpath arguments
+ if [ "$GRADLE_CYGPATTERN" != "" ] ; then
+ OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
+ fi
+ # Now convert the arguments - kludge to limit ourselves to /bin/sh
+ i=0
+ for arg in "$@" ; do
+ CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
+ CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
+
+ if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
+ eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
+ else
+ eval `echo args$i`="\"$arg\""
+ fi
+ i=`expr $i + 1`
+ done
+ case $i in
+ 0) set -- ;;
+ 1) set -- "$args0" ;;
+ 2) set -- "$args0" "$args1" ;;
+ 3) set -- "$args0" "$args1" "$args2" ;;
+ 4) set -- "$args0" "$args1" "$args2" "$args3" ;;
+ 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
+ 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
+ 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
+ 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
+ 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
+ esac
+fi
+
+# Escape application args
+save () {
+ for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
+ echo " "
+}
+APP_ARGS=`save "$@"`
+
+# Collect all arguments for the java command, following the shell quoting and substitution rules
+eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
+
+exec "$JAVACMD" "$@"
diff --git a/gradlew.bat b/gradlew.bat
new file mode 100644
index 0000000000..62bd9b9cce
--- /dev/null
+++ b/gradlew.bat
@@ -0,0 +1,103 @@
+@rem
+@rem Copyright 2015 the original author or authors.
+@rem
+@rem Licensed under the Apache License, Version 2.0 (the "License");
+@rem you may not use this file except in compliance with the License.
+@rem You may obtain a copy of the License at
+@rem
+@rem https://www.apache.org/licenses/LICENSE-2.0
+@rem
+@rem Unless required by applicable law or agreed to in writing, software
+@rem distributed under the License is distributed on an "AS IS" BASIS,
+@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+@rem See the License for the specific language governing permissions and
+@rem limitations under the License.
+@rem
+
+@if "%DEBUG%" == "" @echo off
+@rem ##########################################################################
+@rem
+@rem Gradle startup script for Windows
+@rem
+@rem ##########################################################################
+
+@rem Set local scope for the variables with windows NT shell
+if "%OS%"=="Windows_NT" setlocal
+
+set DIRNAME=%~dp0
+if "%DIRNAME%" == "" set DIRNAME=.
+set APP_BASE_NAME=%~n0
+set APP_HOME=%DIRNAME%
+
+@rem Resolve any "." and ".." in APP_HOME to make it shorter.
+for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
+
+@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
+
+@rem Find java.exe
+if defined JAVA_HOME goto findJavaFromJavaHome
+
+set JAVA_EXE=java.exe
+%JAVA_EXE% -version >NUL 2>&1
+if "%ERRORLEVEL%" == "0" goto init
+
+echo.
+echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:findJavaFromJavaHome
+set JAVA_HOME=%JAVA_HOME:"=%
+set JAVA_EXE=%JAVA_HOME%/bin/java.exe
+
+if exist "%JAVA_EXE%" goto init
+
+echo.
+echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:init
+@rem Get command-line arguments, handling Windows variants
+
+if not "%OS%" == "Windows_NT" goto win9xME_args
+
+:win9xME_args
+@rem Slurp the command line arguments.
+set CMD_LINE_ARGS=
+set _SKIP=2
+
+:win9xME_args_slurp
+if "x%~1" == "x" goto execute
+
+set CMD_LINE_ARGS=%*
+
+:execute
+@rem Setup the command line
+
+set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
+
+@rem Execute Gradle
+"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
+
+:end
+@rem End local scope for the variables with windows NT shell
+if "%ERRORLEVEL%"=="0" goto mainEnd
+
+:fail
+rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
+rem the _cmd.exe /c_ return code!
+if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
+exit /b 1
+
+:mainEnd
+if "%OS%"=="Windows_NT" endlocal
+
+:omega
diff --git a/src/main/java/Duke.java b/src/main/java/Duke.java
deleted file mode 100644
index 5d313334cc..0000000000
--- a/src/main/java/Duke.java
+++ /dev/null
@@ -1,10 +0,0 @@
-public class Duke {
- public static void main(String[] args) {
- String logo = " ____ _ \n"
- + "| _ \\ _ _| | _____ \n"
- + "| | | | | | | |/ / _ \\\n"
- + "| |_| | |_| | < __/\n"
- + "|____/ \\__,_|_|\\_\\___|\n";
- System.out.println("Hello from\n" + logo);
- }
-}
diff --git a/src/main/java/META-INF/MANIFEST.MF b/src/main/java/META-INF/MANIFEST.MF
new file mode 100644
index 0000000000..d79f44e9ee
--- /dev/null
+++ b/src/main/java/META-INF/MANIFEST.MF
@@ -0,0 +1,3 @@
+Manifest-Version: 1.0
+Main-Class: meimei.MeimeiBot
+
diff --git a/src/main/java/meimei/Launcher.java b/src/main/java/meimei/Launcher.java
new file mode 100644
index 0000000000..399a889ecb
--- /dev/null
+++ b/src/main/java/meimei/Launcher.java
@@ -0,0 +1,15 @@
+package meimei;
+
+import javafx.application.Application;
+import meimei.gui.Gui;
+
+/**
+ * A launcher class to workaround classpath issues.
+ * Reused from
+ * this guide.
+ */
+public class Launcher {
+ public static void main(String[] args) {
+ Application.launch(Gui.class, args);
+ }
+}
diff --git a/src/main/java/meimei/MeimeiBot.java b/src/main/java/meimei/MeimeiBot.java
new file mode 100644
index 0000000000..249c6da646
--- /dev/null
+++ b/src/main/java/meimei/MeimeiBot.java
@@ -0,0 +1,120 @@
+package meimei;
+
+import meimei.botexception.BotException;
+import meimei.command.Command;
+import meimei.command.Parser;
+
+/**
+ * Meimei Bot is an interactive bot that offers commands to help the
+ * user keep track of a mutable list of tasks that can be of 3 types:
+ * Todo, Deadline or Event.
+ * These tasks can also be marked as done and will be saved in the hard disk.
+ *
+ * @author Foo Jing Yi
+ */
+public class MeimeiBot {
+ /**
+ * Storage object used by Meimei Bot to load from and write to hard disk
+ */
+ private final Storage storage;
+ /**
+ * TaskList object that contains the list of tasks
+ */
+ private TaskList tasks;
+ /**
+ * Ui object that deals with interactions with the user
+ */
+ private final Ui ui;
+
+ private boolean isRunning;
+
+ /**
+ * Public class constructor that takes in the location of a file as a string
+ * indicating the relative file path.
+ * The list of tasks will be loaded from and saved to this file.
+ *
+ * @param pathFile Relative file path.
+ */
+ public MeimeiBot(String pathFile) {
+ this.ui = new Ui();
+ this.storage = new Storage(pathFile);
+ this.isRunning = false;
+ try {
+ this.tasks = new TaskList(storage.load());
+ } catch (BotException e) {
+ System.out.println(e.getMessage());
+ this.tasks = new TaskList();
+ }
+ }
+
+ /**
+ * Runs the bot in the terminal.
+ */
+ public void run() {
+ this.ui.showLine();
+ this.isRunning = true;
+ System.out.println(start());
+ this.ui.showLine();
+ while (isRunning) {
+ try {
+ String fullCommand = this.ui.readCommand();
+ this.ui.showLine();
+ Command command = Parser.parse(fullCommand);
+ System.out.println(command.execute(this.tasks, this.storage, this.ui));
+ isRunning = !command.isExit();
+ } catch (BotException e) {
+ System.out.println(this.ui.returnError(e.getMessage()));
+ } finally {
+ this.ui.showLine();
+ }
+ }
+ }
+
+ /**
+ * Main method for running bot in the terminal.
+ *
+ * @param args Arguments as pass from command line.
+ */
+ public static void main(String[] args) {
+ new MeimeiBot("data/tasks.txt").run();
+ }
+
+ /**
+ * Runs the bot for the GUI.
+ *
+ * @return The welcome message that the user sees on starting the bot.
+ */
+ public String start() {
+ this.isRunning = true;
+ return this.ui.returnWelcomeMsg();
+ }
+
+ /**
+ * Gets a response from the bot based on the input through the CLI of the GUI.
+ *
+ * @param input The user input from the CLI of the GUI.
+ * @return The reply from Meimei Bot to the user.
+ */
+ public String getResponse(String input) {
+ String response;
+ try {
+ Command command = Parser.parse(input);
+ response = command.execute(tasks, storage, ui);
+ isRunning = !command.isExit();
+ } catch (BotException e) {
+ e.printStackTrace();
+ response = ui.returnError(e.getMessage());
+ }
+
+ return response;
+ }
+
+ /**
+ * Informs the caller of the method on whether the bot is running.
+ *
+ * @return Boolean indicating whether the bot is running.
+ */
+ public boolean isRunning() {
+ return isRunning;
+ }
+}
diff --git a/src/main/java/meimei/Storage.java b/src/main/java/meimei/Storage.java
new file mode 100644
index 0000000000..6b795570f2
--- /dev/null
+++ b/src/main/java/meimei/Storage.java
@@ -0,0 +1,237 @@
+package meimei;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.time.LocalDateTime;
+import java.time.format.DateTimeFormatter;
+import java.time.format.DateTimeParseException;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Scanner;
+
+import meimei.botexception.LoadFailureException;
+import meimei.botexception.SaveFailureException;
+import meimei.task.Deadline;
+import meimei.task.Event;
+import meimei.task.Task;
+import meimei.task.Todo;
+/**
+ * Represents the hard disk storage used by the bot.
+ * While this class does not store the data within the class objects,
+ * each Storage object keeps a link to an actual file where
+ * data is stored, and handles loading and writing to this file.
+ */
+public class Storage {
+ public static final String DONE = "1";
+ public static final String NOT_DONE = "0";
+ public static final String TODO_TASK = "T";
+ public static final String DEADLINE_TASK = "D";
+ public static final String EVENT_TASK = "E";
+ public static final DateTimeFormatter FORMATTER = DateTimeFormatter.ofPattern("d MMM yyyy, h.mm a");
+
+ /** File where data is stored */
+ private final File taskFile;
+
+ /**
+ * Public constructor.
+ *
+ * @param filePath Relative file path.
+ */
+ public Storage(String filePath) {
+ this.taskFile = new File(filePath);
+ }
+
+ /**
+ * Loads saved data from taskFile.
+ *
+ * @return List of tasks to be passed to a TaskList object
+ * @throws LoadFailureException If file cannot be created, read or parsed.
+ */
+ public List load() throws LoadFailureException {
+ if (!this.taskFile.exists()) {
+ createFile();
+ }
+
+ try {
+ Scanner sc = new Scanner(this.taskFile);
+
+ return loadTasksFromFile(sc);
+ } catch (FileNotFoundException e) {
+ e.printStackTrace();
+ throw new LoadFailureException("Cannot find the file. Restart?");
+ }
+ }
+
+ private void createFile() throws LoadFailureException {
+ File directory = this.taskFile.getParentFile();
+ if (directory != null && !directory.exists()) {
+ directory.mkdirs();
+ }
+
+ try {
+ this.taskFile.createNewFile();
+ } catch (IOException e) {
+ e.printStackTrace();
+ throw new LoadFailureException("Somehow cannot make a new file. Help me restart?");
+ }
+ }
+
+ private List loadTasksFromFile(Scanner sc) throws LoadFailureException {
+ List list = new ArrayList<>();
+
+ while (sc.hasNext()) {
+ String storedTask = sc.nextLine();
+ Task task = parseFromStorage(storedTask);
+ list.add(task);
+ }
+
+ return list;
+ }
+
+ /**
+ * Updates file by adding a new task.
+ *
+ * @param task Task to be added to file.
+ * @param isFirstTask Whether task to be added is the first in the file.
+ * @throws SaveFailureException If task cannot be parsed or file cannot be written to.
+ */
+ public void update(Task task, boolean isFirstTask) throws SaveFailureException {
+ try {
+ FileWriter fileWriter;
+ if (isFirstTask) {
+ fileWriter = new FileWriter(this.taskFile);
+ fileWriter.write(parseToStorage(task));
+ } else {
+ fileWriter = new FileWriter(this.taskFile, true);
+ fileWriter.write("\n" + parseToStorage(task));
+ }
+ fileWriter.close();
+ } catch (IOException e) {
+ e.printStackTrace();
+ throw new SaveFailureException("Restart?");
+ }
+ }
+
+ /**
+ * Updates file with list when tasks are marked or deleted.
+ *
+ * @param list Updated list given by TaskList object.
+ * @throws SaveFailureException If tasks cannot be parsed or file cannot be written to.
+ */
+ public void update(List list) throws SaveFailureException {
+ try {
+ FileWriter fileWriter = new FileWriter(this.taskFile);
+
+ String fileContents = "";
+ if (list.size() > 0) {
+ fileContents = parseToStorage(list.get(0));
+
+ for (int i = 1; i < list.size(); i++) {
+ fileContents += "\n" + parseToStorage(list.get(i));
+ }
+ }
+
+ fileWriter.write(fileContents);
+ fileWriter.close();
+ } catch (IOException e) {
+ e.printStackTrace();
+ throw new SaveFailureException("Restart?");
+ }
+ }
+
+ /**
+ * Parses Task objects into strings to be written to the
+ * file on the hard disk.
+ *
+ * @param task Task object to be parsed
+ * @return String representation of the task. Note that this string is
+ * different from the string returned by task.toString().
+ * e.g. "[T][✓] Homework" from task.toString() will be
+ * represented as "T | 1 | Homework".
+ * @throws SaveFailureException If the type of the task cannot be recognised.
+ */
+ protected String parseToStorage(Task task) throws SaveFailureException {
+ String taskTypeString;
+ String status = task.isDone() ? DONE : NOT_DONE;
+ String taskName = task.getTaskName();
+ String taskDescription;
+
+ if (task instanceof Todo) {
+ taskTypeString = TODO_TASK;
+ taskDescription = taskName;
+ } else if (task instanceof Deadline) {
+ taskTypeString = DEADLINE_TASK; // + date
+ taskDescription = taskName + " | " + ((Deadline) task).getDateTime().format(FORMATTER);
+ } else if (task instanceof Event) {
+ taskTypeString = EVENT_TASK;
+ taskDescription = taskName + " | " + ((Event) task).getDateTime().format(FORMATTER);
+ } else {
+ throw new SaveFailureException("Cannot recognise the task types!");
+ }
+
+ return taskTypeString + " | " + status + " | " + taskDescription;
+ }
+
+ /**
+ * Parses string representation of task stored in the file on the hard disk
+ * to create a Task object.
+ *
+ * @param storedTask String representation of the task
+ * @return Task represented by the string input.
+ * @throws LoadFailureException If the string is in the wrong format.
+ */
+ protected Task parseFromStorage(String storedTask) throws LoadFailureException {
+ String[] taskElements = storedTask.split(" \\| ", 4);
+
+ String taskTypeString = taskElements[0];
+ String taskName = "";
+ LocalDateTime dateTime = null;
+
+ if (taskElements.length >= 3) {
+ taskName = taskElements[2];
+ }
+
+ if (taskElements.length >= 4) {
+ try {
+ dateTime = LocalDateTime.parse(taskElements[3], FORMATTER);
+ } catch (DateTimeParseException e) {
+ e.printStackTrace();
+ throw new LoadFailureException("Look like date and time wrong format.");
+ }
+ }
+
+ Task task = createTask(taskTypeString, taskName, dateTime);
+
+ if (taskElements[1].equals(DONE)) {
+ task.markDone();
+ }
+
+ return task;
+ }
+
+ /**
+ * Creates task using elements parsed from source file.
+ *
+ * @param taskTypeString String indicating task type.
+ * @param taskName Name of the task (aka Description).
+ * @param dateTime LocalDateTime object indicating date and time of task (if applicable).
+ * @return Task created according the specified elements.
+ * @throws LoadFailureException If the taskTypeString does not refer to any task type.
+ */
+ private Task createTask(String taskTypeString,
+ String taskName,
+ LocalDateTime dateTime) throws LoadFailureException {
+ if (taskTypeString.equals(TODO_TASK)) {
+ return new Todo(taskName);
+ } else if (taskTypeString.equals(DEADLINE_TASK)) {
+ return new Deadline(taskName, dateTime);
+ } else if (taskTypeString.equals(EVENT_TASK)) {
+ return new Event(taskName, dateTime);
+ } else {
+ throw new LoadFailureException("Cannot identify the type of tasks in file."
+ + "\nProbably wrong format? I overwrite ah!");
+ }
+ }
+}
diff --git a/src/main/java/meimei/TaskList.java b/src/main/java/meimei/TaskList.java
new file mode 100644
index 0000000000..6de34f98e1
--- /dev/null
+++ b/src/main/java/meimei/TaskList.java
@@ -0,0 +1,111 @@
+package meimei;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import meimei.botexception.BotException;
+import meimei.task.Task;
+
+/**
+ * Contains the list of tasks based on user inputs
+ * and handles operations on this list.
+ */
+public class TaskList {
+ /** List object containing tasks represented by Task objects */
+ private final List list;
+
+ /**
+ * Public constructor that creates a new (empty) list.
+ */
+ public TaskList() {
+ this.list = new ArrayList<>();
+ }
+
+ /**
+ * Public constructor taking in an existing list loaded from
+ * the hard disk.
+ *
+ * @param list List loaded from the hard disk.
+ */
+ public TaskList(List list) {
+ this.list = list;
+ }
+
+ /**
+ * Adds a task to the list and updates the hard disk with this new task.
+ *
+ * @param task Task to be added.
+ * @param storage Storage object that updates the relevant file on the hard disk.
+ * @throws BotException If exception is thrown by the update method of the Storage class.
+ */
+ public void addTask(Task task, Storage storage) throws BotException {
+ this.list.add(task);
+ assert this.list.size() > 0 : "No task added.";
+ boolean isFirstTask = this.list.size() <= 1;
+ storage.update(task, isFirstTask);
+ }
+
+ /**
+ * Deletes the task represented by a taskNum from the numbered list
+ * displayed to the user. The task is the "taskNum"th task in the list when numbering
+ * the tasks chronologically starting from 1 (not zero).
+ *
+ * @param taskNum Number that corresponds to the task to be deleted.
+ * @param storage Storage object that updates the relevant file on the hard disk.
+ * @throws BotException If exception is thrown by the update method of the Storage class.
+ */
+ public void deleteTask(int taskNum, Storage storage) throws BotException {
+ assert taskNum > 0;
+ this.list.remove(taskNum - 1);
+ storage.update(this.list);
+ }
+
+ /**
+ * Marks the task represented by a taskNum as done.
+ * The task is the "taskNum"th task in the list when numbering
+ * the tasks chronologically starting from 1 (not zero).
+ *
+ * @param taskNum Number that corresponds to the task to be marked.
+ * @param storage Storage object that updates the relevant file on the hard disk.
+ * @throws BotException If exception is thrown by the update method of the Storage class.
+ */
+ public void markDone(int taskNum, Storage storage) throws BotException {
+ assert taskNum > 0;
+ getTask(taskNum).markDone();
+ storage.update(this.list);
+ }
+
+ /**
+ * Returns the task represented by a taskNum.
+ * The task is the "taskNum"th task in the list when numbering
+ * the tasks chronologically starting from 1 (not zero).
+ *
+ * @param taskNum Number that corresponds to the task to be returned.
+ * @return Task represented by the taskNum.
+ */
+ public Task getTask(int taskNum) {
+ assert taskNum > 0;
+ return this.list.get(taskNum - 1);
+ }
+
+ /**
+ * Returns the number of tasks in the list.
+ *
+ * @return Current size of list.
+ */
+ public int getListLength() {
+ return this.list.size();
+ }
+
+ @Override
+ public String toString() {
+ String finalString = "";
+ int counter = 0;
+ for (Task task : this.list) {
+ counter++;
+ finalString += "\n" + counter + "." + task.toString();
+ }
+
+ return finalString;
+ }
+}
diff --git a/src/main/java/meimei/Ui.java b/src/main/java/meimei/Ui.java
new file mode 100644
index 0000000000..2568c1e18b
--- /dev/null
+++ b/src/main/java/meimei/Ui.java
@@ -0,0 +1,98 @@
+package meimei;
+
+import java.util.Scanner;
+
+import meimei.command.AddCommand;
+import meimei.command.Command;
+import meimei.command.DeleteCommand;
+import meimei.task.Task;
+
+/**
+ * Represents the user interface that deals with interactions with the user.
+ */
+public class Ui {
+ /** Scanner to take in user input */
+ private final Scanner sc = new Scanner(System.in);
+
+ /**
+ * Public constructor. Should only be used by bot.
+ */
+ public Ui() { }
+
+ /**
+ * Displays the welcome message to the user.
+ *
+ * @return Welcome message as a string.
+ */
+ public String returnWelcomeMsg() {
+ return "Eh harlow! I'm Meimei!" + "\nWhat you want ah?";
+ }
+
+ /**
+ * Reads user inputs which should contain commands to the bot.
+ *
+ * @return the line inputted by the user
+ */
+ public String readCommand() {
+ return sc.nextLine();
+ }
+
+ /**
+ * Prints lines to frame bot responses in terminal.
+ */
+ public void showLine() {
+ System.out.println("____________________________________________________________");
+ }
+
+ /**
+ * Returns a string with the response from the bot as given by executing a command.
+ * Only for the following commands: AddCommand, DeleteCommand.
+ *
+ * @param command The command that triggers this method in its execute method.
+ * @param task Task involved in the reply.
+ * @param tasks Full list of tasks.
+ * @return Response from Meimei Bot.
+ */
+ public String returnReply(Command command, Task task, TaskList tasks) {
+ String reply = "";
+ if (command instanceof AddCommand) {
+ reply = getAddReply(task, tasks);
+ } else if (command instanceof DeleteCommand) {
+ reply = getDeleteReply(task, tasks);
+ }
+
+ assert !reply.equals("") : "No reply found in returnReply()";
+
+ return reply;
+ }
+
+ public String returnDoneReply(Task task) {
+ return "Can, I help you mark this as done liao:" + "\n " + task.toString();
+ }
+
+ public String returnExitReply() {
+ return "Ok bye bye! C u again :P";
+ }
+
+ public String returnFindReply(String stringOfFound) {
+ return "Na, I found this:" + stringOfFound;
+ }
+
+ public String returnListReply(TaskList tasks) {
+ return "Na, here is your list lah:" + tasks.toString();
+ }
+
+ public String returnError(String message) {
+ return message;
+ }
+
+ private String getAddReply(Task task, TaskList tasks) {
+ return "Orh. I added:" + "\n " + task.toString()
+ + "\nNow you got " + tasks.getListLength() + " things in the list.";
+ }
+
+ private String getDeleteReply(Task task, TaskList tasks) {
+ return "Okay, I deleted this liao:" + "\n " + task.toString()
+ + "\nNow left " + tasks.getListLength() + " things in the list.";
+ }
+}
diff --git a/src/main/java/meimei/botexception/BotException.java b/src/main/java/meimei/botexception/BotException.java
new file mode 100644
index 0000000000..1653066eb4
--- /dev/null
+++ b/src/main/java/meimei/botexception/BotException.java
@@ -0,0 +1,21 @@
+package meimei.botexception;
+
+/**
+ * Represents exceptions unique to the bot.
+ */
+public class BotException extends Exception {
+
+ /**
+ * Public constructor.
+ *
+ * @param message Error message.
+ */
+ public BotException(String message) {
+ super(message);
+ }
+
+ @Override
+ public String getMessage() {
+ return "Aiyo! " + super.getMessage();
+ }
+}
diff --git a/src/main/java/meimei/botexception/LoadFailureException.java b/src/main/java/meimei/botexception/LoadFailureException.java
new file mode 100644
index 0000000000..d17b0ba53f
--- /dev/null
+++ b/src/main/java/meimei/botexception/LoadFailureException.java
@@ -0,0 +1,16 @@
+package meimei.botexception;
+
+/**
+ * Exception thrown when the bot fails to load data from the source file.
+ */
+public class LoadFailureException extends BotException {
+
+ /**
+ * Public constructor.
+ *
+ * @param message Error message.
+ */
+ public LoadFailureException(String message) {
+ super("I cannot load the source file leh." + message);
+ }
+}
diff --git a/src/main/java/meimei/botexception/NoCommandException.java b/src/main/java/meimei/botexception/NoCommandException.java
new file mode 100644
index 0000000000..4abee33617
--- /dev/null
+++ b/src/main/java/meimei/botexception/NoCommandException.java
@@ -0,0 +1,14 @@
+package meimei.botexception;
+
+/**
+ * Exception thrown when a non-valid user command is inputted.
+ */
+public class NoCommandException extends BotException {
+
+ /**
+ * Public constructor.
+ */
+ public NoCommandException() {
+ super("I cannot find this command leh. Try sth else?");
+ }
+}
diff --git a/src/main/java/meimei/botexception/NoDescriptionException.java b/src/main/java/meimei/botexception/NoDescriptionException.java
new file mode 100644
index 0000000000..7bb824cd9d
--- /dev/null
+++ b/src/main/java/meimei/botexception/NoDescriptionException.java
@@ -0,0 +1,24 @@
+package meimei.botexception;
+
+/**
+ * Exception thrown when the description is missing from is missing for the
+ * AddCommand, DeleteCommand or DoneCommand
+ * bot commands.
+ */
+public class NoDescriptionException extends BotException {
+
+ /**
+ * Public constructor.
+ *
+ * @param commandName Name of the type of command that the bot was trying to create
+ * when the exception was thrown.
+ */
+ public NoDescriptionException(String commandName) {
+ super("The description of " + commandName + " cannot be empty lah. Try again!");
+ }
+
+ @Override
+ public String getMessage() {
+ return super.getMessage();
+ }
+}
diff --git a/src/main/java/meimei/botexception/SaveFailureException.java b/src/main/java/meimei/botexception/SaveFailureException.java
new file mode 100644
index 0000000000..634bcf0b46
--- /dev/null
+++ b/src/main/java/meimei/botexception/SaveFailureException.java
@@ -0,0 +1,16 @@
+package meimei.botexception;
+
+/**
+ * Exception thrown when bot is unable to save tasks to source file.
+ */
+public class SaveFailureException extends BotException {
+
+ /**
+ * Public constructor.
+ *
+ * @param message Error message.
+ */
+ public SaveFailureException(String message) {
+ super("I cannot save to the source file leh." + message);
+ }
+}
diff --git a/src/main/java/meimei/botexception/WrongDateTimeException.java b/src/main/java/meimei/botexception/WrongDateTimeException.java
new file mode 100644
index 0000000000..9b32f6de7b
--- /dev/null
+++ b/src/main/java/meimei/botexception/WrongDateTimeException.java
@@ -0,0 +1,23 @@
+package meimei.botexception;
+
+import meimei.command.CommandType;
+
+/**
+ * Exception thrown when date and time is not given in the correct format
+ * for DEADLINE and EVENT user commands from
+ * CommandType.
+ */
+public class WrongDateTimeException extends BotException {
+
+ /**
+ * Public constructor.
+ *
+ * @param commandType Type of command i.e. deadline/event.
+ * @param separator Between description and date & time.
+ */
+ public WrongDateTimeException(CommandType commandType, String separator) {
+ super("You type wrong lah!" + "\nTry \"" + commandType.toString().toLowerCase()
+ + " {description of task} " + separator
+ + " {date and time in this format: dd/MM/yyyy HH:mm}\"");
+ }
+}
diff --git a/src/main/java/meimei/botexception/WrongItemIndexException.java b/src/main/java/meimei/botexception/WrongItemIndexException.java
new file mode 100644
index 0000000000..d2e250d56e
--- /dev/null
+++ b/src/main/java/meimei/botexception/WrongItemIndexException.java
@@ -0,0 +1,27 @@
+package meimei.botexception;
+
+/**
+ * Exception thrown when description of DELETE or DONE
+ * user commands are not accompanied with a valid task number that is an integer
+ * and within the range 1 to the size of the list of tasks (no tasks can be marked
+ * or deleted if there are no task in the list).
+ */
+public class WrongItemIndexException extends BotException {
+
+ /**
+ * Public constructor.
+ *
+ * @param commandName Name of the command that the bot was trying to execute
+ * when this exception was thrown.
+ * @param listLength Length of the list of tasks saved in the bot.
+ */
+ public WrongItemIndexException(String commandName, int listLength) {
+ super("Cannot find leh. Try typing \"" + commandName + " {index of list item}\"."
+ + "\nYour list only got " + listLength + " things.");
+ }
+
+ @Override
+ public String getMessage() {
+ return super.getMessage();
+ }
+}
diff --git a/src/main/java/meimei/command/AddCommand.java b/src/main/java/meimei/command/AddCommand.java
new file mode 100644
index 0000000000..532b19e218
--- /dev/null
+++ b/src/main/java/meimei/command/AddCommand.java
@@ -0,0 +1,101 @@
+package meimei.command;
+
+import java.time.LocalDateTime;
+import java.time.format.DateTimeFormatter;
+import java.time.format.DateTimeParseException;
+
+import meimei.Storage;
+import meimei.TaskList;
+import meimei.Ui;
+import meimei.botexception.BotException;
+import meimei.botexception.WrongDateTimeException;
+import meimei.task.Deadline;
+import meimei.task.Event;
+import meimei.task.Task;
+import meimei.task.Todo;
+
+/**
+ * Command that adds a task to the user's list when executed.
+ */
+public class AddCommand extends Command {
+ /** DateTime Formatter for formatting date and time from user's command String */
+ public static final DateTimeFormatter FORMATTER = DateTimeFormatter.ofPattern("dd/MM/yyyy HH:mm");
+ /** Represents the type of the command used by the user */
+ private final CommandType commandType;
+ /** Additional information needed for executing the command */
+ private final String description;
+
+ /**
+ * Public constructor.
+ *
+ * @param commandType Either TODO, Deadline,
+ * or Event.
+ * @param description Name of the task (and date and time if
+ * Deadline or Event.
+ */
+ public AddCommand(CommandType commandType, String description) {
+ this.commandType = commandType;
+ this.description = description;
+ }
+
+ @Override
+ public String execute(TaskList tasks, Storage storage, Ui ui) throws BotException {
+ Task task;
+ if (this.commandType == CommandType.TODO) {
+ task = new Todo(this.description);
+ } else if (this.commandType == CommandType.DEADLINE) {
+ task = createDeadline();
+ } else if (this.commandType == CommandType.EVENT) {
+ task = createEvent();
+ } else {
+ throw new BotException("Something went wrong! Try again.");
+ }
+
+ tasks.addTask(task, storage);
+
+ return ui.returnReply(this, task, tasks);
+ }
+
+ /**
+ * Creates Deadline object.
+ *
+ * @return Deadline object as created according to user's specifications.
+ * @throws WrongDateTimeException When date and time are not given or are given in the wrong format.
+ */
+ private Deadline createDeadline() throws WrongDateTimeException {
+ try {
+ String[] descElements = this.description.split(" /by ");
+ String taskName = descElements[0];
+ String dateTimeString = descElements[1];
+ LocalDateTime dateTime = LocalDateTime.parse(dateTimeString, FORMATTER);
+
+ return new Deadline(taskName, dateTime);
+ } catch (DateTimeParseException | IndexOutOfBoundsException e) {
+ throw new WrongDateTimeException(this.commandType, "/by");
+ }
+ }
+
+ /**
+ * Creates Event object.
+ *
+ * @return Event object as created according to user's specifications.
+ * @throws WrongDateTimeException When date and time are not given or are given in the wrong format.
+ */
+ private Event createEvent() throws WrongDateTimeException {
+ try {
+ String[] descElements = this.description.split(" /at ");
+ String taskName = descElements[0];
+ String dateTimeString = descElements[1];
+ LocalDateTime dateTime = LocalDateTime.parse(dateTimeString, FORMATTER);
+
+ return new Event(taskName, dateTime);
+ } catch (DateTimeParseException | IndexOutOfBoundsException e) {
+ throw new WrongDateTimeException(this.commandType, "/at");
+ }
+ }
+
+ @Override
+ public boolean isExit() {
+ return false;
+ }
+}
diff --git a/src/main/java/meimei/command/Command.java b/src/main/java/meimei/command/Command.java
new file mode 100644
index 0000000000..586972c579
--- /dev/null
+++ b/src/main/java/meimei/command/Command.java
@@ -0,0 +1,30 @@
+package meimei.command;
+
+import meimei.Storage;
+import meimei.TaskList;
+import meimei.Ui;
+import meimei.botexception.BotException;
+
+/**
+ * Represents a (bot) command that can be executed by the bot.
+ */
+public abstract class Command {
+
+ /**
+ * Executes the command as derived from the user's input.
+ *
+ * @param tasks The user's tasks.
+ * @param storage Handles updating the hard disk accordingly.
+ * @param ui User interface that presents bots replies in a user friendly way.
+ * @return Response to the user.
+ * @throws BotException If a BotException is thrown in executing the command.
+ */
+ public abstract String execute(TaskList tasks, Storage storage, Ui ui) throws BotException;
+
+ /**
+ * Returns true when the command is an exit command and false otherwise.
+ *
+ * @return A boolean indicating if the command is an exit command.
+ */
+ public abstract boolean isExit();
+}
diff --git a/src/main/java/meimei/command/CommandType.java b/src/main/java/meimei/command/CommandType.java
new file mode 100644
index 0000000000..05a7a1e9b1
--- /dev/null
+++ b/src/main/java/meimei/command/CommandType.java
@@ -0,0 +1,17 @@
+package meimei.command;
+
+/**
+ * Types of user commands.
+ * User has to type these commands in each line of input
+ * to command the bot.
+ */
+public enum CommandType {
+ LIST,
+ DONE,
+ DELETE,
+ BYE,
+ TODO,
+ DEADLINE,
+ EVENT,
+ FIND
+}
diff --git a/src/main/java/meimei/command/DeleteCommand.java b/src/main/java/meimei/command/DeleteCommand.java
new file mode 100644
index 0000000000..22a2779d24
--- /dev/null
+++ b/src/main/java/meimei/command/DeleteCommand.java
@@ -0,0 +1,45 @@
+package meimei.command;
+import meimei.Storage;
+import meimei.TaskList;
+import meimei.Ui;
+import meimei.botexception.BotException;
+import meimei.botexception.WrongItemIndexException;
+import meimei.task.Task;
+
+/**
+ * Command that deletes a task from the user's list when executed.
+ */
+public class DeleteCommand extends Command {
+ /** A string representation of the number corresponding to the task to be deleted */
+ private final String description;
+
+ /**
+ * Public constructor.
+ * @param description String representation of the number
+ * corresponding to the task to be deleted.
+ */
+ public DeleteCommand(String description) {
+ this.description = description;
+ }
+
+ @Override
+ public String execute(TaskList tasks, Storage storage, Ui ui) throws BotException {
+ try {
+ int taskNum = Integer.parseInt(this.description);
+ Task task = tasks.getTask(taskNum);
+ tasks.deleteTask(taskNum, storage);
+
+ return ui.returnReply(this, task, tasks);
+ } catch (NumberFormatException e) {
+ throw new WrongItemIndexException(CommandType.DELETE.toString().toLowerCase(),
+ tasks.getListLength());
+ } catch (BotException botException) {
+ throw botException;
+ }
+ }
+
+ @Override
+ public boolean isExit() {
+ return false;
+ }
+}
diff --git a/src/main/java/meimei/command/DoneCommand.java b/src/main/java/meimei/command/DoneCommand.java
new file mode 100644
index 0000000000..dddbeb0e65
--- /dev/null
+++ b/src/main/java/meimei/command/DoneCommand.java
@@ -0,0 +1,45 @@
+package meimei.command;
+
+import meimei.Storage;
+import meimei.TaskList;
+import meimei.Ui;
+import meimei.botexception.BotException;
+import meimei.botexception.WrongItemIndexException;
+import meimei.task.Task;
+
+/**
+ * Command that marks a task from the user's list as completed("done") when executed.
+ */
+public class DoneCommand extends Command {
+ /** A string representation of the number corresponding to the task to be marked */
+ private final String description;
+
+ /**
+ * Public constructor.
+ * @param description String representation of the number
+ * corresponding to the task to be marked.
+ */
+ public DoneCommand(String description) {
+ this.description = description;
+ }
+
+ @Override
+ public String execute(TaskList tasks, Storage storage, Ui ui) throws BotException {
+ try {
+ int taskNum = Integer.parseInt(this.description);
+ assert taskNum > 0;
+ Task task = tasks.getTask(taskNum);
+ tasks.markDone(taskNum, storage);
+
+ return ui.returnDoneReply(task);
+ } catch (NumberFormatException e) {
+ throw new WrongItemIndexException(CommandType.DONE.toString().toLowerCase(),
+ tasks.getListLength());
+ }
+ }
+
+ @Override
+ public boolean isExit() {
+ return false;
+ }
+}
diff --git a/src/main/java/meimei/command/ExitCommand.java b/src/main/java/meimei/command/ExitCommand.java
new file mode 100644
index 0000000000..bb3a374cc5
--- /dev/null
+++ b/src/main/java/meimei/command/ExitCommand.java
@@ -0,0 +1,21 @@
+package meimei.command;
+
+import meimei.Storage;
+import meimei.TaskList;
+import meimei.Ui;
+
+/**
+ * Command that displays a goodbye message and exits(quits) the bot when executed.
+ */
+public class ExitCommand extends Command {
+
+ @Override
+ public String execute(TaskList tasks, Storage storage, Ui ui) {
+ return ui.returnExitReply();
+ }
+
+ @Override
+ public boolean isExit() {
+ return true;
+ }
+}
diff --git a/src/main/java/meimei/command/FindCommand.java b/src/main/java/meimei/command/FindCommand.java
new file mode 100644
index 0000000000..495a943912
--- /dev/null
+++ b/src/main/java/meimei/command/FindCommand.java
@@ -0,0 +1,70 @@
+package meimei.command;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import meimei.Storage;
+import meimei.TaskList;
+import meimei.Ui;
+import meimei.task.Task;
+
+/**
+ * Command that finds tasks that match a keyword or keywords (either partially or fully)
+ * when executed.
+ */
+public class FindCommand extends Command {
+ /** String to be searched for */
+ private final String description;
+
+ /**
+ * Public constructor. Converts string entered by user to lower case
+ * for simplicity.
+ *
+ * @param description Keyword that user wants to search for amongst tasks.
+ */
+ public FindCommand(String description) {
+ this.description = description.toLowerCase();
+ }
+
+ @Override
+ public String execute(TaskList tasks, Storage storage, Ui ui) {
+ List resultList = findMatchingTasks(tasks);
+
+ String finalString = "";
+ int counter = 0;
+ for (Task task : resultList) {
+ counter++;
+ finalString += "\n" + counter + "." + task.toString();
+ }
+
+ return ui.returnFindReply(finalString);
+ }
+
+ private List findMatchingTasks(TaskList tasks) {
+ String[] keywords = description.split(" ");
+ List resultList = new ArrayList<>();
+
+ for (int i = 1; i <= tasks.getListLength(); i++) {
+ Task task = tasks.getTask(i);
+ if (hasMatch(keywords, task)) {
+ resultList.add(task);
+ }
+ }
+
+ return resultList;
+ }
+
+ private boolean hasMatch(String[] keywords, Task task) {
+ boolean isMatching = false;
+ String simpleTaskName = task.getTaskName().toLowerCase();
+ for (String keyword : keywords) {
+ isMatching = isMatching || simpleTaskName.contains(keyword);
+ }
+ return isMatching;
+ }
+
+ @Override
+ public boolean isExit() {
+ return false;
+ }
+}
diff --git a/src/main/java/meimei/command/ListCommand.java b/src/main/java/meimei/command/ListCommand.java
new file mode 100644
index 0000000000..b424269a7f
--- /dev/null
+++ b/src/main/java/meimei/command/ListCommand.java
@@ -0,0 +1,21 @@
+package meimei.command;
+
+import meimei.Storage;
+import meimei.TaskList;
+import meimei.Ui;
+
+/**
+ * Command that displays the user's list of tasks when executed.
+ */
+public class ListCommand extends Command {
+
+ @Override
+ public String execute(TaskList tasks, Storage storage, Ui ui) {
+ return ui.returnListReply(tasks);
+ }
+
+ @Override
+ public boolean isExit() {
+ return false;
+ }
+}
diff --git a/src/main/java/meimei/command/Parser.java b/src/main/java/meimei/command/Parser.java
new file mode 100644
index 0000000000..ce4196875f
--- /dev/null
+++ b/src/main/java/meimei/command/Parser.java
@@ -0,0 +1,62 @@
+package meimei.command;
+
+import meimei.botexception.BotException;
+import meimei.botexception.NoCommandException;
+import meimei.botexception.NoDescriptionException;
+
+/**
+ * Static class that parses user commands into executable bot commands represented
+ * by Command objects.
+ */
+public class Parser {
+
+ /**
+ * Parse user commands into executable bot commands.
+ *
+ * @param fullCommand User command (the full line of user input).
+ * @return A bot command to be executed.
+ * @throws BotException If description is missing for AddCommand,
+ * DeleteCommand or DoneCommand,
+ * or if user command does not match any CommandType.
+ */
+ public static Command parse(String fullCommand) throws BotException {
+ String[] commandElements = fullCommand.split(" ", 2);
+ String commandString = commandElements[0];
+ CommandType commandType = null;
+
+ for (CommandType type : CommandType.values()) {
+ if (type.toString().equalsIgnoreCase(commandString)) {
+ commandType = type;
+ break;
+ }
+ }
+
+ if (commandType == CommandType.LIST) {
+ return new ListCommand();
+ } else if (commandType == CommandType.BYE) {
+ return new ExitCommand();
+ } else if (commandType == CommandType.DELETE
+ || commandType == CommandType.DONE
+ || commandType == CommandType.TODO
+ || commandType == CommandType.DEADLINE
+ || commandType == CommandType.EVENT
+ || commandType == CommandType.FIND) { // Commands that have a description
+ try {
+ String description = commandElements[1];
+ if (commandType == CommandType.DELETE) {
+ return new DeleteCommand(description);
+ } else if (commandType == CommandType.DONE) {
+ return new DoneCommand(description);
+ } else if (commandType == CommandType.FIND) {
+ return new FindCommand(description);
+ } else {
+ return new AddCommand(commandType, description);
+ }
+ } catch (IndexOutOfBoundsException e) {
+ throw new NoDescriptionException(commandType.toString().toLowerCase());
+ }
+ } else {
+ throw new NoCommandException();
+ }
+ }
+}
diff --git a/src/main/java/meimei/gui/DialogBox.java b/src/main/java/meimei/gui/DialogBox.java
new file mode 100644
index 0000000000..988c77876a
--- /dev/null
+++ b/src/main/java/meimei/gui/DialogBox.java
@@ -0,0 +1,61 @@
+package meimei.gui;
+
+import java.io.IOException;
+import java.util.Collections;
+
+import javafx.collections.FXCollections;
+import javafx.collections.ObservableList;
+import javafx.fxml.FXML;
+import javafx.fxml.FXMLLoader;
+import javafx.geometry.Pos;
+import javafx.scene.Node;
+import javafx.scene.control.Label;
+import javafx.scene.image.Image;
+import javafx.scene.image.ImageView;
+import javafx.scene.layout.HBox;
+
+/**
+ * An example of a custom control using FXML.
+ * This control represents a dialog box consisting of an ImageView to represent the speaker's face and a label
+ * containing text from the speaker.
+ */
+public class DialogBox extends HBox {
+ @FXML
+ private Label dialog;
+ @FXML
+ private ImageView displayPicture;
+
+ private DialogBox(String text, Image img) {
+ try {
+ FXMLLoader fxmlLoader = new FXMLLoader(MainWindow.class.getResource("/view/DialogBox.fxml"));
+ fxmlLoader.setController(this);
+ fxmlLoader.setRoot(this);
+ fxmlLoader.load();
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+
+ dialog.setText(text);
+ displayPicture.setImage(img);
+ }
+
+ /**
+ * Flips the dialog box such that the ImageView is on the left and text on the right.
+ */
+ private void flip() {
+ ObservableList tmp = FXCollections.observableArrayList(this.getChildren());
+ Collections.reverse(tmp);
+ getChildren().setAll(tmp);
+ setAlignment(Pos.TOP_LEFT);
+ }
+
+ public static DialogBox getUserDialog(String text, Image img) {
+ return new DialogBox(text, img);
+ }
+
+ public static DialogBox getMeimeiDialog(String text, Image img) {
+ var db = new DialogBox(text, img);
+ db.flip();
+ return db;
+ }
+}
diff --git a/src/main/java/meimei/gui/Gui.java b/src/main/java/meimei/gui/Gui.java
new file mode 100644
index 0000000000..69c68973c7
--- /dev/null
+++ b/src/main/java/meimei/gui/Gui.java
@@ -0,0 +1,22 @@
+package meimei.gui;
+
+import javafx.application.Application;
+import javafx.scene.Scene;
+import javafx.scene.layout.AnchorPane;
+import javafx.stage.Stage;
+import meimei.MeimeiBot;
+
+/**
+ * A GUI for Duke using FXML.
+ */
+public class Gui extends Application {
+ @Override
+ public void start(Stage stage) {
+ MeimeiBot duke = new MeimeiBot("data/tasks.txt");
+ AnchorPane ap = new MainWindow(duke);
+ Scene scene = new Scene(ap);
+ stage.setTitle("Meimei Bot");
+ stage.setScene(scene);
+ stage.show();
+ }
+}
diff --git a/src/main/java/meimei/gui/MainWindow.java b/src/main/java/meimei/gui/MainWindow.java
new file mode 100644
index 0000000000..5e1bfa4d7d
--- /dev/null
+++ b/src/main/java/meimei/gui/MainWindow.java
@@ -0,0 +1,84 @@
+package meimei.gui;
+
+import java.io.IOException;
+
+import javafx.animation.KeyFrame;
+import javafx.animation.Timeline;
+import javafx.application.Platform;
+import javafx.fxml.FXML;
+import javafx.fxml.FXMLLoader;
+import javafx.scene.control.Button;
+import javafx.scene.control.ScrollPane;
+import javafx.scene.control.TextField;
+import javafx.scene.image.Image;
+import javafx.scene.layout.AnchorPane;
+import javafx.scene.layout.VBox;
+import javafx.util.Duration;
+import meimei.MeimeiBot;
+
+/**
+ * Controller for meimei.gui.MainWindow. Provides the layout for the other controls.
+ * Adapted from this guide.
+ */
+public class MainWindow extends AnchorPane {
+ public static final int EXIT_DELAY_TIME_IN_MILLISECONDS = 5000;
+ @FXML
+ private ScrollPane scrollPane;
+ @FXML
+ private VBox dialogContainer;
+ @FXML
+ private TextField userInput;
+ @FXML
+ private Button sendButton;
+
+ private final MeimeiBot meimeiBot;
+
+ private final Image userProfile = new Image(this.getClass().getResourceAsStream("/images/RalphProfile.png"));
+ private final Image meimeiProfile = new Image(this.getClass().getResourceAsStream("/images/VanellopeProfile.png"));
+
+ /**
+ * Public constructor for the main window of the bot's GUI.
+ *
+ * @param meimeiBot Meimei Bot instance that is used for the GUI instance.
+ */
+ public MainWindow(MeimeiBot meimeiBot) {
+ this.meimeiBot = meimeiBot;
+
+ try {
+ FXMLLoader fxmlLoader = new FXMLLoader(MainWindow.class.getResource("/view/MainWindow.fxml"));
+ fxmlLoader.setController(this);
+ fxmlLoader.setRoot(this);
+ fxmlLoader.load();
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+
+ scrollPane.vvalueProperty().bind(dialogContainer.heightProperty());
+ dialogContainer.getChildren().add(DialogBox.getMeimeiDialog(this.meimeiBot.start(), meimeiProfile));
+ }
+
+ /**
+ * Creates two dialog boxes, one echoing user input and the other containing the bot's reply and then appends them
+ * to the dialog container. Clears the user input after processing. Exits if bot stops running.
+ */
+ @FXML
+ private void handleUserInput() {
+ String input = userInput.getText();
+ String response = meimeiBot.getResponse(input);
+ dialogContainer.getChildren().addAll(
+ DialogBox.getUserDialog(input, userProfile),
+ DialogBox.getMeimeiDialog(response, meimeiProfile)
+ );
+ userInput.clear();
+
+ if (!meimeiBot.isRunning()) {
+ // @@author foojingyi-reused
+ // Reused from https://github.com/PrestonTYR/ip/blob/master/src/main/java/duke/gui/MainWindow.java
+ // with minor modifications.
+ Duration delayDuration = new Duration(EXIT_DELAY_TIME_IN_MILLISECONDS);
+ KeyFrame keyFrame = new KeyFrame(delayDuration, x -> Platform.exit());
+ Timeline tl = new Timeline(keyFrame);
+ tl.play();
+ }
+ }
+}
diff --git a/src/main/java/meimei/task/Deadline.java b/src/main/java/meimei/task/Deadline.java
new file mode 100644
index 0000000000..59a3721c76
--- /dev/null
+++ b/src/main/java/meimei/task/Deadline.java
@@ -0,0 +1,41 @@
+package meimei.task;
+
+import java.time.LocalDateTime;
+import java.time.format.DateTimeFormatter;
+
+/**
+ * Represents a task to be completed by a given deadline. Inherits from Task.
+ *
+ * Adapted from
+ * this page .
+ */
+public class Deadline extends Task {
+ /** Represents the date and time at which to complete the task by */
+ protected LocalDateTime dateTime;
+
+ /**
+ * Public constructor.
+ *
+ * @param taskName Name of the task as given by user.
+ * @param dateTime LocalDateTime object representing the deadline of the task.
+ */
+ public Deadline(String taskName, LocalDateTime dateTime) {
+ super(taskName);
+ this.dateTime = dateTime;
+ }
+
+ /**
+ * Returns the date and time at which to complete the task by.
+ *
+ * @return LocalDateTime object representing the deadline of the task.
+ */
+ public LocalDateTime getDateTime() {
+ return dateTime;
+ }
+
+ @Override
+ public String toString() {
+ return "[D]" + super.toString() + " by: "
+ + this.dateTime.format(DateTimeFormatter.ofPattern("d MMM yyyy, h.mm a"));
+ }
+}
diff --git a/src/main/java/meimei/task/Event.java b/src/main/java/meimei/task/Event.java
new file mode 100644
index 0000000000..a2de5feae8
--- /dev/null
+++ b/src/main/java/meimei/task/Event.java
@@ -0,0 +1,38 @@
+package meimei.task;
+
+import java.time.LocalDateTime;
+import java.time.format.DateTimeFormatter;
+
+/**
+ * Represents an event at a particular date and time. Inherits from Task.
+ */
+public class Event extends Task {
+ /** Represents the date and time of the event */
+ protected LocalDateTime dateTime;
+
+ /**
+ * Public constructor.
+ *
+ * @param taskName Name of the task as given by user.
+ * @param dateTime LocalDateTime object representing the date and time of the event.
+ */
+ public Event(String taskName, LocalDateTime dateTime) {
+ super(taskName);
+ this.dateTime = dateTime;
+ }
+
+ /**
+ * Returns the date and time when the event is held.
+ *
+ * @return LocalDateTime object representing the date and time of the event.
+ */
+ public LocalDateTime getDateTime() {
+ return dateTime;
+ }
+
+ @Override
+ public String toString() {
+ return "[E]" + super.toString() + " at: "
+ + this.dateTime.format(DateTimeFormatter.ofPattern("d MMM yyyy, h.mm a"));
+ }
+}
diff --git a/src/main/java/meimei/task/Task.java b/src/main/java/meimei/task/Task.java
new file mode 100644
index 0000000000..8a5ac7c3b6
--- /dev/null
+++ b/src/main/java/meimei/task/Task.java
@@ -0,0 +1,58 @@
+package meimei.task;
+
+/**
+ * Represents a task that has a name taskName and a status isDone.
+ * Adapted from https://nus-cs2103-ay2021s1.github.io/website/schedule/week2/project.html
+ */
+
+public class Task {
+ /** Name of the task */
+ protected final String taskName;
+ /** Status of the task */
+ protected boolean isDone;
+
+ /**
+ * Public constructor. A task is not done by default when first added.
+ *
+ * @param taskName Name of the task as given by user.
+ */
+ public Task(String taskName) {
+ this.taskName = taskName;
+ this.isDone = false;
+ }
+
+ /**
+ * Returns the name of the task given by the user.
+ *
+ * @return Name of the task.
+ */
+ public String getTaskName() {
+ return taskName;
+ }
+
+ protected String getStatusIcon() {
+ return isDone ? "\u2713" : "\u2718"; //return tick or cross symbols
+ }
+
+ /**
+ * Returns a boolean value representing whether the task has been
+ * completed or not. A true value indicates that the task has been completed.
+ *
+ * @return Boolean indicating whether task has been done.
+ */
+ public boolean isDone() {
+ return this.isDone;
+ }
+
+ /**
+ * Marks a task as having been completed.
+ */
+ public void markDone() {
+ this.isDone = true;
+ }
+
+ @Override
+ public String toString() {
+ return "[" + this.getStatusIcon() + "] " + taskName;
+ }
+}
diff --git a/src/main/java/meimei/task/Todo.java b/src/main/java/meimei/task/Todo.java
new file mode 100644
index 0000000000..f5701964fb
--- /dev/null
+++ b/src/main/java/meimei/task/Todo.java
@@ -0,0 +1,21 @@
+package meimei.task;
+
+/**
+ * Represents a task to be done. Inherits from Task.
+ */
+public class Todo extends Task {
+
+ /**
+ * Public constructor.
+ *
+ * @param taskName Name of the task as given by user.
+ */
+ public Todo(String taskName) {
+ super(taskName);
+ }
+
+ @Override
+ public String toString() {
+ return "[T]" + super.toString();
+ }
+}
diff --git a/src/main/resources/images/RalphProfile.png b/src/main/resources/images/RalphProfile.png
new file mode 100644
index 0000000000..477d575e8b
Binary files /dev/null and b/src/main/resources/images/RalphProfile.png differ
diff --git a/src/main/resources/images/VanellopeProfile.png b/src/main/resources/images/VanellopeProfile.png
new file mode 100644
index 0000000000..48c8b32a4b
Binary files /dev/null and b/src/main/resources/images/VanellopeProfile.png differ
diff --git a/src/main/resources/view/DialogBox.fxml b/src/main/resources/view/DialogBox.fxml
new file mode 100644
index 0000000000..ede775d4f9
--- /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..e7213e5423
--- /dev/null
+++ b/src/main/resources/view/MainWindow.fxml
@@ -0,0 +1,20 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/test/java/meimei/task/DeadlineTest.java b/src/test/java/meimei/task/DeadlineTest.java
new file mode 100644
index 0000000000..aef3f00de3
--- /dev/null
+++ b/src/test/java/meimei/task/DeadlineTest.java
@@ -0,0 +1,20 @@
+package meimei.task;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+import java.time.LocalDateTime;
+import java.time.format.DateTimeFormatter;
+
+import org.junit.jupiter.api.Test;
+
+public class DeadlineTest {
+
+ @Test
+ public void testStringConversion() {
+ assertEquals("[D][\u2718] Assignment by: 6 Nov 2020, 11.59 PM",
+ new Deadline("Assignment",
+ LocalDateTime.parse("06/11/2020 23:59",
+ DateTimeFormatter.ofPattern("dd/MM/yyyy HH:mm")))
+ .toString());
+ }
+}
diff --git a/src/test/java/meimei/task/EventTest.java b/src/test/java/meimei/task/EventTest.java
new file mode 100644
index 0000000000..1e27634f22
--- /dev/null
+++ b/src/test/java/meimei/task/EventTest.java
@@ -0,0 +1,20 @@
+package meimei.task;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+import java.time.LocalDateTime;
+import java.time.format.DateTimeFormatter;
+
+import org.junit.jupiter.api.Test;
+
+public class EventTest {
+
+ @Test
+ public void testStringConversion() {
+ assertEquals("[E][\u2718] Dance Class at: 3 Sep 2020, 8.30 PM",
+ new Event("Dance Class",
+ LocalDateTime.parse("03/09/2020 20:30",
+ DateTimeFormatter.ofPattern("dd/MM/yyyy HH:mm")))
+ .toString());
+ }
+}
diff --git a/src/test/java/meimei/task/TaskTest.java b/src/test/java/meimei/task/TaskTest.java
new file mode 100644
index 0000000000..4939ba454c
--- /dev/null
+++ b/src/test/java/meimei/task/TaskTest.java
@@ -0,0 +1,42 @@
+package meimei.task;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import org.junit.jupiter.api.Test;
+
+public class TaskTest {
+
+ @Test
+ public void testGetTaskName() {
+ assertEquals("Laundry", new Task("Laundry").getTaskName());
+ }
+
+ @Test
+ public void isDone_isDoneAssignedTrue_true() {
+ Task testTask = new Task("Housework");
+ testTask.isDone = true;
+ assertTrue(testTask.isDone());
+ }
+
+ @Test
+ public void isDone_defaultTask_false() {
+ Task testTask = new Task("Housework");
+ assertFalse(testTask.isDone());
+ }
+
+ @Test
+ public void markDone_taskMarkedDone_isDoneVariableIsTrue() {
+ Task testTask = new Task("Housework");
+ testTask.markDone();
+ assertTrue(testTask.isDone);
+ }
+
+ @Test
+ public void testStringConversionForDoneTask() {
+ Task testTask = new Task("Dishes");
+ testTask.markDone();
+ assertEquals("[\u2713] Dishes", testTask.toString());
+ }
+}
diff --git a/src/test/java/meimei/task/TodoTest.java b/src/test/java/meimei/task/TodoTest.java
new file mode 100644
index 0000000000..69a94621e8
--- /dev/null
+++ b/src/test/java/meimei/task/TodoTest.java
@@ -0,0 +1,13 @@
+package meimei.task;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+import org.junit.jupiter.api.Test;
+
+public class TodoTest {
+
+ @Test
+ public void testStringConversion() {
+ assertEquals("[T][\u2718] Homework", new Todo("Homework").toString());
+ }
+}
diff --git a/text-ui-test/EXPECTED.TXT b/text-ui-test/EXPECTED.TXT
index 657e74f6e7..71679996b6 100644
--- a/text-ui-test/EXPECTED.TXT
+++ b/text-ui-test/EXPECTED.TXT
@@ -1,7 +1,77 @@
-Hello from
- ____ _
-| _ \ _ _| | _____
-| | | | | | | |/ / _ \
-| |_| | |_| | < __/
-|____/ \__,_|_|\_\___|
-
+____________________________________________________________
+Eh what's up! I'm Meimei
+What you want ah?
+____________________________________________________________
+____________________________________________________________
+Orh. I added:
+ [T][✘] read book
+Now you got 1 things in the list.
+____________________________________________________________
+____________________________________________________________
+Orh. I added:
+ [D][✘] return book (by: June 6th)
+Now you got 2 things in the list.
+____________________________________________________________
+____________________________________________________________
+Orh. I added:
+ [E][✘] project meeting (at: Aug 6th 2-4pm)
+Now you got 3 things in the list.
+____________________________________________________________
+____________________________________________________________
+Orh. I added:
+ [T][✘] join sports club
+Now you got 4 things in the list.
+____________________________________________________________
+____________________________________________________________
+Na, here is your list lah:
+1.[T][✘] read book
+2.[D][✘] return book (by: June 6th)
+3.[E][✘] project meeting (at: Aug 6th 2-4pm)
+4.[T][✘] join sports club
+____________________________________________________________
+____________________________________________________________
+Can, I help you mark this as done liao:
+ [T][✓] read book
+____________________________________________________________
+____________________________________________________________
+Can, I help you mark this as done liao:
+ [T][✓] join sports club
+____________________________________________________________
+____________________________________________________________
+Na, here is your list lah:
+1.[T][✓] read book
+2.[D][✘] return book (by: June 6th)
+3.[E][✘] project meeting (at: Aug 6th 2-4pm)
+4.[T][✓] join sports club
+____________________________________________________________
+____________________________________________________________
+Orh. I added:
+ [T][✘] borrow book
+Now you got 5 things in the list.
+____________________________________________________________
+____________________________________________________________
+Na, here is your list lah:
+1.[T][✓] read book
+2.[D][✘] return book (by: June 6th)
+3.[E][✘] project meeting (at: Aug 6th 2-4pm)
+4.[T][✓] join sports club
+5.[T][✘] borrow book
+____________________________________________________________
+____________________________________________________________
+Orh. I added:
+ [D][✘] return book (by: Sunday)
+Now you got 6 things in the list.
+____________________________________________________________
+____________________________________________________________
+Orh. I added:
+ [E][✘] project meeting (at: Mon 2-4pm)
+Now you got 7 things in the list.
+____________________________________________________________
+____________________________________________________________
+Orh. I added:
+ [D][✘] do homework (by: no idea :-p)
+Now you got 8 things in the list.
+____________________________________________________________
+____________________________________________________________
+Ok bye bye! C u again :P
+____________________________________________________________
diff --git a/text-ui-test/input.txt b/text-ui-test/input.txt
index e69de29bb2..3cd166bf4e 100644
--- a/text-ui-test/input.txt
+++ b/text-ui-test/input.txt
@@ -0,0 +1,14 @@
+todo read book
+deadline return book /by June 6th
+event project meeting /at Aug 6th 2-4pm
+todo join sports club
+list
+done 1
+done 4
+list
+todo borrow book
+list
+deadline return book /by Sunday
+event project meeting /at Mon 2-4pm
+deadline do homework /by no idea :-p
+bye
\ No newline at end of file
diff --git a/text-ui-test/runtest.bat b/text-ui-test/runtest.bat
index d0facc6310..d11dd855d5 100644
--- a/text-ui-test/runtest.bat
+++ b/text-ui-test/runtest.bat
@@ -7,7 +7,7 @@ REM delete output from previous run
del ACTUAL.TXT
REM compile the code into the bin folder
-javac -cp ..\src -Xlint:none -d ..\bin ..\src\main\java\Duke.java
+javac -cp ..\src -Xlint:none -d ..\bin ..\src\main\java\meimei.MeimeiBot.java
IF ERRORLEVEL 1 (
echo ********** BUILD FAILURE **********
exit /b 1
@@ -15,7 +15,7 @@ IF ERRORLEVEL 1 (
REM no error here, errorlevel == 0
REM run the program, feed commands from input.txt file and redirect the output to the ACTUAL.TXT
-java -classpath ..\bin Duke < input.txt > ACTUAL.TXT
+java -classpath ..\bin meimei.MeimeiBot < input.txt > ACTUAL.TXT
REM compare the output to the expected output
FC ACTUAL.TXT EXPECTED.TXT
diff --git a/text-ui-test/runtest.sh b/text-ui-test/runtest.sh
old mode 100644
new mode 100755
index e169618a34..d139263328
--- a/text-ui-test/runtest.sh
+++ b/text-ui-test/runtest.sh
@@ -1,4 +1,5 @@
#!/usr/bin/env bash
+export LC_ALL=en_GB.UTF-8
# create bin directory if it doesn't exist
if [ ! -d "../bin" ]
@@ -13,7 +14,7 @@ then
fi
# compile the code into the bin folder, terminates if error occurred
-if ! javac -cp ../src -Xlint:none -d ../bin ../src/main/java/Duke.java
+if ! javac -cp ../src -Xlint:none -d ../bin ../src/main/java/*.java
then
echo "********** BUILD FAILURE **********"
exit 1