diff --git a/build.gradle b/build.gradle
new file mode 100644
index 000000000..c361f9811
--- /dev/null
+++ b/build.gradle
@@ -0,0 +1,41 @@
+plugins {
+ id 'java'
+ id 'application'
+ id 'com.github.johnrengelman.shadow' version '7.1.2'
+}
+
+repositories {
+ mavenCentral()
+}
+
+dependencies {
+ testImplementation group: 'org.junit.jupiter', name: 'junit-jupiter-api', version: '5.10.0'
+ testRuntimeOnly group: 'org.junit.jupiter', name: 'junit-jupiter-engine', version: '5.10.0'
+}
+
+test {
+ useJUnitPlatform()
+
+ testLogging {
+ events "passed", "skipped", "failed"
+
+ showExceptions true
+ exceptionFormat "full"
+ showCauses true
+ showStackTraces true
+ showStandardStreams = false
+ }
+}
+
+application {
+ mainClass.set("seedu.duke.Duke")
+}
+
+shadowJar {
+ archiveBaseName = "duke"
+ archiveClassifier = null
+}
+
+run{
+ standardInput = System.in
+}
diff --git a/data/pythia.txt b/data/pythia.txt
new file mode 100644
index 000000000..e716bc412
--- /dev/null
+++ b/data/pythia.txt
@@ -0,0 +1,2 @@
+true | read
+T | false | got apple
diff --git a/docs/README.md b/docs/README.md
index 47b9f984f..f15b0c564 100644
--- a/docs/README.md
+++ b/docs/README.md
@@ -1,30 +1,225 @@
-# Duke User Guide
+# Pythia User Guide
+
+
+
-// Update the title above to match the actual product name
+Pythia chatbot is desktop app **designed to help you manage all of your tasks,
+optimized for use via command-line interface**.
+It allows for adding tasks, deleting, marking and saving them for later.
-// Product screenshot goes here
+# Table of Contents
+- [Feature overview](#feature-overview)
+- [Add `Task`](#add-task)
+- [Add `ToDo`](#add-todo)
+- [Add `Event`](#add-event)
+- [Add `Deadline`](#add-deadline)
+- [View all with `list`](#list-tasks)
+- [Delete a task](#task-delete)
+- [Mark task as done](#mark-task)
+- [Find a task](#find-task)
+- [Exit the app](#exit)
-// Product intro goes here
+# Feature overview
+App supports several types of commands with different functionalities and synatx.
+All of them summarized in the table below.
-## Adding deadlines
+| Feature | Syntax |
+|---------------------|------------------------------------------------------|
+| **List all tasks** | `list` |
+| **Add a task** | `add ` |
+| **Add a todo** | `todo ` |
+| **Add a deadline** | `deadline /by ` |
+| **Add an event** | `event /from /to `|
+| **Mark task as done** | `mark ` |
+| **Delete a task** | `delete ` |
+| **Find a task** | `find ` |
+| **Quit the app** | `bye` |
-// Describe the action and its outcome.
+> **Tip:**
+> To know `` it is advised to execute `list` command first
-// Give examples of usage
+> **Warning:**
+> Once a task is deleted it cannot be recovered
-Example: `keyword (optional arguments)`
+## Add `Task`
+Adds a task to the list of tasks. Use syntax `add `
+where:
+- `` is a task description of the task such
+as a name or anything desired
-// A description of the expected outcome goes here
+### Example:
+```
+add My first task
+ ____________________________________________________________
+ added: my first task
+ ____________________________________________________________
+list
+ ____________________________________________________________
+ 1. [ ] My first task
+ Now you have 1 task in the list.
+ ____________________________________________________________
+```
+
+## Add `ToDo`
+Adds a `TdDo` to the list of tasks. Use syntax `todo `
+where:
+- `` is a `ToDo` description of the task such
+as a name or anything desired.
+
+### Example:
+```
+todo My first todo
+ ____________________________________________________________
+ added: my first todo
+ ____________________________________________________________
+list
+ ____________________________________________________________
+ 1. [ ] My first task
+ 2. [T][ ] My first todo
+ Now you have 2 tasks in the list.
+ ____________________________________________________________
+```
+
+> **Note:**
+> Unless a task is not deleted it will be kept indefinitely.
+
+## Add `Event`
+Adds an `Event` to the list of tasks. Use syntax `event /from /to `
+where:
+- `` is an `Event` description
+- `` is the time at which the event starts, any sequence of characters is accepted
+- `` is the time at which the event end, any sequence of characters is accepted
+
+
+### Example:
+```
+event My first event /from now /to some time in the future
+ ____________________________________________________________
+ added: my first event
+ ____________________________________________________________
+list
+ ____________________________________________________________
+ 1. [ ] My first task
+ 2. [T][ ] My first todo
+ 3. [E][ ] My first event (from now to some time in the future)
+ Now you have 3 tasks in the list.
+ ____________________________________________________________
+```
+
+## Add `Deadline`
+Adds a `Deadline` to the list of tasks. Use syntax `deadline /by `
+where:
+- `` is a `Deadline` description
+- `` date by which the deadline is due, any sequence of characters is accepted
+### Example:
```
-expected output
+deadline My first deadline /by tomorrow :)
+ ____________________________________________________________
+ added: my first deadline
+ ____________________________________________________________
+list
+ ____________________________________________________________
+ 1. [ ] My first task
+ 2. [T][ ] My first todo
+ 3. [E][ ] My first event (from now to some time in the future)
+ 4. [D][ ] My first deadline (by: tomorrow :))
+ Now you have 4 tasks in the list.
+ ____________________________________________________________
```
-## Feature ABC
+## View all with `list`
+You can view all tasks in the list with `list` command. Once a task is added,
+it will be kept indefinitely unless deleted explicitly. Look above for examples (like [Add `Deadline`](#add-deadline))
-// Feature details
+## Delete a task
+Deletes a `Task` from the list of tasks. Use syntax `delete `
+where:
+- `` is a number of the task in the list
+> **Tip:**
+> To know `` it is advised to execute `list` command first as task numbers change upon deletion
-## Feature XYZ
+### Example:
+```
+delete 1
+ ____________________________________________________________
+ Nice! I've deleted this task:
+ [ ] My first task
+ ____________________________________________________________
+list
+ ____________________________________________________________
+ 1. [T][ ] My first todo
+ 2. [E][ ] My first event (from now to some time in the future)
+ 3. [D][ ] My first deadline (by: tomorrow :))
+ Now you have 3 tasks in the list.
+ ____________________________________________________________
+```
+
+## Mark task as done
+Marks a `Task` as done. Use syntax `mark `
+where:
+- `` is a number of the task in the list
+
+> **Tip:**
+> To know `` it is advised to execute `list` command first as task numbers change upon deletion
+
+### Example:
+```
+mark 1
+ ____________________________________________________________
+ Nice! I've marked this task as done:
+ [T][X] My first todo
+ ____________________________________________________________
+list
+ ____________________________________________________________
+ 1. [T][X] My first todo
+ 2. [E][ ] My first event (from now to some time in the future)
+ 3. [D][ ] My first deadline (by: tomorrow :))
+ Now you have 3 tasks in the list.
+ ____________________________________________________________
+```
-// Feature details
\ No newline at end of file
+## Find a task
+Finds a `Task` with a keyword contained in the task description. Use syntax `find `.
+
+### Example:
+```
+list
+ ____________________________________________________________
+ 1. [T][X] My first todo
+ 2. [E][ ] My first event (from now to some time in the future)
+ 3. [D][ ] My first deadline (by: tomorrow :))
+ Now you have 3 tasks in the list.
+ ____________________________________________________________
+add My second task
+ ____________________________________________________________
+ added: my second task
+ ____________________________________________________________
+list
+ ____________________________________________________________
+ 1. [T][X] My first todo
+ 2. [E][ ] My first event (from now to some time in the future)
+ 3. [D][ ] My first deadline (by: tomorrow :))
+ 4. [ ] My second task
+ Now you have 4 tasks in the list.
+ ____________________________________________________________
+find first
+ ____________________________________________________________
+ Here are the matching tasks in your list:
+ 1. [T][X] My first todo
+ 2. [E][ ] My first event (from now to some time in the future)
+ 3. [D][ ] My first deadline (by: tomorrow :))
+ ____________________________________________________________
+```
+
+## Exit the app
+To exit the app use command `bye`. Changes are saved automatically.
+
+### Example:
+```
+bye
+ ____________________________________________________________
+ Your path is set. Until we meet again.
+ ____________________________________________________________
+```
\ No newline at end of file
diff --git a/docs/Screenshot.png b/docs/Screenshot.png
new file mode 100644
index 000000000..03df5dba0
Binary files /dev/null and b/docs/Screenshot.png differ
diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar
new file mode 100644
index 000000000..033e24c4c
Binary files /dev/null and b/gradle/wrapper/gradle-wrapper.jar differ
diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties
new file mode 100644
index 000000000..66c01cfeb
--- /dev/null
+++ b/gradle/wrapper/gradle-wrapper.properties
@@ -0,0 +1,7 @@
+distributionBase=GRADLE_USER_HOME
+distributionPath=wrapper/dists
+distributionUrl=https\://services.gradle.org/distributions/gradle-7.6.2-bin.zip
+networkTimeout=10000
+validateDistributionUrl=true
+zipStoreBase=GRADLE_USER_HOME
+zipStorePath=wrapper/dists
diff --git a/gradlew b/gradlew
new file mode 100755
index 000000000..fcb6fca14
--- /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 000000000..6689b85be
--- /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 5d313334c..000000000
--- a/src/main/java/Duke.java
+++ /dev/null
@@ -1,10 +0,0 @@
-public class Duke {
- public static void main(String[] args) {
- String logo = " ____ _ \n"
- + "| _ \\ _ _| | _____ \n"
- + "| | | | | | | |/ / _ \\\n"
- + "| |_| | |_| | < __/\n"
- + "|____/ \\__,_|_|\\_\\___|\n";
- System.out.println("Hello from\n" + logo);
- }
-}
diff --git a/src/main/java/META-INF/MANIFEST.MF b/src/main/java/META-INF/MANIFEST.MF
new file mode 100644
index 000000000..e6b32ee44
--- /dev/null
+++ b/src/main/java/META-INF/MANIFEST.MF
@@ -0,0 +1,3 @@
+Manifest-Version: 1.0
+Main-Class: pythia.Pythia
+
diff --git a/src/main/java/pythia/Pythia.java b/src/main/java/pythia/Pythia.java
new file mode 100644
index 000000000..c9ceecda6
--- /dev/null
+++ b/src/main/java/pythia/Pythia.java
@@ -0,0 +1,220 @@
+package pythia;
+
+import pythia.utility.Parser;
+import pythia.utility.Ui;
+import pythia.task.Task;
+import pythia.task.ToDo;
+import pythia.task.Deadline;
+import pythia.task.Event;
+import pythia.exceptions.PythiaException;
+import pythia.utility.Storage;
+import pythia.utility.TaskList;
+
+/**
+ * Main class of the Pythia application. This class is responsible for managing
+ * the system's core components, including parsing user commands, storing and
+ * retrieving tasks, and interacting with the user through the user interface.
+ */
+public class Pythia {
+ private static String botName = "Pythia";
+ private static String logo =
+ "____ _ _ _ \n" +
+ "| _ \\ _ _| |_| |__ (_) __ _ \n" +
+ "| |_) | | | | __| '_ \\| |/ _` |\n" +
+ "| __/| |_| | |_| | | | | (_| |\n" +
+ "|_| \\__, |\\__|_| |_|_|\\__,_|\n" +
+ " |___/ ";
+
+ private static boolean isByeSaid;
+ private static TaskList taskList;
+ private static Storage storage;
+ private static Ui ui;
+
+ /**
+ * Constructs a Pythia object that initializes the system with task
+ * storage and user interaction components.
+ *
+ * @param filePath Path to the file where tasks are saved and loaded from.
+ */
+ public Pythia(String filePath) {
+ isByeSaid = false;
+ storage = new Storage(filePath);
+ taskList = storage.load();
+ ui = new Ui();
+ }
+
+ /**
+ * Greets the user by printing a welcome message to the console.
+ */
+ public static void greet() {
+ String helloMsg = "Welcome, seeker. I am " + botName + ".\n" +
+ "What brings you here?";
+ ui.printResponse(helloMsg);
+ }
+
+ /**
+ * Prints a farewell message to the user and sets the exit flag.
+ */
+ public static void sayBye() {
+ String byeMsg = "Your path is set. Until we meet again.";
+ ui.printResponse(byeMsg);
+ isByeSaid = true;
+ }
+
+ /**
+ * Displays the current list of tasks, along with the total number of
+ * remaining tasks.
+ */
+ public static void listTasks() {
+ StringBuilder comment = new StringBuilder();
+ int remainingTasks = taskList.getNumberOfRemainingTasks();
+
+ comment.append("Now you have ").append(remainingTasks);
+ if (remainingTasks == 1) {
+ comment.append(" task in the list.");
+ } else {
+ comment.append(" tasks in the list.");
+ }
+
+ ui.printTaskList(taskList, "", comment.toString());
+ }
+
+ /**
+ * Adds a new generic task with the given name to the task list.
+ *
+ * @param taskName The name of the task to be added.
+ */
+ public static void addTask(String taskName) {
+ taskList.add(new Task(taskName));
+ ui.printAddedTask("added: " + taskName);
+ storage.save(taskList);
+ }
+
+ /**
+ * Adds a specific {@link Task} object to the task list.
+ *
+ * @param task The task to be added to the list.
+ */
+ public static void addTask(Task task) {
+ taskList.add(task);
+ ui.printAddedTask("added: " + task.getName());
+ storage.save(taskList);
+ }
+
+ /**
+ * Adds a new {@link ToDo} task to the task list.
+ *
+ * @param todoName The name of the ToDo task to be added.
+ */
+ public static void addToDo(String todoName) {
+ addTask(new ToDo(todoName));
+ }
+
+ /**
+ * Adds a new {@link Deadline} task to the task list with a specified due date.
+ *
+ * @param deadlineName The name of the deadline task.
+ * @param dueDate The due date of the deadline task.
+ */
+ public static void addDeadline(String deadlineName, String dueDate) {
+ addTask(new Deadline(deadlineName, dueDate));
+ }
+
+ /**
+ * Adds a new {@link Event} task to the task list with specified start and end dates.
+ *
+ * @param eventName The name of the event task.
+ * @param startDate The start date of the event.
+ * @param endDate The end date of the event.
+ */
+ public static void addEvent(String eventName, String startDate, String endDate) {
+ addTask(new Event(eventName, startDate, endDate));
+ }
+
+ /**
+ * Marks a task as completed by its task number.
+ *
+ * @param taskNumber The number of the task in the list (1-based index).
+ */
+ public static void markTask(Integer taskNumber) {
+ try {
+ taskList.markAsDone(taskNumber - 1);
+ String msg = "Nice! I've marked this task as done:\n\t" + taskList.get(taskNumber - 1).toString();
+ ui.printResponse(msg);
+ storage.save(taskList);
+ } catch (IndexOutOfBoundsException e) {
+ ui.printResponse("There is no such task :(");
+ }
+ }
+
+ /**
+ * Deletes a task from the list by its task number.
+ *
+ * @param taskNumber The number of the task to be deleted (1-based index).
+ */
+ public static void deleteTask(Integer taskNumber) {
+ try {
+ String msg = "Nice! I've deleted this task:\n\t" + taskList.get(taskNumber - 1).toString();
+ taskList.remove(taskNumber - 1);
+ ui.printResponse(msg);
+ storage.save(taskList);
+ } catch (IndexOutOfBoundsException e){
+ ui.printResponse("There is no such task :(");
+ }
+ }
+
+ /**
+ * Finds tasks that contain the specified keyword in their name and displays
+ * them to the user.
+ *
+ * @param taskKeyword The keyword to search for in task names.
+ */
+ public static void findTasks(String taskKeyword) {
+ TaskList filteredTaskList = new TaskList();
+ for (Task task : taskList) {
+ String taskName = task.getName();
+ if (taskName.contains(taskKeyword)) {
+ filteredTaskList.add(task);
+ }
+ }
+
+ if (filteredTaskList.getNumberOfTasks() == 0) {
+ ui.printResponse("There is no such task :(");
+ } else {
+ String commentBefore = "Here are the matching tasks in your list:";
+ String commentAfter = "";
+ ui.printTaskList(filteredTaskList, commentBefore, commentAfter);
+ }
+ }
+
+ /**
+ * Runs the main logic of the Pythia application. Continuously accepts user
+ * input and executes parsed commands until the user says "bye".
+ */
+ private static void run() {
+ ui.init();
+ greet();
+ Parser parser = new Parser();
+
+ while (!isByeSaid) {
+ try {
+ String request = ui.getRequest();
+ parser.parse(request);
+ parser.execute();
+ } catch (PythiaException e) {
+ ui.printResponse(e.getUserMessage());
+ }
+ }
+ }
+
+ /**
+ * The main method that runs the Pythia application. It initializes the UI,
+ * greets the user, and enters a loop that processes user input until the user
+ * says "bye".
+ *
+ * @param args Command-line arguments (unused).
+ */
+ public static void main(String[] args) {
+ new Pythia("data/pythia.txt").run();
+ }
+}
diff --git a/src/main/java/pythia/exceptions/PythiaException.java b/src/main/java/pythia/exceptions/PythiaException.java
new file mode 100644
index 000000000..1e188b167
--- /dev/null
+++ b/src/main/java/pythia/exceptions/PythiaException.java
@@ -0,0 +1,25 @@
+package pythia.exceptions;
+
+/**
+ * Represents a custom exception for the Pythia task management system.
+ * This exception extends {@link RuntimeException} and is used to signal
+ * errors that occur during the execution of Pythia's functionalities.
+ */
+public class PythiaException extends RuntimeException {
+ private String userMessage;
+
+ /**
+ * Constructs a PythiaException with the specified detail message and user message.
+ *
+ * @param message The detail message for the exception, which is logged for debugging purposes.
+ * @param userMessage A user-friendly message that can be displayed to the user.
+ */
+ public PythiaException(String message, String userMessage) {
+ super(message);
+ this.userMessage = userMessage;
+ }
+
+ public String getUserMessage() {
+ return userMessage;
+ }
+}
diff --git a/src/main/java/pythia/task/Deadline.java b/src/main/java/pythia/task/Deadline.java
new file mode 100644
index 000000000..22fbccfa3
--- /dev/null
+++ b/src/main/java/pythia/task/Deadline.java
@@ -0,0 +1,63 @@
+package pythia.task;
+
+import pythia.utility.WriteVisitor;
+
+import java.util.Date;
+
+/**
+ * Represents a deadline task that is part of the task management system.
+ * A deadline task includes a name and a due date, and can be marked as done or not done.
+ */
+public class Deadline extends Task {
+ private String dueDate;
+ private Date dueDateDay;
+
+ /**
+ * Constructs a Deadline with the specified name and due date.
+ * The deadline is initially marked as not done.
+ *
+ * @param name The name of the deadline.
+ * @param dueDate The due date of the deadline.
+ */
+ public Deadline(String name, String dueDate) {
+ super(name);
+ this.dueDate = dueDate;
+ }
+
+ /**
+ * Constructs a Deadline with the specified name, done status, and due date.
+ *
+ * @param name The name of the deadline.
+ * @param isDone The initial status of the deadline, indicating whether it is done or not.
+ * @param dueDate The due date of the deadline.
+ */
+ public Deadline(String name, boolean isDone, String dueDate) {
+ super(name);
+ this.dueDate = dueDate;
+ }
+
+ public String getDueDate() {
+ return dueDate;
+ }
+
+ /**
+ * Returns a string representation of the deadline, including its status and due date.
+ *
+ * @return A string indicating the type of task, its status, and its due date.
+ */
+ @Override
+ public String toString() {
+ return "[D]" + super.toString() + " " + "(by: " + dueDate + ")";
+ }
+
+ /**
+ * Accepts a {@link WriteVisitor} to perform a write operation on the deadline.
+ *
+ * @param visitor The visitor that handles writing the deadline's data to a storage format.
+ * @return A string representation of the deadline's data formatted for saving.
+ */
+ @Override
+ public String accept(WriteVisitor visitor) {
+ return visitor.visitDeadline(this);
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/pythia/task/Event.java b/src/main/java/pythia/task/Event.java
new file mode 100644
index 000000000..d8b2fb7a2
--- /dev/null
+++ b/src/main/java/pythia/task/Event.java
@@ -0,0 +1,73 @@
+package pythia.task;
+
+import pythia.utility.WriteVisitor;
+
+import java.util.Date;
+
+/**
+ * Represents an event task that is part of the task management system.
+ * An event task includes a name, a start date, and an end date, and can be marked as done or not done.
+ */
+public class Event extends Task {
+ private String startDate;
+ private Date startDateDay;
+ private String endDate;
+ private Date endDateDay;
+
+ /**
+ * Constructs an Event with the specified name, start date, and end date.
+ * The event is initially marked as not done.
+ *
+ * @param name The name of the event.
+ * @param startDate The start date of the event.
+ * @param endDate The end date of the event.
+ */
+ public Event(String name, String startDate, String endDate) {
+ super(name);
+ this.startDate = startDate;
+ this.endDate = endDate;
+ }
+
+ /**
+ * Constructs an Event with the specified name, done status, start date, and end date.
+ *
+ * @param name The name of the event.
+ * @param isDone The initial status of the event, indicating whether it is done or not.
+ * @param startDate The start date of the event.
+ * @param endDate The end date of the event.
+ */
+ public Event(String name, boolean isDone, String startDate, String endDate) {
+ super(name, isDone);
+ this.startDate = startDate;
+ this.endDate = endDate;
+ }
+
+ public String getStartDate() {
+ return startDate;
+ }
+
+ public String getEndDate() {
+ return endDate;
+ }
+
+ /**
+ * Returns a string representation of the event, including its status and date range.
+ *
+ * @return A string indicating the type of task, its status, and its date range.
+ */
+ @Override
+ public String toString() {
+ return "[E]" + super.toString() + " (from " + startDate + " to " + endDate + ")";
+ }
+
+ /**
+ * Accepts a {@link WriteVisitor} to perform a write operation on the event.
+ *
+ * @param visitor The visitor that handles writing the event's data to a storage format.
+ * @return A string representation of the event's data formatted for saving.
+ */
+ @Override
+ public String accept(WriteVisitor visitor) {
+ return visitor.visitEvent(this);
+ }
+}
diff --git a/src/main/java/pythia/task/Savable.java b/src/main/java/pythia/task/Savable.java
new file mode 100644
index 000000000..4abc90ca5
--- /dev/null
+++ b/src/main/java/pythia/task/Savable.java
@@ -0,0 +1,18 @@
+package pythia.task;
+
+import pythia.utility.WriteVisitor;
+
+/**
+ * Represents a contract for objects that can be saved.
+ * Classes implementing this interface must define how they interact with a
+ * {@link WriteVisitor} for the purpose of saving their state or data.
+ */
+public interface Savable {
+ /**
+ * Accepts a {@link WriteVisitor} to perform a write operation on the implementing object.
+ *
+ * @param visitor The visitor that handles writing the object's data to a storage format.
+ * @return A string representation of the object's data formatted for saving.
+ */
+ public String accept(WriteVisitor visitor);
+}
diff --git a/src/main/java/pythia/task/Task.java b/src/main/java/pythia/task/Task.java
new file mode 100644
index 000000000..18f88d641
--- /dev/null
+++ b/src/main/java/pythia/task/Task.java
@@ -0,0 +1,65 @@
+package pythia.task;
+
+import pythia.utility.WriteVisitor;
+
+/**
+ * Represents a task with a name and a completion status (whether it's done or not).
+ * It provides methods for marking the task as done or not done, and allows for
+ * string representations of the task for display and saving purposes.
+ */
+public class Task implements Savable {
+ private String name;
+ private boolean isDone;
+
+ public Task(String name) {
+ this.name = name;
+ this.isDone = false;
+ }
+
+ public Task(String name, boolean isDone) {
+ this.name = name;
+ this.isDone = isDone;
+ }
+
+ public void markAsDone() {
+ this.isDone = true;
+ }
+
+ public void markAsNotDone() {
+ this.isDone = false;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public boolean getDone() {
+ return isDone;
+ }
+
+ /**
+ * Returns a string representation of the task.
+ * The string indicates whether the task is done or not, followed by the task name.
+ *
+ * @return A string in the format: "[X] " if done, "[ ] " if not done.
+ */
+ @Override
+ public String toString() {
+ if (isDone == false) {
+ return "[ ] " + name;
+ } else {
+ return "[X] " + name;
+ }
+ }
+
+ /**
+ * Accepts a visitor to perform a write operation on this task for saving purposes.
+ *
+ * @param visitor The {@link WriteVisitor} that handles writing the task to a storage format.
+ * @return The formatted string representation of this task.
+ */
+ @Override
+ public String accept(WriteVisitor visitor) {
+ return visitor.visitTask(this);
+ }
+}
diff --git a/src/main/java/pythia/task/ToDo.java b/src/main/java/pythia/task/ToDo.java
new file mode 100644
index 000000000..4cc717a33
--- /dev/null
+++ b/src/main/java/pythia/task/ToDo.java
@@ -0,0 +1,38 @@
+package pythia.task;
+
+import pythia.utility.WriteVisitor;
+
+/**
+ * Represents a to-do task that is part of the task management system.
+ * A to-do task can be marked as done or not done and has a name associated with it.
+ */
+public class ToDo extends Task {
+ public ToDo(String name) {
+ super(name);
+ }
+
+ public ToDo(String name, boolean isDone) {
+ super(name, isDone);
+ }
+
+ /**
+ * Returns a string representation of the to-do task, including its status.
+ *
+ * @return A string indicating the type of task and its status.
+ */
+ @Override
+ public String toString() {
+ return "[T]" + super.toString();
+ }
+
+ /**
+ * Accepts a {@link WriteVisitor} to perform a write operation on the to-do task.
+ *
+ * @param visitor The visitor that handles writing the task's data to a storage format.
+ * @return A string representation of the to-do task's data formatted for saving.
+ */
+ @Override
+ public String accept(WriteVisitor visitor) {
+ return visitor.visitToDo(this);
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/pythia/utility/Parser.java b/src/main/java/pythia/utility/Parser.java
new file mode 100644
index 000000000..0085368b9
--- /dev/null
+++ b/src/main/java/pythia/utility/Parser.java
@@ -0,0 +1,215 @@
+package pythia.utility;
+
+import java.util.ArrayList;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+import pythia.exceptions.PythiaException;
+import pythia.Pythia;
+
+/**
+ * The {@code Parser} class is responsible for parsing and executing
+ * user commands in the Pythia application.
+ * It validates commands, extracts necessary arguments, and invokes
+ * corresponding actions.
+ */
+public class Parser {
+ private String commandType = null;
+ private ArrayList argumentList = null;
+ private String parsingErrorMessage = "Parsing of add command unsuccessful.";
+
+ /**
+ * Constructs a {@code Parser} object.
+ */
+ public Parser() {
+ }
+
+ /**
+ * Parses the command type from the given raw text.
+ * The command type is identified as the first word before any spaces.
+ *
+ * @param rawText the whole, unaltered command input from the user
+ * @return the command type as a string
+ */
+ private String parseCommandType(String rawText) {
+ int spaceIndex = rawText.indexOf(' ');
+
+ String firstPart;
+ if (spaceIndex != -1) {
+ firstPart = rawText.substring(0, spaceIndex);
+ } else {
+ firstPart = rawText;
+ }
+ return firstPart;
+ }
+
+ /**
+ * Handles parsing for the "bye" command.
+ * As no arguments are required for this command, this method is intentionally left empty.
+ *
+ * @param rawText the whole, unaltered command input from the user
+ */
+ private void parseBye(String rawText) {
+ }
+
+ /**
+ * Handles parsing for the "list" command.
+ * As no arguments are required for this command, this method is intentionally left empty.
+ *
+ * @param rawText the whole, unaltered command input from the user
+ */
+ private void parseList(String rawText) {
+ }
+
+ /**
+ * Parses the "add" command, checking if the input format is correct.
+ *
+ * @param rawText the whole, unaltered command input from the user
+ * @throws PythiaException if the command lacks a valid task description
+ */
+ private void parseAdd(String rawText) throws PythiaException {
+ argumentList = tokenize(rawText, "add\\s(.+)");
+ boolean isCorrectFormat = argumentList.size() == 1;
+
+ if (!isCorrectFormat) {
+ throw new PythiaException(parsingErrorMessage, "I am not sure what to add.");
+ }
+ }
+
+ /**
+ * Parses the "mark" command, ensuring it includes a valid task number.
+ *
+ * @param rawText the whole, unaltered command input from the user
+ * @throws PythiaException if the command lacks a valid task number
+ */
+ private void parseMark(String rawText) throws PythiaException {
+ argumentList = tokenize(rawText, "mark\\s(.+)");
+ boolean isCorrectFormat = argumentList.size() == 1 && argumentList.get(0).matches("\\d+");
+
+ if (!isCorrectFormat) {
+ throw new PythiaException(parsingErrorMessage, "Please specify what should I mark as done.");
+ }
+ }
+
+ private void parseToDo(String rawText) throws PythiaException {
+ argumentList = tokenize(rawText, "todo\\s(.+)");
+ boolean isCorrectFormat = argumentList.size() == 1;
+
+ if (!isCorrectFormat) {
+ throw new PythiaException(parsingErrorMessage, "Todo should have a description.");
+ }
+ }
+
+ private void parseDeadline(String rawText) throws PythiaException {
+ argumentList = tokenize(rawText, "deadline\\s(.+)\\s/by\\s(.+)");
+ boolean isCorrectFormat = argumentList.size() == 2;
+
+ if (!isCorrectFormat) {
+ throw new PythiaException(parsingErrorMessage, "Deadline should have description and a date.");
+ }
+ }
+
+ private void parseEvent(String rawText) throws PythiaException {
+ argumentList = tokenize(rawText, "event\\s(.+)\\s/from\\s(.+)\\s/to\\s(.+)");
+ boolean isCorrectFormat = argumentList.size() == 3;
+
+ if (!isCorrectFormat) {
+ throw new PythiaException(parsingErrorMessage, "Event should have description and from and to dates.");
+ }
+ }
+
+ public void parseDelete(String rawText) throws PythiaException {
+ argumentList = tokenize(rawText, "delete\\s(.+)");
+ boolean isCorrectFormat = argumentList.size() == 1 && argumentList.get(0).matches("\\d+");
+
+ if (!isCorrectFormat) {
+ throw new PythiaException(parsingErrorMessage, "Please specify what should I delete.");
+ }
+ }
+
+ public void parseFind(String rawText) throws PythiaException {
+ argumentList = tokenize(rawText, "find\\s(.+)");
+ boolean isCorrectFormat = argumentList.size() == 1;
+
+ if (!isCorrectFormat) {
+ throw new PythiaException(parsingErrorMessage, "Please specify what should I find.");
+ }
+ }
+
+ /**
+ * Splits the given command string into tokens based on the provided regular expression.
+ *
+ * Example usage:
+ * {@code
+ * String rawText = "event /from /to ";
+ * String regex = "event\\s(.+)\\s/from\\s(.+)\\s/to\\s(.+)";
+ * ArrayList tokens = tokenize(rawText, regex);
+ * // Resulting tokens = {, , }
+ * }
+ *
+ * @param rawText the whole, unaltered command input from the user
+ * @param regex the regular expression used to capture command arguments
+ * @return a list of tokens extracted from the input text
+ */
+ private ArrayList tokenize(String rawText, String regex) {
+ Pattern pattern = Pattern.compile(regex);
+ Matcher matcher = pattern.matcher(rawText);
+
+ ArrayList tokens = new ArrayList<>();
+
+ if (matcher.find()) {
+ for (int i = 1; i <= matcher.groupCount(); i++) {
+ tokens.add(matcher.group(i)); // Get specific captured groups
+ }
+ }
+
+ return tokens;
+ }
+
+ /**
+ * Parses the entire command input and determines which specific parsing method to call.
+ * The command is identified by its type and validated accordingly.
+ *
+ * @param rawText the whole, unaltered command input from the user
+ * @throws PythiaException if the command is invalid
+ */
+ public void parse(String rawText) throws PythiaException {
+ commandType = parseCommandType(rawText);
+
+ switch (commandType) {
+ case "bye" -> parseBye(rawText);
+ case "list" -> parseList(rawText);
+ case "add" -> parseAdd(rawText);
+ case "mark" -> parseMark(rawText);
+ case "todo" -> parseToDo(rawText);
+ case "deadline" -> parseDeadline(rawText);
+ case "event" -> parseEvent(rawText);
+ case "delete" -> parseDelete(rawText);
+ case "find" -> parseFind(rawText);
+ }
+ }
+
+ /**
+ * Executes the parsed command by invoking the corresponding method in the {@link Pythia} class.
+ * If no command has been parsed, it throws a {@link PythiaException}.
+ *
+ * @throws PythiaException if no command was parsed or the command is unrecognized
+ */
+ public void execute() throws PythiaException {
+ if (commandType == null) {
+ throw new PythiaException(parsingErrorMessage, "Sorry, I am busy.");
+ }
+
+ switch (commandType) {
+ case "bye" -> Pythia.sayBye();
+ case "list" -> Pythia.listTasks();
+ case "add" -> Pythia.addTask(argumentList.get(0));
+ case "mark" -> Pythia.markTask(Integer.parseInt(argumentList.get(0)));
+ case "todo" -> Pythia.addToDo(argumentList.get(0));
+ case "deadline" -> Pythia.addDeadline(argumentList.get(0), argumentList.get(1));
+ case "event" -> Pythia.addEvent(argumentList.get(0), argumentList.get(1), argumentList.get(2));
+ case "delete" -> Pythia.deleteTask(Integer.parseInt(argumentList.get(0)));
+ case "find" -> Pythia.findTasks(argumentList.get(0));
+ default -> throw new PythiaException(parsingErrorMessage, "Hmm. I am not sure what you mean.");
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/pythia/utility/Storage.java b/src/main/java/pythia/utility/Storage.java
new file mode 100644
index 000000000..b60b94593
--- /dev/null
+++ b/src/main/java/pythia/utility/Storage.java
@@ -0,0 +1,97 @@
+package pythia.utility;
+
+import pythia.task.Task;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+
+import java.io.*;
+
+/**
+ * The {@code Storage} class is responsible for saving
+ * and loading {@link TaskList} objects to and from a file.
+ * It provides methods to serialize a task list into a file
+ * and deserialize a task list from a file.
+ */
+public class Storage {
+ private String filePath;
+ private WriteVisitor writeVisitor;
+
+ /**
+ * Constructs a {@code Storage} object with the specified file path.
+ *
+ * @param filePath the path to the file where the {@link TaskList}
+ * will be stored or read from
+ */
+ public Storage(String filePath) {
+ this.filePath = filePath;
+ this.writeVisitor = new WriteVisitor();
+ }
+
+ /**
+ * Saves the given {@link TaskList} to a file at the specified path.
+ * Each task in the task list is serialized into a string representation
+ * using the {@link WriteVisitor} and written to the file.
+ *
+ * @param taskList the {@link TaskList} object to be saved to the file
+ */
+ public void save(TaskList taskList) {
+ try (BufferedWriter writer = new BufferedWriter(new FileWriter(filePath, false))) {
+ for (Task task : taskList) {
+ String taskAsString = task.accept(writeVisitor);
+ writer.write(taskAsString);
+ writer.newLine();
+ }
+ } catch (IOException e) {
+ System.err.println("Error writing to file: " + e.getMessage());
+ }
+ }
+
+ /**
+ * Loads a {@link TaskList} from the file at the specified path.
+ * Each line in the file is deserialized into a {@link Task} object using
+ * the {@link TaskFromStringFactory} and added to a new task list.
+ *
+ * @return the newly created {@link TaskList} object loaded from the file
+ */
+ public TaskList load() {
+ TaskList taskList = new TaskList();
+ TaskFromStringFactory factory = new TaskFromStringFactory();
+ Path path = Paths.get(filePath);
+ Path directory = path.getParent(); // Get the directory part of the path
+
+ // Check if the directory exists, if not create it
+ if (directory != null && Files.notExists(directory)) {
+ try {
+ Files.createDirectories(directory); // Create all missing directories
+ } catch (IOException e) {
+ System.err.println("Error creating directory: " + e.getMessage());
+ return taskList; // Return empty task list if directory creation fails
+ }
+ }
+
+ // Check if the file exists, if not create an empty file
+ if (Files.notExists(path)) {
+ try {
+ Files.createFile(path);
+ } catch (IOException e) {
+ System.err.println("Error creating new file: " + e.getMessage());
+ return taskList; // Return empty task list if file creation fails
+ }
+ return taskList; // Return empty task list for a newly created file
+ }
+
+ // If file exists, read the tasks from the file
+ try (BufferedReader reader = new BufferedReader(new FileReader(filePath))) {
+ String taskAsString;
+ while ((taskAsString = reader.readLine()) != null) {
+ Task task = factory.create(taskAsString);
+ taskList.add(task);
+ }
+ } catch (IOException e) {
+ System.err.println("Error reading from file: " + e.getMessage());
+ }
+
+ return taskList;
+ }
+}
diff --git a/src/main/java/pythia/utility/TaskFromStringFactory.java b/src/main/java/pythia/utility/TaskFromStringFactory.java
new file mode 100644
index 000000000..1f5079c2c
--- /dev/null
+++ b/src/main/java/pythia/utility/TaskFromStringFactory.java
@@ -0,0 +1,84 @@
+package pythia.utility;
+
+import pythia.task.Deadline;
+import pythia.task.Event;
+import pythia.task.Task;
+import pythia.task.ToDo;
+
+/**
+ * A factory class used for creating {@link Task} objects and its
+ * derived classes ({@link ToDo}, {@link Event}, {@link Deadline})
+ * from a string representation. The factory parses the string and
+ * creates the appropriate type of task based on the information.
+ */
+public class TaskFromStringFactory {
+ /**
+ * Creates a new {@link Task} object or one of its derived classes
+ * from the given string representation. The string must follow
+ * a specific format with fields separated by " | ". The first field
+ * determines the type of task:
+ *
+ * - {@code "T"} - {@link ToDo}
+ * - {@code "E"} - {@link Event}
+ * - {@code "D"} - {@link Deadline}
+ * - Any other identifier creates a generic {@link Task}
+ *
+ *
+ * @param taskAsString a string representation of the task,
+ * with fields separated by " | "
+ * @return an object of the appropriate task type ({@link ToDo},
+ * {@link Event}, {@link Deadline}, or {@link Task})
+ * @throws IllegalArgumentException if the string doesn't have at
+ * least two fields or the type identifier is missing
+ */
+ public Task create(String taskAsString) {
+ String[] fields = taskAsString.split(" \\| ");
+
+ if (fields.length < 2) {
+ throw new IllegalArgumentException("Task should have at least 2 fields");
+ }
+
+ String typeIdentifier = fields[0];
+ return switch (typeIdentifier) {
+ case "T" -> createToDo(fields);
+ case "E" -> createEvent(fields);
+ case "D" -> createDeadline(fields);
+ default -> createTask(fields);
+ };
+ }
+
+ /**
+ * Creates a generic {@link Task} from the parsed string fields.
+ * This is used if no specific type identifier is found in the string.
+ *
+ * @param fields an array of string fields representing the task attributes
+ * @return a new {@link Task} object
+ */
+ private Task createTask(String[] fields) {
+ boolean isDone = Boolean.parseBoolean(fields[0]);
+ String name = fields[1];
+ return new Task(name, isDone);
+ }
+
+ private ToDo createToDo(String[] fields) {
+ boolean isDone = Boolean.parseBoolean(fields[1]);
+ String name = fields[2];
+ return new ToDo(name, isDone);
+ }
+
+ private Event createEvent(String[] fields) {
+ boolean isDone = Boolean.parseBoolean(fields[1]);
+ String name = fields[2];
+ String startDate = fields[3];
+ String endDate = fields[4];
+ return new Event(name, isDone, startDate, endDate);
+ }
+
+ private Deadline createDeadline(String[] fields) {
+ boolean isDone = Boolean.parseBoolean(fields[1]);
+ String name = fields[2];
+
+ String dueDate = fields[3];
+ return new Deadline(name, isDone, dueDate);
+ }
+}
diff --git a/src/main/java/pythia/utility/TaskList.java b/src/main/java/pythia/utility/TaskList.java
new file mode 100644
index 000000000..8721496c9
--- /dev/null
+++ b/src/main/java/pythia/utility/TaskList.java
@@ -0,0 +1,122 @@
+package pythia.utility;
+import pythia.task.Task;
+import java.util.ArrayList;
+import java.util.Iterator;
+
+/**
+ * Class used for storing and managing a list of {@link Task} objects.
+ * This class allows adding, removing, marking tasks as done, and
+ * iterating over the task list. It also keeps track of the number of
+ * remaining tasks that have not been completed.
+ */
+public class TaskList implements Iterable {
+ private ArrayList taskList = new ArrayList();
+ private int remainingTasks = 0; // number of tasks which are not done yet
+ // might be different from array size
+ /**
+ * Constructs an empty {@code TaskList}.
+ */
+ public TaskList() {}
+
+ /**
+ * Returns the number of tasks that have not been marked as done.
+ *
+ * @return the number of remaining tasks
+ */
+ public int getNumberOfRemainingTasks() {
+ return remainingTasks;
+ }
+
+ /**
+ * Returns the total number of tasks in the list, regardless of their completion status.
+ *
+ * @return the total number of tasks
+ */
+ public int getNumberOfTasks() {
+ return taskList.size();
+ }
+
+ /**
+ * Adds a new task to the list and increases the count of remaining tasks.
+ *
+ * @param task the {@link Task} to be added to the list
+ */
+ public void add(Task task) {
+ taskList.add(task);
+ remainingTasks++;
+ }
+
+ /**
+ * Removes the task at the specified index from the list and decreases the count of remaining tasks.
+ *
+ * @param taskNumber the index of the task to be removed
+ * @throws IndexOutOfBoundsException if the index is out of range (less than 0 or greater than or equal to the list size)
+ */
+ public void remove(int taskNumber) throws IndexOutOfBoundsException {
+ if (taskNumber < 0 || taskNumber >= taskList.size()) {
+ throw new IndexOutOfBoundsException();
+ }
+ taskList.remove(taskNumber);
+ remainingTasks--;
+ }
+
+ /**
+ * Returns the task at the specified index in the list.
+ *
+ * @param taskNumber the index of the task to retrieve
+ * @return the {@link Task} at the specified index
+ * @throws IndexOutOfBoundsException if the index is out of range (less than 0 or greater than or equal to the list size)
+ */
+ public Task get(int taskNumber) {
+ return taskList.get(taskNumber);
+ }
+
+ /**
+ * Marks the task at the specified index as done and decreases the count of remaining tasks.
+ *
+ * @param taskNumber the index of the task to mark as done
+ * @throws IndexOutOfBoundsException if the index is out of range (less than 0 or greater than or equal to the list size)
+ */
+ public void markAsDone(int taskNumber) {
+ if (taskNumber < 0 || taskNumber >= taskList.size()) {
+ throw new IndexOutOfBoundsException();
+ }
+ taskList.get(taskNumber).markAsDone();
+ remainingTasks--;
+ }
+
+ /**
+ * Returns an iterator over the tasks in the list in proper sequence.
+ *
+ * @return an {@link Iterator} over the tasks in the list
+ */
+ @Override
+ public Iterator iterator() {
+ return taskList.iterator();
+ }
+
+ /**
+ * Returns a string representation of the task list. Each task is prefixed with its index number
+ * and listed in sequence. The tasks are separated by new lines.
+ *
+ * @return a string representing the task list
+ */
+ @Override
+ public String toString() {
+ if (taskList.isEmpty()) {
+ return "";
+ }
+
+ StringBuilder taskListString = new StringBuilder();
+ for (int i = 0; i < taskList.size() - 1; i++) {
+ taskListString.append(i + 1).append(". ");
+ taskListString.append(taskList.get(i).toString());
+ taskListString.append("\n");
+ }
+ int lastIndex = taskList.size() - 1;
+ taskListString.append(lastIndex + 1).append(". ");
+ taskListString.append(taskList.get(lastIndex).toString());
+
+ return taskListString.toString();
+ }
+}
diff --git a/src/main/java/pythia/utility/Ui.java b/src/main/java/pythia/utility/Ui.java
new file mode 100644
index 000000000..7c05aa55c
--- /dev/null
+++ b/src/main/java/pythia/utility/Ui.java
@@ -0,0 +1,74 @@
+package pythia.utility;
+
+import java.util.Scanner;
+
+/**
+ * A class responsible for handling user interaction and displaying information to the console.
+ * It provides methods to display responses, get user input, and format task lists for output.
+ */
+public class Ui {
+ private static Scanner scanner;;
+
+ /**
+ * Initializes the scanner object for reading user input.
+ */
+ public void init() {
+ scanner = new Scanner(System.in);
+ }
+
+ /**
+ * Prints a formatted response to the console with line separators.
+ *
+ * @param text The message to be printed.
+ */
+ public void printResponse(String text) {
+ String lineSeparator = "____________________________________________________________";
+ text = lineSeparator + "\n" + text + "\n" + lineSeparator + "\n";
+ String formattedText = text.replaceAll("(?m)^", "\t");
+ System.out.print(formattedText);
+ }
+
+ /**
+ * Retrieves a user's input from the console.
+ *
+ * @return The raw string input from the user.
+ */
+ public String getRequest() {
+ return scanner.nextLine();
+ }
+
+ /**
+ * Prints a response for when a task is added, formatting the text to be all lowercase.
+ *
+ * @param text The message to be printed, typically confirming a task addition.
+ */
+ public void printAddedTask(String text) {
+ printResponse(text.toLowerCase());
+ }
+
+ /**
+ * Prints the entire task list to the console, including optional comments
+ * before and after the list.
+ *
+ * @param taskList The list of tasks to be printed.
+ * @param commentBefore An optional comment to be printed before the task list.
+ * @param commentAfter An optional comment to be printed after the task list.
+ */
+ public void printTaskList(TaskList taskList, String commentBefore, String commentAfter) {
+ StringBuilder response = new StringBuilder();
+ if (commentBefore != null && !commentBefore.isEmpty()) {
+ response.append(commentBefore).append("\n");
+ }
+
+ String taskListAsString = taskList.toString();
+ if (taskListAsString != null && !taskListAsString.isEmpty()) {
+ response.append(taskListAsString);
+ }
+
+ if (commentAfter != null && !commentAfter.isEmpty()) {
+ response.append("\n").append(commentAfter);
+ }
+
+ printResponse(response.toString());
+ }
+}
diff --git a/src/main/java/pythia/utility/WriteVisitor.java b/src/main/java/pythia/utility/WriteVisitor.java
new file mode 100644
index 000000000..3530b832f
--- /dev/null
+++ b/src/main/java/pythia/utility/WriteVisitor.java
@@ -0,0 +1,57 @@
+package pythia.utility;
+
+import pythia.task.Deadline;
+import pythia.task.Event;
+import pythia.task.Task;
+import pythia.task.ToDo;
+
+/**
+ * A visitor class responsible for converting various task objects ({@link Task}, {@link ToDo},
+ * {@link Deadline}, {@link Event}) into their respective string representations. The resulting
+ * string format is suitable for storage in text files.
+ */
+public class WriteVisitor {
+
+ /**
+ * Generates a string representation of a generic task.
+ *
+ * @param task The task to be converted to a string.
+ * @return A string representing the task in the format "doneStatus | taskName".
+ */
+ public String visitTask(Task task) {
+ return task.getDone() + " | " + task.getName();
+ }
+
+ /**
+ * Generates a string representation of a {@link ToDo} task.
+ *
+ * @param toDo The {@link ToDo} task to be converted to a string.
+ * @return A string representing the {@link ToDo} task in the format "T | doneStatus | taskName".
+ */
+ public String visitToDo(ToDo toDo) {
+ return "T" + " | " + visitTask(toDo);
+ }
+
+ /**
+ * Generates a string representation of an {@link Event} task, including its start
+ * and end dates.
+ *
+ * @param event The {@link Event} task to be converted to a string.
+ * @return A string representing the {@link Event} task in the format
+ * "E | doneStatus | taskName | startDate | endDate".
+ */
+ public String visitEvent(Event event) {
+ return "E" + " | " + visitTask(event) + " | " + event.getStartDate() + " | " + event.getEndDate();
+ }
+
+ /**
+ * Generates a string representation of a Deadline task, including its due date.
+ *
+ * @param deadline The {@link Deadline} task to be converted to a string.
+ * @return A string representing the {@link Deadline} task in the format
+ * "D | doneStatus | taskName | dueDate".
+ */
+ public String visitDeadline(Deadline deadline) {
+ return "D" + " | " + visitTask(deadline) + " | " + deadline.getDueDate();
+ }
+}
diff --git a/text-ui-test/EXPECTED.TXT b/text-ui-test/EXPECTED.TXT
index 657e74f6e..8a3016cae 100644
--- a/text-ui-test/EXPECTED.TXT
+++ b/text-ui-test/EXPECTED.TXT
@@ -1,7 +1,53 @@
-Hello from
- ____ _
-| _ \ _ _| | _____
-| | | | | | | |/ / _ \
-| |_| | |_| | < __/
-|____/ \__,_|_|\_\___|
-
+ ____________________________________________________________
+ Welcome, seeker. I am Pythia.
+ What brings you here?
+ ____________________________________________________________
+ ____________________________________________________________
+ Now you have 0 tasks in the list.
+ ____________________________________________________________
+ ____________________________________________________________
+ added: read a book
+ ____________________________________________________________
+ ____________________________________________________________
+ 1. [ ] read a book
+ Now you have 1 task in the list.
+ ____________________________________________________________
+ ____________________________________________________________
+ added: borrow book
+ ____________________________________________________________
+ ____________________________________________________________
+ 1. [ ] read a book
+ 2. [T][ ] borrow book
+ Now you have 2 tasks in the list.
+ ____________________________________________________________
+ ____________________________________________________________
+ Nice! I've marked this task as done:
+ [T][X] borrow book
+ ____________________________________________________________
+ ____________________________________________________________
+ 1. [ ] read a book
+ 2. [T][X] borrow book
+ Now you have 1 task in the list.
+ ____________________________________________________________
+ ____________________________________________________________
+ added: return book
+ ____________________________________________________________
+ ____________________________________________________________
+ 1. [ ] read a book
+ 2. [T][X] borrow book
+ 3. [D][ ] return book (by: Sunday)
+ Now you have 2 tasks in the list.
+ ____________________________________________________________
+ ____________________________________________________________
+ added: project meeting
+ ____________________________________________________________
+ ____________________________________________________________
+ 1. [ ] read a book
+ 2. [T][X] borrow book
+ 3. [D][ ] return book (by: Sunday)
+ 4. [E][ ] project meeting (from Mon 2pm to 4pm)
+ Now you have 3 tasks in the list.
+ ____________________________________________________________
+ ____________________________________________________________
+ Your path is set. Until we meet again.
+ ____________________________________________________________
diff --git a/text-ui-test/input.txt b/text-ui-test/input.txt
index e69de29bb..9f5d4ae31 100644
--- a/text-ui-test/input.txt
+++ b/text-ui-test/input.txt
@@ -0,0 +1,12 @@
+list
+add read a book
+list
+todo borrow book
+list
+mark 2
+list
+deadline return book /by Sunday
+list
+event project meeting /from Mon 2pm /to 4pm
+list
+bye
\ No newline at end of file
diff --git a/text-ui-test/runtest.sh b/text-ui-test/runtest.sh
old mode 100644
new mode 100755
index c9ec87003..d67b4adb5
--- a/text-ui-test/runtest.sh
+++ b/text-ui-test/runtest.sh
@@ -20,7 +20,7 @@ then
fi
# run the program, feed commands from input.txt file and redirect the output to the ACTUAL.TXT
-java -classpath ../bin Duke < input.txt > ACTUAL.TXT
+java -classpath ../bin Pythia < input.txt > ACTUAL.TXT
# convert to UNIX format
cp EXPECTED.TXT EXPECTED-UNIX.TXT