diff --git a/.vscode/settings.json b/.vscode/settings.json
new file mode 100644
index 0000000000..fb3ce6c4ae
--- /dev/null
+++ b/.vscode/settings.json
@@ -0,0 +1,4 @@
+{
+ "java.configuration.updateBuildConfiguration": "interactive",
+ "java.checkstyle.configuration": "${workspaceFolder}/config/checkstyle/checkstyle.xml"
+}
\ No newline at end of file
diff --git a/build.gradle b/build.gradle
new file mode 100644
index 0000000000..e204db336e
--- /dev/null
+++ b/build.gradle
@@ -0,0 +1,69 @@
+plugins {
+ id 'java'
+ id 'application'
+ id 'com.github.johnrengelman.shadow' version '7.1.2'
+ id 'checkstyle'
+}
+
+repositories {
+ mavenCentral()
+}
+
+checkstyle {
+ toolVersion = '10.2'
+}
+
+java {
+ sourceCompatibility = JavaVersion.VERSION_11
+ targetCompatibility = JavaVersion.VERSION_11
+}
+
+dependencies {
+ testImplementation group: 'org.junit.jupiter', name: 'junit-jupiter-api', version: '5.10.0'
+ testRuntimeOnly group: 'org.junit.jupiter', name: 'junit-jupiter-engine', version: '5.10.0'
+
+ String javaFxVersion = '17.0.7'
+
+ 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 {
+ mainClass.set("Launcher")
+}
+
+shadowJar {
+ archiveFileName = "duke.jar"
+ archiveBaseName = "duke"
+ archiveClassifier = null
+ dependsOn("distZip", "distTar")
+}
+
+run{
+ standardInput = System.in
+ enableAssertions = true
+}
diff --git a/config/checkstyle/checkstyle.xml b/config/checkstyle/checkstyle.xml
new file mode 100644
index 0000000000..1b7cd0808a
--- /dev/null
+++ b/config/checkstyle/checkstyle.xml
@@ -0,0 +1,429 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/config/checkstyle/suppressions.xml b/config/checkstyle/suppressions.xml
new file mode 100644
index 0000000000..135ea49ee0
--- /dev/null
+++ b/config/checkstyle/suppressions.xml
@@ -0,0 +1,10 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/data/tasks.txt b/data/tasks.txt
new file mode 100644
index 0000000000..3202eea48b
--- /dev/null
+++ b/data/tasks.txt
@@ -0,0 +1,6 @@
+T | 1 | task1
+T | 0 | task2
+D | 0 | task3 | Oct. 10 2023
+D | 0 | task 4 | Jan. 1 2023
+T | 0 | task 5
+E | 0 | task 6 | Jan. 1 2023-Jan. 2 2023
diff --git a/docs/README.md b/docs/README.md
index 8077118ebe..2e47e39ee9 100644
--- a/docs/README.md
+++ b/docs/README.md
@@ -1,29 +1,181 @@
# User Guide
+DukeMax is your task management assistant.
+
+It is optimized with a Command Line Interface (CLI) and enjoying the benefits of a Graphical User Interface (GUI).
+
+Download and run the jar file as the following to use it!
+
+1. Copy the jar file into an empty folder.
+2. Open a command window in that folder.
+3. Run the command `java -jar duke.jar`
## Features
+### Introducing Tasks
+- There are 3 types of tasks: To-do, Deadline, and Event.
+
+- You can create, remove, mark, unmark, print, and find these tasks.
+
+## Usage - Commands
+
+### Quick Navigation
+1. Create Task Commands [Link](#create-related)
+2. Manage Task Commands [Link](#manage-related)
+3. View Task Commands [Link](#view-related)
+4. Exit Command [Link](#exit-command)
+> **Note**
+> All commands are NOT case-sensitive.
+
+---
+### Create-related
+### `todo`
+This command adds a Todo task.
+Format: `todo TASK_DESCRIPTION`
+* There is no time related to a todo task.
+
+Example: `todo task 1`
+
+Expected outcome:
+```
+Got it! This task has been added:
+[T-D][In progress] task 1
+Current # of task: 1
+```
+---
+### `deadline`
+This command adds a Deadline task.
+Format: `deadline TASK_DESCRIPTION /by DATE_TIME`
+* Deadline task has a task description and a deadline.
+* DATE_TIME format: `yyyy-MM-dd HH:mm` `yyyy-MM-dd` `HH:mm`.
+
+Example: `deadline task 2 /by: 2023-01-01`
+
+Expected outcome:
+```
+Got it! This task has been added:
+[DDL][In progress] task 2 (by: Jan. 1 2023)
+Current # of task: 2
+```
+---
+### `event`
+This command adds an Event task.
+
+Format: `event TASK_ESCRIPTION /from DATE_TIME /to DATE_TIME`
+* Event task has a task description, start time, and end time.
+* DATE_TIME format: `yyyy-MM-dd HH:mm` `yyyy-MM-dd` `HH:mm`.
+
+Example: `event task 3 /from 2023-01-01 /to 2023-01-02`
+
+Expected outcome:
+```
+Got it! This task has been added:
+[EVT][In progress] task 3 (from: Jan. 1 2023 to Jan. 2 2023)
+Current # of task: 3
+```
+---
+### Manage-related
+### `mark`
+This task marks a task as completed.
+
+Format: `mark TASK_INDEX`
+
+Example: `mark 1`
+Expected outcome:
+```
+Nice! I've marked this task as complete:
+[T-D][ Completed ] task 1
+Here's a lollipop.
+```
+---
+### `unmark`
+This command marks a task as incomplete.
-### Feature-ABC
+Format: `unmark TASK_INDEX`
-Description of the feature.
+Example: `unmark 1`
-### Feature-XYZ
+Expected outcome:
+```
+OK, I've marked this task as incomplete yet:
+[T-D][In progress] task 1
+Keep up with the good work.
+```
+---
+### `delete`
+This command deletes a task.
+> **Warning**
+> Tasks cannot be recovered after being cleared.
-Description of the feature.
+Format: `delete TASK_INDEX`
-## Usage
+Example: `delete 1`
-### `Keyword` - Describe action
+Expected outcome:
+```
+I've removed this task:
+[T-D][In progress] task 1
+Current # of tasks: 2
+```
+---
+### `clear`
+This command clears all tasks saved.
+> **Warning**
+> Tasks cannot be recovered after being cleared.
-Describe the action and its outcome.
+Example: `clear`
-Example of usage:
+Expected outcome:
+```
+Okay, I have cleared all tasks.
+```
+---
+### View-related
+### `print`
+This command lists all existing tasks
-`keyword (optional arguments)`
+Example: `print`
Expected outcome:
+```
+You have:
+1. [T-D][In progress] task 1
+2. [DDL][In progress] task 2 (by: Jan. 1 2023)
+3. [EVT][In progress] task 3 (from: Jan. 1 2023 to Jan. 2 2023)
+Current # of tasks: 3
+```
+---
+### `print DATE_TIME`
+This command prints tasks with matching dates.
+
+Format: `print DATE_TIME`
-Description of the outcome.
+Example: `print 2023-01-02`
+Expected outcome:
+```
+1. [DDL][In progress] task 2 (by: Jan. 1 2023)
+2. [EVT][In progress] task 3 (from: Jan. 1 2023 to Jan. 2 2023)
+Current # of tasks at Jan. 1 2023: 2
+```
+---
+### `find`
+This command prints tasks with matching task descriptions.
+
+Format: `find TASK_DESCRIPTION`
+
+Example: `find task 1`
+
+Expected outcome:
+```
+1. [T-D][In progress] task 1
+Current # of tasks with task 1: 1
+```
+---
+### Exit-Command
+### `bye`
+This command exits the chatbot
+Example: `bye`
+
+Expected outcome:
```
-expected output
+Bye! Hope to see you again soon!
```
diff --git a/docs/Ui.png b/docs/Ui.png
new file mode 100644
index 0000000000..2a4b9c3ecb
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..033e24c4cd
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..66c01cfeba
--- /dev/null
+++ b/gradle/wrapper/gradle-wrapper.properties
@@ -0,0 +1,7 @@
+distributionBase=GRADLE_USER_HOME
+distributionPath=wrapper/dists
+distributionUrl=https\://services.gradle.org/distributions/gradle-7.6.2-bin.zip
+networkTimeout=10000
+validateDistributionUrl=true
+zipStoreBase=GRADLE_USER_HOME
+zipStorePath=wrapper/dists
diff --git a/gradlew b/gradlew
new file mode 100755
index 0000000000..fcb6fca147
--- /dev/null
+++ b/gradlew
@@ -0,0 +1,248 @@
+#!/bin/sh
+
+#
+# Copyright © 2015-2021 the original 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 POSIX generated by Gradle.
+#
+# Important for running:
+#
+# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
+# noncompliant, but you have some other compliant shell such as ksh or
+# bash, then to run this script, type that shell name before the whole
+# command line, like:
+#
+# ksh Gradle
+#
+# Busybox and similar reduced shells will NOT work, because this script
+# requires all of these POSIX shell features:
+# * functions;
+# * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
+# «${var#prefix}», «${var%suffix}», and «$( cmd )»;
+# * compound commands having a testable exit status, especially «case»;
+# * various built-in commands including «command», «set», and «ulimit».
+#
+# Important for patching:
+#
+# (2) This script targets any POSIX shell, so it avoids extensions provided
+# by Bash, Ksh, etc; in particular arrays are avoided.
+#
+# The "traditional" practice of packing multiple parameters into a
+# space-separated string is a well documented source of bugs and security
+# problems, so this is (mostly) avoided, by progressively accumulating
+# options in "$@", and eventually passing that to Java.
+#
+# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
+# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
+# see the in-line comments for details.
+#
+# There are tweaks for specific operating systems such as AIX, CygWin,
+# Darwin, MinGW, and NonStop.
+#
+# (3) This script is generated from the Groovy template
+# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
+# within the Gradle project.
+#
+# You can find Gradle at https://github.com/gradle/gradle/.
+#
+##############################################################################
+
+# Attempt to set APP_HOME
+
+# Resolve links: $0 may be a link
+app_path=$0
+
+# Need this for daisy-chained symlinks.
+while
+ APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path
+ [ -h "$app_path" ]
+do
+ ls=$( ls -ld "$app_path" )
+ link=${ls#*' -> '}
+ case $link in #(
+ /*) app_path=$link ;; #(
+ *) app_path=$APP_HOME$link ;;
+ esac
+done
+
+# This is normally unused
+# shellcheck disable=SC2034
+APP_BASE_NAME=${0##*/}
+APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit
+
+# Use the maximum available, or set MAX_FD != -1 to use that value.
+MAX_FD=maximum
+
+warn () {
+ echo "$*"
+} >&2
+
+die () {
+ echo
+ echo "$*"
+ echo
+ exit 1
+} >&2
+
+# 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 ;; #(
+ MSYS* | 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
+ if ! command -v java >/dev/null 2>&1
+ then
+ 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
+fi
+
+# Increase the maximum file descriptors if we can.
+if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
+ case $MAX_FD in #(
+ max*)
+ # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.
+ # shellcheck disable=SC3045
+ MAX_FD=$( ulimit -H -n ) ||
+ warn "Could not query maximum file descriptor limit"
+ esac
+ case $MAX_FD in #(
+ '' | soft) :;; #(
+ *)
+ # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
+ # shellcheck disable=SC3045
+ ulimit -n "$MAX_FD" ||
+ warn "Could not set maximum file descriptor limit to $MAX_FD"
+ esac
+fi
+
+# Collect all arguments for the java command, stacking in reverse order:
+# * args from the command line
+# * the main class name
+# * -classpath
+# * -D...appname settings
+# * --module-path (only if needed)
+# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
+
+# For Cygwin or MSYS, switch paths to Windows format before running java
+if "$cygwin" || "$msys" ; then
+ APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
+ CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" )
+
+ JAVACMD=$( cygpath --unix "$JAVACMD" )
+
+ # Now convert the arguments - kludge to limit ourselves to /bin/sh
+ for arg do
+ if
+ case $arg in #(
+ -*) false ;; # don't mess with options #(
+ /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath
+ [ -e "$t" ] ;; #(
+ *) false ;;
+ esac
+ then
+ arg=$( cygpath --path --ignore --mixed "$arg" )
+ fi
+ # Roll the args list around exactly as many times as the number of
+ # args, so each arg winds up back in the position where it started, but
+ # possibly modified.
+ #
+ # NB: a `for` loop captures its iteration list before it begins, so
+ # changing the positional parameters here affects neither the number of
+ # iterations, nor the values presented in `arg`.
+ shift # remove old arg
+ set -- "$@" "$arg" # push replacement arg
+ done
+fi
+
+
+# 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"'
+
+# Collect all arguments for the java command;
+# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of
+# shell script including quotes and variable substitutions, so put them in
+# double quotes to make sure that they get re-expanded; and
+# * put everything else in single quotes, so that it's not re-expanded.
+
+set -- \
+ "-Dorg.gradle.appname=$APP_BASE_NAME" \
+ -classpath "$CLASSPATH" \
+ org.gradle.wrapper.GradleWrapperMain \
+ "$@"
+
+# Stop when "xargs" is not available.
+if ! command -v xargs >/dev/null 2>&1
+then
+ die "xargs is not available"
+fi
+
+# Use "xargs" to parse quoted args.
+#
+# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
+#
+# In Bash we could simply go:
+#
+# readarray ARGS < <( xargs -n1 <<<"$var" ) &&
+# set -- "${ARGS[@]}" "$@"
+#
+# but POSIX shell has neither arrays nor command substitution, so instead we
+# post-process each arg (as a line of input to sed) to backslash-escape any
+# character that might be a shell metacharacter, then use eval to reverse
+# that process (while maintaining the separation between arguments), and wrap
+# the whole thing up as a single "set" statement.
+#
+# This will of course break if any of these variables contains a newline or
+# an unmatched quote.
+#
+
+eval "set -- $(
+ printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
+ xargs -n1 |
+ sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
+ tr '\n' ' '
+ )" '"$@"'
+
+exec "$JAVACMD" "$@"
diff --git a/gradlew.bat b/gradlew.bat
new file mode 100644
index 0000000000..6689b85bee
--- /dev/null
+++ b/gradlew.bat
@@ -0,0 +1,92 @@
+@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=.
+@rem This is normally unused
+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% equ 0 goto execute
+
+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 execute
+
+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
+
+: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 %*
+
+:end
+@rem End local scope for the variables with windows NT shell
+if %ERRORLEVEL% equ 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!
+set EXIT_CODE=%ERRORLEVEL%
+if %EXIT_CODE% equ 0 set EXIT_CODE=1
+if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
+exit /b %EXIT_CODE%
+
+:mainEnd
+if "%OS%"=="Windows_NT" endlocal
+
+:omega
diff --git a/src/main/java/DialogBox.java b/src/main/java/DialogBox.java
new file mode 100644
index 0000000000..caec846582
--- /dev/null
+++ b/src/main/java/DialogBox.java
@@ -0,0 +1,67 @@
+import java.io.IOException;
+import java.util.Collections;
+
+import javafx.collections.FXCollections;
+import javafx.collections.ObservableList;
+import javafx.fxml.FXML;
+import javafx.fxml.FXMLLoader;
+import javafx.geometry.Pos;
+import javafx.scene.Node;
+import javafx.scene.image.Image;
+import javafx.scene.image.ImageView;
+import javafx.scene.layout.HBox;
+import javafx.scene.text.Text;
+import javafx.scene.text.TextAlignment;
+/**
+ * An example of a custom control using FXML.
+ * This control represents a dialog box consisting of an ImageView to represent the speaker's face and a label
+ * containing text from the speaker.
+ */
+public class DialogBox extends HBox {
+ @FXML
+ private Text dialog;
+ @FXML
+ private ImageView displayPicture;
+
+ private DialogBox(String text, Image img) {
+ try {
+ FXMLLoader fxmlLoader = new FXMLLoader(MainWindow.class.getResource("view/DialogBox.fxml"));
+ fxmlLoader.setController(this);
+ fxmlLoader.setRoot(this);
+ fxmlLoader.load();
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+
+ dialog.setText(text);
+ dialog.setTextAlignment(TextAlignment.RIGHT);
+ displayPicture.setImage(img);
+ }
+
+ /**
+ * Flips the dialog box such that the ImageView is on the left and text on the right.
+ */
+ private void flip() {
+ ObservableList tmp = FXCollections.observableArrayList(this.getChildren());
+ Collections.reverse(tmp);
+ getChildren().setAll(tmp);
+ setAlignment(Pos.TOP_LEFT);
+ dialog.setTextAlignment(TextAlignment.LEFT);
+ }
+
+ public static DialogBox getUserDialog(String text, Image img) {
+ return new DialogBox(text, img);
+ }
+
+ public static DialogBox getDukeDialog(String text, Image img) {
+ var db = new DialogBox(text, img);
+ db.flip();
+ return db;
+ }
+
+ public static DialogBox getDukeGreet(String text, Image img) {
+ var db = new DialogBox(text, img);
+ db.flip();
+ return db;
+ }
+}
diff --git a/src/main/java/Duke.java b/src/main/java/Duke.java
index 5d313334cc..cf72f8fab9 100644
--- a/src/main/java/Duke.java
+++ b/src/main/java/Duke.java
@@ -1,10 +1,81 @@
+import java.util.Scanner;
+
+import duke.helper.DukeException;
+import duke.helper.Parser;
+import duke.helper.Ui;
+import duke.storage.Storage;
+import duke.storage.TaskList;
+
+/**
+ * Duke class
+ */
public class Duke {
- public static void main(String[] args) {
- String logo = " ____ _ \n"
- + "| _ \\ _ _| | _____ \n"
- + "| | | | | | | |/ / _ \\\n"
- + "| |_| | |_| | < __/\n"
- + "|____/ \\__,_|_|\\_\\___|\n";
- System.out.println("Hello from\n" + logo);
+ public static final Scanner SCANNER = new Scanner(System.in);
+ public static final String FILEPATH = "./data/tasks.txt";
+
+ private TaskList taskList;
+
+ /**
+ * Constructs the Duke class.
+ *
+ * @param path the path to store the user's task list
+ */
+ public Duke(String path) {
+ Storage.setPath(path);
+ try {
+ taskList = new TaskList(Storage.load());
+ } catch (DukeException e) {
+ Ui.print("Unable to load tasks");
+ taskList = new TaskList();
+ } finally {
+ Parser.setTaskList(taskList);
+ }
+ }
+
+ /**
+ * Get chatbot responce for user input.
+ *
+ * @param input the user input
+ * @return the chatbot's response
+ */
+ public String getResponse(String input) {
+ try {
+ String response = Parser.parse(input);
+ return response;
+ } catch (DukeException e) {
+ return e.getMessage();
+ }
+ }
+
+ /**
+ * Greet method for Duke.
+ *
+ * @return the chatbot's response
+ */
+ public String greet() {
+ return Ui.greet();
+ }
+
+ /**
+ * Main method for the chatbot. Initiate a Duke-Max instance.
+ *
+ * @param args arguments
+ */
+ public static void main(String[] args) throws DukeException {
+ new Duke("data/tasks.txt");
+ Ui.greet();
+ String input = SCANNER.nextLine();
+
+ while (!input.equals("bye")) {
+ try {
+ Parser.parse(input);
+ } catch (DukeException e) {
+ Ui.print(e.toString());
+ } finally {
+ input = SCANNER.nextLine();
+ }
+ }
+ Ui.exit();
+ SCANNER.close();
}
}
diff --git a/src/main/java/Launcher.java b/src/main/java/Launcher.java
new file mode 100644
index 0000000000..43d64c26fb
--- /dev/null
+++ b/src/main/java/Launcher.java
@@ -0,0 +1,10 @@
+import javafx.application.Application;
+
+/**
+ * A launcher class to workaround classpath issues.
+ */
+public class Launcher {
+ public static void main(String[] args) {
+ Application.launch(Main.class, args);
+ }
+}
diff --git a/src/main/java/Main.java b/src/main/java/Main.java
new file mode 100644
index 0000000000..e9ea475dd8
--- /dev/null
+++ b/src/main/java/Main.java
@@ -0,0 +1,30 @@
+import java.io.IOException;
+
+import javafx.application.Application;
+import javafx.fxml.FXMLLoader;
+import javafx.scene.Scene;
+import javafx.scene.layout.AnchorPane;
+import javafx.stage.Stage;
+
+/**
+ * A GUI for Duke using FXML.
+ */
+public class Main extends Application {
+
+ private Duke duke = new Duke("data/tasks.txt");
+
+ @Override
+ public void start(Stage stage) {
+ try {
+ FXMLLoader fxmlLoader = new FXMLLoader(Main.class.getResource("/view/MainWindow.fxml"));
+ AnchorPane ap = fxmlLoader.load();
+ Scene scene = new Scene(ap);
+ stage.setScene(scene);
+ stage.setResizable(false);
+ fxmlLoader.getController().setDuke(duke);
+ stage.show();
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ }
+}
diff --git a/src/main/java/MainWindow.java b/src/main/java/MainWindow.java
new file mode 100644
index 0000000000..a5de74c83d
--- /dev/null
+++ b/src/main/java/MainWindow.java
@@ -0,0 +1,57 @@
+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;
+/**
+ * Controller for MainWindow. Provides the layout for the other controls.
+ */
+public class MainWindow extends AnchorPane {
+ @FXML
+ private ScrollPane scrollPane;
+ @FXML
+ private VBox dialogContainer;
+ @FXML
+ private TextField userInput;
+ @FXML
+ private Button sendButton;
+
+ private Duke duke;
+
+ private Image userImage = new Image(this.getClass().getResourceAsStream("/images/DaUser.png"));
+ private Image dukeImage = new Image(this.getClass().getResourceAsStream("/images/DaDuke.png"));
+
+ @FXML
+ public void initialize() {
+ scrollPane.vvalueProperty().bind(dialogContainer.heightProperty());
+ }
+
+ public void setDuke(Duke d) {
+ duke = d;
+ String greet = duke.greet();
+ dialogContainer.getChildren().addAll(
+ DialogBox.getDukeGreet(greet, dukeImage)
+ );
+ }
+
+ /**
+ * Creates two dialog boxes, one echoing user input and the other containing Duke's reply and then appends them to
+ * the dialog container. Clears the user input after processing.
+ */
+ @FXML
+ private void handleUserInput() {
+ String input = userInput.getText();
+ String response = duke.getResponse(input);
+ dialogContainer.getChildren().addAll(
+ DialogBox.getUserDialog(input, userImage),
+ DialogBox.getDukeDialog(response, dukeImage)
+ );
+ userInput.clear();
+ if (input.equals("bye")) {
+ userInput.setDisable(true);
+ sendButton.setDisable(true);
+ }
+ }
+}
diff --git a/src/main/java/duke/helper/DukeException.java b/src/main/java/duke/helper/DukeException.java
new file mode 100644
index 0000000000..d7d123bb9a
--- /dev/null
+++ b/src/main/java/duke/helper/DukeException.java
@@ -0,0 +1,36 @@
+package duke.helper;
+
+/**
+ * Duke Exception class
+ */
+public class DukeException extends Exception {
+
+ /**
+ * Constucts the DukeException class.
+ *
+ * @param message the message of the exception
+ */
+ public DukeException(String message) {
+ super(message);
+ }
+
+ /**
+ * returns the detail message of this throwable
+ *
+ * @return the message of the exception
+ */
+ @Override
+ public String getMessage() {
+ return ":( Oh no! " + super.getMessage();
+ }
+
+ /**
+ * returns the detail message of this throwable
+ *
+ * @return the message of the exception
+ */
+ @Override
+ public String toString() {
+ return ":( Oh no! " + super.getMessage();
+ }
+}
diff --git a/src/main/java/duke/helper/Parser.java b/src/main/java/duke/helper/Parser.java
new file mode 100644
index 0000000000..4a6f7810e7
--- /dev/null
+++ b/src/main/java/duke/helper/Parser.java
@@ -0,0 +1,437 @@
+package duke.helper;
+
+import java.time.LocalDate;
+import java.time.LocalDateTime;
+import java.time.LocalTime;
+import java.time.format.DateTimeFormatter;
+import java.time.format.DateTimeParseException;
+
+import duke.storage.TaskList;
+import duke.tasks.Deadline;
+import duke.tasks.Event;
+import duke.tasks.Task;
+import duke.tasks.ToDo;
+
+/**
+ * Parser class for taking in user input and analyzing it
+ */
+public class Parser {
+ private static TaskList taskList;
+
+ /**
+ * initializes the task list
+ *
+ * @param tasks the task list to be initialized
+ */
+ public static void setTaskList(TaskList tasks) {
+ taskList = tasks;
+ }
+
+ /**
+ * categorises command based on user input
+ *
+ * @param input the user input
+ * @return the bot response
+ */
+ public static String parse(String input) throws DukeException {
+ String command = input.split("\\s")[0].toUpperCase();
+ String content = input.replace(input.split("\\s")[0], "");
+ switch(command) {
+ case "BYE":
+ return parseBye(content);
+ case "CLEAR":
+ return parseClear(content);
+ case "DELETE":
+ return parseDelete(content);
+ case "MARK":
+ return parseMark(content);
+ case "UNMARK":
+ return parseUnmark(content);
+ case "FIND":
+ return parseFind(content);
+ case "PRINT":
+ return parsePrint(content);
+ case "TODO":
+ return parseTodo(content);
+ case "EVENT":
+ return parseEvent(content);
+ case "DEADLINE":
+ return parseDeadline(content);
+ default:
+ throw new DukeException("Sorry, I don't recognize this command." + "\n" + "Please try again.");
+ }
+ }
+
+ /**
+ * executes the bye command and checks for exceptions
+ *
+ * @param content the message of the command
+ * @return the bot response
+ */
+ private static String parseBye(String content) throws DukeException {
+ if (content.isBlank() || content.isEmpty() || content == null) {
+ return Ui.exit();
+ } else {
+ throw new DukeException("Sorry, I don't recognize this command." + "\n" + "Please try again.");
+ }
+ }
+
+ /**
+ * executes the clear command and checks for exceptions
+ *
+ * @param content the message of the command
+ * @return the bot response
+ */
+ private static String parseClear(String content) throws DukeException {
+ if (content.isBlank() || content.isEmpty() || content == null) {
+ return taskList.clear();
+ } else {
+ throw new DukeException("Sorry, I don't recognize this command." + "\n" + "Please try again.");
+ }
+ }
+
+ /**
+ * executes the delete command and checks for exceptions
+ *
+ * @param content the message of the command
+ * @return the bot response
+ */
+ private static String parseDelete(String content) throws DukeException {
+ if (content.isBlank() || content.isEmpty() || content == null) {
+ throw new DukeException("Please specify the item number you wish to delete.");
+ }
+ int deleteItem = -1;
+ try {
+ deleteItem = Integer.valueOf(content.substring(1));
+ } catch (NumberFormatException e) {
+ throw new DukeException("Please specify the item number you wish to delete.");
+ }
+ if (deleteItem <= 0 || deleteItem > taskList.size()) {
+ throw new DukeException("Please specify a task that exist.");
+ }
+ return taskList.delete(deleteItem);
+ }
+
+ /**
+ * executes the mark command and checks for exceptions
+ *
+ * @param content the message of the command
+ */
+ private static String parseMark(String content) throws DukeException {
+ if (content.isBlank() || content.isEmpty() || content == null) {
+ throw new DukeException("Please specify the item number you wish to mark.");
+ }
+ int markItem = -1;
+ try {
+ markItem = Integer.valueOf(content.substring(1));
+ } catch (NumberFormatException e) {
+ throw new DukeException("Please specify the item number you wish to mark.");
+ }
+ if (markItem <= 0 || markItem > taskList.size()) {
+ throw new DukeException("Please specify a task that exist.");
+ }
+ return taskList.mark(markItem, true);
+ }
+
+ /**
+ * executes the unmark command and checks for exceptions
+ *
+ * @param content the message of the command
+ * @return the bot response
+ */
+ private static String parseUnmark(String content) throws DukeException {
+ if (content.isBlank() || content.isEmpty() || content == null) {
+ throw new DukeException("Please specify the item number you wish to unmark.");
+ }
+ int unmarkItem = -1;
+ try {
+ unmarkItem = Integer.valueOf(content.substring(1));
+ } catch (NumberFormatException e) {
+ throw new DukeException("Please specify the item number you wish to unmark.");
+ }
+ if (unmarkItem <= 0 || unmarkItem > taskList.size()) {
+ throw new DukeException("Please specify the item number you wish to unmark.");
+ }
+ return taskList.mark(unmarkItem, false);
+ }
+
+ /**
+ * executes the find command and checks for exceptions
+ *
+ * @param content the message of the command
+ * @return the bot response
+ */
+ private static String parseFind(String content) throws DukeException {
+ if (content.isBlank() || content.isEmpty() || content == null) {
+ throw new DukeException("Please specify the keyword you wish to find.");
+ }
+ String keyword = content.substring(1);
+ return taskList.find(keyword);
+ }
+
+ /**
+ * executes the print command and checks for exceptions
+ *
+ * @param content the message of the command
+ * @return the bot response
+ */
+ private static String parsePrint(String content) throws DukeException {
+ if (content.isBlank() || content.isEmpty() || content == null) {
+ return taskList.print();
+ }
+ String time = formatTime(content.substring(1));
+ if (time == null) {
+ String messageSorry = "Sorry, you have entered the wrong format for time.";
+ String messageFormat = "Please enter in the format of yyyy-MM-dd HH:mm:ss or yyyy-MM-dd";
+ throw new DukeException(messageSorry + "\n" + messageFormat);
+ }
+ return taskList.print(time);
+ }
+
+ /**
+ * executes the todo command and checks for exceptions
+ *
+ * @param content the message of the command
+ * @return the bot response
+ */
+ private static String parseTodo(String content) throws DukeException {
+ if (content.isBlank() || content.isEmpty() || content == null) {
+ throw new DukeException("Sorry, the todo task must have a title.");
+ }
+ Task todoTask = new ToDo(content.substring(1));
+ return taskList.add(todoTask);
+ }
+
+ /**
+ * executes the event command and checks for exceptions
+ *
+ * @param content the message of the command
+ * @return the bot response
+ */
+ private static String parseEvent(String content) throws DukeException {
+ //when user didn't provide title and start & end time
+ if (content.isBlank() || content.isEmpty()
+ || !content.contains(" /from ") || !content.contains(" /to ") || content == null) {
+ throw new DukeException("Sorry, this event must have a title, start time, and end time.");
+ }
+ String[] event = new String[3];
+ try {
+ event[0] = content.substring(1, content.indexOf(" /"));
+ event[1] = content.substring(content.indexOf("/from") + 6,
+ content.indexOf(" /to"));
+ event[2] = content.substring(content.indexOf("/to") + 4);
+ } catch (StringIndexOutOfBoundsException e) {
+ return ("Sorry, this event must have a title, start time, and end time.");
+ }
+ //when user provide empty title
+ if (event[0].isBlank() || event[0].isEmpty() || event[0] == null) {
+ throw new DukeException("Sorry, the event must have a title.");
+ }
+ //when user didn't provide the starting time
+ if (event[1].isBlank() || event[1].isEmpty() || event[1] == null) {
+ throw new DukeException("Sorry, the event must have a starting time");
+ }
+ //when user didn't provide the end time
+ if (event[2].isBlank() || event[2].isEmpty() || event[2] == null) {
+ throw new DukeException("Sorry, the event must have an end time");
+ }
+ //when user provide wrong time format
+ String startTime = formatTime(event[1]);
+ String endTime = formatTime(event[2]);
+ if (startTime == null || endTime == null) {
+ throw new DukeException("Please enter the time with this format: yyyy-MM-dd HH:mm:ss");
+ }
+ Task eventTask = new Event(event[0], startTime, endTime);
+ return taskList.add(eventTask);
+ }
+
+ /**
+ * executes the deadline command and checks for exceptions
+ *
+ * @param content the message of the command
+ * @return the bot response
+ */
+ private static String parseDeadline(String content) throws DukeException {
+ if (content.isBlank() || content.isEmpty() || !content.contains(" /by ") || content == null) {
+ throw new DukeException("Sorry, the deadline task must have a title and a deadline.");
+ }
+ String[] ddl = new String[2];
+ try {
+ ddl = content.split(" /by ");
+ ddl[0] = ddl[0].substring(1);
+ } catch (ArrayIndexOutOfBoundsException e) {
+ return "Sorry, this deadline task must have a title and a deadline.";
+ }
+ //when user provide empty title
+ if (ddl[0].isBlank() || ddl[0].isEmpty() || ddl[0] == null) {
+ throw new DukeException("Sorry, the deadline event must have a title.");
+ }
+ //when user didn't provide ddl
+ if (ddl[1].isBlank() || ddl[1].isEmpty() || ddl[1] == null) {
+ throw new DukeException("Sorry, the deadline event must have a dealine");
+ }
+ String ddlTime = formatTime(ddl[1]);
+ //when user provide wrong time format
+ if (ddlTime == null) {
+ throw new DukeException("Please enter the time with this format: yyyy-MM-dd HH:mm:ss");
+ }
+ Task ddlTask = new Deadline(ddl[0], ddlTime);
+ return taskList.add(ddlTask);
+ }
+
+ /**
+ * format date-time string input into another date-time format
+ *
+ * @param input the message to be translated into another date-time format
+ * @return the reformatted time string
+ */
+ // @@author infiBeyond-reused
+ // Reused from Chen Qun's iP changeTimeFormat method with minor modifications
+ // Link to Chen Qun's implementation:
+ // https://github.com/jean-cq/ip/blob/master/src/main/java/urchatbot/parser/Parser.java
+ // changeTimeFormat method
+ private static String formatTime(String input) {
+ try {
+ // when input have a date and a time
+ // convert string to LocalDateTime then convert to another format
+ DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
+ LocalDateTime dateTime = LocalDateTime.parse(input, formatter);
+ return dateTime.format(DateTimeFormatter.ofPattern("MMM d yyyy HH:mm"));
+ } catch (DateTimeParseException notDateTime) {
+ try {
+ // input is only a date
+ LocalDate date = LocalDate.parse(input);
+ return date.format(DateTimeFormatter.ofPattern("MMM d yyyy"));
+ } catch (DateTimeParseException notDate) {
+ try {
+ // input is only a time
+ LocalTime time = LocalTime.parse(input);
+ return time.format(DateTimeFormatter.ofPattern("HH:mm"));
+ } catch (DateTimeParseException nullError) {
+ return null;
+ }
+ }
+ }
+ }
+ // @@author
+
+ /**
+ * dummy method for testing parse
+ *
+ * @param input the user input
+ * @return the detected command
+ */
+ public static String[] parseTest(String input) throws DukeException {
+ String testCommand = input.split("\\s")[0].toUpperCase();
+ String testContent = input.replace(input.split("\\s")[0], "");
+ String task = "";
+ String[] result = new String[2];
+ result[0] = testCommand;
+ switch(testCommand) {
+ case "TODO":
+ task = testTodo(testContent);
+ result[1] = task;
+ return result;
+ case "EVENT":
+ task = testEvent(testContent);
+ result[1] = task;
+ return result;
+ case "DEADLINE":
+ task = testDeadline(testContent);
+ result[1] = task;
+ return result;
+ default:
+ throw new DukeException("Sorry, I don't recognize this command." + "\n" + "Please try again.");
+ }
+ }
+
+ /**
+ * dummy method for testing parse todo
+ *
+ * @param content the user input
+ * @return the todo task detected
+ */
+ public static String testTodo(String content) throws DukeException {
+ if (content.isBlank() || content.isEmpty() || content == null) {
+ throw new DukeException("Sorry, the todo task must have a title.");
+ }
+ ToDo todoTask = new ToDo(content.substring(1));
+ return todoTask.getStatus();
+ }
+
+ /**
+ * dummy method for testing parse event
+ *
+ * @param content the user input
+ * @return the event task detected
+ */
+ public static String testEvent(String content) throws DukeException {
+ if (content.isBlank() || content.isEmpty()
+ || !content.contains(" /from ") || !content.contains(" /to ") || content == null) {
+ throw new DukeException("Sorry, this event must have a title, start time, and end time.");
+ }
+ String[] event = new String[3];
+ try {
+ event[0] = content.substring(1, content.indexOf(" /"));
+ event[1] = content.substring(content.indexOf("/from") + 6,
+ content.indexOf(" /to"));
+ event[2] = content.substring(content.indexOf("/to") + 4);
+ } catch (StringIndexOutOfBoundsException e) {
+ return ("Sorry, this event must have a title, start time, and end time.");
+ }
+ //when user provide empty title
+ if (event[0].isBlank() || event[0].isEmpty() || event[0] == null) {
+ throw new DukeException("Sorry, the event must have a title.");
+ }
+ //when user didn't provide the starting time
+ if (event[1].isBlank() || event[1].isEmpty() || event[1] == null) {
+ throw new DukeException("Sorry, the event must have a starting time");
+ }
+ //when user didn't provide the end time
+ if (event[2].isBlank() || event[2].isEmpty() || event[2] == null) {
+ throw new DukeException("Sorry, the event must have an end time");
+ }
+ //when user provide wrong time format
+ String startTime = formatTime(event[1]);
+ String endTime = formatTime(event[2]);
+ if (startTime == null || endTime == null) {
+ throw new DukeException("Please enter the time with this format: yyyy-MM-dd HH:mm:ss");
+ }
+ Event eventTask = new Event(event[0], startTime, endTime);
+ return eventTask.getStatus();
+ }
+
+ /**
+ * dummy method for testing parse deadline
+ *
+ * @param content the user input
+ * @return the deadline task detected
+ */
+ public static String testDeadline(String content) throws DukeException {
+ if (content.isBlank() || content.isEmpty() || !content.contains(" /by ") || content == null) {
+ throw new DukeException("Sorry, the deadline task must have a title and a deadline.");
+ }
+ String[] ddl = new String[2];
+ try {
+ ddl = content.split(" /by ");
+ ddl[0] = ddl[0].substring(1);
+ } catch (ArrayIndexOutOfBoundsException e) {
+ return "Sorry, this deadline task must have a title and a deadline.";
+ }
+ //when user provide empty title
+ if (ddl[0].isBlank() || ddl[0].isEmpty() || ddl[0] == null) {
+ throw new DukeException("Sorry, the deadline event must have a title.");
+ }
+ //when user didn't provide ddl
+ if (ddl[1].isBlank() || ddl[1].isEmpty() || ddl[1] == null) {
+ throw new DukeException("Sorry, the deadline event must have a dealine");
+ }
+ String ddlTime = formatTime(ddl[1]);
+ //when user provide wrong time format
+ if (ddlTime == null) {
+ throw new DukeException("Please enter the time with this format: yyyy-MM-dd HH:mm:ss");
+ }
+ Deadline ddlTask = new Deadline(ddl[0], ddlTime);
+ return ddlTask.getStatus();
+ }
+}
diff --git a/src/main/java/duke/helper/Ui.java b/src/main/java/duke/helper/Ui.java
new file mode 100644
index 0000000000..65071c59f1
--- /dev/null
+++ b/src/main/java/duke/helper/Ui.java
@@ -0,0 +1,52 @@
+package duke.helper;
+
+/**
+ * Ui class
+ */
+public class Ui {
+ private static final String NAME = "Duke Max";
+
+ /**
+ * construct the UI class
+ *
+ * @return the bot greeting message
+ */
+ public static String greet() {
+ String[] messageList = {("Hello! I'm " + NAME + "."), ("What can I do for you?")};
+ return print(messageList);
+ }
+
+ /**
+ * executes the exit command and prints the exit message
+ *
+ * @return the bot response
+ */
+ public static String exit() {
+ return print("Bye. Hope to see you again soon!");
+ }
+
+ /**
+ * print the given message
+ *
+ * @param message the message to be printed
+ * @return the message input
+ */
+ public static String print(String message) {
+ return message;
+ }
+
+ /**
+ * print an array of list in order
+ *
+ * @param messageList the array of messages to be printed
+ * @return the message inputs with line breaks
+ */
+ public static String print(String[] messageList) {
+ String response = messageList[0] + "\n";
+ for (int i = 1; i < messageList.length; i++) {
+ response = response + messageList[i] + "\n";
+ }
+ return response;
+ }
+}
+
diff --git a/src/main/java/duke/storage/Storage.java b/src/main/java/duke/storage/Storage.java
new file mode 100644
index 0000000000..307069bc41
--- /dev/null
+++ b/src/main/java/duke/storage/Storage.java
@@ -0,0 +1,99 @@
+package duke.storage;
+
+import java.io.BufferedReader;
+import java.io.FileReader;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.ArrayList;
+
+import duke.helper.DukeException;
+import duke.helper.Ui;
+import duke.tasks.Task;
+
+/**
+ * Storage class for import and export between txt file and TaskList.
+ */
+// Solution below adapted by
+// https://github.com/jean-cq/ip/blob/master/src/main/java/urchatbot/storage/Storage.java
+public class Storage {
+ private static String path;
+ private static ArrayList taskList = new ArrayList();
+
+ /**
+ * set the file path to the given path
+ *
+ * @param filePath the given file path
+ */
+ public static void setPath(String filePath) {
+ path = filePath;
+ }
+
+ /**
+ * save the arraylist of tasks in txt format
+ *
+ * @param tasks the arraylist of tasks to be saved
+ */
+ public static void save(ArrayList tasks) {
+ try (PrintWriter printwriter = new PrintWriter(new FileWriter(path))) {
+ for (Task task : tasks) {
+ printwriter.write(task.toFile() + "\n");
+ }
+ } catch (IOException e) {
+ System.out.println("There is an error saving this file: " + e.getMessage());
+ }
+ }
+
+ /**
+ * load tasks in txt format into an arraylist
+ *
+ * @return an arraylist translated from the txt file
+ */
+ // @@author infiBeyond-reused
+ // Reused from Chen Qun's iP load method with minor modifications
+ // Link to Chen Qun's implementation:
+ // https://github.com/jean-cq/ip/blob/master/src/main/java/urchatbot/storage/Storage.java
+ // load method
+ public static ArrayList load() throws DukeException {
+ handleMissing(path);
+
+ try (BufferedReader reader = new BufferedReader(new FileReader(path))) {
+ String text = reader.readLine();
+ while (text != null) {
+ Task task = Task.convertStringToTask(text);
+ taskList.add(task);
+ text = reader.readLine();
+ }
+ } catch (IOException e) {
+ Ui.print("There is an error loading tasks from file: " + e.toString());
+ }
+ return taskList;
+ }
+ // @@author
+
+ /**
+ * handles missing txt file by creating a new one
+ *
+ * @param testPath the file path to test if exist
+ */
+ private static void handleMissing(String testPath) {
+ try {
+ //if directory or path doesn't exist
+ Path directoryPath = Paths.get(".", "data");
+ if (!Files.exists(directoryPath)) {
+ Files.createDirectories(directoryPath);
+ }
+
+ Path path = directoryPath.resolve("tasks.txt");
+ if (!Files.exists(path)) {
+ Files.createFile(path);
+ }
+ } catch (IOException e) {
+ e.printStackTrace();
+ System.out.println("There is an error loading file: " + e.getMessage());
+ }
+ }
+}
diff --git a/src/main/java/duke/storage/TaskList.java b/src/main/java/duke/storage/TaskList.java
new file mode 100644
index 0000000000..473beff95a
--- /dev/null
+++ b/src/main/java/duke/storage/TaskList.java
@@ -0,0 +1,196 @@
+package duke.storage;
+
+import java.util.ArrayList;
+
+import duke.helper.Ui;
+import duke.tasks.Task;
+
+/**
+ * TskList that runs task-related commands and stores task in an arrayList.
+ */
+public class TaskList {
+ private static ArrayList taskList;
+
+ /**
+ * constuct the TaskList by initializing the taskList
+ */
+ public TaskList() {
+ taskList = new ArrayList<>();
+ }
+
+ /**
+ * constuct the TaskList by initializing the taskList
+ * with a given arraylist
+ *
+ * @param tasks the given arraylist
+ */
+ public TaskList(ArrayList tasks) {
+ taskList = tasks;
+ }
+
+ /**
+ * execute the clear command
+ *
+ * @return the bot response
+ */
+ public String clear() {
+ taskList.clear();
+ Storage.save(taskList);
+ return Ui.print("Okay, I have cleared all tasks.");
+ }
+
+ /**
+ * execute the delete command
+ *
+ * @param num the index of the task the user want to delete
+ * @return the bot response
+ */
+ public String delete(int num) {
+ String taskNumber = "Current # of " + plural(taskList.size() - 1, "task") + ": " + (taskList.size() - 1);
+ String[] messageList = {"I've removed this task:", taskList.get(num - 1).getStatus(), taskNumber};
+ taskList.remove(num - 1);
+ Storage.save(taskList);
+ return Ui.print(messageList);
+ }
+
+ /**
+ * execute the mark command
+ *
+ * @param num the index of the task the user want to mark
+ * @param isDone a boolean value that indicated whether the task is done or not
+ * @return the bot response
+ */
+ public String mark(int num, boolean isDone) {
+ String message = taskList.get(num - 1).markItem(isDone);
+ Storage.save(taskList);
+ return message;
+ }
+
+ /**
+ * execute find command by printing task with corresponding keyword
+ *
+ * @param keyword the keyword to search for in the tasks
+ * @return the bot response
+ */
+ public String find(String keyword) {
+ int index = 1;
+ String message = "";
+ for (Task task: taskList) {
+ if (task.getTask().contains(keyword)) {
+ message = message + (index + ". " + task.getStatus()) + "\n";
+ index++;
+ }
+ }
+ index -= 1;
+ if (index == 0) {
+ return Ui.print("There is no task with keyword: " + keyword);
+ } else {
+ message = message + ("Current # of " + plural(index, "task") + " with " + keyword + ": " + index);
+ return Ui.print(message);
+ }
+ }
+
+ /**
+ * execute print command by printing all tasks
+ *
+ * @return the bot response
+ */
+ public String print() {
+ if (taskList.size() == 0) {
+ return Ui.print("There is currently no task. Keep the good work going!");
+ }
+ int index = 1;
+ String message = "You have: " + "\n";
+ for (Task task: taskList) {
+ message = message + (index + ". " + task.getStatus()) + "\n";
+ index++;
+ }
+ message = message + ("Current # of " + plural(taskList.size(), "task") + ": " + taskList.size());
+ return Ui.print(message);
+ }
+
+ /**
+ * execute the print command by printing task with given time
+ *
+ * @param time the time of tasks the user want to see
+ * @return the bot response
+ */
+ public String print(String time) {
+ int index = 1;
+ String message = "";
+ for (Task task: taskList) {
+ if (task.getTime() != null && task.getTime().contains(time)) {
+ message = message + (index + ". " + task.getStatus()) + "\n";
+ index++;
+ }
+ }
+ index -= 1;
+ if (index == 0) {
+ return Ui.print("There is no task at " + time);
+ } else {
+ message = message + ("Current # of " + plural(index, "task") + " at " + time + ": " + index);
+ return Ui.print(message);
+ }
+ }
+
+ /**
+ * execute the add command
+ *
+ * @param input the task to add to the taskList
+ * @return the bot response
+ */
+ public String add(Task input) {
+ String taskDescription = input.getTask();
+ for (Task task: taskList) {
+ if (task.getTask().equals(taskDescription)) {
+ String duplicateWarning = "Oh no! This task already exists.";
+ String[] messageList = {duplicateWarning, task.getStatus(), "Please check your input again."};
+ return Ui.print(messageList);
+ }
+ }
+ taskList.add(input);
+ Storage.save(taskList);
+ String taskNumber = "Current # of " + plural(taskList.size(), "task") + ": " + taskList.size();
+ String[] messageList = {("Got it! This task has been added: "), (input.getStatus()), taskNumber};
+ return Ui.print(messageList);
+ }
+
+ /**
+ * dummy test method for add command
+ *
+ * @param input the task to add to the taskList
+ * @return the bot response
+ */
+ public String addTest(Task input) {
+ taskList.add(input);
+ String taskNumber = "Current # of " + plural(taskList.size(), "task") + ": " + taskList.size();
+ String[] messageList = {("Got it! This task has been added: "), (input.getStatus()), taskNumber};
+ return Ui.print(messageList);
+ }
+
+ /**
+ * returns the size ot the taskList
+ *
+ * @return the number of tasks in the taskList
+ */
+ public int size() {
+ return taskList.size();
+ }
+
+ /**
+ * return the task by the given index if it exists
+ *
+ * @param num the index of the task
+ * @return the task by the given index
+ */
+ public Task get(int num) {
+ if (num >= 0 && num < taskList.size()) {
+ return taskList.get(num);
+ }
+ return null;
+ }
+
+ private static String plural(int count, String word) {
+ return (count <= 1) ? word : (word + "s");
+ }
+}
diff --git a/src/main/java/duke/tasks/Deadline.java b/src/main/java/duke/tasks/Deadline.java
new file mode 100644
index 0000000000..7ac9ce0aeb
--- /dev/null
+++ b/src/main/java/duke/tasks/Deadline.java
@@ -0,0 +1,46 @@
+package duke.tasks;
+
+/**
+* Deadline class with due time
+*/
+public class Deadline extends Task {
+ private String deadline;
+
+ /**
+ * constructs the deadline class
+ *
+ * @param task the description of the task
+ * @param deadline the deadline of the task
+ */
+ public Deadline(String task, String deadline) {
+ super(task);
+ this.deadline = deadline;
+ }
+
+ /**
+ * returns the status of the deadline task
+ *
+ * @return a formatted string of the status of the deadline
+ */
+ @Override
+ public String getStatus() {
+ String time = "(by: " + deadline + ")";
+ return "[DDL]" + super.getStatus() + " " + time;
+ }
+
+ /**
+ * returns the deadline of the deadline task
+ *
+ * @return the deadline
+ */
+ @Override
+ public String getTime() {
+ return deadline;
+ }
+
+ @Override
+ public String toFile() {
+ return super.isDone ? ("D | 1 | " + super.task + " | " + this.deadline)
+ : ("D | 0 | " + super.task + " | " + this.deadline);
+ }
+}
diff --git a/src/main/java/duke/tasks/Event.java b/src/main/java/duke/tasks/Event.java
new file mode 100644
index 0000000000..a018acbe93
--- /dev/null
+++ b/src/main/java/duke/tasks/Event.java
@@ -0,0 +1,49 @@
+package duke.tasks;
+
+/**
+* Event class with start and end time
+*/
+public class Event extends Task {
+ private String from;
+ private String to;
+
+ /**
+ * constructs the event class
+ *
+ * @param task the description of the task
+ * @param from the begin time of the task
+ * @param to the end time of the task
+ */
+ public Event(String task, String from, String to) {
+ super(task);
+ this.from = from;
+ this.to = to;
+ }
+
+ /**
+ * returns the status of the event task
+ *
+ * @return a formatted string of the status of the event
+ */
+ @Override
+ public String getStatus() {
+ String time = "(from: " + from + " to: " + to + ")";
+ return "[EVT]" + super.getStatus() + " " + time;
+ }
+
+ /**
+ * returns the begin and end time of the event task
+ *
+ * @return a string containing the begin and end time
+ */
+ @Override
+ public String getTime() {
+ return this.from + " " + this.to;
+ }
+
+ @Override
+ public String toFile() {
+ return super.isDone ? ("E | 1 | " + super.task + " | " + this.from + "-" + this.to)
+ : ("E | 0 | " + super.task + " | " + this.from + "-" + this.to);
+ }
+}
diff --git a/src/main/java/duke/tasks/Task.java b/src/main/java/duke/tasks/Task.java
new file mode 100644
index 0000000000..625c325efb
--- /dev/null
+++ b/src/main/java/duke/tasks/Task.java
@@ -0,0 +1,102 @@
+package duke.tasks;
+
+import duke.helper.Ui;
+
+/**
+* Task class with task description and completeness indicator.
+*/
+public class Task {
+ protected String task;
+ protected boolean isDone;
+
+ /**
+ * constructs the task class
+ *
+ * @param task the description of the task
+ */
+ public Task(String task) {
+ this.task = task;
+ this.isDone = false;
+ }
+
+ /**
+ * getter method that return the description of the task
+ *
+ * @return the description of the task
+ */
+ public String getTask() {
+ return this.task;
+ }
+
+ /**
+ * returns the status of the task
+ *
+ * @return a formatted string of the status of the task
+ */
+ public String getStatus() {
+ String status = "[" + (isDone ? " Completed " : "In Progress") + "]";
+ return status + " " + this.getTask();
+ }
+
+ public String getTime() {
+ return null;
+ }
+
+ /**
+ * setter method for the task status
+ *
+ * @param isDone new status of the tasks
+ * @return the bot response
+ */
+ public String markItem(Boolean isDone) {
+ this.isDone = isDone;
+ if (this.isDone) {
+ String greet = "Nice! I've marked this task as complete:";
+ String encouragement = "Here's a lollipop.";
+ String[] messageList = {greet, this.getStatus(), encouragement};
+ return Ui.print(messageList);
+ } else {
+ String greet = "OK, I've marked this task as incomplete yet:";
+ String encouragement = "Keep up with the good work.";
+ String[] messageList = {greet, this.getStatus(), encouragement};
+ return Ui.print(messageList);
+ }
+ }
+
+ public String toFile() {
+ return task;
+ }
+
+ /**
+ * translate text input to task instance
+ *
+ * @param text record from the txt file
+ * @return a Task instance in accordance to the input text
+ */
+ public static Task convertStringToTask(String text) {
+ String[] tasks = text.split("\\|");
+ String type = tasks[0].trim();
+ boolean isCompleted = tasks[1].trim().equals("1");
+ String task = tasks[2].trim();
+
+ if (type.equals("T")) {
+ ToDo temp = new ToDo(task);
+ temp.markItem(isCompleted);
+ return temp;
+ } else if (type.equals("D")) {
+ String ddl = tasks[3].trim();
+ Deadline temp = new Deadline(task, ddl);
+ temp.markItem(isCompleted);
+ return temp;
+ } else if (type.equals("E")) {
+ String[] timeDuration = tasks[3].trim().split("-");
+ String from = timeDuration[0].trim();
+ String to = timeDuration[1].trim();
+ Event temp = new Event(task, from, to);
+ temp.markItem(isCompleted);
+ return temp;
+ } else {
+ return null;
+ }
+ }
+}
diff --git a/src/main/java/duke/tasks/ToDo.java b/src/main/java/duke/tasks/ToDo.java
new file mode 100644
index 0000000000..6448b9db88
--- /dev/null
+++ b/src/main/java/duke/tasks/ToDo.java
@@ -0,0 +1,31 @@
+package duke.tasks;
+
+/**
+* ToDo class
+*/
+public class ToDo extends Task {
+
+ /**
+ * construct the todo class
+ *
+ * @param task the description of the todo
+ */
+ public ToDo(String task) {
+ super(task);
+ }
+
+ /**
+ * returns the status of the todo task
+ *
+ * @return a formatted string of the status of the todo
+ */
+ @Override
+ public String getStatus() {
+ return "[T - D]" + super.getStatus();
+ }
+
+ @Override
+ public String toFile() {
+ return super.isDone ? ("T | 1 | " + super.task) : ("T | 0 | " + super.task);
+ }
+}
diff --git a/src/main/resources/META-INF/MANIFEST.MF b/src/main/resources/META-INF/MANIFEST.MF
new file mode 100644
index 0000000000..37197ef4e8
--- /dev/null
+++ b/src/main/resources/META-INF/MANIFEST.MF
@@ -0,0 +1,3 @@
+Manifest-Version: 1.0
+Main-Class: Main
+
diff --git a/src/main/resources/images/DaDuke.png b/src/main/resources/images/DaDuke.png
new file mode 100644
index 0000000000..5bce7f6f2a
Binary files /dev/null and b/src/main/resources/images/DaDuke.png differ
diff --git a/src/main/resources/images/DaUser.png b/src/main/resources/images/DaUser.png
new file mode 100644
index 0000000000..310dc2dfc0
Binary files /dev/null and b/src/main/resources/images/DaUser.png differ
diff --git a/src/main/resources/view/DialogBox.fxml b/src/main/resources/view/DialogBox.fxml
new file mode 100644
index 0000000000..97ad9963d5
--- /dev/null
+++ b/src/main/resources/view/DialogBox.fxml
@@ -0,0 +1,29 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/main/resources/view/MainWindow.fxml b/src/main/resources/view/MainWindow.fxml
new file mode 100644
index 0000000000..caa3a26b6d
--- /dev/null
+++ b/src/main/resources/view/MainWindow.fxml
@@ -0,0 +1,29 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/test/java/duke/ParserTest.java b/src/test/java/duke/ParserTest.java
new file mode 100644
index 0000000000..2ebff824ac
--- /dev/null
+++ b/src/test/java/duke/ParserTest.java
@@ -0,0 +1,40 @@
+package duke;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+import org.junit.jupiter.api.Test;
+
+import duke.helper.DukeException;
+import duke.helper.Parser;
+
+public class ParserTest {
+ @Test
+ public void todoTest() throws DukeException {
+ String input = "todo task 1";
+ String[] result = Parser.parseTest(input);
+ String command = result[0];
+ String todo = result[1];
+ assertEquals(command, "TODO");
+ assertEquals(todo, "[T - D][In Progress] task 1");
+ }
+
+ @Test
+ public void deadlineTest() throws DukeException {
+ String input = "deadline task 2 /by 2023-01-01";
+ String[] result = Parser.parseTest(input);
+ String command = result[0];
+ String deadline = result[1];
+ assertEquals(command, "DEADLINE");
+ assertEquals(deadline, "[DDL][In Progress] task 2 (by: Jan. 1 2023)");
+ }
+
+ @Test
+ public void eventTest() throws DukeException {
+ String input = "event task 3 /from 2023-01-01 /to 2023-01-02";
+ String[] result = Parser.parseTest(input);
+ String command = result[0];
+ String event = result[1];
+ assertEquals(command, "EVENT");
+ assertEquals(event, "[EVT][In Progress] task 3 (from: Jan. 1 2023 to: Jan. 2 2023)");
+ }
+}
diff --git a/src/test/java/duke/TodoTest.java b/src/test/java/duke/TodoTest.java
new file mode 100644
index 0000000000..fc15f97949
--- /dev/null
+++ b/src/test/java/duke/TodoTest.java
@@ -0,0 +1,22 @@
+package duke;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+import org.junit.jupiter.api.Test;
+
+import duke.storage.TaskList;
+import duke.tasks.Task;
+import duke.tasks.ToDo;
+
+public class TodoTest {
+ @Test
+ public void addTodoTest() {
+ TaskList taskList = new TaskList();
+ Task testToDo = new ToDo("test1");
+ taskList.addTest(testToDo);
+ String status = "[T - D][In Progress] test1";
+ assertEquals(1, taskList.size());
+ assertEquals(status, taskList.get(0).getStatus());
+ }
+}
+
diff --git a/text-ui-test/runtest.sh b/text-ui-test/runtest.sh
old mode 100644
new mode 100755