diff --git a/README.md b/README.md
deleted file mode 100644
index 8715d4d915..0000000000
--- a/README.md
+++ /dev/null
@@ -1,24 +0,0 @@
-# Duke project template
-
-This is a project template for a greenfield Java project. It's named after the Java mascot _Duke_. Given below are instructions on how to use it.
-
-## Setting up in Intellij
-
-Prerequisites: JDK 11, update Intellij to the most recent version.
-
-1. Open Intellij (if you are not in the welcome screen, click `File` > `Close Project` to close the existing project first)
-1. Open the project into Intellij as follows:
- 1. Click `Open`.
- 1. Select the project directory, and click `OK`.
- 1. If there are any further prompts, accept the defaults.
-1. Configure the project to use **JDK 11** (not other versions) as explained in [here](https://www.jetbrains.com/help/idea/sdk.html#set-up-jdk).
- In the same dialog, set the **Project language level** field to the `SDK default` option.
-3. After that, locate the `src/main/java/Duke.java` file, right-click it, and choose `Run Duke.main()` (if the code editor is showing compile errors, try restarting the IDE). If the setup is correct, you should see something like the below as the output:
- ```
- Hello from
- ____ _
- | _ \ _ _| | _____
- | | | | | | | |/ / _ \
- | |_| | |_| | < __/
- |____/ \__,_|_|\_\___|
- ```
diff --git a/build.gradle b/build.gradle
new file mode 100644
index 0000000000..93ac77cbd0
--- /dev/null
+++ b/build.gradle
@@ -0,0 +1,67 @@
+plugins {
+ id 'java'
+ id 'checkstyle'
+ id 'application'
+ id 'com.github.johnrengelman.shadow' version '7.1.2'
+ id 'org.openjfx.javafxplugin' version '0.0.13'
+}
+
+checkstyle {
+ toolVersion = '10.2'
+}
+
+repositories {
+ mavenCentral()
+}
+
+dependencies {
+ implementation 'com.google.code.gson:gson:2.10.1'
+ 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'
+
+ // JavaFX 17.0.7 dependencies for macOS on ARM64
+ implementation 'org.openjfx:javafx-base:17.0.7:mac-aarch64'
+ implementation 'org.openjfx:javafx-controls:17.0.7:mac-aarch64'
+ implementation 'org.openjfx:javafx-fxml:17.0.7:mac-aarch64'
+ implementation 'org.openjfx:javafx-graphics:17.0.7:mac-aarch64'
+
+ // JavaFX 17.0.7 dependencies for Linux
+ implementation 'org.openjfx:javafx-base:17.0.7:linux'
+ implementation 'org.openjfx:javafx-controls:17.0.7:linux'
+ implementation 'org.openjfx:javafx-fxml:17.0.7:linux'
+ implementation 'org.openjfx:javafx-graphics:17.0.7:linux'
+
+ // JavaFX 17.0.7 dependencies for Windows
+ implementation 'org.openjfx:javafx-base:17.0.7:win'
+ implementation 'org.openjfx:javafx-controls:17.0.7:win'
+ implementation 'org.openjfx:javafx-fxml:17.0.7:win'
+ implementation 'org.openjfx:javafx-graphics:17.0.7:win'
+}
+
+test {
+ useJUnitPlatform()
+
+ testLogging {
+ events "passed", "skipped", "failed"
+
+ showExceptions true
+ exceptionFormat "full"
+ showCauses true
+ showStackTraces true
+ showStandardStreams = false
+ }
+}
+
+application {
+ mainClass.set("woofwoof.Launcher")
+}
+
+shadowJar {
+ archiveBaseName = "woofwoof"
+ archiveClassifier = null
+ dependsOn("distZip", "distTar")
+}
+
+run{
+ standardInput = System.in
+}
\ No newline at end of file
diff --git a/config/checkstyle/checkstyle.xml b/config/checkstyle/checkstyle.xml
new file mode 100644
index 0000000000..d732949de8
--- /dev/null
+++ b/config/checkstyle/checkstyle.xml
@@ -0,0 +1,435 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ 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/docs/GFMDPractice.md b/docs/GFMDPractice.md
new file mode 100644
index 0000000000..d2eecaa2e8
--- /dev/null
+++ b/docs/GFMDPractice.md
@@ -0,0 +1,74 @@
+>THIS DOCUMENT WAS CREATED SOLELY FOR PRACTICE PURPOSES.
+
+# Duke project
+## Hello from
+```
+ ██╗██╗███╗ ██╗ ██████╗ ███████╗██╗ ██╗███████╗███╗ ██╗ ██████╗
+ ██║██║████╗ ██║██╔════╝ ██╔════╝██║ ██║██╔════╝████╗ ██║██╔════╝
+ ██║██║██╔██╗ ██║██║ ███╗ ███████╗███████║█████╗ ██╔██╗ ██║██║ ███╗
+ ██ ██║██║██║╚██╗██║██║ ██║ ╚════██║██╔══██║██╔══╝ ██║╚██╗██║██║ ██║
+ ╚█████╔╝██║██║ ╚████║╚██████╔╝ ███████║██║ ██║███████╗██║ ╚████║╚██████╔╝
+ ╚════╝ ╚═╝╚═╝ ╚═══╝ ╚═════╝ ╚══════╝╚═╝ ╚═╝╚══════╝╚═╝ ╚═══╝ ╚═════╝
+```
+## Project Description
+This is my project, created from a template for a greenfield Java project.
+It's named after the Java mascot _Duke_.
+This project is a Java application that aims to provide a user-friendly task management system. It allows users to manage their tasks efficiently and keep track of their schedules.
+
+## Features
+
+- **Task Creation**: Users can add various types of tasks, including todo, deadline, and event tasks.
+- **Task List**: View a list of all tasks, including their descriptions and completion status.
+- **Task Completion**: Mark tasks as complete when they are done.
+- **Task Deletion**: Remove tasks that are no longer needed.
+- **Task Searching**: Search for tasks based on keywords.
+- **Data Storage**: Data is automatically saved and loaded for seamless usage.
+
+## Installation Option 1
+
+1. Go to tjingsheng ip [releases](https://github.com/tjingsheng/ip/releases)
+2. **Get Java**: Ensure you have a functioning version of Java.
+3. **Copy the JAR File**: Copy the JAR file you want to run into an empty folder.
+4. **Open a Command Window**: Open a command prompt (Windows) or terminal (macOS/Linux) in the same folder where you copied the JAR file.
+5. **Run the Command**: Use the following command to run the JAR file:
+6. **Enjoy**: Duke is very fun.
+
+ ```shell
+ java -jar duke.jar
+
+## Installation Option 2
+
+1. Clone the tjingsheng ip [repository](https://github.com/tjingshengg/ip) to your local machine.
+2. Open the project in your preferred Java development environment.
+3. Build and run the application.
+4. Enjoy
+
+## Todo List
+
+- [x] Task 1 : Get woof
+- [ ] Task 2 : Experiment with it
+- [ ] Task 3 : Profit ???
+- [ ] Task 4 : ~~Cry~~
+
+## Project Sample
+```
+ /**
+ * Runs the Duke application, allowing the user to interact with it via the command line.
+ */
+ public static void runDuke() {
+ Scanner scanner = new Scanner(System.in);
+ Ui.helloWorld();
+ boolean isConversing = true;
+ while (isConversing) {
+ String rawCommand = Ui.getUserInput(scanner);
+ TaskList taskList = TaskFileHandler.readFromFile();
+ Ui.getBotMessage();
+ Command command = Parser.parse(rawCommand);
+ command.execute(taskList);
+ TaskFileHandler.saveToFile(taskList);
+ isConversing = !command.isByeCommand();
+ }
+ }
+```
+
+>>>>> "Duke hehe Duke hehe" - Jing Sheng
diff --git a/docs/README.md b/docs/README.md
index 8077118ebe..ee23528eb9 100644
--- a/docs/README.md
+++ b/docs/README.md
@@ -1,29 +1,262 @@
-# User Guide
+# Woof Woof Bot User Guide
-## Features
+Welcome to WoofWoof Bot, your personal task manager! This bot can help you keep track of your to-do tasks, deadlines, events, and more.
-### Feature-ABC
+![woofwoof1.png](woofwoof1.png)
+![woofwoof2.png](woofwoof2.png)
+![woofwoof3.png](woofwoof3.png)
+![woofwoof4.png](woofwoof4.png)
-Description of the feature.
+## Table of Contents
+- [Purpose of the User Guide](#purpose-of-the-user-guide)
+- [Getting Started](#getting-started)
+ - [Installation Option 1](#installation-option-1)
+ - [Installation Option 2](#installation-option-2)
+- [Quick Start Commands](#quick-start-commands)
+ - [Todo](#1-todo)
+ - [Deadline](#2-deadline)
+ - [Event](#3-event)
+ - [Mark](#4-mark)
+ - [Unmark](#5-unmark)
+ - [Delete](#6-delete)
+ - [List](#7-list)
+ - [Find](#8-find)
+ - [Sort](#9-sort)
+ - [Bye](#10-bye)
+ - [Help](#11-help)
+- [Notes](#notes)
-### Feature-XYZ
+## Purpose of the User Guide
-Description of the feature.
+This User Guide serves as your go-to resource for understanding and effectively using WoofWoof Bot. Whether you're new to the bot or just need a quick refresher, you'll find everything you need right here.
-## Usage
+## Getting Started
-### `Keyword` - Describe action
+Before you dive into the nitty-gritty of task management, it's essential to set up WoofWoof Bot correctly. You have two installation options to choose from:
-Describe the action and its outcome.
+### Installation Option 1
-Example of usage:
+1. **Go to** tjingsheng ip [releases](https://github.com/tjingsheng/ip/releases)
+2. **Get Java**: Ensure you have a functioning version of Java.
+3. **Copy the JAR File**: Copy the JAR file you want to run into an empty folder.
+4. **Open a Command Window**: Open a command prompt (Windows) or terminal (macOS/Linux) in the same folder where you copied the JAR file.
+5. **Run the Command**: Use the following command to run the JAR file:\
+ Run the GUI version: ```java -jar woofwoof```\
+ Run the CLI version: ```java -jar woofwoof cli```
+6. **Enjoy**: WoofWoof is very fun.
-`keyword (optional arguments)`
+### Installation Option 2
-Expected outcome:
+1. Clone the tjingsheng ip [repository](https://github.com/tjingsheng/ip) to your local machine.
+2. Open the project in your preferred Java development environment.
+3. Go to the target main directory:\
+ GUI version: ```\main\java\WoofWoof\Launcher```\
+ CLI version: ```\main\java\Woof```
+5. Run the main class
+4. Enjoy
-Description of the outcome.
+## Quick Start Commands
+### 1. Todo
+Add a simple to-do task.
+>Format: `todo `\
+>Example: `todo study`
+
+Expected Output:
+```
+Got it. I've added this task:
+ [T][ ] study
+You have 1 tasks in the task list.
+```
+
+### 2. Deadline
+Add a task with a deadline.
+>Format: `deadline /by `\
+>Example: `deadline assignment /by 2023-12-31`
+
+Expected Output:
+```
+Doggo:
+Got it. I've added this task:
+ [D][ ] assignment
+ ~By: 31 Dec 2023
+You have 2 tasks in the task list.
+```
+
+### 3. Event
+Add an event with a start and end date.
+>Format: `event /from /to `\
+>Example: `event enjoy /from 2023-11-25 /to 2024-01-14`
+
+Expected Output:
+```
+Got it. I've added this task:
+ [E][ ] enjoy
+ ~From: 25 Nov 2023
+ ~To : 14 Jan 2024
+You have 3 tasks in the task list.
+```
+
+### 4. Mark
+Mark a task as completed.
+>Format: `mark `\
+>Example: `mark 2`
+
+Expected Output:
+```
+Ok! I've marked this task as done:
+ [D][X] assignment
+ ~By: 31 Dec 2023
+You have 3 tasks in the task list.
+```
+
+
+### 5. Unmark
+Unmark a completed task as incomplete.
+>Format: `unmark `\
+>Example: `unmark 2`
+
+Expected Output:
+```
+Ok! I've marked this task as undone:
+ [D][ ] assignment
+ ~By: 31 Dec 2023
+You have 3 tasks in the task list.
+```
+
+### 6. Delete
+Delete a task.
+>Format: `delete `\
+>Example: `delete 2`
+
+Expected Output:
+```
+Ok! I've deleted this task:
+ [D][ ] assignment
+ ~By: 31 Dec 2023
+You have 1 tasks in the task list.
+```
+
+### 7. List
+View all your tasks.
+>Format: `list`
+>Example: `list`
+
+Expected Output:
+```
+Here are all the tasks in your list:
+ 1. [T][ ] study
+ 2. [E][ ] enjoy
+ ~From: 25 Nov 2023
+ ~To : 14 Jan 2024
+You have 2 tasks in the task list.
+```
+
+### 8. Find
+Search for tasks containing n keywords.
+>Format: `find ... `\
+>Example: `find study`
+
+Expected Output:
```
-expected output
+Here are your matching tasks in your list:
+ 1. [T][ ] study
+You have 2 tasks in the task list.
```
+
+### 9. Sort
+Sort tasks by date and time.
+>Format: `sort`
+>Example: `sort`
+
+Expected Output
+```
+Your tasks have been sorted:
+ 1. [E][ ] enjoy
+ ~From: 25 Nov 2023
+ ~To : 14 Jan 2024
+ 2. [T][ ] study
+You have 2 tasks in the task list.
+```
+
+### 10. Bye
+Say goodbye to WoofWoof Bot.
+>Format: `bye`
+>Example: `bye`
+
+Expected Output
+```
+Bye. Hope to see you again soon!
+Offing myself... woof :(
+```
+
+### 11. Help
+Request help on how to use the bot.
+>Format: `help`
+>Example: `help`
+
+Expected Output
+```
+Woof! Woof! Here to help:
+
+Available Commands:
+
+1. Todo : Add a simple to-do task.
+ Format : `todo `
+ Example : `todo study`
+
+2. Deadline : Add a task with a deadline.
+ Format : `deadline /by `
+ Example : `deadline assignment /by 2023-12-31`
+
+3. Event : Add an event with a start and end date.
+ Format : `event /from /to `
+ Example : `event enjoy /from 2023-11-25 /to 2024-01-14`
+
+4. Mark : Mark a task as completed.
+ Format : `mark `
+ Example : `mark 1`
+
+5. Unmark : Unmark a completed task as incomplete.
+ Format : `unmark `
+ Example : `unmark 1`
+
+6. Delete : Delete a task.
+ Format : `delete `
+ Example : `delete 2`
+
+7. List : View all your tasks.
+ Format : `list`
+
+8. Find : Search for tasks containing n keywords.
+ Format : `find ... `
+ Example : `find meeting project`
+
+9. Sort : Sort tasks by date and time.
+ Format : `sort`
+
+10. Bye : Say goodbye to WoofWoof Bot.
+ Format : `bye`
+
+11. Help : Request help on how to use the bot.
+ Format : `help`
+
+Notes:
+- Replace `` with a task description.
+- `` should follow the yyyy-mm-dd date format.
+ e.g. 2023-12-31
+- `` should be the index of the task in the list
+ you want to manage.
+
+Feel free to ask for help using the `help` command if you
+have any questions or encounter issues.
+Woof Woof is here to make your life easier!
+```
+
+### Notes
+- Replace `` with a brief task description.
+- `` should follow the yyyy-mm-dd date format (e.g., 2023-12-31).
+- `` is the index of the task in the list you want to manage.
+
+That's it! You're now ready to start using WoofWoof Bot to stay organised and manage your tasks efficiently.
+
diff --git a/docs/Ui.png b/docs/Ui.png
new file mode 100644
index 0000000000..f3226de8a9
Binary files /dev/null and b/docs/Ui.png differ
diff --git a/docs/woofwoof1.png b/docs/woofwoof1.png
new file mode 100644
index 0000000000..7cf3b3baa5
Binary files /dev/null and b/docs/woofwoof1.png differ
diff --git a/docs/woofwoof2.png b/docs/woofwoof2.png
new file mode 100644
index 0000000000..53c85101fe
Binary files /dev/null and b/docs/woofwoof2.png differ
diff --git a/docs/woofwoof3.png b/docs/woofwoof3.png
new file mode 100644
index 0000000000..f3e7b64a81
Binary files /dev/null and b/docs/woofwoof3.png differ
diff --git a/docs/woofwoof4.png b/docs/woofwoof4.png
new file mode 100644
index 0000000000..30c5544b2c
Binary files /dev/null and b/docs/woofwoof4.png differ
diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties
new file mode 100644
index 0000000000..62f495dfed
--- /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-8.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/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/command/ByeCommand.java b/src/main/java/command/ByeCommand.java
new file mode 100644
index 0000000000..1ebe1b1aaa
--- /dev/null
+++ b/src/main/java/command/ByeCommand.java
@@ -0,0 +1,58 @@
+package command;
+
+import enums.CommandWord;
+import enums.WoofMessage;
+import exceptions.WoofInvalidCommandException;
+import parser.Parser;
+import tasks.TaskList;
+
+/**
+ * The `ByeCommand` class represents a command to exit the application.
+ * When executed, it displays a bye message to the user.
+ */
+public class ByeCommand extends Command {
+
+ /**
+ * Constructs a new `ByeCommand` with the specified raw command string.
+ *
+ * @param rawCommand The raw command string.
+ */
+ public ByeCommand(String rawCommand) {
+ super(rawCommand);
+ }
+
+ /**
+ * Validates the `ByeCommand`.
+ * It checks if the command is correctly formatted and if the specified task index is valid.
+ *
+ * @param rawCommand The raw command string.
+ * @throws WoofInvalidCommandException If the command is invalid, it throws a woof invalid command exception with an
+ * error message.
+ */
+ public static void validate(String rawCommand) {
+ assert rawCommand != null : "raw command cannot be null";
+
+ String[] args = Parser.getArgs(rawCommand);
+ validateArgsLengthEquals(CommandWord.BYE, args, 1);
+ validateNotNullArgs(CommandWord.BYE, args);
+ validateNotEmptyArgs(CommandWord.BYE, args);
+ validateCommandWord(CommandWord.BYE, args[0]);
+ }
+
+ /**
+ * Executes the `ByeCommand`. It shows the bye message to the user.
+ *
+ * @param taskList The task list (not used in this command).
+ */
+ public String execute(TaskList taskList) {
+ assert taskList != null : "task list cannot be null";
+
+ try {
+ validate(super.getRawCommand());
+ } catch (WoofInvalidCommandException e) {
+ return e.getMessage();
+ }
+
+ return WoofMessage.BYE.toFormattedValue();
+ }
+}
diff --git a/src/main/java/command/Command.java b/src/main/java/command/Command.java
new file mode 100644
index 0000000000..246e602f72
--- /dev/null
+++ b/src/main/java/command/Command.java
@@ -0,0 +1,269 @@
+package command;
+
+import java.time.LocalDate;
+
+import enums.CommandWord;
+import enums.ExceptionMessage;
+import exceptions.WoofException;
+import exceptions.WoofInvalidCommandException;
+import parser.Parser;
+import tasks.TaskList;
+import woof.Woof;
+/**
+ * The `Command` class is an abstract class representing a command in the application.
+ * All specific command classes should inherit from this class and implement the `execute` method.
+ */
+public abstract class Command {
+ /**
+ * The raw command entered by the user.
+ */
+ private final String rawCommand;
+
+ /**
+ * Constructs a new `Command` with the specified raw command string.
+ *
+ * @param rawCommand The raw command string.
+ */
+ public Command(String rawCommand) {
+ assert rawCommand != null : "raw command cannot be null";
+
+ this.rawCommand = rawCommand;
+ }
+
+ /**
+ * Executes the command. Subclasses must implement this method to define their behavior.
+ *
+ * @param taskList The task list to perform the command on.
+ */
+ public abstract String execute(TaskList taskList);
+
+ /**
+ * Parses a date string received by the application using the input formatter.
+ *
+ * @param string The date string to be parsed.
+ * @return The parsed LocalDate.
+ */
+ public static LocalDate parseDateTimeIn(String string) {
+ return Woof.parseDateTimeIn(string);
+ }
+
+ /**
+ * Gets the raw command entered by the user.
+ *
+ * @return The raw command string.
+ */
+ public String getRawCommand() {
+ return this.rawCommand;
+ }
+
+ /**
+ * Checks if the command is a "bye" command.
+ *
+ * @return `true` if it's a "bye" command, `false` otherwise.
+ */
+ public boolean isByeCommand() {
+ return CommandWord.commandWordToValueMap(Parser.getArgs(rawCommand)[0]).equals(CommandWord.BYE);
+ }
+
+ /**
+ * Validates the length of the input string array `args` against the specified `length`. If the length of the array
+ * does not match the expected length, a `WoofInvalidCommandException` is thrown with a message indicating the
+ * invalid number of arguments for the given `commandWord`.
+ *
+ * @param commandWord The command word associated with the operation for which the arguments are being validated.
+ * @param args The input string array to be validated.
+ * @param correctLength The expected length of the `args` array.
+ *
+ * @throws WoofInvalidCommandException If the length of `args` does not match the expected `length`.
+ */
+ public static void validateArgsLengthEquals(CommandWord commandWord, String[] args, int correctLength) {
+ assert commandWord != null : "command word cannot be null";
+ assert args != null : "args cannot be null";
+ assert correctLength >= 0 : "correct length must more than or equals to 0";
+
+ if (args.length != correctLength) {
+ throw new WoofInvalidCommandException(
+ ExceptionMessage.INVALID_NUMBER_OF_ARGUMENTS.toFormattedValue(
+ commandWord.toValue()
+ )
+ );
+ }
+ }
+
+ /**
+ * Validates the length of the input string array `args` against the specified `length`. If the length
+ * of the array matches the unexpected length, a `WoofInvalidCommandException` is thrown with
+ * a message indicating the invalid number of arguments for the given `commandWord`.
+ *
+ * @param commandWord The command word associated with the operation for which the arguments are being
+ * validated.
+ * @param args The input string array to be validated.
+ * @param unexpectedLength The expected length of the `args` array.
+ *
+ * @throws WoofInvalidCommandException If the length of `args` does not match the expected `length`.
+ */
+ public static void validateArgsLengthNotEquals(CommandWord commandWord, String[] args, int unexpectedLength) {
+ assert commandWord != null : "command word cannot be null";
+ assert args != null : "args cannot be null";
+
+ if (args.length == unexpectedLength) {
+ throw new WoofInvalidCommandException(
+ ExceptionMessage.INVALID_NUMBER_OF_ARGUMENTS.toFormattedValue(
+ commandWord.toValue()
+ )
+ );
+ }
+ }
+
+ /**
+ * Validates the length of the input string array `args` against the specified `length`. If the length
+ * of the array less than (not more than equal) to the expected length, a `WoofInvalidCommandException` is thrown
+ * with a message indicating the invalid number of arguments for the given `commandWord`.
+ *
+ * @param commandWord The command word associated with the operation for which the arguments are being validated.
+ * @param args The input string array to be validated.
+ * @param minLength The minimium length of the `args` array.
+ *
+ * @throws WoofInvalidCommandException If the length of `args` does not match the expected `length`.
+ */
+ public static void validateArgsLengthMoreThanEquals(CommandWord commandWord, String[] args, int minLength) {
+ assert commandWord != null : "command word cannot be null";
+ assert args != null : "args cannot be null";
+
+ if (args.length < minLength) {
+ throw new WoofInvalidCommandException(
+ ExceptionMessage.INVALID_NUMBER_OF_ARGUMENTS.toFormattedValue(
+ commandWord.toValue()
+ )
+ );
+ }
+ }
+
+ /**
+ * Validates that none of the provided arguments are null. If any of the arguments are null,
+ * a `WoofInvalidCommandException` is thrown with a message indicating null arguments for
+ * the given `commandWord`.
+ *
+ * @param commandWord The command word associated with the operation for which the arguments are being validated.
+ * @param args The arguments to check for null values.
+ *
+ * @throws WoofInvalidCommandException If any of the arguments are null.
+ */
+ public static void validateNotNullArgs(CommandWord commandWord, String... args) {
+ assert commandWord != null : "command word cannot be null";
+ assert args != null : "args cannot be null";
+
+ for (String arg : args) {
+ if (arg == null) {
+ throw new WoofInvalidCommandException(
+ ExceptionMessage.NULL_ARGUMENT.toFormattedValue(
+ commandWord.toValue()
+ )
+ );
+ }
+ }
+ }
+
+ /**
+ * Validates that none of the provided arguments are empty (have zero length). If any of the arguments are empty,
+ * a `WoofInvalidCommandException` is thrown with a message indicating empty arguments for the given `commandWord`.
+ *
+ * @param commandWord The command word associated with the operation for which the arguments are being validated.
+ * @param args The arguments to check for empty values.
+ *
+ * @throws WoofInvalidCommandException If any of the arguments are empty.
+ */
+ public static void validateNotEmptyArgs(CommandWord commandWord, String... args) {
+ assert commandWord != null : "command word cannot be null";
+ assert args != null : "args cannot be null";
+
+ for (String arg : args) {
+ if (arg.isEmpty()) {
+ throw new WoofInvalidCommandException(
+ ExceptionMessage.EMPTY_ARGUMENT.toFormattedValue(
+ commandWord.toValue()
+ )
+ );
+ }
+ }
+ }
+
+ /**
+ * Validates that the argument provided matches the expected `CommandWord`. If it doesn't match,
+ * a `WoofInvalidCommandException` is thrown with a message indicating an invalid command word for
+ * the expected `commandWord`.
+ *
+ * @param commandWord The command word associated with the operation for which the arguments are being validated.
+ * @param arg The argument to check for the command word.
+ *
+ * @throws WoofInvalidCommandException If the first argument doesn't match the expected `CommandWord`.
+ */
+ public static void validateCommandWord(CommandWord commandWord, String arg) {
+ assert commandWord != null : "command word cannot be null";
+ assert arg != null : "arg cannot be null";
+
+ if (!CommandWord.commandWordToValueMap(arg).equals(commandWord)) {
+ throw new WoofInvalidCommandException(
+ ExceptionMessage.INVALID_COMMAND_WORD.toFormattedValue(
+ commandWord.toValue()
+ )
+ );
+ }
+ }
+
+ /**
+ * Validates a date and time string against a specific date and time format. The format is determined
+ * by the `getDateTimeFormatter()` method.
+ *
+ * @param string The date and time string to validate.
+ *
+ * @throws WoofInvalidCommandException If the string is null or doesn't match the expected date and time format.
+ */
+ public static void validateDateTime(String string) {
+ assert string != null : "datetime string cannot be null";
+
+ try {
+ Woof.validateInDateTime(string);
+ } catch (WoofException e) {
+ throw new WoofInvalidCommandException(ExceptionMessage.INVALID_DATE_TIME_FORMAT.toFormattedValue(string));
+ }
+ }
+
+ /**
+ * Validates a task index string to ensure it is a valid index for tasks in the application.
+ * It delegates the validation to the `TaskList.validateTaskIndex` method and handles any exceptions
+ * by throwing a `WoofInvalidCommandException` with an appropriate error message.
+ *
+ * @param string The task index string to validate.
+ *
+ * @throws WoofInvalidCommandException If the task index is invalid, or if there's a problem during validation.
+ */
+ public static void validateTaskIndex(TaskList taskList, String string) {
+ assert string != null : "task index string cannot be null";
+ TaskList.validateTaskIndex(string, taskList);
+ }
+
+ /**
+ * Validates that a start date is before an end date.
+ *
+ * @param startDateStr The start date string to validate.
+ * @param endDateStr The end date string to validate.
+ *
+ * @throws WoofInvalidCommandException If the start date is not before the end date.
+ */
+ public static void validateStartDateBeforeEndDate(String startDateStr, String endDateStr) {
+ assert startDateStr != null : "Start date cannot be null";
+ assert endDateStr != null : "End date cannot be null";
+
+ validateDateTime(startDateStr);
+ validateDateTime(endDateStr);
+ LocalDate startDate = parseDateTimeIn(startDateStr);
+ LocalDate endDate = parseDateTimeIn(endDateStr);
+
+ if (endDate.isBefore(startDate)) {
+ throw new WoofInvalidCommandException(
+ ExceptionMessage.START_DATE_AFTER_END_DATE.toFormattedValue(startDateStr, endDateStr)
+ );
+ }
+ }
+}
diff --git a/src/main/java/command/DeadlineCommand.java b/src/main/java/command/DeadlineCommand.java
new file mode 100644
index 0000000000..c7682a285a
--- /dev/null
+++ b/src/main/java/command/DeadlineCommand.java
@@ -0,0 +1,68 @@
+package command;
+
+import java.time.LocalDate;
+
+import enums.CommandWord;
+import exceptions.WoofInvalidCommandException;
+import parser.Parser;
+import tasks.DeadlineTask;
+import tasks.TaskList;
+
+/**
+ * The `DeadlineCommand` class represents a command to create a new deadline task.
+ * When executed, it parses the command and adds a new deadline task to the task list.
+ */
+public class DeadlineCommand extends Command {
+
+ /**
+ * Constructs a new `DeadlineCommand` with the specified raw command string.
+ *
+ * @param rawCommand The raw command string.
+ */
+ public DeadlineCommand(String rawCommand) {
+ super(rawCommand);
+ }
+
+ /**
+ * Validates the `DeadlineCommand`.
+ * It checks if the command is correctly formatted and if the specified task index is valid.
+ *
+ * @param rawCommand The raw command string.
+ * @throws WoofInvalidCommandException If the command is invalid, it throws a woof invalid command exception with an
+ * error message.
+ */
+ public static void validate(String rawCommand) throws WoofInvalidCommandException {
+ assert rawCommand != null : "raw command cannot be null";
+
+ String[] args = Parser.getArgs(rawCommand);
+ validateArgsLengthEquals(CommandWord.DEADLINE, args, 4);
+ validateNotNullArgs(CommandWord.DEADLINE, args);
+ validateNotEmptyArgs(CommandWord.DEADLINE, args);
+ validateCommandWord(CommandWord.DEADLINE, args[0]);
+ validateCommandWord(CommandWord.BY, args[2]);
+ validateDateTime(args[3]);
+ }
+
+ /**
+ * Executes the `DeadlineCommand`. It parses the command, validates it, and adds a new
+ * deadline task to the task list if the command is valid.
+ *
+ * @param taskList The task list to which the deadline task is added.
+ */
+ public String execute(TaskList taskList) {
+ assert taskList != null : "task list cannot be null";
+
+ String rawCommand = super.getRawCommand();
+ try {
+ validate(rawCommand);
+ } catch (WoofInvalidCommandException e) {
+ return e.getMessage();
+ }
+
+ String[] args = Parser.getArgs(rawCommand);
+ String description = args[1];
+ LocalDate endDate = parseDateTimeIn(args[3]);
+ return taskList.addTask(new DeadlineTask(description, endDate));
+ }
+
+}
diff --git a/src/main/java/command/DeleteCommand.java b/src/main/java/command/DeleteCommand.java
new file mode 100644
index 0000000000..874bf8cfed
--- /dev/null
+++ b/src/main/java/command/DeleteCommand.java
@@ -0,0 +1,67 @@
+package command;
+
+import enums.CommandWord;
+import exceptions.WoofInvalidCommandException;
+import parser.Parser;
+import tasks.TaskList;
+
+/**
+ * The `DeleteCommand` class represents a command to delete a task.
+ * When executed, it parses the command, validates it, and deletes
+ * the specified task from the task list if the command is valid.
+ */
+public class DeleteCommand extends Command {
+
+ /**
+ * Constructs a new `DeleteCommand` with the specified raw command string.
+ *
+ * @param rawCommand The raw command string.
+ */
+ public DeleteCommand(String rawCommand) {
+ super(rawCommand);
+ }
+
+ /**
+ * Validates the `DeleteCommand`.
+ * It checks if the command is correctly formatted and if the specified task index is valid.
+ *
+ * @param rawCommand The raw command string.
+ * @param taskList The task list against which to validate the task index.
+ * @throws WoofInvalidCommandException If the command is invalid, it throws a woof invalid command exception with an
+ * error message.
+ */
+ public static void validate(String rawCommand, TaskList taskList) throws WoofInvalidCommandException {
+ assert rawCommand != null : "raw command cannot be null";
+ assert taskList != null : "task list cannot be null";
+
+ String[] args = Parser.getArgs(rawCommand);
+ validateArgsLengthEquals(CommandWord.DELETE, args, 2);
+ validateNotNullArgs(CommandWord.DELETE, args);
+ validateNotEmptyArgs(CommandWord.DELETE, args);
+ validateCommandWord(CommandWord.DELETE, args[0]);
+ validateTaskIndex(taskList, args[1]);
+ }
+
+
+
+ /**
+ * Executes the "delete" command. It parses the command, validates it, and deletes
+ * the specified task from the task list if the command is valid.
+ *
+ * @param taskList The task list from which the task is deleted.
+ */
+ public String execute(TaskList taskList) {
+ assert taskList != null : "task list cannot be null";
+
+ String rawCommand = super.getRawCommand();
+ try {
+ validate(rawCommand, taskList);
+ } catch (WoofInvalidCommandException e) {
+ return e.getMessage();
+ }
+
+ String[] args = Parser.getArgs(rawCommand);
+ return taskList.deleteTask(args[1]);
+ }
+
+}
diff --git a/src/main/java/command/EventCommand.java b/src/main/java/command/EventCommand.java
new file mode 100644
index 0000000000..7b976e1d69
--- /dev/null
+++ b/src/main/java/command/EventCommand.java
@@ -0,0 +1,73 @@
+package command;
+
+import java.time.LocalDate;
+
+import enums.CommandWord;
+import exceptions.WoofInvalidCommandException;
+import parser.Parser;
+import tasks.EventTask;
+import tasks.TaskList;
+import woof.Woof;
+
+/**
+ * The `EventCommand` class represents a command to create a new event task.
+ * When executed, it parses the command, validates it, and adds a
+ * new event task to the task list if the command is valid.
+ */
+public class EventCommand extends Command {
+
+ /**
+ * Constructs a new `EventCommand` with the specified raw command string.
+ *
+ * @param rawCommand The raw command string.
+ */
+ public EventCommand(String rawCommand) {
+ super(rawCommand);
+ }
+
+ /**
+ * Validates the `EventCommand`.
+ * It checks if the command is correctly formatted.
+ *
+ * @param rawCommand The raw command string.
+ * @throws WoofInvalidCommandException If the command is invalid, it throws a woof invalid command exception with an
+ * error message.
+ */
+ public static void validate(String rawCommand) throws WoofInvalidCommandException {
+ assert rawCommand != null : "raw command cannot be null";
+
+ String[] args = Parser.getArgs(rawCommand);
+ validateArgsLengthEquals(CommandWord.EVENT, args, 6);
+ validateNotNullArgs(CommandWord.EVENT, args);
+ validateNotEmptyArgs(CommandWord.EVENT, args);
+ validateCommandWord(CommandWord.EVENT, args[0]);
+ validateCommandWord(CommandWord.FROM, args[2]);
+ validateDateTime(args[3]);
+ validateCommandWord(CommandWord.TO, args[4]);
+ validateDateTime(args[5]);
+ validateStartDateBeforeEndDate(args[3], args[5]);
+ }
+
+ /**
+ * Executes the `EventCommand`. It parses the command, validates it, and adds a new
+ * event task to the task list if the command is valid.
+ *
+ * @param taskList The task list to which the event task is added.
+ */
+ public String execute(TaskList taskList) {
+ assert taskList != null : "task list cannot be null";
+
+ String rawCommand = super.getRawCommand();
+ try {
+ validate(rawCommand);
+ } catch (WoofInvalidCommandException e) {
+ return e.getMessage();
+ }
+ String[] args = Parser.getArgs(rawCommand);
+ String description = args[1];
+ LocalDate startDate = Woof.parseDateTimeIn(args[3]);
+ LocalDate endDate = Woof.parseDateTimeIn(args[5]);
+ return taskList.addTask(new EventTask(description, startDate, endDate));
+ }
+
+}
diff --git a/src/main/java/command/FindCommand.java b/src/main/java/command/FindCommand.java
new file mode 100644
index 0000000000..cef36aed17
--- /dev/null
+++ b/src/main/java/command/FindCommand.java
@@ -0,0 +1,57 @@
+package command;
+
+import enums.CommandWord;
+import exceptions.WoofInvalidCommandException;
+import parser.Parser;
+import tasks.TaskList;
+
+/**
+ * The `FindCommand` class represents a command to search for tasks based on a keyword in the Woof application.
+ */
+public class FindCommand extends Command {
+ /**
+ * Constructs a `FindCommand` with the given raw command.
+ *
+ * @param rawCommand The raw command input by the user.
+ */
+ public FindCommand(String rawCommand) {
+ super(rawCommand);
+ }
+
+ /**
+ * Validates the `FindCommand`.
+ *
+ * @param rawCommand The raw command input by the user.
+ * @throws WoofInvalidCommandException If the command is invalid, it throws a woof invalid command exception with an
+ * error message.
+ */
+ public static void validate(String rawCommand) throws WoofInvalidCommandException {
+ assert rawCommand != null : "raw command cannot be null";
+
+ String[] args = Parser.getArgs(rawCommand);
+ validateArgsLengthMoreThanEquals(CommandWord.FIND, args, 2);
+ validateNotNullArgs(CommandWord.FIND, args);
+ validateNotEmptyArgs(CommandWord.FIND, args);
+ validateCommandWord(CommandWord.FIND, args[0]);
+ }
+
+
+ /**
+ * Executes the `FindCommand` to search for tasks based on the specified keyword.
+ *
+ * @param taskList The task list in which to search for tasks.
+ */
+ public String execute(TaskList taskList) {
+ assert taskList != null : "task list cannot be null";
+
+ String rawCommand = super.getRawCommand();
+ try {
+ validate(rawCommand);
+ } catch (WoofInvalidCommandException e) {
+ return e.getMessage();
+ }
+ String[] args = Parser.getArgs(rawCommand);
+ String keySentence = args[1];
+ return taskList.findTask(keySentence.split("\\s+"));
+ }
+}
diff --git a/src/main/java/command/HelpCommand.java b/src/main/java/command/HelpCommand.java
new file mode 100644
index 0000000000..01b428b51c
--- /dev/null
+++ b/src/main/java/command/HelpCommand.java
@@ -0,0 +1,52 @@
+package command;
+
+import enums.WoofMessage;
+import exceptions.WoofInvalidCommandException;
+import tasks.TaskList;
+
+/**
+ * The `HelpCommand` class represents a command request for help.
+ * When executed, it displays a help message to the user.
+ */
+public class HelpCommand extends Command {
+
+ /**
+ * Constructs a new `HelpCommand` with the specified raw command string.
+ *
+ * @param rawCommand The raw command string.
+ */
+ public HelpCommand(String rawCommand) {
+ super(rawCommand);
+ }
+
+ /**
+ * Validates the `HelpCommand`.
+ * It checks if the command is correctly formatted and if the specified task index is valid.
+ *
+ * @param rawCommand The raw command string.
+ * @throws WoofInvalidCommandException If the command is invalid, it throws a woof invalid command exception with an
+ * error message.
+ */
+ public static void validate(String rawCommand) {
+ assert rawCommand != null : "raw command cannot be null";
+
+ String[] args = parser.Parser.getArgs(rawCommand);
+ validateArgsLengthEquals(enums.CommandWord.HELP, args, 1);
+ validateNotNullArgs(enums.CommandWord.HELP, args);
+ validateNotEmptyArgs(enums.CommandWord.HELP, args);
+ validateCommandWord(enums.CommandWord.HELP, args[0]);
+ }
+
+ /**
+ * Executes the `HelpCommand`. It shows the bye message to the user.
+ */
+ public String execute(TaskList taskList) {
+ try {
+ validate(super.getRawCommand());
+ } catch (exceptions.WoofInvalidCommandException e) {
+ return e.getMessage();
+ }
+
+ return WoofMessage.HELP.toFormattedValue();
+ }
+}
diff --git a/src/main/java/command/ListCommand.java b/src/main/java/command/ListCommand.java
new file mode 100644
index 0000000000..297bd50816
--- /dev/null
+++ b/src/main/java/command/ListCommand.java
@@ -0,0 +1,60 @@
+package command;
+
+import enums.CommandWord;
+import exceptions.WoofInvalidCommandException;
+import parser.Parser;
+import tasks.TaskList;
+
+/**
+ * The `ListCommand` class represents a command to list all tasks.
+ * When executed, it validates the command and displays a list of all tasks in the task list.
+ */
+public class ListCommand extends Command {
+
+ /**
+ * Constructs a new `ListCommand` with the specified raw command string.
+ *
+ * @param rawCommand The raw command string.
+ */
+ public ListCommand(String rawCommand) {
+ super(rawCommand);
+ }
+
+ /**
+ * Validates the `ListCommand`.
+ * It checks if the command is correctly formatted.
+ *
+ * @param rawCommand The raw command string.
+ * @throws WoofInvalidCommandException If the command is invalid, it throws a woof invalid command exception with an
+ * error message.
+ */
+ public static void validate(String rawCommand) throws WoofInvalidCommandException {
+ assert rawCommand != null : "raw command cannot be null";
+
+ String[] args = Parser.getArgs(rawCommand);
+ validateArgsLengthEquals(CommandWord.LIST, args, 1);
+ validateNotNullArgs(CommandWord.LIST, args);
+ validateNotEmptyArgs(CommandWord.LIST, args);
+ validateCommandWord(CommandWord.LIST, args[0]);
+ }
+
+
+ /**
+ * Executes the `ListCommand`. It validates the command and displays
+ * a list of all tasks in the task list.
+ *
+ * @param taskList The task list from which tasks are listed.
+ */
+ public String execute(TaskList taskList) {
+ assert taskList != null : "task list cannot be null";
+
+ String rawCommand = super.getRawCommand();
+ try {
+ validate(rawCommand);
+ } catch (WoofInvalidCommandException e) {
+ return e.getMessage();
+ }
+ return taskList.listAllTasks();
+ }
+
+}
diff --git a/src/main/java/command/MarkCommand.java b/src/main/java/command/MarkCommand.java
new file mode 100644
index 0000000000..90686a8ab6
--- /dev/null
+++ b/src/main/java/command/MarkCommand.java
@@ -0,0 +1,66 @@
+package command;
+
+import enums.CommandWord;
+import exceptions.WoofInvalidCommandException;
+import parser.Parser;
+import tasks.TaskList;
+
+/**
+ * The `MarkCommand` class represents a command to mark a task as done.
+ * When executed, it parses the command, validates it, and marks
+ * the specified task as done in the task list if the command is valid.
+ */
+public class MarkCommand extends Command {
+
+ /**
+ * Constructs a new `MarkCommand` with the specified raw command string.
+ *
+ * @param rawCommand The raw command string.
+ */
+ public MarkCommand(String rawCommand) {
+ super(rawCommand);
+ }
+
+ /**
+ * Validates the `MarkCommand`.
+ * It checks if the command is correctly formatted and if the specified task index is valid.
+ *
+ * @param rawCommand The raw command string.
+ * @param taskList The task list against which to validate the task index.
+ * @throws WoofInvalidCommandException If the command is invalid, it throws a woof invalid command exception with an
+ * error message.
+ */
+ public static void validate(String rawCommand, TaskList taskList) throws WoofInvalidCommandException {
+ assert rawCommand != null : "raw command cannot be null";
+ assert taskList != null : "task list cannot be null";
+
+ String[] args = Parser.getArgs(rawCommand);
+ validateArgsLengthEquals(CommandWord.MARK, args, 2);
+ validateNotNullArgs(CommandWord.MARK, args);
+ validateNotEmptyArgs(CommandWord.MARK, args);
+ validateCommandWord(CommandWord.MARK, args[0]);
+ validateTaskIndex(taskList, args[1]);
+ }
+
+
+ /**
+ * Executes the `MarkCommand`. It parses the command, validates it, and marks
+ * the specified task as done in the task list if the command is valid.
+ *
+ * @param taskList The task list in which the task is marked as done.
+ */
+ public String execute(TaskList taskList) {
+ assert taskList != null : "task list cannot be null";
+
+ String rawCommand = super.getRawCommand();
+ try {
+ validate(rawCommand, taskList);
+ } catch (WoofInvalidCommandException e) {
+ return e.getMessage();
+ }
+ String[] args = Parser.getArgs(super.getRawCommand());
+ String taskIndex = args[1];
+ return taskList.markTaskDone(taskIndex);
+ }
+
+}
diff --git a/src/main/java/command/NullCommand.java b/src/main/java/command/NullCommand.java
new file mode 100644
index 0000000000..749fa1c2b8
--- /dev/null
+++ b/src/main/java/command/NullCommand.java
@@ -0,0 +1,60 @@
+package command;
+
+import enums.CommandWord;
+import enums.WoofMessage;
+import exceptions.WoofInvalidCommandException;
+import parser.Parser;
+import tasks.TaskList;
+
+/**
+ * The `NullCommand` class represents a command that is not recognized or is not valid.
+ * When executed, it shows a "confused" message to the user.
+ */
+public class NullCommand extends Command {
+
+ /**
+ * Constructs a new `NullCommand` with the specified raw command string.
+ *
+ * @param rawCommand The raw command string.
+ */
+ public NullCommand(String rawCommand) {
+ super(rawCommand);
+ }
+
+ /**
+ * Validates the `NullCommand`.
+ * It checks if the command is correctly formatted.
+ *
+ * @param rawCommand The raw command string.
+ * @throws WoofInvalidCommandException If the command is invalid, it throws a woof invalid command exception with an
+ * error message.
+ */
+ public static void validate(String rawCommand) throws WoofInvalidCommandException {
+ assert rawCommand != null : "raw command cannot be null";
+
+ String[] args = Parser.getArgs(rawCommand);
+ validateArgsLengthNotEquals(CommandWord.NULL_COMMAND, args, 0);
+ validateNotNullArgs(CommandWord.NULL_COMMAND, args);
+ validateNotEmptyArgs(CommandWord.NULL_COMMAND, args);
+ validateCommandWord(CommandWord.NULL_COMMAND, args[0]);
+ }
+
+ /**
+ * Executes the `NullCommand`. It validates the command and displays
+ * a "confused" message to the user if the command is not recognized or valid.
+ *
+ * @param taskList The task list (not used in this command).
+ */
+ public String execute(TaskList taskList) {
+ assert taskList != null : "task list cannot be null";
+
+ String rawCommand = super.getRawCommand();
+ try {
+ validate(rawCommand);
+ } catch (WoofInvalidCommandException e) {
+ return e.getMessage();
+ }
+ return WoofMessage.CONFUSED.toFormattedValue(rawCommand);
+ }
+
+}
diff --git a/src/main/java/command/SortCommand.java b/src/main/java/command/SortCommand.java
new file mode 100644
index 0000000000..9814de0b0a
--- /dev/null
+++ b/src/main/java/command/SortCommand.java
@@ -0,0 +1,58 @@
+package command;
+
+import enums.CommandWord;
+import exceptions.WoofInvalidCommandException;
+import parser.Parser;
+import tasks.TaskList;
+
+/**
+ * The `SortCommand` class represents a command to list all tasks.
+ * When executed, it validates the command and displays a list of all tasks in the task list.
+ */
+public class SortCommand extends Command {
+
+ /**
+ * Constructs a new `SortCommand` with the specified raw command string.
+ *
+ * @param rawCommand The raw command string.
+ */
+ public SortCommand(String rawCommand) {
+ super(rawCommand);
+ }
+
+ /**
+ * Validates the `SortCommand`.
+ * It checks if the command is correctly formatted.
+ *
+ * @param rawCommand The raw command string.
+ * @throws WoofInvalidCommandException If the command is invalid, it throws a woof invalid command exception with an
+ * error message.
+ */
+ public static void validate(String rawCommand) throws WoofInvalidCommandException {
+ assert rawCommand != null : "raw command cannot be null";
+
+ String[] args = Parser.getArgs(rawCommand);
+ validateArgsLengthEquals(CommandWord.SORT, args, 1);
+ validateNotNullArgs(CommandWord.SORT, args);
+ validateNotEmptyArgs(CommandWord.SORT, args);
+ validateCommandWord(CommandWord.SORT, args[0]);
+ }
+
+ /**
+ * Executes the `SortCommand`. It validates the command and displays
+ * a list of all tasks in the task list.
+ *
+ * @param taskList The task list from which tasks are listed.
+ */
+ public String execute(TaskList taskList) {
+ assert taskList != null : "task list cannot be null";
+
+ String rawCommand = super.getRawCommand();
+ try {
+ validate(rawCommand);
+ } catch (WoofInvalidCommandException e) {
+ return e.getMessage();
+ }
+ return taskList.sortTasks();
+ }
+}
diff --git a/src/main/java/command/TodoCommand.java b/src/main/java/command/TodoCommand.java
new file mode 100644
index 0000000000..717218ce83
--- /dev/null
+++ b/src/main/java/command/TodoCommand.java
@@ -0,0 +1,61 @@
+package command;
+
+import enums.CommandWord;
+import exceptions.WoofInvalidCommandException;
+import parser.Parser;
+import tasks.TaskList;
+import tasks.TodoTask;
+
+/**
+ * The `TodoCommand` class represents a command to create a new todo task.
+ * When executed, it parses the command, validates it, and adds
+ * a new todo task to the task list if the command is valid.
+ */
+public class TodoCommand extends Command {
+ /**
+ * Constructs a new `TodoCommand` with the specified raw command string.
+ *
+ * @param rawCommand The raw command string.
+ */
+ public TodoCommand(String rawCommand) {
+ super(rawCommand);
+ }
+
+ /**
+ * Validates the `TodoCommand`.
+ * It checks if the command is correctly formatted.
+ *
+ * @param rawCommand The raw command string.
+ * @throws WoofInvalidCommandException If the command is invalid, it throws a woof invalid command exception with an
+ * error message.
+ */
+ public static void validate(String rawCommand) throws WoofInvalidCommandException {
+ assert rawCommand != null : "raw command cannot be null";
+
+ String[] args = Parser.getArgs(rawCommand);
+ validateArgsLengthEquals(CommandWord.TODO, args, 2);
+ validateNotNullArgs(CommandWord.TODO, args);
+ validateNotEmptyArgs(CommandWord.TODO, args);
+ validateCommandWord(CommandWord.TODO, args[0]);
+ }
+
+ /**
+ * Executes the `TodoCommand`. It parses the command, validates it, and adds a new
+ * todo task to the task list if the command is valid.
+ *
+ * @param taskList The task list to which the todo task is added.
+ */
+ public String execute(TaskList taskList) {
+ assert taskList != null : "task list cannot be null";
+
+ String rawCommand = super.getRawCommand();
+ try {
+ validate(rawCommand);
+ } catch (WoofInvalidCommandException e) {
+ return e.getMessage();
+ }
+ String[] args = Parser.getArgs(rawCommand);
+ String description = args[1];
+ return taskList.addTask(new TodoTask(description));
+ }
+}
diff --git a/src/main/java/command/UnmarkCommand.java b/src/main/java/command/UnmarkCommand.java
new file mode 100644
index 0000000000..d5167b1664
--- /dev/null
+++ b/src/main/java/command/UnmarkCommand.java
@@ -0,0 +1,65 @@
+package command;
+
+import enums.CommandWord;
+import exceptions.WoofInvalidCommandException;
+import parser.Parser;
+import tasks.TaskList;
+
+/**
+ * The `UnmarkCommand` class represents a command to unmark a task as done.
+ * When executed, it parses the command, validates it, and marks the specified task as
+ * undone in the task list if the command is valid.
+ */
+public class UnmarkCommand extends Command {
+
+ /**
+ * Constructs a new `UnmarkCommand` with the specified raw command string.
+ *
+ * @param rawCommand The raw command string.
+ */
+ public UnmarkCommand(String rawCommand) {
+ super(rawCommand);
+ }
+
+ /**
+ * Validates the `UnmarkCommand`.
+ * It checks if the command is correctly formatted and if the specified task index is valid.
+ *
+ * @param rawCommand The raw command string.
+ * @param taskList The task list against which to validate the task index.
+ * @throws WoofInvalidCommandException If the command is invalid, it throws a woof invalid command exception with an
+ * error message.
+ */
+ public static void validate(String rawCommand, TaskList taskList) throws WoofInvalidCommandException {
+ assert rawCommand != null : "raw command cannot be null";
+ assert taskList != null : "task list cannot be null";
+
+ String[] args = Parser.getArgs(rawCommand);
+ validateArgsLengthEquals(CommandWord.UNMARK, args, 2);
+ validateNotNullArgs(CommandWord.UNMARK, args);
+ validateNotEmptyArgs(CommandWord.UNMARK, args);
+ validateCommandWord(CommandWord.UNMARK, args[0]);
+ validateTaskIndex(taskList, args[1]);
+ }
+
+ /**
+ * Executes the `UnmarkCommand`. It parses the command, validates it, and marks the specified task as
+ * undone in the task list if the command is valid.
+ *
+ * @param taskList The task list in which the task is marked as undone.
+ */
+ public String execute(TaskList taskList) {
+ assert taskList != null : "task list cannot be null";
+
+ String rawCommand = super.getRawCommand();
+ try {
+ validate(rawCommand, taskList);
+ } catch (WoofInvalidCommandException e) {
+ return e.getMessage();
+ }
+
+ String[] args = Parser.getArgs(super.getRawCommand());
+ String taskIndex = args[1];
+ return taskList.markTaskUndone(taskIndex);
+ }
+}
diff --git a/src/main/java/enums/CommandWord.java b/src/main/java/enums/CommandWord.java
new file mode 100644
index 0000000000..63b5d0f3b0
--- /dev/null
+++ b/src/main/java/enums/CommandWord.java
@@ -0,0 +1,59 @@
+package enums;
+
+/**
+ * The `CommandWord` enum represents the possible command words used in the Woof application.
+ */
+public enum CommandWord {
+ BY("/by"),
+ BYE("bye"),
+ DEADLINE("deadline"),
+ DELETE("delete"),
+ EVENT("event"),
+ EXIT("exit"),
+ FIND("find"),
+ FROM("/from"),
+ HELP("help"),
+ LIST("list"),
+ MARK("mark"),
+ NULL_COMMAND(""),
+ SORT("sort"),
+ TO("/to"),
+ TODO("todo"),
+ UNMARK("unmark");
+
+ private final String value;
+
+ /**
+ * Constructs a `CommandWord` enum with the given value.
+ *
+ * @param value The string representation of the command word.
+ */
+ CommandWord(String value) {
+ this.value = value;
+ }
+
+ /**
+ * Maps a string value to the corresponding `CommandWord` enum.
+ * If no match is found, it returns the `NULL_COMMAND` enum.
+ *
+ * @param value The string value to map to a command word.
+ * @return The corresponding `CommandWord` enum, or `NULL_COMMAND` if no match is found.
+ */
+ public static CommandWord commandWordToValueMap(String value) {
+ for (CommandWord e : values()) {
+ if (e.toValue().equals(value)) {
+ return e;
+ }
+ }
+ return NULL_COMMAND;
+ }
+
+ /**
+ * Gets the string representation of the `CommandWord`.
+ *
+ * @return The string representation of the `CommandWord`.
+ */
+ public String toValue() {
+ return this.value;
+ }
+}
diff --git a/src/main/java/enums/ExceptionMessage.java b/src/main/java/enums/ExceptionMessage.java
new file mode 100644
index 0000000000..169fab2a57
--- /dev/null
+++ b/src/main/java/enums/ExceptionMessage.java
@@ -0,0 +1,58 @@
+package enums;
+
+/**
+ * The `ExceptionMessage` enum represents the possible exception messages used in the Woof application.
+ */
+public enum ExceptionMessage {
+ // INVALID COMMAND EXCEPTION MESSAGES
+ INVALID_NUMBER_OF_ARGUMENTS(
+ "wrong number of arguments for '%s'!"),
+ NULL_ARGUMENT(
+ "null argument not allowed for '%s'!"),
+ EMPTY_ARGUMENT(
+ "I don't want empty arguments for '%s' :("),
+ INVALID_COMMAND_WORD(
+ "bad command word for '%s'..."),
+ INVALID_DATE_TIME_FORMAT(
+ "me no understand your datetime format '%s'"),
+ START_DATE_AFTER_END_DATE(
+ "just like all things in life, you can't end, before you start.'%s' is before '%s'."),
+
+ // STORAGE EXCEPTION MESSAGES
+ UNABLE_TO_CREATE_FILE(
+ "me can't create file. Computer go '%s'"),
+ UNABLE_TO_READ_FILE(
+ "me can't read file. Computer go '%s'"),
+ UNABLE_TO_UPDATE_FILE(
+ "me can't update file. Computer go '%s'"),
+ UNABLE_TO_CREATE_DIRECTORY(
+ "me can't create folder"),
+
+ // TASK INDEX EXCEPTION MESSAGE
+ NON_INTEGER_TASK_INDEX("bad task index '%s', give me integer!"),
+ UNABLE_TO_PARSE_INDEX("idk how to parse '%s', Computer go '%s'"),
+ TASK_INDEX_NOT_IN_LIST("'%s' not in my task list");
+
+ private final String value;
+
+ /**
+ * Constructs an `ExceptionMessage` enum with the given value.
+ *
+ * @param value The string representation of the command word.
+ */
+ ExceptionMessage(String value) {
+ assert value != null : "value cannot be null";
+
+ this.value = value;
+ }
+
+ /**
+ * Gets the string representation of the `ExceptionMessage` with the appropriate args word.
+ *
+ * @param args The arguments to replace placeholders in the exception message.
+ * @return The string representation of the `ExceptionMessage` with the appropriate command word.
+ */
+ public String toFormattedValue(String ... args) {
+ return String.format(this.value, (Object[]) args);
+ }
+}
diff --git a/src/main/java/enums/FilePath.java b/src/main/java/enums/FilePath.java
new file mode 100644
index 0000000000..c1384a2280
--- /dev/null
+++ b/src/main/java/enums/FilePath.java
@@ -0,0 +1,40 @@
+package enums;
+
+/**
+ * The `FilePath` enum represents file paths used in the Woof application.
+ */
+public enum FilePath {
+ CUSTOM_FONT("/fonts/sono/static/Sono-Light.ttf"),
+ USER_DISPLAY_PICTURE("/images/userDisplayPicture.jpeg"),
+ BOT_DISPLAY_PICTURE("/images/botDisplayPicture.jpeg"),
+ CUSTOM_CURSOR("/images/paw.png"),
+ DIALOG_AREA_CSS("/styles/dialogArea.css"),
+ ROOT_CSS("/styles/root.css"),
+ SCROLL_PANE_CSS("/styles/scrollPane.css"),
+ SEND_BUTTON_CSS("/styles/sendButton.css"),
+ USER_INPUT_CSS("/styles/userInput.css"),
+ CLEAR_BUTTON_CSS("/styles/clearButton.css"),
+ DEFAULT_STORAGE_PATH(".data/task.json"),
+ FXML_VIEW_PATH("/views/WoofWoof.fxml");
+
+ private final String value;
+
+ /**
+ * Constructs a `FilePath` enum with the given value.
+ *
+ * @param value The string representation of the `FilePath`.
+ */
+ FilePath(String value) {
+ this.value = value;
+ }
+
+ /**
+ * Gets the string representation of the `FilePath`.
+ *
+ * @return The string representation of the `FilePath`.
+ */
+ public String toValue() {
+ return this.value;
+ }
+}
+
diff --git a/src/main/java/enums/TaskType.java b/src/main/java/enums/TaskType.java
new file mode 100644
index 0000000000..f73ec0f3f4
--- /dev/null
+++ b/src/main/java/enums/TaskType.java
@@ -0,0 +1,30 @@
+package enums;
+
+/**
+ * The `TaskType` enum represents the different types of tasks in the Woof application.
+ */
+public enum TaskType {
+ TODO("[T]"),
+ DEADLINE("[D]"),
+ EVENT("[E]");
+
+ private final String symbol;
+
+ /**
+ * Constructs a `TaskType` enum with the given symbol.
+ *
+ * @param symbol The symbol associated with the task type.
+ */
+ TaskType(String symbol) {
+ this.symbol = symbol;
+ }
+
+ /**
+ * Returns the symbol associated with the `TaskType`.
+ *
+ * @return The symbol associated with the `TaskType`.
+ */
+ public String toSymbol() {
+ return this.symbol;
+ }
+}
diff --git a/src/main/java/enums/WoofMessage.java b/src/main/java/enums/WoofMessage.java
new file mode 100644
index 0000000000..925c1c4fd1
--- /dev/null
+++ b/src/main/java/enums/WoofMessage.java
@@ -0,0 +1,84 @@
+package enums;
+
+/**
+ * The `WoofMessage` enum represents the possible woof messages used in the Woof application.
+ */
+public enum WoofMessage {
+ WOOF_TITLE("Woof Woof"),
+ HI("Woof Woof! I'm Oreo!!!\nWhat can I do for you?\nsend `help` for help."),
+ BYE("Bye. Hope to see you again soon!\nOffing myself... woof :("),
+ CONFUSED("OOPS!!! I'm sorry, I don't know what is\n'%s'\nsend `help` for help."),
+ TASK_LIST_COUNT("You have %s tasks in the task list."),
+ TASK_ADDED("Got it. I've added this task:\n%s%s%s"),
+ NO_MATCHING_TASKS("No tasks matched your keyword(s)!\n"),
+ TASKS_FOUND("Here are your matching tasks in your list:\n%s%s"),
+ EMPTY_TASK_LIST("There's nothing to sort... really\n"),
+ TASKS_LISTED("Here are all the tasks in your list:\n%s%s"),
+ TASK_DONE("Ok! I've marked this task as done:\n%s%s%s"),
+ TASKS_SORTED("Your tasks have been sorted:\n%s%s"),
+ TASK_UNDONE("Ok! I've marked this task as undone:\n%s%s%s"),
+ TASK_DELETED("Ok! I've deleted this task:\n%s%s%s"),
+ HELP("Woof! Woof! Here to help:\n\n"
+ + "Available Commands:\n\n"
+ + "1. Todo : Add a simple to-do task.\n"
+ + " Format : `todo `\n"
+ + " Example : `todo study`\n\n"
+ + "2. Deadline : Add a task with a deadline.\n"
+ + " Format : `deadline /by `\n"
+ + " Example : `deadline assignment /by 2023-12-31`\n\n"
+ + "3. Event : Add an event with a start and end date.\n"
+ + " Format : `event /from /to `\n"
+ + " Example : `event enjoy /from 2023-11-25 /to 2024-01-14`\n\n"
+ + "4. Mark : Mark a task as completed.\n"
+ + " Format : `mark `\n"
+ + " Example : `mark 1`\n\n"
+ + "5. Unmark : Unmark a completed task as incomplete.\n"
+ + " Format : `unmark `\n"
+ + " Example : `unmark 1`\n\n"
+ + "6. Delete : Delete a task.\n"
+ + " Format : `delete `\n"
+ + " Example : `delete 2`\n\n"
+ + "7. List : View all your tasks.\n"
+ + " Format : `list`\n\n"
+ + "8. Find : Search for tasks containing n keywords.\n"
+ + " Format : `find ... `\n"
+ + " Example : `find meeting project`\n\n"
+ + "9. Sort : Sort tasks by date and time.\n"
+ + " Format : `sort`\n\n"
+ + "10. Bye : Say goodbye to WoofWoof Bot.\n"
+ + " Format : `bye`\n\n"
+ + "11. Help : Request help on how to use the bot.\n"
+ + " Format : `help`\n\n"
+ + "Notes:\n"
+ + "- Replace `` with a task description.\n"
+ + "- `` should follow the yyyy-mm-dd date format.\n"
+ + " e.g. 2023-12-31\n"
+ + "- `` should be the index of the task in the list\n"
+ + " you want to manage.\n\n"
+ + "Feel free to ask for help using the `help` command if you\n"
+ + "have any questions or encounter issues.\n"
+ + "Woof Woof is here to make your life easier!");
+
+ private final String value;
+
+ /**
+ * Constructs a `Exception Message` enum with the given value.
+ *
+ * @param value The string representation of the command word.
+ */
+ WoofMessage(String value) {
+ assert value != null : "value cannot be null";
+
+ this.value = value;
+ }
+
+ /**
+ * Gets the string representation of the woof message with the appropriate args word.
+ *
+ * @param args The arguments to replace placeholders in the exception message.
+ * @return The string representation of the exception message with the appropriate command word.
+ */
+ public String toFormattedValue(String... args) {
+ return String.format(this.value, (Object[]) args);
+ }
+}
diff --git a/src/main/java/exceptions/WoofException.java b/src/main/java/exceptions/WoofException.java
new file mode 100644
index 0000000000..99a67562ca
--- /dev/null
+++ b/src/main/java/exceptions/WoofException.java
@@ -0,0 +1,16 @@
+package exceptions;
+
+/**
+ * The `WoofException` class represents a custom exception specific to the Woof application.
+ * It is used to indicate exceptional situations or errors within the application.
+ */
+public class WoofException extends RuntimeException {
+ /**
+ * Constructs a new `WoofException` with the specified error message.
+ *
+ * @param message The error message describing the exception.
+ */
+ public WoofException(String message) {
+ super(String.format("Woof! %s", message));
+ }
+}
diff --git a/src/main/java/exceptions/WoofInvalidCommandException.java b/src/main/java/exceptions/WoofInvalidCommandException.java
new file mode 100644
index 0000000000..84323e69ab
--- /dev/null
+++ b/src/main/java/exceptions/WoofInvalidCommandException.java
@@ -0,0 +1,16 @@
+package exceptions;
+
+/**
+ * The `WoofInvalidCommandException` class represents a custom exception specific to the Woof application.
+ * It is used to indicate that an invalid command has been encountered.
+ */
+public class WoofInvalidCommandException extends WoofException {
+ /**
+ * Constructs a new `WoofInvalidCommandException` with the specified error message.
+ *
+ * @param message The error message describing the invalid command.
+ */
+ public WoofInvalidCommandException(String message) {
+ super(String.format("command bad ... %s", message));
+ }
+}
diff --git a/src/main/java/exceptions/WoofStorageException.java b/src/main/java/exceptions/WoofStorageException.java
new file mode 100644
index 0000000000..470593daa4
--- /dev/null
+++ b/src/main/java/exceptions/WoofStorageException.java
@@ -0,0 +1,17 @@
+package exceptions;
+
+/**
+ * The `WoofStorageException` class represents a custom exception specific to the Woof application.
+ * It is used to indicate exceptions related to storage operations,
+ * such as reading from or writing to storage.
+ */
+public class WoofStorageException extends WoofException {
+ /**
+ * Constructs a new `WoofStorageException` with the specified error message.
+ *
+ * @param message The error message describing the storage-related exception.
+ */
+ public WoofStorageException(String message) {
+ super(String.format("storage... %s", message));
+ }
+}
diff --git a/src/main/java/parser/Parser.java b/src/main/java/parser/Parser.java
new file mode 100644
index 0000000000..2d8bd8cde3
--- /dev/null
+++ b/src/main/java/parser/Parser.java
@@ -0,0 +1,100 @@
+package parser;
+
+import java.util.ArrayList;
+
+import command.ByeCommand;
+import command.Command;
+import command.DeadlineCommand;
+import command.DeleteCommand;
+import command.EventCommand;
+import command.FindCommand;
+import command.HelpCommand;
+import command.ListCommand;
+import command.MarkCommand;
+import command.NullCommand;
+import command.SortCommand;
+import command.TodoCommand;
+import command.UnmarkCommand;
+import enums.CommandWord;
+
+/**
+ * The `Parser` class is responsible for parsing user commands and creating corresponding `Command` objects.
+ */
+public class Parser {
+ /**
+ * Parses a raw user command and returns the corresponding `Command` object.
+ *
+ * @param rawCommand The raw user command.
+ * @return A `Command` object representing the parsed command.
+ */
+ public static Command parse(String rawCommand) {
+ switch (CommandWord.commandWordToValueMap(getArgs(rawCommand)[0])) {
+ case BYE:
+ return new ByeCommand(rawCommand);
+ case DEADLINE:
+ return new DeadlineCommand(rawCommand);
+ case DELETE:
+ return new DeleteCommand(rawCommand);
+ case EVENT:
+ return new EventCommand(rawCommand);
+ case FIND:
+ return new FindCommand(rawCommand);
+ case HELP:
+ return new HelpCommand(rawCommand);
+ case LIST:
+ return new ListCommand(rawCommand);
+ case MARK:
+ return new MarkCommand(rawCommand);
+ case UNMARK:
+ return new UnmarkCommand(rawCommand);
+ case SORT:
+ return new SortCommand(rawCommand);
+ case TODO:
+ return new TodoCommand(rawCommand);
+ default:
+ return new NullCommand(rawCommand);
+ }
+ }
+
+ /**
+ * Splits a raw user command into individual arguments.
+ *
+ * @param rawCommand The raw user command.
+ * @return An array of individual arguments.
+ */
+ public static String[] getArgs(String rawCommand) {
+ assert rawCommand != null : "raw command cannot be null";
+
+ ArrayList result = new ArrayList<>();
+
+ String[] words = rawCommand.trim().split(" ");
+ if (words.length == 0) {
+ return result.toArray(new String[0]);
+ }
+
+ String mainCommand = words[0];
+ assert mainCommand != null : "main command cannot be null";
+
+ StringBuilder subCommand = new StringBuilder();
+
+ result.add(mainCommand);
+ for (int i = 1; i < words.length; i++) {
+ String currentWord = words[i];
+ if (currentWord.startsWith("/")) {
+ if (!subCommand.toString().trim().isEmpty()) {
+ result.add(subCommand.toString().trim());
+ }
+ result.add(currentWord);
+ subCommand = new StringBuilder();
+ } else {
+ subCommand.append(" ").append(currentWord);
+ }
+ }
+
+ if (!subCommand.toString().trim().isEmpty()) {
+ result.add(subCommand.toString().trim());
+ }
+
+ return result.toArray(new String[0]);
+ }
+}
diff --git a/src/main/java/storage/LocalDateAdapter.java b/src/main/java/storage/LocalDateAdapter.java
new file mode 100644
index 0000000000..d4bde7fcda
--- /dev/null
+++ b/src/main/java/storage/LocalDateAdapter.java
@@ -0,0 +1,52 @@
+package storage;
+
+import java.lang.reflect.Type;
+import java.time.LocalDate;
+
+import com.google.gson.JsonDeserializationContext;
+import com.google.gson.JsonDeserializer;
+import com.google.gson.JsonElement;
+import com.google.gson.JsonParseException;
+import com.google.gson.JsonPrimitive;
+import com.google.gson.JsonSerializationContext;
+import com.google.gson.JsonSerializer;
+
+import woof.Woof;
+
+/**
+ * The `LocalDateAdapter` class is a Gson adapter for serializing and deserializing `LocalDate` objects.
+ */
+public class LocalDateAdapter implements JsonSerializer, JsonDeserializer {
+ /**
+ * Deserialize a `LocalDate` object from a JSON element.
+ *
+ * @param json The JSON element containing the date as a string.
+ * @param typeOfT The type of the object to deserialize.
+ * @param context The deserialization context.
+ * @return A `LocalDate` object deserialized from the JSON element.
+ *
+ * @throws JsonParseException If there is an issue with JSON parsing.
+ */
+ @Override
+ public LocalDate deserialize(
+ JsonElement json,
+ Type typeOfT,
+ JsonDeserializationContext context
+ ) throws JsonParseException {
+ String dateStr = json.getAsString();
+ return LocalDate.parse(dateStr, Woof.getDateTimeOutFormatter());
+ }
+
+ /**
+ * Serialize a `LocalDate` object to a JSON element.
+ *
+ * @param src The `LocalDate` object to serialize.
+ * @param typeOfSrc The type of the source object.
+ * @param context The serialization context.
+ * @return A JSON element representing the serialized `LocalDate` object.
+ */
+ @Override
+ public JsonElement serialize(LocalDate src, Type typeOfSrc, JsonSerializationContext context) {
+ return new JsonPrimitive(Woof.getDateTimeOutFormatter().format(src));
+ }
+}
diff --git a/src/main/java/storage/TaskAdapter.java b/src/main/java/storage/TaskAdapter.java
new file mode 100644
index 0000000000..eb17840035
--- /dev/null
+++ b/src/main/java/storage/TaskAdapter.java
@@ -0,0 +1,60 @@
+package storage;
+
+import java.lang.reflect.Type;
+
+import com.google.gson.JsonDeserializationContext;
+import com.google.gson.JsonDeserializer;
+import com.google.gson.JsonElement;
+import com.google.gson.JsonObject;
+import com.google.gson.JsonParseException;
+import com.google.gson.JsonSerializationContext;
+import com.google.gson.JsonSerializer;
+
+import tasks.Task;
+
+/**
+ * The `TaskAdapter` class is a Gson adapter for serializing and deserializing `Task` objects.
+ */
+public class TaskAdapter implements JsonDeserializer, JsonSerializer {
+ /**
+ * Serialize a `Task` object to a JSON element.
+ *
+ * @param src The `Task` object to serialize.
+ * @param typeOfSrc The type of the source object.
+ * @param context The serialization context.
+ * @return A JSON element representing the serialized `Task` object.
+ */
+ @Override
+ public JsonElement serialize(Task src, Type typeOfSrc, JsonSerializationContext context) {
+ JsonObject jsonObject = new JsonObject();
+ jsonObject.addProperty("type", src.getClass().getName());
+ jsonObject.add("data", context.serialize(src));
+ return jsonObject;
+ }
+
+ /**
+ * Deserialize a `Task` object from a JSON element.
+ *
+ * @param json The JSON element containing the serialized `Task` object.
+ * @param typeOfT The type of the object to deserialize.
+ * @param context The deserialization context.
+ * @return A `Task` object deserialized from the JSON element.
+ * @throws JsonParseException If there is an issue with JSON parsing.
+ */
+ @Override
+ public Task deserialize(
+ JsonElement json,
+ Type typeOfT,
+ JsonDeserializationContext context
+ ) throws JsonParseException {
+ JsonObject jsonObject = json.getAsJsonObject();
+ String type = jsonObject.get("type").getAsString();
+ JsonElement data = jsonObject.get("data");
+ try {
+ Class> clazz = Class.forName(type);
+ return context.deserialize(data, clazz);
+ } catch (ClassNotFoundException e) {
+ throw new JsonParseException(e);
+ }
+ }
+}
diff --git a/src/main/java/storage/TaskFileHandler.java b/src/main/java/storage/TaskFileHandler.java
new file mode 100644
index 0000000000..67f6047005
--- /dev/null
+++ b/src/main/java/storage/TaskFileHandler.java
@@ -0,0 +1,118 @@
+package storage;
+
+import java.io.File;
+import java.io.FileReader;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.time.DateTimeException;
+import java.time.LocalDate;
+
+import com.google.gson.Gson;
+import com.google.gson.GsonBuilder;
+import com.google.gson.JsonSyntaxException;
+
+import enums.ExceptionMessage;
+import enums.FilePath;
+import exceptions.WoofStorageException;
+import tasks.Task;
+import tasks.TaskList;
+
+
+/**
+ * The `TaskFileHandler` class is responsible for reading and writing tasks to a JSON file.
+ */
+public class TaskFileHandler {
+ private static final String storageLocation = FilePath.DEFAULT_STORAGE_PATH.toValue();
+ /**
+ * Reads tasks from the JSON file and returns a `TaskList` object.
+ *
+ * @return A `TaskList` containing the tasks read from the file.
+ * @throws WoofStorageException If there is an issue with reading or parsing the file.
+ */
+ public static TaskList readFromFile() {
+ createFileIfNotExists();
+ Gson gson = new GsonBuilder()
+ .registerTypeAdapter(Task.class, new TaskAdapter())
+ .registerTypeAdapter(LocalDate.class, new LocalDateAdapter())
+ .setPrettyPrinting()
+ .create();
+ Task[] tasks;
+ try (FileReader r = new FileReader(storageLocation)) {
+ tasks = gson.fromJson(r, Task[].class);
+ } catch (IOException e) {
+ throw new WoofStorageException(ExceptionMessage.UNABLE_TO_READ_FILE.toFormattedValue(e.getMessage()));
+ } catch (JsonSyntaxException | DateTimeException e) {
+ return destroyFileAndRetry();
+ }
+
+ return new TaskList(tasks);
+ }
+
+ /**
+ * Destroys the file content and retries reading tasks from the JSON file.
+ *
+ * @return A `TaskList` containing the tasks after destroying the file content and retrying.
+ * @throws WoofStorageException If there is an issue with destroying the file content or reading tasks.
+ */
+ private static TaskList destroyFileAndRetry() {
+ try (FileWriter w = new FileWriter(storageLocation)) {
+ w.write("");
+ } catch (IOException e) {
+ throw new WoofStorageException(ExceptionMessage.UNABLE_TO_UPDATE_FILE.toFormattedValue(e.getMessage()));
+ }
+
+ try (FileReader r = new FileReader(storageLocation)) {
+ Task[] tasks = new Gson().fromJson(r, Task[].class);
+ return new TaskList(tasks);
+ } catch (IOException e) {
+ throw new WoofStorageException(ExceptionMessage.UNABLE_TO_UPDATE_FILE.toFormattedValue(e.getMessage()));
+ }
+ }
+
+ /**
+ * Saves tasks to the JSON file.
+ *
+ * @param taskList The `TaskList` containing tasks to be saved.
+ * @throws WoofStorageException If there is an issue with writing to the file.
+ */
+ public static void saveToFile(TaskList taskList) {
+ Task[] tasks = taskList.getTasks();
+ Gson gson = new GsonBuilder()
+ .registerTypeAdapter(Task.class, new TaskAdapter())
+ .registerTypeAdapter(LocalDate.class, new LocalDateAdapter())
+ .setPrettyPrinting()
+ .create();
+ try (FileWriter w = new FileWriter(storageLocation)) {
+ gson.toJson(tasks, w);
+ } catch (IOException e) {
+ throw new WoofStorageException(ExceptionMessage.UNABLE_TO_UPDATE_FILE.toFormattedValue(e.getMessage()));
+ }
+ }
+
+ /**
+ * Creates the JSON file if it does not exist.
+ *
+ * @throws WoofStorageException If there is an issue with file creation.
+ */
+ private static void createFileIfNotExists() {
+ File file = new File(storageLocation);
+ if (!file.exists()) {
+ try {
+ if (!file.getParentFile().mkdirs()) {
+ throw new WoofStorageException(
+ ExceptionMessage.UNABLE_TO_CREATE_DIRECTORY.toFormattedValue()
+ );
+ }
+ if (!file.createNewFile()) {
+ throw new WoofStorageException(
+ ExceptionMessage.UNABLE_TO_CREATE_FILE.toFormattedValue()
+ );
+ }
+ } catch (IOException e) {
+ throw new WoofStorageException(
+ ExceptionMessage.UNABLE_TO_CREATE_FILE.toFormattedValue(e.getMessage())
+ );
+ }
+ }
+ }
+}
diff --git a/src/main/java/tasks/DeadlineTask.java b/src/main/java/tasks/DeadlineTask.java
new file mode 100644
index 0000000000..b27374fdc3
--- /dev/null
+++ b/src/main/java/tasks/DeadlineTask.java
@@ -0,0 +1,110 @@
+package tasks;
+
+import java.time.LocalDate;
+import java.util.Objects;
+
+import enums.TaskType;
+
+/**
+ * The `DeadlineTask` class represents a task with a specific deadline in the Woof application.
+ * It extends the `Task` class and includes the deadline information.
+ */
+public class DeadlineTask extends Task {
+ /**
+ * The deadline date of the task.
+ */
+ private final LocalDate endDate;
+
+ /**
+ * Constructs a `DeadlineTask` with the given description and deadline date.
+ *
+ * @param description The description of the task.
+ * @param endDate The deadline date of the task.
+ */
+ public DeadlineTask(String description, LocalDate endDate) {
+ super(description);
+ this.endDate = endDate;
+ }
+
+ /**
+ * Constructs a `DeadlineTask` with the given description and deadline date.
+ *
+ * @param description The description of the task.
+ * @param endDate The deadline date of the task.
+ * @param isDone The optional is done to mark a task as per is done.
+ */
+ public DeadlineTask(String description, LocalDate endDate, Boolean isDone) {
+ super(description, isDone);
+ this.endDate = endDate;
+ }
+
+ /**
+ * Gets the deadline date formatted as a string.
+ *
+ * @return A string representation of the deadline date.
+ */
+ public String getDeadline() {
+ return String.format("%s~By: %s\n", getTabStopTwo(), parseDateTimeOut(endDate));
+ }
+
+ /**
+ * Retrieves the type of task associated with this object, which is "DEADLINE."
+ *
+ * @return The task type, which is "DEADLINE" for objects of this class.
+ */
+ @Override
+ public TaskType getTaskType() {
+ return TaskType.DEADLINE;
+ }
+
+ /**
+ * Retrieves the datetime associated with deadlines, the end date and time, represented as a long value.
+ *
+ * @return The end date and time as a long value.
+ */
+ @Override
+ public long getDateTimeLong() {
+ return this.endDate.toEpochDay();
+ }
+
+ /**
+ * Generates a string representation of the `DeadlineTask`.
+ *
+ * @return A string representation of the task, including its symbol, description, and deadline.
+ */
+ @Override
+ public String toString() {
+ return String.format("%s%s%s", TaskType.DEADLINE.toSymbol(), super.toString() , getDeadline());
+ }
+
+ /**
+ * Checks if this `DeadlineTask` is equal to another object.
+ *
+ * @param o The object to compare to.
+ * @return `true` if the objects are equal, `false` otherwise.
+ */
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (!(o instanceof DeadlineTask)) {
+ return false;
+ }
+ if (!super.equals(o)) {
+ return false;
+ }
+ DeadlineTask otherDeadlineTask = (DeadlineTask) o;
+ return this.endDate.equals(otherDeadlineTask.endDate);
+ }
+
+ /**
+ * Generates a hash code for this `DeadlineTask`.
+ *
+ * @return A hash code for the task, including its description and deadline.
+ */
+ @Override
+ public int hashCode() {
+ return Objects.hash(super.hashCode(), this.endDate);
+ }
+}
diff --git a/src/main/java/tasks/EventTask.java b/src/main/java/tasks/EventTask.java
new file mode 100644
index 0000000000..5a71c2580e
--- /dev/null
+++ b/src/main/java/tasks/EventTask.java
@@ -0,0 +1,124 @@
+package tasks;
+
+import java.time.LocalDate;
+import java.util.Objects;
+
+import enums.TaskType;
+
+/**
+ * The `EventTask` class represents an event task with a specific start and end date in the Woof application.
+ * It extends the `Task` class and includes the event date range information.
+ */
+public class EventTask extends Task {
+ /**
+ * The start date of the event task.
+ */
+ private final LocalDate startDate;
+
+ /**
+ * The end date of the event task.
+ */
+ private final LocalDate endDate;
+
+ /**
+ * Constructs an `EventTask` with the given description, start date, and end date.
+ *
+ * @param description The description of the event task.
+ * @param startDate The start date of the event task.
+ * @param endDate The end date of the event task.
+ */
+ public EventTask(String description, LocalDate startDate, LocalDate endDate) {
+ super(description);
+ this.startDate = startDate;
+ this.endDate = endDate;
+ }
+
+ /**
+ * Constructs an `EventTask` with the given description, start date, and end date.
+ *
+ * @param description The description of the event task.
+ * @param startDate The start date of the event task.
+ * @param endDate The end date of the event task.
+ * @param isDone The optional is done to mark a task as per is done.
+ */
+ public EventTask(String description, LocalDate startDate, LocalDate endDate, Boolean isDone) {
+ super(description, isDone);
+ this.startDate = startDate;
+ this.endDate = endDate;
+ }
+
+ /**
+ * Gets the event date range formatted as a string.
+ *
+ * @return A string representation of the event date range.
+ */
+ public String getEventRange() {
+ return String.format("%s~From: %s\n%s~To : %s",
+ getTabStopTwo(),
+ parseDateTimeOut(startDate),
+ getTabStopTwo(),
+ parseDateTimeOut(endDate)
+ );
+ }
+
+ /**
+ * Retrieves the type of task associated with this object, which is "DEADLINE."
+ *
+ * @return The task type, which is "EVENT" for objects of this class.
+ */
+ @Override
+ public TaskType getTaskType() {
+ return TaskType.EVENT;
+ }
+
+ /**
+ * Retrieves the datetime associated with events, the start date and time, represented as a long value.
+ *
+ * @return The start date and time as a long value.
+ */
+ @Override
+ public long getDateTimeLong() {
+ return this.startDate.toEpochDay();
+ }
+
+ /**
+ * Generates a string representation of the `EventTask`.
+ *
+ * @return A string representation of the task, including its symbol, description, and event date range.
+ */
+ @Override
+ public String toString() {
+ return String.format("%s%s%s\n", TaskType.EVENT.toSymbol() , super.toString() , getEventRange());
+ }
+
+ /**
+ * Checks if this `EventTask` is equal to another object.
+ *
+ * @param o The object to compare to.
+ * @return `true` if the objects are equal, `false` otherwise.
+ */
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (!(o instanceof EventTask)) {
+ return false;
+ }
+ if (!super.equals(o)) {
+ return false;
+ }
+ EventTask eventTask = (EventTask) o;
+ return this.startDate.equals(eventTask.startDate) && this.endDate.equals(eventTask.endDate);
+ }
+
+ /**
+ * Generates a hash code for this `EventTask`.
+ *
+ * @return A hash code for the task, including its description and event date range.
+ */
+ @Override
+ public int hashCode() {
+ return Objects.hash(super.hashCode(), this.startDate, this.endDate);
+ }
+}
diff --git a/src/main/java/tasks/Task.java b/src/main/java/tasks/Task.java
new file mode 100644
index 0000000000..815cd24c08
--- /dev/null
+++ b/src/main/java/tasks/Task.java
@@ -0,0 +1,224 @@
+package tasks;
+
+import java.time.LocalDate;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Objects;
+
+import enums.TaskType;
+import woof.Woof;
+
+/**
+ * The `Task` class represents a task in the Woof application.
+ */
+public abstract class Task implements Comparable {
+ /**
+ * The first tab stop position, typically at column 5.
+ * Adjust this constant to change the position of the first tab stop.
+ */
+ static final int TAB_STOP_ONE = 5;
+
+ /**
+ * The second tab stop position, typically at column 12.
+ * Adjust this constant to change the position of the second tab stop.
+ */
+ static final int TAB_STOP_TWO = 12;
+
+ /**
+ * The description of the task.
+ */
+ protected String description;
+
+ /**
+ * A flag indicating whether the task is marked as done.
+ */
+ private boolean isDone;
+
+ /**
+ * Constructs a `Task` with the given description and initializes it as not done.
+ *
+ * @param description The description of the task.
+ */
+ public Task(String description) {
+ assert description != null : "description cannot be null";
+
+ this.description = Woof.wrapText(
+ description,
+ getTabStopTwo(),
+ Woof.getChatWidth() - TAB_STOP_TWO);
+ this.isDone = false;
+ }
+
+ /**
+ * Constructs a `Task` with the given description and initializes it as not done.
+ *
+ * @param description The description of the task.
+ * @param isDone The optional is done to mark a task as per is done.
+ */
+ public Task(String description, Boolean isDone) {
+ assert description != null : "description cannot be null";
+ assert isDone != null : "is done cannot be null";
+
+ this.description = Woof.wrapText(
+ description,
+ getTabStopTwo(),
+ Woof.getChatWidth() - TAB_STOP_TWO);
+ this.isDone = isDone;
+ }
+
+ /**
+ * Formats a LocalDate into a user-friendly date string for presentation.
+ *
+ * @param localDate The LocalDate to format.
+ * @return A formatted date string.
+ */
+ public static String parseDateTimeOut(LocalDate localDate) {
+ return Woof.parseDateTimeOut(localDate);
+ }
+
+ /**
+ * Gets the status icon representing the task's completion status.
+ *
+ * @return The status icon ("[X]" for done, "[ ]" for not done).
+ */
+ private String getStatusIcon() {
+ return (isDone ? "[X]" : "[ ]");
+ }
+
+ /**
+ * Gets a tab stop to the first level for indenting a task to the same level as in a list.
+ *
+ * @return The tab space " ".
+ */
+ protected String getTabStopOne() {
+ return " ".repeat(TAB_STOP_ONE);
+ }
+
+ /**
+ * Gets a tab stop to the second level for indenting to the description of a task.
+ *
+ * @return The tab space " ".
+ */
+ protected String getTabStopTwo() {
+ return " ".repeat(TAB_STOP_TWO);
+ }
+
+ /**
+ * Generates a string representation of the `Task`.
+ *
+ * @return A string representation of the task, including its status icon and description.
+ */
+ public String toString() {
+ return String.format("%s %s\n", this.getStatusIcon(), this.description);
+ }
+
+ /**
+ * Marks the task as done.
+ */
+ public void markDone() {
+ this.isDone = true;
+ }
+
+ /**
+ * Marks the task as not done.
+ */
+ public void markUndone() {
+ this.isDone = false;
+ }
+
+ /**
+ * Checks if the task is marked as done.
+ *
+ * @return `true` if the task is done, `false` otherwise.
+ */
+ public boolean isDone() {
+ return this.isDone;
+ }
+
+ /**
+ * Checks if the task description contains a specified keyword.
+ *
+ * @param keyword The keyword to search for within the task description.
+ * @return `true` if the task description contains the keyword, `false` otherwise.
+ */
+ public boolean hasKeyWord(String keyword) {
+ return this.description.contains(keyword);
+ }
+
+ public abstract TaskType getTaskType();
+
+ public abstract long getDateTimeLong();
+
+ /**
+ * Compares this task to another task for ordering based on specific criteria.
+ * The comparison is primarily based on the following criteria, in order of priority:
+ * 1. Task completion status (done or not done).
+ * 2. Task type (Deadline, Event, Todo).
+ * 3. Date and time associated with the task (if applicable).
+ * 4. Task description (in case of equal status, type, and date/time).
+ *
+ * @param otherTask The task to compare to.
+ * @return A negative integer, zero, or a positive integer as this task is less than,
+ * equal to, or greater than the specified task.
+ */
+ @Override
+ public int compareTo(Task otherTask) {
+ final ArrayList taskPriority = new ArrayList<>(Arrays.asList(
+ TaskType.DEADLINE,
+ TaskType.EVENT,
+ TaskType.TODO)
+ );
+
+ int doneCompare = Boolean.compare(
+ this.isDone(),
+ otherTask.isDone());
+ if (doneCompare != 0) {
+ return doneCompare;
+ }
+
+ int priorityCompare = Integer.compare(
+ taskPriority.indexOf(this.getTaskType()),
+ taskPriority.indexOf(otherTask.getTaskType())
+ );
+ if (priorityCompare != 0) {
+ return priorityCompare;
+ }
+
+ int dateTimeCompare = Long.compare(
+ this.getDateTimeLong(),
+ otherTask.getDateTimeLong());
+ if (dateTimeCompare != 0) {
+ return dateTimeCompare;
+ }
+
+ return this.description.compareTo(otherTask.description);
+ }
+
+ /**
+ * Checks if this `Task` is equal to another object.
+ *
+ * @param o The object to compare to.
+ * @return `true` if the objects are equal, `false` otherwise.
+ */
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (!(o instanceof Task)) {
+ return false;
+ }
+ Task otherTask = (Task) o;
+ return this.isDone == otherTask.isDone && this.description.equals(otherTask.description);
+ }
+
+ /**
+ * Generates a hash code for this `Task`.
+ *
+ * @return A hash code for the task, including its description and completion status.
+ */
+ @Override
+ public int hashCode() {
+ return Objects.hash(description, isDone);
+ }
+}
diff --git a/src/main/java/tasks/TaskList.java b/src/main/java/tasks/TaskList.java
new file mode 100644
index 0000000000..1fa4599120
--- /dev/null
+++ b/src/main/java/tasks/TaskList.java
@@ -0,0 +1,224 @@
+package tasks;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.stream.Collectors;
+import java.util.stream.IntStream;
+
+import enums.ExceptionMessage;
+import enums.WoofMessage;
+import exceptions.WoofInvalidCommandException;
+
+/**
+ * The `TaskList` class represents a collection of tasks in the Woof application.
+ * It provides methods for adding, marking tasks as done/undone, deleting tasks,
+ * and listing all tasks in the list.
+ */
+public class TaskList {
+ /**
+ * The list of tasks managed by this `TaskList`.
+ */
+ protected ArrayList tasks;
+
+ /**
+ * Constructs a `TaskList` with an initial array of tasks.
+ *
+ * @param tasks The initial array of tasks to populate the list.
+ */
+ public TaskList(Task[] tasks) {
+ if (tasks == null) {
+ this.tasks = new ArrayList<>(1);
+ } else {
+ this.tasks = new ArrayList<>(Arrays.asList(tasks));
+ }
+ }
+
+ /**
+ * Gets a task from the list by its index.
+ *
+ * @param taskIndex The index of the task to retrieve.
+ * @return The task at the specified index, or `null` if the index is out of bounds.
+ */
+ public Task getTask(int taskIndex) {
+ if (taskIndex < 0 || taskIndex >= tasks.size()) {
+ return null;
+ }
+ return this.tasks.get(taskIndex);
+ }
+
+ /**
+ * Gets the number of tasks in the list.
+ *
+ * @return The number of tasks in the list.
+ */
+ public int size() {
+ return this.tasks.size();
+ }
+
+ /**
+ * Retrieves all tasks in the list as an array.
+ *
+ * @return An array containing all the tasks in the list.
+ */
+ public Task[] getTasks() {
+ Task[] taskArray = new Task[this.size()];
+ return this.tasks.toArray(taskArray);
+ }
+
+ /**
+ * Adds a task to the list and returns a confirmation message.
+ *
+ * @param task The task to be added.
+ * @return A confirmation message.
+ */
+ public String addTask(Task task) {
+ this.tasks.add(task);
+ return WoofMessage.TASK_ADDED.toFormattedValue(task.getTabStopOne(), task.toString(), getTaskCountMessage());
+ }
+
+ /**
+ * Marks a task as done and returns a confirmation message.
+ *
+ * @param text The text representing the task to be marked as done.
+ * @return A confirmation message.
+ */
+ public String markTaskDone(String text) {
+ int taskIndex = Integer.parseInt(text) - 1;
+ Task task = this.tasks.get(taskIndex);
+ task.markDone();
+ return WoofMessage.TASK_DONE.toFormattedValue(task.getTabStopOne(), task.toString(), getTaskCountMessage());
+ }
+
+ /**
+ * Marks a task as undone and returns a confirmation message.
+ *
+ * @param text The text representing the task to be marked as undone.
+ * @return A confirmation message.
+ */
+ public String markTaskUndone(String text) {
+ int taskIndex = Integer.parseInt(text) - 1;
+ Task task = this.tasks.get(taskIndex);
+ task.markUndone();
+ return WoofMessage.TASK_UNDONE.toFormattedValue(task.getTabStopOne(), task.toString(), getTaskCountMessage());
+ }
+
+ /**
+ * Deletes a task from the list and returns a confirmation message.
+ *
+ * @param text The text representing the task to be deleted.
+ * @return A confirmation message.
+ */
+ public String deleteTask(String text) {
+ int taskIndex = Integer.parseInt(text) - 1;
+ Task task = this.tasks.get(taskIndex);
+ this.tasks.remove(taskIndex);
+ return WoofMessage.TASK_DELETED.toFormattedValue(task.getTabStopOne(), task.toString(), getTaskCountMessage());
+ }
+
+ /**
+ * Lists all tasks in the list and returns them along with task numbers as a formatted string.
+ *
+ * @return A formatted string containing the list of tasks with task numbers.
+ */
+ public String listAllTasks() {
+ String taskList = IntStream.range(0, tasks.size())
+ .mapToObj(i -> String.format("%3d. %s", i + 1, tasks.get(i).toString()))
+ .collect(Collectors.joining(""));
+ return WoofMessage.TASKS_LISTED.toFormattedValue(taskList, getTaskCountMessage());
+ }
+
+ /**
+ * Sorts the tasks in the task list based on their natural order (using compareTo).
+ *
+ * @return A message indicating the sorting result or an error message if the task list is empty.
+ */
+ public String sortTasks() {
+ Collections.sort(this.tasks);
+ String sortedTaskList = IntStream.range(0, tasks.size())
+ .mapToObj(i -> {
+ String taskNumber = String.format("%3d.", i + 1);
+ return String.format("%s %s", taskNumber, tasks.get(i).toString());
+ })
+ .collect(Collectors.joining(""));
+ if (this.size() == 0) {
+ return WoofMessage.TASKS_SORTED.toFormattedValue(
+ WoofMessage.EMPTY_TASK_LIST.toFormattedValue(),
+ getTaskCountMessage());
+ }
+ return WoofMessage.TASKS_SORTED.toFormattedValue(sortedTaskList, getTaskCountMessage());
+ }
+
+ /**
+ * Searches for tasks that contain any of the specified keywords in their descriptions.
+ *
+ * @param keywords The keywords to search for within task descriptions.
+ * @return A string containing the matching tasks or a message if no matches are found.
+ */
+ public String findTask(String ...keywords) {
+
+ String matchedTasks = IntStream.range(0, tasks.size())
+ .filter(i -> containsKeywords(tasks.get(i), keywords))
+ .mapToObj(i -> String.format("%3d. %s", i + 1, tasks.get(i).toString()))
+ .collect(Collectors.joining(""));
+
+ if (matchedTasks.isEmpty()) {
+ return WoofMessage.TASKS_FOUND.toFormattedValue(
+ WoofMessage.NO_MATCHING_TASKS.toFormattedValue(),
+ getTaskCountMessage());
+ }
+
+ return WoofMessage.TASKS_FOUND.toFormattedValue(matchedTasks, getTaskCountMessage());
+ }
+
+ /**
+ * Checks if a task contains any of the specified keywords within its description.
+ *
+ * @param task The task to check for keyword matches.
+ * @param keywords The keywords to search for within the task's description.
+ * @return true if the task contains any of the specified keywords, false otherwise.
+ */
+ private boolean containsKeywords(Task task, String[] keywords) {
+ for (String keyword : keywords) {
+ if (task.hasKeyWord(keyword)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+
+ /**
+ * Returns a string indicating the number of tasks in the task list.
+ *
+ * @return A string mentioning the number of tasks in the task list.
+ */
+ public String getTaskCountMessage() {
+ return WoofMessage.TASK_LIST_COUNT.toFormattedValue(String.valueOf(this.size()));
+ }
+
+ /**
+ * Validates whether a task index is valid within the list.
+ *
+ * @param text The text representing the task index to validate.
+ */
+ public static void validateTaskIndex(String text, TaskList taskList) {
+ int index;
+ try {
+ index = Integer.parseInt(text);
+ } catch (NumberFormatException e) {
+ throw new WoofInvalidCommandException(
+ ExceptionMessage.NON_INTEGER_TASK_INDEX.toFormattedValue(text)
+ );
+ } catch (Exception e) {
+ throw new WoofInvalidCommandException(
+ ExceptionMessage.UNABLE_TO_PARSE_INDEX.toFormattedValue(text, e.getMessage())
+ );
+ }
+ if (index < 1 || index > taskList.size()) {
+ throw new WoofInvalidCommandException(
+ ExceptionMessage.TASK_INDEX_NOT_IN_LIST.toFormattedValue(text)
+ );
+ }
+ }
+}
diff --git a/src/main/java/tasks/TodoTask.java b/src/main/java/tasks/TodoTask.java
new file mode 100644
index 0000000000..9bf60a449f
--- /dev/null
+++ b/src/main/java/tasks/TodoTask.java
@@ -0,0 +1,88 @@
+package tasks;
+
+import java.util.Objects;
+
+import enums.TaskType;
+
+/**
+ * The `TodoTask` class represents a "TODO" task in the Woof application.
+ * It is a specific type of task that has no specific due date or event range.
+ */
+public class TodoTask extends Task {
+ /**
+ * Constructs a `TodoTask` with the given description.
+ *
+ * @param description The description of the "TODO" task.
+ */
+ public TodoTask(String description) {
+ super(description);
+ }
+
+ /**
+ * Constructs a `TodoTask` with the given description.
+ *
+ * @param description The description of the "TODO" task.
+ * @param isDone The optional is done to mark a task as per is done.
+ */
+ public TodoTask(String description, Boolean isDone) {
+ super(description, isDone);
+ }
+
+ /**
+ * Retrieves the type of task associated with this object, which is "DEADLINE."
+ *
+ * @return The task type, which is "TODO" for objects of this class.
+ */
+ @Override
+ public TaskType getTaskType() {
+ return TaskType.TODO;
+ }
+
+ /**
+ * Retrieves the datetime associated with "TODO", 0 since todo has no datetime associated.
+ *
+ * @return The start date and time as a long value.
+ */
+ @Override
+ public long getDateTimeLong() {
+ return 0;
+ }
+
+
+ /**
+ * Generates a string representation of the `TodoTask`.
+ *
+ * @return A string representation of the "TODO" task, including its status icon and description.
+ */
+ @Override
+ public String toString() {
+ return String.format("%s%s", TaskType.TODO.toSymbol(), super.toString());
+ }
+
+ /**
+ * Checks if this `TodoTask` is equal to another object.
+ *
+ * @param o The object to compare to.
+ * @return `true` if the objects are equal, `false` otherwise.
+ */
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (!(o instanceof TodoTask)) {
+ return false;
+ }
+ return super.equals(o);
+ }
+
+ /**
+ * Generates a hash code for this `TodoTask`.
+ *
+ * @return A hash code for the "TODO" task, including its description and completion status.
+ */
+ @Override
+ public int hashCode() {
+ return Objects.hash(super.hashCode());
+ }
+}
diff --git a/src/main/java/ui/Ui.java b/src/main/java/ui/Ui.java
new file mode 100644
index 0000000000..0474e07304
--- /dev/null
+++ b/src/main/java/ui/Ui.java
@@ -0,0 +1,58 @@
+package ui;
+
+import java.util.Scanner;
+
+import enums.WoofMessage;
+import woof.Woof;
+
+/**
+ * The `Ui` class provides user interface-related functionality for the Woof application.
+ * It includes methods for generating and returning messages, getting user input, and printing divider lines.
+ */
+public class Ui {
+ /**
+ * Generates and returns a welcome message when the Woof application starts.
+ *
+ * @return A welcome message.
+ */
+ public static String getHelloWorldMessage() {
+ return WoofMessage.HI.toFormattedValue();
+ }
+
+ /**
+ * Retrieves user input using a specified `Scanner` object and returns a user prompt.
+ *
+ * @param scanner The `Scanner` object for reading user input.
+ * @return A user prompt.
+ */
+ public static String getUserInput(Scanner scanner) {
+ return scanner.nextLine();
+ }
+
+ /**
+ * Generates and returns a title header for the bot's responses.
+ *
+ * @return A message prompt for the bot.
+ */
+ public static String getBotTitle() {
+ return getDividerLine() + "\nOreo: ";
+ }
+
+ /**
+ * Generates and returns a title header for the user's responses.
+ *
+ * @return A message prompt for the bot.
+ */
+ public static String getUserTitle() {
+ return getDividerLine() + "\nYou: ";
+ }
+
+ /**
+ * Generates and returns a horizontal divider line as a string to separate messages in the chat window.
+ *
+ * @return A string representing a horizontal divider line.
+ */
+ public static String getDividerLine() {
+ return '\n' + "═".repeat(Woof.getChatWidth());
+ }
+}
diff --git a/src/main/java/woof/Woof.java b/src/main/java/woof/Woof.java
new file mode 100644
index 0000000000..6b689c7283
--- /dev/null
+++ b/src/main/java/woof/Woof.java
@@ -0,0 +1,185 @@
+package woof;
+
+import java.time.LocalDate;
+import java.time.format.DateTimeFormatter;
+import java.time.format.DateTimeParseException;
+import java.util.Scanner;
+
+import command.Command;
+import command.NullCommand;
+import enums.ExceptionMessage;
+import exceptions.WoofException;
+import parser.Parser;
+import storage.TaskFileHandler;
+import tasks.TaskList;
+import ui.Ui;
+
+/**
+ * The `Woof` class is the main class for the Woof CLI application.
+ * It handles user interactions and the core functionality of the application.
+ */
+public class Woof {
+ private static final int CHAT_WIDTH = 60;
+
+ /**
+ * The main entry point for the application.
+ *
+ * @param args Command-line arguments (not used in this application).
+ */
+ public static void main(String[] args) {
+ runWoofCli();
+ }
+
+ /**
+ * Runs the Woof CLI application, allowing the user to interact with it via the command line.
+ */
+ public static void runWoofCli() {
+ Scanner scanner = new Scanner(System.in);
+ System.out.print(Ui.getHelloWorldMessage());
+ converse(scanner);
+ scanner.close();
+ }
+
+ /**
+ * Handles the conversation loop of the Woof CLI application.
+ *
+ * @param scanner The Scanner object for user input.
+ */
+ public static void converse(Scanner scanner) {
+ Command command = new NullCommand("");
+ while (!command.isByeCommand()) {
+ TaskList taskList = TaskFileHandler.readFromFile();
+
+ System.out.println(Ui.getUserTitle());
+ String rawCommand = Ui.getUserInput(scanner);
+
+ System.out.println(Ui.getBotTitle());
+ command = Parser.parse(rawCommand);
+ String response = command.execute(taskList);
+ String wrappedResponse = wrapText(response, "" , getChatWidth());
+ System.out.println(wrappedResponse);
+
+ TaskFileHandler.saveToFile(taskList);
+ }
+ }
+
+ /**
+ * Validates input date-time string using the application date-time formatter.
+ *
+ * @param string The date-time string to validate.
+ */
+ public static void validateInDateTime(String string) {
+ assert string != null : "datetime string cannot be null";
+
+ try {
+ parseDateTimeIn(string);
+ } catch (DateTimeParseException e) {
+ throw new WoofException(ExceptionMessage.INVALID_DATE_TIME_FORMAT.toFormattedValue(string));
+ }
+ }
+
+ /**
+ * Updates the task list by executing a command, saving the file, and returning the result.
+ *
+ * @param command The command to be executed.
+ * @return The result message of executing the command.
+ */
+ public static String updateFileAndExecute(Command command) {
+ assert command != null : "command cannot be null";
+
+ TaskList taskList = TaskFileHandler.readFromFile();
+ String result = command.execute(taskList);
+ TaskFileHandler.saveToFile(taskList);
+ return result;
+ }
+
+ /**
+ * Adds a separator every specified length to a text, wrapping it.
+ *
+ * @param message The text to be wrapped.
+ * @param separator The separator to be inserted when the text wraps.
+ * @param length The maximum length of each line before wrapping.
+ * @return The wrapped text with separators.
+ */
+ public static String wrapText(String message, String separator, int length) {
+ assert message != null : "text cannot be null";
+ assert separator != null : "separator cannot be null";
+ assert length > 0 : "length has to be more than 0";
+
+ int currentLineLength = 0;
+ StringBuilder result = new StringBuilder();
+
+ for (int i = 0; i < message.length(); i++) {
+ char currentChar = message.charAt(i);
+ if (currentChar == '\n') {
+ result.append(currentChar);
+ result.append(separator);
+ currentLineLength = -1;
+ } else if (currentLineLength >= length) {
+ result.append('\n');
+ result.append(separator);
+ result.append(currentChar);
+ currentLineLength = 0;
+ } else {
+ result.append(currentChar);
+ }
+ currentLineLength++;
+ }
+ return result.toString();
+ }
+
+ /**
+ * Provides the DateTimeFormatter for parsing and formatting input dates.
+ *
+ * @return The DateTimeFormatter for input date strings in ISO_LOCAL_DATE format.
+ */
+ public static DateTimeFormatter getDateTimeInFormatter() {
+ return DateTimeFormatter.ISO_LOCAL_DATE;
+ }
+
+ /**
+ * Provides the DateTimeFormatter for formatting dates to be presented to the user.
+ *
+ * @return The DateTimeFormatter for output date strings in "dd MMM yyyy" format.
+ */
+ public static DateTimeFormatter getDateTimeOutFormatter() {
+ return DateTimeFormatter.ofPattern("dd MMM yyyy");
+ }
+
+ /**
+ * Parses a date string received by the application using the input formatter.
+ *
+ * @param dateString The date string to be parsed.
+ * @return The parsed LocalDate.
+ * @throws WoofException If the date string is invalid and cannot be parsed.
+ */
+ public static LocalDate parseDateTimeIn(String dateString) {
+ assert dateString != null : "Date string cannot be null";
+
+ try {
+ return LocalDate.parse(dateString, getDateTimeInFormatter());
+ } catch (DateTimeParseException e) {
+ throw new WoofException(ExceptionMessage.INVALID_DATE_TIME_FORMAT.toFormattedValue(dateString));
+ }
+ }
+
+ /**
+ * Get the chat width for chat formatting.
+ */
+ public static int getChatWidth() {
+ return Woof.CHAT_WIDTH;
+ }
+
+ /**
+ * Formats a LocalDate into a user-friendly date string for presentation.
+ *
+ * @param date The LocalDate to format.
+ * @return A formatted date string.
+ * @throws IllegalArgumentException If the input LocalDate is null.
+ */
+ public static String parseDateTimeOut(LocalDate date) {
+ assert date != null : "Date cannot be null";
+
+ return date.format(getDateTimeOutFormatter());
+ }
+}
diff --git a/src/main/java/woofwoof/DialogBox.java b/src/main/java/woofwoof/DialogBox.java
new file mode 100644
index 0000000000..d66c644283
--- /dev/null
+++ b/src/main/java/woofwoof/DialogBox.java
@@ -0,0 +1,165 @@
+package woofwoof;
+
+import javafx.collections.FXCollections;
+import javafx.collections.ObservableList;
+import javafx.geometry.Insets;
+import javafx.geometry.Pos;
+import javafx.scene.Node;
+import javafx.scene.effect.DropShadow;
+import javafx.scene.image.Image;
+import javafx.scene.layout.HBox;
+import javafx.scene.layout.StackPane;
+import javafx.scene.paint.Color;
+import javafx.scene.paint.ImagePattern;
+import javafx.scene.shape.Circle;
+import javafx.scene.shape.Rectangle;
+import javafx.scene.text.Text;
+
+
+/**
+ * The `DialogBox` class represents a graphical dialog box for displaying messages in the GUI application.
+ * It provides methods for creating user and application dialog boxes, flipping their alignment,
+ * and setting text wrapping.
+ */
+public class DialogBox extends HBox {
+ /**
+ * Private constructor to create a new `DialogBox`.
+ *
+ * @param message The text content to be displayed in the dialog box.
+ * @param img The image to be displayed in the dialog box.
+ */
+ private DialogBox(String message, Image img) {
+ assert message != null : "message cannot be null";
+ assert img != null : "image cannot be null";
+
+ this.setPadding(new Insets(10.0, 0.0, 10.0, 0.0));
+ DropShadow dropShadow = createDropShadow();
+ Circle displayPicture = createDisplayPicture(img);
+ Text text = createText(message);
+ Rectangle messageBubble = createMessageBubble(text);
+
+ displayPicture.setEffect(dropShadow);
+ messageBubble.setEffect(dropShadow);
+ StackPane bubbleDialog = createBubbleDialog(messageBubble, text);
+ this.getChildren().addAll(bubbleDialog, displayPicture);
+ }
+
+ /**
+ * Create and configure the drop shadow effect for elements.
+ *
+ * @return The configured DropShadow effect.
+ */
+ private DropShadow createDropShadow() {
+ DropShadow dropShadow = new DropShadow();
+ dropShadow.setRadius(5.0);
+ dropShadow.setColor(new Color(0.0, 0.0, 0.0, 0.5));
+ return dropShadow;
+ }
+
+ /**
+ * Create and configure the display picture (circle with an image fill).
+ *
+ * @param img The display image to be displayed.
+ * @return The configured Circle element for the display picture.
+ */
+ private Circle createDisplayPicture(Image img) {
+ assert img != null : "image cannot be null";
+
+ Circle displayPicture = new Circle(0, 0, 50);
+ displayPicture.setFill(new ImagePattern(img));
+ displayPicture.setEffect(createDropShadow());
+ return displayPicture;
+ }
+
+ /**
+ * Create a Text element with the given message content.
+ *
+ * @param message The text content to be displayed.
+ * @return The configured Text element.
+ */
+ private Text createText(String message) {
+ assert message != null : "message cannot be null";
+
+ return new Text(message);
+ }
+
+ /**
+ * Create and configure the message bubble (rounded rectangle).
+ *
+ * @param text The Text element to calculate the height of the bubble.
+ * @return The configured Rectangle element for the message bubble.
+ */
+ private Rectangle createMessageBubble(Text text) {
+ assert text != null : "text cannot be null";
+
+ text.setFont(WoofWoof.getFont());
+ double messageBubbleHeight = text.getBoundsInLocal().getHeight() + 40;
+ Rectangle messageBubble = new Rectangle(560, messageBubbleHeight);
+ messageBubble.setFill(Color.WHITE);
+ messageBubble.setArcWidth(40);
+ messageBubble.setArcHeight(40);
+ messageBubble.setEffect(createDropShadow());
+ return messageBubble;
+ }
+
+ /**
+ * Create a StackPane to combine the message bubble and text message.
+ *
+ * @param messageBubble The message bubble element.
+ * @param text The text message element.
+ * @return The configured StackPane containing the message bubble and text message.
+ */
+ private StackPane createBubbleDialog(Rectangle messageBubble, Text text) {
+ assert text != null : "message cannot be null";
+
+ Insets bubbleMargin = new Insets(20.0); // top, right, bottom, left
+ StackPane bubbleDialog = new StackPane(messageBubble, text);
+ StackPane.setMargin(text, bubbleMargin);
+ bubbleDialog.setAlignment(Pos.TOP_LEFT);
+ bubbleDialog.setStyle(
+ "-fx-padding: 0px 20px 0px 20px;"
+ );
+ return bubbleDialog;
+ }
+
+ /**
+ * Flips the dialog box, changing the alignment to place the display picture on the left and message bubble
+ * on the right.
+ */
+ private void flip() {
+ this.setAlignment(Pos.TOP_LEFT);
+ ObservableList tmp = FXCollections.observableArrayList(this.getChildren());
+ FXCollections.reverse(tmp);
+ this.getChildren().setAll(tmp);
+ }
+
+ /**
+ * Creates a new user dialog box.
+ *
+ * @param message The message content to be displayed in the user dialog box.
+ * @param img The display image to be displayed in the user dialog box.
+ * @return A new `DialogBox` representing the user's dialog.
+ */
+ public static DialogBox getUserDialog(String message, Image img) {
+ assert message != null : "message cannot be null";
+ assert img != null : "image cannot be null";
+
+ return new DialogBox(message, img);
+ }
+
+ /**
+ * Creates a new dialog box and flips it to place the ImageView on the left and message on the right.
+ *
+ * @param message The message content to be displayed in the application dialog box.
+ * @param img The display image to be displayed in the application dialog box.
+ * @return A new `DialogBox` representing the application's dialog.
+ */
+ public static DialogBox getBotDialog(String message, Image img) {
+ assert message != null : "message cannot be null";
+ assert img != null : "image cannot be null";
+
+ var db = new DialogBox(message, img);
+ db.flip();
+ return db;
+ }
+}
diff --git a/src/main/java/woofwoof/Launcher.java b/src/main/java/woofwoof/Launcher.java
new file mode 100644
index 0000000000..5024d20e7b
--- /dev/null
+++ b/src/main/java/woofwoof/Launcher.java
@@ -0,0 +1,25 @@
+package woofwoof;
+
+import java.util.Objects;
+
+import javafx.application.Application;
+import woof.Woof;
+
+/**
+ * The Launcher class is responsible for launching the WoofWoof application.
+ */
+public class Launcher {
+ /**
+ * The main method of the Launcher class.
+ *
+ * @param args The command-line arguments. If 'cli' is provided as an argument, it launches the CLI mode;
+ * otherwise, it launches the GUI mode.
+ */
+ public static void main(String[] args) {
+ if (args.length != 1 || !Objects.equals(args[0], "cli")) {
+ Application.launch(WoofWoof.class, args);
+ } else {
+ Woof.main(null);
+ }
+ }
+}
diff --git a/src/main/java/woofwoof/WoofWoof.java b/src/main/java/woofwoof/WoofWoof.java
new file mode 100644
index 0000000000..deb6a6fa9c
--- /dev/null
+++ b/src/main/java/woofwoof/WoofWoof.java
@@ -0,0 +1,256 @@
+package woofwoof;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URL;
+import java.util.Objects;
+import java.util.concurrent.Executors;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.TimeUnit;
+
+import command.Command;
+import enums.FilePath;
+import enums.WoofMessage;
+import javafx.application.Application;
+import javafx.application.Platform;
+import javafx.fxml.FXML;
+import javafx.fxml.FXMLLoader;
+import javafx.scene.ImageCursor;
+import javafx.scene.Scene;
+import javafx.scene.control.Button;
+import javafx.scene.control.ScrollPane;
+import javafx.scene.control.TextArea;
+import javafx.scene.image.Image;
+import javafx.scene.image.PixelWriter;
+import javafx.scene.image.WritableImage;
+import javafx.scene.input.KeyCode;
+import javafx.scene.layout.VBox;
+import javafx.scene.paint.Color;
+import javafx.scene.text.Font;
+import javafx.stage.Stage;
+import parser.Parser;
+import woof.Woof;
+
+/**
+ * The `WoofWoof` class is the main class for the Woof GUI application.
+ * The `WoofWoof` class relies on the existence of the `Woof` class, as it only extends `Woof` to support GUI.
+ */
+public class WoofWoof extends Application {
+ private static final Font FONT = loadCustomFont();
+
+ @FXML
+ private ScrollPane scrollPane;
+ @FXML
+ private VBox dialogArea;
+ @FXML
+ private Button clearButton;
+ @FXML
+ private TextArea userInput;
+ @FXML
+ private Button sendButton;
+ private Scene scene;
+
+ private Woof woof;
+ private final Image user = getImageFromFilePath(FilePath.USER_DISPLAY_PICTURE);
+ private final Image bot = getImageFromFilePath(FilePath.BOT_DISPLAY_PICTURE);
+
+
+ /**
+ * Retrieves an image from a file path. If the file path is invalid or the resource is not found,
+ * it returns an empty image.
+ *
+ * @param filePath The path to the image file.
+ * @return An Image object loaded from the specified file path or an empty image if not found.
+ */
+ public Image getImageFromFilePath(FilePath filePath) {
+ InputStream inputStream;
+ try {
+ inputStream = Objects.requireNonNull(this.getClass().getResourceAsStream(filePath.toValue()));
+ } catch (NullPointerException e) {
+ return getEmptyImage();
+ }
+ return new Image(inputStream);
+ }
+
+ /**
+ * Generates an empty image with a single pixel of gray color (0.5, 0.5, 0.5, 1).
+ *
+ * @return An empty Image object with a single gray pixel.
+ */
+ public Image getEmptyImage() {
+ WritableImage img = new WritableImage(1, 1);
+ PixelWriter pw = img.getPixelWriter();
+ pw.setColor(0, 0, new Color(0.5, 0.5, 0.5, 1));
+ return img;
+ }
+
+ /**
+ * Initializes the WoofWoof application by reading the task list from a file
+ * and loading the font.
+ */
+ @FXML
+ public void initialize() {
+ this.userInput.setOnKeyPressed(event -> {
+ if (event.getCode() == KeyCode.ENTER) {
+ handleUserSubmit();
+ }
+ });
+ this.sendButton.setOnMouseEntered(e -> this.sendButton.setStyle("-fx-base: #C1E1C1"));
+ this.sendButton.setOnMouseExited(e -> this.sendButton.setStyle("-fx-base: #C5CBEC"));
+ this.clearButton.setOnMouseEntered(e -> this.clearButton.setStyle("-fx-base: #FAA0A0"));
+ this.clearButton.setOnMouseExited(e -> this.clearButton.setStyle("-fx-base: #C5CBEC"));
+ this.sendButton.setOnMouseClicked(event -> handleUserSubmit());
+
+ this.userInput.setFont(getFont());
+ this.sendButton.setFont(getFont());
+ this.clearButton.setFont(getFont());
+
+ this.dialogArea.getChildren().addAll(
+ DialogBox.getBotDialog(
+ Woof.wrapText(WoofMessage.HI.toFormattedValue(), "", Woof.getChatWidth()), this.bot
+ )
+ );
+ this.dialogArea.prefHeightProperty().bind(this.scrollPane.heightProperty());
+ }
+
+ /**
+ * Sets the Woof instance for the application.
+ *
+ * @param w The Woof instance to set.
+ */
+ public void setWoof(Woof w) {
+ this.woof = w;
+ }
+
+ /**
+ * Starts the WoofWoof application.
+ *
+ * @param primaryStage The primary stage for the application.
+ */
+ @Override
+ public void start(Stage primaryStage) {
+ try {
+ FXMLLoader fxmlLoader = new FXMLLoader(WoofWoof.class.getResource(FilePath.FXML_VIEW_PATH.toValue()));
+ this.scene = new Scene(fxmlLoader.load());
+ setCustomCursorOnApplication();
+ loadCssStyles();
+ primaryStage.setScene(this.scene);
+ primaryStage.setTitle(WoofMessage.WOOF_TITLE.toFormattedValue());
+ primaryStage.setMinHeight(290.0);
+ fxmlLoader.getController().setWoof(this.woof);
+ primaryStage.show();
+ primaryStage.maxWidthProperty().bind(primaryStage.widthProperty());
+ primaryStage.minWidthProperty().bind(primaryStage.widthProperty());
+ } catch (IOException e) {
+ System.out.printf("really oh no\n%s\n", e.getMessage());
+ }
+ }
+
+ /**
+ * Loads the font and sets the custom font for the class.
+ */
+ private static Font loadCustomFont() {
+ URL fontResourceUrl = WoofWoof.class.getResource(FilePath.CUSTOM_FONT.toValue());
+ return Font.loadFont(Objects.requireNonNull(fontResourceUrl).toExternalForm(), 14);
+ }
+
+ /**
+ * Gets the custom font.
+ */
+ public static Font getFont() {
+ return WoofWoof.FONT;
+ }
+
+ /**
+ * Sets up and applies a custom cursor when entering a JavaFX scene and handles cursor reset on click.
+ */
+ private void setCustomCursorOnApplication() {
+ String imagePath = FilePath.CUSTOM_CURSOR.toValue();
+ Image defaultCursorImage = new Image(imagePath, 40, 40, false, true);
+ ImageCursor defaultCursor = new ImageCursor(defaultCursorImage, 20, 20);
+ this.scene.setCursor(defaultCursor);
+ }
+
+ /**
+ * Load CSS styles from file paths and apply them to the scene.
+ */
+ private void loadCssStyles() {
+ String[] cssFilePaths = {
+ FilePath.ROOT_CSS.toValue(),
+ FilePath.SCROLL_PANE_CSS.toValue(),
+ FilePath.DIALOG_AREA_CSS.toValue(),
+ FilePath.CLEAR_BUTTON_CSS.toValue(),
+ FilePath.USER_INPUT_CSS.toValue(),
+ FilePath.SEND_BUTTON_CSS.toValue(),
+ };
+ for (String cssFilePath : cssFilePaths) {
+ String css = Objects.requireNonNull(getClass().getResource(cssFilePath)).toExternalForm();
+ this.scene.getStylesheets().add(css);
+ }
+ }
+
+ /**
+ * Handles user submit, generates responses, and updates the dialog.
+ */
+ @FXML
+ private void handleUserSubmit() {
+ String message = this.userInput.getText().trim();
+ if (!message.isEmpty()) {
+ String response = processMessage(message);
+ this.dialogArea.getChildren().addAll(
+ DialogBox.getUserDialog(Woof.wrapText(message, "", Woof.getChatWidth()), this.user),
+ DialogBox.getBotDialog(Woof.wrapText(response, "", Woof.getChatWidth()), this.bot)
+ );
+ this.userInput.clear();
+ }
+ Platform.runLater(() -> Platform.runLater(()->this.scrollPane.setVvalue(1.0)));
+ }
+
+ /**
+ * Handles user clear, clears the dialog area and text area.
+ */
+ @FXML
+ private void handleUserClear() {
+ this.dialogArea.getChildren().clear();
+ this.userInput.clear();
+ }
+
+ /**
+ * Schedule the closing of the JavaFX stage after a 1-second delay.
+ */
+ private void scheduleCloseAfterDelay() {
+ ScheduledExecutorService executorService = Executors.newSingleThreadScheduledExecutor();
+ executorService.schedule(() -> Platform.runLater(() -> {
+ Stage currentStage = (Stage) this.dialogArea.getScene().getWindow();
+ currentStage.close();
+ executorService.shutdown();
+ }),
+ 1, TimeUnit.SECONDS
+ );
+ }
+
+ /**
+ * Processes the message and generates a response to user input.
+ *
+ * @param message The user's message.
+ * @return The response message.
+ */
+ private String processMessage(String message) {
+ assert message != null : "message cannot be null";
+
+ Command command = Parser.parse(message);
+ if (command.isByeCommand()) {
+ scheduleCloseAfterDelay();
+ }
+ return Woof.updateFileAndExecute(command);
+ }
+
+ /**
+ * The main entry point of the WoofWoof application.
+ *
+ * @param args The command-line arguments (not used in JavaFX applications).
+ */
+ public static void main(String[] args) {
+ launch(args);
+ }
+}
diff --git a/src/main/resources/fonts/sono/OFL.txt b/src/main/resources/fonts/sono/OFL.txt
new file mode 100644
index 0000000000..0ed8abb251
--- /dev/null
+++ b/src/main/resources/fonts/sono/OFL.txt
@@ -0,0 +1,93 @@
+Copyright 2020 The Sono Project Authors (https://github.com/sursly/sono)
+
+This Font Software is licensed under the SIL Open Font License, Version 1.1.
+This license is copied below, and is also available with a FAQ at:
+http://scripts.sil.org/OFL
+
+
+-----------------------------------------------------------
+SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
+-----------------------------------------------------------
+
+PREAMBLE
+The goals of the Open Font License (OFL) are to stimulate worldwide
+development of collaborative font projects, to support the font creation
+efforts of academic and linguistic communities, and to provide a free and
+open framework in which fonts may be shared and improved in partnership
+with others.
+
+The OFL allows the licensed fonts to be used, studied, modified and
+redistributed freely as long as they are not sold by themselves. The
+fonts, including any derivative works, can be bundled, embedded,
+redistributed and/or sold with any software provided that any reserved
+names are not used by derivative works. The fonts and derivatives,
+however, cannot be released under any other type of license. The
+requirement for fonts to remain under this license does not apply
+to any document created using the fonts or their derivatives.
+
+DEFINITIONS
+"Font Software" refers to the set of files released by the Copyright
+Holder(s) under this license and clearly marked as such. This may
+include source files, build scripts and documentation.
+
+"Reserved Font Name" refers to any names specified as such after the
+copyright statement(s).
+
+"Original Version" refers to the collection of Font Software components as
+distributed by the Copyright Holder(s).
+
+"Modified Version" refers to any derivative made by adding to, deleting,
+or substituting -- in part or in whole -- any of the components of the
+Original Version, by changing formats or by porting the Font Software to a
+new environment.
+
+"Author" refers to any designer, engineer, programmer, technical
+writer or other person who contributed to the Font Software.
+
+PERMISSION & CONDITIONS
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of the Font Software, to use, study, copy, merge, embed, modify,
+redistribute, and sell modified and unmodified copies of the Font
+Software, subject to the following conditions:
+
+1) Neither the Font Software nor any of its individual components,
+in Original or Modified Versions, may be sold by itself.
+
+2) Original or Modified Versions of the Font Software may be bundled,
+redistributed and/or sold with any software, provided that each copy
+contains the above copyright notice and this license. These can be
+included either as stand-alone text files, human-readable headers or
+in the appropriate machine-readable metadata fields within text or
+binary files as long as those fields can be easily viewed by the user.
+
+3) No Modified Version of the Font Software may use the Reserved Font
+Name(s) unless explicit written permission is granted by the corresponding
+Copyright Holder. This restriction only applies to the primary font name as
+presented to the users.
+
+4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
+Software shall not be used to promote, endorse or advertise any
+Modified Version, except to acknowledge the contribution(s) of the
+Copyright Holder(s) and the Author(s) or with their explicit written
+permission.
+
+5) The Font Software, modified or unmodified, in part or in whole,
+must be distributed entirely under this license, and must not be
+distributed under any other license. The requirement for fonts to
+remain under this license does not apply to any document created
+using the Font Software.
+
+TERMINATION
+This license becomes null and void if any of the above conditions are
+not met.
+
+DISCLAIMER
+THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
+OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
+COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
+DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
+OTHER DEALINGS IN THE FONT SOFTWARE.
diff --git a/src/main/resources/fonts/sono/README.txt b/src/main/resources/fonts/sono/README.txt
new file mode 100644
index 0000000000..261ee8dcfd
--- /dev/null
+++ b/src/main/resources/fonts/sono/README.txt
@@ -0,0 +1,77 @@
+Sono Variable Font
+==================
+
+This download contains Sono as both a variable font and static fonts.
+
+Sono is a variable font with these axes:
+ MONO
+ wght
+
+This means all the styles are contained in a single file:
+ Sono-VariableFont_MONO,wght.ttf
+
+If your app fully supports variable fonts, you can now pick intermediate styles
+that aren’t available as static fonts. Not all apps support variable fonts, and
+in those cases you can use the static font files for Sono:
+ static/Sono_Proportional-ExtraLight.ttf
+ static/Sono_Proportional-Light.ttf
+ static/Sono_Proportional-Regular.ttf
+ static/Sono_Proportional-Medium.ttf
+ static/Sono_Proportional-SemiBold.ttf
+ static/Sono_Proportional-Bold.ttf
+ static/Sono_Proportional-ExtraBold.ttf
+ static/Sono-ExtraLight.ttf
+ static/Sono-Light.ttf
+ static/Sono-Regular.ttf
+ static/Sono-Medium.ttf
+ static/Sono-SemiBold.ttf
+ static/Sono-Bold.ttf
+ static/Sono-ExtraBold.ttf
+
+Get started
+-----------
+
+1. Install the font files you want to use
+
+2. Use your app's font picker to view the font family and all the
+available styles
+
+Learn more about variable fonts
+-------------------------------
+
+ https://developers.google.com/web/fundamentals/design-and-ux/typography/variable-fonts
+ https://variablefonts.typenetwork.com
+ https://medium.com/variable-fonts
+
+In desktop apps
+
+ https://theblog.adobe.com/can-variable-fonts-illustrator-cc
+ https://helpx.adobe.com/nz/photoshop/using/fonts.html#variable_fonts
+
+Online
+
+ https://developers.google.com/fonts/docs/getting_started
+ https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Fonts/Variable_Fonts_Guide
+ https://developer.microsoft.com/en-us/microsoft-edge/testdrive/demos/variable-fonts
+
+Installing fonts
+
+ MacOS: https://support.apple.com/en-us/HT201749
+ Linux: https://www.google.com/search?q=how+to+install+a+font+on+gnu%2Blinux
+ Windows: https://support.microsoft.com/en-us/help/314960/how-to-install-or-remove-a-font-in-windows
+
+Android Apps
+
+ https://developers.google.com/fonts/docs/android
+ https://developer.android.com/guide/topics/ui/look-and-feel/downloadable-fonts
+
+License
+-------
+Please read the full license text (OFL.txt) to understand the permissions,
+restrictions and requirements for usage, redistribution, and modification.
+
+You can use them in your products & projects – print or digital,
+commercial or otherwise.
+
+This isn't legal advice, please consider consulting a lawyer and see the full
+license for all details.
diff --git a/src/main/resources/fonts/sono/Sono-VariableFont_MONO,wght.ttf b/src/main/resources/fonts/sono/Sono-VariableFont_MONO,wght.ttf
new file mode 100644
index 0000000000..4793a34b80
Binary files /dev/null and b/src/main/resources/fonts/sono/Sono-VariableFont_MONO,wght.ttf differ
diff --git a/src/main/resources/fonts/sono/static/Sono-Bold.ttf b/src/main/resources/fonts/sono/static/Sono-Bold.ttf
new file mode 100644
index 0000000000..e403d1cc40
Binary files /dev/null and b/src/main/resources/fonts/sono/static/Sono-Bold.ttf differ
diff --git a/src/main/resources/fonts/sono/static/Sono-ExtraBold.ttf b/src/main/resources/fonts/sono/static/Sono-ExtraBold.ttf
new file mode 100644
index 0000000000..841477d983
Binary files /dev/null and b/src/main/resources/fonts/sono/static/Sono-ExtraBold.ttf differ
diff --git a/src/main/resources/fonts/sono/static/Sono-ExtraLight.ttf b/src/main/resources/fonts/sono/static/Sono-ExtraLight.ttf
new file mode 100644
index 0000000000..a4501c601b
Binary files /dev/null and b/src/main/resources/fonts/sono/static/Sono-ExtraLight.ttf differ
diff --git a/src/main/resources/fonts/sono/static/Sono-Light.ttf b/src/main/resources/fonts/sono/static/Sono-Light.ttf
new file mode 100644
index 0000000000..1a893377ee
Binary files /dev/null and b/src/main/resources/fonts/sono/static/Sono-Light.ttf differ
diff --git a/src/main/resources/fonts/sono/static/Sono-Medium.ttf b/src/main/resources/fonts/sono/static/Sono-Medium.ttf
new file mode 100644
index 0000000000..ee40d6c1df
Binary files /dev/null and b/src/main/resources/fonts/sono/static/Sono-Medium.ttf differ
diff --git a/src/main/resources/fonts/sono/static/Sono-Regular.ttf b/src/main/resources/fonts/sono/static/Sono-Regular.ttf
new file mode 100644
index 0000000000..82eb78f5e1
Binary files /dev/null and b/src/main/resources/fonts/sono/static/Sono-Regular.ttf differ
diff --git a/src/main/resources/fonts/sono/static/Sono-SemiBold.ttf b/src/main/resources/fonts/sono/static/Sono-SemiBold.ttf
new file mode 100644
index 0000000000..b994cdfcaa
Binary files /dev/null and b/src/main/resources/fonts/sono/static/Sono-SemiBold.ttf differ
diff --git a/src/main/resources/fonts/sono/static/Sono_Proportional-Bold.ttf b/src/main/resources/fonts/sono/static/Sono_Proportional-Bold.ttf
new file mode 100644
index 0000000000..42ce09ed8c
Binary files /dev/null and b/src/main/resources/fonts/sono/static/Sono_Proportional-Bold.ttf differ
diff --git a/src/main/resources/fonts/sono/static/Sono_Proportional-ExtraBold.ttf b/src/main/resources/fonts/sono/static/Sono_Proportional-ExtraBold.ttf
new file mode 100644
index 0000000000..11088fa0e0
Binary files /dev/null and b/src/main/resources/fonts/sono/static/Sono_Proportional-ExtraBold.ttf differ
diff --git a/src/main/resources/fonts/sono/static/Sono_Proportional-ExtraLight.ttf b/src/main/resources/fonts/sono/static/Sono_Proportional-ExtraLight.ttf
new file mode 100644
index 0000000000..6bd23dd93e
Binary files /dev/null and b/src/main/resources/fonts/sono/static/Sono_Proportional-ExtraLight.ttf differ
diff --git a/src/main/resources/fonts/sono/static/Sono_Proportional-Light.ttf b/src/main/resources/fonts/sono/static/Sono_Proportional-Light.ttf
new file mode 100644
index 0000000000..ed31dfb925
Binary files /dev/null and b/src/main/resources/fonts/sono/static/Sono_Proportional-Light.ttf differ
diff --git a/src/main/resources/fonts/sono/static/Sono_Proportional-Medium.ttf b/src/main/resources/fonts/sono/static/Sono_Proportional-Medium.ttf
new file mode 100644
index 0000000000..10c8bc26ef
Binary files /dev/null and b/src/main/resources/fonts/sono/static/Sono_Proportional-Medium.ttf differ
diff --git a/src/main/resources/fonts/sono/static/Sono_Proportional-Regular.ttf b/src/main/resources/fonts/sono/static/Sono_Proportional-Regular.ttf
new file mode 100644
index 0000000000..99367c3878
Binary files /dev/null and b/src/main/resources/fonts/sono/static/Sono_Proportional-Regular.ttf differ
diff --git a/src/main/resources/fonts/sono/static/Sono_Proportional-SemiBold.ttf b/src/main/resources/fonts/sono/static/Sono_Proportional-SemiBold.ttf
new file mode 100644
index 0000000000..32ee6416cb
Binary files /dev/null and b/src/main/resources/fonts/sono/static/Sono_Proportional-SemiBold.ttf differ
diff --git a/src/main/resources/images/botDisplayPicture.jpeg b/src/main/resources/images/botDisplayPicture.jpeg
new file mode 100644
index 0000000000..c4e5e19df8
Binary files /dev/null and b/src/main/resources/images/botDisplayPicture.jpeg differ
diff --git a/src/main/resources/images/paw.png b/src/main/resources/images/paw.png
new file mode 100644
index 0000000000..a5c37be4c2
Binary files /dev/null and b/src/main/resources/images/paw.png differ
diff --git a/src/main/resources/images/paws.png b/src/main/resources/images/paws.png
new file mode 100644
index 0000000000..1f6d667dab
Binary files /dev/null and b/src/main/resources/images/paws.png differ
diff --git a/src/main/resources/images/userDisplayPicture.jpeg b/src/main/resources/images/userDisplayPicture.jpeg
new file mode 100644
index 0000000000..294da37a50
Binary files /dev/null and b/src/main/resources/images/userDisplayPicture.jpeg differ
diff --git a/src/main/resources/styles/clearButton.css b/src/main/resources/styles/clearButton.css
new file mode 100644
index 0000000000..d28eab081b
--- /dev/null
+++ b/src/main/resources/styles/clearButton.css
@@ -0,0 +1,5 @@
+#clearButton {
+ -fx-base: #C5CBEC;
+ -fx-pref-height: 60px;
+ -fx-pref-width: 100px;
+}
diff --git a/src/main/resources/styles/dialogArea.css b/src/main/resources/styles/dialogArea.css
new file mode 100644
index 0000000000..6612b8331d
--- /dev/null
+++ b/src/main/resources/styles/dialogArea.css
@@ -0,0 +1,5 @@
+#dialogArea {
+ -fx-background-color: #C5CBEC;
+ -fx-pref-width: 740.0;
+ -fx-padding: 10px 20px 0px 20px;
+}
diff --git a/src/main/resources/styles/root.css b/src/main/resources/styles/root.css
new file mode 100644
index 0000000000..437a6463a0
--- /dev/null
+++ b/src/main/resources/styles/root.css
@@ -0,0 +1,5 @@
+.root {
+ -fx-pref-height: 800px;
+ -fx-pref-width: 800px;
+ -fx-background: #AFBAEA;
+}
diff --git a/src/main/resources/styles/scrollPane.css b/src/main/resources/styles/scrollPane.css
new file mode 100644
index 0000000000..bc81bb4d8f
--- /dev/null
+++ b/src/main/resources/styles/scrollPane.css
@@ -0,0 +1,23 @@
+#scrollPane {
+ -fx-background-color: #C5CBEC;
+ -fx-base: #C5CBEC;
+}
+
+/* Hide the arrow buttons for the vertical scroll bar */
+#scrollPane .scroll-bar:vertical .increment-button,
+#scrollPane .scroll-bar:vertical .decrement-button {
+ -fx-padding: 0;
+ -fx-opacity: 0;
+}
+
+/* Adjust the width of the vertical scroll bar */
+#scrollPane .scroll-bar:vertical {
+ -fx-background-color: #C5CBEC;
+ -fx-pref-width: 20px;
+}
+
+/* Customize the thumb of the vertical scroll bar */
+#scrollPane .scroll-bar:vertical .thumb {
+ -fx-background-color: #8E9CE2;
+ -fx-border-radius: 20px;
+}
diff --git a/src/main/resources/styles/sendButton.css b/src/main/resources/styles/sendButton.css
new file mode 100644
index 0000000000..8ec640218a
--- /dev/null
+++ b/src/main/resources/styles/sendButton.css
@@ -0,0 +1,5 @@
+#sendButton {
+ -fx-base: #C5CBEC;
+ -fx-pref-height: 60px;
+ -fx-pref-width: 100px;
+}
diff --git a/src/main/resources/styles/userInput.css b/src/main/resources/styles/userInput.css
new file mode 100644
index 0000000000..66d2cff5b5
--- /dev/null
+++ b/src/main/resources/styles/userInput.css
@@ -0,0 +1,23 @@
+#userInput {
+ -fx-control-inner-background: #C5CBEC;
+ -fx-max-height: 60.0;
+}
+
+/* Hide the arrow buttons for the vertical scroll bar */
+#userInput .scroll-bar:vertical .increment-button,
+#userInput .scroll-bar:vertical .decrement-button {
+ -fx-padding: 0;
+ -fx-opacity: 0;
+}
+
+/* Adjust the width of the vertical scroll bar */
+#userInput .scroll-bar:vertical {
+ -fx-background-color: #C5CBEC;
+ -fx-pref-width: 20px;
+}
+
+/* Customize the thumb of the vertical scroll bar */
+#userInput .scroll-bar:vertical .thumb {
+ -fx-background-color: #8E9CE2;
+ -fx-border-radius: 20px;
+}
diff --git a/src/main/resources/views/WoofWoof.fxml b/src/main/resources/views/WoofWoof.fxml
new file mode 100644
index 0000000000..1b6cb5381f
--- /dev/null
+++ b/src/main/resources/views/WoofWoof.fxml
@@ -0,0 +1,46 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/test/java/command/ByeCommandTest.java b/src/test/java/command/ByeCommandTest.java
new file mode 100644
index 0000000000..46ec69d9c2
--- /dev/null
+++ b/src/test/java/command/ByeCommandTest.java
@@ -0,0 +1,67 @@
+package command;
+
+import static org.junit.jupiter.api.Assertions.assertAll;
+import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertThrowsExactly;
+
+import java.io.ByteArrayOutputStream;
+import java.io.PrintStream;
+
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+import exceptions.WoofInvalidCommandException;
+import tasks.TaskList;
+
+public class ByeCommandTest {
+ private final ByteArrayOutputStream outContent = new ByteArrayOutputStream();
+ private final PrintStream originalOut = System.out;
+ @BeforeEach
+ public void setUpStreams() {
+ // Redirect System.out to the ByteArrayOutputStream
+ System.setOut(new PrintStream(this.outContent));
+ }
+
+ @AfterEach
+ public void restoreStreams() {
+ // Restore the original System.out
+ System.setOut(this.originalOut);
+ }
+
+ @Test
+ public void testValidate() {
+ // Arrange, Act, Assert
+ assertAll((
+ ) -> assertDoesNotThrow((
+ ) -> ByeCommand.validate("bye")), (
+ ) -> assertThrowsExactly(WoofInvalidCommandException.class, (
+ ) -> ByeCommand.validate("")), (
+ ) -> assertThrowsExactly(WoofInvalidCommandException.class, (
+ ) -> ByeCommand.validate("bye some argument")), (
+ ) -> assertThrows(WoofInvalidCommandException.class, (
+ ) -> ByeCommand.validate("list")), (
+ ) -> assertThrows(WoofInvalidCommandException.class, (
+ ) -> ByeCommand.validate("todo some task")), (
+ ) -> assertThrows(WoofInvalidCommandException.class, (
+ ) -> ByeCommand.validate("deadline some task"))
+ );
+ }
+
+ @Test
+ public void testExecuteShowsByeMessage() {
+ // Arrange
+ TaskList taskList = new TaskList(null);
+ ByeCommand byeCommand = new ByeCommand("bye");
+
+ // Act
+ String actualOutput = byeCommand.execute(taskList);
+ String expectedOutput = "Bye. Hope to see you again soon!" + System.lineSeparator()
+ + "Offing myself... woof :(";
+
+ // Assert
+ assertEquals(expectedOutput, actualOutput);
+ }
+}
diff --git a/src/test/java/command/DeadlineCommandTest.java b/src/test/java/command/DeadlineCommandTest.java
new file mode 100644
index 0000000000..f2d983e61c
--- /dev/null
+++ b/src/test/java/command/DeadlineCommandTest.java
@@ -0,0 +1,83 @@
+package command;
+
+import static org.junit.jupiter.api.Assertions.assertAll;
+import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertThrowsExactly;
+
+import java.io.ByteArrayOutputStream;
+import java.io.PrintStream;
+import java.time.LocalDate;
+
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+import exceptions.WoofInvalidCommandException;
+import tasks.DeadlineTask;
+import tasks.Task;
+import tasks.TaskList;
+import woof.Woof;
+
+public class DeadlineCommandTest {
+ private final ByteArrayOutputStream outContent = new ByteArrayOutputStream();
+ private final PrintStream originalOut = System.out;
+ @BeforeEach
+ public void setUpStreams() {
+ // Redirect System.out to the ByteArrayOutputStream
+ System.setOut(new PrintStream(this.outContent));
+ }
+
+ @AfterEach
+ public void restoreStreams() {
+ // Restore the original System.out
+ System.setOut(this.originalOut);
+ }
+ @Test
+ public void testValidate() {
+ // Arrange, Act, Assert
+ assertAll((
+ ) -> assertDoesNotThrow((
+ ) -> DeadlineCommand.validate("deadline study /by 2023-01-01")), (
+ ) -> assertDoesNotThrow((
+ ) -> DeadlineCommand.validate("deadline study /by 2023-01-01")), (
+ ) -> assertThrowsExactly(WoofInvalidCommandException.class, (
+ ) -> DeadlineCommand.validate("")), (
+ ) -> assertThrowsExactly(WoofInvalidCommandException.class, (
+ ) -> DeadlineCommand.validate("deadline")), (
+ ) -> assertThrowsExactly(WoofInvalidCommandException.class, (
+ ) -> DeadlineCommand.validate("deadline study /by 2023/01/01")), (
+ ) -> assertThrowsExactly(WoofInvalidCommandException.class, (
+ ) -> DeadlineCommand.validate("deadline /by 2023-01-01"))
+ );
+ }
+
+ @Test
+ public void testExecuteCreateTask() {
+ // Arrange
+ DeadlineCommand deadlineCommand = new DeadlineCommand("deadline some task /by 2023-12-31");
+ LocalDate endDate = Woof.parseDateTimeIn("2023-12-31");
+ Task expectedTask = new DeadlineTask("some task", endDate);
+ TaskList taskList = new TaskList(null);
+
+ // Act
+ deadlineCommand.execute(taskList);
+
+ // Assert
+ Task retrievedTask = taskList.getTask(0);
+ assertEquals(expectedTask, retrievedTask);
+ }
+
+ @Test
+ public void testExecuteNoTaskCreatedIfValidationFail() {
+ // Arrange
+ TaskList taskList = new TaskList(null);
+ TodoCommand invalidCommand = new TodoCommand("dealine");
+
+ // Act
+ invalidCommand.execute(taskList);
+
+ // Assert
+ assertEquals(0, taskList.size());
+ }
+}
diff --git a/src/test/java/command/DeleteCommandTest.java b/src/test/java/command/DeleteCommandTest.java
new file mode 100644
index 0000000000..c95b7bc32d
--- /dev/null
+++ b/src/test/java/command/DeleteCommandTest.java
@@ -0,0 +1,92 @@
+package command;
+
+import static org.junit.jupiter.api.Assertions.assertAll;
+import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertThrowsExactly;
+
+import java.io.ByteArrayOutputStream;
+import java.io.PrintStream;
+import java.time.LocalDate;
+
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+import exceptions.WoofInvalidCommandException;
+import tasks.DeadlineTask;
+import tasks.EventTask;
+import tasks.Task;
+import tasks.TaskList;
+import tasks.TodoTask;
+import woof.Woof;
+
+public class DeleteCommandTest {
+ private final ByteArrayOutputStream outContent = new ByteArrayOutputStream();
+ private final PrintStream originalOut = System.out;
+ @BeforeEach
+ public void setUpStreams() {
+ // Redirect System.out to the ByteArrayOutputStream
+ System.setOut(new PrintStream(this.outContent));
+ }
+
+ @AfterEach
+ public void restoreStreams() {
+ // Restore the original System.out
+ System.setOut(this.originalOut);
+ }
+
+ @Test
+ public void testValidate() {
+ //Arrange
+ LocalDate startDate = Woof.parseDateTimeIn("2023-01-01");
+ LocalDate endDate = Woof.parseDateTimeIn("2023-12-31");
+ Task task1 = new TodoTask("some task 1");
+ Task task2 = new DeadlineTask("some task 2", endDate);
+ Task task3 = new EventTask("some task 2", startDate, endDate);
+ TaskList taskList = new TaskList(new Task[]{task1, task2, task3});
+
+ // Act, Assert
+ assertAll((
+ ) -> assertDoesNotThrow((
+ ) -> DeleteCommand.validate("delete 1", taskList)), (
+ ) -> assertDoesNotThrow((
+ ) -> DeleteCommand.validate("delete 2", taskList)), (
+ ) -> assertThrowsExactly(WoofInvalidCommandException.class, (
+ ) -> DeleteCommand.validate("", taskList)), (
+ ) -> assertThrowsExactly(WoofInvalidCommandException.class, (
+ ) -> DeleteCommand.validate("delete", taskList)), (
+ ) -> assertThrowsExactly(WoofInvalidCommandException.class, (
+ ) -> DeleteCommand.validate("delete a", taskList)), (
+ ) -> assertThrowsExactly(WoofInvalidCommandException.class, (
+ ) -> DeleteCommand.validate("delete 0", taskList)), (
+ ) -> assertThrowsExactly(WoofInvalidCommandException.class, (
+ ) -> DeleteCommand.validate("delete 4", taskList))
+ );
+ }
+
+ @Test
+ public void testExecuteDeleteTask() {
+ // Arrange
+ DeleteCommand deleteCommand = new DeleteCommand("delete 1");
+ TaskList taskList = new TaskList(new Task[]{new TodoTask("some task 1")});
+
+ deleteCommand.execute(taskList);
+
+ // Assert
+ assertEquals(taskList.size(), 0);
+ }
+
+ @Test
+ public void testExecuteNoTaskDeletedIfValidationFails() {
+ // Arrange
+ TaskList taskList = new TaskList(new Task[] {new TodoTask("Task 1")});
+ DeleteCommand invalidCommand = new DeleteCommand("delete 1?");
+
+ // Act
+ invalidCommand.execute(taskList);
+
+ // Assert
+ assertEquals(1, taskList.size());
+ }
+}
diff --git a/src/test/java/command/EventCommandTest.java b/src/test/java/command/EventCommandTest.java
new file mode 100644
index 0000000000..16b7775531
--- /dev/null
+++ b/src/test/java/command/EventCommandTest.java
@@ -0,0 +1,87 @@
+package command;
+
+import static org.junit.jupiter.api.Assertions.assertAll;
+import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertThrowsExactly;
+
+import java.io.ByteArrayOutputStream;
+import java.io.PrintStream;
+import java.time.LocalDate;
+
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+import exceptions.WoofInvalidCommandException;
+import tasks.EventTask;
+import tasks.Task;
+import tasks.TaskList;
+import woof.Woof;
+
+public class EventCommandTest {
+ private final ByteArrayOutputStream outContent = new ByteArrayOutputStream();
+ private final PrintStream originalOut = System.out;
+ @BeforeEach
+ public void setUpStreams() {
+ // Redirect System.out to the ByteArrayOutputStream
+ System.setOut(new PrintStream(this.outContent));
+ }
+
+ @AfterEach
+ public void restoreStreams() {
+ // Restore the original System.out
+ System.setOut(this.originalOut);
+ }
+
+ @Test
+ public void testValidate() {
+ // Arrange, Act, Assert
+ assertAll((
+ ) -> assertDoesNotThrow((
+ ) -> EventCommand.validate("event some task /from 2023-01-01 /to 2023-12-31")), (
+ ) -> assertThrowsExactly(WoofInvalidCommandException.class, (
+ ) -> EventCommand.validate("event some task /from 2023-01-01 /to 2022-12-31")), (
+ ) -> assertThrowsExactly(WoofInvalidCommandException.class, (
+ ) -> EventCommand.validate("event")), (
+ ) -> assertThrowsExactly(WoofInvalidCommandException.class, (
+ ) -> EventCommand.validate("")), (
+ ) -> assertThrowsExactly(WoofInvalidCommandException.class, (
+ ) -> EventCommand.validate("event some task /from 2023-01-01")), (
+ ) -> assertThrowsExactly(WoofInvalidCommandException.class, (
+ ) -> EventCommand.validate("event some task /to 2023-12-31")), (
+ ) -> assertThrowsExactly(WoofInvalidCommandException.class, (
+ ) -> EventCommand.validate("event /from 2023-01-01 /to 2023-12-31"))
+ );
+ }
+
+ @Test
+ public void testExecuteCreateTask() {
+ // Arrange
+ TaskList taskList = new TaskList(null);
+ EventCommand eventCommand = new EventCommand("event TaskName /from 2023-01-01 /to 2023-12-31");
+
+ // Act
+ eventCommand.execute(taskList);
+ LocalDate startDate = Woof.parseDateTimeIn("2023-01-01");
+ LocalDate endDate = Woof.parseDateTimeIn("2023-12-31");
+ EventTask expectedTask = new EventTask("TaskName", startDate, endDate);
+
+ // Assert
+ Task retrievedTask = taskList.getTask(0);
+ assertEquals(retrievedTask, expectedTask);
+ }
+
+ @Test
+ public void testExecuteNoTaskCreatedIfValidationFail() {
+ // Arrange
+ TaskList taskList = new TaskList(null);
+ EventCommand invalidCommand = new EventCommand("event");
+
+ // Act
+ invalidCommand.execute(taskList);
+
+ // Assert
+ assertEquals(0, taskList.size());
+ }
+}
diff --git a/src/test/java/command/FindCommandTest.java b/src/test/java/command/FindCommandTest.java
new file mode 100644
index 0000000000..a52e0f59c3
--- /dev/null
+++ b/src/test/java/command/FindCommandTest.java
@@ -0,0 +1,138 @@
+package command;
+
+import static org.junit.jupiter.api.Assertions.assertAll;
+import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertThrowsExactly;
+
+import java.io.ByteArrayOutputStream;
+import java.io.PrintStream;
+import java.time.LocalDate;
+
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+import exceptions.WoofInvalidCommandException;
+import tasks.DeadlineTask;
+import tasks.EventTask;
+import tasks.Task;
+import tasks.TaskList;
+import tasks.TodoTask;
+
+
+public class FindCommandTest {
+ private final ByteArrayOutputStream outContent = new ByteArrayOutputStream();
+ private final PrintStream originalOut = System.out;
+ @BeforeEach
+ public void setUpStreams() {
+ // Redirect System.out to the ByteArrayOutputStream
+ System.setOut(new PrintStream(this.outContent));
+ }
+
+ @AfterEach
+ public void restoreStreams() {
+ // Restore the original System.out
+ System.setOut(this.originalOut);
+ }
+
+ @Test
+ public void testValidate() {
+ // Act and Assert
+ assertAll((
+ ) -> assertDoesNotThrow((
+ ) -> FindCommand.validate("find keyword")), (
+ ) -> assertDoesNotThrow((
+ ) -> FindCommand.validate("find keyword extra argument")), (
+ ) -> assertThrowsExactly(WoofInvalidCommandException.class, (
+ ) -> FindCommand.validate("")), (
+ ) -> assertThrowsExactly(WoofInvalidCommandException.class, (
+ ) -> FindCommand.validate("/find keyword")), (
+ ) -> assertThrowsExactly(WoofInvalidCommandException.class, (
+ ) -> FindCommand.validate("find")), (
+ ) -> assertThrowsExactly(WoofInvalidCommandException.class, (
+ ) -> FindCommand.validate("list keyword"))
+ );
+ }
+
+
+ @Test
+ public void testExecuteFindsMatchingTasks() {
+ // Arrange
+ Task[] tasks = {
+ new TodoTask("Task 1"),
+ new TodoTask("Task 2"),
+ new TodoTask("Task 3")
+ };
+ TaskList taskList = new TaskList(tasks);
+
+ FindCommand findCommand = new FindCommand("find 2");
+ String expectedOutput = "Here are your matching tasks in your list:" + System.lineSeparator()
+ + " 2. [T][ ] Task 2" + System.lineSeparator()
+ + "You have 3 tasks in the task list.";
+
+ // Act
+ String actualOutput = findCommand.execute(taskList);
+
+ // Assert
+ assertEquals(expectedOutput, actualOutput);
+ }
+
+ @Test
+ public void testExecuteFindsMatchingTasksForMultiKeywords() {
+ // Arrange
+ Task[] tasks = {
+ new TodoTask("Eating"),
+ new TodoTask("Sleeping"),
+ new TodoTask("Drinking"),
+ new TodoTask("Task 1"),
+ new TodoTask("Task 2"),
+ new TodoTask("Task 3"),
+ new TodoTask("22")
+ };
+ TaskList taskList = new TaskList(tasks);
+
+ FindCommand findCommand = new FindCommand("find 2 ing");
+ String expectedOutput = "Here are your matching tasks in your list:" + System.lineSeparator()
+ + " 1. [T][ ] Eating" + System.lineSeparator()
+ + " 2. [T][ ] Sleeping" + System.lineSeparator()
+ + " 3. [T][ ] Drinking" + System.lineSeparator()
+ + " 5. [T][ ] Task 2" + System.lineSeparator()
+ + " 7. [T][ ] 22" + System.lineSeparator()
+ + "You have 7 tasks in the task list.";
+
+ // Act
+ String actualOutput = findCommand.execute(taskList);
+
+ // Assert
+ assertEquals(expectedOutput, actualOutput);
+ }
+
+ @Test
+ public void testExecuteNoMatchingTasks() {
+ // Arrange
+ Task[] tasks = {
+ new TodoTask(
+ "Task 1"),
+ new DeadlineTask(
+ "Task 2",
+ LocalDate.parse("2023-12-31")),
+ new EventTask(
+ "Task 3",
+ LocalDate.parse("2023-01-01"),
+ LocalDate.parse("2023-12-31"))
+ };
+ TaskList taskList = new TaskList(tasks);
+
+ FindCommand findCommand = new FindCommand("find NonExistentTask");
+ String expectedOutput = "Here are your matching tasks in your list:" + System.lineSeparator()
+ + "No tasks matched your keyword(s)!" + System.lineSeparator()
+ + "You have 3 tasks in the task list.";
+
+ // Act
+ String actualOutput = findCommand.execute(taskList);
+
+ // Assert
+ assertEquals(expectedOutput, actualOutput);
+ }
+}
diff --git a/src/test/java/command/HelpCommandTest.java b/src/test/java/command/HelpCommandTest.java
new file mode 100644
index 0000000000..8b61233cd4
--- /dev/null
+++ b/src/test/java/command/HelpCommandTest.java
@@ -0,0 +1,120 @@
+package command;
+
+import static org.junit.jupiter.api.Assertions.assertAll;
+import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertThrowsExactly;
+
+import java.io.ByteArrayOutputStream;
+import java.io.PrintStream;
+
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+import exceptions.WoofInvalidCommandException;
+import tasks.TaskList;
+
+public class HelpCommandTest {
+ private final ByteArrayOutputStream outContent = new ByteArrayOutputStream();
+ private final PrintStream originalOut = System.out;
+
+ @BeforeEach
+ public void setUpStreams() {
+ // Redirect System.out to the ByteArrayOutputStream
+ System.setOut(new PrintStream(this.outContent));
+ }
+
+ @AfterEach
+ public void restoreStreams() {
+ // Restore the original System.out
+ System.setOut(this.originalOut);
+ }
+
+ @Test
+ public void testValidate() {
+ // Arrange, Act, Assert
+ assertAll((
+ ) -> assertDoesNotThrow((
+ ) -> HelpCommand.validate("help")), (
+ ) -> assertThrowsExactly(WoofInvalidCommandException.class, (
+ ) -> HelpCommand.validate("")), (
+ ) -> assertThrowsExactly(WoofInvalidCommandException.class, (
+ ) -> HelpCommand.validate("bye some argument")), (
+ ) -> assertThrows(WoofInvalidCommandException.class, (
+ ) -> HelpCommand.validate("list")), (
+ ) -> assertThrows(WoofInvalidCommandException.class, (
+ ) -> HelpCommand.validate("todo some task")), (
+ ) -> assertThrows(WoofInvalidCommandException.class, (
+ ) -> HelpCommand.validate("deadline some task"))
+ );
+ }
+
+ @Test
+ public void testExecuteShowsHelpMessage() {
+ // Arrange
+ TaskList taskList = new TaskList(null);
+ HelpCommand helpCommand = new HelpCommand("help");
+
+ // Act
+ String actualOutput = helpCommand.execute(taskList);
+ String expectedOutput = "Woof! Woof! Here to help:" + System.lineSeparator()
+ + System.lineSeparator()
+ + "Available Commands:" + System.lineSeparator() + System.lineSeparator()
+ + "1. Todo : Add a simple to-do task."
+ + System.lineSeparator()
+ + " Format : `todo `" + System.lineSeparator()
+ + " Example : `todo study`" + System.lineSeparator()
+ + System.lineSeparator()
+ + "2. Deadline : Add a task with a deadline." + System.lineSeparator()
+ + " Format : `deadline /by `" + System.lineSeparator()
+ + " Example : `deadline assignment /by 2023-12-31`" + System.lineSeparator()
+ + System.lineSeparator()
+ + "3. Event : Add an event with a start and end date." + System.lineSeparator()
+ + " Format : `event /from /to `" + System.lineSeparator()
+ + " Example : `event enjoy /from 2023-11-25 /to 2024-01-14`" + System.lineSeparator()
+ + System.lineSeparator()
+ + "4. Mark : Mark a task as completed." + System.lineSeparator()
+ + " Format : `mark `" + System.lineSeparator()
+ + " Example : `mark 1`" + System.lineSeparator()
+ + System.lineSeparator()
+ + "5. Unmark : Unmark a completed task as incomplete." + System.lineSeparator()
+ + " Format : `unmark `" + System.lineSeparator()
+ + " Example : `unmark 1`" + System.lineSeparator()
+ + System.lineSeparator()
+ + "6. Delete : Delete a task." + System.lineSeparator()
+ + " Format : `delete `" + System.lineSeparator()
+ + " Example : `delete 2`" + System.lineSeparator()
+ + System.lineSeparator()
+ + "7. List : View all your tasks." + System.lineSeparator()
+ + " Format : `list`" + System.lineSeparator()
+ + System.lineSeparator()
+ + "8. Find : Search for tasks containing n keywords." + System.lineSeparator()
+ + " Format : `find ... `" + System.lineSeparator()
+ + " Example : `find meeting project`" + System.lineSeparator()
+ + System.lineSeparator()
+ + "9. Sort : Sort tasks by date and time." + System.lineSeparator()
+ + " Format : `sort`" + System.lineSeparator()
+ + System.lineSeparator()
+ + "10. Bye : Say goodbye to WoofWoof Bot." + System.lineSeparator()
+ + " Format : `bye`" + System.lineSeparator()
+ + System.lineSeparator()
+ + "11. Help : Request help on how to use the bot." + System.lineSeparator()
+ + " Format : `help`" + System.lineSeparator()
+ + System.lineSeparator()
+ + "Notes:" + System.lineSeparator()
+ + "- Replace `` with a task description." + System.lineSeparator()
+ + "- `` should follow the yyyy-mm-dd date format." + System.lineSeparator()
+ + " e.g. 2023-12-31" + System.lineSeparator()
+ + "- `` should be the index of the task in the list" + System.lineSeparator()
+ + " you want to manage." + System.lineSeparator()
+ + System.lineSeparator()
+ + "Feel free to ask for help using the `help` command if you" + System.lineSeparator()
+ + "have any questions or encounter issues." + System.lineSeparator()
+ + "Woof Woof is here to make your life easier!";
+
+ // Assert
+ assertEquals(expectedOutput, actualOutput);
+ }
+}
diff --git a/src/test/java/command/ListCommandTest.java b/src/test/java/command/ListCommandTest.java
new file mode 100644
index 0000000000..2b5a17426d
--- /dev/null
+++ b/src/test/java/command/ListCommandTest.java
@@ -0,0 +1,107 @@
+package command;
+
+import static org.junit.jupiter.api.Assertions.assertAll;
+import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertThrowsExactly;
+
+import java.io.ByteArrayOutputStream;
+import java.io.PrintStream;
+
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+import exceptions.WoofInvalidCommandException;
+import tasks.DeadlineTask;
+import tasks.EventTask;
+import tasks.Task;
+import tasks.TaskList;
+import tasks.TodoTask;
+import woof.Woof;
+
+public class ListCommandTest {
+ private final ByteArrayOutputStream outContent = new ByteArrayOutputStream();
+ private final PrintStream originalOut = System.out;
+ @BeforeEach
+ public void setUpStreams() {
+ // Redirect System.out to the ByteArrayOutputStream
+ System.setOut(new PrintStream(this.outContent));
+ }
+
+ @AfterEach
+ public void restoreStreams() {
+ // Restore the original System.out
+ System.setOut(this.originalOut);
+ }
+
+ @Test
+ public void testValidate() {
+ // Act and Assert
+ assertAll((
+ ) -> assertDoesNotThrow((
+ ) -> ListCommand.validate("list")), (
+ ) -> assertThrowsExactly(WoofInvalidCommandException.class, (
+ ) -> ListCommand.validate("/list")), (
+ ) -> assertThrowsExactly(WoofInvalidCommandException.class, (
+ ) -> ListCommand.validate("")), (
+ ) -> assertThrowsExactly(WoofInvalidCommandException.class, (
+ ) -> ListCommand.validate("list some argument")), (
+ ) -> assertThrowsExactly(WoofInvalidCommandException.class, (
+ ) -> ListCommand.validate("todo")), (
+ ) -> assertThrowsExactly(WoofInvalidCommandException.class, (
+ ) -> ListCommand.validate("event some task")), (
+ ) -> assertThrowsExactly(WoofInvalidCommandException.class, (
+ ) -> ListCommand.validate("deadline some task"))
+ );
+ }
+
+
+ @Test
+ public void testExecuteListTasks() {
+ // Arrange
+ Task[] tasks = {
+ new TodoTask(
+ "Task 1"),
+ new DeadlineTask(
+ "Task 2",
+ Woof.parseDateTimeIn("2023-12-31")),
+ new EventTask(
+ "Task 3",
+ Woof.parseDateTimeIn("2023-01-01"),
+ Woof.parseDateTimeIn("2023-12-31"))
+ };
+ TaskList taskList = new TaskList(tasks);
+
+ ListCommand listCommand = new ListCommand("list");
+ String expectedOutput = "Here are all the tasks in your list:" + System.lineSeparator()
+ + " 1. [T][ ] Task 1" + System.lineSeparator()
+ + " 2. [D][ ] Task 2" + System.lineSeparator()
+ + " ~By: 31 Dec 2023" + System.lineSeparator()
+ + " 3. [E][ ] Task 3" + System.lineSeparator()
+ + " ~From: 01 Jan 2023" + System.lineSeparator()
+ + " ~To : 31 Dec 2023" + System.lineSeparator()
+ + "You have 3 tasks in the task list.";
+
+ // Act
+ String actualOutput = listCommand.execute(taskList);
+
+ // Assert
+ assertEquals(expectedOutput, actualOutput);
+ }
+
+ @Test
+ public void testExecuteListNoTasks() {
+ // Arrange
+ TaskList taskList = new TaskList(null);
+ ListCommand listCommand = new ListCommand("list");
+ String expectedOutput = "Here are all the tasks in your list:" + System.lineSeparator()
+ + "You have 0 tasks in the task list.";
+
+ // Act
+ String actualOutput = listCommand.execute(taskList);
+
+ // Assert
+ assertEquals(expectedOutput, actualOutput);
+ }
+}
diff --git a/src/test/java/command/MarkCommandTest.java b/src/test/java/command/MarkCommandTest.java
new file mode 100644
index 0000000000..f00842c5f8
--- /dev/null
+++ b/src/test/java/command/MarkCommandTest.java
@@ -0,0 +1,105 @@
+package command;
+
+import static org.junit.jupiter.api.Assertions.assertAll;
+import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
+import static org.junit.jupiter.api.Assertions.assertNotEquals;
+import static org.junit.jupiter.api.Assertions.assertThrowsExactly;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import java.io.ByteArrayOutputStream;
+import java.io.PrintStream;
+
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+import exceptions.WoofInvalidCommandException;
+import tasks.Task;
+import tasks.TaskList;
+import tasks.TodoTask;
+
+
+public class MarkCommandTest {
+ private final ByteArrayOutputStream outContent = new ByteArrayOutputStream();
+ private final PrintStream originalOut = System.out;
+ @BeforeEach
+ public void setUpStreams() {
+ // Redirect System.out to the ByteArrayOutputStream
+ System.setOut(new PrintStream(this.outContent));
+ }
+
+ @AfterEach
+ public void restoreStreams() {
+ // Restore the original System.out
+ System.setOut(this.originalOut);
+ }
+
+ @Test
+ public void testValidate() {
+ // Arrange
+ Task[] tasks = {
+ new TodoTask("Task 1"),
+ new TodoTask("Task 2"),
+ new TodoTask("Task 3")
+ };
+ TaskList taskList = new TaskList(tasks);
+
+ // Act, Assert
+ assertAll((
+ ) -> assertDoesNotThrow((
+ ) -> MarkCommand.validate("mark 1", taskList)), (
+ ) -> assertDoesNotThrow((
+ ) -> MarkCommand.validate("mark 2", taskList)), (
+ ) -> assertThrowsExactly(WoofInvalidCommandException.class, (
+ ) -> MarkCommand.validate("", taskList)), (
+ ) -> assertThrowsExactly(WoofInvalidCommandException.class, (
+ ) -> MarkCommand.validate("mark", taskList)), (
+ ) -> assertThrowsExactly(WoofInvalidCommandException.class, (
+ ) -> MarkCommand.validate("mark 0", taskList)), (
+ ) -> assertThrowsExactly(WoofInvalidCommandException.class, (
+ ) -> MarkCommand.validate("mark 4", taskList)), (
+ ) -> assertThrowsExactly(WoofInvalidCommandException.class, (
+ ) -> MarkCommand.validate("mark a", taskList))
+ );
+
+ }
+
+ @Test
+ public void testExecuteMarksTaskAsDone() {
+ // Arrange
+ Task[] tasks = {
+ new TodoTask("Task 1"),
+ new TodoTask("Task 2"),
+ new TodoTask("Task 3")
+ };
+ TaskList taskList = new TaskList(tasks);
+ MarkCommand markCommand = new MarkCommand("mark 1");
+
+ // Act
+ markCommand.execute(taskList);
+
+ // Assert
+ Task task = taskList.getTask(0);
+ assertTrue(task.isDone());
+ }
+
+ @Test
+ public void testExecuteNoTaskMarkedIfValidationFails() {
+ // Arrange
+ Task[] tasks = {
+ new TodoTask("Task 1"),
+ new TodoTask("Task 2"),
+ new TodoTask("Task 3")
+ };
+ TaskList taskList = new TaskList(tasks);
+
+ MarkCommand invalidCommand = new MarkCommand("mark 0");
+
+ // Act
+ invalidCommand.execute(taskList);
+
+ // Assert
+ Task task = taskList.getTask(0);
+ assertNotEquals("", task.isDone());
+ }
+}
diff --git a/src/test/java/command/NullCommandTest.java b/src/test/java/command/NullCommandTest.java
new file mode 100644
index 0000000000..319cae6b83
--- /dev/null
+++ b/src/test/java/command/NullCommandTest.java
@@ -0,0 +1,97 @@
+package command;
+
+import static org.junit.jupiter.api.Assertions.assertAll;
+import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
+import static org.junit.jupiter.api.Assertions.assertNotEquals;
+import static org.junit.jupiter.api.Assertions.assertThrowsExactly;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import java.io.ByteArrayOutputStream;
+import java.io.PrintStream;
+
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+import exceptions.WoofInvalidCommandException;
+import tasks.Task;
+import tasks.TaskList;
+import tasks.TodoTask;
+
+
+public class NullCommandTest {
+ private final ByteArrayOutputStream outContent = new ByteArrayOutputStream();
+ private final PrintStream originalOut = System.out;
+ @BeforeEach
+ public void setUpStreams() {
+ // Redirect System.out to the ByteArrayOutputStream
+ System.setOut(new PrintStream(this.outContent));
+ }
+
+ @AfterEach
+ public void restoreStreams() {
+ // Restore the original System.out
+ System.setOut(this.originalOut);
+ }
+
+ @Test
+ public void testValidate() {
+ // Arrange, Act, Assert
+ assertAll((
+ ) -> assertDoesNotThrow((
+ ) -> NullCommand.validate("hehe")), (
+ ) -> assertThrowsExactly(WoofInvalidCommandException.class, (
+ ) -> NullCommand.validate("")), (
+ ) -> assertThrowsExactly(WoofInvalidCommandException.class, (
+ ) -> NullCommand.validate(" ")), (
+ ) -> assertThrowsExactly(WoofInvalidCommandException.class, (
+ ) -> NullCommand.validate("unmark task 1")), (
+ ) -> assertThrowsExactly(WoofInvalidCommandException.class, (
+ ) -> NullCommand.validate("todo abc"))
+ );
+ }
+
+ @Test
+ public void testExecuteMarksTaskAsDone() {
+ // Arrange
+ Task[] tasks = {
+ new TodoTask("Task 1"),
+ new TodoTask("Task 2"),
+ new TodoTask("Task 3")
+ };
+ TaskList taskList = new TaskList(tasks);
+
+ MarkCommand markCommand = new MarkCommand("mark 1");
+ UnmarkCommand unmarkCommand = new UnmarkCommand("unmark 1");
+
+ // Act
+ markCommand.execute(taskList);
+ unmarkCommand.execute(taskList);
+
+ // Assert
+ Task task = taskList.getTask(0);
+ assertNotEquals("", task.isDone());
+ }
+
+ @Test
+ public void testExecuteNoTaskMarkedIfValidationFails() {
+ // Arrange
+ Task[] tasks = {
+ new TodoTask("Task 1"),
+ new TodoTask("Task 2"),
+ new TodoTask("Task 3")
+ };
+ TaskList taskList = new TaskList(tasks);
+
+ MarkCommand markCommand = new MarkCommand("mark 1");
+ MarkCommand invalidCommand = new MarkCommand("unmark 1");
+
+ // Act
+ markCommand.execute(taskList);
+ invalidCommand.execute(taskList);
+
+ // Assert
+ Task task = taskList.getTask(0);
+ assertTrue(task.isDone());
+ }
+}
diff --git a/src/test/java/command/SortCommandTest.java b/src/test/java/command/SortCommandTest.java
new file mode 100644
index 0000000000..e4c473bac6
--- /dev/null
+++ b/src/test/java/command/SortCommandTest.java
@@ -0,0 +1,364 @@
+package command;
+
+import static org.junit.jupiter.api.Assertions.assertAll;
+import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertThrowsExactly;
+
+import java.io.ByteArrayOutputStream;
+import java.io.PrintStream;
+
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+import exceptions.WoofInvalidCommandException;
+import tasks.DeadlineTask;
+import tasks.EventTask;
+import tasks.Task;
+import tasks.TaskList;
+import tasks.TodoTask;
+import woof.Woof;
+
+public class SortCommandTest {
+ private final ByteArrayOutputStream outContent = new ByteArrayOutputStream();
+ private final PrintStream originalOut = System.out;
+
+ @BeforeEach
+ public void setUpStreams() {
+ // Redirect System.out to the ByteArrayOutputStream
+ System.setOut(new PrintStream(this.outContent));
+ }
+
+ @AfterEach
+ public void restoreStreams() {
+ // Restore the original System.out
+ System.setOut(this.originalOut);
+ }
+
+ @Test
+ public void testValidate() {
+ // Act and Assert
+ assertAll((
+ ) -> assertDoesNotThrow((
+ ) -> SortCommand.validate("sort")), (
+ ) -> assertThrowsExactly(WoofInvalidCommandException.class, (
+ ) -> SortCommand.validate("")), (
+ ) -> assertThrowsExactly(WoofInvalidCommandException.class, (
+ ) -> SortCommand.validate("/sort")), (
+ ) -> assertThrowsExactly(WoofInvalidCommandException.class, (
+ ) -> SortCommand.validate("sort some argument")), (
+ ) -> assertThrowsExactly(WoofInvalidCommandException.class, (
+ ) -> SortCommand.validate("todo")), (
+ ) -> assertThrowsExactly(WoofInvalidCommandException.class, (
+ ) -> SortCommand.validate("event some task")), (
+ ) -> assertThrowsExactly(WoofInvalidCommandException.class, (
+ ) -> SortCommand.validate("deadline some task"))
+ );
+ }
+
+ @Test
+ public void testExecuteSortTasks() {
+ // Arrange
+ Task[] tasks = {
+ new TodoTask(
+ "Task 6",
+ true
+ ),
+ new TodoTask(
+ "a"
+ ),
+ new DeadlineTask(
+ "b",
+ Woof.parseDateTimeIn("2023-01-01")
+ ),
+ new DeadlineTask(
+ "Task 8",
+ Woof.parseDateTimeIn("2023-02-03")
+ ),
+ new TodoTask(
+ "c",
+ true
+ ),
+ new TodoTask(
+ "d"
+ ),
+ new TodoTask(
+ "Task 1"
+ ),
+ new EventTask(
+ "e",
+ Woof.parseDateTimeIn("2023-01-01"),
+ Woof.parseDateTimeIn("2023-01-01"),
+ true
+ ),
+ new TodoTask(
+ "f"
+ ),
+ new EventTask(
+ "4",
+ Woof.parseDateTimeIn("2023-05-05"),
+ Woof.parseDateTimeIn("2023-05-06")
+ ),
+ new DeadlineTask(
+ "g",
+ Woof.parseDateTimeIn("2023-01-01")
+ ),
+ new TodoTask(
+ "Task 2",
+ true
+ ),
+ new TodoTask(
+ "Task 9"
+ ),
+ new TodoTask(
+ "Task 10"
+ ),
+ new EventTask(
+ "h",
+ Woof.parseDateTimeIn("2023-02-15"),
+ Woof.parseDateTimeIn("2023-02-15"),
+ true
+ ),
+ new TodoTask(
+ "i"
+ ),
+ new TodoTask(
+ "Task 3",
+ true
+ ),
+ new EventTask(
+ "j",
+ Woof.parseDateTimeIn("2023-03-10"),
+ Woof.parseDateTimeIn("2023-03-10")
+ ),
+ new DeadlineTask(
+ "k",
+ Woof.parseDateTimeIn("2023-04-20")
+ ),
+ new EventTask(
+ "l",
+ Woof.parseDateTimeIn("2023-05-05"),
+ Woof.parseDateTimeIn("2023-05-06")
+ ),
+ new TodoTask(
+ "Task 5"
+ ),
+ new TodoTask(
+ "Task 7"
+ ),
+ new DeadlineTask(
+ "Task 13",
+ Woof.parseDateTimeIn("2023-06-01")
+ ),
+ new EventTask(
+ "Task 12",
+ Woof.parseDateTimeIn("2023-07-15"),
+ Woof.parseDateTimeIn("2023-07-16"),
+ true
+ ),
+ new TodoTask(
+ "Task 11",
+ true
+ ),
+ new TodoTask(
+ "Task 19"
+ ),
+ new DeadlineTask(
+ "Task 15",
+ Woof.parseDateTimeIn("2023-08-30")
+ ),
+ new EventTask(
+ "Task 16",
+ Woof.parseDateTimeIn("2023-09-10"),
+ Woof.parseDateTimeIn("2023-09-11")
+ ),
+ new TodoTask(
+ "Task 17"
+ ),
+ new TodoTask(
+ "Task 18",
+ true
+ ),
+ new EventTask(
+ "Task 19",
+ Woof.parseDateTimeIn("2023-10-20"),
+ Woof.parseDateTimeIn("2023-10-21")
+ ),
+ new DeadlineTask(
+ "Task 20",
+ Woof.parseDateTimeIn("2023-11-05")
+ ),
+ new TodoTask(
+ "Task m",
+ true
+ ),
+ new DeadlineTask(
+ "Task n",
+ Woof.parseDateTimeIn("2023-11-15"),
+ true
+ ),
+ new EventTask(
+ "Task o",
+ Woof.parseDateTimeIn("2023-12-10"),
+ Woof.parseDateTimeIn("2023-12-11"),
+ true
+ ),
+ new TodoTask(
+ "Task p"
+ ),
+ new EventTask(
+ "Task q",
+ Woof.parseDateTimeIn("2024-01-05"),
+ Woof.parseDateTimeIn("2024-01-06")
+ ),
+ new DeadlineTask(
+ "Task r",
+ Woof.parseDateTimeIn("2024-02-20")
+ ),
+ new EventTask(
+ "Task s",
+ Woof.parseDateTimeIn("2024-03-15"),
+ Woof.parseDateTimeIn("2024-03-16"),
+ true
+ ),
+ new TodoTask(
+ "Task t"
+ ),
+ new TodoTask(
+ "Task u",
+ true
+ ),
+ new EventTask(
+ "Task v",
+ Woof.parseDateTimeIn("2024-04-20"),
+ Woof.parseDateTimeIn("2024-04-21")
+ ),
+ new DeadlineTask(
+ "Task w",
+ Woof.parseDateTimeIn("2024-05-10")
+ ),
+ new EventTask(
+ "Task x",
+ Woof.parseDateTimeIn("2024-06-05"),
+ Woof.parseDateTimeIn("2024-06-06")
+ ),
+ new TodoTask(
+ "Task y"
+ ),
+ new TodoTask(
+ "Task z",
+ true
+ )
+ };
+ TaskList taskList = new TaskList(tasks);
+
+ SortCommand sortCommand = new SortCommand("sort");
+ String expectedOutput = "Your tasks have been sorted:" + System.lineSeparator()
+ + " 1. [D][ ] b" + System.lineSeparator()
+ + " ~By: 01 Jan 2023" + System.lineSeparator()
+ + " 2. [D][ ] g" + System.lineSeparator()
+ + " ~By: 01 Jan 2023" + System.lineSeparator()
+ + " 3. [D][ ] Task 8" + System.lineSeparator()
+ + " ~By: 03 Feb 2023" + System.lineSeparator()
+ + " 4. [D][ ] k" + System.lineSeparator()
+ + " ~By: 20 Apr 2023" + System.lineSeparator()
+ + " 5. [D][ ] Task 13" + System.lineSeparator()
+ + " ~By: 01 Jun 2023" + System.lineSeparator()
+ + " 6. [D][ ] Task 15" + System.lineSeparator()
+ + " ~By: 30 Aug 2023" + System.lineSeparator()
+ + " 7. [D][ ] Task 20" + System.lineSeparator()
+ + " ~By: 05 Nov 2023" + System.lineSeparator()
+ + " 8. [D][ ] Task r" + System.lineSeparator()
+ + " ~By: 20 Feb 2024" + System.lineSeparator()
+ + " 9. [D][ ] Task w" + System.lineSeparator()
+ + " ~By: 10 May 2024" + System.lineSeparator()
+ + " 10. [E][ ] j" + System.lineSeparator()
+ + " ~From: 10 Mar 2023" + System.lineSeparator()
+ + " ~To : 10 Mar 2023" + System.lineSeparator()
+ + " 11. [E][ ] 4" + System.lineSeparator()
+ + " ~From: 05 May 2023" + System.lineSeparator()
+ + " ~To : 06 May 2023" + System.lineSeparator()
+ + " 12. [E][ ] l" + System.lineSeparator()
+ + " ~From: 05 May 2023" + System.lineSeparator()
+ + " ~To : 06 May 2023" + System.lineSeparator()
+ + " 13. [E][ ] Task 16" + System.lineSeparator()
+ + " ~From: 10 Sep 2023" + System.lineSeparator()
+ + " ~To : 11 Sep 2023" + System.lineSeparator()
+ + " 14. [E][ ] Task 19" + System.lineSeparator()
+ + " ~From: 20 Oct 2023" + System.lineSeparator()
+ + " ~To : 21 Oct 2023" + System.lineSeparator()
+ + " 15. [E][ ] Task q" + System.lineSeparator()
+ + " ~From: 05 Jan 2024" + System.lineSeparator()
+ + " ~To : 06 Jan 2024" + System.lineSeparator()
+ + " 16. [E][ ] Task v" + System.lineSeparator()
+ + " ~From: 20 Apr 2024" + System.lineSeparator()
+ + " ~To : 21 Apr 2024" + System.lineSeparator()
+ + " 17. [E][ ] Task x" + System.lineSeparator()
+ + " ~From: 05 Jun 2024" + System.lineSeparator()
+ + " ~To : 06 Jun 2024" + System.lineSeparator()
+ + " 18. [T][ ] Task 1" + System.lineSeparator()
+ + " 19. [T][ ] Task 10" + System.lineSeparator()
+ + " 20. [T][ ] Task 17" + System.lineSeparator()
+ + " 21. [T][ ] Task 19" + System.lineSeparator()
+ + " 22. [T][ ] Task 5" + System.lineSeparator()
+ + " 23. [T][ ] Task 7" + System.lineSeparator()
+ + " 24. [T][ ] Task 9" + System.lineSeparator()
+ + " 25. [T][ ] Task p" + System.lineSeparator()
+ + " 26. [T][ ] Task t" + System.lineSeparator()
+ + " 27. [T][ ] Task y" + System.lineSeparator()
+ + " 28. [T][ ] a" + System.lineSeparator()
+ + " 29. [T][ ] d" + System.lineSeparator()
+ + " 30. [T][ ] f" + System.lineSeparator()
+ + " 31. [T][ ] i" + System.lineSeparator()
+ + " 32. [D][X] Task n" + System.lineSeparator()
+ + " ~By: 15 Nov 2023" + System.lineSeparator()
+ + " 33. [E][X] e" + System.lineSeparator()
+ + " ~From: 01 Jan 2023" + System.lineSeparator()
+ + " ~To : 01 Jan 2023" + System.lineSeparator()
+ + " 34. [E][X] h" + System.lineSeparator()
+ + " ~From: 15 Feb 2023" + System.lineSeparator()
+ + " ~To : 15 Feb 2023" + System.lineSeparator()
+ + " 35. [E][X] Task 12" + System.lineSeparator()
+ + " ~From: 15 Jul 2023" + System.lineSeparator()
+ + " ~To : 16 Jul 2023" + System.lineSeparator()
+ + " 36. [E][X] Task o" + System.lineSeparator()
+ + " ~From: 10 Dec 2023" + System.lineSeparator()
+ + " ~To : 11 Dec 2023" + System.lineSeparator()
+ + " 37. [E][X] Task s" + System.lineSeparator()
+ + " ~From: 15 Mar 2024" + System.lineSeparator()
+ + " ~To : 16 Mar 2024" + System.lineSeparator()
+ + " 38. [T][X] Task 11" + System.lineSeparator()
+ + " 39. [T][X] Task 18" + System.lineSeparator()
+ + " 40. [T][X] Task 2" + System.lineSeparator()
+ + " 41. [T][X] Task 3" + System.lineSeparator()
+ + " 42. [T][X] Task 6" + System.lineSeparator()
+ + " 43. [T][X] Task m" + System.lineSeparator()
+ + " 44. [T][X] Task u" + System.lineSeparator()
+ + " 45. [T][X] Task z" + System.lineSeparator()
+ + " 46. [T][X] c" + System.lineSeparator()
+ + "You have 46 tasks in the task list.";
+
+ // Act
+ String actualOutput = sortCommand.execute(taskList);
+
+ // Assert
+ assertEquals(expectedOutput, actualOutput);
+ }
+
+ @Test
+ public void testExecuteSortNoTasks() {
+ // Arrange
+ TaskList taskList = new TaskList(null);
+ SortCommand sortCommand = new SortCommand("sort");
+ String expectedOutput = "Your tasks have been sorted:" + System.lineSeparator()
+ + "There's nothing to sort... really" + System.lineSeparator()
+ + "You have 0 tasks in the task list.";
+
+ // Act
+ String actualOutput = sortCommand.execute(taskList);
+
+ // Assert
+ assertEquals(expectedOutput, actualOutput);
+ }
+}
diff --git a/src/test/java/command/TodoCommandTest.java b/src/test/java/command/TodoCommandTest.java
new file mode 100644
index 0000000000..ba03ebb05c
--- /dev/null
+++ b/src/test/java/command/TodoCommandTest.java
@@ -0,0 +1,79 @@
+package command;
+
+import static org.junit.jupiter.api.Assertions.assertAll;
+import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertThrowsExactly;
+
+import java.io.ByteArrayOutputStream;
+import java.io.PrintStream;
+
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+import exceptions.WoofInvalidCommandException;
+import tasks.TaskList;
+import tasks.TodoTask;
+
+
+public class TodoCommandTest {
+ private final ByteArrayOutputStream outContent = new ByteArrayOutputStream();
+ private final PrintStream originalOut = System.out;
+ @BeforeEach
+ public void setUpStreams() {
+ // Redirect System.out to the ByteArrayOutputStream
+ System.setOut(new PrintStream(this.outContent));
+ }
+
+ @AfterEach
+ public void restoreStreams() {
+ // Restore the original System.out
+ System.setOut(this.originalOut);
+ }
+
+ @Test
+ public void testValidate() {
+ // Act and Assert
+ assertAll((
+ ) -> assertDoesNotThrow((
+ ) -> TodoCommand.validate("todo some task")), (
+ ) -> assertThrowsExactly(WoofInvalidCommandException.class, (
+ ) -> TodoCommand.validate("todo")), (
+ ) -> assertThrowsExactly(WoofInvalidCommandException.class, (
+ ) -> TodoCommand.validate("")), (
+ ) -> assertThrowsExactly(WoofInvalidCommandException.class, (
+ ) -> TodoCommand.validate("event some task")), (
+ ) -> assertThrowsExactly(WoofInvalidCommandException.class, (
+ ) -> TodoCommand.validate("deadline some task"))
+ );
+ }
+
+ @Test
+ public void testExecuteCreateTask() {
+ // Arrange
+ TaskList taskList = new TaskList(null);
+ TodoCommand todoCommand = new TodoCommand("todo TaskName");
+
+ // Act
+ todoCommand.execute(taskList);
+ TodoTask expectedTask = new TodoTask("TaskName");
+
+ // Assert
+ assertEquals(1, taskList.size());
+ assertEquals(expectedTask, taskList.getTask(0));
+ }
+
+ @Test
+ public void testExecuteNoTaskCreatedIfValidationFail() {
+ // Arrange
+ TaskList taskList = new TaskList(null);
+ TodoCommand invalidCommand = new TodoCommand("todo");
+
+ // Act
+ invalidCommand.execute(taskList);
+
+ // Assert
+ assertEquals(0, taskList.size());
+ }
+}
diff --git a/src/test/java/command/UnmarkCommandTest.java b/src/test/java/command/UnmarkCommandTest.java
new file mode 100644
index 0000000000..47f2971574
--- /dev/null
+++ b/src/test/java/command/UnmarkCommandTest.java
@@ -0,0 +1,113 @@
+package command;
+
+import static org.junit.jupiter.api.Assertions.assertAll;
+import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertThrowsExactly;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import java.io.ByteArrayOutputStream;
+import java.io.PrintStream;
+
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+import exceptions.WoofInvalidCommandException;
+import tasks.Task;
+import tasks.TaskList;
+import tasks.TodoTask;
+
+
+public class UnmarkCommandTest {
+ private final ByteArrayOutputStream outContent = new ByteArrayOutputStream();
+ private final PrintStream originalOut = System.out;
+ @BeforeEach
+ public void setUpStreams() {
+ // Redirect System.out to the ByteArrayOutputStream
+ System.setOut(new PrintStream(this.outContent));
+ }
+
+ @AfterEach
+ public void restoreStreams() {
+ // Restore the original System.out
+ System.setOut(this.originalOut);
+ }
+
+ @Test
+ public void testValidate() {
+ // Arrange
+ Task[] tasks = {
+ new TodoTask("Task 1"),
+ new TodoTask("Task 2"),
+ new TodoTask("Task 3")
+ };
+ TaskList taskList = new TaskList(tasks);
+
+ // Act, Assert
+ assertAll((
+ ) -> assertDoesNotThrow((
+ ) -> UnmarkCommand.validate("unmark 1", taskList)), (
+ ) -> assertDoesNotThrow((
+ ) -> UnmarkCommand.validate("unmark 2", taskList)), (
+ ) -> assertThrowsExactly(WoofInvalidCommandException.class, (
+ ) -> UnmarkCommand.validate("", taskList)), (
+ ) -> assertThrowsExactly(WoofInvalidCommandException.class, (
+ ) -> UnmarkCommand.validate("unmark", taskList)), (
+ ) -> assertThrowsExactly(WoofInvalidCommandException.class, (
+ ) -> UnmarkCommand.validate("unmark 0", taskList)), (
+ ) -> assertThrowsExactly(WoofInvalidCommandException.class, (
+ ) -> UnmarkCommand.validate("unmark 4", taskList)), (
+ ) -> assertThrowsExactly(WoofInvalidCommandException.class, (
+ ) -> UnmarkCommand.validate("unmark a", taskList))
+ );
+ }
+
+ @Test
+ public void testExecuteMarksTaskAsDone() {
+ // Arrange
+ Task[] tasks = {
+ new TodoTask(
+ "Task 1"),
+ new TodoTask(
+ "Task 2",
+ true),
+ new TodoTask(
+ "Task 3")
+ };
+ TaskList taskList = new TaskList(tasks);
+ UnmarkCommand unmarkCommand = new UnmarkCommand("unmark 1");
+
+ // Act
+ unmarkCommand.execute(taskList);
+
+ // Assert
+ Task task = taskList.getTask(0);
+ assertFalse(task.isDone());
+ }
+
+ @Test
+ public void testExecuteNoTaskMarkedIfValidationFails() {
+ // Arrange
+ Task[] tasks = {
+ new TodoTask(
+ "Task 1",
+ true),
+ new TodoTask(
+ "Task 2",
+ true),
+ new TodoTask(
+ "Task 3")
+ };
+ TaskList taskList = new TaskList(tasks);
+
+ MarkCommand invalidCommand = new MarkCommand("unmark 1");
+
+ // Act
+ invalidCommand.execute(taskList);
+
+ // Assert
+ Task task = taskList.getTask(0);
+ assertTrue(task.isDone());
+ }
+}
diff --git a/text-ui-test/EXPECTED.TXT b/text-ui-test/EXPECTED.TXT
index 657e74f6e7..fb223268f6 100644
--- a/text-ui-test/EXPECTED.TXT
+++ b/text-ui-test/EXPECTED.TXT
@@ -1,7 +1,62 @@
-Hello from
- ____ _
-| _ \ _ _| | _____
-| | | | | | | |/ / _ \
-| |_| | |_| | < __/
-|____/ \__,_|_|\_\___|
-
+Hello! I'm Jing Sheng
+What can I do for you?
+════════════════════════════════════════════════════════════════════════════════
+You:════════════════════════════════════════════════════════════════════════════════
+Bot:
+Got it. I've added this task:
+ [T][ ] borrow book
+Now you have 1 tasks in the task list.
+════════════════════════════════════════════════════════════════════════════════
+You:════════════════════════════════════════════════════════════════════════════════
+Bot:
+Here are the tasks in your list:
+ 1. [T][ ] borrow book
+You have 1 tasks in the task list.
+════════════════════════════════════════════════════════════════════════════════
+You:════════════════════════════════════════════════════════════════════════════════
+Bot:
+Got it. I've added this task:
+ [D][ ] return book (by: Sunday)
+Now you have 2 tasks in the task list.
+════════════════════════════════════════════════════════════════════════════════
+You:════════════════════════════════════════════════════════════════════════════════
+Bot:
+Got it. I've added this task:
+ [E][ ] project meeting (from: Mon 2pm to 4pm)
+Now you have 3 tasks in the task list.
+════════════════════════════════════════════════════════════════════════════════
+You:════════════════════════════════════════════════════════════════════════════════
+Bot:
+Here are the tasks in your list:
+ 1. [T][ ] borrow book
+ 2. [D][ ] return book (by: Sunday)
+ 3. [E][ ] project meeting (from: Mon 2pm to 4pm)
+You have 3 tasks in the task list.
+════════════════════════════════════════════════════════════════════════════════
+You:════════════════════════════════════════════════════════════════════════════════
+Bot:
+Nice! I've marked this task as done:
+ [T][X] borrow book
+════════════════════════════════════════════════════════════════════════════════
+You:════════════════════════════════════════════════════════════════════════════════
+Bot:
+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)
+You have 3 tasks in the task list.
+════════════════════════════════════════════════════════════════════════════════
+You:════════════════════════════════════════════════════════════════════════════════
+Bot:
+Ok! I've marked this task as undone:
+ [T][ ] borrow book
+════════════════════════════════════════════════════════════════════════════════
+You:════════════════════════════════════════════════════════════════════════════════
+Bot:
+Here are the tasks in your list:
+ 1. [T][ ] borrow book
+ 2. [D][ ] return book (by: Sunday)
+ 3. [E][ ] project meeting (from: Mon 2pm to 4pm)
+You have 3 tasks in the task list.
+════════════════════════════════════════════════════════════════════════════════
+You:
\ No newline at end of file
diff --git a/text-ui-test/input.txt b/text-ui-test/input.txt
index e69de29bb2..06368eb637 100644
--- a/text-ui-test/input.txt
+++ b/text-ui-test/input.txt
@@ -0,0 +1,9 @@
+todo borrow book
+list
+deadline return book /by Sunday
+event project meeting /from Mon 2pm /to 4pm
+list
+mark 1
+list
+unmark 1
+list
\ No newline at end of file
diff --git a/text-ui-test/runtest.bat b/text-ui-test/runtest.bat
index 0873744649..fd432a56c8 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 woof.Woof < input.txt > ACTUAL.TXT
REM compare the output to the expected output
FC ACTUAL.TXT EXPECTED.TXT