package seedu.address.model;

import java.util.Set;
import java.util.logging.Logger;

import javafx.collections.transformation.FilteredList;
import seedu.address.commons.core.ComponentManager;
import seedu.address.commons.core.LogsCenter;
import seedu.address.commons.core.UnmodifiableObservableList;
import seedu.address.commons.events.model.AddressBookChangedEvent;
import seedu.address.commons.util.CollectionUtil;
import seedu.address.commons.util.StringUtil;
import seedu.address.model.task.ReadOnlyTask;
import seedu.address.model.task.Task;
import seedu.address.model.task.UniqueTaskList;
import seedu.address.model.task.UniqueTaskList.TaskNotFoundException;

/**
 * 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 AddressBook addressBook;
    private final FilteredList<ReadOnlyTask> filteredTasks;

    /**
     * Initializes a ModelManager with the given addressBook and userPrefs.
     */
    public ModelManager(ReadOnlyAddressBook addressBook, UserPrefs userPrefs) {
        super();
        assert !CollectionUtil.isAnyNull(addressBook, userPrefs);

        logger.fine("Initializing with address book: " + addressBook + " and user prefs " + userPrefs);

        this.addressBook = new AddressBook(addressBook);
        filteredTasks = new FilteredList<>(this.addressBook.getTaskList());
    }

    public ModelManager() {
        this(new AddressBook(), new UserPrefs());
    }

    @Override
    public void resetData(ReadOnlyAddressBook newData) {
        addressBook.resetData(newData);
        indicateAddressBookChanged();
    }

    @Override
    public ReadOnlyAddressBook getAddressBook() {
        return addressBook;
    }

    /** Raises an event to indicate the model has changed */
    private void indicateAddressBookChanged() {
        raise(new AddressBookChangedEvent(addressBook));
    }

    @Override
    public synchronized void deleteTask(ReadOnlyTask target) throws TaskNotFoundException {
        addressBook.removeTask(target);
        indicateAddressBookChanged();
    }

    @Override
    public synchronized void addTask(Task task) throws UniqueTaskList.DuplicateTaskException {
        addressBook.addTask(task);
        updateFilteredListToShowAll();
        indicateAddressBookChanged();
    }

    @Override
    public void updateTask(int filteredTaskListIndex, ReadOnlyTask editedTask)
            throws UniqueTaskList.DuplicateTaskException {
        assert editedTask != null;

        int taskIndex = filteredTasks.getSourceIndex(filteredTaskListIndex);
        addressBook.updateTask(taskIndex, editedTask);
        indicateAddressBookChanged();
    }

    //=========== Filtered Person List Accessors =============================================================

    @Override
    public UnmodifiableObservableList<ReadOnlyTask> getFilteredTaskList() {
        return new UnmodifiableObservableList<>(filteredTasks);
    }

    @Override
    public void updateFilteredListToShowAll() {
        filteredTasks.setPredicate(null);
    }

    @Override
    public void updateFilteredTaskList(Set<String> keywords) {
        updateFilteredTaskList(new PredicateExpression(new NameQualifier(keywords)));
    }

    private void updateFilteredTaskList(Expression expression) {
        filteredTasks.setPredicate(expression::satisfies);
    }

    //========== Inner classes/interfaces used for filtering =================================================

    interface Expression {
        boolean satisfies(ReadOnlyTask task);
        @Override
        String toString();
    }

    private class PredicateExpression implements Expression {

        private final Qualifier qualifier;

        PredicateExpression(Qualifier qualifier) {
            this.qualifier = qualifier;
        }

        @Override
        public boolean satisfies(ReadOnlyTask task) {
            return qualifier.run(task);
        }

        @Override
        public String toString() {
            return qualifier.toString();
        }
    }

    interface Qualifier {
        boolean run(ReadOnlyTask task);
        @Override
        String toString();
    }

    private class NameQualifier implements Qualifier {
        private Set<String> nameKeyWords;

        NameQualifier(Set<String> nameKeyWords) {
            this.nameKeyWords = nameKeyWords;
        }

        @Override
        public boolean run(ReadOnlyTask task) {
            return nameKeyWords.stream()
                    .filter(keyword -> StringUtil.containsWordIgnoreCase(task.getContent().fullContent, keyword))
                    .findAny()
                    .isPresent();
        }

        @Override
        public String toString() {
            return "name=" + String.join(", ", nameKeyWords);
        }
    }

}

@@ -45,16 +45,40 @@
 il.Set;%0A
+import java.util.Stack;%0A
 import j
@@ -102,16 +102,16 @@
 Logger;%0A
-
 %0Aimport 
@@ -1061,17 +1061,255 @@
 dTasks;%0A
+    private final Stack%3CString%3E stackOfUndo;%0A    private final Stack%3CReadOnlyTask%3E stackOfDeletedTasksAdd;%0A    private final Stack%3CReadOnlyTask%3E stackOfDeletedTasks;%0A    private final Stack%3CInteger%3E stackOfDeletedTaskIndex;%0A    %0A    %0A    
 %0A
-
     /**%0A
@@ -1546,32 +1546,220 @@
 ook, userPrefs);
+%0A        %0A        stackOfUndo = new Stack%3C%3E();%0A        stackOfDeletedTasksAdd = new Stack%3C%3E();%0A        stackOfDeletedTasks = new Stack%3C%3E();%0A        stackOfDeletedTaskIndex = new Stack%3C%3E();
 %0A%0A        logger
@@ -2220,32 +2220,37 @@
 Changed();%0A    %7D
+%0A    
 %0A%0A    @Override%0A
@@ -2545,20 +2545,19 @@
 ronized 
-void
+int
  deleteT
@@ -2611,32 +2611,51 @@
 eption %7B%0A       
+ int indexRemoved =
  addressBook.rem
@@ -2701,32 +2701,61 @@
 sBookChanged();%0A
+        return indexRemoved;%0A
     %7D%0A%0A    @Over
@@ -3327,29 +3327,456 @@
 kChanged();%0A
-
     %7D
+%0A    %0A    %0A    public Stack%3CString%3E getUndoStack() %7B%0A        return stackOfUndo;%0A    %7D%0A%0A    public Stack%3CReadOnlyTask%3E getDeletedStackOfTasksAdd() %7B%0A        return stackOfDeletedTasksAdd;%0A    %7D%0A    %0A    public Stack%3CReadOnlyTask%3E getDeletedStackOfTasks() %7B%0A        return stackOfDeletedTasks;%0A    %7D%0A    %0A    public Stack%3CInteger%3E getDeletedStackOfTasksIndex() %7B%0A        return stackOfDeletedTaskIndex;%0A    %7D%0A    %0A    %0A    %0A    
 %0A%0A    //====

Update ModelManager.java