diff --git a/.gitignore b/.gitignore
index f69985ef1f..fe8925d278 100644
--- a/.gitignore
+++ b/.gitignore
@@ -15,3 +15,5 @@ bin/
 
 /text-ui-test/ACTUAL.txt
 text-ui-test/EXPECTED-UNIX.TXT
+text-ui-test/*
+Duke_Tasks
diff --git a/build.gradle b/build.gradle
new file mode 100644
index 0000000000..2e0733d270
--- /dev/null
+++ b/build.gradle
@@ -0,0 +1,53 @@
+plugins {
+    id 'java'
+    id 'application'
+    id 'checkstyle'
+    id 'com.github.johnrengelman.shadow' version '5.1.0'
+}
+
+checkstyle {
+    toolVersion = '10.2'
+}
+
+run {
+    enableAssertions = true
+}
+
+group 'org.example'
+version '0.2'
+mainClassName = 'duke.gui.Launcher'
+
+// Output to build/libs/Duke.jar
+shadowJar {
+    archiveBaseName.set('Duke')
+    archiveClassifier.set('')
+    archiveVersion.set('')
+}
+
+repositories {
+    mavenCentral()
+}
+
+test {
+    useJUnitPlatform()
+}
+
+dependencies {
+    testImplementation group: 'org.junit.jupiter', name: 'junit-jupiter-api', version: '5.5.0'
+    testRuntimeOnly group: 'org.junit.jupiter', name: 'junit-jupiter-engine', version: '5.5.0'
+
+    String javaFxVersion = '11'
+
+    implementation group: 'org.openjfx', name: 'javafx-base', version: javaFxVersion, classifier: 'win'
+    implementation group: 'org.openjfx', name: 'javafx-base', version: javaFxVersion, classifier: 'mac'
+    implementation group: 'org.openjfx', name: 'javafx-base', version: javaFxVersion, classifier: 'linux'
+    implementation group: 'org.openjfx', name: 'javafx-controls', version: javaFxVersion, classifier: 'win'
+    implementation group: 'org.openjfx', name: 'javafx-controls', version: javaFxVersion, classifier: 'mac'
+    implementation group: 'org.openjfx', name: 'javafx-controls', version: javaFxVersion, classifier: 'linux'
+    implementation group: 'org.openjfx', name: 'javafx-fxml', version: javaFxVersion, classifier: 'win'
+    implementation group: 'org.openjfx', name: 'javafx-fxml', version: javaFxVersion, classifier: 'mac'
+    implementation group: 'org.openjfx', name: 'javafx-fxml', version: javaFxVersion, classifier: 'linux'
+    implementation group: 'org.openjfx', name: 'javafx-graphics', version: javaFxVersion, classifier: 'win'
+    implementation group: 'org.openjfx', name: 'javafx-graphics', version: javaFxVersion, classifier: 'mac'
+    implementation group: 'org.openjfx', name: 'javafx-graphics', version: javaFxVersion, classifier: 'linux'
+}
\ No newline at end of file
diff --git a/config/checkstyle/checkstyle.xml b/config/checkstyle/checkstyle.xml
new file mode 100644
index 0000000000..fb88cedfc2
--- /dev/null
+++ b/config/checkstyle/checkstyle.xml
@@ -0,0 +1,434 @@
+
+
+
+
+
+
+
+    
+        
+    
+
+    
+        
+        
+    
+
+    
+        
+        
+        
+    
+
+    
+        
+    
+
+    
+        
+        
+    
+
+    
+    
+
+        
+        
+            
+            
+            
+        
+
+        
+
+        
+        
+            
+            
+            
+            
+        
+
+        
+        
+
+        
+        
+
+        
+
+        
+
+        
+        
+            
+            
+        
+
+        
+            
+            
+            
+        
+
+        
+            
+            
+            
+        
+
+        
+            
+            
+            
+            
+            
+        
+
+        
+            
+            
+            
+            
+        
+
+        
+            
+            
+            
+            
+        
+
+        
+            
+            
+            
+        
+
+        
+            
+            
+        
+
+        
+            
+            
+        
+
+        
+            
+            
+        
+
+
+        
+
+        
+        
+
+        
+        
+
+        
+            
+            
+        
+
+        
+            
+            
+            
+        
+
+        
+        
+            
+            
+        
+
+        
+        
+
+        
+
+        
+        
+
+        
+            
+            
+        
+
+        
+
+        
+        
+            
+            
+            
+        
+
+        
+
+        
+        
+
+        
+        
+
+        
+        
+
+        
+            
+        
+
+        
+
+        
+
+        
+        
+
+        
+            
+            
+            
+            
+            
+            
+            
+            
+            
+            
+            
+            
+        
+
+        
+            
+            
+        
+
+        
+            
+            
+            
+        
+
+        
+        
+            
+            
+            
+        
+
+        
+            
+            
+            
+        
+        
+            
+            
+            
+        
+
+        
+            
+            
+            
+        
+        
+            
+            
+            
+        
+
+        
+            
+            
+        
+
+        
+            
+            
+            
+        
+
+        
+
+        
+        
+
+        
+            
+            
+        
+
+        
+        
+            
+            
+        
+
+        
+
+        
+        
+
+        
+        
+
+        
+        
+
+        
+        
+
+        
+        
+
+        
+        
+
+        
+        
+            
+        
+
+        
+        
+            
+            
+            
+            
+            
+            
+        
+
+        
+
+        
+        
+            
+            
+            
+        
+
+        
+        
+
+    
+
\ 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/README.md b/docs/README.md
index 8077118ebe..8e0cc72ae9 100644
--- a/docs/README.md
+++ b/docs/README.md
@@ -1,29 +1,193 @@
-# User Guide
+# Duke: User Guide
 
-## Features 
+## Features
 
-### Feature-ABC
+### Commands:
 
-Description of the feature.
 
-### Feature-XYZ
+| Command | Description |
+| --- | --- |
+| [todo](#create-a-todo-todo) | Adds a new Todo task |
+| [deadline](#create-a-deadline-deadline) | Adds a new Deadline task |
+| [event](#create-an-event-event) | Adds a new Event task |
+| [list](#list-out-all-tasks-list) | Shows info on all stored tasks |
+| [mark](#mark-a-task-as-done-mark) | Indicates a task is done |
+| [unmark](#mark-a-finished-task-as-incomplete-unmark) | Indicates a task is incomplete |
+| [delete](#delete-a-task-delete) | Remove a task |
+| [find](#find-tasks-containing-a-keyword-find) | List out the tasks whose description contains a certain keyword |
+| [sort](#sort-tasks-in-chronological-order-sort) | Sort tasks in ascending order of time value,  and number of time attributes |
+| [SAVE](#ensure-data-on-tasks-is-saved-save ) | Make Duke save data on exit |
+| [WIPE](#ensure-data-on-tasks-is-deleted-wipe) | Make Duke clear data on exit |
+| [bye](#quit-bye) | Exit the app |
 
-Description of the feature.
 
 ## Usage
 
-### `Keyword` - Describe action
+### Format Annotations:
 
-Describe the action and its outcome.
+[] indicates a required argument, [[]] indicates an optional argument
 
-Example of usage: 
+### Create a Todo: `todo`
 
-`keyword (optional arguments)`
+Adds a new Todo task
+
+Format: `todo [DESCRIPTION]`
+
+Example of usage: `todo TP team meeting`
+
+Expected outcome:
+```  
+Got it. I've added this task:  
+  [T][ ] TP team meeting
+Now you have 1 task in the list.  
+```  
+
+### Create a Deadline: `deadline`
+
+Adds a new Deadline task
+
+Format: `deadline [DESCRIPTION] /by [D/M/YYYY] [[HH:MM(AM/PM)]]`
+
+Example of usage: `deadline IP /by 19/9/2022 11:59PM`
+
+Expected outcome:
+```  
+Got it. I've added this task:  
+  [D][ ] IP (by 19 Sep 2022, 11:59PM)
+Now you have 2 tasks in the list.  
+```
+### Create an Event: `event`
+
+Adds a new Event task
+
+Format: `event [DESCRIPTION] /at [D/M/YYYY] [[HH:MM(AM/PM)]]`
+
+Example of usage: `event TP tutorials /at 21/9/2022`
+
+Expected outcome:
+```  
+Got it. I've added this task:  
+  [E][ ] TP tutorials (by 21 Sep 2022)
+Now you have 3 tasks in the list.  
+```
+### List out all tasks: `list`
+
+Shows info on all stored tasks
+
+Format: `list`
+
+Example of usage: `list`
+
+Expected outcome:
+```  
+Ok, here are your tasks:
+  1. [T][ ] TP team meeting
+  2. [D][ ] IP (by 19 Sep 2022, 11:59PM)
+  3. [E][ ] TP tutorials (by 21 Sep 2022)  
+```
+### Mark a task as done: `mark`
+
+Indicates a task is done
+
+Format: `mark [TASK NUMBER]`
+
+Example of usage: `mark 1`
+
+Expected outcome:
+```  
+Nice! I've marked this task as done:
+  [T][X] TP team meeting
+```
+### Mark a finished task as incomplete: `unmark`
+
+Indicates a task is incomplete
+
+Format: `unmark [TASK NUMBER]`
+
+Example of usage: `unmark 1`
+
+Expected outcome:
+```  
+OK, I've marked this task as not done yet:
+  [T][ ] TP team meeting
+```
+### Delete a task: `delete`
+
+Remove a task
+
+Format: `delete [TASK NUMBER]`
+
+Example of usage: `delete 3`
+
+Expected outcome:
+```
+Noted. I've removed this task:
+  [E][ ] TP tutorials (at: 21 Sep 2022)
+Now you have 2 tasks in the list.
+```
+### Find tasks containing a keyword: `find`
+
+List out the tasks whose description contains a certain keyword
+
+Format: `find [KEYWORD]`
+
+Example of usage: `find TP`
 
 Expected outcome:
+```
+Here are the tasks containing the keyword "TP" :
+  1. [T][ ] TP team meeting
+  3. [E][ ] TP tutorials (at: 21 Sep 2022)
+```
+### Sort tasks in chronological order: `sort`
+
+Sort tasks in ascending order of time value,  and number of time attributes
+
+Format: `sort`
 
-Description of the outcome.
+Example of usage: `sort`
 
+Expected outcome:
+```
+Ok, here are your tasks:
+  1. [T][ ] TP team meeting
+  2. [E][ ] TP tutorials (at: 21 Sep 2022)
+  3. [D][ ] IP (by: 19 Sep 2022, 11:59PM)
 ```
-expected output
+### Ensure data on tasks is saved: `SAVE`
+
+Make Duke save data on exit
+
+Format: `SAVE`
+
+Example of usage: `SAVE`
+
+Expected outcome:
+```
+Data will be saved on exit
+```
+### Ensure data on tasks is deleted: `WIPE`
+
+Make Duke clear data on exit
+
+Format: `WIPE`
+
+Example of usage: `WIPE`
+
+Expected outcome:
+```
+Data will be wiped on exit
+```
+### Quit: `bye`
+
+Exit the app
+
+Format: `bye`
+
+Example of usage: `bye`
+
+Expected outcome:
 ```
+Bye. Hope to see you again soon!
+[App closes after a few seconds]
+```
\ No newline at end of file
diff --git a/docs/Ui.png b/docs/Ui.png
new file mode 100644
index 0000000000..31e11f1500
Binary files /dev/null and b/docs/Ui.png differ
diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar
new file mode 100644
index 0000000000..41d9927a4d
Binary files /dev/null and b/gradle/wrapper/gradle-wrapper.jar differ
diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties
new file mode 100644
index 0000000000..41dfb87909
--- /dev/null
+++ b/gradle/wrapper/gradle-wrapper.properties
@@ -0,0 +1,5 @@
+distributionBase=GRADLE_USER_HOME
+distributionPath=wrapper/dists
+distributionUrl=https\://services.gradle.org/distributions/gradle-7.4-bin.zip
+zipStoreBase=GRADLE_USER_HOME
+zipStorePath=wrapper/dists
diff --git a/gradlew b/gradlew
new file mode 100644
index 0000000000..1b6c787337
--- /dev/null
+++ b/gradlew
@@ -0,0 +1,234 @@
+#!/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/master/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
+
+APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit
+
+APP_NAME="Gradle"
+APP_BASE_NAME=${0##*/}
+
+# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
+
+# Use the maximum available, or set MAX_FD != -1 to use that value.
+MAX_FD=maximum
+
+warn () {
+    echo "$*"
+} >&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
+    which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+fi
+
+# Increase the maximum file descriptors if we can.
+if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
+    case $MAX_FD in #(
+      max*)
+        MAX_FD=$( ulimit -H -n ) ||
+            warn "Could not query maximum file descriptor limit"
+    esac
+    case $MAX_FD in  #(
+      '' | soft) :;; #(
+      *)
+        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
+
+# 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 \
+        "$@"
+
+# 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..107acd32c4
--- /dev/null
+++ b/gradlew.bat
@@ -0,0 +1,89 @@
+@rem
+@rem Copyright 2015 the original author or authors.
+@rem
+@rem Licensed under the Apache License, Version 2.0 (the "License");
+@rem you may not use this file except in compliance with the License.
+@rem You may obtain a copy of the License at
+@rem
+@rem      https://www.apache.org/licenses/LICENSE-2.0
+@rem
+@rem Unless required by applicable law or agreed to in writing, software
+@rem distributed under the License is distributed on an "AS IS" BASIS,
+@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+@rem See the License for the specific language governing permissions and
+@rem limitations under the License.
+@rem
+
+@if "%DEBUG%" == "" @echo off
+@rem ##########################################################################
+@rem
+@rem  Gradle startup script for Windows
+@rem
+@rem ##########################################################################
+
+@rem Set local scope for the variables with windows NT shell
+if "%OS%"=="Windows_NT" setlocal
+
+set DIRNAME=%~dp0
+if "%DIRNAME%" == "" set DIRNAME=.
+set APP_BASE_NAME=%~n0
+set APP_HOME=%DIRNAME%
+
+@rem Resolve any "." and ".." in APP_HOME to make it shorter.
+for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
+
+@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
+
+@rem Find java.exe
+if defined JAVA_HOME goto findJavaFromJavaHome
+
+set JAVA_EXE=java.exe
+%JAVA_EXE% -version >NUL 2>&1
+if "%ERRORLEVEL%" == "0" goto 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%"=="0" goto mainEnd
+
+:fail
+rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
+rem the _cmd.exe /c_ return code!
+if  not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
+exit /b 1
+
+:mainEnd
+if "%OS%"=="Windows_NT" endlocal
+
+:omega
diff --git a/src/main/java/Duke.java b/src/main/java/Duke.java
deleted file mode 100644
index 5d313334cc..0000000000
--- a/src/main/java/Duke.java
+++ /dev/null
@@ -1,10 +0,0 @@
-public class Duke {
-    public static void main(String[] args) {
-        String logo = " ____        _        \n"
-                + "|  _ \\ _   _| | _____ \n"
-                + "| | | | | | | |/ / _ \\\n"
-                + "| |_| | |_| |   <  __/\n"
-                + "|____/ \\__,_|_|\\_\\___|\n";
-        System.out.println("Hello from\n" + logo);
-    }
-}
diff --git a/src/main/java/duke/gui/DialogBox.java b/src/main/java/duke/gui/DialogBox.java
new file mode 100644
index 0000000000..c9504117c5
--- /dev/null
+++ b/src/main/java/duke/gui/DialogBox.java
@@ -0,0 +1,62 @@
+package duke.gui;
+
+import java.io.IOException;
+import java.util.Collections;
+
+import javafx.collections.FXCollections;
+import javafx.collections.ObservableList;
+import javafx.fxml.FXML;
+import javafx.fxml.FXMLLoader;
+import javafx.geometry.Pos;
+import javafx.scene.Node;
+import javafx.scene.control.Label;
+import javafx.scene.image.Image;
+import javafx.scene.image.ImageView;
+import javafx.scene.layout.HBox;
+
+/**
+ * An example of a custom control using FXML.
+ * This control represents a dialog box consisting of an ImageView to represent the speaker's face and a label
+ * containing text from the speaker.
+ */
+public class DialogBox extends HBox {
+    @FXML
+    private Label dialog;
+    @FXML
+    private ImageView displayPicture;
+
+    private DialogBox(String text, Image img) {
+        try {
+            FXMLLoader fxmlLoader = new FXMLLoader(MainWindow.class.getResource("/view/DialogBox.fxml"));
+            fxmlLoader.setController(this);
+            fxmlLoader.setRoot(this);
+            fxmlLoader.load();
+        } catch (IOException e) {
+            e.printStackTrace();
+        }
+
+        dialog.setText(text);
+        displayPicture.setImage(img);
+    }
+
+    /**
+     * Flips the dialog box such that the ImageView is on the left and text on the right.
+     *
+     * @return The box
+     */
+    private DialogBox flip() {
+        ObservableList tmp = FXCollections.observableArrayList(this.getChildren());
+        Collections.reverse(tmp);
+        getChildren().setAll(tmp);
+        setAlignment(Pos.TOP_LEFT);
+        return this;
+    }
+
+    public static DialogBox getUserDialog(String text, Image img) {
+        return new DialogBox(text, img);
+    }
+
+    public static DialogBox getDukeDialog(String text, Image img) {
+        return new DialogBox(text, img).flip();
+    }
+}
diff --git a/src/main/java/duke/gui/Launcher.java b/src/main/java/duke/gui/Launcher.java
new file mode 100644
index 0000000000..2bd944a7b2
--- /dev/null
+++ b/src/main/java/duke/gui/Launcher.java
@@ -0,0 +1,12 @@
+package duke.gui;
+
+import javafx.application.Application;
+
+/**
+ * A launcher class to workaround classpath issues.
+ */
+public class Launcher {
+    public static void main(String[] args) {
+        Application.launch(Main.class, args);
+    }
+}
diff --git a/src/main/java/duke/gui/Main.java b/src/main/java/duke/gui/Main.java
new file mode 100644
index 0000000000..048c557815
--- /dev/null
+++ b/src/main/java/duke/gui/Main.java
@@ -0,0 +1,32 @@
+package duke.gui;
+
+import java.io.IOException;
+
+import javafx.application.Application;
+import javafx.fxml.FXMLLoader;
+import javafx.scene.Scene;
+import javafx.scene.layout.AnchorPane;
+import javafx.stage.Stage;
+
+/**
+ * A GUI for Duke using FXML.
+ */
+public class Main extends Application {
+
+    @Override
+    public void start(Stage stage) {
+        try {
+            FXMLLoader fxmlLoader = new FXMLLoader(Main.class.getResource("/view/MainWindow.fxml"));
+            AnchorPane ap = fxmlLoader.load();
+            Scene scene = new Scene(ap);
+            stage.setScene(scene);
+            stage.setTitle("Duke");
+            stage.setResizable(false);
+            stage.setMinHeight(600.0);
+            stage.setMinWidth(400.0);
+            stage.show();
+        } catch (IOException e) {
+            e.printStackTrace();
+        }
+    }
+}
diff --git a/src/main/java/duke/gui/MainWindow.java b/src/main/java/duke/gui/MainWindow.java
new file mode 100644
index 0000000000..685dd707b1
--- /dev/null
+++ b/src/main/java/duke/gui/MainWindow.java
@@ -0,0 +1,69 @@
+package duke.gui;
+
+import java.io.IOException;
+import java.util.Timer;
+import java.util.TimerTask;
+
+import duke.services.Duke;
+import duke.services.Ui;
+import javafx.application.Platform;
+import javafx.fxml.FXML;
+import javafx.scene.control.Button;
+import javafx.scene.control.ScrollPane;
+import javafx.scene.control.TextField;
+import javafx.scene.image.Image;
+import javafx.scene.layout.AnchorPane;
+import javafx.scene.layout.VBox;
+
+/**
+ * Controller for MainWindow. Provides the layout for the other controls.
+ */
+public class MainWindow extends AnchorPane {
+    @FXML
+    private ScrollPane scrollPane;
+    @FXML
+    private VBox dialogContainer;
+    @FXML
+    private TextField userInput;
+    @FXML
+    private Button sendButton;
+
+    private final Image userImage = new Image(this.getClass().getResourceAsStream("/images/User.png"));
+    private final Image dukeImage = new Image(this.getClass().getResourceAsStream("/images/Duke.png"));
+
+    @FXML
+    public void initialize() throws IOException {
+        //Scroll down to the end every time dialogContainer's height changes
+        scrollPane.vvalueProperty().bind(dialogContainer.heightProperty());
+
+        Duke.activate();
+        //Show Duke's greeting
+        dialogContainer.getChildren().add(DialogBox.getDukeDialog(Ui.getReply(), dukeImage));
+    }
+
+    /**
+     * Creates two dialog boxes, one echoing user input and the other containing Duke's reply and then appends them to
+     * the dialog container. Clears the user input after processing.
+     */
+    @FXML
+    private void handleUserInput() {
+        String input = userInput.getText();
+        String response = Duke.getResponse(input);
+        dialogContainer.getChildren().addAll(
+                DialogBox.getUserDialog(input, userImage),
+                DialogBox.getDukeDialog(response, dukeImage)
+        );
+        userInput.clear();
+
+        if (!Duke.isActive()) {
+            userInput.setDisable(true);
+            sendButton.setDisable(true);
+            new Timer().schedule(new TimerTask() {
+                public void run() {
+                    Platform.exit();
+                    System.exit(0);
+                }
+            }, 2000);
+        }
+    }
+}
diff --git a/src/main/java/duke/services/Duke.java b/src/main/java/duke/services/Duke.java
new file mode 100644
index 0000000000..ac46302483
--- /dev/null
+++ b/src/main/java/duke/services/Duke.java
@@ -0,0 +1,91 @@
+package duke.services;
+
+import java.io.IOException;
+
+/**
+ * Provides Duke's high-level functionality
+ */
+public class Duke {
+
+    /** Is Duke interacting with the user? */
+    private static boolean isActive;
+
+    /**
+     * Duke performs its activation behaviour. Save data is loaded.
+     *
+     * @throws IOException From IO errors when loading save data
+     */
+    public static void activate() throws IOException {
+        isActive = true;
+        Storage.loadData();
+        Ui.setReply(new String[] {
+            "Hello! I'm Duke",
+            "What can I do for you?",
+        });
+    }
+
+    /**
+     * Responds to the command and gives a reply
+     *
+     * @param command User's inputted command
+     * @return Duke's reply
+     */
+    public static String getResponse(String command) {
+        String[] words = Parser.convertToWords(command);
+        if (words.length > 0) {
+            try {
+                //could try grouping words.length == 1 cases
+                if (words.length == 1 && words[0].equals("bye")) {
+                    Duke.deactivate();
+                } else if (words.length == 1 && words[0].equals("list")) {
+                    TaskList.listTasks();
+                } else if (words.length == 1 && words[0].equals("SAVE")) {
+                    Storage.wipeDataOnExit(false);
+                } else if (words.length == 1 && words[0].equals("WIPE")) {
+                    Storage.wipeDataOnExit(true);
+                } else if (words[0].equals("todo")) {
+                    TaskList.addTodo(words);
+                } else if (words[0].equals("deadline")) {
+                    TaskList.addDeadline(words);
+                } else if (words[0].equals("event")) {
+                    TaskList.addEvent(words);
+                } else if (words[0].equals("mark")) {
+                    TaskList.markTaskAsDone(words);
+                } else if (words[0].equals("unmark")) {
+                    TaskList.markTaskAsNotDone(words);
+                } else if (words[0].equals("delete")) {
+                    TaskList.deleteTask(words);
+                } else if (words[0].equals("find")) {
+                    TaskList.findTasksContainingKeyword(words);
+                } else if (words[0].equals("sort")) {
+                    TaskList.sortByTimeAsc();
+                } else {
+                    Ui.setReply(new String[]{"I'm sorry, I don't know that command"});
+                }
+            } catch (IllegalArgumentException | IOException e) {
+                Ui.setReply(new String[]{e.getMessage()});
+            }
+        }
+        assert !Ui.getReply().isEmpty() : "Reply can't be empty";
+        return Ui.getReply();
+    }
+
+
+
+    /**
+     * Duke performs its deactivation behaviour. Data is saved.
+     *
+     * @throws IOException From IO errors when saving data
+     */
+    public static void deactivate() throws IOException {
+        Storage.saveData();
+        Ui.setReply(new String[] {
+            "Bye. Hope to see you again soon!",
+        });
+        isActive = false;
+    }
+
+    public static boolean isActive() {
+        return isActive;
+    }
+}
diff --git a/src/main/java/duke/services/Parser.java b/src/main/java/duke/services/Parser.java
new file mode 100644
index 0000000000..f97822e4a6
--- /dev/null
+++ b/src/main/java/duke/services/Parser.java
@@ -0,0 +1,163 @@
+package duke.services;
+
+import java.time.LocalDate;
+import java.time.LocalDateTime;
+import java.time.format.DateTimeFormatter;
+import java.time.format.DateTimeFormatterBuilder;
+import java.time.format.DateTimeParseException;
+import java.util.Arrays;
+import java.util.Locale;
+
+/** Handles reading of user commands and string manipulation */
+public class Parser {
+
+    /** Points to the current word being read in the current command */
+    private static int currWordIndex = 0;
+
+    /**
+     * Converts the command to an array of words split by " ".
+     *
+     * @param command User's inputted command
+     * @return An array of the command's words, split by " ".
+     */
+    public static String[] convertToWords(String command) {
+        return Arrays.stream(command.strip().split(" ")).toArray(String[]::new);
+    }
+
+    /**
+     * Retrieves the description argument in the command
+     * @param words The words of the command entered, first is some valid command name
+     * @param stop The word before which the description ends
+     * @return The description specified in words
+     * @throws IllegalArgumentException If words specifies an empty description
+     */
+    public static String getDescription(String[] words, String stop) {
+        currWordIndex = 1;
+        StringBuilder descBuilder = new StringBuilder();
+        boolean descIsEmpty = true;
+
+        while (currWordIndex < words.length && !words[currWordIndex].equals(stop)) {
+            if (words[currWordIndex].isEmpty()) {
+                descBuilder.append(" ");
+            } else {
+                descBuilder.append(words[currWordIndex]).append(" ");
+                descIsEmpty = false;
+            }
+            ++currWordIndex;
+        }
+
+        if (descIsEmpty) {
+            throw new IllegalArgumentException("OOPS!!! Description can't be empty");
+        }
+
+        return descBuilder.deleteCharAt(descBuilder.length() - 1).toString(); //remove last whitespace
+    }
+
+    /**
+     * Retrieves the timing argument in the command, which must be of the form d/M/yyyy
+     * followed by an optional (h:mm)am/pm
+     * @param words The words of the command entered, first is some valid command name
+     * @param flag The flag that the timing belongs to
+     * @return The timing specified in words
+     * @throws IllegalArgumentException If words is missing flag or the timing is empty/incorrect format
+     */
+    public static String getTiming(String[] words, String flag) {
+        if (currWordIndex >= words.length) {
+            throw new IllegalArgumentException("OOPS!!! " + flag + " not found");
+        } else if (currWordIndex == words.length - 1) {
+            throw new IllegalArgumentException("OOPS!!! Timing for " + flag + " can't be empty");
+        }
+
+        ++currWordIndex;
+        try {
+            return (currWordIndex == words.length - 1)
+                    ? reformatDate(words[currWordIndex], "d/M/yyyy", "d MMM yyyy")
+                    : reformatDateTime(words[currWordIndex] + " " + words[++currWordIndex],
+                    "d/M/yyyy h:mma", "d MMM yyyy, h:mma");
+        } catch (DateTimeParseException e) {
+            throw new IllegalArgumentException("OOPS!!! I don't understand that date or time");
+        }
+    }
+
+    /**
+     * Converts the date into a LocalDate
+     * @param date The date to convert
+     * @param format The date format
+     * @return The date as a LocalDate
+     * @throws IllegalArgumentException If format is incorrect
+     */
+    public static LocalDate convertToLocalDate(String date, String format) {
+        DateTimeFormatter formatter = new DateTimeFormatterBuilder()
+                .appendPattern(format)
+                .toFormatter(Locale.getDefault());
+        return LocalDate.parse(date, formatter);
+    }
+
+    /**
+     * Converts the date to a new format
+     * @param date The date to reformat
+     * @param inFormat The current format
+     * @param outFormat The new format
+     * @return The reformatted date
+     * @throws IllegalArgumentException If format is incorrect
+     */
+    public static String reformatDate(String date, String inFormat, String outFormat) {
+        return convertToLocalDate(date, inFormat)
+                .format(DateTimeFormatter.ofPattern(outFormat, Locale.getDefault()));
+    }
+
+    /**
+     * Converts the datetime into a LocalDateTime
+     * @param dateTime The datetime to convert
+     * @param format The datetime format
+     * @return The datetime as a LocalDateTime
+     * @throws IllegalArgumentException If format is incorrect
+     */
+    public static LocalDateTime convertToLocalDateTime(String dateTime, String format) {
+        DateTimeFormatter formatter = new DateTimeFormatterBuilder()
+                .parseCaseInsensitive()
+                .appendPattern(format)
+                .toFormatter(Locale.getDefault());
+        return LocalDateTime.parse(dateTime, formatter);
+    }
+
+    /**
+     * Converts the datetime to a new format
+     * @param dateTime The datetime to reformat
+     * @param inFormat The current format
+     * @param outFormat The new format
+     * @return The reformatted datetime
+     * @throws IllegalArgumentException If datetime has incorrect format
+     */
+    public static String reformatDateTime(String dateTime, String inFormat, String outFormat) {
+        return convertToLocalDateTime(dateTime, inFormat)
+                .format(DateTimeFormatter.ofPattern(outFormat, Locale.getDefault()));
+    }
+
+    /**
+     * Gets the task number (integer pointing to a task) specified in the command
+     *
+     * @param words The words of the command entered, first is always some valid command name
+     * @return The task number specified
+     * @throws IllegalArgumentException If words has an invalid number of arguments or invalid argument value
+     */
+    public static int getTaskNumber(String[] words) {
+        if (TaskList.getTasks().size() == 0) {
+            throw new IllegalArgumentException("OOPS!!! No tasks stored for me to do that");
+        }
+
+        int taskNumber = 0;
+
+        try {
+            taskNumber = (words.length == 2) ? Integer.parseInt(words[1]) : 0;
+            if (taskNumber <= 0 || taskNumber > TaskList.getTasks().size()) {
+                throw new IllegalArgumentException();
+            }
+        } catch (IllegalArgumentException e) {
+            throw new IllegalArgumentException("OOPS!!! The task number must be from 1 to "
+                    + TaskList.getTasks().size());
+        }
+
+        return taskNumber;
+    }
+}
diff --git a/src/main/java/duke/services/Storage.java b/src/main/java/duke/services/Storage.java
new file mode 100644
index 0000000000..3ac9c63c24
--- /dev/null
+++ b/src/main/java/duke/services/Storage.java
@@ -0,0 +1,106 @@
+package duke.services;
+
+import java.io.BufferedReader;
+import java.io.BufferedWriter;
+import java.io.File;
+import java.io.FileReader;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.Arrays;
+
+import duke.tasks.Deadline;
+import duke.tasks.Event;
+import duke.tasks.Task;
+import duke.tasks.Todo;
+
+/** Handles saving and loading data */
+public class Storage {
+
+    /** Saved data on the stored tasks */
+    private static File dataSaved;
+
+    /** Is data going to be wiped on exit? */
+    private static boolean willWipeData = false;
+
+    /**
+     * Loads saved data on stored tasks from Duke_Tasks.txt if it exists, otherwise creates it
+     *
+     * @throws IOException If an IO error occurs or the parent directory doesn't exist
+     */
+    public static void loadData() throws IOException {
+        Path path = Paths.get("Duke_Tasks");
+        boolean directoryExists = Files.exists(path);
+        if (directoryExists) {
+            dataSaved = path.toFile();
+            BufferedReader br = new BufferedReader(new FileReader(dataSaved));
+            String line = br.readLine();
+            String[] words;
+            Task task;
+            while (line != null) {
+                //format of savedata lines: [typeSymbol][1 or 0] [desc] [flag] [timing]
+                assert !line.isEmpty() : "Savedata lines must be non-empty";
+                words = Arrays.stream(line.split(" ")).toArray(String[]::new);
+                if (line.charAt(0) == 'T') {
+                    task = new Todo(Parser.getDescription(words, null));
+                } else if (line.charAt(0) == 'D') {
+                    task = new Deadline(Parser.getDescription(words, "/by"), Parser.getTiming(words, "/by"));
+                } else if (line.charAt(0) == 'E') {
+                    task = new Event(Parser.getDescription(words, "/at"), Parser.getTiming(words, "/at"));
+                } else {
+                    throw new IllegalArgumentException("OOPS!!! Found a stored task of unknown type");
+                }
+                if (line.charAt(1) == '1') {
+                    task.markAsDone();
+                }
+                TaskList.getTasks().add(task);
+                line = br.readLine();
+            }
+        } else {
+            dataSaved = Files.createFile(path).toFile();
+        }
+    }
+
+    /**
+     * Tells storage whether to wipe or save data on exit and displays outcome
+     *
+     * @param willWipe True/false will make data be wiped/saved on exit resp.
+     */
+    public static void wipeDataOnExit(boolean willWipe) {
+        willWipeData = willWipe;
+        Ui.setReply(new String[] {
+            "Data will be " + (willWipe ? "wiped" : "saved") + " on exit"}
+        );
+    }
+
+    /**
+     * Writes saved data on stored tasks to Duke_Tasks.txt
+     *
+     * @throws IOException If an I/O error occurs or the parent directory doesn't exist
+     */
+    public static void saveData() throws IOException {
+        new FileWriter(dataSaved).close();
+        if (!willWipeData) {
+            BufferedWriter bf = new BufferedWriter(new FileWriter(dataSaved));
+            StringBuilder lineBuilder = new StringBuilder();
+            for (Task task : TaskList.getTasks()) {
+                //format of savedata lines: [typeSymbol][1 or 0] [desc] [flag] [timing]
+                lineBuilder.append(task.getTypeSymbol())
+                        .append(task.getStatusIcon().equals("X") ? '1' : '0')
+                        .append(" ")
+                        .append(task.getDescription());
+                if (task instanceof Deadline) {
+                    lineBuilder.append(" /by ").append(((Deadline) task).getEnteredDeadline());
+                } else if (task instanceof Event) {
+                    lineBuilder.append(" /at ").append(((Event) task).getEnteredTime());
+                }
+                bf.write(lineBuilder.toString());
+                bf.newLine();
+                lineBuilder.setLength(0);
+            }
+            bf.close();
+        }
+    }
+}
diff --git a/src/main/java/duke/services/TaskList.java b/src/main/java/duke/services/TaskList.java
new file mode 100644
index 0000000000..7aefe0fce5
--- /dev/null
+++ b/src/main/java/duke/services/TaskList.java
@@ -0,0 +1,174 @@
+package duke.services;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import duke.tasks.Deadline;
+import duke.tasks.Event;
+import duke.tasks.Task;
+import duke.tasks.Todo;
+
+/** Handles tasks */
+public class TaskList {
+    /** The tasks stored */
+    private static List tasks = new ArrayList<>();
+
+    public static List getTasks() {
+        return tasks;
+    }
+
+    /**
+     * Stores the task and displays outcome
+     */
+    public static void addTask(Task task) {
+        tasks.add(task);
+        Ui.setReply(new String[] {
+            "Got it. I've added this task:",
+            "  " + task,
+            "Now you have " + tasks.size() + " task" + (tasks.size() == 1 ? "" : "s") + " in the list."
+        });
+    }
+
+    /**
+     * Stores a Todo outlined in the command and displays outcome
+     * @param words The words of the command entered, first is always "todo"
+     * @throws IllegalArgumentException If words specifies an empty description
+     */
+    public static void addTodo(String[] words) {
+        addTask(new Todo(Parser.getDescription(words, null)));
+    }
+
+    /**
+     * Stores a Deadline outlined in the command and displays outcome
+     * @param words The words of the command entered, first is always "deadline"
+     * @throws IllegalArgumentException if words specifies an empty description or empty date or is missing /by
+     */
+    public static void addDeadline(String[] words) {
+        addTask(new Deadline(Parser.getDescription(words, "/by"), Parser.getTiming(words, "/by")));
+    }
+
+    /**
+     * Stores an Event outlined in the command and displays outcome
+     * @param words The words of the command entered, first is always "event"
+     * @throws IllegalArgumentException If words specifies an empty description or empty date or is missing /at
+     */
+    public static void addEvent(String[] words) {
+        addTask(new Event(Parser.getDescription(words, "/at"), Parser.getTiming(words, "/at")));
+    }
+
+    /**
+     * Marks the specified task as done and displays outcome
+     * @param words The words of the command entered, first is always "mark"
+     * @throws IllegalArgumentException If words has an invalid number of arguments or invalid argument value
+     */
+    public static void markTaskAsDone(String[] words) {
+        Task task = tasks.get(Parser.getTaskNumber(words) - 1).markAsDone();
+        Ui.setReply(new String[]{
+            "Nice! I've marked this task as done:",
+            "  " + task
+        });
+    }
+
+    /**
+     * Marks the specified task as not done and displays outcome
+     * @param words The words of the command entered, first is always "unmark"
+     * @throws IllegalArgumentException If words has an invalid number of arguments or invalid argument value
+     */
+    public static void markTaskAsNotDone(String[] words) {
+        Task task = tasks.get(Parser.getTaskNumber(words) - 1).markAsNotDone();
+        Ui.setReply(new String[]{
+            "OK, I've marked this task as not done yet:",
+            "  " + task
+        });
+    }
+
+    /**
+     * Deletes the specified task and displays outcome
+     * @param words The words of the command entered, first is always "delete"
+     * @throws IllegalArgumentException If words has an invalid number of arguments or invalid argument value
+     */
+    public static void deleteTask(String[] words) {
+        Task removedTask = tasks.remove(Parser.getTaskNumber(words) - 1);
+        Ui.setReply(new String[]{
+            "Noted. I've removed this task:",
+            "  " + removedTask,
+            "Now you have " + tasks.size() + " task" + (tasks.size() == 1 ? "" : "s") + " in the list."
+        });
+    }
+
+    /**
+     * Lists out information on all tasks stored
+     *
+     * @param initialMessage The statement before the list of tasks
+     */
+    public static void listTasks(String initialMessage) {
+        String[] taskDescriptions = new String[getTasks().size() + 1];
+        taskDescriptions[0] = initialMessage;
+        for (int i = 1; i < taskDescriptions.length; ++i) {
+            taskDescriptions[i] = "  " + i + ". " + tasks.get(i - 1);
+        }
+        Ui.setReply(taskDescriptions);
+    }
+
+    public static void listTasks() {
+        listTasks("Here are the tasks in your list:");
+    }
+
+    /**
+     * Displays all tasks whose descriptions contain the keyword specified in words
+     * @param words The words of the command entered, first is always "find"
+     */
+    public static void findTasksContainingKeyword(String[] words) {
+        StringBuilder keywordBuilder = new StringBuilder();
+        for (int i = 1; i < words.length; ++i) {
+            keywordBuilder.append(words[i]).append(" ");
+        }
+        String keyword = words.length == 1 ? "" : keywordBuilder.deleteCharAt(keywordBuilder.length() - 1).toString();
+
+        ArrayList matchingTasks = new ArrayList<>();
+        matchingTasks.add("Here are the tasks containing the keyword \"" + keyword + "\" :");
+        for (int i = 0; i < tasks.size(); ++i) {
+            Task currTask = tasks.get(i);
+            if (currTask.getDescription().contains(keyword)) {
+                matchingTasks.add("  " + (i + 1) + ". " + currTask);
+            }
+        }
+        Ui.setReply(matchingTasks.toArray(String[]::new));
+    }
+
+    /**
+     * Sorts tasks sorted in ascending order of datetime and detail,
+     * e.g. tasks without any time are always first.
+     */
+    public static void sortByTimeAsc() {
+        tasks.sort((task1, task2) -> {
+            if (task1 instanceof Todo) {
+                return -1;
+            } else if (task2 instanceof Todo) {
+                return 1;
+            }
+            String time1 = (task1 instanceof Event) ? ((Event) task1).getEnteredTime()
+                    : ((Deadline) task1).getEnteredDeadline();
+            String time2 = (task2 instanceof Event) ? ((Event) task2).getEnteredTime()
+                    : ((Deadline) task2).getEnteredDeadline();
+            String dateFormat = "d/M/yyyy";
+            String dateTimeFormat = "d/M/yyyy h:mma";
+
+            if (time1.contains(" ")) {
+                if (!time2.contains(" ")) {
+                    return 1;
+                }
+                return (Parser.convertToLocalDateTime(time1, dateTimeFormat))
+                        .compareTo(Parser.convertToLocalDateTime(time2, dateTimeFormat));
+            } else {
+                if (time2.contains(" ")) {
+                    return -1;
+                }
+                return (Parser.convertToLocalDate(time1, dateFormat))
+                        .compareTo(Parser.convertToLocalDate(time2, dateFormat));
+            }
+        });
+
+        listTasks("Ok, here are your tasks:");
+    }
+}
diff --git a/src/main/java/duke/services/Ui.java b/src/main/java/duke/services/Ui.java
new file mode 100644
index 0000000000..5b35b54003
--- /dev/null
+++ b/src/main/java/duke/services/Ui.java
@@ -0,0 +1,27 @@
+package duke.services;
+
+/**
+ * Handles Duke's dialog
+ */
+public class Ui {
+    /** What Duke will tell the user after a command */
+    private static String reply;
+
+    /**
+     * Updates duke's reply to the given lines, separated by newline
+     */
+    public static void setReply(String[] lines) {
+        StringBuilder replyBuilder = new StringBuilder();
+        System.out.println("____________________________________________________________");
+        for (String line : lines) {
+            System.out.println(line);
+            replyBuilder.append(line).append("\n");
+        }
+        System.out.println("____________________________________________________________\n");
+        reply = replyBuilder.toString();
+    }
+
+    public static String getReply() {
+        return reply;
+    }
+}
diff --git a/src/main/java/duke/tasks/Deadline.java b/src/main/java/duke/tasks/Deadline.java
new file mode 100644
index 0000000000..a63f16ad2b
--- /dev/null
+++ b/src/main/java/duke/tasks/Deadline.java
@@ -0,0 +1,37 @@
+package duke.tasks;
+
+import duke.services.Parser;
+
+/**
+ * Tasks with a deadline
+ */
+public class Deadline extends Task {
+
+    /** Format: d MMM yyyy, h:mma */
+    private String deadline;
+
+    /**
+     * Constructs a new Deadline with the given description and deadline
+     *
+     * @param description The description
+     * @param deadline The deadline
+     */
+    public Deadline(String description, String deadline) {
+        super(description, 'D');
+        this.deadline = deadline;
+    }
+
+    /**
+     * @return The deadline in the format that was entered
+     */
+    public String getEnteredDeadline() {
+        return (deadline.indexOf(',') == -1)
+                ? Parser.reformatDate(deadline, "d MMM yyyy", "d/M/yyyy")
+                : Parser.reformatDateTime(deadline, "d MMM yyyy, h:mma", "d/M/yyyy h:mma");
+    }
+
+    @Override
+    public String toString() {
+        return super.toString() + " (by: " + deadline + ")";
+    }
+}
diff --git a/src/main/java/duke/tasks/Event.java b/src/main/java/duke/tasks/Event.java
new file mode 100644
index 0000000000..5c753fcaa6
--- /dev/null
+++ b/src/main/java/duke/tasks/Event.java
@@ -0,0 +1,37 @@
+package duke.tasks;
+
+import duke.services.Parser;
+
+/**
+ * Tasks that occur at a certain time
+ */
+public class Event extends Task {
+
+    /** The time of occurrence. Format: d MMM yyyy, h:mma */
+    private String time;
+
+    /**
+     * Constructs a new Event with the given description and timing of occurrence
+     *
+     * @param description The description
+     * @param time The time of occurrence
+     */
+    public Event(String description, String time) {
+        super(description, 'E');
+        this.time = time;
+    }
+
+    /**
+     * @return The time in the format that was entered
+     */
+    public String getEnteredTime() {
+        return (time.indexOf(',') == -1)
+                ? Parser.reformatDate(time, "d MMM yyyy", "d/M/yyyy")
+                : Parser.reformatDateTime(time, "d MMM yyyy, h:mma", "d/M/yyyy h:mma");
+    }
+
+    @Override
+    public String toString() {
+        return super.toString() + " (at: " + time + ")";
+    }
+}
diff --git a/src/main/java/duke/tasks/Task.java b/src/main/java/duke/tasks/Task.java
new file mode 100644
index 0000000000..5bf68caf1b
--- /dev/null
+++ b/src/main/java/duke/tasks/Task.java
@@ -0,0 +1,68 @@
+package duke.tasks;
+
+/**
+ * Tasks for keeping track of
+ */
+public abstract class Task {
+
+    /** Describes what this task entails */
+    protected String description;
+
+    /** Indicates what kind of task this is */
+    protected final char typeSymbol;
+
+    /** Is this task done? */
+    protected boolean isDone;
+
+    /**
+     * Constructs a new task with the given description
+     * @param description The task description
+     * @param typeSymbol The symbol to indicate type
+     */
+    public Task(String description, char typeSymbol) {
+        this.description = description;
+        this.typeSymbol = typeSymbol;
+        this.isDone = false;
+    }
+
+    /**
+     * Marks this task as done
+     * @return The task
+     */
+    public Task markAsDone() {
+        this.isDone = true;
+        return this;
+    }
+
+    /**
+     * Marks this task as not done
+     * @return The task
+     */
+    public Task markAsNotDone() {
+        this.isDone = false;
+        return this;
+    }
+
+    public char getTypeSymbol() {
+        return typeSymbol;
+    }
+
+    /**
+     * @return "X" if the task is done, " " otherwise
+     */
+    public String getStatusIcon() {
+        return (isDone ? "X" : " "); // mark done task with X
+    }
+
+    public String getDescription() {
+        return description;
+    }
+
+    /**
+     * @return The task type, status and description
+     */
+    @Override
+    public String toString() {
+        return "[" + getTypeSymbol() + "][" + getStatusIcon() + "] " + getDescription();
+    }
+}
diff --git a/src/main/java/duke/tasks/Todo.java b/src/main/java/duke/tasks/Todo.java
new file mode 100644
index 0000000000..8627da1695
--- /dev/null
+++ b/src/main/java/duke/tasks/Todo.java
@@ -0,0 +1,16 @@
+package duke.tasks;
+
+/**
+ * Tasks with only a description
+ */
+public class Todo extends Task {
+
+    /**
+     * Constructs a new Todo with the given description
+     *
+     * @param description The task description
+     */
+    public Todo(String description) {
+        super(description, 'T');
+    }
+}
diff --git a/src/main/resources/images/Duke.png b/src/main/resources/images/Duke.png
new file mode 100644
index 0000000000..d893658717
Binary files /dev/null and b/src/main/resources/images/Duke.png differ
diff --git a/src/main/resources/images/User.png b/src/main/resources/images/User.png
new file mode 100644
index 0000000000..3c82f45461
Binary files /dev/null and b/src/main/resources/images/User.png differ
diff --git a/src/main/resources/view/DialogBox.fxml b/src/main/resources/view/DialogBox.fxml
new file mode 100644
index 0000000000..5c328f0d0e
--- /dev/null
+++ b/src/main/resources/view/DialogBox.fxml
@@ -0,0 +1,29 @@
+
+
+
+
+
+
+
+
+
+    
+        
+        
+         
+            
+         
+    
+    
+        
+    
+
diff --git a/src/main/resources/view/MainWindow.fxml b/src/main/resources/view/MainWindow.fxml
new file mode 100644
index 0000000000..3f15e94377
--- /dev/null
+++ b/src/main/resources/view/MainWindow.fxml
@@ -0,0 +1,19 @@
+
+
+
+
+
+
+
+
+
+    
+        
+        
+        
+            
+                
+            
+        
+    
+
diff --git a/src/test/java/duke/services/ParserTest.java b/src/test/java/duke/services/ParserTest.java
new file mode 100644
index 0000000000..e496dbe38e
--- /dev/null
+++ b/src/test/java/duke/services/ParserTest.java
@@ -0,0 +1,65 @@
+package duke.services;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+import org.junit.jupiter.api.Test;
+
+import duke.tasks.Todo;
+
+public class ParserTest {
+    @Test
+    public void getTaskNumber() {
+        //exception on trying to get task number when there are no tasks
+        assertThrows(IllegalArgumentException.class, () -> Parser.getTaskNumber(new String[] {"mark", "1"}));
+        TaskList.addTask(new Todo(""));
+        assertEquals(Parser.getTaskNumber(new String[] {"mark", "1"}), 1);
+        assertThrows(IllegalArgumentException.class, () -> Parser.getTaskNumber(new String[] {"mark"}));
+        assertThrows(IllegalArgumentException.class, () -> Parser.getTaskNumber(new String[] {"mark", "2a"}));
+        assertThrows(IllegalArgumentException.class, () -> Parser.getTaskNumber(new String[] {"mark", "9"}));
+    }
+
+    @Test
+    public void getDescriptionAndTiming_correctEvent() {
+        String[] event1 = new String[] {"event", "presentation", "/at", "9/10/2022"};
+        assertEquals(Parser.getDescription(event1, "/at"), "presentation");
+        assertEquals(Parser.getTiming(event1, "/at"), "9 Oct 2022");
+    }
+
+    @Test
+    public void getDescriptionAndTiming_correctDeadlineWithEmptySpaces() {
+        String[] deadline1 = new String[] {"deadline", "finish", "", "", "work", "/by", "23/4/2021", "4:23pm"};
+        assertEquals(Parser.getDescription(deadline1, "/by"), "finish   work");
+        assertEquals(Parser.getTiming(deadline1, "/by"), "23 Apr 2021, 4:23PM");
+    }
+
+    @Test
+    public void getDescription_commandWordOnly_emptyDescriptionException() {
+        assertThrows(IllegalArgumentException.class, () ->
+                Parser.getDescription(new String[] {"event"}, "/at"));
+    }
+
+    @Test
+    public void getDescription_commandWordAndTimingFlagOnly_emptyDescriptionException() {
+        assertThrows(IllegalArgumentException.class, () ->
+                Parser.getDescription(new String[] {"deadline", "/by"}, "/by"));
+    }
+
+    @Test
+    public void getDescriptionAndTiming_emptyTimingArgument_emptyTimingException() {
+        assertThrows(IllegalArgumentException.class, () -> {
+            String[] deadline = new String[]{"deadline", "buy", "food", "/by"};
+            Parser.getDescription(deadline, "/by");
+            Parser.getTiming(deadline, "/by");
+        });
+    }
+
+    @Test
+    public void getDescriptionAndTiming_wrongFlag_flagNotFoundException() {
+        assertThrows(IllegalArgumentException.class, () -> {
+            String[] deadline = new String[]{"deadline", "buy", "food", "/at", "2/5/2022"};
+            Parser.getDescription(deadline, "/by");
+            Parser.getTiming(deadline, "/by");
+        });
+    }
+}
diff --git a/src/test/java/duke/tasks/TodoTest.java b/src/test/java/duke/tasks/TodoTest.java
new file mode 100644
index 0000000000..3a03140cc8
--- /dev/null
+++ b/src/test/java/duke/tasks/TodoTest.java
@@ -0,0 +1,28 @@
+package duke.tasks;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+import org.junit.jupiter.api.Test;
+
+
+public class TodoTest {
+    @Test
+    public void testDescription() {
+        assertEquals(new Todo("run").toString(), "[T][ ] run");
+        assertEquals(new Todo("create  test methods for class").toString(),
+                "[T][ ] create  test methods for class");
+    }
+
+    @Test
+    public void testMarking() {
+        Todo todo = new Todo("run");
+        todo.markAsDone();
+        assertEquals(todo.toString(), "[T][X] run");
+        todo.markAsDone();
+        assertEquals(todo.toString(), "[T][X] run");
+        todo.markAsNotDone();
+        assertEquals(todo.toString(), "[T][ ] run");
+        todo.markAsNotDone();
+        assertEquals(todo.toString(), "[T][ ] run");
+    }
+}
diff --git a/text-ui-test/EXPECTED.TXT b/text-ui-test/EXPECTED.TXT
deleted file mode 100644
index 657e74f6e7..0000000000
--- a/text-ui-test/EXPECTED.TXT
+++ /dev/null
@@ -1,7 +0,0 @@
-Hello from
- ____        _        
-|  _ \ _   _| | _____ 
-| | | | | | | |/ / _ \
-| |_| | |_| |   <  __/
-|____/ \__,_|_|\_\___|
-
diff --git a/text-ui-test/input.txt b/text-ui-test/input.txt
deleted file mode 100644
index e69de29bb2..0000000000
diff --git a/text-ui-test/runtest.bat b/text-ui-test/runtest.bat
index 0873744649..72b29c56e6 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 duke.services.Duke < input.txt > ACTUAL.TXT
 
 REM compare the output to the expected output
 FC ACTUAL.TXT EXPECTED.TXT