diff --git a/.gitignore b/.gitignore
index 2873e189e1..54f041ac58 100644
--- a/.gitignore
+++ b/.gitignore
@@ -13,5 +13,18 @@ src/main/resources/docs/
*.iml
bin/
+
+/test-ui-test/
+text-ui-test/EXPECTED.TXT
/text-ui-test/ACTUAL.TXT
text-ui-test/EXPECTED-UNIX.TXT
+text-ui-test/data/
+
+*.class
+
+/src/main/java/checkstyle-8.2-all.jar
+/src/main/java/cs2030_checks.xml
+
+data/savedTasks.txt
+Event flow.txt
+src/main/java/exceptions/All wrong cmds.txt
diff --git a/build.gradle b/build.gradle
new file mode 100644
index 0000000000..1cd44b4b20
--- /dev/null
+++ b/build.gradle
@@ -0,0 +1,55 @@
+plugins {
+ id 'java'
+ id 'application'
+ 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 = "wessy.Launcher"
+}
+
+shadowJar {
+ archiveBaseName = "wessy"
+ archiveClassifier = null
+}
+//
+//run{
+// standardInput = System.in
+//}
diff --git a/config/checkstyle/checkstyle-8.2-all.jar b/config/checkstyle/checkstyle-8.2-all.jar
new file mode 100644
index 0000000000..7ddfab3b61
Binary files /dev/null and b/config/checkstyle/checkstyle-8.2-all.jar differ
diff --git a/config/checkstyle/checkstyle.xml b/config/checkstyle/checkstyle.xml
new file mode 100644
index 0000000000..d6a7a451b7
--- /dev/null
+++ b/config/checkstyle/checkstyle.xml
@@ -0,0 +1,232 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/config/checkstyle/suppressions.xml b/config/checkstyle/suppressions.xml
new file mode 100644
index 0000000000..135ea49ee0
--- /dev/null
+++ b/config/checkstyle/suppressions.xml
@@ -0,0 +1,10 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/data/Wessy.txt b/data/Wessy.txt
new file mode 100644
index 0000000000..49fcbe817d
--- /dev/null
+++ b/data/Wessy.txt
@@ -0,0 +1,3 @@
+T~%~0~%~Chicken
+D~%~1~%~essay~%~2030
+E~%~0~%~meeting~%~2pm~%~5pm
diff --git a/docs/README.md b/docs/README.md
index 8077118ebe..f8fe263a8c 100644
--- a/docs/README.md
+++ b/docs/README.md
@@ -1,29 +1,161 @@
# User Guide
-## Features
+## Features that Wessy has
-### Feature-ABC
+### Able to keep track of the tasks you need to do.
+Different types of tasks can specify different things.
+For example, the `deadline` task can keep track of the specified deadline.
+The task with fixed duration (using the `fix` command) can record exactly how much time is required to do the task.
-Description of the feature.
+### Able to tick off the tasks that you have completed.
+This can be achieved by marking those tasks that are completed via the `mark` command. This action can be undone using the `unmark`.
-### Feature-XYZ
+### Able to continue from where you left when reopening the program each other
+The tasks and their status will be exactly the same as they were when you quited the program previously.
-Description of the feature.
+## Commands that Wessy accepts and how to use them
-## Usage
+### `todo` - To create a "todo" task.
+To create a task that only consists of the task description.
-### `Keyword` - Describe action
+If you send: `todo Water the plants.`
-Describe the action and its outcome.
+Wessy's response:
+```
+Wessy:
+Got it. I've added this task:
+ [T][ ] Water the plants
+Now you have 1 task in the list.
+```
+
+### `deadline` - To create a deadline.
+To create a task that consists of the task description and a specified deadline, using the `/by` time specifier.
+
+If you send: `deadline Finish writing ES2660 essay /by 12/2/2023 16:00`
+
+Wessy's response:
+```
+Wessy:
+Got it. I've added this task:
+ [D][ ] Finish writing ES2660 essay (by: 2023-02/12 16:00)
+Now you have 2 tasks in the list.
+```
+
+### `event` - To create a event.
+To create a event with specified start and end times, using the `/from` and `/to` time specifiers.
+
+If you send:
+`event CS2103T project meeting /from 12-2-2023 09.00 /to 12-2-2023 11.00`
+
+Wessy's response:
+```
+Wessy:
+Got it. I've added this task:
+ [E][ ] CS2103T project meeting (from: 12-2-2023 09.00 to: 12-2-2023 11.00)
+Now you have 3 tasks in the list.
+```
+
+### `doafter` - To create a task that is to be done after a specific time/task.
+To create a task that is to be done after a specific time/task, using the `/after` specifier.
+
+If you send: `doafter Google search for abs workouts /after midterms`
+
+Wessy's response:
+```
+Wessy:
+Got it. I've added this task:
+ [A][ ] Google search for abs workouts (after: midterms)
+Now you have 4 tasks in the list.
+```
+
+### `fix` - To create a task with a fixed duration.
+To create a task with a fixed duration, using the `/need` specifier.
+
+If you send:
+`fix Do abs workouts /for 30 minutes`
+
+Wessy's response:
+```
+Wessy:
+Got it. I've added this task:
+ [F][ ] Do abs workouts (for: 30 minutes)
+Now you have 5 tasks in the list.
+```
+
+### `mark` - To mark the specified task as done.
+Specify which task to mark by inserting the task number after the `mark` command, using 1-based indexing.
-Example of usage:
+If you send: `mark 2`
-`keyword (optional arguments)`
+Wessy's response:
+```
+Wessy:
+You mark this task as done already:
+ [D][X] Finish writing ES2660 essay (by: 2023-02/12 16:00)
+```
+
+### `unmark` - To mark the specified task as not done.
+Specify which task to mark by inserting the task number after the `unmark` command, using 1-based indexing.
+
+If you send: `unmark 3`
+
+Wessy's response:
+```
+Wessy:
+You mark this task as not done yet:
+ [E][ ] CS2103T project meeting (from: 12-2-2023 09.00 to: 12-2-2023 11.00)
+```
+
+### `find` - To search for tasks that have the specific phrase in their descriptions.
+Specify what phrase to search for by entering it after the `find` command.
+
+If you send: `find abs workouts`
+
+Wessy's response:
+```
+Wessy:
+Here are the matching tasks on your list:
+1.[A][ ] Google search for abs workouts (after: midterms)
+2.[F][ ] Do abs workouts (for: 30 minutes)
+```
-Expected outcome:
+### `delete` - To delete the specified task.
+Specify which task to delete by inserting the task number after the `delete` command, using 1-based indexing.
-Description of the outcome.
+If you send: `delete 4`
+Wessy's response:
```
-expected output
+Wessy:
+Noted! I've removed this task:
+ [A][ ] Google search for abs workouts (after: midterms)
+Now you have 4 tasks on the list.
+```
+
+### `list` - To list all the tasks that are currently on the list.
+
+Wessy's response:
+```
+Wessy:
+Here are the tasks on your list:
+1.[T][ ] Water the plants
+2.[D][X] Finish writing ES2660 essay (by: 2023-02/12 16:00)
+3.[E][ ] CS2103T project meeting (from: 12-2-2023 09.00 to: 12-2-2023 11.00)
+4.[F][ ] Do abs workouts (for: 30 minutes)
+```
+
+### `clear` - To clear all the tasks that are currently on the list.
+
+Wessy's response:
+```
+Wessy:
+You have cleared your task list. The list is empty now.
+```
+
+### `bye` - To quit the program.
+
+Wessy's response:
```
+Wessy:
+Bye. Hope to see you again soon!
+```
\ No newline at end of file
diff --git a/docs/Ui.png b/docs/Ui.png
new file mode 100644
index 0000000000..d698d2caed
Binary files /dev/null and b/docs/Ui.png differ
diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar
new file mode 100644
index 0000000000..f3d88b1c2f
Binary files /dev/null and b/gradle/wrapper/gradle-wrapper.jar differ
diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties
new file mode 100644
index 0000000000..b7c8c5dbf5
--- /dev/null
+++ b/gradle/wrapper/gradle-wrapper.properties
@@ -0,0 +1,5 @@
+distributionBase=GRADLE_USER_HOME
+distributionPath=wrapper/dists
+distributionUrl=https\://services.gradle.org/distributions/gradle-6.2-bin.zip
+zipStoreBase=GRADLE_USER_HOME
+zipStorePath=wrapper/dists
diff --git a/gradlew b/gradlew
new file mode 100755
index 0000000000..2fe81a7d95
--- /dev/null
+++ b/gradlew
@@ -0,0 +1,183 @@
+#!/usr/bin/env sh
+
+#
+# Copyright 2015 the original author or authors.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# https://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+##############################################################################
+##
+## Gradle start up script for UN*X
+##
+##############################################################################
+
+# Attempt to set APP_HOME
+# Resolve links: $0 may be a link
+PRG="$0"
+# Need this for relative symlinks.
+while [ -h "$PRG" ] ; do
+ ls=`ls -ld "$PRG"`
+ link=`expr "$ls" : '.*-> \(.*\)$'`
+ if expr "$link" : '/.*' > /dev/null; then
+ PRG="$link"
+ else
+ PRG=`dirname "$PRG"`"/$link"
+ fi
+done
+SAVED="`pwd`"
+cd "`dirname \"$PRG\"`/" >/dev/null
+APP_HOME="`pwd -P`"
+cd "$SAVED" >/dev/null
+
+APP_NAME="Gradle"
+APP_BASE_NAME=`basename "$0"`
+
+# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
+
+# Use the maximum available, or set MAX_FD != -1 to use that value.
+MAX_FD="maximum"
+
+warn () {
+ echo "$*"
+}
+
+die () {
+ echo
+ echo "$*"
+ echo
+ exit 1
+}
+
+# OS specific support (must be 'true' or 'false').
+cygwin=false
+msys=false
+darwin=false
+nonstop=false
+case "`uname`" in
+ CYGWIN* )
+ cygwin=true
+ ;;
+ Darwin* )
+ darwin=true
+ ;;
+ MINGW* )
+ msys=true
+ ;;
+ NONSTOP* )
+ nonstop=true
+ ;;
+esac
+
+CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
+
+# Determine the Java command to use to start the JVM.
+if [ -n "$JAVA_HOME" ] ; then
+ if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
+ # IBM's JDK on AIX uses strange locations for the executables
+ JAVACMD="$JAVA_HOME/jre/sh/java"
+ else
+ JAVACMD="$JAVA_HOME/bin/java"
+ fi
+ if [ ! -x "$JAVACMD" ] ; then
+ die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+ fi
+else
+ JAVACMD="java"
+ which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+fi
+
+# Increase the maximum file descriptors if we can.
+if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
+ MAX_FD_LIMIT=`ulimit -H -n`
+ if [ $? -eq 0 ] ; then
+ if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
+ MAX_FD="$MAX_FD_LIMIT"
+ fi
+ ulimit -n $MAX_FD
+ if [ $? -ne 0 ] ; then
+ warn "Could not set maximum file descriptor limit: $MAX_FD"
+ fi
+ else
+ warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
+ fi
+fi
+
+# For Darwin, add options to specify how the application appears in the dock
+if $darwin; then
+ GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
+fi
+
+# For Cygwin or MSYS, switch paths to Windows format before running java
+if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
+ APP_HOME=`cygpath --path --mixed "$APP_HOME"`
+ CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
+ JAVACMD=`cygpath --unix "$JAVACMD"`
+
+ # We build the pattern for arguments to be converted via cygpath
+ ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
+ SEP=""
+ for dir in $ROOTDIRSRAW ; do
+ ROOTDIRS="$ROOTDIRS$SEP$dir"
+ SEP="|"
+ done
+ OURCYGPATTERN="(^($ROOTDIRS))"
+ # Add a user-defined pattern to the cygpath arguments
+ if [ "$GRADLE_CYGPATTERN" != "" ] ; then
+ OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
+ fi
+ # Now convert the arguments - kludge to limit ourselves to /bin/sh
+ i=0
+ for arg in "$@" ; do
+ CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
+ CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
+
+ if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
+ eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
+ else
+ eval `echo args$i`="\"$arg\""
+ fi
+ i=`expr $i + 1`
+ done
+ case $i in
+ 0) set -- ;;
+ 1) set -- "$args0" ;;
+ 2) set -- "$args0" "$args1" ;;
+ 3) set -- "$args0" "$args1" "$args2" ;;
+ 4) set -- "$args0" "$args1" "$args2" "$args3" ;;
+ 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
+ 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
+ 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
+ 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
+ 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
+ esac
+fi
+
+# Escape application args
+save () {
+ for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
+ echo " "
+}
+APP_ARGS=`save "$@"`
+
+# Collect all arguments for the java command, following the shell quoting and substitution rules
+eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
+
+exec "$JAVACMD" "$@"
diff --git a/gradlew.bat b/gradlew.bat
new file mode 100644
index 0000000000..62bd9b9cce
--- /dev/null
+++ b/gradlew.bat
@@ -0,0 +1,103 @@
+@rem
+@rem Copyright 2015 the original author or authors.
+@rem
+@rem Licensed under the Apache License, Version 2.0 (the "License");
+@rem you may not use this file except in compliance with the License.
+@rem You may obtain a copy of the License at
+@rem
+@rem https://www.apache.org/licenses/LICENSE-2.0
+@rem
+@rem Unless required by applicable law or agreed to in writing, software
+@rem distributed under the License is distributed on an "AS IS" BASIS,
+@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+@rem See the License for the specific language governing permissions and
+@rem limitations under the License.
+@rem
+
+@if "%DEBUG%" == "" @echo off
+@rem ##########################################################################
+@rem
+@rem Gradle startup script for Windows
+@rem
+@rem ##########################################################################
+
+@rem Set local scope for the variables with windows NT shell
+if "%OS%"=="Windows_NT" setlocal
+
+set DIRNAME=%~dp0
+if "%DIRNAME%" == "" set DIRNAME=.
+set APP_BASE_NAME=%~n0
+set APP_HOME=%DIRNAME%
+
+@rem Resolve any "." and ".." in APP_HOME to make it shorter.
+for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
+
+@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
+
+@rem Find java.exe
+if defined JAVA_HOME goto findJavaFromJavaHome
+
+set JAVA_EXE=java.exe
+%JAVA_EXE% -version >NUL 2>&1
+if "%ERRORLEVEL%" == "0" goto init
+
+echo.
+echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:findJavaFromJavaHome
+set JAVA_HOME=%JAVA_HOME:"=%
+set JAVA_EXE=%JAVA_HOME%/bin/java.exe
+
+if exist "%JAVA_EXE%" goto init
+
+echo.
+echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:init
+@rem Get command-line arguments, handling Windows variants
+
+if not "%OS%" == "Windows_NT" goto win9xME_args
+
+:win9xME_args
+@rem Slurp the command line arguments.
+set CMD_LINE_ARGS=
+set _SKIP=2
+
+:win9xME_args_slurp
+if "x%~1" == "x" goto execute
+
+set CMD_LINE_ARGS=%*
+
+:execute
+@rem Setup the command line
+
+set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
+
+@rem Execute Gradle
+"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
+
+:end
+@rem End local scope for the variables with windows NT shell
+if "%ERRORLEVEL%"=="0" goto mainEnd
+
+:fail
+rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
+rem the _cmd.exe /c_ return code!
+if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
+exit /b 1
+
+:mainEnd
+if "%OS%"=="Windows_NT" endlocal
+
+:omega
diff --git a/src/main/java/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/wessy/CmdType.java b/src/main/java/wessy/CmdType.java
new file mode 100644
index 0000000000..492cb4acbc
--- /dev/null
+++ b/src/main/java/wessy/CmdType.java
@@ -0,0 +1,84 @@
+package wessy;
+
+import java.util.Map;
+import java.util.HashMap;
+
+/**
+ * CmdType is an enumeration that represents all the different types of commands
+ * "Wessy" takes in.
+ */
+public enum CmdType {
+ LIST("list"),
+ BYE("bye"),
+ MARK("mark"),
+ UNMARK("unmark"),
+ TODO("todo"),
+ DEADLINE("deadline", "/by"),
+ EVENT("event", "/from", "/to"),
+ DOAFTER("doafter", "/after"),
+ FIXEDDURATION("fix", "/for"),
+ DELETE("delete"),
+ FIND("find"),
+ CLEAR("clear");
+
+ private static final Map COMMANDS = new HashMap<>();
+ static {
+ COMMANDS.put("bye", CmdType.BYE);
+ COMMANDS.put("list", CmdType.LIST);
+ COMMANDS.put("todo", CmdType.TODO);
+ COMMANDS.put("deadline", CmdType.DEADLINE);
+ COMMANDS.put("event", CmdType.EVENT);
+ COMMANDS.put("doafter", CmdType.DOAFTER);
+ COMMANDS.put("fix", CmdType.FIXEDDURATION);
+ COMMANDS.put("mark", CmdType.MARK);
+ COMMANDS.put("unmark", CmdType.UNMARK);
+ COMMANDS.put("delete", CmdType.DELETE);
+ COMMANDS.put("find", CmdType.FIND);
+ COMMANDS.put("clear", CmdType.CLEAR);
+ }
+
+ private final String cmd;
+ private final String[] specifiers;
+
+ /** Constructs an instance of CmdType that corresponds to the different
+ * types of commands, along with the command in its text form.
+ */
+ CmdType(String str, String... specifiers) {
+ this.cmd = str;
+ this.specifiers = specifiers;
+ }
+
+ /**
+ * Converts a command (if valid) from its text form to the corresponding
+ * CmdType.
+ *
+ * @param str Specified command in its text form.
+ * @return A CmdType that corresponds to str.
+ */
+ public static CmdType getCmdType(String str) {
+ return COMMANDS.get(str);
+ }
+
+ /**
+ * Converts CmdType to command in its text form.
+ *
+ * @return The text form of the command when it is of CmdType type.
+ */
+ @Override
+ public String toString() {
+ return cmd;
+ }
+
+ /**
+ * Gets the length of the string when the command is in its text form.
+ *
+ * @return The length of string when the command is in its text form.
+ */
+ public int getStrLength() {
+ return cmd.length();
+ }
+
+ public String[] getSpecifiers() {
+ return specifiers;
+ }
+}
diff --git a/src/main/java/wessy/Launcher.java b/src/main/java/wessy/Launcher.java
new file mode 100644
index 0000000000..6354716784
--- /dev/null
+++ b/src/main/java/wessy/Launcher.java
@@ -0,0 +1,12 @@
+package wessy;
+
+import javafx.application.Application;
+
+/**
+ * A launcher class to workaround classpath issues.
+ */
+public class Launcher {
+ public static void main(String[] args) {
+ Application.launch(Main.class, args);
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/wessy/Main.java b/src/main/java/wessy/Main.java
new file mode 100644
index 0000000000..4dfe6916a1
--- /dev/null
+++ b/src/main/java/wessy/Main.java
@@ -0,0 +1,35 @@
+package wessy;
+
+import java.io.IOException;
+
+import javafx.application.Application;
+import javafx.fxml.FXMLLoader;
+import javafx.scene.Scene;
+import javafx.scene.layout.AnchorPane;
+import javafx.stage.Stage;
+import wessy.javafxnodes.MainWindow;
+
+/**
+ * A GUI for Wessy using FXML.
+ */
+public class Main extends Application {
+
+ private Wessy wessy = new Wessy();
+
+ @Override
+ public void start(Stage stage) {
+ try {
+ FXMLLoader fxmlLoader = new FXMLLoader(wessy.Main.class.getResource("/view/MainWindow.fxml"));
+ AnchorPane ap = fxmlLoader.load();
+ Scene scene = new Scene(ap);
+
+ stage.setTitle("Wessy");
+ stage.setScene(scene);
+ fxmlLoader.getController().setWessy(wessy);
+ stage.show();
+
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/wessy/Wessy.java b/src/main/java/wessy/Wessy.java
new file mode 100644
index 0000000000..d0c275a56d
--- /dev/null
+++ b/src/main/java/wessy/Wessy.java
@@ -0,0 +1,199 @@
+package wessy;
+
+import java.io.IOException;
+import java.time.format.DateTimeParseException;
+
+import wessy.components.*;
+import wessy.exceptions.TimeSpecifierException;
+import wessy.exceptions.UnspecifiedTimeException;
+import wessy.exceptions.integer.EmptyListException;
+import wessy.exceptions.integer.InvalidIntegerException;
+import wessy.exceptions.integer.NotPositiveIntegerException;
+import wessy.task.Task;
+
+import wessy.exceptions.WessyException;
+import wessy.exceptions.CommandNotFoundException;
+
+import wessy.exceptions.numofinput.MissingInputException;
+import wessy.exceptions.numofinput.TooManyInputException;
+
+import wessy.exceptions.integer.NotAnIntegerException;
+
+/**
+ *
+ */
+public class Wessy {
+ private static final String CORRECT_FORMAT_MSG = "Please enter the date (and time, if any) in the correct format.";
+ private static final String PERMISSION_MSG = "You do not have the permission to access the file.";
+ private static final String IO_MSG = "There is some issue in the input-output operation.";
+
+ private final Storage storage;
+ private final TaskList tasks;
+ private final Ui ui;
+
+ /**
+ * Constructs an instance of Wessy.
+ *
+ * @param filePath
+ */
+ public Wessy(String filePath) {
+ ui = new Ui();
+ storage = new Storage(filePath);
+
+ TaskList loadedTasks;
+ try {
+ loadedTasks = new TaskList(storage.load());
+ } catch (IOException | SecurityException ex) {
+ System.err.println(ex.getMessage());
+ ex.printStackTrace();
+ loadedTasks = new TaskList();
+ }
+ tasks = loadedTasks;
+ }
+
+ public Wessy() {
+ this("data/savedTasks.txt");
+ }
+
+ /**
+ *
+ */
+ public String startsUp() {
+ return ui.getWelcomeMessage(tasks.printAsStr());
+ }
+
+ /**
+ * A helper function
+ *
+ * @param userInput
+ * @param cmd
+ * @throws MissingInputException
+ * @throws NotAnIntegerException
+ * @throws TooManyInputException
+ */
+ void checkBeforeParse(String userInput, CmdType cmd) throws MissingInputException, NotAnIntegerException,
+ TooManyInputException {
+ UserInputChecker.checkMissingInput(userInput, cmd);
+ UserInputChecker.checkNotNumerical(userInput, cmd);
+ UserInputChecker.checkTooManyInputs(userInput, cmd);
+ }
+
+ /**
+ * A helper function
+ *
+ * @throws IOException
+ */
+ void saveToStorage() throws IOException {
+ storage.save(tasks.saveAsStr());
+ }
+
+ /**
+ *
+ */
+ public String respond(String userInput) {
+ try {
+ int oldSize = tasks.getSize();
+ CmdType cmd = Parser.getCmd(userInput);
+
+ checkForUnknownCmd(cmd);
+ UserInputChecker.checkSpacingAftCmd(userInput, cmd);
+
+ assert cmd != null;
+ assert cmd == CmdType.BYE || cmd == CmdType.LIST || cmd == CmdType.TODO ||
+ cmd == CmdType.DEADLINE || cmd == CmdType.EVENT || cmd == CmdType.MARK ||
+ cmd == CmdType.UNMARK || cmd == CmdType.DELETE || cmd == CmdType.CLEAR;
+
+ return triage(userInput, cmd);
+
+ } catch (DateTimeParseException dtpe) {
+ return ui.handleException(CORRECT_FORMAT_MSG);
+ } catch (SecurityException se) {
+ se.printStackTrace();
+ return ui.handleException(PERMISSION_MSG);
+ } catch (IOException ioe) {
+ ioe.printStackTrace();
+ return ui.handleException(IO_MSG);
+
+ } catch (WessyException we) {
+ return ui.getMessage(we.toString());
+
+ } catch (Exception ex) {
+ return ui.getMessage(ex.getMessage());
+ }
+ }
+
+ private String triage(String userInput, CmdType cmd) throws MissingInputException, TimeSpecifierException,
+ UnspecifiedTimeException, IOException, TooManyInputException, NotAnIntegerException,
+ NotPositiveIntegerException, InvalidIntegerException, EmptyListException {
+
+ switch (cmd) {
+
+ // Commands that do not require any argument
+ case BYE:
+ return ui.getByeMessage();
+ case LIST:
+ return ui.getListOrFindMessage(tasks.printAsStr(), true);
+
+ // Task-creating commands
+ case TODO:
+ // Fallthrough
+ case DEADLINE:
+ // Fallthrough
+ case EVENT:
+ // Fallthrough
+ case DOAFTER:
+ // Fallthrough
+ case FIXEDDURATION:
+
+ UserInputChecker.checkMissingInput(userInput, cmd);
+ UserInputChecker.checkMissingKeyword(userInput, cmd);
+ if (cmd != CmdType.TODO) {
+ UserInputChecker.checkSpecifierMissingInput(userInput, cmd);
+ }
+ String[] taskComponents = Parser.getTaskComponents(userInput, cmd);
+ Task newTask = tasks.add(taskComponents, cmd);
+ saveToStorage();
+ return ui.getAddedMessage(newTask, tasks.getSize());
+
+ // Task's status manipulating commands
+ case MARK:
+ // Fallthrough
+ case UNMARK:
+
+ checkBeforeParse(userInput, cmd);
+
+ boolean isMark = cmd == CmdType.MARK;
+ Task updatedTask = tasks.markOrUnmark(Parser.parseInt(userInput, cmd), isMark);
+ saveToStorage();
+ return ui.getMarkUnmarkMessage(updatedTask, isMark);
+
+ case DELETE:
+ checkBeforeParse(userInput, cmd);
+
+ Task deletedTask = tasks.delete(Parser.parseInt(userInput, cmd));
+ saveToStorage();
+ return ui.getDeleteMessage(deletedTask, tasks.getSize());
+
+ case FIND:
+ String target = userInput.substring(cmd.getStrLength() + 1);
+ return ui.getListOrFindMessage(tasks.find(target), false);
+
+ // Not covered in deliverables. Might be useful for debugging or future extended features.
+ case CLEAR:
+ tasks.clear();
+ saveToStorage();
+ return ui.getClearMessage();
+ }
+ return handleOtherCases();
+ }
+
+ private String handleOtherCases() {
+ return ui.getMessage(new CommandNotFoundException().toString());
+ }
+
+ private void checkForUnknownCmd(CmdType cmd) throws CommandNotFoundException {
+ if (cmd == null) {
+ throw new CommandNotFoundException();
+ }
+ }
+}
diff --git a/src/main/java/wessy/components/Parser.java b/src/main/java/wessy/components/Parser.java
new file mode 100644
index 0000000000..11b2096069
--- /dev/null
+++ b/src/main/java/wessy/components/Parser.java
@@ -0,0 +1,215 @@
+package wessy.components;
+
+import java.time.LocalDateTime;
+import java.time.format.DateTimeParseException;
+
+import java.util.Arrays;
+
+import wessy.CmdType;
+import wessy.exceptions.integer.NotPositiveIntegerException;
+
+/**
+ * Parser is a utility class that mainly processes the user input when "Wessy"
+ * first receives it. While it checks for some formats of the String, it
+ * delegates checking of the other formatting issues to UserInputChecker class.
+ */
+public class Parser {
+ private static final int START = 0;
+ private static final char SPACE = ' ';
+ private static final String DUMMY_TIME = "T12:34:56";
+ private static final String TIME_ZERO_PADDING = ":00";
+
+ private static final String DATE_SEPARATOR = "-";
+ private static final String UNDESIRED_DATE_SEPARATOR = "/";
+ private static final char TIME_SEPARATOR = ':';
+ private static final char UNDESIRED_TIME_SEPARATOR = '.';
+ private static final String DATETIME_SEPARATOR = "T";
+
+ /**
+ * Converts the first word in the line of user input, to the corresponding
+ * CmdType, if it is a valid command type.
+ *
+ * @param userInput The line which the user inputs.
+ * @return The corresponding CmdType, if the first word is a valid command.
+ */
+ public static CmdType getCmd(String userInput) {
+ String spaceAsStr = Character.toString(SPACE);
+ int idx = userInput.contains(spaceAsStr) ? userInput.indexOf(SPACE) : userInput.length();
+ return CmdType.getCmdType(userInput.substring(START, idx));
+ }
+
+ /**
+ * Parses the line of user input and chunks it into the different components
+ * required to initialise a ToDo, Deadline or Event object. The components
+ * are possibly the task description, and the timings specified for the
+ * deadline task or the event.
+ *
+ * @param userInput The line which the user inputs.
+ * @param cmd The command specified, in the form of CmdType. It only accepts
+ * CmdType.TODO, CmdType.DEADLINE and CmdType.EVENT.
+ * @return An array of Strings consisting of the components required to
+ * initialise a task.
+ */
+ public static String[] getTaskComponents(String userInput, CmdType cmd) {
+ assert cmd == CmdType.TODO || cmd == CmdType.DEADLINE || cmd == CmdType.EVENT
+ || cmd == CmdType.DOAFTER || cmd == CmdType.FIXEDDURATION;
+
+ String[] specifiers = cmd.getSpecifiers();
+ String remainingStr = userInput.substring(cmd.getStrLength() + 1);
+
+ switch (cmd) {
+ case TODO:
+ return new String[]{remainingStr};
+
+ case DEADLINE:
+ // Fallthrough
+ case DOAFTER:
+ // Fallthrough
+ case FIXEDDURATION:
+ return singleSpecifierStrSlicer(remainingStr, specifiers[0]);
+
+ case EVENT:
+ String from = specifiers[0];
+ String to = specifiers[1];
+ int firstIdx = remainingStr.indexOf(from);
+ int secondIdx = remainingStr.indexOf(to);
+ return new String[]{removeSpacePadding(remainingStr.substring(START, firstIdx)),
+ removeSpacePadding(remainingStr.substring(firstIdx + from.length(), secondIdx)),
+ removeSpacePadding(remainingStr.substring(secondIdx + to.length()))};
+ }
+ return new String[0];
+ }
+
+ private static String[] singleSpecifierStrSlicer(String str, String specifier) {
+ int idx = str.indexOf(specifier);
+ if (str.contains(specifier + "s")) {
+ specifier += "s";
+ }
+ return new String[]{removeSpacePadding(str.substring(START, idx)),
+ removeSpacePadding(str.substring(idx + specifier.length()))};
+ }
+
+ /**
+ * Parses the input str and converts it into a LocalDateTime object.
+ *
+ * @param str The specified date and time as a String.
+ * @return A LocalDateTime object that is represented by str, in its String
+ * form.
+ * @throws DateTimeParseException If the format of str is wrong and thus
+ * cannot be parsed into a LocalDateTime object.
+ */
+ public static LocalDateTime parseDateTime(String str) throws DateTimeParseException {
+ str = removeSpacePadding(str);
+
+ if (count(str, TIME_SEPARATOR) == 2) {
+ return LocalDateTime.parse(str);
+ }
+
+ if (str.length() <= 10) {
+ return LocalDateTime.parse(standardiseDateFormat(str) + DUMMY_TIME);
+ }
+
+ int idx = 10;
+ if (str.charAt(9) == SPACE) {
+ idx = 9;
+ } else if (str.charAt(8) == SPACE) {
+ idx = 8;
+ }
+ if (str.charAt(idx + 3) == TIME_SEPARATOR) {
+ return LocalDateTime.parse(standardiseDateFormat(str.substring(0, idx)) + DATETIME_SEPARATOR
+ + str.substring(idx + 1) + TIME_ZERO_PADDING);
+ }
+ if (str.charAt(idx + 3) == UNDESIRED_TIME_SEPARATOR) {
+ return LocalDateTime.parse(standardiseDateFormat(str.substring(0, idx)) + DATETIME_SEPARATOR
+ + str.substring(idx + 1, idx + 3) + TIME_SEPARATOR + str.substring(idx + 4)
+ + TIME_ZERO_PADDING);
+ }
+
+ return LocalDateTime.parse(standardiseDateFormat(str.substring(0, idx)) + DATETIME_SEPARATOR
+ + str.substring(idx + 1, idx + 3) + TIME_SEPARATOR + str.substring(idx + 3)
+ + TIME_ZERO_PADDING);
+ }
+
+ /**
+ * Counts the number of occurrence "target" appears in str.
+ *
+ * @param str The String we scan through while counting the number of
+ * occurrence.
+ * @param target The character we look out for when scanning through str.
+ * @return The number of occurrence "target" appears in str.
+ */
+ private static int count(String str, char target) {
+ int targetAsInt = (int) target;
+ long count = str.chars().filter(c -> c == targetAsInt).count();
+ return (int) count;
+ }
+
+ /**
+ * Standardises format of date and time in str, by say making sure that the
+ * separator in the date is "-" instead of "/", making sure the date has 8
+ * digits. This is to prepare for the execution of parseDateTime(...).
+ *
+ * @param str The specified date and time in String form.
+ * @return A standardised String format of the specified date and time.
+ * @throws DateTimeParseException If str does not represent any date and time.
+ */
+ private static String standardiseDateFormat(String str) throws DateTimeParseException {
+ try {
+ String[] components = str.split(DATE_SEPARATOR, 3);
+
+ if (str.contains(UNDESIRED_DATE_SEPARATOR)) {
+ components = str.split(UNDESIRED_DATE_SEPARATOR, 3);
+ }
+
+ components = Arrays.stream(components)
+ .map( component -> component.length() == 1 ? "0" + component : component)
+ .toArray(String[]::new);
+
+ if (components[0].length() == 4) {
+ return components[0] + DATE_SEPARATOR + components[1] + DATE_SEPARATOR + components[2];
+ }
+ return components[2] + DATE_SEPARATOR + components[1] + DATE_SEPARATOR + components[0];
+
+ } catch (ArrayIndexOutOfBoundsException ex) {
+ throw new DateTimeParseException("", str, 0);
+ }
+ }
+
+ /**
+ * Removes the command word from the user input and then the space paddings
+ * on the 2 sides. Afterwards, converts the processed String into a integer
+ * using Integer.parseInt(...).
+ *
+ * @param userInput The String to be parsed to an integer.
+ * @param cmd The specified command.
+ * @return The integer to which the String is converted.
+ * @throws NotPositiveIntegerException If the output integer is not positive.
+ */
+ public static int parseInt(String userInput, CmdType cmd) throws NotPositiveIntegerException {
+ int num = Integer.parseInt(removeSpacePadding(userInput.substring(cmd.getStrLength())));
+ if (num <= 0) {
+ throw new NotPositiveIntegerException();
+ }
+ assert num > 0;
+ return num;
+ }
+
+ /**
+ * A helper function that removes the space paddings on the two ends of str.
+ *
+ * @param str The String to be processed.
+ * @return The shorter String after removing the space paddings on the two ends.
+ */
+ public static String removeSpacePadding(String str) {
+ int start = 0;
+ while (str.charAt(start) == SPACE) {
+ start++;
+ }
+ int end = str.length() - 1;
+ while (str.charAt(end) == SPACE) {
+ end--;
+ }
+ assert str.charAt(start) != ' ' && str.charAt(end) != ' ';
+ return str.substring(start, end + 1);
+ }
+}
diff --git a/src/main/java/wessy/components/Storage.java b/src/main/java/wessy/components/Storage.java
new file mode 100644
index 0000000000..e37d55c5ac
--- /dev/null
+++ b/src/main/java/wessy/components/Storage.java
@@ -0,0 +1,127 @@
+package wessy.components;
+
+import java.util.List;
+import java.util.ArrayList;
+
+import java.util.Scanner;
+
+import java.io.File;
+import java.io.FileWriter;
+import java.io.IOException;
+
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+
+import wessy.components.Parser;
+import wessy.task.*;
+
+/**
+ *
+ */
+public class Storage {
+ private final String folderPath;
+ private final String fileName;
+
+ /**
+ * Constructs an instance of Storage.
+ *
+ * @param filePath
+ */
+ public Storage(String filePath) {
+ int lastIdx = filePath.lastIndexOf('/');
+ if (lastIdx == -1) {
+ this.folderPath = "";
+ this.fileName = filePath;
+ } else {
+ this.folderPath = filePath.substring(0, lastIdx);
+ this.fileName = filePath.substring(lastIdx + 1); // Need to add a slash
+ }
+ }
+
+ /**
+ * Constructs an instance of Storage.
+ */
+ public Storage() {
+ this("data/savedTasks.txt");
+ }
+
+ /**
+ *
+ *
+ * @return
+ */
+ String getFullPath() {
+ return folderPath + "/" + fileName;
+ }
+
+ /**
+ *
+ *
+ * @return
+ * @throws SecurityException
+ * @throws IOException
+ */
+ public List load() throws SecurityException, IOException {
+ List tasks = new ArrayList();
+
+ File savedFile = new File(folderPath + "/" + fileName);
+ if (savedFile.exists()) {
+ Scanner sc = new Scanner(savedFile);
+
+ while (sc.hasNextLine()) {
+ String[] taskComponents = sc.nextLine().split("~%~");
+ int numOfComponents = taskComponents.length;
+ boolean isDone = taskComponents[1].charAt(0) == '1';
+
+ switch (taskComponents[0].charAt(0)) {
+ case 'T':
+ assert numOfComponents == 3;
+ tasks.add(new ToDo(taskComponents[2], isDone));
+ break;
+ case 'D':
+ assert numOfComponents == 4;
+ tasks.add(new Deadline(taskComponents[2], Parser.parseDateTime(taskComponents[3]), isDone));
+ break;
+ case 'E':
+ assert numOfComponents == 5;
+ tasks.add(new Event(taskComponents[2], Parser.parseDateTime(taskComponents[3]),
+ Parser.parseDateTime(taskComponents[4]), isDone));
+ break;
+ case 'A':
+ assert numOfComponents == 4;
+ tasks.add(new DoAfterTask(taskComponents[2], taskComponents[3], isDone));
+ break;
+ case 'F':
+ assert numOfComponents == 4;
+ tasks.add(new FixedDurationTask(taskComponents[2], taskComponents[3], isDone));
+ break;
+ }
+ }
+ }
+ return tasks;
+ }
+
+ /**
+ *
+ *
+ * @param tasksAsStr
+ * @return
+ * @throws IOException
+ * @throws SecurityException
+ */
+ public void save(String tasksAsStr) throws IOException, SecurityException {
+ // CREATE FOLDERS
+ Path path = Paths.get(folderPath);
+ Files.createDirectories(path);
+
+ // CREATE FILE
+ File file = new File(getFullPath());
+ file.createNewFile();
+
+ // WRITE FILE
+ FileWriter fw = new FileWriter(getFullPath());
+ fw.write(tasksAsStr);
+ fw.close();
+ }
+}
diff --git a/src/main/java/wessy/components/TaskList.java b/src/main/java/wessy/components/TaskList.java
new file mode 100644
index 0000000000..9fdc535d7c
--- /dev/null
+++ b/src/main/java/wessy/components/TaskList.java
@@ -0,0 +1,221 @@
+package wessy.components;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import java.time.format.DateTimeParseException;
+
+import java.util.function.BiFunction;
+import java.util.function.BinaryOperator;
+import java.util.stream.Collectors;
+import java.util.stream.IntStream;
+import java.util.stream.Stream;
+import wessy.CmdType;
+import wessy.components.Parser;
+import wessy.task.Task;
+import wessy.task.ToDo;
+import wessy.task.Deadline;
+import wessy.task.DoAfterTask;
+import wessy.task.FixedDurationTask;
+import wessy.task.Event;
+
+import wessy.exceptions.integer.EmptyListException;
+import wessy.exceptions.integer.InvalidIntegerException;
+
+/**
+ *
+ */
+public class TaskList {
+ private static final String SEPARATOR = "~%~";
+ private final List tasks;
+
+ /**
+ * Constructs an instance of TaskList.
+ */
+ public TaskList() {
+ this.tasks = new ArrayList();
+ }
+
+ /**
+ * Constructs an instance of TaskList.
+ *
+ * @param taskList
+ */
+ public TaskList(List taskList) {
+ this.tasks = taskList;
+ }
+
+ /**
+ * Add a new task (either todo, deadline or event) into the current list of
+ * tasks.
+ *
+ * @param strings
+ * @return
+ * @throws DateTimeParseException
+ */
+ public Task add(String[] strings, CmdType cmd) throws DateTimeParseException {
+ int oldSize = tasks.size();
+
+ switch (cmd) {
+ case TODO:
+ tasks.add(new ToDo(Parser.removeSpacePadding(strings[0])));
+ break;
+ case DEADLINE:
+ tasks.add(new Deadline(Parser.removeSpacePadding(strings[0]), Parser.parseDateTime(strings[1])));
+ break;
+ case DOAFTER:
+ tasks.add(new DoAfterTask(Parser.removeSpacePadding(strings[0]),
+ Parser.removeSpacePadding(strings[1])));
+ break;
+ case FIXEDDURATION:
+ tasks.add(new FixedDurationTask(Parser.removeSpacePadding(strings[0]),
+ Parser.removeSpacePadding(strings[1])));
+ break;
+ case EVENT:
+ tasks.add(new Event(Parser.removeSpacePadding(strings[0]), Parser.parseDateTime(strings[1]),
+ Parser.parseDateTime(strings[2])));
+ break;
+ }
+ assert tasks.size() == oldSize + 1;
+ return tasks.get(getSize() - 1);
+ }
+
+ /**
+ * Marks or unmarks a task on the list, based on the specified task number.
+ *
+ * @param taskNum
+ * @param isMark
+ * @return
+ * @throws EmptyListException
+ * @throws InvalidIntegerException
+ */
+ // For "mark" & "unmark"
+ public Task markOrUnmark(int taskNum, boolean isMark) throws EmptyListException, InvalidIntegerException {
+ CmdType cmd = isMark ? CmdType.MARK : CmdType.UNMARK;
+ checkEmptyList(cmd);
+ assert tasks.size() > 0;
+ assert !tasks.isEmpty();
+ checkOutOfUppBound(taskNum, cmd);
+ assert taskNum <= tasks.size();
+
+ int idx = taskNum - 1;
+ if (isMark) {
+ tasks.get(idx).mark();
+ } else {
+ tasks.get(idx).unmark();
+ }
+ assert tasks.get(idx).checkIsDone() == isMark;
+ return tasks.get(idx);
+ }
+
+ /**
+ * Delete a task on the list, based on the specified task number.
+ *
+ * @param taskNum
+ * @return
+ * @throws EmptyListException
+ * @throws InvalidIntegerException
+ */
+ public Task delete(int taskNum) throws EmptyListException, InvalidIntegerException {
+ checkEmptyList(CmdType.DELETE);
+ assert tasks.size() > 0;
+ assert !tasks.isEmpty();
+ checkOutOfUppBound(taskNum, CmdType.DELETE);
+ assert taskNum <= tasks.size();
+ return tasks.remove(taskNum - 1);
+ }
+
+ /**
+ * Delete all the tasks on the list.
+ */
+ public void clear() {
+ tasks.clear();
+ assert tasks.size() == 0;
+ assert tasks.isEmpty();
+ }
+
+ public String[] find(String target) {
+ String finalTarget = Parser.removeSpacePadding(target);
+ return printAsStr(tasks.stream().filter(task -> task.toString().contains(finalTarget)));
+ }
+
+ /**
+ * Checks is the current task list empty.
+ *
+ * @param cmd
+ * @return
+ * @throws EmptyListException
+ */
+ public void checkEmptyList(CmdType cmd) throws EmptyListException {
+ if (tasks.isEmpty()) {
+ throw new EmptyListException(cmd.toString());
+ }
+ assert tasks.size() > 0;
+ assert !tasks.isEmpty();
+ }
+
+ /**
+ * Checks is the specified task number more than the total number of tasks
+ * on the list.
+ *
+ * @param taskNum
+ * @param cmd
+ * @return
+ * @throws InvalidIntegerException
+ */
+ public void checkOutOfUppBound(int taskNum, CmdType cmd) throws InvalidIntegerException {
+ int n = getSize();
+ assert n == tasks.size();
+ if (taskNum - 1 >= n) {
+ throw new InvalidIntegerException(cmd.toString(), taskNum, n);
+ }
+ assert taskNum <= tasks.size();
+ }
+
+ /**
+ * Gets the total number of tasks on the list.
+ *
+ * @return Total number of tasks on the list.
+ */
+ public int getSize() {
+ return tasks.size();
+ }
+
+ /**
+ * Saves all the tasks on the list to Wessy's storage, by passing a suitable
+ * String format of the task list.
+ *
+ * @return
+ */
+ // For interaction with Storage
+ public String saveAsStr() {
+ BiFunction appender = (curr, task) -> curr.append(task.saveAsStr(SEPARATOR));
+ BinaryOperator combiner = (sb1, sb2) -> sb1.append(sb2.toString());
+ return tasks.stream().reduce(new StringBuilder(), appender, combiner).toString();
+ }
+
+ /**
+ * Passes all the tasks on the list to Wessy's ui to print them out in
+ * "list" message.
+ *
+ * @return
+ */
+ public String[] printAsStr() {
+ return printAsStr(tasks);
+ }
+
+ // HELPER FUNCTION
+ private static String[] printAsStr(List foundResults) {
+ return printToStream(foundResults).toArray(String[]::new);
+ }
+
+ private static String[] printAsStr(Stream foundResults) {
+ return printAsStr(foundResults.collect(Collectors.toList()));
+ }
+
+ private static Stream printToStream(List taskList) {
+ int n = taskList.size();
+ return IntStream.range(0, n)
+ .mapToObj(i -> "" + (i + 1) + "." + taskList.get(i));
+ }
+}
diff --git a/src/main/java/wessy/components/Ui.java b/src/main/java/wessy/components/Ui.java
new file mode 100644
index 0000000000..6073a39e2a
--- /dev/null
+++ b/src/main/java/wessy/components/Ui.java
@@ -0,0 +1,213 @@
+package wessy.components;
+
+import java.util.Arrays;
+import java.util.Scanner;
+
+import wessy.task.Task;
+
+/**
+ *
+ */
+public class Ui {
+ private final static String OPENING = "Wessy:\n";
+ private final Scanner sc;
+
+ /**
+ * Constructs an instance of Ui.
+ */
+ public Ui() {
+ this.sc = new Scanner(System.in);
+ }
+
+ /**
+ * Handles inputs.
+ *
+ * @return A boolean value
+ */
+ public boolean hasNextLine() {
+ return sc.hasNextLine();
+ }
+
+ /**
+ * Handles inputs.
+ *
+ * @return
+ */
+ public String readNextLine() {
+ return sc.nextLine();
+ }
+
+ /**
+ * Prints output message for "bye" command.
+ */
+ public String getByeMessage() {
+ return getMessage("Bye. Hope to see you again soon!\n");
+ }
+
+ /**
+ * Prints output message for "list" command.
+ *
+ * @param tasks
+ * @param isList
+ */
+ public String getListOrFindMessage(String[] tasks, boolean isList) {
+ return getMessage(getListOrFindContent(tasks, isList));
+ }
+
+ /**
+ * A helper function used in pritnListMessage(...).
+ *
+ * @param tasks
+ * @param isList
+ */
+ private String getListOrFindContent(String[] tasks, boolean isList) {
+ String extraWord = isList ? "" : "matching ";
+ if (tasks.length == 0) {
+ if (isList) {
+ return "WOOHOO! You do not have any task on the list.\n";
+ } else {
+ return "Sorry. We did not find any task that matches the text you entered.\n";
+ }
+ } else {
+ return "Here are the " + extraWord + "tasks on your list:\n" + getMultiln(tasks);
+ }
+ }
+
+ /**
+ * Prints output message for "todo", "deadline" and "event" commands.
+ *
+ * @param task
+ * @param size
+ */
+ public String getAddedMessage(Task task, int size) {
+ String numOfTasks = " " + size + " task";
+ if (size > 1) {
+ numOfTasks += "s";
+ }
+ return getMessage("Got it. I've added this task:", " " + task, "Now you have" + numOfTasks
+ + " on the list.");
+ }
+
+ /**
+ * Prints output message for "mark" and "unmark" commands.
+ *
+ * @param chosenTask
+ * @param isDone
+ */
+ public String getMarkUnmarkMessage(Task chosenTask, boolean isDone) {
+ String end = isDone ? "done already:" : "not done yet:";
+ return getMessage("You mark this task as " + end, " " + chosenTask);
+ }
+
+ /**
+ * Prints output message for "delete" command.
+ *
+ * @param chosenTask
+ * @param totalNumOfTasks
+ */
+ public String getDeleteMessage(Task chosenTask, int totalNumOfTasks) {
+ return getMessage("Noted. I've removed this task:", " " + chosenTask, String.format(
+ "Now you have %d task%s on the list.", totalNumOfTasks, totalNumOfTasks == 1 ? "" : "s"));
+ }
+
+ /**
+ * Prints output message for "clear" command.
+ */
+ public String getClearMessage() {
+ return getMessage("You have cleared your task list. The list is empty now.");
+ }
+
+ /**
+ * Prints output message when starting up Wessy.
+ *
+ * @param tasks
+ */
+ public String getWelcomeMessage(String[] tasks) {
+ return "Hi, I am Wessy, your personal assistant chatbot.\n\n" +
+ getListOrFindContent(tasks, true);
+ }
+
+ /**
+ * A helper function
+ *
+ * @param str
+ */
+ private void println(String str) {
+ int length = str.length();
+ if (length <= 64) {
+ String message = " | " + str;
+ message += " ".repeat(67 - length) + "|";
+ System.out.println(message);
+ } else {
+
+ System.out.println(" | " + str.substring(0, 64) + " |");
+
+ int remainingLength = length - 64;
+ int leftover = remainingLength % 62;
+ int n = (int) Math.floor(remainingLength/62);
+
+ for (int i = 0; i < n; i++) {
+ System.out.println(" | " + str.substring(62 * i, 62 * (i + 1)) + " |");
+ }
+
+ String message = " | " + str.substring(length - leftover);
+ message += " ".repeat(65 - leftover) + "|";
+ System.out.println(message);
+ }
+ }
+
+ /**
+ * A helper function
+ *
+ * @param str
+ */
+ private void printErr(String str) {
+ int length = str.length();
+ if (length <= 64) {
+ String message = " | " + str;
+ message += " ".repeat(68 - length) + "|";
+ System.err.println(message);
+ } else {
+
+ System.err.println(" | " + str.substring(0, 64) + " |");
+
+ int remainingLength = length - 64;
+ int leftover = remainingLength % 62;
+ int n = (int) Math.floor(remainingLength/62);
+
+ for (int i = 0; i < n; i++) {
+ System.err.println(" | " + str.substring(62 * i, 62 * (i + 1)) + " |");
+ }
+
+ String message = " | " + str.substring(length - leftover);
+ message += " ".repeat(65 - leftover) + "|";
+ System.err.println(message);
+ }
+ }
+
+ /**
+ * A helper function
+ *
+ * @param linesOfString
+ */
+ private String getMultiln(String... linesOfString) {
+ return Arrays.stream(linesOfString).reduce("", (curr, next) -> curr + next + "\n");
+ }
+
+ /**
+ * A helper function
+ *
+ * @param linesOfString
+ */
+ public String getMessage(String... linesOfString) {
+ return OPENING + getMultiln(linesOfString);
+ }
+
+ /**
+ * @param message
+ * @return
+ */
+ public String handleException(String message) {
+ return getMessage("ERROR! " + message);
+ }
+}
diff --git a/src/main/java/wessy/components/UserInputChecker.java b/src/main/java/wessy/components/UserInputChecker.java
new file mode 100644
index 0000000000..2673e7c469
--- /dev/null
+++ b/src/main/java/wessy/components/UserInputChecker.java
@@ -0,0 +1,238 @@
+package wessy.components;
+
+import java.util.stream.IntStream;
+
+import wessy.CmdType;
+import wessy.exceptions.MissingSpacingException;
+import wessy.exceptions.TimeSpecifierException;
+import wessy.exceptions.UnspecifiedTimeException;
+
+import wessy.exceptions.numofinput.MissingInputException;
+import wessy.exceptions.numofinput.TooManyInputException;
+
+import wessy.exceptions.integer.NotAnIntegerException;
+
+/**
+ * UserInputChecker is a utility class that checks for all the formatting issues
+ * that could be possibly found in user input (except for the date time related
+ * one which will be checked by the Parser class).
+ */
+public class UserInputChecker {
+ private static final int START = 0;
+ private static final char SPACE = ' ';
+ private static final String SPACE_AS_STR = Character.toString(SPACE);
+
+ /**
+ * Throws MissingSpaceException when there is a missing space after the
+ * first word, which is usually the command word, in the user input line.
+ * This method is only used for the "mark", "unmark", "delete", "todo",
+ * "deadline" and "event" commands.
+ *
+ * @param userInput The line of user input to be checked.
+ * @param cmd The specified command. Only accepts CmdType.TODO,
+ * CmdType.DEADLINE, CmdType.EVENT, CmdType.MARK, CmdType.UNMARK,
+ * and CmdType.DELETE.
+ * @throws MissingSpacingException If the space is missing after the command word.
+ */
+ public static void checkSpacingAftCmd(String userInput, CmdType cmd) throws MissingSpacingException {
+ if (isCmdThatNeedsInput(cmd)) {
+ assert cmd == CmdType.TODO || cmd == CmdType.DEADLINE || cmd == CmdType.EVENT ||
+ cmd == CmdType.MARK || cmd == CmdType.UNMARK || cmd == CmdType.DELETE;
+
+ boolean noSpacing = userInput.equals(cmd.toString()) || userInput.charAt(cmd.getStrLength()) != SPACE;
+ if (noSpacing) {
+ throw new MissingSpacingException(cmd.toString());
+ }
+ }
+ }
+
+ /**
+ * Throws MissingInputException if the commands that require some inputs
+ * (namely "todo", "deadline", "event", "mark", "unmark" and "delete") have
+ * missing input.
+ *
+ * @param userInput The line of user input to be checked.
+ * @param cmd The specified command. Only accepts CmdType.TODO,
+ * CmdType.DEADLINE, CmdType.EVENT, CmdType.MARK, CmdType.UNMARK,
+ * and CmdType.DELETE.
+ * @throws MissingInputException If the input after the command word is missing.
+ */
+ public static void checkMissingInput(String userInput, CmdType cmd) throws MissingInputException {
+ String remainingStr = userInput.substring(cmd.getStrLength());
+ if (isCmdThatNeedsInput(cmd) && isAllSpaces(remainingStr)) {
+
+ assert cmd == CmdType.TODO || cmd == CmdType.DEADLINE || cmd == CmdType.EVENT ||
+ cmd == CmdType.MARK || cmd == CmdType.UNMARK || cmd == CmdType.DELETE;
+ throw new MissingInputException(cmd.toString());
+ }
+ }
+
+ /**
+ * Throws TimeSpecifierException if the substring " /by" is missing for
+ * deadline command, or if the substring " /from" or " /to" is missing for event command.
+ *
+ * @param userInput The line of user input to be checked.
+ * @param cmd The specified command. Only accepts CmdType.DEADLINE and CmdType.EVENT.
+ * @throws TimeSpecifierException If the required time specifier keyword is missing.
+ */
+ public static void checkMissingKeyword(String userInput, CmdType cmd) throws TimeSpecifierException {
+ assert hasSingleSpecifier(cmd) || cmd == CmdType.EVENT;
+
+ String[] specifiers = cmd.getSpecifiers();
+ String missingSpecifier = null;
+
+ for (String specifier : specifiers) {
+ if (!userInput.contains(SPACE_AS_STR + specifier)) {
+ missingSpecifier = specifier;
+ break;
+ }
+ }
+
+ if (missingSpecifier != null) {
+ throw new TimeSpecifierException(missingSpecifier);
+ }
+ }
+
+ /**
+ * Throws UnspecifiedTimeException if the "/by" time is not specified for
+ * the deadline command.
+ *
+ * @param userInput The line of user input to be checked.
+ * @throws UnspecifiedTimeException if the "/by" time input is missing for
+ * the deadline command.
+ */
+ private static void checkSingleSpecifierMissingInput(String userInput, CmdType cmd, String specifier) throws
+ UnspecifiedTimeException {
+ assert hasSingleSpecifier(cmd);
+
+ if (cmd == CmdType.FIXEDDURATION && userInput.contains(specifier + "s")) {
+ specifier += "s";
+ }
+
+ int pos = userInput.indexOf(specifier);
+ UnspecifiedTimeException ex = null;
+
+ if (isAllSpaces(userInput.substring(cmd.getStrLength(), pos))) {
+ ex = new UnspecifiedTimeException(specifier, true);
+ } else if (isAllSpaces(userInput.substring(pos + specifier.length()))) {
+ ex = new UnspecifiedTimeException(specifier, false);
+ }
+
+ if (ex != null) {
+ throw ex;
+ }
+ }
+
+ /**
+ * Throws UnspecifiedTimeException if the "/from" or "/to" time is not
+ * specified for the event command.
+ *
+ * @param userInput The line of user input to be checked.
+ * @throws UnspecifiedTimeException if any of the time input is missing for
+ * the event command.
+ */
+ public static void checkSpecifierMissingInput(String userInput, CmdType cmd) throws UnspecifiedTimeException {
+ assert hasSingleSpecifier(cmd) || cmd == CmdType.EVENT;
+ String[] specifiers = cmd.getSpecifiers();
+
+ if (cmd == CmdType.EVENT) {
+
+ String from = specifiers[0];
+ String to = specifiers[1];
+ int fPos = userInput.indexOf(from);
+ int tPos = userInput.indexOf(to);
+
+ UnspecifiedTimeException ex = null;
+ if (isAllSpaces(userInput.substring(cmd.getStrLength(), fPos))) {
+ ex = new UnspecifiedTimeException(from, true);
+ } else if (isAllSpaces(userInput.substring(fPos + from.length(), tPos))) {
+ ex = new UnspecifiedTimeException(from, false);
+ } else if (isAllSpaces(userInput.substring(tPos + to.length()))) {
+ ex = new UnspecifiedTimeException(to, false);
+ }
+
+ if (ex != null) {
+ throw ex;
+ }
+ } else {
+ checkSingleSpecifierMissingInput(userInput, cmd, specifiers[0]);
+ }
+ }
+
+ /**
+ * Throws NotAnIntegerException when there are character in the String that
+ * is not a digit nor a space (" ").
+ *
+ * @param userInput The line of user input to be checked.
+ * @param cmd The specified command. Only accepts CmdType.MARK,
+ * CmdType.UNMARK and CmdType.DELETE.
+ * @throws NotAnIntegerException If any invalid character is found in the String.
+ */
+ public static void checkNotNumerical(String userInput, CmdType cmd) throws NotAnIntegerException {
+ String str = userInput.substring(cmd.getStrLength());
+
+ boolean isNumerical = str.chars().allMatch(UserInputChecker::isValidChar);
+ if (!isNumerical) {
+ throw new NotAnIntegerException();
+ }
+ }
+
+ private static boolean isValidChar(int target) {
+ int numOfDigits = 10;
+ int[] extras = new int[]{(int) ' ', (int) '-'};
+
+ return IntStream.concat(IntStream.range(START, numOfDigits).map(i -> i + '0'), IntStream.of(extras))
+ .anyMatch(c -> target == c);
+ }
+
+ /**
+ * Throws TooManyInputException when there are too many inputs after the
+ * first word (the command word). This method does so by checking is there
+ * any spacing in the rest of the user input line. Remember that the "mark",
+ * "unmark" and "delete" commands only accept one input.
+ *
+ * @param userInput The line of user input to be checked.
+ * @param cmd The specified command. Only accepts CmdType.MARK,
+ * CmdType.UNMARK and CmdType.DELETE.
+ * @throws TooManyInputException If there are too many inputs after the
+ * command word.
+ */
+ public static void checkTooManyInputs(String userInput, CmdType cmd) throws TooManyInputException {
+ String str = Parser.removeSpacePadding(userInput.substring(cmd.getStrLength()));
+
+ if (str.chars().anyMatch(UserInputChecker::isSpace)) {
+ throw new TooManyInputException(cmd.toString());
+ }
+ }
+
+ /**
+ * A helper function to check does str only consist of space (" ").
+ *
+ * @param str A String to be checked.
+ * @return A boolean value indicating does str only consist of space (" ").
+ */
+ private static boolean isAllSpaces(String str) {
+ return str.chars().allMatch(UserInputChecker::isSpace);
+ }
+
+ private static boolean isSpace(int c) {
+ return c == (int) ' ';
+ }
+
+ /**
+ * A helper function to check is cmd CmdType.TODO, CmdType.DEADLINE,
+ * CmdType.EVENT, CmdType.MARK, CmdType.UNMARK or CmdType.DELETE.
+ *
+ * @param cmd The specified command.
+ * @return A boolean value indicating is the command todo, deadline, event,
+ * mark, unmark or delete.
+ */
+ private static boolean isCmdThatNeedsInput(CmdType cmd) {
+ return (cmd == CmdType.TODO || cmd == CmdType.DEADLINE || cmd == CmdType.EVENT || cmd == CmdType.MARK
+ || cmd == CmdType.UNMARK || cmd == CmdType.DELETE);
+ }
+
+ private static boolean hasSingleSpecifier(CmdType cmd) {
+ return (cmd == CmdType.DEADLINE || cmd == CmdType.DOAFTER || cmd == CmdType.FIXEDDURATION);
+ }
+}
diff --git a/src/main/java/wessy/exceptions/CommandNotFoundException.java b/src/main/java/wessy/exceptions/CommandNotFoundException.java
new file mode 100644
index 0000000000..9928c40102
--- /dev/null
+++ b/src/main/java/wessy/exceptions/CommandNotFoundException.java
@@ -0,0 +1,14 @@
+package wessy.exceptions;
+
+/**
+ * CommandNotFoundException is an exception that should be thrown when the user
+ * inputs some text that could not be recognised as a command.
+ */
+public class CommandNotFoundException extends WessyException {
+ /**
+ * Constructs an instance of CommandNotFoundException.
+ */
+ public CommandNotFoundException() {
+ super("I'm sorry, but I don't know what that means.");
+ }
+}
diff --git a/src/main/java/wessy/exceptions/MissingSpacingException.java b/src/main/java/wessy/exceptions/MissingSpacingException.java
new file mode 100644
index 0000000000..10ca3720e5
--- /dev/null
+++ b/src/main/java/wessy/exceptions/MissingSpacingException.java
@@ -0,0 +1,17 @@
+package wessy.exceptions;
+
+/**
+ * MissingSpacingException is an exception that should be thrown when the user
+ * does not enter a space after the commands he inputs, for commands that
+ * require some arguments (todo, event, deadline, mark, unmark).
+ */
+public class MissingSpacingException extends WessyException {
+ /**
+ * Constructs an instance of MissingSpacingException.
+ *
+ * @param cmd The command requested by the user, in its String form.
+ */
+ public MissingSpacingException(String cmd) {
+ super(String.format("The spacing after '%s' is missing.", cmd));
+ }
+}
diff --git a/src/main/java/wessy/exceptions/TimeSpecifierException.java b/src/main/java/wessy/exceptions/TimeSpecifierException.java
new file mode 100644
index 0000000000..4e6de5359a
--- /dev/null
+++ b/src/main/java/wessy/exceptions/TimeSpecifierException.java
@@ -0,0 +1,19 @@
+package wessy.exceptions;
+
+/**
+ * TimeSpecifierException is an exception that summarises all other wrong ways
+ * of inputting and using "/by", "/from" and "/to" keywords, that are not
+ * covered by other exceptions.
+ */
+public class TimeSpecifierException extends WessyException {
+ /**
+ * Constructs an instance of TimeSpecifierException.
+ *
+ * @param keyword The keyword the user uses wrongly. It is either "/by",
+ * "from" or "to".
+ */
+ public TimeSpecifierException(String keyword) {
+ super("Either '" + keyword + "' is missing, or it is used in the " +
+ "wrong format.");
+ }
+}
diff --git a/src/main/java/wessy/exceptions/UnspecifiedTimeException.java b/src/main/java/wessy/exceptions/UnspecifiedTimeException.java
new file mode 100644
index 0000000000..b97e589eed
--- /dev/null
+++ b/src/main/java/wessy/exceptions/UnspecifiedTimeException.java
@@ -0,0 +1,21 @@
+package wessy.exceptions;
+
+/**
+ * UnspecifiedTimeException is an exception that should be thrown when some
+ * arguments for "deadline" or "event" commands are missing, such as the task
+ * description, the specified time after "/by", "/from" and "/to".
+ */
+public class UnspecifiedTimeException extends WessyException {
+ /**
+ * Constructs an instance of UnspecifiedTimeException
+ *
+ * @param keyword The keyword where the user has missed out the argument
+ * before or after it.
+ * @param isBefore A boolean value to indicate the argument is missing
+ * before or after the said keyword.
+ */
+ public UnspecifiedTimeException(String keyword, boolean isBefore) {
+ super("The input " + (isBefore ? "before '" : "after '") + keyword +
+ "' is missing.");
+ }
+}
diff --git a/src/main/java/wessy/exceptions/WessyException.java b/src/main/java/wessy/exceptions/WessyException.java
new file mode 100644
index 0000000000..fec406cfcd
--- /dev/null
+++ b/src/main/java/wessy/exceptions/WessyException.java
@@ -0,0 +1,31 @@
+package wessy.exceptions;
+
+/**
+ * WessyException is an abstract base class that serves as the parent for all the
+ * exceptions that might occur when the programme, "Wessy" is running.
+ */
+public abstract class WessyException extends Exception {
+ private static final String OPENING = "ERROR! ";
+ private final String message;
+
+ /**
+ * Serves as a constructor for all the child classes to initialise. The
+ * error message is specified in str
+ *
+ * @param str The text where the child class input, to specify the error
+ * message.
+ */
+ protected WessyException(String str) {
+ this.message = OPENING + str;
+ }
+
+ /** Returns the String representation of the child exceptions, unless
+ * otherwise overridden.
+ *
+ * @return The string representation of the child exceptions.
+ */
+ @Override
+ public String toString() {
+ return message;
+ }
+}
diff --git a/src/main/java/wessy/exceptions/integer/EmptyListException.java b/src/main/java/wessy/exceptions/integer/EmptyListException.java
new file mode 100644
index 0000000000..687d1e6485
--- /dev/null
+++ b/src/main/java/wessy/exceptions/integer/EmptyListException.java
@@ -0,0 +1,19 @@
+package wessy.exceptions.integer;
+
+import wessy.exceptions.WessyException;
+
+/**
+ * EmptyListException is an exception that should be thrown when the task list
+ * is empty and yet the user requests for a task to be marked, unmarked or
+ * deleted.
+ */
+public class EmptyListException extends WessyException {
+ /**
+ * Constructs an instance of EmptyListException.
+ *
+ * @param cmd The command requested by the user, in its String form.
+ */
+ public EmptyListException(String cmd) {
+ super("You do not have any task on the list for you to " + cmd + ".");
+ }
+}
diff --git a/src/main/java/wessy/exceptions/integer/InvalidIntegerException.java b/src/main/java/wessy/exceptions/integer/InvalidIntegerException.java
new file mode 100644
index 0000000000..c6a6a36a75
--- /dev/null
+++ b/src/main/java/wessy/exceptions/integer/InvalidIntegerException.java
@@ -0,0 +1,22 @@
+package wessy.exceptions.integer;
+
+import wessy.exceptions.WessyException;
+
+/**
+ * InvalidIntegerException is an exception that should be thrown when the user
+ * wants to mark, unmark or delete a task and the task number he specifies is
+ * out of the upper bounds, given the total number of tasks on the list.
+ */
+public class InvalidIntegerException extends WessyException {
+ /**
+ * Constructs an instance of InvalidIntegerException.
+ *
+ * @param cmd The command requested by the user, in its String form.
+ * @param taskNum The task number specified by the user.
+ * @param total Total number of tasks on the list.
+ */
+ public InvalidIntegerException(String cmd, int taskNum, int total) {
+ super(String.format("You can't %s Task %d as you only have %d tasks on"
+ + " the list", cmd, taskNum, total));
+ }
+}
diff --git a/src/main/java/wessy/exceptions/integer/NotAnIntegerException.java b/src/main/java/wessy/exceptions/integer/NotAnIntegerException.java
new file mode 100644
index 0000000000..b04f3cb4ac
--- /dev/null
+++ b/src/main/java/wessy/exceptions/integer/NotAnIntegerException.java
@@ -0,0 +1,17 @@
+package wessy.exceptions.integer;
+
+import wessy.exceptions.WessyException;
+
+/**
+ * NotAnIntegerException is an exception that should be thrown when the user
+ * wants to mark, unmark or delete a task, and he inputs some strings that could
+ * not be parsed into an integer.
+ */
+public class NotAnIntegerException extends WessyException {
+ /**
+ * Constructs an instance of NotAnIntegerException.
+ */
+ public NotAnIntegerException() {
+ super("What you just input is not an integer. Please input an integer.");
+ }
+}
diff --git a/src/main/java/wessy/exceptions/integer/NotPositiveIntegerException.java b/src/main/java/wessy/exceptions/integer/NotPositiveIntegerException.java
new file mode 100644
index 0000000000..efbe9ba577
--- /dev/null
+++ b/src/main/java/wessy/exceptions/integer/NotPositiveIntegerException.java
@@ -0,0 +1,19 @@
+package wessy.exceptions.integer;
+
+import wessy.exceptions.WessyException;
+
+/**
+ * NotPositiveIntegerException is an exception that should be thrown when the
+ * user wants to mark, unmark or delete a task, and he specifies a non-positive
+ * integer. Remember that the task list starts from 1 instead of 0, in terms of
+ * user's input.
+ */
+public class NotPositiveIntegerException extends WessyException {
+ /**
+ * Constructs an instance of NotPositiveIntegerException.
+ */
+ public NotPositiveIntegerException() {
+ super("The number you just input is not a positive integer. Please " +
+ "input a positive integer.");
+ }
+}
diff --git a/src/main/java/wessy/exceptions/numofinput/MissingInputException.java b/src/main/java/wessy/exceptions/numofinput/MissingInputException.java
new file mode 100644
index 0000000000..f1cc290aba
--- /dev/null
+++ b/src/main/java/wessy/exceptions/numofinput/MissingInputException.java
@@ -0,0 +1,41 @@
+package wessy.exceptions.numofinput;
+
+import wessy.exceptions.WessyException;
+
+/**
+ * MissingInputException is an exception that should be thrown when the task
+ * description is missing for the "event", "deadline" or "todo" commands, or
+ * when the task number is missing for the "mark", "unmark" or "delete"
+ * commands.
+ */
+public class MissingInputException extends WessyException {
+ private static final String ENDING = " is missing.";
+ private final String cmd;
+
+ /**
+ * Constructs an instance of MissingInputException.
+ *
+ * @param cmd The command requested by the user, in its String form.
+ */
+ public MissingInputException(String cmd) {
+ super((cmd.equals("mark") || cmd.equals("unmark"))
+ ? "The chosen task number of the '"
+ : "The task description of the '");
+ this.cmd = cmd;
+ }
+
+ /** Overrides the toString() method of the parent class, WessyException.
+ * Returns the String representation of this exception, by adding more
+ * details to the result returned by the parent's method.
+ *
+ * @return The String representation of this exception.
+ */
+ @Override
+ public String toString() {
+ String addStr = "'";
+ if (cmd.equals("mark") || cmd.equals("unmark")) {
+ addStr += " command";
+ }
+ return super.toString() + cmd + addStr + ENDING;
+ }
+}
diff --git a/src/main/java/wessy/exceptions/numofinput/TooManyInputException.java b/src/main/java/wessy/exceptions/numofinput/TooManyInputException.java
new file mode 100644
index 0000000000..4750c3ebaa
--- /dev/null
+++ b/src/main/java/wessy/exceptions/numofinput/TooManyInputException.java
@@ -0,0 +1,19 @@
+package wessy.exceptions.numofinput;
+
+import wessy.exceptions.WessyException;
+
+/**
+ * TooManyInputException is an exception that should be thrown when the user
+ * wants to mark, unmark or delete a task, and he enters more than one integer
+ * to specify the task numbers.
+ */
+public class TooManyInputException extends WessyException {
+ /**
+ * Constructs an instance of TooManyInputException.
+ *
+ * @param cmd The command requested by the user, in its String form.
+ */
+ public TooManyInputException(String cmd) {
+ super(String.format("The '%s' command only takes in 1 input.", cmd));
+ }
+}
diff --git a/src/main/java/wessy/javafxnodes/DialogBox.java b/src/main/java/wessy/javafxnodes/DialogBox.java
new file mode 100644
index 0000000000..bbddeb2943
--- /dev/null
+++ b/src/main/java/wessy/javafxnodes/DialogBox.java
@@ -0,0 +1,97 @@
+package wessy.javafxnodes;
+
+import java.io.IOException;
+import java.util.Collections;
+
+import javafx.fxml.FXML;
+import javafx.fxml.FXMLLoader;
+
+import javafx.collections.FXCollections;
+import javafx.collections.ObservableList;
+
+import javafx.geometry.Insets;
+import javafx.geometry.Pos;
+
+import javafx.scene.image.Image;
+import javafx.scene.image.ImageView;
+
+import javafx.scene.Node;
+import javafx.scene.control.Label;
+import javafx.scene.layout.HBox;
+import javafx.scene.layout.Pane;
+
+import javafx.scene.layout.Border;
+import javafx.scene.layout.BorderStroke;
+import javafx.scene.layout.BorderStrokeStyle;
+import javafx.scene.layout.BorderWidths;
+import javafx.scene.layout.CornerRadii;
+
+import javafx.scene.paint.Color;
+import javafx.scene.shape.Circle;
+
+/**
+ * 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 {
+
+ private static final double TEXT_PADDING = 10.0;
+ private static final double CIRCLE_CLIP_RADIUS = 50.0;
+ private static final double BORDER_WIDTH = 15.0;
+
+ @FXML
+ private Label dialog;
+ @FXML
+ private Pane displayPicture;
+
+ private DialogBox(String text, Image img) {
+ try {
+ FXMLLoader fxmlLoader = new FXMLLoader(
+ wessy.javafxnodes.MainWindow.class.getResource("/view/DialogBox.fxml")
+ );
+
+ fxmlLoader.setController(this);
+ fxmlLoader.setRoot(this);
+ fxmlLoader.load();
+
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+
+ dialog.setText(text);
+ dialog.setPadding(new Insets(TEXT_PADDING));
+
+ ImageView iv = (ImageView) displayPicture.lookup("#iv");
+ iv.setImage(img);
+ displayPicture.setClip(new Circle(CIRCLE_CLIP_RADIUS, CIRCLE_CLIP_RADIUS, CIRCLE_CLIP_RADIUS));
+
+ double halfWidthReduction = BORDER_WIDTH * (-1) / 2;
+ this.setBorder(new Border(new BorderStroke(Color.WHITE,
+ BorderStrokeStyle.SOLID,
+ CornerRadii.EMPTY,
+ new BorderWidths(BORDER_WIDTH),
+ new Insets(halfWidthReduction, 0, halfWidthReduction, 0)
+ )));
+ }
+
+ /**
+ * 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 getUserDialogBox(String text, Image img) {
+ return new DialogBox(text, img);
+ }
+
+ public static DialogBox getWessyDialogBox(String text, Image img) {
+ var db = new DialogBox(text, img);
+ db.flip();
+ return db;
+ }
+}
diff --git a/src/main/java/wessy/javafxnodes/MainWindow.java b/src/main/java/wessy/javafxnodes/MainWindow.java
new file mode 100644
index 0000000000..000d50404f
--- /dev/null
+++ b/src/main/java/wessy/javafxnodes/MainWindow.java
@@ -0,0 +1,62 @@
+package wessy.javafxnodes;
+
+import javafx.fxml.FXML;
+
+import javafx.scene.control.Button;
+import javafx.scene.control.ScrollPane;
+import javafx.scene.control.TextField;
+import javafx.scene.image.Image;
+import javafx.scene.layout.AnchorPane;
+import javafx.scene.layout.VBox;
+
+import wessy.Wessy;
+
+/**
+ * Controller for MainWindow. Provides the layout for the other controls.
+ */
+public class MainWindow extends AnchorPane {
+
+ @FXML
+ private ScrollPane scrollPane;
+ @FXML
+ private VBox dialogContainer;
+ @FXML
+ private TextField userInput;
+ @FXML
+ private Button sendButton;
+
+ private Wessy wessy = new Wessy();
+
+ private Image userImage = new Image(this.getClass().getResourceAsStream("/images/dummy_user.jpeg"));
+ private Image wessyImage = new Image(this.getClass().getResourceAsStream("/images/water walley.jpg"));
+
+ @FXML
+ public void initialize() {
+ scrollPane.vvalueProperty().bind(dialogContainer.heightProperty());
+ String opening = wessy.startsUp();
+ dialogContainer.getChildren().addAll(
+ DialogBox.getWessyDialogBox(opening, wessyImage)
+ );
+ }
+
+ public void setWessy(Wessy w) {
+ wessy = w;
+ }
+
+ /**
+ * Creates two dialog boxes, one echoing user input and the other containing Duke's reply and then appends them to
+ * the dialog container. Clears the user input after processing.
+ */
+ @FXML
+ private void handleUserInput() {
+ String input = userInput.getText();
+ String response = wessy.respond(input);
+
+ dialogContainer.getChildren().addAll(
+ DialogBox.getUserDialogBox(input, userImage),
+ DialogBox.getWessyDialogBox(response, wessyImage)
+ );
+
+ userInput.clear();
+ }
+}
diff --git a/src/main/java/wessy/task/Deadline.java b/src/main/java/wessy/task/Deadline.java
new file mode 100644
index 0000000000..a9cdae5080
--- /dev/null
+++ b/src/main/java/wessy/task/Deadline.java
@@ -0,0 +1,81 @@
+package wessy.task;
+
+import java.time.LocalDateTime;
+
+/**
+ * This Deadline class inherits from its parent class, Task, and it encapsulates
+ * the information and operations required while handling a "deadline" task.
+ */
+public class Deadline extends Task {
+ private final LocalDateTime by;
+
+ /**
+ * Constructs an instance of Deadline by specifying the task description,
+ * the specified time by that this Deadline task needs to be done and
+ * whether the task has been done.
+ *
+ * @param description The specified task description.
+ * @param by The specified time by that this Deadline task needs to be done.
+ * @param isDone The status of whether the task has been done.
+ */
+ public Deadline(String description, LocalDateTime by, boolean isDone){
+ super(description, isDone);
+ this.by=by;
+ }
+
+ /**
+ * Constructs an instance of Deadline by specifying only the task
+ * description and the specified time by that this Deadline task needs to be
+ * done. The status of this Deadline task is by default set as not done when
+ * initialised.
+ *
+ * @param description The specified task description.
+ * @param by The specified time by that this Deadline task needs to be done.
+ */
+ public Deadline(String description, LocalDateTime by) {
+ this(description, by, false);
+ }
+
+ /**
+ * Overrides the toString method of the parent class, Task. Returns the
+ * String representation of this Deadline object by providing more details
+ * on the specified time by that this Deadline task needs to be done, on top
+ * of the String representation returned by its parent's toString method.
+ *
+ * @return The String representation of this Deadline object.
+ */
+ @Override
+ public String toString() {
+ return "[D]" + super.toString() + " (by: " + toString(by) + ")";
+ }
+
+ /**
+ * A helper function that takes in a LocalDateTime object and converts it
+ * into a useful String representation.
+ *
+ * @param dateTime The LocalDateTime object that this method takes in.
+ * @return A useful String representation of the inputted LocalDateTime
+ * object.
+ */
+ public static String toString(LocalDateTime dateTime) {
+ String str = dateTime.toString();
+ if (str.substring(11).equals("12:34:56")) {
+ return str.substring(0, 10);
+ }
+ return str.substring(0, 10) + " " + str.substring(11, 16);
+ }
+
+ /**
+ * Converts this Deadline object into a String representation that will be
+ * used while saving this task in a .txt file to the Wessy's storage.
+ *
+ * @param separator A string that indicates the partition between the
+ * different fields of a Deadline object.
+ * @return A String representation that will be used while saving this task
+ * to the Wessy's storage.
+ */
+ @Override
+ public String saveAsStr(String separator) {
+ return "D" + super.saveAsStr(separator) + separator + by + "\n";
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/wessy/task/DoAfterTask.java b/src/main/java/wessy/task/DoAfterTask.java
new file mode 100644
index 0000000000..9405eaacdf
--- /dev/null
+++ b/src/main/java/wessy/task/DoAfterTask.java
@@ -0,0 +1,24 @@
+package wessy.task;
+
+public class DoAfterTask extends Task {
+ private final String after;
+
+ public DoAfterTask(String description, String after, boolean isDone){
+ super(description, isDone);
+ this.after = after;
+ }
+
+ public DoAfterTask(String description, String after) {
+ this(description, after, false);
+ }
+
+ @Override
+ public String toString() {
+ return "[A]" + super.toString() + " (after: " + after + ")";
+ }
+
+ @Override
+ public String saveAsStr(String separator) {
+ return "A" + super.saveAsStr(separator) + separator + after + "\n";
+ }
+}
diff --git a/src/main/java/wessy/task/Event.java b/src/main/java/wessy/task/Event.java
new file mode 100644
index 0000000000..70b81dc560
--- /dev/null
+++ b/src/main/java/wessy/task/Event.java
@@ -0,0 +1,87 @@
+package wessy.task;
+
+import java.time.LocalDateTime;
+
+/**
+ * This Event class inherits from its parent class, Task, and it encapsulates
+ * the information and operations required while handling an event.
+ */
+public class Event extends Task {
+ private final LocalDateTime from;
+ private final LocalDateTime to;
+
+ /**
+ * Constructs an instance of Event by specifying the task description, the
+ * specified time from which the Event starts, the specified time til which
+ * the Event ends and whether the task has been done.
+ *
+ * @param description The specified task description.
+ * @param from The specified time from which the Event starts.
+ * @param to The specified time til which the Event ends.
+ * @param isDone The status of whether the task has been done.
+ */
+ public Event(String description, LocalDateTime from, LocalDateTime to,
+ boolean isDone) {
+ super(description, isDone);
+ this.from = from;
+ this.to = to;
+ }
+
+ /**
+ * Constructs an instance of Event by specifying the task description, the
+ * specified time from which the Event starts and the specified time til
+ * which the Event ends. The status of this Deadline is by default set as
+ * not done when initialised.
+ *
+ * @param description The specified task description.
+ * @param from The specified time from which the Event starts.
+ * @param to The specified time til which the Event ends.
+ */
+ public Event(String description, LocalDateTime from, LocalDateTime to) {
+ this(description, from, to, false);
+ }
+
+ /**
+ * Overrides the toString method of the parent class, Task. Returns the
+ * String representation of this Event object by providing more details on
+ * the specified time from that this Event starts and the specified time til
+ * which this Event ends, on top of the String representation returned by
+ * its parent's toString method.
+ *
+ * @return The String representation of this Event object.
+ */
+ @Override
+ public String toString() {
+ return "[E]" + super.toString() + " (from: " + toString(from) + " to: " + toString(to) + ")";
+ }
+
+ /**
+ * A helper function that takes in a LocalDateTime object and converts it
+ * into a useful String representation.
+ *
+ * @param dateTime The LocalDateTime object that this method takes in.
+ * @return A useful String representation of the inputted LocalDateTime
+ * object.
+ */
+ public static String toString(LocalDateTime dateTime) {
+ String str = dateTime.toString();
+ if (str.substring(11).equals("12:34:56")) {
+ return str.substring(0, 10);
+ }
+ return str.substring(0, 10) + " " + str.substring(11, 16);
+ }
+
+ /**
+ * Converts this Event object into a String representation that will be
+ * used while saving this task in a .txt file to the Wessy's storage.
+ *
+ * @param separator A string that indicates the partition between the
+ * different fields of an Event object.
+ * @return A String representation that will be used while saving this task
+ * to the Wessy's storage.
+ */
+ @Override
+ public String saveAsStr(String separator) {
+ return "E" + super.saveAsStr(separator) + separator + from + separator + to + "\n";
+ }
+}
diff --git a/src/main/java/wessy/task/FixedDurationTask.java b/src/main/java/wessy/task/FixedDurationTask.java
new file mode 100644
index 0000000000..653a2c6b70
--- /dev/null
+++ b/src/main/java/wessy/task/FixedDurationTask.java
@@ -0,0 +1,24 @@
+package wessy.task;
+
+public class FixedDurationTask extends Task {
+ private final String requiredDuration;
+
+ public FixedDurationTask(String description, String duration, boolean isDone){
+ super(description, isDone);
+ this.requiredDuration = duration;
+ }
+
+ public FixedDurationTask(String description, String duration) {
+ this(description, duration, false);
+ }
+
+ @Override
+ public String toString() {
+ return "[F]" + super.toString() + " (for: " + requiredDuration + ")";
+ }
+
+ @Override
+ public String saveAsStr(String separator) {
+ return "F" + super.saveAsStr(separator) + separator + requiredDuration + "\n";
+ }
+}
diff --git a/src/main/java/wessy/task/Task.java b/src/main/java/wessy/task/Task.java
new file mode 100644
index 0000000000..9b51361d24
--- /dev/null
+++ b/src/main/java/wessy/task/Task.java
@@ -0,0 +1,95 @@
+package wessy.task;
+
+/**
+ * Task is an abstract base class that serves as the parent class for ToDo,
+ * Deadline and Event classes. It abstracts the common fields like the task
+ * description and the status of whether the task has been done.
+ */
+public abstract class Task {
+ protected final String description;
+ protected boolean isDone;
+
+ /**
+ * Serves as a constructor for all the child classes to initialise. The task
+ * description and the status of whether the task has been done need to be
+ * specified.
+ *
+ * @param description The specified task description.
+ * @param isDone The status of whether the task has been done.
+ */
+ public Task(String description, boolean isDone) {
+ this.description = description;
+ this.isDone = isDone;
+ }
+
+ /**
+ * Serves as a constructor for all the child classes to initialise. Only the
+ * task description needs to be specified.
+ *
+ * @param description The specified task description.
+ */
+ public Task(String description) {
+ this(description, false);
+ }
+
+ /**
+ * Represents the status of whether this Task has been done in a form of a
+ * String, with "[X]" indicating done and "[ ]" indicating not done.
+ *
+ * @return An indication showing whether this Task has been done, in the
+ * form of "[ ]".
+ */
+ String getStatusIcon() {
+ return (isDone ? "X" : " "); // mark done task with X
+ }
+
+ /**
+ * Provides the common component of the String representation of all kinds
+ * of Task, so that the respective child classes can use it to come up with
+ * their String representation.
+ *
+ * @return The common component of the children's String representation.
+ */
+ @Override
+ public String toString() {
+ return "[" + getStatusIcon() + "] " + description;
+ }
+
+ /**
+ * Provides the common component for the child classes to converts into a
+ * String representation that will be used while saving this task in a .txt
+ * file to the Wessy's storage.
+ *
+ * @param separator A string that indicates the partition between the
+ * * different fields of a Task object.
+ * @return The common component of the String representation used while
+ * saving the task to the Wessy's storage
+ */
+ public String saveAsStr(String separator) {
+ String mark = isDone ? "1" : "0";
+ return separator + mark + separator + description;
+ }
+
+ /**
+ * Marks this task as done.
+ */
+ public void mark() {
+ isDone = true;
+ }
+
+ /**
+ * Marks this task as "not done".
+ */
+ public void unmark() {
+ isDone = false;
+ }
+
+ /**
+ * Checks whether this task has been done.
+ *
+ * @return A boolean value indicating whether this task has been done.
+ */
+ public boolean checkIsDone() {
+ return isDone;
+ }
+}
diff --git a/src/main/java/wessy/task/ToDo.java b/src/main/java/wessy/task/ToDo.java
new file mode 100644
index 0000000000..7c14eb0f4e
--- /dev/null
+++ b/src/main/java/wessy/task/ToDo.java
@@ -0,0 +1,54 @@
+package wessy.task;
+
+/**
+ * This ToDo class inherits from its parent class, Task, and it encapsulates
+ * the information and operations required while handling a "todo" task.
+ */
+public class ToDo extends Task {
+ /**
+ * Constructs an instance of ToDo by specifying the task description and
+ * whether the task has been done.
+ *
+ * @param description The specified task description.
+ * @param isDone The status of whether the task has been done.
+ */
+ public ToDo(String description, boolean isDone) {
+ super(description, isDone);
+ }
+
+ /**
+ * Constructs an instance of ToDo by specifying the task description. The
+ * status of this ToDo is by default set as not done when initialised.
+ *
+ * @param description The specified task description.
+ */
+ public ToDo(String description) {
+ this(description, false);
+ }
+
+ /**
+ * Overrides the toString method of the parent class, Task. Returns the
+ * String representation of this ToDo object by adding "[T]" in front of the
+ * String representation returned by the parent's toString method.
+ *
+ * @return The String representation of this ToDo object.
+ */
+ @Override
+ public String toString() {
+ return "[T]" + super.toString();
+ }
+
+ /**
+ * Converts this ToDo object into a String representation that will be
+ * used while saving this task in a .txt file to the Wessy's storage.
+ *
+ * @param separator A string that indicates the partition between the
+ * different fields of a ToDo object.
+ * @return A String representation that will be used while saving this task
+ * to the Wessy's storage.
+ */
+ @Override
+ public String saveAsStr(String separator) {
+ return "T" + super.saveAsStr(separator) + "\n";
+ }
+}
diff --git a/src/main/resources/images/dummy_user.jpeg b/src/main/resources/images/dummy_user.jpeg
new file mode 100644
index 0000000000..6411b2efa3
Binary files /dev/null and b/src/main/resources/images/dummy_user.jpeg differ
diff --git a/src/main/resources/images/water walley.jpg b/src/main/resources/images/water walley.jpg
new file mode 100644
index 0000000000..38fb94d788
Binary files /dev/null and b/src/main/resources/images/water walley.jpg differ
diff --git a/src/main/resources/view/DialogBox.fxml b/src/main/resources/view/DialogBox.fxml
new file mode 100644
index 0000000000..306d8bceba
--- /dev/null
+++ b/src/main/resources/view/DialogBox.fxml
@@ -0,0 +1,17 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/main/resources/view/MainWindow.fxml b/src/main/resources/view/MainWindow.fxml
new file mode 100644
index 0000000000..f3e4c8cd83
--- /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/wessy/ParserTest.java b/src/test/java/wessy/ParserTest.java
new file mode 100644
index 0000000000..1eca96ac4b
--- /dev/null
+++ b/src/test/java/wessy/ParserTest.java
@@ -0,0 +1,35 @@
+package wessy;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNull;
+
+import org.junit.jupiter.api.Test;
+
+import java.time.LocalDateTime;
+import wessy.components.Parser;
+
+public class ParserTest {
+ @Test
+ public void getCmd() {
+ assertEquals(Parser.getCmd("bye"), CmdType.BYE);
+ assertNull(Parser.getCmd("listkjcbjdfvb"));
+ assertEquals(Parser.getCmd("event "), CmdType.EVENT);
+ assertEquals(Parser.getCmd("todo jsvdjnv"), CmdType.TODO);
+ assertNull(Parser.getCmd("dEaDlIne bjb"));
+ }
+
+ @Test
+ public void parseDateTime() {
+ assertEquals(Parser.parseDateTime(" 2023/3/2 "), LocalDateTime.parse("2023-03-02T12:34:56"));
+ assertEquals(Parser.parseDateTime("10-3-2022 1700 "), LocalDateTime.parse("2022-03-10T17:00:00"));
+ assertEquals(Parser.parseDateTime("2023-11-5 13:45"), LocalDateTime.parse("2023-11-05T13:45:00"));
+ assertEquals(Parser.parseDateTime(" 3/12/2022 04.25"), LocalDateTime.parse("2022-12-03T04:25:00"));
+ }
+
+ @Test
+ public void removeSpacePadding() {
+ assertEquals(Parser.removeSpacePadding("FEGW"), "FEGW");
+ assertEquals(Parser.removeSpacePadding("A BCD"), "A BCD");
+ assertEquals(Parser.removeSpacePadding(" fkjv efb d jhbdjkdf "), "fkjv efb d jhbdjkdf");
+ }
+}
diff --git a/text-ui-test/EXPECTED.TXT b/text-ui-test/EXPECTED.TXT
index 657e74f6e7..0f49d12bc3 100644
--- a/text-ui-test/EXPECTED.TXT
+++ b/text-ui-test/EXPECTED.TXT
@@ -1,7 +1,62 @@
-Hello from
- ____ _
-| _ \ _ _| | _____
-| | | | | | | |/ / _ \
-| |_| | |_| | < __/
-|____/ \__,_|_|\_\___|
-
+ -Wessy----------------------------------------------------------------
+ | Hi, I am Wessy, your personal assistant chatbot. |
+ | Please type something to interact with me. |
+ ----------------------------------------------------------------------
+ -Wessy----------------------------------------------------------------
+ | WOOHOO! You do not have any task on the list. |
+ ----------------------------------------------------------------------
+ -Wessy----------------------------------------------------------------
+ | Got it. I've added this task: |
+ | [T][ ] borrow book |
+ | Now you have 1 task in the list. |
+ ----------------------------------------------------------------------
+ -Wessy----------------------------------------------------------------
+ | Here are the tasks in your list: |
+ | 1.[T][ ] borrow book |
+ ----------------------------------------------------------------------
+ -Wessy----------------------------------------------------------------
+ | You have already marked this task as not done yet: |
+ | [T][ ] borrow book |
+ ----------------------------------------------------------------------
+ -Wessy----------------------------------------------------------------
+ | Nice! I've marked this task as done: |
+ | [T][X] borrow book |
+ ----------------------------------------------------------------------
+ -Wessy----------------------------------------------------------------
+ | Here are the tasks in your list: |
+ | 1.[T][X] borrow book |
+ ----------------------------------------------------------------------
+ -Wessy----------------------------------------------------------------
+ | Got it. I've added this task: |
+ | [D][ ] return book (by: Sunday) |
+ | Now you have 2 tasks in the list. |
+ ----------------------------------------------------------------------
+ -Wessy----------------------------------------------------------------
+ | Here are the tasks in your list: |
+ | 1.[T][X] borrow book |
+ | 2.[D][ ] return book (by: Sunday) |
+ ----------------------------------------------------------------------
+ -Wessy----------------------------------------------------------------
+ | Got it. I've added this task: |
+ | [E][ ] project meeting (from: Mon 2pm to: 4pm) |
+ | Now you have 3 tasks in the list. |
+ ----------------------------------------------------------------------
+ -Wessy----------------------------------------------------------------
+ | Here are the tasks in your list: |
+ | 1.[T][X] borrow book |
+ | 2.[D][ ] return book (by: Sunday) |
+ | 3.[E][ ] project meeting (from: Mon 2pm to: 4pm) |
+ ----------------------------------------------------------------------
+ -Wessy----------------------------------------------------------------
+ | Noted. I've removed this task: |
+ | [D][ ] return book (by: Sunday) |
+ | Now you have 2 tasks in the list. |
+ ----------------------------------------------------------------------
+ -Wessy----------------------------------------------------------------
+ | Here are the tasks in your list: |
+ | 1.[T][X] borrow book |
+ | 2.[E][ ] project meeting (from: Mon 2pm to: 4pm) |
+ ----------------------------------------------------------------------
+ -Wessy----------------------------------------------------------------
+ | Bye. Hope to see you again soon! |
+ ----------------------------------------------------------------------
diff --git a/text-ui-test/input.txt b/text-ui-test/input.txt
index e69de29bb2..98b75c3b16 100644
--- a/text-ui-test/input.txt
+++ b/text-ui-test/input.txt
@@ -0,0 +1,13 @@
+list
+todo borrow book
+list
+unmark 1
+mark 1
+list
+deadline return book /by Sunday
+list
+event project meeting /from Mon 2pm /to 4pm
+list
+delete 2
+list
+bye
\ No newline at end of file
diff --git a/text-ui-test/runtest.bat b/text-ui-test/runtest.bat
index 0873744649..76f08b7b8f 100644
--- a/text-ui-test/runtest.bat
+++ b/text-ui-test/runtest.bat
@@ -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 Wessy < 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 c9ec870033..5df2bda09a
--- a/text-ui-test/runtest.sh
+++ b/text-ui-test/runtest.sh
@@ -20,7 +20,7 @@ then
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 Wessy < input.txt > ACTUAL.TXT
# convert to UNIX format
cp EXPECTED.TXT EXPECTED-UNIX.TXT