text:
# A0140145W

###### src/main/java/seedu/address/MainApp.java

``` java
/**
 * The main entry point to the application.
 */
public class MainApp extends Application {
    private static final Logger logger = LogsCenter.getLogger(MainApp.class);

    public static final Version VERSION = new Version(0, 4, 0, true);

    /** Name of the application */
    public static final String NAME = "Task Tracker";

    private final String configPath;
    private Ui ui;
    private Logic logic;
    private Storage storage;
    private Model model;

    public MainApp() {
        this(null);
    }

    public MainApp(String configPath) {
        this.configPath = configPath;
    }

```
###### src/main/java/seedu/address/MainApp.java

``` java
    private Model initModelManager(Storage storage, ReadOnlyConfig config) {
        Optional<ReadOnlyTaskBook> addressBookOptional;
        ReadOnlyTaskBook initialData;
        try {
            addressBookOptional = storage.readTaskBook();
            if (!addressBookOptional.isPresent()) {
                logger.info("Data file not found. Will be starting with an empty TaskBook");
            }
            initialData = addressBookOptional.orElse(new TaskBook());
        } catch (DataConversionException e) {
            logger.warning("Data file not in the correct format. Will be starting with an empty TaskBook");
            initialData = new TaskBook();
        } catch (IOException e) {
            logger.warning("Problem while reading from the file. . Will be starting with an empty TaskBook");
            initialData = new TaskBook();
        }

        return new ModelManager(config, initialData);
    }

```
###### src/main/java/seedu/address/MainApp.java

``` java
    private ConfigStorage initConfigStorage(String configFilePath) {
        if (configFilePath == null) {
            configFilePath = getApplicationParameter("config");
        }
        if (configFilePath == null) {
            configFilePath = Config.DEFAULT_CONFIG_FILE;
        } else {
            logger.info("Custom Config file specified " + configFilePath);
        }

        logger.info("Using config file: " + configFilePath);
        return new JsonConfigStorage(configFilePath);
    }

```
###### src/main/java/seedu/address/logic/LogicManager.java

``` java
/**
 * The main LogicManager of the app.
 */
public class LogicManager extends ComponentManager implements Logic {
    private final Logger logger = LogsCenter.getLogger(LogicManager.class);

    private final Model model;
    private final Storage storage;
    private final Parser<? extends Command> parser;

    public LogicManager(Model model, Storage storage, Parser<? extends Command> parser) {
        this.model = model;
        this.storage = storage;
        this.parser = parser;
    }

    public LogicManager(Model model, Storage storage) {
        this(model, storage, new TaskTrackerParser());
    }

    @Override
    public CommandResult execute(Command command) throws CommandException, IOException {
        final TaskBookChangeListener taskBookListener = new TaskBookChangeListener(model.getTaskBook());
        final Config oldConfig = new Config(model.getConfig());
        final CommandResult result = command.execute(model);
        updateConfigStorage(oldConfig);
        updateTaskBookStorage(taskBookListener);
        if (model.hasUncommittedChanges()) {
            model.recordState(result.feedbackToUser);
        }
        return result;
    }

    @Override
    public CommandResult execute(String commandText) throws ParseException, CommandException, IOException {
        logger.info("----------------[USER COMMAND][" + commandText + "]");
        return execute(parser.parse(commandText));
    }

    private void updateTaskBookStorage(TaskBookChangeListener listener) throws IOException {
        if (listener.getHasChanged()) {
            logger.info("Task book data changed, saving to file");
            storage.saveTaskBook(model.getTaskBook());
        }
    }

    private void updateConfigStorage(Config oldConfig) throws IOException {
        final ReadOnlyConfig newConfig = model.getConfig();
        if (!oldConfig.getTaskBookFilePath().equals(newConfig.getTaskBookFilePath())) {
            logger.info("Task book file path changed, moving task book");
            try {
                storage.moveTaskBook(newConfig.getTaskBookFilePath());
            } catch (IOException e) {
                // Recover by reverting the task book path
                model.setTaskBookFilePath(oldConfig.getTaskBookFilePath());
                throw e;
            }
        }

        if (!oldConfig.equals(newConfig)) {
            logger.info("Config changed, saving to file");
            storage.saveConfig(newConfig);
        }
    }

    @Override
    public List<String> autocomplete(String input, int pos) {
        return parser.autocomplete(model, input, pos);
    }

    @Override
    public ReadOnlyModel getModel() {
        return model;
    }

}
```
###### src/main/java/seedu/address/logic/Logic.java

``` java
/**
 * API of the Logic component
 */
public interface Logic {

    /**
     * Executes the command and returns the result.
     * @param command The command to execute.
     * @return the result of the command execution.
     * @throws CommandException if an error occurred while executing the command.
     * @throws IOException if an error occurred while saving the modified model to disk.
     */
    CommandResult execute(Command command) throws CommandException, IOException;

```
###### src/main/java/seedu/address/logic/Logic.java

``` java
    List<String> autocomplete(String input, int pos);

    /** Returns the logic's component associated model. */
    ReadOnlyModel getModel();

}
```
###### src/main/java/seedu/address/logic/parser/ExitCommandParser.java

``` java
public class ExitCommandParser implements Parser<ExitCommand> {

    private final CommandLineParser cmdParser = new CommandLineParser();

    @Override
    public ExitCommand parse(String str) throws ParseException {
        cmdParser.parse(str);
        return new ExitCommand();
    }

}
```
###### src/main/java/seedu/address/logic/parser/MarkDeadlineUnfinishedCommandParser.java

``` java
public class MarkDeadlineUnfinishedCommandParser implements Parser<MarkDeadlineUnfinishedCommand> {

    private final Argument<Integer> indexArg = new Argument<>("INDEX", new IndexParser(TaskType.DEADLINE));
    private final CommandLineParser cmdParser = new CommandLineParser()
                                                    .addArgument(indexArg);

    @Override
    public MarkDeadlineUnfinishedCommand parse(String str) throws ParseException {
        cmdParser.parse(str);
        return new MarkDeadlineUnfinishedCommand(indexArg.getValue());
    }

}
```
###### src/main/java/seedu/address/logic/parser/MarkFinishedCommandParser.java

``` java
public class MarkFinishedCommandParser implements Parser<Command> {

    private final OverloadParser<Command> overloadParser = new OverloadParser<Command>()
            .addParser("Mark a deadline as finished", new MarkDeadlineFinishedParser())
            .addParser("Mark a floating task as finished", new MarkFloatingTaskFinishedParser());

    @Override
    public Command parse(String str) throws ParseException {
        return overloadParser.parse(str);
    }

}
```
###### src/main/java/seedu/address/logic/parser/HelpCommandParser.java

``` java
public class HelpCommandParser implements Parser<HelpCommand> {

    private final CommandLineParser cmdParser = new CommandLineParser();

    @Override
    public HelpCommand parse(String str) throws ParseException {
        cmdParser.parse(str);
        return new HelpCommand();
    }

}
```
###### src/main/java/seedu/address/logic/parser/DateTimeArgument.java

``` java
public class DateTimeArgument implements CommandLineParser.ArgumentParser {

    private final Argument<LocalDate> dateArg;
    private final Argument<LocalTime> timeArg;

    public DateTimeArgument(String dateName, String timeName) {
        this.dateArg = new Argument<>(dateName, new DateParser());
        this.timeArg = new Argument<>(timeName, new TimeParser());
    }

    public Optional<LocalDate> getDate() {
        return dateArg.isPresent() ? Optional.of(dateArg.getValue()) : Optional.empty();
    }

    public Optional<SubstringRange> getDateRange() {
        return dateArg.isPresent() ? Optional.of(dateArg.getRange()) : Optional.empty();
    }

    public Optional<LocalTime> getTime() {
        return timeArg.isPresent() ? Optional.of(timeArg.getValue()) : Optional.empty();
    }

    public Optional<SubstringRange> getTimeRange() {
        return timeArg.isPresent() ? Optional.of(timeArg.getRange()) : Optional.empty();
    }

    public void setReferenceDateTime(LocalDateTime referenceDateTime) {
        dateArg.setParser(new DateParser(referenceDateTime.toLocalDate()));
        timeArg.setParser(new TimeParser(referenceDateTime.toLocalTime()));
    }

    @Override
    public String getName() {
        return dateArg.getName() + " " + timeArg.getName();
    }

    @Override
    public void reset() {
        dateArg.reset();
        timeArg.reset();
    }

    @Override
    public void parse(CommandLineScanner scanner) throws ParseException {
        try {
            dateArg.parse(scanner);
        } catch (ParseException e) {
            // Okay for now
        }
        try {
            timeArg.parse(scanner);
        } catch (ParseException e) {
            if (!dateArg.isPresent()) {
                throw e;
            }
        }
    }

    @Override
    public List<String> autocomplete(ReadOnlyModel model, CommandLineScanner scanner, int pos) {
        // We simply just advance the scanner if we are able to parse as date/time
        try {
            dateArg.parse(scanner);
        } catch (ParseException e) {
            // Do nothing, we are just advancing the scanner if possible.
        }
        try {
            timeArg.parse(scanner);
        } catch (ParseException e) {
            // Do nothing, we are just advancing the scanner if possible
        }
        return Collections.emptyList();
    }

}
```
###### src/main/java/seedu/address/logic/parser/AddDeadlineParser.java

``` java
    @Override
    public AddDeadlineCommand parse(String str) throws ParseException {
        // Tell date/time parsers the current time
        final LocalDateTime now = referenceDateTime.orElse(LocalDateTime.now());
        dateTimeArg.setReferenceDateTime(now);

        // Parse command
        cmdParser.parse(str);

        final LocalDate date = dateTimeArg.getDate().orElse(now.toLocalDate());
        final LocalTime time = dateTimeArg.getTime().orElse(LocalTime.of(23, 59));
        final DeadlineTask toAdd = new DeadlineTask(nameArg.getValue(), LocalDateTime.of(date, time));
        return new AddDeadlineCommand(toAdd);
    }

}
```
###### src/main/java/seedu/address/logic/parser/MarkTaskUnfinishedCommandParser.java

``` java
public class MarkTaskUnfinishedCommandParser implements Parser<Command> {

    private final OverloadParser<Command> overloadParser = new OverloadParser<Command>()
            .addParser("Mark a deadline task as unfinished", new MarkDeadlineUnfinishedCommandParser())
            .addParser("Mark a floating task as unfinished", new MarkFloatingTaskUnfinishedCommandParser());

    @Override
    public Command parse(String str) throws ParseException {
        return overloadParser.parse(str);
    }

}
```
###### src/main/java/seedu/address/logic/parser/CommandLineParser.java

``` java
/**
 * A utility class that uses a {@link CommandLineScanner} to parse a string, breaking it down into its
 * individual {@link CommandLineParser.Argument} and {@link CommandLineParser.Flag}
 */
public class CommandLineParser {

    private static String MSG_ARG_NOT_PROVIDED = "Required argument not provided: %s";
    private static String MSG_FLAG_NOT_PROVIDED = "Required flag not provided: %s%s";
    private static String MSG_EXPECTED_KEYWORD = "Expected keyword: %s";
    private static String MSG_TOO_MANY_ARGS = "Too many arguments";
    private static String MSG_FLAG_MULTIPLE = "%s specified multiple times";

    private final List<ArgumentParser> arguments = new ArrayList<>();
    private final Map<String, FlagParser> flags = new HashMap<>();

    public CommandLineParser addArgument(ArgumentParser argument) {
        arguments.add(argument);
        return this;
    }

    public CommandLineParser addArgument(String keyword) {
        Parser<Object> parser = str -> {
            if (str.equals(keyword)) {
                return new Object(); // dummy object
            } else {
                SubstringRange range = new SubstringRange(0, str.length());
                throw new ParseException(String.format(MSG_EXPECTED_KEYWORD, keyword), range);
            }
        };
        arguments.add(new Argument<Object>(keyword, parser));
        return this;
    }

    public CommandLineParser putFlag(FlagParser flag) {
        flags.put(flag.getPrefix(), flag);
        return this;
    }

    private Collection<String> getPrefixesForFlags() {
        return flags.values().stream().map(flag -> flag.getPrefix()).collect(Collectors.toList());
    }

    private void reset() {
        // Reset all argument parsers
        for (ArgumentParser argument : arguments) {
            argument.reset();
        }

        // Reset all flag parsers
        for (FlagParser flag : flags.values()) {
            flag.reset();
        }
    }

    public void parse(String str) throws ParseException {
        reset();

        final CommandLineScanner scanner = new CommandLineScanner(str, getPrefixesForFlags());

        // Parse arguments
        for (ArgumentParser argument : arguments) {
            argument.parse(scanner);
        }

        if (!flags.isEmpty()) {
            // Parse flags
            Optional<CommandLineScanner.Flag> scannerFlag;
            while ((scannerFlag = scanner.nextFlag()).isPresent()) {
                FlagParser flag = flags.get(scannerFlag.get().prefix);
                assert flag != null; // CommandLineScanner should only return flags it recognizes
                flag.parseFlag(scannerFlag.get());
            }

            // Any non-optional flags that are not specified?
            for (FlagParser flag : flags.values()) {
                if (!flag.isOptional() && !flag.isPresent()) {
                    throw new ParseException(String.format(MSG_FLAG_NOT_PROVIDED, flag.getPrefix(), flag.getName()));
                }
            }
        } else {
            // Do we have any extra arguments that should not be there?
            scanner.skipWhitespace();
            if (!scanner.getRemainingInput().isEmpty()) {
                SubstringRange range = new SubstringRange(scanner.getInputPosition(), scanner.getInput().length());
                throw new ParseException(MSG_TOO_MANY_ARGS, range);
            }
        }
    }

    public List<String> autocomplete(ReadOnlyModel model, String str, int pos) {
        reset();

        final CommandLineScanner scanner = new CommandLineScanner(str, getPrefixesForFlags());

        // Autocomplete arguments
        for (ArgumentParser argument : arguments) {
            final List<String> candidates = argument.autocomplete(model, scanner, pos);
            if (scanner.getInputPosition() >= pos) {
                return candidates;
            }
        }

        // Autocomplete flags
        try {
            Optional<CommandLineScanner.Flag> scannerFlag;
            while ((scannerFlag = scanner.nextFlag()).isPresent()) {
                final FlagParser flag = flags.get(scannerFlag.get().prefix);
                final List<String> candidates = flag.autocomplete(model, scannerFlag.get(), pos);
                if (scanner.getInputPosition() >= pos) {
                    return candidates;
                }
            }
        } catch (ParseException e) {
            // The CommandLineScanner found an unrecognized flag prefix and so can't continue any more.
            return Collections.emptyList();
        }

        return Collections.emptyList();
    }

    public interface ArgumentParser {
        /** Name of the argument. */
        String getName();

        /** Resets the parser */
        default void reset() {}

        /**
         * Scans and parses in input from the provided {@link CommandLineScanner}.
         * @throws ParseException of there was an error parsing the input. The input position of the
         * CommandLineScanner must NOT be modified in this case.
         */
        void parse(CommandLineScanner scanner) throws ParseException;

        /**
         * Autocompletes the input from the provided {@link CommandLineScanner}, with the "caret position"
         * at <code>pos</code>.
         * @return the list of autocomplete suggestions if pos is within the range of the input handled by
         * this argument.
         */
        List<String> autocomplete(ReadOnlyModel model, CommandLineScanner scanner, int pos);
    }

    /**
     * An command line argument parser for a single argument of type T.
     */
    public static class Argument<T> implements ArgumentParser {
        private final String name;
        private Parser<? extends T> parser;
        private boolean present;
        private T value;
        private SubstringRange range;

        public Argument(String name, Parser<? extends T> parser) {
            assert !CollectionUtil.isAnyNull(name, parser);
            this.name = name;
            this.parser = parser;
        }

        @Override
        public String getName() {
            return name;
        }

        public void setParser(Parser<? extends T> parser) {
            assert parser != null;
            this.parser = parser;
        }

        public boolean isPresent() {
            return present;
        }

        public void set(T value, SubstringRange range) {
            assert !CollectionUtil.isAnyNull(value, range);
            this.value = value;
            this.range = range;
            present = true;
        }

        @Override
        public void reset() {
            present = false;
            value = null;
            range = null;
        }

        public T getValue() {
            assert present;
            return value;
        }

        public SubstringRange getRange() {
            assert present;
            return range;
        }

        /**
         * Parses the provided {@link CommandLineScanner.Argument}.
         */
        public void parse(CommandLineScanner.Argument arg) throws ParseException {
            final T value;
            try {
                value = parser.parse(arg.value);
            } catch (ParseException e) {
                final ParseExceptionBuilder builder = new ParseExceptionBuilder(e);
                builder.prependMessage(getName() + ": ");
                if (arg.quoted) {
                    builder.clearRanges()
                            .addRange(arg.range);
                } else {
                    builder.indentRanges(arg.range.getStart());
                }
                throw builder.build();
            }
            set(value, arg.range);
        }

        @Override
        public void parse(CommandLineScanner scanner) throws ParseException {
            Optional<CommandLineScanner.Argument> arg = scanner.peekNextArgument();
            if (!arg.isPresent()) {
                SubstringRange range = new SubstringRange(scanner.getInputPosition(), scanner.getInput().length());
                throw new ParseException(String.format(MSG_ARG_NOT_PROVIDED, name), range);
            }
            parse(arg.get());
            scanner.nextArgument();
        }

        /**
         * Autocompletes the input from the provided {@link CommandLineScanner.Argument}, with the
         * "caret position" at <code>pos</code>.
         */
        public List<String> autocomplete(ReadOnlyModel model, CommandLineScanner.Argument arg, int pos) {
            if (arg.quoted) {
                // TODO: we don't support quoted arguments yet because autocompleting them will be tricky
                // (e.g. need to requote the arguments)
                return Collections.emptyList();
            } else if (arg.range.contains(pos)) {
                return parser.autocomplete(model, arg.value, pos - arg.range.getStart());
            } else if (pos > arg.range.getEnd()) {
                // Attempt to parse this argument, in case other arguments/flags need this argument as
                // context for autocompletion
                try {
                    parse(arg);
                } catch (ParseException e) {
                    // This is okay as the validity of this argument may not be required for a successful
                    // autocomplete.
                }
            }
            return Collections.emptyList();
        }

        @Override
        public List<String> autocomplete(ReadOnlyModel model, CommandLineScanner scanner, int pos) {
            Optional<CommandLineScanner.Argument> arg = scanner.nextArgument();
            if (!arg.isPresent() && pos <= scanner.getInputPosition()) {
                // Caret position is within the blank space between the arguments and the flags sections
                return parser.autocomplete(model, "", 0);
            } else if (arg.isPresent()) {
                // Caret position is within the argument
                return autocomplete(model, arg.get(), pos);
            } else {
                // Caret is within the "flags" section
                return Collections.emptyList();
            }
        }
    }

    /**
     * A command line argument parser for parsing a rest argument of type T.
     */
    public static class RestArgument<T> extends Argument<T> {
        public RestArgument(String name, Parser<? extends T> parser) {
            super(name, parser);
        }

        @Override
        public void parse(CommandLineScanner scanner) throws ParseException {
            final CommandLineScanner.Argument restArgument = scanner.peekNextRestArgument();
            parse(restArgument);
            scanner.nextRestArgument();
        }

        @Override
        public List<String> autocomplete(ReadOnlyModel model, CommandLineScanner scanner, int pos) {
            return autocomplete(model, scanner.nextRestArgument(), pos);
        }
    }

    /**
     * A command line argument parser for parsing a variable number of arguments of type T.
     */
    public static class ListArgument<T> implements ArgumentParser {
        private final String name;
        private Parser<? extends T> parser;
        private final ArrayList<Argument<T>> arguments = new ArrayList<>();
        private SubstringRange range;

        public ListArgument(String name, Parser<? extends T> parser) {
            assert !CollectionUtil.isAnyNull(name, parser);
            this.name = name;
            this.parser = parser;
        }

        @Override
        public String getName() {
            return name;
        }

        public void setParser(Parser<? extends T> parser) {
            assert parser != null;
            this.parser = parser;
        }

        @Override
        public void reset() {
            arguments.clear();
            range = null;
        }

        public List<T> getValues() {
            return arguments.stream()
                            .map(arg -> arg.getValue())
                            .collect(Collectors.toList());
        }

        public SubstringRange getRange() {
            assert range != null;
            return range;
        }

        @Override
        public void parse(CommandLineScanner scanner) throws ParseException {
            scanner.skipWhitespace();
            range = new SubstringRange(scanner.getInputPosition(), scanner.getInputPosition());
            while (scanner.peekNextArgument().isPresent()) {
                final Argument<T> argument = new Argument<>(name + "[" + arguments.size() + "]", parser);
                argument.parse(scanner);
                arguments.add(argument);
                range = new SubstringRange(range.getStart(), scanner.getInputPosition());
            }
        }

        @Override
        public List<String> autocomplete(ReadOnlyModel model, CommandLineScanner scanner, int pos) {
            while (scanner.nextArgument().isPresent()) {
                final Argument<T> argument = new Argument<>(name + "[" + arguments.size() + "]", parser);
                final List<String> candidates = argument.autocomplete(model, scanner, pos);
                if (scanner.getInputPosition() >= pos) {
                    return candidates;
                }
            }
            return Collections.emptyList();
        }

    }

    public interface FlagParser {
        /** Returns the flag prefix */
        String getPrefix();

        /** Returns the flag name */
        String getName();

        /** Resets the flag parsing state */
        default void reset() {}

        /** Returns true if the flag is optional */
        boolean isOptional();

        /** Returns true if the flag has a value */
        boolean isPresent();

        /** Parses the {@link CommandLineScanner.Flag} */
        void parseFlag(CommandLineScanner.Flag flag) throws ParseException;

        /**
         * Autocompletes the {@link CommandLineScanner.Flag}, with the "caret position" at <code>pos</code>.
         * <code>pos</code> is guaranteed to be within the range of the flag.
         */
        List<String> autocomplete(ReadOnlyModel model, CommandLineScanner.Flag flag, int pos);
    }

    private abstract static class BaseFlag<T> implements FlagParser {

        private final String prefix;
        private final String name;
        private Parser<? extends T> parser;
        protected T value;
        protected boolean present;
        protected SubstringRange range;

        protected BaseFlag(String prefix, String name, Parser<? extends T> parser) {
            assert !CollectionUtil.isAnyNull(prefix, name, parser);
            this.prefix = prefix;
            this.name = name;
            this.parser = parser;
        }

        @Override
        public String getPrefix() {
            return prefix;
        }

        @Override
        public String getName() {
            return name;
        }

        @Override
        public boolean isPresent() {
            return present;
        }

        public void setParser(Parser<? extends T> parser) {
            assert parser != null;
            this.parser = parser;
        }

        @Override
        public void reset() {
            present = false;
        }

        @Override
        public void parseFlag(CommandLineScanner.Flag flag) throws ParseException {
            assert flag.prefix.equals(prefix);
            if (present) {
                throw new ParseException(String.format(MSG_FLAG_MULTIPLE, prefix), flag.range);
            }
            try {
                value = parser.parse(flag.value);
            } catch (ParseException e) {
                final ParseExceptionBuilder builder = new ParseExceptionBuilder(e);
                builder.prependMessage(getName() + ": ");
                if (flag.quoted) {
                    builder.clearRanges().addRange(flag.range);
                } else {
                    builder.indentRanges(flag.range.getStart());
                }
                throw builder.build();
            }
            present = true;
            range = flag.range;
        }

        @Override
        public List<String> autocomplete(ReadOnlyModel model, CommandLineScanner.Flag flag, int pos) {
            if (flag.quoted) {
                // TODO: we don't support quoted arguments yet because autocompleting them will be tricky
                // (e.g. need to requote the arguments)
                return Collections.emptyList();
            }
            return parser.autocomplete(model, flag.value, pos - flag.range.getStart());
        }
    }

    public static class Flag<T> extends BaseFlag<T> {

        public Flag(String prefix, String name, Parser<? extends T> parser) {
            super(prefix, name, parser);
        }

        public T getValue() {
            assert present;
            return value;
        }

        public SubstringRange getRange() {
            assert present;
            return range;
        }

        @Override
        public boolean isOptional() {
            return false;
        }

    }

    public static class OptionalFlag<T> extends BaseFlag<T> {

        public OptionalFlag(String prefix, String name, Parser<? extends T> parser) {
            super(prefix, name, parser);
        }

        public Optional<T> getValue() {
            return isPresent() ? Optional.of(value) : Optional.empty();
        }

        public Optional<SubstringRange> getRange() {
            return isPresent() ? Optional.of(range) : Optional.empty();
        }

        @Override
        public boolean isOptional() {
            return true;
        }

    }

}
```
###### src/main/java/seedu/address/logic/parser/NameParser.java

``` java
/**
 * A parser that parses a string {@link Name}.
 */
public class NameParser implements Parser<Name> {

    @Override
    public Name parse(String str) throws ParseException {
        try {
            return new Name(str);
        } catch (IllegalValueException e) {
            throw new ParseException(e.getMessage(), e, SubstringRange.of(str));
        }
    }

}
```
###### src/main/java/seedu/address/logic/parser/MarkFloatingTaskFinishedParser.java

``` java
public class MarkFloatingTaskFinishedParser implements Parser<MarkFloatingTaskFinishedCommand> {

    private final Argument<Integer> indexArg = new Argument<>("INDEX", new IndexParser(TaskType.FLOAT));
    private final CommandLineParser cmdParser = new CommandLineParser()
                                                    .addArgument(indexArg);

    @Override
    public MarkFloatingTaskFinishedCommand parse(String str) throws ParseException {
        cmdParser.parse(str);
        return new MarkFloatingTaskFinishedCommand(indexArg.getValue());
    }

}
```
###### src/main/java/seedu/address/logic/parser/CommandLineScanner.java

``` java
/**
 * Tokenizes command line strings made up of zero or more of the following:
 *
 * <ul>
 * <li>Quoted strings, which are surrounded by quotes (e.g. <code>"quoted string"</code>).
 * Use backslashes to embed quotes inside the string. (e.g. <code>"escaped \" backslash"</code>).</li>
 * <li>Unquoted strings, which are surrounded by word boundaries. (e.g. <code>word</code> is a single
 * unquoted string, while <code>two words</code> are two unquoted strings.</li>
 * <li>Arguments, which are either quoted or unquoted strings that must come before flags.</li>
 * <li>A single rest argument, which is either a quoted string or multiple unquoted strings that come before
 * flags.</li>
 * <li>Flags, which are strings which have prefix which belong to the list of <code>flagPrefixes</code></li>
 * </ul>
 */
public class CommandLineScanner {

    /** Pattern for parsing unquoted strings */
    private static Pattern PAT_UNQUOTED = Pattern.compile("^(?<unquotedString>[^\\s]+)");

    /** Pattern for parsing quoted strings */
    private static Pattern PAT_QUOTED = Pattern.compile("^\"(?<quotedString>(?:\\\\\"|[^\"])*)\"");

    private final String input;

    private int inputPosition;

    private final List<String> flagPrefixes;

    public CommandLineScanner(String input, Collection<String> flagPrefixes) {
        assert input != null;
        this.input = input;
        this.flagPrefixes = new ArrayList<>(flagPrefixes);
    }

    public CommandLineScanner(String input) {
        this(input, Collections.emptyList());
    }

    public CommandLineScanner(CommandLineScanner toBeCopied) {
        this(toBeCopied.input, toBeCopied.flagPrefixes);
        this.inputPosition = toBeCopied.inputPosition;
    }

    public String getInput() {
        return input;
    }

    public Collection<String> getFlagPrefixes() {
        return Collections.unmodifiableList(flagPrefixes);
    }

    public int getInputPosition() {
        return inputPosition;
    }

    /**
     * Returns the substring of input that has not been processed yet.
     */
    public String getRemainingInput() {
        return input.substring(inputPosition);
    }

    /**
     * Attempts to match the string with a flag prefix, and returns the first successfully matched one.
     */
    private Optional<String> matchFlagPrefix(String str) {
        for (String flagPrefix : flagPrefixes) {
            if (str.startsWith(flagPrefix)) {
                return Optional.of(flagPrefix);
            }
        }
        return Optional.empty();
    }

    /**
     * Skip over whitespace in the input, if any.
     */
    public void skipWhitespace() {
        while (inputPosition < input.length() && Character.isWhitespace(input.charAt(inputPosition))) {
            inputPosition++;
        }
    }

    /**
     * Returns the next unquoted string (if any).
     */
    public Optional<String> nextUnquotedString() {
        skipWhitespace();
        final Matcher matcher = PAT_UNQUOTED.matcher(input);
        matcher.region(inputPosition, input.length());
        if (matcher.find()) {
            inputPosition = matcher.end();
            return Optional.of(matcher.group("unquotedString"));
        } else {
            return Optional.empty();
        }
    }

    /**
     * Returns the next unquoted string (if any), but does not advance the input position.
     */
    public Optional<String> peekNextUnquotedString() {
        return new CommandLineScanner(this).nextUnquotedString();
    }

    /**
     * Returns the next quoted string (if any).
     */
    public Optional<String> nextQuotedString() {
        skipWhitespace();
        final Matcher matcher = PAT_QUOTED.matcher(input);
        matcher.region(inputPosition, input.length());
        if (matcher.find()) {
            inputPosition = matcher.end();
            return Optional.of(unescapeQuotes(matcher.group("quotedString")));
        } else {
            return Optional.empty();
        }
    }

    private String unescapeQuotes(String str) {
        return str.replaceAll("\\\\\"", "\"");
    }

    /**
     * Returns the next quoted string (if any), but does not advance the input position.
     */
    public Optional<String> peekNextQuotedString() {
        return new CommandLineScanner(this).nextQuotedString();
    }

    /**
     * Returns the next argument (if any).
     */
    public Optional<Argument> nextArgument() {
        Optional<String> value;
        skipWhitespace();
        int startInputPosition = inputPosition;
        if ((value = nextQuotedString()).isPresent()) {
            return Optional.of(new Argument(value.get(), startInputPosition, inputPosition, true));
        } else if ((value = nextUnquotedString()).isPresent()) {
            if (matchFlagPrefix(value.get()).isPresent()) {
                return Optional.empty();
            } else {
                return Optional.of(new Argument(value.get(), startInputPosition, inputPosition, false));
            }
        } else {
            return Optional.empty();
        }
    }

    /**
     * Returns the next argument (if any), but does not advance the input position.
     */
    public Optional<Argument> peekNextArgument() {
        return new CommandLineScanner(this).nextArgument();
    }

    /**
     * Returns the "rest argument", which is either:
     * 1. A single quoted argument.
     * 2. Or the reset of the input until the next flag as a single argument.
     */
    public Argument nextRestArgument() {
        skipWhitespace();
        int startPosition = inputPosition;
        Optional<String> quotedString = nextQuotedString();
        if (quotedString.isPresent()) {
            return new Argument(quotedString.get(), startPosition, inputPosition, true);
        } else {
            while (!matchFlagPrefix(getRemainingInput()).isPresent()) {
                if (!nextUnquotedString().isPresent()) {
                    break;
                }
                skipWhitespace();
            }
            String value = input.substring(startPosition, inputPosition);
            String trimmedValue = value.trim();
            int endPosition = inputPosition - (value.length() - trimmedValue.length());
            return new Argument(trimmedValue, startPosition, endPosition, false);
        }
    }

    /**
     * Returns the "rest argument", but does not advance the input position.
     */
    public Argument peekNextRestArgument() {
        return new CommandLineScanner(this).nextRestArgument();
    }

    /**
     * Returns the next flag (if any).
     * @throws ParseException if the input cannot be parsed as a flag
     */
    public Optional<Flag> nextFlag() throws ParseException {
        skipWhitespace();
        if (getRemainingInput().isEmpty()) {
            return Optional.empty();
        }
        Optional<String> flagPrefix = matchFlagPrefix(getRemainingInput());
        if (flagPrefix.isPresent()) {
            inputPosition += flagPrefix.get().length();
            if (peekNextQuotedString().isPresent()) {
                skipWhitespace();
                int startPosition = inputPosition;
                String value = nextQuotedString().get();
                return Optional.of(new Flag(flagPrefix.get(), value, startPosition, inputPosition, true));
            } else {
                Argument rest = nextRestArgument();
                return Optional.of(new Flag(flagPrefix.get(), rest.value, rest.range, false));
            }
        } else {
            String flag = peekNextUnquotedString().orElse("");
            throw new ParseException("not a flag: " + flag, new SubstringRange(inputPosition, input.length()));
        }
    }

    public static final class Argument {
        public final String value;
        public final SubstringRange range;
        public final boolean quoted;

        public Argument(String value, SubstringRange range, boolean quoted) {
            this.value = value;
            this.range = range;
            this.quoted = quoted;
        }

        public Argument(String value, int start, int end, boolean quoted) {
            this(value, new SubstringRange(start, end), quoted);
        }

        @Override
        public boolean equals(Object other) {
            return other == this
                    || (other instanceof Argument
                    && value.equals(((Argument)other).value)
                    && range.equals(((Argument)other).range)
                    && quoted == ((Argument)other).quoted
                    );
        }

        @Override
        public String toString() {
            return MoreObjects.toStringHelper(this)
                    .add("value", value)
                    .add("range", range)
                    .add("quoted", quoted)
                    .toString();
        }
    }

    public static final class Flag {
        public final String prefix;
        public final String value;
        public final SubstringRange range;
        public final boolean quoted;

        public Flag(String prefix, String value, SubstringRange range, boolean quoted) {
            this.prefix = prefix;
            this.value = value;
            this.range = range;
            this.quoted = quoted;
        }

        public Flag(String prefix, String value, int start, int end, boolean quoted) {
            this(prefix, value, new SubstringRange(start, end), quoted);
        }

        @Override
        public boolean equals(Object other) {
            return other == this
                    || (other instanceof Flag
                    && prefix.equals(((Flag)other).prefix)
                    && value.equals(((Flag)other).value)
                    && range.equals(((Flag)other).range)
                    && quoted == ((Flag)other).quoted
                    );
        }

        @Override
        public String toString() {
            return MoreObjects.toStringHelper(this)
                    .add("prefix", prefix)
                    .add("value", value)
                    .add("range", range)
                    .add("quoted", quoted)
                    .toString();
        }

    }

}
```
###### src/main/java/seedu/address/logic/parser/FindCommandParser.java

``` java
/**
 * Parser for the "find" command.
 */
public class FindCommandParser implements Parser<Command> {
    private final ListArgument<String> keywordsArg = new ListArgument<>("KEYWORDS", x -> x);
    private final CommandLineParser cmdParser = new CommandLineParser()
                                                    .addArgument(keywordsArg);

    @Override
    public Command parse(String str) throws ParseException {
        cmdParser.parse(str);

        final HashSet<String> keywords = new HashSet<>(keywordsArg.getValues());
        if (keywords.isEmpty()) {
            throw new ParseException("must provide at least one keyword", keywordsArg.getRange());
        }

        final TaskPredicate predicate = new TaskNameContainsKeywordsPredicate(keywords);
        return new ListCommand(predicate);
    }

}
```
###### src/main/java/seedu/address/logic/parser/DeleteCommandParser.java

``` java
/**
 * Parser for the "del" command.
 */
public class DeleteCommandParser implements Parser<Command> {

    private final OverloadParser<Command> overloadParser = new OverloadParser<Command>()
            .addParser("Delete an event", new DeleteEventParser())
            .addParser("Delete a deadline", new DeleteDeadlineParser())
            .addParser("Delete a floating task", new DeleteFloatingTaskParser());

    @Override
    public Command parse(String str) throws ParseException {
        return overloadParser.parse(str);
    }

}
```
###### src/main/java/seedu/address/logic/parser/AddEventParser.java

``` java
/**
 * Parser for {@link AddEventCommand}
 */
public class AddEventParser implements Parser<AddEventCommand> {

    private final Argument<Name> nameArg = new Argument<>("NAME", new NameParser());
    private final DateTimeArgument startArg = new DateTimeArgument("START_DATE", "END_DATE");
    private final DateTimeArgument endArg = new DateTimeArgument("END_DATE", "END_TIME");
    private final CommandLineParser cmdParser = new CommandLineParser()
                                                        .addArgument(nameArg)
                                                        .addArgument(startArg)
                                                        .addArgument("to")
                                                        .addArgument(endArg);

    private final Optional<LocalDateTime> referenceDateTime;

    public AddEventParser() {
        this(Optional.empty());
    }

    public AddEventParser(LocalDateTime referenceDateTime) {
        this(Optional.of(referenceDateTime));
    }

    public AddEventParser(Optional<LocalDateTime> referenceDateTime) {
        this.referenceDateTime = referenceDateTime;
    }

    @Override
    public AddEventCommand parse(String str) throws ParseException {
        // Tell date/time parsers the current time
        final LocalDateTime now = referenceDateTime.orElse(LocalDateTime.now());
        startArg.setReferenceDateTime(now);
        endArg.setReferenceDateTime(now);

        // Parse command
        cmdParser.parse(str);

        final LocalDateTimeDuration duration = makeDuration(now);
        final EventTask toAdd = new EventTask(nameArg.getValue(), duration);
        return new AddEventCommand(toAdd);
    }

    private LocalDateTimeDuration makeDuration(LocalDateTime now) throws ParseException {
        final LocalDate startDate = startArg.getDate().orElse(now.toLocalDate());
        final LocalTime startTime = startArg.getTime().orElse(LocalTime.of(0, 0));
        final LocalDate endDate = endArg.getDate().orElse(startDate);
        final LocalTime endTime = endArg.getTime().orElse(LocalTime.of(23, 59));
        try {
            return new LocalDateTimeDuration(LocalDateTime.of(startDate, startTime),
                                             LocalDateTime.of(endDate, endTime));
        } catch (IllegalValueException e) {
            throw new ParseExceptionBuilder(e)
                    .addRangeOptional(startArg.getDateRange())
                    .addRangeOptional(startArg.getTimeRange())
                    .addRangeOptional(endArg.getDateRange())
                    .addRangeOptional(endArg.getTimeRange())
                    .build();
        }
    }

}
```
###### src/main/java/seedu/address/logic/parser/EditEventParser.java

``` java
public class EditEventParser implements Parser<EditEventCommand> {

    private final Argument<Integer> indexArg = new Argument<>("INDEX", new IndexParser(TaskType.EVENT));
    private final OptionalFlag<LocalDate> newStartDateFlag = new OptionalFlag<>("sd-", "NEW_START_DATE",
            new DateParser().withAutocomplete(this::autocompleteStartDate));
    private final OptionalFlag<LocalTime> newStartTimeFlag = new OptionalFlag<>("st-", "NEW_START_TIME",
            new TimeParser().withAutocomplete(this::autocompleteStartTime));
    private final OptionalFlag<LocalDate> newEndDateFlag = new OptionalFlag<>("ed-", "NEW_END_DATE",
            new DateParser().withAutocomplete(this::autocompleteEndDate));
    private final OptionalFlag<LocalTime> newEndTimeFlag = new OptionalFlag<>("et-", "NEW_END_TIME",
            new TimeParser().withAutocomplete(this::autocompleteEndTime));
    private final OptionalFlag<Name> newNameFlag = new OptionalFlag<>("n-", "NEW_NAME",
            new NameParser().withAutocomplete(this::autocompleteName));
    private final CommandLineParser cmdParser = new CommandLineParser()
                                                        .addArgument(indexArg)
                                                        .putFlag(newStartDateFlag)
                                                        .putFlag(newStartTimeFlag)
                                                        .putFlag(newEndDateFlag)
                                                        .putFlag(newEndTimeFlag)
                                                        .putFlag(newNameFlag);

    private final Optional<LocalDateTime> referenceDateTime;

    public EditEventParser() {
        this(Optional.empty());
    }

    public EditEventParser(LocalDateTime referenceDateTime) {
        this(Optional.of(referenceDateTime));
    }

    public EditEventParser(Optional<LocalDateTime> referenceDateTime) {
        this.referenceDateTime = referenceDateTime;
    }

    @Override
    public EditEventCommand parse(String str) throws ParseException {
        // Tell date/time parsers the current time
        final LocalDateTime now = referenceDateTime.orElse(LocalDateTime.now());
        newStartDateFlag.setParser(new DateParser(now.toLocalDate()));
        newStartTimeFlag.setParser(new TimeParser(now.toLocalTime()));
        newEndDateFlag.setParser(new DateParser(now.toLocalDate()));
        newEndTimeFlag.setParser(new TimeParser(now.toLocalTime()));

        cmdParser.parse(str);

        return new EditEventCommand(indexArg.getValue(), newNameFlag.getValue(), newStartDateFlag.getValue(),
                                    newStartTimeFlag.getValue(), newEndDateFlag.getValue(),
                                    newEndTimeFlag.getValue());
    }

    @Override
    public List<String> autocomplete(ReadOnlyModel model, String input, int pos) {
        return cmdParser.autocomplete(model, input, pos);
    }

    private List<String> autocompleteField(ReadOnlyModel model, String input, int pos,
                                           Function<EventTask, String> mapper) {
        if (!indexArg.isPresent() || !input.trim().isEmpty()) {
            return Collections.emptyList();
        }
        final EventTask eventTask;
        try {
            eventTask = model.getEventTask(indexArg.getValue());
        } catch (IllegalValueException e) {
            return Collections.emptyList(); // Invalid index, so no autocompletions
        }
        return Arrays.asList(mapper.apply(eventTask));
    }

    private List<String> autocompleteName(ReadOnlyModel model, String input, int pos) {
        return autocompleteField(model, input, pos, eventTask -> eventTask.getName().toString());
    }

    private List<String> autocompleteStartDate(ReadOnlyModel model, String input, int pos) {
        return autocompleteField(model, input, pos,
            eventTask -> new DateParser().format(eventTask.getStart().toLocalDate()));
    }

    private List<String> autocompleteStartTime(ReadOnlyModel model, String input, int pos) {
        return autocompleteField(model, input, pos,
            eventTask -> new TimeParser().format(eventTask.getStart().toLocalTime()));
    }

    private List<String> autocompleteEndDate(ReadOnlyModel model, String input, int pos) {
        return autocompleteField(model, input, pos,
            eventTask -> new DateParser().format(eventTask.getEnd().toLocalDate()));
    }

    private List<String> autocompleteEndTime(ReadOnlyModel model, String input, int pos) {
        return autocompleteField(model, input, pos,
            eventTask -> new TimeParser().format(eventTask.getEnd().toLocalTime()));
    }

}
```
###### src/main/java/seedu/address/logic/parser/TaskPredicateParser.java

``` java
/**
 * Parses a task predicate name, and returns the corresponding task predicate.
 */
public class TaskPredicateParser implements Parser<TaskPredicate> {

    private static final List<String> KEYWORDS = Arrays.asList("all", "fin", "unfin");

    private final LocalDateTime referenceDateTime;

    public TaskPredicateParser(LocalDateTime referenceDateTime) {
        assert referenceDateTime != null;
        this.referenceDateTime = referenceDateTime;
    }

    @Override
    public TaskPredicate parse(String str) throws ParseException {
        switch (str.trim()) {
        case "":
        case "all":
            return null;
        case "fin":
            return new TaskFinishedPredicate(referenceDateTime);
        case "unfin":
            return new TaskUnfinishedPredicate(referenceDateTime);
        default:
            throw new ParseException("unknown predicate: " + str.trim(), SubstringRange.of(str));
        }
    }

    @Override
    public List<String> autocomplete(ReadOnlyModel model, String input, int pos) {
        if (!input.trim().isEmpty() && pos != CharMatcher.WHITESPACE.trimTrailingFrom(input).length()) {
            return Collections.emptyList();
        }
        final String trimInput = input.trim();
        return KEYWORDS.stream()
                        .filter(keyword -> keyword.startsWith(trimInput))
                        .map(keyword -> keyword.substring(trimInput.length()))
                        .sorted()
                        .collect(Collectors.toList());
    }

}
```
###### src/main/java/seedu/address/logic/parser/DateParser.java

``` java
/**
 * A parser for dates in day/month/year format.
 */
public class DateParser implements Parser<LocalDate> {

    private final LocalDate referenceDate;

    private final DateTimeFormatter dateFormatter;

    public DateParser(LocalDate referenceDate) {
        this.referenceDate = referenceDate;
        dateFormatter = new DateTimeFormatterBuilder()
                .appendPattern("d[/M[/uuuu]]")
                .parseDefaulting(ChronoField.MONTH_OF_YEAR, this.referenceDate.getMonthValue())
                .parseDefaulting(ChronoField.YEAR, this.referenceDate.getYear())
                .toFormatter()
                .withResolverStyle(ResolverStyle.STRICT);
    }

    public DateParser() {
        this(LocalDate.now());
    }

    public LocalDate getReferenceDate() {
        return referenceDate;
    }

    @Override
    public LocalDate parse(String str) throws ParseException {
        assert str != null;
        final Optional<LocalDate> nameDate = parseAsName(str.trim());
        if (nameDate.isPresent()) {
            return nameDate.get();
        }
        try {
            return LocalDate.parse(str.trim(), dateFormatter);
        } catch (DateTimeParseException e) {
            throw new ParseException(e.toString(), e, SubstringRange.of(str));
        }
    }

    private Optional<LocalDate> parseAsName(String name) {
        switch (name) {
        case "tdy": // today
            return Optional.of(referenceDate);
        case "tmr": // tomorrow
            return Optional.of(referenceDate.plusDays(1));
        case "yst": // yesterday
            return Optional.of(referenceDate.minusDays(1));
        default:
            return Optional.empty();
        }
    }

    /**
     * Returns the string representation of a LocalDate. This string can be parsed by {@link #parse} to
     * get back the same LocalDate.
     * @see #parse
     */
    public String format(LocalDate date) {
        return dateFormatter.format(date);
    }

}
```
###### src/main/java/seedu/address/logic/parser/DeleteFloatingTaskParser.java

``` java
/**
 * Parser for "del-float" command.
 */
public class DeleteFloatingTaskParser implements Parser<DeleteFloatingTaskCommand> {

    private final Argument<Integer> indexArg = new Argument<>("INDEX", new IndexParser(TaskType.FLOAT));
    private final CommandLineParser cmdParser = new CommandLineParser()
                                                    .addArgument(indexArg);

    @Override
    public DeleteFloatingTaskCommand parse(String str) throws ParseException {
        cmdParser.parse(str);
        return new DeleteFloatingTaskCommand(indexArg.getValue());
    }

}
```
###### src/main/java/seedu/address/logic/parser/ListCommandParser.java

``` java
/**
 * Parser for the "list" command.
 */
public class ListCommandParser implements Parser<ListCommand> {

    private final RestArgument<TaskPredicate> taskPredicateArg = new RestArgument<>("FILTER", new TaskPredicateParser(LocalDateTime.now()));
    private final CommandLineParser cmdParser = new CommandLineParser()
                                                    .addArgument(taskPredicateArg);

    private final Optional<LocalDateTime> referenceDateTime;

    public ListCommandParser(Optional<LocalDateTime> referenceDateTime) {
        this.referenceDateTime = referenceDateTime;
    }

    public ListCommandParser() {
        this(Optional.empty());
    }

    @Override
    public ListCommand parse(String str) throws ParseException {
        // Tell parsers the current time
        final LocalDateTime now = referenceDateTime.orElse(LocalDateTime.now());
        taskPredicateArg.setParser(new TaskPredicateParser(now));

        cmdParser.parse(str);

        return new ListCommand(taskPredicateArg.getValue());
    }

    @Override
    public List<String> autocomplete(ReadOnlyModel model, String input, int pos) {
        return cmdParser.autocomplete(model, input, pos);
    }

}
```
###### src/main/java/seedu/address/logic/parser/TaskTrackerParser.java

``` java
/**
 * Top level task tracker command-line parser.
 */
public class TaskTrackerParser implements Parser<Command> {
    private final SubcommandParser parser = new SubcommandParser()
            .putSubcommand("add", new AddTaskParser())
            .putSubcommand("edit", new EditCommandParser())
            .putSubcommand("del", new DeleteCommandParser())
            .putSubcommand("fin", new MarkFinishedCommandParser())
            .putSubcommand("unfin", new MarkTaskUnfinishedCommandParser())
            .putSubcommand("clear", new ClearCommandParser())
            .putSubcommand("exit", new ExitCommandParser())
            .putSubcommand("help", new HelpCommandParser())
            .putSubcommand("setdatadir", new SetDataDirectoryParser())
            .putSubcommand("list", new ListCommandParser())
            .putSubcommand("undo", new UndoCommandParser())
            .putSubcommand("redo", new RedoCommandParser())
            .putSubcommand("find", new FindCommandParser())
            ;

    @Override
    public Command parse(String userInput) throws ParseException {
        return parser.parse(userInput);
    }

    @Override
    public List<String> autocomplete(ReadOnlyModel model, String input, int pos) {
        return parser.autocomplete(model, input, pos);
    }

}
```
###### src/main/java/seedu/address/logic/parser/OverloadParser.java

``` java
/**
 * A parser that tries to parse the input string with multiple parsers, and returns the first successful
 * result.
 */
public class OverloadParser<T> implements Parser<T> {

    private final List<Candidate<T>> candidates = new ArrayList<>();

    public OverloadParser<T> addParser(String name, Parser<? extends T> parser) {
        candidates.add(new Candidate<T>(name, parser));
        return this;
    }

    @Override
    public T parse(String str) throws ParseException {
        List<CandidateException> candidateExceptions = new ArrayList<>();

        // Try all parsers, while collecting their exceptions
        for (Candidate<T> candidate : candidates) {
            try {
                return candidate.parser.parse(str);
            } catch (ParseException e) {
                candidateExceptions.add(new CandidateException(candidate.name, e));
            }
        }

        throw makeParseException(candidateExceptions);
    }

    @Override
    public List<String> autocomplete(ReadOnlyModel model, String input, int pos) {
        // Try all parsers, collecting their autocompletions
        return candidates.stream()
                        .map(candidate -> candidate.parser.autocomplete(model, input, pos))
                        .flatMap(x -> x.stream())
                        .collect(Collectors.toList());
    }

    private ParseException makeParseException(List<CandidateException> candidateExceptions) {
        final ParseExceptionBuilder builder = new ParseExceptionBuilder("Tried to parse the input as the following but failed:");
        for (CandidateException candidateException : candidateExceptions) {
            builder.appendMessage("\n\n").appendMessage(candidateException.name).appendMessage(":\n    ")
                .appendMessage(candidateException.exception.getMessage());
            builder.addRanges(candidateException.exception.getRanges());
        }
        return builder.build();
    }

    private static class Candidate<T> {
        final String name;
        final Parser<? extends T> parser;

        Candidate(String name, Parser<? extends T> parser) {
            this.name = name;
            this.parser = parser;
        }
    }

    private static class CandidateException {
        final String name;
        final ParseException exception;

        CandidateException(String name, ParseException exception) {
            this.name = name;
            this.exception = exception;
        }
    }

}
```
###### src/main/java/seedu/address/logic/parser/AddTaskParser.java

``` java
    public AddTaskParser(LocalDateTime referenceDateTime) {
        this(Optional.of(referenceDateTime));
    }

    public AddTaskParser(Optional<LocalDateTime> referenceDateTime) {
        overloadParser = new OverloadParser<AddTaskCommand>()
                            .addParser("Add an event", new AddEventParser(referenceDateTime))
                            .addParser("Add a deadline", new AddDeadlineParser(referenceDateTime))
                            .addParser("Add a floating task", new AddFloatingTaskParser());
    }

    @Override
    public AddTaskCommand parse(String str) throws ParseException {
        return overloadParser.parse(str);
    }
}
```
###### src/main/java/seedu/address/logic/parser/Parser.java

``` java
/**
 * Represents a parser which can parse a string and produce a result of type T.
 *
 * This is a functional interface whose functional method is {@link #parse}.
 */
public interface Parser<T> {

    /**
     * Parses an input string and returns the parsed result as an object with type T.
     * @throws ParseException if the input string could not be parsed. The ranges of the ParseException will
     * contain the substring ranges of the input string which caused the parsing to fail.
     */
    T parse(String str) throws ParseException;

    default List<String> autocomplete(ReadOnlyModel model, String input, int pos) {
        return Collections.emptyList();
    }

    /**
     * Returns a parser that overrides {@link #autocomplete} with the specified function.
     */
    default Parser<T> withAutocomplete(final AutocompleteCallback callback) {
        final Parser<T> parent = this;
        return new Parser<T>() {
            @Override
            public T parse(String str) throws ParseException {
                return parent.parse(str);
            }

            @Override
            public List<String> autocomplete(ReadOnlyModel model, String input, int pos) {
                return callback.call(model, input, pos);
            }
        };
    }

    interface AutocompleteCallback {
        List<String> call(ReadOnlyModel model, String input, int pos);
    }

}
```
###### src/main/java/seedu/address/logic/parser/ParseExceptionBuilder.java

``` java
public class ParseExceptionBuilder {

    private StringBuilder message = new StringBuilder();
    private Throwable cause;
    private ArrayList<SubstringRange> ranges = new ArrayList<>();

    public ParseExceptionBuilder(String message) {
        assert message != null;
        this.message.append(message);
    }

    public ParseExceptionBuilder(Throwable cause) {
        this.message.append(cause.getMessage());
        this.cause = cause;
    }

    public ParseExceptionBuilder(ParseException cause) {
        this((Throwable)cause);
        this.ranges.addAll(cause.getRanges());
    }

    public ParseExceptionBuilder prependMessage(String message) {
        this.message.insert(0, message);
        return this;
    }

    public ParseExceptionBuilder appendMessage(String message) {
        this.message.append(message);
        return this;
    }

    public ParseExceptionBuilder addRange(SubstringRange range) {
        ranges.add(range);
        return this;
    }

    public ParseExceptionBuilder addRangeOptional(Optional<SubstringRange> range) {
        if (range.isPresent()) {
            addRange(range.get());
        }
        return this;
    }

    public ParseExceptionBuilder addRanges(Collection<SubstringRange> ranges) {
        this.ranges.addAll(ranges);
        return this;
    }

    public ParseExceptionBuilder indentRanges(int x) {
        ranges = ranges.stream()
                    .map(range -> range.indent(x))
                    .collect(Collectors.toCollection(ArrayList::new));
        return this;
    }

    public ParseExceptionBuilder clearRanges() {
        ranges.clear();
        return this;
    }

    public ParseException build() {
        return new ParseException(message.toString(), cause, ranges);
    }

}
```
###### src/main/java/seedu/address/logic/parser/DeleteEventParser.java

``` java
public class DeleteEventParser implements Parser<DeleteEventCommand> {

    private final Argument<Integer> indexArg = new Argument<>("INDEX", new IndexParser(TaskType.EVENT));
    private final CommandLineParser cmdParser = new CommandLineParser()
                                                    .addArgument(indexArg);

    @Override
    public DeleteEventCommand parse(String str) throws ParseException {
        cmdParser.parse(str);
        return new DeleteEventCommand(indexArg.getValue());
    }

}
```
###### src/main/java/seedu/address/logic/parser/IndexParser.java

``` java
/**
 * Parses a task index of format <code><PREFIX><INDEX></code>, where
 * <code><PREFIX></code> is a prefix which the index must have, and <code><INDEX></code>
 * is a 1-index signed integer index.
 */
public class IndexParser implements Parser<Integer> {

    private static final String MSG_NO_PREFIX = "%2$s must start with \"%1$s\"";
    private static final String MSG_NOT_INDEX = "invalid %2$s: %3$s";

    private final String prefix;
    private final String name;

    public IndexParser(String prefix, String name) {
        CollectionUtil.isAnyNull(prefix, name);
        this.prefix = prefix;
        this.name = name;
    }

    public IndexParser(TaskType taskType) {
        this(taskType.getPrefixString(), taskType.getName() + " index");
    }

    @Override
    public Integer parse(String str) throws ParseException {
        assert str != null;
        if (!str.startsWith(prefix)) {
            throw new ParseException(String.format(MSG_NO_PREFIX, prefix, name),
                                     new SubstringRange(0, Math.min(prefix.length(), str.length())));
        }

        String indexStr = str.substring(prefix.length());
        if (!StringUtil.isUnsignedInteger(indexStr)) {
            throw new ParseException(String.format(MSG_NOT_INDEX, prefix, name, indexStr),
                                     new SubstringRange(prefix.length(), str.length()));
        }

        return Integer.parseInt(indexStr);
    }

}
```
###### src/main/java/seedu/address/logic/parser/EditCommandParser.java

``` java
public class EditCommandParser implements Parser<Command> {

    private final OverloadParser<Command> overloadParser;

    public EditCommandParser() {
        this(Optional.empty());
    }

    public EditCommandParser(LocalDateTime referenceDateTime) {
        this(Optional.of(referenceDateTime));
    }

    public EditCommandParser(Optional<LocalDateTime> referenceDateTime) {
        overloadParser = new OverloadParser<Command>()
                            .addParser("Edit an event", new EditEventParser(referenceDateTime))
                            .addParser("Edit a deadline", new EditDeadlineParser(referenceDateTime))
                            .addParser("Edit a floating task", new EditFloatingTaskParser());
    }

    @Override
    public Command parse(String str) throws ParseException {
        return overloadParser.parse(str);
    }

    @Override
    public List<String> autocomplete(ReadOnlyModel model, String input, int pos) {
        return overloadParser.autocomplete(model, input, pos);
    }

}
```
###### src/main/java/seedu/address/logic/parser/MarkDeadlineFinishedParser.java

``` java
public class MarkDeadlineFinishedParser implements Parser<MarkDeadlineFinishedCommand> {

    private final Argument<Integer> indexArg = new Argument<>("INDEX", new IndexParser(TaskType.DEADLINE));
    private final CommandLineParser cmdParser = new CommandLineParser()
                                                    .addArgument(indexArg);

    @Override
    public MarkDeadlineFinishedCommand parse(String str) throws ParseException {
        cmdParser.parse(str);
        return new MarkDeadlineFinishedCommand(indexArg.getValue());
    }

}
```
###### src/main/java/seedu/address/logic/parser/PriorityParser.java

``` java
public class PriorityParser implements Parser<Priority> {

    @Override
    public Priority parse(String str) throws ParseException {
        try {
            return new Priority(str);
        } catch (IllegalValueException e) {
            throw new ParseException(e.getMessage(), e, SubstringRange.of(str));
        }
    }

}
```
###### src/main/java/seedu/address/logic/parser/EditDeadlineParser.java

``` java
    @Override
    public EditDeadlineCommand parse(String str) throws ParseException {
        // Tell date/time parsers the current time
        final LocalDateTime now = referenceDateTime.orElse(LocalDateTime.now());
        newDateFlag.setParser(new DateParser(now.toLocalDate()));
        newTimeFlag.setParser(new TimeParser(now.toLocalTime()));

        cmdParser.parse(str);

        return new EditDeadlineCommand(indexArg.getValue(), newNameFlag.getValue(), newDateFlag.getValue(),
                                       newTimeFlag.getValue());
    }

    @Override
    public List<String> autocomplete(ReadOnlyModel model, String input, int pos) {
        return cmdParser.autocomplete(model, input, pos);
    }

    private List<String> autocompleteField(ReadOnlyModel model, String input, int pos,
                                           Function<DeadlineTask, String> mapper) {
        if (!indexArg.isPresent() || !input.trim().isEmpty()) {
            return Collections.emptyList();
        }
        final DeadlineTask deadlineTask;
        try {
            deadlineTask = model.getDeadlineTask(indexArg.getValue());
        } catch (IllegalValueException e) {
            return Collections.emptyList(); // Invalid index, so no autocompletions
        }
        return Arrays.asList(mapper.apply(deadlineTask));
    }

    private List<String> autocompleteName(ReadOnlyModel model, String input, int pos) {
        return autocompleteField(model, input, pos, deadlineTask -> deadlineTask.getName().toString());
    }

    private List<String> autocompleteDueDate(ReadOnlyModel model, String input, int pos) {
        return autocompleteField(model, input, pos,
            deadlineTask -> new DateParser().format(deadlineTask.getDue().toLocalDate()));
    }

    private List<String> autocompleteDueTime(ReadOnlyModel model, String input, int pos) {
        return autocompleteField(model, input, pos,
            deadlineTask -> new TimeParser().format(deadlineTask.getDue().toLocalTime()));
    }

}
```
###### src/main/java/seedu/address/logic/parser/EditFloatingTaskParser.java

``` java
public class EditFloatingTaskParser implements Parser<EditFloatingTaskCommand> {

    private final Argument<Integer> indexArg = new Argument<>("INDEX", new IndexParser(TaskType.FLOAT));
    private final OptionalFlag<Name> newNameFlag = new OptionalFlag<>("n-", "NEW_NAME",
            new NameParser().withAutocomplete(this::autocompleteName));
    private final OptionalFlag<Priority> newPriorityFlag = new OptionalFlag<>("p-", "NEW_PRIORITY",
            new PriorityParser().withAutocomplete(this::autocompletePriority));
    private final CommandLineParser cmdParser = new CommandLineParser()
                                                        .addArgument(indexArg)
                                                        .putFlag(newNameFlag)
                                                        .putFlag(newPriorityFlag);

    @Override
    public EditFloatingTaskCommand parse(String args) throws ParseException {
        cmdParser.parse(args);

        return new EditFloatingTaskCommand(indexArg.getValue(), newNameFlag.getValue(),
                                           newPriorityFlag.getValue());
    }

    @Override
    public List<String> autocomplete(ReadOnlyModel model, String input, int pos) {
        return cmdParser.autocomplete(model, input, pos);
    }

    private List<String> autocompleteField(ReadOnlyModel model, String input, int pos,
                                           Function<FloatingTask, String> mapper) {
        if (!indexArg.isPresent() || !input.trim().isEmpty()) {
            return Collections.emptyList();
        }
        final FloatingTask floatingTask;
        try {
            floatingTask = model.getFloatingTask(indexArg.getValue());
        } catch (IllegalValueException e) {
            return Collections.emptyList(); // Invalid index, so no autocompletions
        }
        return Arrays.asList(mapper.apply(floatingTask));
    }

    private List<String> autocompleteName(ReadOnlyModel model, String input, int pos) {
        return autocompleteField(model, input, pos, floatingTask -> floatingTask.getName().toString());
    }

    private List<String> autocompletePriority(ReadOnlyModel model, String input, int pos) {
        return autocompleteField(model, input, pos, floatingTask -> floatingTask.getPriority().toString());
    }

}
```
###### src/main/java/seedu/address/logic/parser/ParseException.java

``` java
/**
 * Signals that a string input could not be parsed.
 */
public class ParseException extends IllegalValueException {

    private final List<SubstringRange> ranges = new ArrayList<>();

    /**
     * @param message should contain relevant information on why the input is invalid.
     * @param ranges substring range(s) of the invalid input.
     */
    public ParseException(String message, Collection<SubstringRange> ranges) {
        super(message);
        this.ranges.addAll(ranges);
    }

    public ParseException(String message, SubstringRange... ranges) {
        this(message, Arrays.asList(ranges));
    }

    /**
     * @param message should contain relevant information on why the input is invalid.
     * @param cause the exception which caused this exception to be thrown.
     * @param ranges substring range(s) of the invalid input.
     */
    public ParseException(String message, Throwable cause, Collection<SubstringRange> ranges) {
        super(message, cause);
        this.ranges.addAll(ranges);
    }

    public ParseException(String message, Throwable cause, SubstringRange... ranges) {
        this(message, cause, Arrays.asList(ranges));
    }

    public List<SubstringRange> getRanges() {
        return Collections.unmodifiableList(ranges);
    }

}
```
###### src/main/java/seedu/address/logic/parser/MarkFloatingTaskUnfinishedCommandParser.java

``` java
public class MarkFloatingTaskUnfinishedCommandParser implements Parser<MarkFloatingTaskUnfinishedCommand> {

    private final Argument<Integer> indexArg = new Argument<>("INDEX", new IndexParser(TaskType.FLOAT));
    private final CommandLineParser cmdParser = new CommandLineParser()
                                                    .addArgument(indexArg);

    @Override
    public MarkFloatingTaskUnfinishedCommand parse(String str) throws ParseException {
        cmdParser.parse(str);
        return new MarkFloatingTaskUnfinishedCommand(indexArg.getValue());
    }

}
```
###### src/main/java/seedu/address/logic/parser/TimeParser.java

``` java
/**
 * A parser for 12-hour clock times.
 */
public class TimeParser implements Parser<LocalTime> {

    private static final Pattern PATTERN_TIME = Pattern.compile("(?<hour>\\d{1,2})"
            + "(?:[.:](?<minute>\\d{2}))?(?<ampm>am|pm)");

    private final LocalTime referenceTime;

    public TimeParser(LocalTime referenceTime) {
        this.referenceTime = referenceTime;
    }

    public TimeParser() {
        this(LocalTime.now());
    }

    public LocalTime getReferenceTime() {
        return referenceTime;
    }

    @Override
    public LocalTime parse(String str) throws ParseException {
        final Matcher matcher = PATTERN_TIME.matcher(str.trim());
        if (!matcher.matches()) {
            throw new ParseException("invalid time format", SubstringRange.of(str));
        }

        int hour = Integer.parseInt(matcher.group("hour"));
        if (hour > 12) {
            throw new ParseException("invalid hour: " + hour,
                                     new SubstringRange(matcher.start("hour"), matcher.end("hour")));
        } else if (hour == 12) {
            hour = 0;
        }

        final int minute = matcher.group("minute") != null ? Integer.parseInt(matcher.group("minute")) : 0;
        if (minute >= 60) {
            assert matcher.group("minute") != null;
            throw new ParseException("invalid minute: " + minute,
                                     new SubstringRange(matcher.start("minute"), matcher.end("minute")));
        }

        final boolean isPM = matcher.group("ampm").equals("pm");
        return LocalTime.of(hour + (isPM ? 12 : 0), minute);
    }

    /**
     * Returns the string representation of a LocalTime. This string can be parsed by {@link #parse} to
     * get back a LocalTime with the same hour and minute.
     */
    public String format(LocalTime time) {
        final StringBuilder sb = new StringBuilder();
        int hour = time.getHour() % 12 == 0 ? 12 : (time.getHour() % 12);
        sb.append(hour);
        if (time.getMinute() != 0) {
            sb.append(":").append(String.format("%02d", time.getMinute()));
        }
        sb.append(time.getHour() >= 12 ? "pm" : "am");
        return sb.toString();
    }

}
```
###### src/main/java/seedu/address/logic/parser/SetDataDirectoryParser.java

``` java
/**
 * Parser for the "setdatadir" command.
 */
public class SetDataDirectoryParser implements Parser<SetDataDirectoryCommand> {

    private final RestArgument<File> newDirArg = new RestArgument<>("NEW_DIRECTORY", new FileParser());
    private final CommandLineParser cmdParser = new CommandLineParser().addArgument(newDirArg);

    @Override
    public SetDataDirectoryCommand parse(String str) throws ParseException {
        cmdParser.parse(str);
        return new SetDataDirectoryCommand(newDirArg.getValue());
    }

    @Override
    public List<String> autocomplete(ReadOnlyModel model, String input, int pos) {
        return cmdParser.autocomplete(model, input, pos);
    }

}
```
###### src/main/java/seedu/address/logic/parser/AddFloatingTaskParser.java

``` java
public class AddFloatingTaskParser implements Parser<AddFloatingTaskCommand> {

    private final Argument<Name> nameArg = new Argument<>("NAME", new NameParser());
    private final OptionalFlag<Priority> priorityFlag = new OptionalFlag<>("p-", "PRIORITY", new PriorityParser());
    private final CommandLineParser cmdParser = new CommandLineParser()
                                                    .addArgument(nameArg)
                                                    .putFlag(priorityFlag);

    @Override
    public AddFloatingTaskCommand parse(String str) throws ParseException {
        cmdParser.parse(str);

        final FloatingTask toAdd;
        try {
            toAdd = new FloatingTask(nameArg.getValue(),
                                     priorityFlag.getValue().orElse(new Priority("0")));
        } catch (IllegalValueException e) {
            throw new AssertionError("Should not happen", e);
        }
        return new AddFloatingTaskCommand(toAdd);
    }

}
```
###### src/main/java/seedu/address/logic/parser/SubcommandParser.java

``` java
/**
 * A parser that implements subcommands (selection of parser based on first word of input)
 */
public class SubcommandParser implements Parser<Command> {

    private static final String MSG_NO_COMMAND = "No command name given";

    private static final String MSG_UNKNOWN_COMMAND = "Unknown command: %s";

    private Map<String, Parser<? extends Command>> parsers = new HashMap<>();

    public SubcommandParser putSubcommand(String name, Parser<? extends Command> parser) {
        parsers.put(name, parser);
        return this;
    }

    @Override
    public Command parse(String str) throws ParseException {
        final CommandLineScanner scanner = new CommandLineScanner(str);
        Optional<CommandLineScanner.Argument> subcommandArg = scanner.nextArgument();
        if (!subcommandArg.isPresent()) {
            throw new ParseException(MSG_NO_COMMAND, SubstringRange.of(str));
        }
        if (!parsers.containsKey(subcommandArg.get().value)) {
            throw new ParseException(String.format(MSG_UNKNOWN_COMMAND, subcommandArg.get().value),
                                     subcommandArg.get().range);
        }
        Parser<? extends Command> parser = parsers.get(subcommandArg.get().value);
        try {
            return parser.parse(scanner.getRemainingInput());
        } catch (ParseException e) {
            throw new ParseExceptionBuilder(e)
                        .prependMessage(subcommandArg.get().value + ": ")
                        .indentRanges(scanner.getInputPosition())
                        .build();
        }
    }

    @Override
    public List<String> autocomplete(ReadOnlyModel model, String input, int pos) {
        final CommandLineScanner scanner = new CommandLineScanner(input);
        Optional<CommandLineScanner.Argument> subcommandArg = scanner.nextArgument();
        if (!subcommandArg.isPresent()) {
            return autocompleteCommand(model, "");
        } else if (pos - scanner.getInputPosition() <= 0) {
            // Cursor must be at the end of the command word and word must not be quoted
            if (pos != subcommandArg.get().range.getEnd() || subcommandArg.get().quoted) {
                return Collections.emptyList();
            } else {
                return autocompleteCommand(model, subcommandArg.get().value);
            }
        } else if (parsers.containsKey(subcommandArg.get().value)) {
            final Parser<? extends Command> parser = parsers.get(subcommandArg.get().value);
            return parser.autocomplete(model, scanner.getRemainingInput(), pos - scanner.getInputPosition());
        } else {
            return Collections.emptyList(); // invalid command
        }
    }

    private List<String> autocompleteCommand(ReadOnlyModel model, String input) {
        return parsers.keySet().stream()
                .filter(commandWord -> commandWord.startsWith(input))
                .map(commandWord -> commandWord.substring(input.length()))
                .sorted()
                .collect(Collectors.toList());
    }

}
```
###### src/main/java/seedu/address/logic/parser/FileParser.java

``` java
/**
 * Parser that parses a valid file path string.
 */
public class FileParser implements Parser<File> {

    @Override
    public File parse(String str) throws ParseException {
        if (str.isEmpty()) {
            throw new ParseException("path cannot be empty", SubstringRange.of(str));
        }
        try {
            return Paths.get(str).toFile();
        } catch (InvalidPathException e) {
            throw new ParseException(e.getMessage(), e, SubstringRange.of(str));
        }
    }

}
```
###### src/main/java/seedu/address/logic/parser/DeleteDeadlineParser.java

``` java
public class DeleteDeadlineParser implements Parser<DeleteDeadlineCommand> {

    private final Argument<Integer> indexArg = new Argument<>("INDEX", new IndexParser(TaskType.DEADLINE));
    private final CommandLineParser cmdParser = new CommandLineParser()
                                                    .addArgument(indexArg);

    @Override
    public DeleteDeadlineCommand parse(String str) throws ParseException {
        cmdParser.parse(str);
        return new DeleteDeadlineCommand(indexArg.getValue());
    }

}
```
###### src/main/java/seedu/address/logic/commands/AddFloatingTaskCommand.java

``` java
/**
 * Adds an event task to the task book.
 */
public class AddFloatingTaskCommand implements AddTaskCommand {

    private static final String MESSAGE_SUCCESS = "Added new floating task \"%s\", with priority %s.";

    private final FloatingTask floatingTask;

    public AddFloatingTaskCommand(FloatingTask floatingTask) {
        this.floatingTask = floatingTask;
    }

```
###### src/main/java/seedu/address/logic/commands/EditEventCommand.java

``` java
public class EditEventCommand implements Command {

    private static final String MESSAGE_EDIT_TASK_SUCCESS = "Edited event task "
                                                            + TaskType.EVENT.getPrefixString() + "%d.";

    private final int targetIndex;
    private final Optional<Name> newName;
    private final Optional<LocalDate> newStartDate;
    private final Optional<LocalTime> newStartTime;
    private final Optional<LocalDate> newEndDate;
    private final Optional<LocalTime> newEndTime;

    public EditEventCommand(int targetIndex, Optional<Name> newName, Optional<LocalDate> newStartDate,
                            Optional<LocalTime> newStartTime, Optional<LocalDate> newEndDate,
                            Optional<LocalTime> newEndTime) {
        this.targetIndex = targetIndex;
        this.newName = newName;
        this.newStartDate = newStartDate;
        this.newStartTime = newStartTime;
        this.newEndDate = newEndDate;
        this.newEndTime = newEndTime;
    }

    public int getTargetIndex() {
        return targetIndex;
    }

    public Optional<LocalDate> getNewStartDate() {
        return newStartDate;
    }

    public Optional<LocalTime> getNewStartTime() {
        return newStartTime;
    }

    public Optional<LocalDate> getNewEndDate() {
        return newEndDate;
    }

    public Optional<LocalTime> getNewEndTime() {
        return newEndTime;
    }

    @Override
    public CommandResult execute(Model model) throws CommandException {
        EventTask oldEventTask;
        try {
            oldEventTask = model.getEventTask(targetIndex);
        } catch (IllegalValueException e) {
            throw new CommandException(e);
        }

        EventTask newEventTask;
        try {
            newEventTask = new EventTask(
                    newName.orElse(oldEventTask.getName()),
                    LocalDateTime.of(
                            newStartDate.orElse(oldEventTask.getStart().toLocalDate()),
                            newStartTime.orElse(oldEventTask.getStart().toLocalTime())
                    ),
                    LocalDateTime.of(
                            newEndDate.orElse(oldEventTask.getEnd().toLocalDate()),
                            newEndTime.orElse(oldEventTask.getEnd().toLocalTime())
                    )
            );
        } catch (IllegalValueException e) {
            throw new CommandException(e);
        }

        try {
            model.setEventTask(targetIndex, newEventTask);
        } catch (IllegalValueException e) {
            throw new AssertionError("The target event cannot be missing", e);
        }

        return new CommandResult(String.format(MESSAGE_EDIT_TASK_SUCCESS, targetIndex));
    }

}
```
###### src/main/java/seedu/address/logic/commands/AddEventCommand.java

``` java
/**
 * Adds an event task to the task book.
 */
public class AddEventCommand implements AddTaskCommand {

    private static final String MESSAGE_SUCCESS = "Added new event task \"%s\", from \"%s\" to \"%s\".";

    private final EventTask eventTask;

    public AddEventCommand(EventTask eventTask) {
        this.eventTask = eventTask;
    }

```
###### src/main/java/seedu/address/logic/commands/AddEventCommand.java

``` java
    @Override
    public CommandResult execute(Model model) {
        assert model != null;
        model.addEventTask(eventTask);
        return new CommandResult(String.format(MESSAGE_SUCCESS, eventTask.getName(),
                                               StringUtil.localDateTimeToPrettyString(eventTask.getStart()),
                                               StringUtil.localDateTimeToPrettyString(eventTask.getEnd())));
    }

}
```
###### src/main/java/seedu/address/logic/commands/SetDataDirectoryCommand.java

``` java
/**
 * Command that changes the directory where application data is stored.
 */
public class SetDataDirectoryCommand implements Command {

    private static final String MESSAGE_CHANGE = "Data directory changed to: %s";

    private final File newDir;

    public SetDataDirectoryCommand(File newDir) {
        assert newDir.isAbsolute();
        this.newDir = newDir;
    }

    @Override
    public CommandResult execute(Model model) {
        final File newTaskBookFile = new File(newDir, "taskbook.json");
        model.setTaskBookFilePath(newTaskBookFile.getPath());
        model.setTaskSelect(Optional.empty());
        return new CommandResult(String.format(MESSAGE_CHANGE, newDir.getAbsolutePath()));
    }

}
```
###### src/main/java/seedu/address/logic/commands/ExitCommand.java

``` java
/**
 * Requests to terminate the application.
 */
public class ExitCommand implements Command {

    @Override
    public CommandResult execute(Model model) {
        return new Result();
    }

    private static class Result extends CommandResult implements ExitCommandResult {
        private static final String MSG_EXIT = "Exiting as requested...";

        private Result() {
            super(MSG_EXIT);
        }
    }

}
```
###### src/main/java/seedu/address/logic/commands/MarkFloatingTaskUnfinishedCommand.java

``` java
public class MarkFloatingTaskUnfinishedCommand implements Command {

    private static final String MSG_SUCCESS = "Floating task " + TaskType.FLOAT.getPrefixString()
                                                + "%s unfinished.";

    private final int targetIndex;

    public MarkFloatingTaskUnfinishedCommand(int targetIndex) {
        this.targetIndex = targetIndex;
    }

    public int getTargetIndex() {
        return targetIndex;
    }

    @Override
    public CommandResult execute(Model model) throws CommandException {
        assert model != null;
        final FloatingTask oldFloatingTask;
        try {
            oldFloatingTask = model.getFloatingTask(targetIndex);
        } catch (IllegalValueException e) {
            throw new CommandException(e);
        }

        final FloatingTask finishedFloatingTask = new FloatingTaskBuilder(oldFloatingTask)
                                                    .setFinished(false)
                                                    .build();
        try {
            model.setFloatingTask(targetIndex, finishedFloatingTask);
        } catch (IllegalValueException e) {
            throw new AssertionError("The target floating task cannot be missing", e);
        }

        return new CommandResult(String.format(MSG_SUCCESS, targetIndex));
    }

}
```
###### src/main/java/seedu/address/logic/commands/CommandException.java

``` java
/**
 * Indicates an error which occurred while executing a {@link Command}.
 */
public class CommandException extends Exception {

    /**
     * Constructs a new exception with the specified detail message.
     */
    public CommandException(String message) {
        super(message);
    }

    /**
     * Constructs a new exception with the specified cause and the cause's detail message.
     */
    public CommandException(Throwable cause) {
        super(cause.getMessage(), cause);
    }

    /**
     * Constructs a new exception with the specified detail message and cause.
     */
    public CommandException(String message, Throwable cause) {
        super(message, cause);
    }

}
```
###### src/main/java/seedu/address/logic/commands/EditFloatingTaskCommand.java

``` java
public class EditFloatingTaskCommand implements Command {

    private static final String MESSAGE_EDIT_TASK_SUCCESS = "Edited floating task "
                                                           + TaskType.FLOAT.getPrefixString() + "%d.";

    private final int targetIndex;
    private final Optional<Name> newName;
    private final Optional<Priority> newPriority;

    public EditFloatingTaskCommand(int targetIndex, Optional<Name> newName, Optional<Priority> newPriority) {
        this.targetIndex = targetIndex;
        this.newName = newName;
        this.newPriority = newPriority;
    }

```
###### src/main/java/seedu/address/logic/commands/EditFloatingTaskCommand.java

``` java
    public Optional<Name> getNewName() {
        return newName;
    }

    public Optional<Priority> getNewPriority() {
        return newPriority;
    }

```
###### src/main/java/seedu/address/logic/commands/HelpCommand.java

``` java
    private static class Result extends CommandResult implements HelpCommandResult {
        private static final String MSG_EXIT = "Opening help window...";

        private Result() {
            super(MSG_EXIT);
        }
    }

}
```
###### src/main/java/seedu/address/logic/commands/AddTaskCommand.java

``` java
/**
 * Adds an event task to the task book.
 */
public interface AddTaskCommand extends Command {

    String COMMAND_WORD = "add";

    String MESSAGE_USAGE = COMMAND_WORD + ": Adds an task to the TaskTracker.\n";

    String MESSAGE_SUCCESS = "New task added: %1$s";

    Task getTask();

}
```
###### src/main/java/seedu/address/logic/commands/ListCommand.java

``` java
/**
 * Lists all tasks that matches a certain {@link TaskPredicate}
 */
public class ListCommand implements Command {

    private static final String MSG_LIST_ALL = "Listing all tasks.";
    private static final String MSG_LIST_FILTER = "Listing all tasks matching filter: %s";

    private final TaskPredicate taskPredicate;

    public ListCommand(TaskPredicate taskPredicate) {
        this.taskPredicate = taskPredicate;
    }

    @Override
    public CommandResult execute(Model model) {
        assert model != null;
        model.setTaskPredicate(taskPredicate);
        return new CommandResult(taskPredicate != null ? String.format(MSG_LIST_FILTER, taskPredicate.toHumanReadableString())
                                                         : MSG_LIST_ALL);
    }

}
```
###### src/main/java/seedu/address/logic/commands/MarkDeadlineUnfinishedCommand.java

``` java
public class MarkDeadlineUnfinishedCommand implements Command {

    private static final String MSG_SUCCESS = "Deadline task " + TaskType.DEADLINE.getPrefixString()
                                                + "%s unfinished.";

    private final int targetIndex;

    public MarkDeadlineUnfinishedCommand(int targetIndex) {
        this.targetIndex = targetIndex;
    }

    public int getTargetIndex() {
        return targetIndex;
    }

    @Override
    public CommandResult execute(Model model) throws CommandException {
        assert model != null;
        final DeadlineTask oldDeadlineTask;
        try {
            oldDeadlineTask = model.getDeadlineTask(targetIndex);
        } catch (IllegalValueException e) {
            throw new CommandException(e);
        }

        final DeadlineTask finishedDeadlineTask = new DeadlineTaskBuilder(oldDeadlineTask)
                                                    .setFinished(false)
                                                    .build();

        try {
            model.setDeadlineTask(targetIndex, finishedDeadlineTask);
        } catch (IllegalValueException e) {
            throw new AssertionError("The target deadline cannot be missing", e);
        }

        return new CommandResult(String.format(MSG_SUCCESS, targetIndex));
    }

}
```
###### src/main/java/seedu/address/logic/commands/DeleteEventCommand.java

``` java
public class DeleteEventCommand implements Command {

    public static final String MESSAGE_DELETE_TASK_SUCCESS = "Deleted event "
                                                             + TaskType.EVENT.getPrefixString()
                                                             + "%d. %s";

    private final int targetIndex;

    public DeleteEventCommand(int targetIndex) {
        this.targetIndex = targetIndex;
    }

    @Override
    public CommandResult execute(Model model) throws CommandException {
        try {
            final EventTask deletedTask = model.removeEventTask(targetIndex);
            return new CommandResult(String.format(MESSAGE_DELETE_TASK_SUCCESS, targetIndex, deletedTask.getName()));
        } catch (IllegalValueException e) {
            throw new CommandException(e);
        }
    }

}
```
###### src/main/java/seedu/address/logic/commands/AddDeadlineCommand.java

``` java
/**
 * Adds an deadline task to the task book.
 */
public class AddDeadlineCommand implements AddTaskCommand {

    private static final String MESSAGE_SUCCESS = "Added new deadline task \"%s\", due on \"%s\".";

    private final DeadlineTask deadlineTask;

    public AddDeadlineCommand(DeadlineTask deadlineTask) {
        this.deadlineTask = deadlineTask;
    }

```
###### src/main/java/seedu/address/model/filter/TaskPredicate.java

``` java
/**
 * Represents a predicate used to filter {@link FloatingTask}, {@link DeadlineTask} and {@link EventTask}.
 */
public interface TaskPredicate {

    /**
     * Evaluates the predicate on the given {@link FloatingTask}.
     * @returns true if the input floating task matches the predicate, otherwise false.
     */
    boolean test(FloatingTask floatingTask);

    /**
     * Evaluates the predicate on the given {@link DeadlineTask}
     * @returns true if the input deadline task matches the predicate, otherwise false.
     */
    boolean test(DeadlineTask deadlineTask);

    /**
     * Evaluates the predicate on the given {@link EventTask}
     * @returns true if the input event task matches the predicate, otherwise false.
     */
    boolean test(EventTask eventTask);

    /**
     * Returns a human-readable explanation of the predicate. This explanation must read correctly when
     * used as follows: "Filtering by: XX", where "XX" is the return value of this method.
     */
    String toHumanReadableString();

}
```
###### src/main/java/seedu/address/model/filter/TaskNameContainsKeywordsPredicate.java

``` java
public class TaskNameContainsKeywordsPredicate implements TaskPredicate {

    private final HashSet<String> keywords;

    public TaskNameContainsKeywordsPredicate(Set<String> keywords) {
        assert !keywords.isEmpty();
        this.keywords = new HashSet<>(keywords);
    }

    private boolean testTask(Task task) {
        return keywords.stream()
                        .filter(keyword -> StringUtil.containsIgnoreCase(task.getName().toString(), keyword))
                        .findAny()
                        .isPresent();
    }

    @Override
    public boolean test(FloatingTask floatingTask) {
        return testTask(floatingTask);
    }

    @Override
    public boolean test(DeadlineTask deadlineTask) {
        return testTask(deadlineTask);
    }

    @Override
    public boolean test(EventTask eventTask) {
        return testTask(eventTask);
    }

    @Override
    public String toHumanReadableString() {
        return keywords.stream()
                    .collect(Collectors.joining(", ", "name contains any of keywords: ", ""));
    }

}
```
###### src/main/java/seedu/address/model/filter/TaskFinishedPredicate.java

``` java
/**
 * A predicate that returns true if the {@link Task} is finished.
 * <p><ul>
 * <li> Floating tasks are finished when {@link FloatingTask#isFinished()} returns true.
 * <li> Deadline tasks are finished when {@link DeadlineTask#isFinished()} returns true.
 * <li> Event tasks are finished when their end time is before the reference datetime.
 * </ul>
 */
public class TaskFinishedPredicate implements TaskPredicate {
    private final LocalDateTime referenceDateTime;

    public TaskFinishedPredicate(LocalDateTime referenceDateTime) {
        assert referenceDateTime != null;
        this.referenceDateTime = referenceDateTime;
    }

    public LocalDateTime getReferenceDateTime() {
        return referenceDateTime;
    }

    @Override
    public boolean test(FloatingTask floatingTask) {
        return floatingTask.isFinished();
    }

    @Override
    public boolean test(DeadlineTask deadlineTask) {
        return deadlineTask.isFinished();
    }

    @Override
    public boolean test(EventTask eventTask) {
        return eventTask.getEnd().isBefore(referenceDateTime);
    }

    @Override
    public String toHumanReadableString() {
        return "finished tasks";
    }

    @Override
    public boolean equals(Object other) {
        return other == this
               || (other instanceof TaskFinishedPredicate
               && referenceDateTime.equals(((TaskFinishedPredicate)other).getReferenceDateTime()));
    }

}
```
###### src/main/java/seedu/address/model/filter/TaskUnfinishedPredicate.java

``` java
/**
 * A predicate that returns true if the {@link Task} is not finished yet.
 * <p><ul>
 * <li> Floating tasks are finished when {@link FloatingTask#isFinished()} returns false.
 * <li> Deadline tasks are finished when {@link DeadlineTask#isFinished()} returns false.
 * <li> Event tasks are finished when their end time is not before the reference datetime.
 * </ul>
 */
public class TaskUnfinishedPredicate implements TaskPredicate {
    private final LocalDateTime referenceDateTime;

    public TaskUnfinishedPredicate(LocalDateTime referenceDateTime) {
        assert referenceDateTime != null;
        this.referenceDateTime = referenceDateTime;
    }

    public LocalDateTime getReferenceDateTime() {
        return referenceDateTime;
    }

    @Override
    public boolean test(FloatingTask floatingTask) {
        return !floatingTask.isFinished();
    }

    @Override
    public boolean test(DeadlineTask deadlineTask) {
        return !deadlineTask.isFinished();
    }

    @Override
    public boolean test(EventTask eventTask) {
        return !eventTask.getEnd().isBefore(referenceDateTime);
    }

    @Override
    public String toHumanReadableString() {
        return "unfinished tasks";
    }

    @Override
    public boolean equals(Object other) {
        return other == this
               || (other instanceof TaskUnfinishedPredicate
               && referenceDateTime.equals(((TaskUnfinishedPredicate)other).getReferenceDateTime()));
    }

}
```
###### src/main/java/seedu/address/model/compare/FloatingTaskPriorityComparator.java

``` java
/**
 * Compares two floating tasks in the order of:
 * 1. Priority (reverse order)
 * 2. Name
 */
public class FloatingTaskPriorityComparator implements Comparator<FloatingTask> {

    @Override
    public int compare(FloatingTask a, FloatingTask b) {
        return ComparisonChain.start()
                .compare(b.getPriority().toInteger(), a.getPriority().toInteger())
                .compare(a.getName().toString(), b.getName().toString())
                .result();
    }

}
```
###### src/main/java/seedu/address/model/compare/EventTaskStartEndComparator.java

``` java
/**
 * Compares two event tasks in the following order:
 * 1. Start datetime
 * 2. End datetime
 * 3. Name
 */
public class EventTaskStartEndComparator implements Comparator<EventTask> {

    @Override
    public int compare(EventTask a, EventTask b) {
        return ComparisonChain.start()
                .compare(a.getStart(), b.getStart())
                .compare(a.getEnd(), b.getEnd())
                .compare(a.getName().toString(), b.getName().toString())
                .result();
    }

}
```
###### src/main/java/seedu/address/model/compare/DeadlineTaskDueComparator.java

``` java
/**
 * Compares two DeadlineTasks in the order of
 * 1. Due date
 * 2. Name
 */
public class DeadlineTaskDueComparator implements Comparator<DeadlineTask> {

    @Override
    public int compare(DeadlineTask a, DeadlineTask b) {
        return ComparisonChain.start()
                .compare(a.getDue(), b.getDue())
                .compare(a.getName().toString(), b.getName().toString())
                .result();
    }

}
```
###### src/main/java/seedu/address/model/TaskBook.java

``` java
    /**
     * Tasks are copied into this TaskBook.
     */
    public TaskBook(ReadOnlyTaskBook toBeCopied) {
        this();
        resetData(toBeCopied);
    }

    /**
     * Replaces the contents of this TaskBook with {@code newData}.
     */
    public void resetData(ReadOnlyTaskBook newData) {
        setFloatingTasks(newData.getFloatingTasks());
        setDeadlineTasks(newData.getDeadlineTasks());
        setEventTasks(newData.getEventTasks());
    }

    //// floating task operations

```
###### src/main/java/seedu/address/model/TaskBook.java

``` java
    public void setEventTasks(Collection<EventTask> eventTasks) {
        this.eventTasks.setAll(eventTasks);
    }

    public void addEventTask(EventTask eventTask) {
        eventTasks.add(eventTask);
    }

```
###### src/main/java/seedu/address/model/TaskBook.java

``` java
    public void setEventTask(int index, EventTask newEventTask) {
        eventTasks.set(index, newEventTask);
    }

    //// util methods

    @Override
    public String toString() {
        return MoreObjects.toStringHelper(this)
            .add("floatingTasks", floatingTasks)
            .add("deadlineTasks", deadlineTasks)
            .add("eventTasks", eventTasks)
            .toString();
    }

```
###### src/main/java/seedu/address/model/ReadOnlyModel.java

``` java
/**
 * Read only view of a model component.
 */
public interface ReadOnlyModel {

    //// Config

    /** Returns current config as a read-only view */
    ReadOnlyConfig getConfig();

    /** Returns configured task book file path. */
    String getTaskBookFilePath();

    //// Task Book

    /** Returns the TaskBook */
    ReadOnlyTaskBook getTaskBook();

    //// Task Select

    /** Returns the task being selected, if any. */
    Optional<TaskSelect> getTaskSelect();

    //// Task Filtering

    /** Returns the {@link TaskPredicate} used to filter tasks as a read-only property. */
    ReadOnlyProperty<TaskPredicate> taskPredicateProperty();

    /** Returns the {@link TaskPredicate} used to filter tasks. */
    TaskPredicate getTaskPredicate();

    //// Floating Tasks

    /** Retrieves the given floating task given its working index. */
    FloatingTask getFloatingTask(int workingIndex) throws IllegalValueException;

```
###### src/main/java/seedu/address/model/ReadOnlyModel.java

``` java
    /** Returns the comparator used to sort the floating task list. */
    Comparator<? super FloatingTask> getFloatingTaskComparator();

    //// Deadline Tasks

```
###### src/main/java/seedu/address/model/ReadOnlyModel.java

``` java
    /** Returns the comparator used to sort the deadline task list. */
    Comparator<? super DeadlineTask> getDeadlineTaskComparator();

    //// Event Tasks

    /** Retrieves the given event task with the specified working index */
    EventTask getEventTask(int workingIndex) throws IllegalValueException;

    /** Returns the filtered event task list as an unmodifiable ObservableList */
    ObservableList<IndexedItem<EventTask>> getEventTaskList();

    /** Returns the comparator used to sort the event task list. */
    Comparator<? super EventTask> getEventTaskComparator();

    //// Undo/Redo

```
###### src/main/java/seedu/address/model/IndexedItem.java

``` java
/**
 * Represents a list item that has an associated working index.
 */
public interface IndexedItem<E> {
    /** Returns the item's working index. This index will remain the same as long as the list is not repopulated. */
    int getWorkingIndex();

    /** Returns the item value. */
    E getItem();
}
```
###### src/main/java/seedu/address/model/config/ReadOnlyConfig.java

``` java
/**
 * Unmodifiable view of a {@link Config} object.
 */
public interface ReadOnlyConfig {

    /**
     * Returns the configured log level.
     */
    Level getLogLevel();

    /**
     * Returns the configured task book file path as a read only property.
     */
    ReadOnlyProperty<String> taskBookFilePathProperty();

    /**
     * Returns the configured task book file path.
     */
    String getTaskBookFilePath();

}
```
###### src/main/java/seedu/address/model/config/Config.java

``` java
    public Config(ReadOnlyConfig config) {
        this();
        resetData(config);
    }

    public void resetData(ReadOnlyConfig config) {
        setLogLevel(config.getLogLevel());
        setTaskBookFilePath(config.getTaskBookFilePath());
    }

```
###### src/main/java/seedu/address/model/config/Config.java

``` java
    @Override
    public ReadOnlyProperty<String> taskBookFilePathProperty() {
        return taskBookFilePath;
    }

    @Override
    public String getTaskBookFilePath() {
        return taskBookFilePath.get();
    }

    public void setTaskBookFilePath(String taskBookFilePath) {
        assert taskBookFilePath != null;
        this.taskBookFilePath.set(taskBookFilePath);
    }

```
###### src/main/java/seedu/address/model/config/WindowRect.java

``` java
/**
 * Stores a UI Window's dimensions.
 */
public class WindowRect {
    private static final double DEFAULT_WIDTH = 600;
    private static final double DEFAULT_HEIGHT = 740;

    private final double width;
    private final double height;
    private final Optional<Double> x;
    private final Optional<Double> y;

    public WindowRect(double width, double height, Optional<Double> x, Optional<Double> y) {
        this.width = width;
        this.height = height;
        this.x = x;
        this.y = y;
    }

    public WindowRect(double width, double height, double x, double y) {
        this(width, height, Optional.of(x), Optional.of(y));
    }

    public WindowRect(double width, double height) {
        this(width, height, Optional.empty(), Optional.empty());
    }

    public WindowRect() {
        this(DEFAULT_WIDTH, DEFAULT_HEIGHT);
    }

    public double getWidth() {
        return width;
    }

    public double getHeight() {
        return height;
    }

    public Optional<Double> getX() {
        return x;
    }

    public Optional<Double> getY() {
        return y;
    }

    @Override
    public boolean equals(Object other) {
        return other == this
               || (other instanceof WindowRect
               && width == ((WindowRect)other).width
               && height == ((WindowRect)other).height
               && x.equals(((WindowRect)other).x)
               && y.equals(((WindowRect)other).y)
               );
    }

    @Override
    public int hashCode() {
        return Objects.hash(width, height, x, y);
    }

    @Override
    public String toString() {
        return MoreObjects.toStringHelper(this)
                .add("width", width)
                .add("height", height)
                .add("x", x)
                .add("y", y)
                .toString();
    }
}
```
###### src/main/java/seedu/address/model/task/EventTask.java

``` java
public final class EventTask extends Task {

    private static final String FMT_STRING = "EventTask[name=%s, duration=%s]";

    private final LocalDateTimeDuration duration;

    public EventTask(Name name, LocalDateTimeDuration duration) {
        super(name);
        assert duration != null;
        this.duration = duration;
    }

    public EventTask(String name, LocalDateTimeDuration duration) throws IllegalValueException {
        this(new Name(name), duration);
    }

    public EventTask(Name name, LocalDateTime start, LocalDateTime end) throws IllegalValueException {
        this(name, new LocalDateTimeDuration(start, end));
    }

    public EventTask(String name, LocalDateTime start, LocalDateTime end) throws IllegalValueException {
        this(name, new LocalDateTimeDuration(start, end));
    }

    public LocalDateTimeDuration getDuration() {
        return duration;
    }

    public LocalDateTime getStart() {
        return duration.getStart();
    }

    public LocalDateTime getEnd() {
        return duration.getEnd();
    }

    @Override
    public boolean equals(Object other) {
        return other == this
               || (other instanceof EventTask
               && name.equals(((EventTask)other).name)
               && duration.equals(((EventTask)other).duration));
    }

    @Override
    public int hashCode() {
        return Objects.hash(name, duration);
    }

    @Override
    public String toString() {
        return String.format(FMT_STRING, name, duration);
    }

}
```
###### src/main/java/seedu/address/model/task/DeadlineTaskBuilder.java

``` java
/**
 * A builder for {@link DeadlineTask}
 */
public class DeadlineTaskBuilder {
    private Name name;
    private LocalDate dueDate;
    private LocalTime dueTime;
    private boolean finished;

    public DeadlineTaskBuilder(Name name, LocalDateTime due, boolean finished) {
        setName(name);
        setDue(due);
        setFinished(finished);
    }

    public DeadlineTaskBuilder(DeadlineTask template) {
        this(template.getName(), template.getDue(), template.isFinished());
    }

    public DeadlineTaskBuilder setName(Name name) {
        assert name != null;
        this.name = name;
        return this;
    }

    public DeadlineTaskBuilder setName(String name) throws IllegalValueException {
        this.name = new Name(name);
        return this;
    }

    public DeadlineTaskBuilder setDue(LocalDateTime due) {
        assert due != null;
        setDueDate(due.toLocalDate());
        setDueTime(due.toLocalTime());
        return this;
    }

    public DeadlineTaskBuilder setDueDate(LocalDate dueDate) {
        assert dueDate != null;
        this.dueDate = dueDate;
        return this;
    }

    public DeadlineTaskBuilder setDueTime(LocalTime dueTime) {
        assert dueTime != null;
        this.dueTime = dueTime;
        return this;
    }

    public DeadlineTaskBuilder setFinished(boolean finished) {
        this.finished = finished;
        return this;
    }

    public DeadlineTask build() {
        return new DeadlineTask(name, LocalDateTime.of(dueDate, dueTime), finished);
    }
}
```
###### src/main/java/seedu/address/model/task/Task.java

``` java
    public Name getName() {
        return name;
    }
}
```
###### src/main/java/seedu/address/model/task/TaskSelect.java

``` java
/**
 * Represents a task that is being selected.
 * Guarantees: immutable, a POJO.
 */
public class TaskSelect {
    /** The type of task being selected. */
    private final TaskType taskType;

    /** The working index of the task being selected. */
    private final int workingIndex;

    public TaskSelect(TaskType taskType, int workingIndex) {
        this.taskType = taskType;
        this.workingIndex = workingIndex;
    }

    public TaskType getTaskType() {
        return taskType;
    }

    public int getWorkingIndex() {
        return workingIndex;
    }

    @Override
    public boolean equals(Object other) {
        return other == this
                || (other instanceof TaskSelect
                && taskType == ((TaskSelect)other).taskType
                && workingIndex == ((TaskSelect)other).workingIndex);
    }

    @Override
    public String toString() {
        return MoreObjects.toStringHelper(this)
                .add("taskType", taskType)
                .add("workingIndex", workingIndex)
                .toString();
    }

}
```
###### src/main/java/seedu/address/model/task/FloatingTaskBuilder.java

``` java
/**
 * A builder for {@link FloatingTask}
 */
public class FloatingTaskBuilder {
    private Name name;
    private Priority priority;
    private boolean finished;

    public FloatingTaskBuilder(Name name, Priority priority, boolean finished) {
        setName(name);
        setPriority(priority);
        setFinished(finished);
    }

    public FloatingTaskBuilder(FloatingTask template) {
        this(template.getName(), template.getPriority(), template.isFinished());
    }

    public FloatingTaskBuilder setName(Name name) {
        assert name != null;
        this.name = name;
        return this;
    }

    public FloatingTaskBuilder setName(String name) throws IllegalValueException {
        this.name = new Name(name);
        return this;
    }

    public FloatingTaskBuilder setPriority(Priority priority) {
        assert priority != null;
        this.priority = priority;
        return this;
    }

    public FloatingTaskBuilder setFinished(boolean finished) {
        this.finished = finished;
        return this;
    }

    public FloatingTask build() {
        return new FloatingTask(name, priority, finished);
    }
}
```
###### src/main/java/seedu/address/model/task/TaskType.java

``` java
/**
 * Enumeration of task types and their associated info.
 */
public enum TaskType {
    FLOAT("f", "floating task"),
    DEADLINE("d", "deadline task"),
    EVENT("e", "event task");

    private final String prefixString;
    private final String name;

    TaskType(String prefixString, String name) {
        this.prefixString = prefixString;
        this.name = name;
    }

    public String getPrefixString() {
        return prefixString;
    }

    public String getName() {
        return name;
    }

}
```
###### src/main/java/seedu/address/model/ModelManager.java

``` java
/**
 * Represents the in-memory model of the address book data.
 * All changes to any model should be synchronized.
 */
public class ModelManager extends ComponentManager implements Model {
    private static final Logger logger = LogsCenter.getLogger(ModelManager.class);

    private final Config config;
    private final WorkingTaskBook workingTaskBook;
    private Optional<TaskSelect> taskSelect = Optional.empty();

    //for undo
    private ArrayList<Commit> commits = new ArrayList<Commit>();
    private int head = -1; //head points to a the current commit which holds the TaskBook displayed by the UI

    /**
     * Initializes a ModelManager with the given config and TaskBook
     * TaskBook and its variables should not be null
     */
    public ModelManager(ReadOnlyConfig config, ReadOnlyTaskBook taskBook) {
        super();
        assert taskBook != null;

        logger.fine("Initializing with config: " + config + " and task book: " + taskBook);

        this.config = new Config(config);
        this.workingTaskBook = new WorkingTaskBook(taskBook);
        recordState("initial commit");
    }

```
###### src/main/java/seedu/address/model/ModelManager.java

``` java
    @Override
    public ReadOnlyConfig getConfig() {
        return config;
    }

    @Override
    public String getTaskBookFilePath() {
        return config.getTaskBookFilePath();
    }

    @Override
    public void setTaskBookFilePath(String filePath) {
        config.setTaskBookFilePath(filePath);
    }

    //// Task Book

    @Override
    public void resetTaskBook(ReadOnlyTaskBook newData) {
        workingTaskBook.resetData(newData);
        setTaskSelect(Optional.empty());
    }

```
###### src/main/java/seedu/address/model/ModelManager.java

``` java
    @Override
    public Optional<TaskSelect> getTaskSelect() {
        return taskSelect;
    }

    @Override
    public void setTaskSelect(Optional<TaskSelect> taskSelect) {
        assert taskSelect != null;
        this.taskSelect = taskSelect;
    }

    //// Task filtering

    @Override
    public ReadOnlyProperty<TaskPredicate> taskPredicateProperty() {
        return workingTaskBook.taskPredicateProperty();
    }

    @Override
    public TaskPredicate getTaskPredicate() {
        return workingTaskBook.getTaskPredicate();
    }

    @Override
    public void setTaskPredicate(TaskPredicate taskFilter) {
        workingTaskBook.setTaskPredicate(taskFilter);
        setTaskSelect(Optional.empty());
    }

    //// Floating tasks

    @Override
    public synchronized int addFloatingTask(FloatingTask floatingTask) {
        final int workingIndex = workingTaskBook.addFloatingTask(floatingTask);
        setTaskSelect(Optional.of(new TaskSelect(TaskType.FLOAT, workingIndex)));
        return workingIndex;
    }

```
###### src/main/java/seedu/address/model/ModelManager.java

``` java
    @Override
    public synchronized FloatingTask removeFloatingTask(int workingIndex) throws IllegalValueException {
        final FloatingTask removedFloating = workingTaskBook.removeFloatingTask(workingIndex);
        if (getTaskSelect().equals(Optional.of(new TaskSelect(TaskType.FLOAT, workingIndex)))) {
            setTaskSelect(Optional.empty());
        }
        return removedFloating;
    }

```
###### src/main/java/seedu/address/model/ModelManager.java

``` java
    @Override
    public Comparator<? super FloatingTask> getFloatingTaskComparator() {
        return workingTaskBook.getFloatingTaskComparator();
    }

    @Override
    public void setFloatingTaskComparator(Comparator<? super FloatingTask> comparator) {
        workingTaskBook.setFloatingTaskComparator(comparator);
        setTaskSelect(Optional.empty());
    }

    //// Deadline tasks

    @Override
    public synchronized int addDeadlineTask(DeadlineTask deadlineTask) {
        final int workingIndex = workingTaskBook.addDeadlineTask(deadlineTask);
        setTaskSelect(Optional.of(new TaskSelect(TaskType.DEADLINE, workingIndex)));
        return workingIndex;
    }

    @Override
    public synchronized DeadlineTask getDeadlineTask(int workingIndex) throws IllegalValueException {
        return workingTaskBook.getDeadlineTask(workingIndex);
    }

    @Override
    public synchronized DeadlineTask removeDeadlineTask(int workingIndex) throws IllegalValueException {
        final DeadlineTask removedDeadline = workingTaskBook.removeDeadlineTask(workingIndex);
        if (getTaskSelect().equals(Optional.of(new TaskSelect(TaskType.DEADLINE, workingIndex)))) {
            setTaskSelect(Optional.empty());
        }
        return removedDeadline;
    }

```
###### src/main/java/seedu/address/model/ModelManager.java

``` java
    @Override
    public synchronized void setDeadlineTask(int workingIndex, DeadlineTask newDeadlineTask)
            throws IllegalValueException {
        workingTaskBook.setDeadlineTask(workingIndex, newDeadlineTask);
        setTaskSelect(Optional.of(new TaskSelect(TaskType.DEADLINE, workingIndex)));
    }

    @Override
    public ObservableList<IndexedItem<DeadlineTask>> getDeadlineTaskList() {
        return workingTaskBook.getDeadlineTaskList();
    }

    @Override
    public Comparator<? super DeadlineTask> getDeadlineTaskComparator() {
        return workingTaskBook.getDeadlineTaskComparator();
    }

    @Override
    public void setDeadlineTaskComparator(Comparator<? super DeadlineTask> comparator) {
        workingTaskBook.setDeadlineTaskComparator(comparator);
        setTaskSelect(Optional.empty());
    }

    //// Event tasks

    @Override
    public synchronized int addEventTask(EventTask eventTask) {
        final int workingIndex = workingTaskBook.addEventTask(eventTask);
        setTaskSelect(Optional.of(new TaskSelect(TaskType.EVENT, workingIndex)));
        return workingIndex;
    }

```
###### src/main/java/seedu/address/model/ModelManager.java

``` java
    @Override
    public synchronized EventTask removeEventTask(int workingIndex) throws IllegalValueException {
        final EventTask removedEvent = workingTaskBook.removeEventTask(workingIndex);
        if (getTaskSelect().equals(Optional.of(new TaskSelect(TaskType.EVENT, workingIndex)))) {
            setTaskSelect(Optional.empty());
        }
        return removedEvent;
    }

```
###### src/main/java/seedu/address/model/ModelManager.java

``` java
    @Override
    public Comparator<? super EventTask> getEventTaskComparator() {
        return workingTaskBook.getEventTaskComparator();
    }

    @Override
    public void setEventTaskComparator(Comparator<? super EventTask> comparator) {
        workingTaskBook.setEventTaskComparator(comparator);
        setTaskSelect(Optional.empty());
    }

    ////undo redo

```
###### src/main/java/seedu/address/model/ModelManager.java

``` java
    @Override
    public Commit recordState(String name) {
        assert name != null;
        //clear redoable, which are the commits above head
        commits.subList(head + 1, commits.size()).clear();
        final Commit newCommit = new Commit(name, workingTaskBook, getConfig(), getTaskSelect());
        commits.add(newCommit);
        head = commits.size() - 1;
        return newCommit;
    }

```
###### src/main/java/seedu/address/model/ModelManager.java

``` java
    private class Commit implements Model.Commit {
        private String name;
        private final WorkingTaskBook workingTaskBook;
        private final Config config;
        private final Optional<TaskSelect> taskSelect;

        private Commit(String name, WorkingTaskBook workingTaskBook, ReadOnlyConfig config,
                       Optional<TaskSelect> taskSelect) {
            this.name = name;
            this.workingTaskBook = new WorkingTaskBook(workingTaskBook);
            this.config = new Config(config);
            this.taskSelect = taskSelect;
        }

        @Override
        public String getName() {
            return name;
        }

        private ReadOnlyTaskBook getTaskBook() {
            return workingTaskBook.getTaskBook();
        }

        @Override
        public boolean equals(Object other) {
            return other == this
                   || (other instanceof Commit
                   && name.equals(((Commit)other).name)
                   && workingTaskBook.equals(((Commit)other).workingTaskBook)
                   );
        }
    }

```
###### src/main/java/seedu/address/model/Model.java

``` java
/**
 * The API of the Model component.
 */
public interface Model extends ReadOnlyModel {

    //// Config

    /** Sets configured task book file path */
    void setTaskBookFilePath(String taskBookFilePath);

    //// Task Book

    /** Clears existing backing task book and replaces with the provided new task book data. */
    void resetTaskBook(ReadOnlyTaskBook newTaskBook);

    //// Task Select

    /** Sets the task being selected. */
    void setTaskSelect(Optional<TaskSelect> taskSelect);

    //// Task Filtering

    /** Sets the {@link TaskPredicate} used to filter tasks. If the filter is null, no filter is applied. */
    void setTaskPredicate(TaskPredicate taskPredicate);

    //// Floating Tasks

    /* Adds the given floating task and returns its working index. */
    int addFloatingTask(FloatingTask floatingTask);

```
###### src/main/java/seedu/address/model/Model.java

``` java
    /** Sets the comparator used to sort the floating task list. */
    void setFloatingTaskComparator(Comparator<? super FloatingTask> comparator);

    //// Deadline Tasks

    /** Adds the given deadline task and returns its working index. */
    int addDeadlineTask(DeadlineTask deadlineTask);

```
###### src/main/java/seedu/address/model/Model.java

``` java
    /** Sets the comparator used to sort the deadline task list. */
    void setDeadlineTaskComparator(Comparator<? super DeadlineTask> comparator);

    //// Event Tasks

    /** Adds the given event task and returns its working index */
    int addEventTask(EventTask eventTask);

    /** Removes the given event task and returns it. */
    EventTask removeEventTask(int workingIndex) throws IllegalValueException;

```
###### src/main/java/seedu/address/model/Model.java

``` java
    /** Replaces the given event task with a new event task */
    void setEventTask(int workingIndex, EventTask newEventTask) throws IllegalValueException;

    /** Sets the comparator used to sort the event task list. */
    void setEventTaskComparator(Comparator<? super EventTask> comparator);

    //// undo/redo

    /**
     * Saves the state of the model as a commit.
     * @param name The name of the commit.
     * @return The new commit.
     */
    Commit recordState(String name);

    /**
     * Redoes the most recently undone commit.
     * @return The commit that was redone.
     * @throws HeadAtBoundaryException if there are no more commits to redo.
     */
    Commit redo() throws HeadAtBoundaryException;

```
###### src/main/java/seedu/address/model/Model.java

``` java
    public interface Commit {
        /** The name of the commit */
        String getName();
    }

}
```
###### src/main/java/seedu/address/model/TaskBookChangeListener.java

``` java
public class TaskBookChangeListener extends ObservableListChangeListener {

    public TaskBookChangeListener(ReadOnlyTaskBook taskBook) {
        super(taskBook.getFloatingTasks(), taskBook.getDeadlineTasks(), taskBook.getEventTasks());
    }

}
```
###### src/main/java/seedu/address/model/WorkingTaskBook.java

``` java
/**
 * A wrapper around a task book that gives each task a stable index, and adds filtering and sorting of the
 * tasks lists.
 *
 * Having a stable index (called the workingIndex) means that when a task is deleted, or is modified such
 * that its position on the sorted list changes, the indexes of the rest of the tasks will still remain the
 * same.
 */
public class WorkingTaskBook {
    public static final Comparator<FloatingTask> DEFAULT_FLOATING_TASK_COMPARATOR = new FloatingTaskPriorityComparator();
    public static final Comparator<DeadlineTask> DEFAULT_DEADLINE_TASK_COMPARATOR = new DeadlineTaskDueComparator();
    public static final Comparator<EventTask> DEFAULT_EVENT_TASK_COMPARATOR = new EventTaskStartEndComparator();

    private final TaskBook taskBook;
    private final WorkingItemList<FloatingTask> workingFloatingTasks;
    private final WorkingItemList<DeadlineTask> workingDeadlineTasks;
    private final WorkingItemList<EventTask> workingEventTasks;
    private final SimpleObjectProperty<TaskPredicate> taskPredicate = new SimpleObjectProperty<>();

    public WorkingTaskBook(ReadOnlyTaskBook taskBook) {
        this.taskBook = new TaskBook(taskBook);
        this.workingFloatingTasks = new WorkingItemList<>(this.taskBook.getFloatingTasks());
        this.workingFloatingTasks.setComparator(DEFAULT_FLOATING_TASK_COMPARATOR);
        this.workingDeadlineTasks = new WorkingItemList<>(this.taskBook.getDeadlineTasks());
        this.workingDeadlineTasks.setComparator(DEFAULT_DEADLINE_TASK_COMPARATOR);
        this.workingEventTasks = new WorkingItemList<>(this.taskBook.getEventTasks());
        this.workingEventTasks.setComparator(DEFAULT_EVENT_TASK_COMPARATOR);
    }

    public WorkingTaskBook(WorkingTaskBook toBeCopied) {
        this(toBeCopied.taskBook);
        resetData(toBeCopied);
    }

    public ReadOnlyTaskBook getTaskBook() {
        return taskBook;
    }

    public void resetData(ReadOnlyTaskBook toBeCopied) {
        taskBook.resetData(toBeCopied);
        workingFloatingTasks.repopulate();
        workingDeadlineTasks.repopulate();
        workingEventTasks.repopulate();
    }

    public void resetData(WorkingTaskBook toBeCopied) {
        this.taskBook.resetData(toBeCopied.taskBook);
        workingFloatingTasks.resetData(toBeCopied.workingFloatingTasks);
        workingDeadlineTasks.resetData(toBeCopied.workingDeadlineTasks);
        workingEventTasks.resetData(toBeCopied.workingEventTasks);
    }

    /**
     * Returns the {@link TaskPredicate} used to filter tasks in the working lists as a read-only property.
     */
    public ReadOnlyProperty<TaskPredicate> taskPredicateProperty() {
        return taskPredicate;
    }

    /**
     * Returns the {@link TaskPredicate} used to filter tasks in the working lists.
     */
    public TaskPredicate getTaskPredicate() {
        return taskPredicate.get();
    }

    /**
     * Sets the {@link TaskPredicate} used to filter tasks in the working lists.
     * If the filter is null, no filter is applied.
     */
    public void setTaskPredicate(TaskPredicate taskPredicate) {
        workingFloatingTasks.setPredicate(taskPredicate != null ? taskPredicate::test : null);
        workingDeadlineTasks.setPredicate(taskPredicate != null ? taskPredicate::test : null);
        workingEventTasks.setPredicate(taskPredicate != null ? taskPredicate::test : null);
        this.taskPredicate.set(taskPredicate);
    }

    /// Floating tasks

    public int addFloatingTask(FloatingTask floatingTask) {
        taskBook.addFloatingTask(floatingTask);
        return workingFloatingTasks.add(floatingTask, taskBook.getFloatingTasks().size() - 1);
    }

    public FloatingTask getFloatingTask(int workingIndex) throws IllegalValueException {
        return workingFloatingTasks.get(workingIndex);
    }

    public FloatingTask removeFloatingTask(int workingIndex) throws IllegalValueException {
        final int sourceIndex = workingFloatingTasks.getSourceIndex(workingIndex);
        final FloatingTask removedFloating = taskBook.removeFloatingTask(sourceIndex);
        workingFloatingTasks.remove(workingIndex);
        return removedFloating;
    }

```
###### src/main/java/seedu/address/model/WorkingTaskBook.java

``` java
    public void setFloatingTask(int workingIndex, FloatingTask newFloatingTask) throws IllegalValueException {
        final int sourceIndex = workingFloatingTasks.getSourceIndex(workingIndex);
        taskBook.setFloatingTask(sourceIndex, newFloatingTask);
        workingFloatingTasks.set(workingIndex, newFloatingTask);
    }

    public ObservableList<IndexedItem<FloatingTask>> getFloatingTaskList() {
        return workingFloatingTasks.getWorkingItemList();
    }

    /**
     * Returns the comparator used to sort floating tasks in the floating task working list.
     */
    public Comparator<? super FloatingTask> getFloatingTaskComparator() {
        return workingFloatingTasks.getComparator();
    }

    /**
     * Sets the comparator used to sort floating tasks in the floating task working list.
     * The comparator must not be null.
     */
    public void setFloatingTaskComparator(Comparator<? super FloatingTask> comparator) {
        workingFloatingTasks.setComparator(comparator);
    }

    /// Deadline tasks

    public int addDeadlineTask(DeadlineTask deadlineTask) {
        taskBook.addDeadlineTask(deadlineTask);
        return workingDeadlineTasks.add(deadlineTask, taskBook.getDeadlineTasks().size() - 1);
    }

    public DeadlineTask getDeadlineTask(int workingIndex) throws IllegalValueException {
        return workingDeadlineTasks.get(workingIndex);
    }

    public DeadlineTask removeDeadlineTask(int workingIndex) throws IllegalValueException {
        final int sourceIndex = workingDeadlineTasks.getSourceIndex(workingIndex);
        final DeadlineTask removedDeadline = taskBook.removeDeadlineTask(sourceIndex);
        workingDeadlineTasks.remove(workingIndex);
        return removedDeadline;
    }

```
###### src/main/java/seedu/address/model/WorkingTaskBook.java

``` java
    public void setDeadlineTask(int workingIndex, DeadlineTask newDeadlineTask) throws IllegalValueException {
        final int sourceIndex = workingDeadlineTasks.getSourceIndex(workingIndex);
        taskBook.setDeadlineTask(sourceIndex, newDeadlineTask);
        workingDeadlineTasks.set(workingIndex, newDeadlineTask);
    }

    public ObservableList<IndexedItem<DeadlineTask>> getDeadlineTaskList() {
        return workingDeadlineTasks.getWorkingItemList();
    }

    /**
     * Returns the comparator used to sort deadline tasks in the deadline task working list.
     */
    public Comparator<? super DeadlineTask> getDeadlineTaskComparator() {
        return workingDeadlineTasks.getComparator();
    }

    /**
     * Sets the comparator used to sort deadline tasks in the deadline task working list.
     * The comparator must not be null.
     */
    public void setDeadlineTaskComparator(Comparator<? super DeadlineTask> comparator) {
        workingDeadlineTasks.setComparator(comparator);
    }

    /// Event tasks

    public int addEventTask(EventTask eventTask) {
        taskBook.addEventTask(eventTask);
        return workingEventTasks.add(eventTask, taskBook.getEventTasks().size() - 1);
    }

    public EventTask getEventTask(int workingIndex) throws IllegalValueException {
        return workingEventTasks.get(workingIndex);
    }

    public EventTask removeEventTask(int workingIndex) throws IllegalValueException {
        final int sourceIndex = workingEventTasks.getSourceIndex(workingIndex);
        final EventTask removedEvent = taskBook.removeEventTask(sourceIndex);
        workingEventTasks.remove(workingIndex);
        return removedEvent;
    }

```
###### src/main/java/seedu/address/model/WorkingTaskBook.java

``` java
    public void setEventTask(int workingIndex, EventTask newEventTask) throws IllegalValueException {
        final int sourceIndex = workingEventTasks.getSourceIndex(workingIndex);
        taskBook.setEventTask(sourceIndex, newEventTask);
        workingEventTasks.set(workingIndex, newEventTask);
    }

    public ObservableList<IndexedItem<EventTask>> getEventTaskList() {
        return workingEventTasks.getWorkingItemList();
    }

    /**
     * Returns the comparator used to stort event tasks in the event task working list.
     */
    public Comparator<? super EventTask> getEventTaskComparator() {
        return workingEventTasks.getComparator();
    }

    /**
     * Sets the comparator used to sort event tasks in the event task working list.
     * The comparator must not be null.
     */
    public void setEventTaskComparator(Comparator<? super EventTask> comparator) {
        workingEventTasks.setComparator(comparator);
    }

    /**
     * Pre-conditions: E is immutable.
     */
    private static class WorkingItemList<E> {
        /**
         * The source list which we will generate the workingItemList from when {@link #repopulate()} is called.
         */
        private final ObservableList<E> sourceList;

        /**
         * Our base internal working item list. To keep indexes stable, items are never deleted from this
         * list unless the list is repopulated.
         */
        private final ObservableList<WorkingItem<E>> workingItemList = FXCollections.observableArrayList();

        /**
         * Filters away the "deleted" items from the workingItemList.
         */
        private final FilteredList<WorkingItem<E>> filteredWorkingItemList = new FilteredList<>(workingItemList, x -> x.item.isPresent());

        /**
         * Sorts the filteredWorkingItemList.
         */
        private final SortedList<WorkingItem<E>> sortedWorkingItemList = new SortedList<>(filteredWorkingItemList);

        /**
         * The comparator used to sort the workingItemList
         */
        private Comparator<? super E> comparator;

        /**
         * The predicate used to filter the sourceList in {@link #repopulate()}.
         */
        private Predicate<? super E> predicate;

        private WorkingItemList(ObservableList<E> sourceList) {
            this.sourceList = sourceList;
            repopulate();
        }

        private void resetData(WorkingItemList<E> toBeCopied) {
            this.workingItemList.setAll(toBeCopied.workingItemList);
            this.sortedWorkingItemList.setComparator(toBeCopied.sortedWorkingItemList.getComparator());
            this.comparator = toBeCopied.comparator;
            this.predicate = toBeCopied.predicate;
        }

        private ObservableList<IndexedItem<E>> getWorkingItemList() {
            return new MappedList<IndexedItem<E>, WorkingItem<E>>(sortedWorkingItemList, x -> x);
        }

        private void setPredicate(Predicate<? super E> predicate) {
            this.predicate = predicate;
            repopulate();
        }

        private Comparator<? super E> getComparator() {
            return comparator;
        }

        private void setComparator(Comparator<? super E> comparator) {
            assert comparator != null;
            sortedWorkingItemList.setComparator((a, b) -> {
                return comparator.compare(a.getItem(), b.getItem());
            });
            this.comparator = comparator;
            repopulate();
        }

        private WorkingItem<E> getWorkingItem(int workingIndex) throws IllegalValueException {
            if (workingIndex < 0 || workingIndex >= workingItemList.size()
                    || !workingItemList.get(workingIndex).item.isPresent()) {
                throw new IllegalValueException("index does not exist: " + workingIndex);
            } else {
                return workingItemList.get(workingIndex);
            }
        }

        private int getSourceIndex(int workingIndex) throws IllegalValueException {
            return getWorkingItem(workingIndex).sourceIndex;
        }

        private E get(int workingIndex) throws IllegalValueException {
            return getWorkingItem(workingIndex).getItem();
        }

        private void set(int workingIndex, E item) throws IllegalValueException {
            final int sourceIndex = getSourceIndex(workingIndex);
            workingItemList.set(workingIndex, new WorkingItem<>(workingIndex, item, sourceIndex));
        }

        int add(E item, int sourceIndex) {
            final int newWorkingIndex = workingItemList.size();
            final WorkingItem<E> newWorkingItem = new WorkingItem<>(newWorkingIndex, item, sourceIndex);
            workingItemList.add(newWorkingItem);
            return newWorkingIndex;
        }

        void remove(int workingIndex) throws IllegalValueException {
            final int deletedSourceIndex = getSourceIndex(workingIndex);
            workingItemList.set(workingIndex, new WorkingItem<>());
            // Adjust mapping of sourceIndexes
            for (WorkingItem<E> item : workingItemList) {
                if (item.sourceIndex > deletedSourceIndex) {
                    item.sourceIndex--;
                }
            }
        }

        void repopulate() {
            ArrayList<WorkingItem<E>> newWorkingList = new ArrayList<>();
            // Filter the list (while taking note of the source index)
            for (int i = 0; i < sourceList.size(); i++) {
                final E item = sourceList.get(i);
                if (predicate != null && !predicate.test(item)) {
                    continue;
                }
                newWorkingList.add(new WorkingItem<>(0, item, i));
            }
            // Sort the list using our current comparator
            if (sortedWorkingItemList.getComparator() != null) {
                Collections.sort(newWorkingList, sortedWorkingItemList.getComparator());
            }
            // Add our 0-index empty value (so that indexes start at one)
            newWorkingList.add(0, new WorkingItem<>());
            // Re-index the list
            for (int i = 1; i < newWorkingList.size(); i++) {
                final WorkingItem<E> src = newWorkingList.get(i);
                newWorkingList.set(i, new WorkingItem<>(i, src.getItem(), src.sourceIndex));
            }
            workingItemList.setAll(newWorkingList);
        }

        int size () {
            return workingItemList.size();
        }
    }

    private static class WorkingItem<E> implements IndexedItem<E> {
        private final int workingIndex;
        private final Optional<E> item;
        private int sourceIndex;

        private WorkingItem(int workingIndex, Optional<E> item, int sourceIndex) {
            this.workingIndex = workingIndex;
            this.item = item;
            this.sourceIndex = sourceIndex;
        }

        private WorkingItem(int workingIndex, E item, int sourceIndex) {
            this(workingIndex, Optional.of(item), sourceIndex);
        }

        private WorkingItem() {
            this(-1, Optional.empty(), -1);
        }

        @Override
        public int getWorkingIndex() {
            return workingIndex;
        }

        @Override
        public E getItem() {
            return item.get();
        }

        @Override
        public String toString() {
            return MoreObjects.toStringHelper(this)
                    .add("workingIndex", workingIndex)
                    .add("item", item)
                    .toString();
        }
    }

}
```
###### src/main/java/seedu/address/ui/DeadlineTaskListCard.java

``` java
public class DeadlineTaskListCard extends UiPart<Pane> {

    private static final String FXML = "/view/DeadlineTaskListCard.fxml";

    @FXML
    private Label indexLabel;

    @FXML
    private Label nameLabel;

    @FXML
    private Label dueLabel;

    /**
     * @param deadlineTask The deadline task to display. Can be null to not display anything.
     */
    public DeadlineTaskListCard(DeadlineTask deadlineTask, int index) {
        super(FXML);
        if (deadlineTask != null) {
            indexLabel.setWrapText(true);
            indexLabel.setText(TaskType.DEADLINE.getPrefixString() + index + ". ");
            nameLabel.setText(deadlineTask.getName().toString());
            dueLabel.setText(StringUtil.localDateTimeToPrettyString(deadlineTask.getDue()));
            if (deadlineTask.isFinished()) {
                getRoot().getStyleClass().add("finished");
            } else if (deadlineTask.getDue().isBefore(LocalDateTime.now())) {
                getRoot().getStyleClass().add("overdue");
            }
        } else {
            getRoot().setVisible(false);
        }
    }

}
```
###### src/main/java/seedu/address/ui/MainWindow.java

``` java
/**
 * The Main Window. Provides the basic application layout.
 */
public class MainWindow extends UiPart<Scene> {

    private static final String ICON = "/images/address_book_32.png";
    private static final String FXML = "/view/MainWindow.fxml";
    public static final int MIN_HEIGHT = 600;
    public static final int MIN_WIDTH = 450;

    // Independent Ui parts residing in this Ui container
    private ResultDisplay resultDisplay;
    private CommandBox commandBox;
    private StatusBarFooter statusBarFooter;

    // Handles to elements of this Ui container
    @FXML
    private VBox rootLayout;

    @FXML
    private UiRegion commandBoxPlaceholder;

    @FXML
    private UiRegion taskListPanelPlaceholder;

    private FloatingTaskListPane floatingTaskListPane;

    @FXML
    private UiRegion floatingTaskListRegion;

    private EventTaskListPane eventTaskListPane;

    @FXML
    private UiRegion deadlineTaskListRegion;

    private DeadlineTaskListPane deadlineTaskListPane;

    @FXML
    private UiRegion eventTaskListRegion;

    @FXML
    private UiRegion resultDisplayPlaceholder;

    @FXML
    private UiRegion statusbarPlaceholder;

    @FXML
    private UiRegion topBarRegion;
    private TopBar topBar;

    private final Stage primaryStage;
    private final Logic logic;

    public MainWindow(Stage primaryStage, Config config, Logic logic) {
        super(FXML);
        this.primaryStage = primaryStage;
        this.logic = logic;

        //Configure the UI
        setTitle(MainApp.NAME);
        setIcon(ICON);
        setWindowMinSize();
        fillInnerParts(config, logic);
        primaryStage.setOnShown(this::onShown);
    }

    void fillInnerParts(Config config, Logic logic) {
        topBar = new TopBar(logic.getModel().taskPredicateProperty());
        topBarRegion.setNode(topBar.getRoot());
        floatingTaskListPane = new FloatingTaskListPane(logic.getModel());
        floatingTaskListRegion.setNode(floatingTaskListPane.getRoot());
        eventTaskListPane = new EventTaskListPane(logic.getModel());
        eventTaskListRegion.setNode(eventTaskListPane.getRoot());
        deadlineTaskListPane = new DeadlineTaskListPane(logic.getModel());
        deadlineTaskListRegion.setNode(deadlineTaskListPane.getRoot());
        resultDisplay = new ResultDisplay();
        resultDisplayPlaceholder.setNode(resultDisplay.getRoot());
        commandBox = new CommandBox(resultDisplay, logic);
        commandBox.setOnCommandResult(this::onCommandResult);
        commandBoxPlaceholder.setNode(commandBox.getRoot());
        statusBarFooter = new StatusBarFooter(logic.getModel());
        statusbarPlaceholder.setNode(statusBarFooter.getRoot());
    }

```
###### src/main/java/seedu/address/ui/MainWindow.java

``` java
    private void handleHelp() {
        final HelpWindow helpWindow = new HelpWindow();
    }

```
###### src/main/java/seedu/address/ui/MainWindow.java

``` java
    private void onCommandResult(CommandResult result) {
        updateTaskSelection();

        if (result instanceof ExitCommandResult) {
            handleExit();
        }

        if (result instanceof HelpCommandResult) {
            handleHelp();
        }
    }

    /**
     * Update task selection in UI.
     */
    private void updateTaskSelection() {
        final Optional<TaskSelect> taskSelect = logic.getModel().getTaskSelect();
        final TaskType taskType = taskSelect.map(x -> x.getTaskType()).orElse(null);
        final int workingIndex = taskSelect.map(x -> x.getWorkingIndex()).orElse(0);

        // Floating task list pane
        if (taskType == TaskType.FLOAT) {
            floatingTaskListPane.select(workingIndex);
        } else {
            floatingTaskListPane.clearSelect();
        }

        // Deadline task list pane
        if (taskType == TaskType.DEADLINE) {
            deadlineTaskListPane.select(workingIndex);
        } else {
            deadlineTaskListPane.clearSelect();
        }

        // Event task list pane
        if (taskType == TaskType.EVENT) {
            eventTaskListPane.select(workingIndex);
        } else {
            eventTaskListPane.clearSelect();
        }
    }

    /**
     * Called after the window (primaryStage) is shown.
     */
    private void onShown(WindowEvent ev) {
        commandBox.requestFocus();
    }

}
```
###### src/main/java/seedu/address/ui/FloatingTaskListPane.java

``` java
    /**
     * Selects a floating task as specified by its working index.
     */
    public void select(int workingIndex) {
        final List<IndexedItem<FloatingTask>> floatingTaskList = floatingTaskListView.getItems();
        for (int i = 0; i < floatingTaskList.size(); i++) {
            if (floatingTaskList.get(i).getWorkingIndex() == workingIndex) {
                floatingTaskListView.scrollTo(i);
                floatingTaskListView.getSelectionModel().select(i);
                return;
            }
        }
    }

    /**
     * Clears any floating task selection.
     */
    public void clearSelect() {
        floatingTaskListView.getSelectionModel().clearSelection();
    }

```
###### src/main/java/seedu/address/ui/FloatingTaskListPane.java

``` java
    private static class FloatingTaskListCell extends ListCell<IndexedItem<FloatingTask>> {
        @Override
        protected void updateItem(IndexedItem<FloatingTask> floatingTask, boolean empty) {
            super.updateItem(floatingTask, empty);
            final FloatingTaskListCard card;
            card = new FloatingTaskListCard(floatingTask != null ? floatingTask.getItem() : null,
                                            floatingTask != null ? floatingTask.getWorkingIndex() : 0);
            setPrefWidth(10); // This will stop the cards from extending beyond the horizontal listview width
            setGraphic(card.getRoot());
        }
    }
}
```
###### src/main/java/seedu/address/ui/StatusBarFooter.java

``` java
/**
 * A ui for the status bar that is displayed at the footer of the application.
 */
public class StatusBarFooter extends UiPart<Pane> {

    private static final String FXML = "/view/StatusBarFooter.fxml";

    @FXML
    private StatusBar taskCounter;

    @FXML
    private StatusBar syncStatus;

    @FXML
    private StatusBar saveLocationStatus;

    public StatusBarFooter(ReadOnlyModel model) {
        super(FXML);
        connectTaskCounter(model);
        connectSaveLocation(model);
    }

```
###### src/main/java/seedu/address/ui/StatusBarFooter.java

``` java
    private void connectSaveLocation(ReadOnlyModel model) {
        saveLocationStatus.textProperty().bind(model.getConfig().taskBookFilePathProperty());
    }

}
```
###### src/main/java/seedu/address/ui/EventTaskListCard.java

``` java
public class EventTaskListCard extends UiPart<Pane> {

    private static final String FXML = "/view/EventTaskListCard.fxml";

    @FXML
    private Label indexLabel;

    @FXML
    private Label nameLabel;

    @FXML
    private Label startLabel;

    @FXML
    private Label endLabel;

    /**
     * @param eventTask The event task to display. Can be null to not display anything.
     */
    public EventTaskListCard(EventTask eventTask, int index) {
        super(FXML);
        if (eventTask != null) {
            indexLabel.setText(TaskType.EVENT.getPrefixString() + index + ". ");
            nameLabel.setText(eventTask.getName().toString());
            startLabel.setText(StringUtil.localDateTimeToPrettyString(eventTask.getStart()));
            endLabel.setText(StringUtil.localDateTimeToPrettyString(eventTask.getEnd()));
            if (eventTask.getEnd().isBefore(LocalDateTime.now())) {
                getRoot().getStyleClass().add("finished");
            } else if (eventTask.getStart().isBefore(LocalDateTime.now())) {
                getRoot().getStyleClass().add("inProgress");
            }
        } else {
            getRoot().setVisible(false);
        }
    }

}
```
###### src/main/java/seedu/address/ui/TrayIcon.java

``` java
/**
 * A tray icon.
 */
public class TrayIcon {

    public enum MessageType { ERROR, INFO, NONE, WARNING }

    private final String title;
    private final Image image;
    private Runnable trayIconAction;
    private Optional<java.awt.TrayIcon> trayIcon = Optional.empty();

    public TrayIcon(Image image, String title) {
        this.image = image;
        this.title = title;
        if (!GraphicsEnvironment.isHeadless()) {
            javax.swing.SwingUtilities.invokeLater(this::addIconToTrayAWT);
        }
    }

    public synchronized void setTrayIconAction(Runnable trayIconAction) {
        this.trayIconAction = trayIconAction;
    }

    public void displayMessage(final String caption, final String text, final MessageType messageType) {
        javax.swing.SwingUtilities.invokeLater(() -> {
            synchronized (this) {
                if (!this.trayIcon.isPresent()) {
                    return;
                }
                this.trayIcon.get().displayMessage(caption, text, messageTypeToAWTMessageType(messageType));
            }
        });
    }

    private java.awt.TrayIcon.MessageType messageTypeToAWTMessageType(MessageType messageType) {
        switch (messageType) {
        case ERROR:
            return java.awt.TrayIcon.MessageType.ERROR;
        case INFO:
            return java.awt.TrayIcon.MessageType.INFO;
        case WARNING:
            return java.awt.TrayIcon.MessageType.WARNING;
        default:
            return java.awt.TrayIcon.MessageType.NONE;
        }
    }

    /**
     * Destroys the tray icon.
     */
    public synchronized void destroy() {
        if (trayIcon.isPresent()) {
            javax.swing.SwingUtilities.invokeLater(this::removeIconFromTrayAWT);
        }

        while (trayIcon.isPresent()) {
            try {
                wait();
            } catch (InterruptedException e) { }
        }
    }

    /**
     * Runs on the AWT event thread.
     */
    private void addIconToTrayAWT() {
        // ensure awt toolkit is initialized
        java.awt.Toolkit.getDefaultToolkit();

        // app requires system tray for support, just exit if there is no support.
        if (!SystemTray.isSupported()) {
            return;
        }

        final java.awt.SystemTray tray = SystemTray.getSystemTray();
        final java.awt.Image image = SwingFXUtils.fromFXImage(this.image, null);
        final java.awt.TrayIcon trayIcon = new java.awt.TrayIcon(image, this.title, null);
        trayIcon.addActionListener(event -> Platform.runLater(this::onTrayIconActionFX));

        // Add the icon to the system tray
        try {
            tray.add(trayIcon);
        } catch (AWTException e) {
            return;
        }

        synchronized (this) {
            this.trayIcon = Optional.of(trayIcon);
        }
    }

    /**
     * Runs on the AWT event thread
     */
    private synchronized void removeIconFromTrayAWT() {
        final java.awt.SystemTray tray = SystemTray.getSystemTray();
        if (this.trayIcon.isPresent()) {
            final java.awt.TrayIcon trayIcon = this.trayIcon.get();
            this.trayIcon = Optional.empty();
            tray.remove(trayIcon);
        }
        notifyAll();
    }

    private synchronized void onTrayIconActionFX() {
        if (this.trayIconAction != null) {
            this.trayIconAction.run();
        }
    }

}
```
###### src/main/java/seedu/address/ui/ResultDisplay.java

``` java
/**
 * A ui for the status bar that is displayed at the header of the application.
 */
public class ResultDisplay extends UiPart<Pane> {

    private static final String FXML = "/view/ResultDisplay.fxml";

    @FXML
    private TextArea resultDisplay;

    private final StringProperty displayed = new SimpleStringProperty("");

    public ResultDisplay() {
        super(FXML);
        resultDisplay.textProperty().bind(displayed);
    }

```
###### src/main/java/seedu/address/ui/UiManager.java

``` java
    @Override
    public void stop() {
        if (hotkeyListener != null) {
            hotkeyListener.destroy();
            hotkeyListener = null;
        }
        if (trayIcon != null) {
            trayIcon.destroy();
            trayIcon = null;
        }
        primaryStage = null;
    }

```
###### src/main/java/seedu/address/ui/UiManager.java

``` java
    /**
     * Hide the UI, running it in the background.
     */
    public void hide() {
        Platform.setImplicitExit(false);
        savedWindowRect = primaryStage.isMaximized() ? Optional.empty() : Optional.of(getWindowRect());
        primaryStage.hide();
        if (!stillRunningMessageShown) {
            trayIcon.displayMessage("Task Tracker is still running!", "Double-click here to re-open it!",
                                    MessageType.INFO);
            stillRunningMessageShown = true;
        }
    }

    /**
     * Show the UI, bringing it to the foreground.
     */
    public void show() {
        primaryStage.show();
        primaryStage.toFront();
        primaryStage.requestFocus();
        Platform.setImplicitExit(true);
        if (savedWindowRect.isPresent()) {
            setWindowRect(savedWindowRect.get());
        }
    }

    /**
     * Toggle the hiding/showing of the UI.
     */
    public void toggleHide() {
        if (primaryStage.isShowing()) {
            hide();
        } else {
            show();
        }
    }

    /**
     * Returns the dimensions of the primary window of the UI.
     */
    public WindowRect getWindowRect() {
        return new WindowRect(primaryStage.getWidth(), primaryStage.getHeight(),
                              primaryStage.getX(), primaryStage.getY());
    }

    /**
     * Sets the dimensions of the primary window of the UI.
     */
    public void setWindowRect(WindowRect rect) {
        assert rect != null;
        primaryStage.setWidth(rect.getWidth());
        primaryStage.setHeight(rect.getHeight());
        final Rectangle2D bounds = Screen.getPrimary().getVisualBounds();
        primaryStage.setX(rect.getX().orElse((bounds.getWidth() - primaryStage.getWidth()) / 2));
        primaryStage.setY(rect.getY().orElse((bounds.getHeight() - primaryStage.getHeight()) / 2));
    }

    /**
     * Loads fonts contained within our /view/
     */
    private void loadFonts() {
        for (String font : FONTS) {
            Font.loadFont(MainApp.class.getResource(font).toExternalForm(), 10);
        }
    }

}
```
###### src/main/java/seedu/address/ui/EventTaskListPane.java

``` java
    /**
     * Selects an event task as specified by its working index.
     */
    public void select(int workingIndex) {
        final List<IndexedItem<EventTask>> eventTaskList = eventTaskListView.getItems();
        for (int i = 0; i < eventTaskList.size(); i++) {
            if (eventTaskList.get(i).getWorkingIndex() == workingIndex) {
                eventTaskListView.scrollTo(i);
                eventTaskListView.getSelectionModel().select(i);
                return;
            }
        }
    }

    /**
     * Clears any event task selection.
     */
    public void clearSelect() {
        eventTaskListView.getSelectionModel().clearSelection();
    }

```
###### src/main/java/seedu/address/ui/EventTaskListPane.java

``` java
    private static class EventTaskListCell extends ListCell<IndexedItem<EventTask>> {
        @Override
        protected void updateItem(IndexedItem<EventTask> eventTask, boolean empty) {
            super.updateItem(eventTask, empty);
            final EventTaskListCard card;
            card = new EventTaskListCard(eventTask != null ? eventTask.getItem() : null,
                                         eventTask != null ? eventTask.getWorkingIndex() : 0);
            setPrefWidth(10); // This will stop the cards from extending beyond the horizontal listview width
            setGraphic(card.getRoot());
        }
    }

}
```
###### src/main/java/seedu/address/ui/DeadlineTaskListPane.java

``` java
    /**
     * Selects a deadline task as specified by its working index.
     */
    public void select(int workingIndex) {
        final List<IndexedItem<DeadlineTask>> deadlineTaskList = deadlineTaskListView.getItems();
        for (int i = 0; i < deadlineTaskList.size(); i++) {
            if (deadlineTaskList.get(i).getWorkingIndex() == workingIndex) {
                deadlineTaskListView.scrollTo(i);
                deadlineTaskListView.getSelectionModel().select(i);
                return;
            }
        }
    }

    /**
     * Clears any deadline task selection.
     */
    public void clearSelect() {
        deadlineTaskListView.getSelectionModel().clearSelection();
    }

```
###### src/main/java/seedu/address/ui/DeadlineTaskListPane.java

``` java
    private static class DeadlineTaskListCell extends ListCell<IndexedItem<DeadlineTask>> {
        @Override
        protected void updateItem(IndexedItem<DeadlineTask> deadlineTask, boolean empty) {
            super.updateItem(deadlineTask, empty);
            final DeadlineTaskListCard card;
            card = new DeadlineTaskListCard(deadlineTask != null ? deadlineTask.getItem() : null,
                                            deadlineTask != null ? deadlineTask.getWorkingIndex() : 0);
            setPrefWidth(10); // This will stop the cards from extending beyond the horizontal width
            setGraphic(card.getRoot());
        }
    }

}
```
###### src/main/java/seedu/address/ui/TopBar.java

``` java
/**
 * The application header bar which is usually displayed at the top of the user interface.
 */
public class TopBar extends UiPart<Region> {
    private static final String FXML = "/view/TopBar.fxml";
    private static final String MSG_FILTER = "Filtering by: %s";

    @FXML
    private Pane statusBox;

    @FXML
    private Labeled filterLabel;

    private final SimpleObjectProperty<TaskPredicate> predicate = new SimpleObjectProperty<>();

    public TopBar() {
        super(FXML);
        predicate.addListener((observable, oldValue, newValue) -> {
            if (oldValue != null && newValue == null) {
                statusBox.getStyleClass().removeAll("with-filter");
            } else if (oldValue == null && newValue != null) {
                statusBox.getStyleClass().add("with-filter");
            }
            filterLabel.setText(String.format(MSG_FILTER, newValue != null ? newValue.toHumanReadableString() : ""));
        });
    }

    public TopBar(ReadOnlyProperty<TaskPredicate> predicate) {
        this();
        this.predicate.bind(predicate);
    }

    public ObjectProperty<TaskPredicate> predicateProperty() {
        return predicate;
    }

}
```
###### src/main/java/seedu/address/ui/UiPart.java

``` java
/**
 * Base class for UI parts.
 * A 'UI part' represents a distinct part of the UI. e.g. Windows, dialogs, panels, status bars, etc.
 */
public class UiPart<T> {

    private final FXMLLoader loader;

    public UiPart(URL url) {
        assert url != null;
        loader = new FXMLLoader(url);
        loader.setController(this);
        try {
            loader.load();
        } catch (IOException e) {
            throw new RuntimeException("Unexpected exception occurred while loading " + url + ": " + e);
        }
    }

    public UiPart(String name) {
        this(MainApp.class.getResource(name));
    }

    public T getRoot() {
        return loader.getRoot();
    }
}
```
###### src/main/java/seedu/address/ui/CommandBox.java

``` java
public class CommandBox extends UiPart<Pane> {
    private final Logger logger = LogsCenter.getLogger(CommandBox.class);

    private static final String FXML = "/view/CommandBox.fxml";

    private final ResultDisplay resultDisplay;

    private final Logic logic;

    @FXML
    private InlineCssTextArea commandTextField;

    private OnCommandResultCallback onCommandResultCallback;

    private final Popup autocompletePopup;
    private final ListView<String> autocompleteListView;

    public CommandBox(ResultDisplay resultDisplay, Logic logic) {
        super(FXML);
        this.resultDisplay = resultDisplay;
        this.logic = logic;
        autocompleteListView = new ListView<>();
        autocompleteListView.setOnKeyPressed(this::handleListViewKeyPressed);
        autocompletePopup = new Popup();
        autocompletePopup.getContent().add(autocompleteListView);
        commandTextField.setPopupWindow(autocompletePopup);
        commandTextField.setPopupAlignment(PopupAlignment.CARET_CENTER);
        commandTextField.setPopupAnchorOffset(new Point2D(4, 0));
    }

    public void setOnCommandResult(OnCommandResultCallback callback) {
        this.onCommandResultCallback = callback;
    }

    /**
     * Focus on the command box text field.
     */
    public void requestFocus() {
        commandTextField.requestFocus();
    }

    @FXML
    private void handleKeyPressed(KeyEvent ke) {
        if (ke.getCode() == KeyCode.TAB) {
            ke.consume();
            handleAutocomplete();
        } else if (ke.getCode() == KeyCode.ENTER) {
            ke.consume();
            handleCommandInputChanged();
        } else {
            commandTextField.getStyleClass().removeAll("error");
            autocompletePopup.hide();
        }
    }

    private void handleListViewKeyPressed(KeyEvent ke) {
        if (ke.getCode() == KeyCode.TAB) {
            ke.consume();
            if (autocompleteListView.getSelectionModel().getSelectedIndex() == autocompleteListView.getItems().size() - 1) {
                autocompleteListView.getSelectionModel().selectFirst();
            } else {
                autocompleteListView.getSelectionModel().selectNext();
            }
        } else if (ke.getCode() == KeyCode.ENTER) {
            ke.consume();
            commandTextField.insertText(commandTextField.getCaretPosition(), autocompleteListView.getSelectionModel().getSelectedItem());
            autocompletePopup.hide();
        }
    }

    private void handleAutocomplete() {
        final List<String> candidates = logic.autocomplete(commandTextField.getText(),
                                                           commandTextField.getCaretPosition());
        if (candidates.size() == 1) {
            commandTextField.insertText(commandTextField.getCaretPosition(), candidates.get(0));
        } else if (candidates.size() > 1) {
            autocompleteListView.setItems(FXCollections.observableList(candidates));
            autocompleteListView.getSelectionModel().selectFirst();
            autocompletePopup.show(commandTextField.getScene().getWindow());
        }
    }

    private void handleCommandInputChanged() {
        try {
            final CommandResult result = logic.execute(commandTextField.getText());
            logger.info("Result: " + result.feedbackToUser);
            resultDisplay.postMessage(result.feedbackToUser);
            setStyleToIndicateCorrectCommand();
            if (onCommandResultCallback != null) {
                onCommandResultCallback.call(result);
            }
        } catch (ParseException | CommandException | IOException e) {
            logger.info("Result: " + e.toString());
            setStyleToIndicateIncorrectCommand();
            resultDisplay.postMessage(e.getMessage());
        }
    }

```
###### src/main/java/seedu/address/ui/CommandBox.java

``` java
    public interface OnCommandResultCallback {
        void call(CommandResult result);
    }

}
```
###### src/main/java/seedu/address/ui/UiRegion.java

``` java
public class UiRegion extends Region {

    public UiRegion() {
        super();
        setMinHeight(Region.USE_PREF_SIZE);
    }

    public Node getNode() {
        return getChildren().size() > 0 ? getChildren().get(0) : null;
    }

    public void setNode(Node child) {
        getChildren().clear();
        getChildren().add(child);
    }

    @Override
    protected void layoutChildren() {
        for (Node node : getChildren()) {
            layoutInArea(node, 0, 0, getWidth(), getHeight(), 0, HPos.LEFT, VPos.TOP);
        }
    }

}
```
###### src/main/java/seedu/address/ui/HotkeyListener.java

``` java
public class HotkeyListener {

    private static int identifierCounter;
    private static int refCount;
    private final int identifier;
    private Runnable action;

    public HotkeyListener(int modifier, int keycode) {
        if (GraphicsEnvironment.isHeadless() || !JIntellitype.isJIntellitypeSupported()) {
            this.identifier = 0;
            return;
        }
        synchronized (HotkeyListener.class) {
            this.identifier = ++identifierCounter;
            JIntellitype.getInstance().addHotKeyListener(identifier -> {
                if (identifier != this.identifier) {
                    return;
                }
                Platform.runLater(this::onHotkey);
            });
            JIntellitype.getInstance().registerHotKey(this.identifier, modifier, keycode);
            refCount++;
        }
    }

    public synchronized void setAction(Runnable action) {
        this.action = action;
    }

    public synchronized void destroy() {
        if (this.identifier <= 0) {
            return;
        }
        JIntellitype.getInstance().unregisterHotKey(this.identifier);
        synchronized (HotkeyListener.class) {
            if (--refCount <= 0) {
                JIntellitype.getInstance().cleanUp();
            }
        }
    }

    private synchronized void onHotkey() {
        if (this.action != null) {
            this.action.run();
        }
    }
}
```
###### src/main/java/seedu/address/storage/JsonStorageModule.java

``` java
public class JsonStorageModule extends Module {

    @Override
    public String getModuleName() {
        return "JsonStorageModule";
    }

    @Override
    public Version version() {
        return Version.unknownVersion();
    }

    @Override
    public void setupModule(SetupContext context) {
        context.setMixInAnnotations(LocalDateTimeDuration.class, JsonLocalDateTimeDurationMixin.class);
        context.setMixInAnnotations(Name.class, JsonNameMixin.class);
        context.setMixInAnnotations(Priority.class, JsonPriorityMixin.class);
        context.setMixInAnnotations(Task.class, JsonTaskMixin.class);
        context.setMixInAnnotations(FloatingTask.class, JsonFloatingTaskMixin.class);
        context.setMixInAnnotations(EventTask.class, JsonEventTaskMixin.class);
        context.setMixInAnnotations(DeadlineTask.class, JsonDeadlineTaskMixin.class);
        context.setMixInAnnotations(TaskBook.class, JsonTaskBookMixin.class);
    }

}
```
###### src/main/java/seedu/address/storage/JsonEventTaskMixin.java

``` java
@JsonPropertyOrder({"name", "start", "end"})
public abstract class JsonEventTaskMixin {

    JsonEventTaskMixin(@JsonProperty("name") Name name, @JsonProperty("start") LocalDateTime start,
                       @JsonProperty("end") LocalDateTime end) {
    }

    @JsonIgnore
    abstract LocalDateTimeDuration getDuration();

}
```
###### src/main/java/seedu/address/storage/config/ConfigStorage.java

``` java
/**
 * Represents a storage for {@link seedu.address.model.config.Config}
 */
public interface ConfigStorage {

    /**
     * Returns the file path of the config file.
     */
    String getConfigFilePath();

    /**
     * Returns the Config data as a {@link ReadOnlyConfig}.
     * Returns {@code Optional.empty()} if config file was not found.
     * @throws DataConversionException if the data in storage cannot be parsed.
     * @throws IOException if an IO error occurred while reading from the storage.
     */
    Optional<ReadOnlyConfig> readConfig() throws DataConversionException, IOException;

    /**
     * @see #readConfig()
     */
    Optional<ReadOnlyConfig> readConfig(String filePath) throws DataConversionException, IOException;

    /**
     * Saves the given {@link ReadOnlyConfig} to the storage.
     * @param config cannot be null.
     * @throws IOException if an IO error occurred while writing to the storage.
     */
    void saveConfig(ReadOnlyConfig config) throws IOException;

    /**
     * @see #saveConfig(ReadOnlyConfig)
     */
    void saveConfig(ReadOnlyConfig config, String filePath) throws IOException;

}
```
###### src/main/java/seedu/address/storage/config/JsonConfigModule.java

``` java
/**
 * Jackson module for serialization/deserialization of {@link Config} objects.
 */
public class JsonConfigModule extends Module {

    @Override
    public String getModuleName() {
        return "JsonConfigModule";
    }

    @Override
    public Version version() {
        return Version.unknownVersion();
    }

    @Override
    public void setupModule(SetupContext context) {
        final SimpleSerializers serializers = new SimpleSerializers();
        final SimpleDeserializers deserializers = new SimpleDeserializers();

        serializers.addSerializer(Level.class, new ToStringSerializer());
        deserializers.addDeserializer(Level.class, new JsonLevelDeserializer(Level.class));

        context.addSerializers(serializers);
        context.addDeserializers(deserializers);
        context.setMixInAnnotations(Config.class, JsonConfigMixin.class);
    }

}
```
###### src/main/java/seedu/address/storage/config/JsonConfigStorage.java

``` java
public class JsonConfigStorage implements ConfigStorage {

    private static final Logger logger = LogsCenter.getLogger(JsonConfigStorage.class);

    private final String filePath;

    private final ObjectMapper objectMapper;

    public JsonConfigStorage(String filePath, ObjectMapper objectMapper) {
        assert !CollectionUtil.isAnyNull(filePath, objectMapper);
        this.filePath = filePath;
        this.objectMapper = objectMapper;
    }

    public JsonConfigStorage(String filePath) {
        this(filePath, initDefaultObjectMapper());
    }

    private static ObjectMapper initDefaultObjectMapper() {
        return new ObjectMapper()
                .enable(SerializationFeature.INDENT_OUTPUT)
                .disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES)
                .registerModule(new Jdk8Module())
                .registerModule(new JsonConfigModule());
    }

    @Override
    public String getConfigFilePath() {
        return filePath;
    }

    @Override
    public Optional<ReadOnlyConfig> readConfig() throws DataConversionException, IOException {
        return readConfig(filePath);
    }

    @Override
    public Optional<ReadOnlyConfig> readConfig(String filePath) throws DataConversionException, IOException {
        assert filePath != null;
        final File configFile = new File(filePath);
        if (!configFile.exists()) {
            logger.info("Config file " + configFile + " not found");
            return Optional.empty();
        }
        try {
            return Optional.of(objectMapper.readValue(configFile, Config.class));
        } catch (JsonProcessingException e) {
            throw new DataConversionException(e);
        }
    }

    @Override
    public void saveConfig(ReadOnlyConfig config) throws IOException {
        saveConfig(config, filePath);
    }

    @Override
    public void saveConfig(ReadOnlyConfig config, String filePath) throws IOException {
        assert !CollectionUtil.isAnyNull(config, filePath);
        final File file = new File(filePath);
        FileUtil.createIfMissing(file);
        objectMapper.writeValue(file, config);
    }

}
```
###### src/main/java/seedu/address/storage/config/JsonLevelDeserializer.java

``` java
public class JsonLevelDeserializer extends FromStringDeserializer<Level> {

    public JsonLevelDeserializer(Class<?> vc) {
        super(vc);
    }

    @Override
    protected Level _deserialize(String value, DeserializationContext ctxt) throws IOException {
        return Level.parse(value);
    }

}
```
###### src/main/java/seedu/address/storage/JsonLocalDateTimeDurationMixin.java

``` java
@JsonPropertyOrder({"start", "end"})
public abstract class JsonLocalDateTimeDurationMixin {

    public JsonLocalDateTimeDurationMixin(@JsonProperty("start") LocalDateTime start,
                                          @JsonProperty("end") LocalDateTime end) {
    }

    @JsonIgnore
    abstract List<TemporalUnit> getUnits();

}
```
###### src/main/java/seedu/address/storage/JsonNameMixin.java

``` java
public abstract class JsonNameMixin {
    @JsonCreator
    JsonNameMixin(String name) {}

    @JsonValue
    public abstract String toString();
}
```
###### src/main/java/seedu/address/storage/JsonTaskBookStorage.java

``` java
public class JsonTaskBookStorage implements TaskBookStorage {

    private static final Logger logger = LogsCenter.getLogger(JsonTaskBookStorage.class);

    private String filePath;

    private final ObjectMapper objectMapper;

    public JsonTaskBookStorage(String filePath, ObjectMapper objectMapper) {
        assert !CollectionUtil.isAnyNull(filePath, objectMapper);
        this.filePath = filePath;
        this.objectMapper = objectMapper;
    }

    public JsonTaskBookStorage(String filePath) {
        this(filePath, initDefaultObjectMapper());
    }

    private static ObjectMapper initDefaultObjectMapper() {
        return new ObjectMapper()
            .enable(SerializationFeature.INDENT_OUTPUT)
            .registerModule(new JavaTimeModule())
            .registerModule(new JsonStorageModule());
    }

    @Override
    public String getTaskBookFilePath() {
        return filePath;
    }

    @Override
    public Optional<ReadOnlyTaskBook> readTaskBook() throws DataConversionException, IOException {
        return readTaskBook(filePath);
    }

    @Override
    public Optional<ReadOnlyTaskBook> readTaskBook(String filePath) throws DataConversionException, IOException {
        assert filePath != null;
        final File taskBookFile = new File(filePath);
        if (!taskBookFile.exists()) {
            logger.info("TaskBook file " + taskBookFile + " not found");
            return Optional.empty();
        }
        try {
            return Optional.of(objectMapper.readValue(taskBookFile, TaskBook.class));
        } catch (JsonProcessingException e) {
            throw new DataConversionException(e);
        }
    }

    @Override
    public void saveTaskBook(ReadOnlyTaskBook taskBook) throws IOException {
        saveTaskBook(taskBook, filePath);
    }

    @Override
    public void saveTaskBook(ReadOnlyTaskBook taskBook, String filePath) throws IOException {
        assert !CollectionUtil.isAnyNull(taskBook, filePath);
        final File file = new File(filePath);
        FileUtil.createIfMissing(file);
        objectMapper.writeValue(file, taskBook);
    }

    @Override
    public void moveTaskBook(String newFilePath) throws IOException {
        assert newFilePath != null;
        final File file = new File(filePath);
        final File newFile = new File(newFilePath);
        if (FileUtil.isFileExists(newFile)) {
            throw new IOException(newFilePath + " already exists.");
        }
        if (FileUtil.isFileExists(file)) {
            Files.createParentDirs(newFile);
            Files.move(file, newFile);
        }
        filePath = newFilePath;
    }

}
```
###### src/main/java/seedu/address/storage/JsonTaskMixin.java

``` java
public abstract class JsonTaskMixin {
    JsonTaskMixin(@JsonProperty("name") Name name) {}
}
```
###### src/main/java/seedu/address/storage/TaskBookStorage.java

``` java
    /**
     * Moves the task book to the newFilePath.
     * @throws IOException if an IO error occurred while moving the task book file.
     *         The configured task book file path will remain unchanged.
     */
    void moveTaskBook(String newFilePath) throws IOException;

}
```
###### src/main/java/seedu/address/storage/StorageManager.java

``` java
/**
 * Manages storage of TaskBook data in local storage.
 */
public class StorageManager extends ComponentManager implements Storage {

    private static final Logger logger = LogsCenter.getLogger(StorageManager.class);

    private final ConfigStorage configStorage;

    private final TaskBookStorage taskBookStorage;

    public StorageManager(ConfigStorage configStorage, TaskBookStorage taskBookStorage) {
        super();
        this.configStorage = configStorage;
        this.taskBookStorage = taskBookStorage;
    }

    public StorageManager(ConfigStorage configStorage, String taskBookFilePath) {
        this(configStorage, new JsonTaskBookStorage(taskBookFilePath));
    }

    public StorageManager(String configFilePath, String taskBookFilePath) {
        this(new JsonConfigStorage(configFilePath), taskBookFilePath);
    }

    // ================ ConfigStorage methods =========================

    @Override
    public String getConfigFilePath() {
        return configStorage.getConfigFilePath();
    }

    @Override
    public Optional<ReadOnlyConfig> readConfig() throws DataConversionException, IOException {
        return configStorage.readConfig();
    }

    @Override
    public Optional<ReadOnlyConfig> readConfig(String filePath) throws DataConversionException, IOException {
        return configStorage.readConfig(filePath);
    }

    @Override
    public void saveConfig(ReadOnlyConfig config) throws IOException {
        configStorage.saveConfig(config);
    }

    @Override
    public void saveConfig(ReadOnlyConfig config, String filePath) throws IOException {
        configStorage.saveConfig(config, filePath);
    }

    // ================ TaskBook methods ==============================

```
###### src/main/java/seedu/address/storage/StorageManager.java

``` java
    @Override
    public void moveTaskBook(String newFilePath) throws IOException {
        logger.fine("Attempting to move task book from " + taskBookStorage.getTaskBookFilePath() + " to "
                    + newFilePath);
        taskBookStorage.moveTaskBook(newFilePath);
    }

}
```
###### src/main/java/seedu/address/commons/exceptions/IllegalValueException.java

``` java
    /**
     * @param message should contain relevant information on the failed constraint(s).
     * @param cause The Throwable which caused this exception to be thrown.
     */
    public IllegalValueException(String message, Throwable cause) {
        super(message, cause);
    }
}
```
###### src/main/java/seedu/address/commons/core/Version.java

``` java
    @Override
    public int compareTo(Version other) {
        return this.major != other.major ? this.major - other.major
               : this.minor != other.minor ? this.minor - other.minor
               : this.patch != other.patch ? this.patch - other.patch
               : this.isEarlyAccess == other.isEarlyAccess() ? 0
               : this.isEarlyAccess ? -1 : 1;
    }

```
###### src/main/java/seedu/address/commons/time/LocalDateTimeDuration.java

``` java
/**
 * Represents a period bounded by two LocalDateTimes.
 *
 * Guarantees: Immutable POJO with non-null values. End LocalDateTime is after start LocalDateTime.
 */
public final class LocalDateTimeDuration implements Comparable<LocalDateTimeDuration>, TemporalAmount {

    private static final String FMT_STRING = "LocalDateTimeDuration[start=%s, end=%s]";

    private final LocalDateTime start;

    private final LocalDateTime end;

    private final Duration duration;

    public LocalDateTimeDuration(LocalDateTime start, LocalDateTime end) throws IllegalValueException {
        assert !CollectionUtil.isAnyNull(start, end);
        if (end.isBefore(start)) {
            throw new IllegalValueException("end datetime must be after start datetime");
        }
        this.start = start;
        this.end = end;
        this.duration = Duration.between(start, end);
    }

    public LocalDateTime getStart() {
        return start;
    }

    public LocalDateTime getEnd() {
        return end;
    }

    @Override
    public Temporal addTo(Temporal temporal) {
        return temporal.plus(duration);
    }

    @Override
    public long get(TemporalUnit unit) {
        return duration.get(unit);
    }

    @Override
    public List<TemporalUnit> getUnits() {
        return duration.getUnits();
    }

    @Override
    public Temporal subtractFrom(Temporal temporal) {
        return temporal.minus(duration);
    }

    @Override
    public int compareTo(LocalDateTimeDuration other) {
        int cmp = start.compareTo(other.start);
        if (cmp != 0) {
            return cmp;
        }
        return start.compareTo(other.end);
    }

    @Override
    public boolean equals(Object other) {
        return other == this
               || (other instanceof LocalDateTimeDuration
               && start.equals(((LocalDateTimeDuration)other).start)
               && end.equals(((LocalDateTimeDuration)other).end));
    }

    @Override
    public int hashCode() {
        return Objects.hash(start, end);
    }

    @Override
    public String toString() {
        return String.format(FMT_STRING, start, end);
    }

}
```
###### src/main/java/seedu/address/commons/util/ObservableListChangeListener.java

``` java
/**
 * A utility class that tracks whether ObservableList(s) have changed.
 */
public class ObservableListChangeListener {

    private boolean hasChanged = false;

    private final ObservableList<?>[] lists;

    public ObservableListChangeListener(ObservableList<?>... lists) {
        // Store a reference to the observable lists so they do not get GC'd
        this.lists = lists;
        // Install our listeners on the observable lists
        for (ObservableList<?> list : lists) {
            list.addListener((ListChangeListener.Change<? extends Object> change) -> {
                hasChanged = true;
            });
        }
    }

    public void setHasChanged(boolean hasChanged) {
        this.hasChanged = hasChanged;
    }

    public boolean getHasChanged() {
        return hasChanged;
    }

}
```

###### src/main/java/seedu/address/commons/util/SubstringRange.java

``` java
/**
 * Represents a subtring of a string.
 */
public class SubstringRange implements Comparable<SubstringRange> {

    private final int start;

    private final int end;

    public SubstringRange(int start, int end) {
        assert start >= 0;
        assert end >= 0;
        assert end >= start;
        this.start = start;
        this.end = end;
    }

    /**
     * Returns a substring range that covers the whole string.
     */
    public static SubstringRange of(String str) {
        return new SubstringRange(0, str.length());
    }

    public int getStart() {
        return start;
    }

    public int getEnd() {
        return end;
    }

    public SubstringRange indent(int x) {
        assert start + x >= 0;
        return new SubstringRange(start + x, end + x);
    }

    /**
     * Returns true if x is within this substring range from start (inclusive) to end (inclusive).
     */
    public boolean contains(int x) {
        return x >= start && x <= end;
    }

    @Override
    public int compareTo(SubstringRange other) {
        return ComparisonChain.start()
                .compare(start, other.start)
                .compare(end, other.end)
                .result();
    }

    @Override
    public boolean equals(Object other) {
        return other == this
                || (other instanceof SubstringRange
                && start == ((SubstringRange)other).start
                && end == ((SubstringRange)other).end
                );
    }

    @Override
    public String toString() {
        return MoreObjects.toStringHelper(this)
                .add("start", start)
                .add("end", end)
                .toString();
    }

}
```