diff --git a/docs/README.md b/docs/README.md
index 8077118eb..5e1bac1d4 100644
--- a/docs/README.md
+++ b/docs/README.md
@@ -1,29 +1,69 @@
-# User Guide
+# KevBot Guide
## Features
-### Feature-ABC
+### List
-Description of the feature.
+List all outstanding todos, deadlines, and events.
-### Feature-XYZ
+### Find
-Description of the feature.
+Filter the listed tasks by a specific keyword(s).
-## Usage
+### Mark
+
+Mark a task as completed/incomplete.
+
+### Delete
-### `Keyword` - Describe action
+Delete a specific task.
-Describe the action and its outcome.
+### Add
+
+Add a new todo, deadline, or event task.
+
+## Usage
+
+### `list` - List all tasks
Example of usage:
-`keyword (optional arguments)`
+`list`
+
+### `find` - Match tasks by keyword
+
+Example of usage:
+
+`find (keyword)`
+
+### `mark` - Mark a task
+Marks an undone task as done and vice versa. Task index is 1-based as displayed in the list command.
+
+Example of usage:
+
+`mark (task index)`
+
+### `delete` - Remove a task
+Task index is 1-based as displayed in the list command.
+
+Example of usage:
+
+`delete (task index)`
+
+### `todo` - Add a todo
+
+Example of usage:
+
+`todo (description)`
+
+### `deadline` - Add a deadline
+
+Example of usage:
+
+`deadline (description) /by (end time)`
-Expected outcome:
+### `event` - Add an event
-Description of the outcome.
+Example of usage:
-```
-expected output
-```
+`event (description) /from (start time) /to (end time)`
\ No newline at end of file
diff --git a/duke.txt b/duke.txt
new file mode 100644
index 000000000..8b865ac94
--- /dev/null
+++ b/duke.txt
@@ -0,0 +1,6 @@
+T|false|asdfkasdh
+T|true|re98ui
+D|true|return book|tmrw
+E|false|proj meeting|today|forever
+T|false|hu
+T|true|deez
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..6e864153e
--- /dev/null
+++ b/src/main/java/META-INF/MANIFEST.MF
@@ -0,0 +1,3 @@
+Manifest-Version: 1.0
+Main-Class: duke.Duke
+
diff --git a/src/main/java/duke/Duke.java b/src/main/java/duke/Duke.java
new file mode 100644
index 000000000..46d6919e2
--- /dev/null
+++ b/src/main/java/duke/Duke.java
@@ -0,0 +1,59 @@
+package duke;
+
+import duke.command.DukeException;
+import duke.parser.Parser;
+import duke.storage.StorageFile;
+import duke.tasklist.TaskList;
+import duke.ui.TextUi;
+
+public class Duke {
+ private TextUi ui;
+ private Parser parser;
+ private TaskList tasks;
+ private StorageFile storage;
+
+ public Duke(String filePath) {
+ tasks = new TaskList();
+ ui = new TextUi();
+ parser = new Parser();
+ storage = new StorageFile(filePath);
+
+ try {
+ tasks = new TaskList(storage.load());
+ } catch (DukeException e) {
+ ui.showInitFailedMessage();
+ tasks = new TaskList();
+ }
+ }
+
+ public void run() {
+ ui.showWelcomeMessage();
+
+ String userCommandText = ui.getUserCommand();
+ while (!userCommandText.equals("bye")) {
+ String result = handleCommand(userCommandText);
+ ui.showResultToUser(result);
+ userCommandText = ui.getUserCommand();
+ }
+
+ ui.showGoodbyeMessage();
+ }
+
+ public String handleCommand(String userInput) {
+ if (userInput.equals("list")) {
+ return tasks.getIndexedTasks();
+ } else {
+ try {
+ String result = parser.executeCommand(userInput, tasks);
+ storage.saveTasks(tasks);
+ return result;
+ } catch (Exception e) {
+ return e.getMessage();
+ }
+ }
+ }
+
+ public static void main(String[] args) {
+ new Duke("duke.txt").run();
+ }
+}
diff --git a/src/main/java/duke/command/DukeException.java b/src/main/java/duke/command/DukeException.java
new file mode 100644
index 000000000..2ef6e369c
--- /dev/null
+++ b/src/main/java/duke/command/DukeException.java
@@ -0,0 +1,10 @@
+package duke.command;
+
+/**
+ * Wrapper for an exception throw within Duke
+ */
+public class DukeException extends Exception {
+ public DukeException(String message) {
+ super(message);
+ }
+}
diff --git a/src/main/java/duke/parser/Parser.java b/src/main/java/duke/parser/Parser.java
new file mode 100644
index 000000000..0438f0b05
--- /dev/null
+++ b/src/main/java/duke/parser/Parser.java
@@ -0,0 +1,108 @@
+package duke.parser;
+
+import duke.command.DukeException;
+import duke.task.Deadline;
+import duke.task.Event;
+import duke.task.Todo;
+import duke.tasklist.TaskList;
+
+import java.util.HashMap;
+
+/**
+ * A Parser
object is responsible for parsing and executing
+ * a user provided command string.
+ */
+public class Parser {
+ public Parser() {}
+
+ /**
+ * Returns a human-readable result of the command
+ * executed.
+ *
+ * @param line inputted by user.
+ * @param tasks that are currently in the TaskList
+ * @return A human-readable string of the command's result.
+ * @throws DukeException If command has issues.
+ */
+ public String executeCommand(String line, TaskList tasks) throws DukeException {
+ int divider = line.indexOf(" ");
+ if (divider == -1) {
+ throw new DukeException("Sorry! Not sure what you mean");
+ }
+ if (divider == line.length() - 1) {
+ throw new DukeException("Please enter a non-empty parameter value");
+ }
+
+
+ if (line.startsWith("todo")){
+ String description = line.substring(divider + 1);
+
+ return tasks.addTask(new Todo(description));
+ } else if (line.startsWith("find")) {
+ String keyword = line.substring(divider + 1);
+
+ return tasks.getIndexedTasksByKeyword(keyword);
+ }
+
+ if (line.contains("mark") || line.startsWith("delete")) {
+ int idx = Integer.parseInt(line.substring(divider + 1)) - 1;
+ if (idx < 0 || idx >= tasks.size()) {
+ throw new DukeException("Sorry! That's not a valid task");
+ }
+
+ if (line.contains("mark")) {
+ return tasks.markTask(idx, line.startsWith("mark"));
+ } else {
+ return tasks.removeTask(idx);
+ }
+ }
+
+ HashMap parameters = parseParameters(line);
+ String description = parameters.get("description");
+ if (description == null) {
+ throw new DukeException("Sorry! Please provide a valid description");
+ }
+ if (line.startsWith("deadline")) {
+ String by = parameters.get("by");
+ if (by == null) {
+ throw new DukeException("Sorry! Please provide a valid `by`");
+ }
+ return tasks.addTask(new Deadline(description, by));
+ } else if (line.startsWith("event")) {
+ String from = parameters.get("from");
+ String to = parameters.get("to");
+ if (from == null || to == null) {
+ throw new DukeException("Sorry! Please provide a valid `from` and/or `to`");
+ }
+ return tasks.addTask(new Event(description, from, to));
+ } else {
+ throw new DukeException("Sorry! Please enter a valid command");
+ }
+ }
+
+ private static HashMap parseParameters(String line) throws DukeException {
+ HashMap fieldToValue = new HashMap<>();
+
+ int startDescription = line.indexOf(" ");
+ int endOfDescription = line.indexOf(" /");
+ if (startDescription == endOfDescription || startDescription == -1 || endOfDescription == -1) {
+ throw new DukeException("Sorry! Not sure what you mean");
+ }
+ fieldToValue.put("description", line.substring(startDescription + 1, endOfDescription));
+
+ String[] splitParams = line.split(" /");
+ for (int i = 1; i < splitParams.length; i++) {
+ String rawParam = splitParams[i];
+ int divider = rawParam.indexOf(" ");
+ if (divider == -1) {
+ throw new DukeException("Sorry! Please enter valid inputs");
+ }
+
+ String field = rawParam.substring(0, divider);
+ String value = rawParam.substring(divider + 1);
+ fieldToValue.put(field, value);
+ }
+
+ return fieldToValue;
+ }
+}
diff --git a/src/main/java/duke/storage/StorageFile.java b/src/main/java/duke/storage/StorageFile.java
new file mode 100644
index 000000000..b5b2883c6
--- /dev/null
+++ b/src/main/java/duke/storage/StorageFile.java
@@ -0,0 +1,93 @@
+package duke.storage;
+
+import duke.command.DukeException;
+import duke.task.Deadline;
+import duke.task.Event;
+import duke.task.Task;
+import duke.task.Todo;
+import duke.tasklist.TaskList;
+
+import java.io.File;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Scanner;
+
+/**
+ * Handles Duke's storage capabilities. A StorageFile
object handles the
+ * loading and saving aspects of new tasks.
+ */
+public class StorageFile {
+ String filePath;
+ public StorageFile(String filePath) {
+ this.filePath = filePath;
+ }
+
+ /**
+ * Loads all tasks from the .txt file
+ * when the program is first ran.
+ *
+ * @return List of Task objects representing saved tasks.
+ * @throws DukeException If there is an issue loading tasks.
+ */
+ public List load() throws DukeException {
+ List tasks = new ArrayList<>();
+ File f = new File(filePath);
+
+ try {
+ f.createNewFile();
+ Scanner s = new Scanner(f);
+ while (s.hasNext()) {
+ String line = s.nextLine();
+ String[] parsed = line.split("\\|");
+ String taskType = parsed[0];
+ String description = parsed[2];
+
+ switch (taskType) {
+ case "T":
+ tasks.add(new Todo(description));
+ break;
+ case "D":
+ String by = parsed[3];
+ tasks.add(new Deadline(description, by));
+ break;
+ case "E":
+ String from = parsed[3];
+ String to = parsed[4];
+ tasks.add(new Event(description, from, to));
+ break;
+ default:
+ throw new DukeException("Unknown task type detected");
+ }
+
+ if (parsed[1].equals("true")) {
+ tasks.get(tasks.size() - 1).setStatus(true);
+ }
+ }
+ } catch (IOException e) {
+ throw new DukeException(e.getMessage());
+ }
+
+ return tasks;
+ }
+
+ /**
+ * Saves all tasks to the .txt file after
+ * a command that modifies the TaskList
+ *
+ * @param tasks to be written to the file
+ * @throws DukeException If there is an error writing.
+ */
+ public void saveTasks(TaskList tasks) throws DukeException {
+ File f = new File(filePath);
+
+ try {
+ FileWriter fw = new FileWriter(f);
+ fw.write(tasks.getSerializedTasks());
+ fw.close();
+ } catch (IOException e) {
+ throw new DukeException(e.getMessage());
+ }
+ }
+}
diff --git a/src/main/java/duke/task/Deadline.java b/src/main/java/duke/task/Deadline.java
new file mode 100644
index 000000000..b9e24ebb7
--- /dev/null
+++ b/src/main/java/duke/task/Deadline.java
@@ -0,0 +1,22 @@
+package duke.task;
+
+/**
+ * Type of task that represents a deadline with end time.
+ */
+public class Deadline extends Task {
+ private String by;
+ public Deadline(String description, String by) {
+ super(description);
+ this.by = by;
+ }
+
+ @Override
+ public String getFormattedTask() {
+ return "[D] " + super.getFormattedTask() + " (by: " + by + ")";
+ }
+
+ @Override
+ public String getSerializedString() {
+ return "D|" + super.getSerializedString() + "|" + by;
+ }
+}
diff --git a/src/main/java/duke/task/Event.java b/src/main/java/duke/task/Event.java
new file mode 100644
index 000000000..c587b7fbc
--- /dev/null
+++ b/src/main/java/duke/task/Event.java
@@ -0,0 +1,24 @@
+package duke.task;
+
+/**
+ * Type of task that represents an event with start/end times.
+ */
+public class Event extends Task {
+ private String from;
+ private String to;
+ public Event(String description, String from, String to) {
+ super(description);
+ this.from = from;
+ this.to = to;
+ }
+
+ @Override
+ public String getFormattedTask() {
+ return "[E] " + super.getFormattedTask() + " (from: " + from + " to: " + to + ")";
+ }
+
+ @Override
+ public String getSerializedString() {
+ return "E|" + super.getSerializedString() + "|" + from + "|" + to;
+ }
+}
diff --git a/src/main/java/duke/task/Task.java b/src/main/java/duke/task/Task.java
new file mode 100644
index 000000000..3b8e54c05
--- /dev/null
+++ b/src/main/java/duke/task/Task.java
@@ -0,0 +1,54 @@
+package duke.task;
+
+/**
+ * Generic task class. Contains different methods to format a task.
+ */
+public class Task {
+ protected String description;
+ protected boolean isDone;
+
+ public Task(String description) {
+ this.description = description;
+ this.isDone = false;
+ }
+
+ /**
+ * Returns a formatted task string for printing purposes.
+ *
+ * @return String of formatted task.
+ */
+ public String getFormattedTask() {
+ return "[" + getStatusIcon() + "] " + description;
+ }
+ private String getStatusIcon() {
+ return (isDone ? "X" : " "); // mark done task with X
+ }
+
+ /**
+ * Setter to allow isDone status to be modified
+ *
+ * @param isDone boolean of new status
+ */
+ public void setStatus(boolean isDone) {
+ this.isDone = isDone;
+ }
+
+ /**
+ * Returns a string of the task serialized to be saved
+ * in the text file.
+ *
+ * @return Serialized string
+ */
+ public String getSerializedString() {
+ return isDone + "|" + description;
+ }
+
+ /**
+ * Returns description of the task.
+ *
+ * @return Task description.
+ */
+ public String getDescription() {
+ return description;
+ }
+}
diff --git a/src/main/java/duke/task/Todo.java b/src/main/java/duke/task/Todo.java
new file mode 100644
index 000000000..a12dcda86
--- /dev/null
+++ b/src/main/java/duke/task/Todo.java
@@ -0,0 +1,20 @@
+package duke.task;
+
+/**
+ * Type of task that represents a to-do object.
+ */
+public class Todo extends Task {
+ public Todo(String description) {
+ super(description);
+ }
+
+ @Override
+ public String getFormattedTask() {
+ return "[T] " + super.getFormattedTask();
+ }
+
+ @Override
+ public String getSerializedString() {
+ return "T|" + super.getSerializedString();
+ }
+}
diff --git a/src/main/java/duke/tasklist/TaskList.java b/src/main/java/duke/tasklist/TaskList.java
new file mode 100644
index 000000000..5f947b59f
--- /dev/null
+++ b/src/main/java/duke/tasklist/TaskList.java
@@ -0,0 +1,131 @@
+package duke.tasklist;
+
+import duke.task.Task;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Wrapper around the List to allow for controlled access and formatting methods.
+ */
+public class TaskList {
+ private List tasks = new ArrayList<>();
+ public TaskList() {}
+
+ public TaskList(List tasks) {
+ this.tasks = tasks;
+ }
+
+ /**
+ * Marks specific task as either done or undone
+ * and returns the status of the marking.
+ *
+ * @param index of task to be marked.
+ * @param isDone boolean representing status of the task.
+ * @return Status of marking as string.
+ */
+ public String markTask(int index, boolean isDone) {
+ final StringBuilder formatted = new StringBuilder();
+ tasks.get(index).setStatus(isDone);
+
+ if (isDone) {
+ formatted.append("Nice! I've marked this task as done:").append("\n");
+ } else {
+ formatted.append("OK, I've marked this task as not done yet:").append("\n");
+ }
+ formatted.append(tasks.get(index).getFormattedTask());
+
+ return formatted.toString();
+ }
+
+
+ /**
+ * Adds a new task to the TaskList and returns
+ * the status of the addition.
+ *
+ * @param task to be added
+ * @return Status of the addition.
+ */
+ public String addTask(Task task) {
+ tasks.add(task);
+
+ return "Got it. I've added this task:\n" + task.getFormattedTask() + "\nNow you have " + tasks.size() + " tasks in the list.";
+ }
+
+ /**
+ * Removes a task from the TaskList by index and
+ * returns the status of the removal.
+ *
+ * @param idx of task to be removed
+ * @return Status of the removal
+ */
+ public String removeTask(int idx) {
+ Task removedTask = tasks.remove(idx);
+
+ return "Got it. I've removed this task:\n" + removedTask.getFormattedTask() + "\nNow you have " + tasks.size() + " tasks in the list.";
+ }
+
+ /**
+ * Returns a task object by index.
+ *
+ * @param idx Index of task to return.
+ * @return task object
+ */
+ public Task get(int idx) {
+ return tasks.get(idx);
+ }
+
+ /**
+ * Returns a formatted version of all tasks
+ * in TaskList with 1-based indexing.
+ *
+ * @return String formatted list
+ */
+ public String getIndexedTasks() {
+ StringBuilder formatted = new StringBuilder();
+ for (int i = 0; i < tasks.size() && tasks.get(i) != null; i++) {
+ formatted.append(i + 1).append(". ").append(tasks.get(i).getFormattedTask()).append("\n");
+ }
+
+ return formatted.toString();
+ }
+
+ public int size() {
+ return tasks.size();
+ }
+
+ /**
+ * Returns a serialized version of all tasks
+ * in TaskList for saving into the .txt file.
+ *
+ * @return String serialized list
+ */
+ public String getSerializedTasks() {
+ StringBuilder formatted = new StringBuilder();
+ for (Task task : tasks) {
+ formatted.append(task.getSerializedString()).append("\n");
+ }
+
+ return formatted.toString();
+ }
+
+ /**
+ * Returns a formatted version of all tasks
+ * in TaskList with 1-based indexing that match
+ * the specified keyword parameter.
+ *
+ * @param keyword string to match against description of tasks
+ * @return String formatted list
+ */
+ public String getIndexedTasksByKeyword(String keyword) {
+ int idx = 1;
+ StringBuilder formatted = new StringBuilder();
+ for (Task task : tasks) {
+ if (task.getDescription().contains(keyword)) {
+ formatted.append(idx++).append(". ").append(task.getFormattedTask()).append("\n");
+ }
+ }
+
+ return formatted.toString();
+ }
+}
diff --git a/src/main/java/duke/ui/TextUi.java b/src/main/java/duke/ui/TextUi.java
new file mode 100644
index 000000000..4f07c625d
--- /dev/null
+++ b/src/main/java/duke/ui/TextUi.java
@@ -0,0 +1,63 @@
+package duke.ui;
+import java.io.InputStream;
+import java.io.PrintStream;
+import java.util.Scanner;
+
+/**
+ * TextUi object is responsible for all messages displayed to the user.
+ * All formatting is specified and followed in this class.
+ */
+public class TextUi {
+ public static final String MESSAGE_WELCOME = "Hello! I'm KevBot";
+ private static final String MESSAGE_GOODBYE = "Bye. Hope to see you again soon!";
+ public static final String MESSAGE_INIT_FAILED = "Failed to initialise address book application. Exiting...";
+
+ /** A decorative prefix added to the beginning of lines printed by AddressBook */
+ private static final String LINE_PREFIX = "|| ";
+
+ /** A platform independent line separator. */
+ private static final String LS = System.lineSeparator();
+
+ private static final String DIVIDER = "===================================================";
+
+ private final Scanner in;
+ private final PrintStream out;
+ public TextUi() {
+ this(System.in, System.out);
+ }
+
+ public TextUi(InputStream in, PrintStream out) {
+ this.in = new Scanner(in);
+ this.out = out;
+ }
+
+ public void showWelcomeMessage() {
+ showToUser(
+ DIVIDER,
+ DIVIDER,
+ MESSAGE_WELCOME,
+ DIVIDER);
+ }
+
+ public void showGoodbyeMessage() {
+ showToUser(MESSAGE_GOODBYE, DIVIDER, DIVIDER);
+ }
+
+ public void showResultToUser(String result) {
+ showToUser(result, DIVIDER);
+ }
+
+ public String getUserCommand() {
+ return in.nextLine();
+ }
+
+ private void showToUser(String... message) {
+ for (String m : message) {
+ out.println(LINE_PREFIX + m.replace("\n", LS + LINE_PREFIX));
+ }
+ }
+
+ public void showInitFailedMessage() {
+ showToUser(MESSAGE_INIT_FAILED, DIVIDER, DIVIDER);
+ }
+}