package seedu.jobs.model.task;

import java.util.EmptyStackException;
import java.util.Iterator;
import java.util.List;

import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import seedu.jobs.commons.core.UnmodifiableObservableList;
import seedu.jobs.commons.exceptions.DuplicateDataException;
import seedu.jobs.commons.util.CollectionUtil;
import seedu.jobs.logic.commands.exceptions.CommandException;
import seedu.jobs.model.FixedStack;

/**
 * A list of persons that enforces uniqueness between its elements and does not allow nulls.
 *
 * Supports a minimal set of list operations.
 *
 * @see Task#equals(Object)
 * @see CollectionUtil#elementsAreUnique(Collection)
 */
public class UniqueTaskList implements Iterable<Task> {

    private final ObservableList<Task> internalList = FXCollections.observableArrayList();
    //@@author A0164440M
    private final FixedStack<ObservableList<Task>> undoStack = new FixedStack();
    private final FixedStack<ObservableList<Task>> redoStack = new FixedStack();
    private boolean initialize = true;
    //@@author
    /**
     * Returns true if the list contains an equivalent task as the given argument.
     */
    public boolean contains(ReadOnlyTask toCheck) {
        assert toCheck != null;
        return internalList.contains(toCheck);
    }

    /**
     * Adds a task to the list.
     *
     * @throws DuplicateTaskException if the person to add is a duplicate of an existing person in the list.
     */
    public void add(Task toAdd) throws DuplicateTaskException {
        assert toAdd != null;
        if (contains(toAdd)) {
            throw new DuplicateTaskException();
        }
        //@@author A0164440M
        ObservableList<Task> stackList = FXCollections.observableArrayList();
        for (Task t : internalList) {
            stackList.add(t);
        }
        undoStack.push(stackList);
        //@@author
        internalList.add(toAdd);
    }

    /**
     * Updates the task in the list at position {@code index} with {@code editedPerson}.
     *
     * @throws DuplicateTaskException if updating the task's details causes the task to be equivalent to
     *      another existing person in the list.
     * @throws IllegalTimeException
     * @throws IndexOutOfBoundsException if {@code index} < 0 or >= the size of the list.
     */
    public void updateTask(int index, ReadOnlyTask editedTask) throws DuplicateTaskException, IllegalTimeException {
        assert editedTask != null;

        //@@author A0164440M
        ObservableList<Task> stackList = FXCollections.observableArrayList();
        Task temp;
        for (Task t : internalList) {
            temp = new Task(t);
            stackList.add(temp);
        }
        undoStack.push(stackList);
        //@@author

        Task taskToUpdate = internalList.get(index);
        if (!taskToUpdate.equals(editedTask) && internalList.contains(editedTask)) {
            throw new DuplicateTaskException();
        }

        taskToUpdate.resetData(editedTask);
        // TODO: The code below is just a workaround to notify observers of the updated person.
        // The right way is to implement observable properties in the Task class.
        // Then, TaskCard should then bind its text labels to those observable properties.
        internalList.set(index, taskToUpdate);
    }

    /**
     * Removes the equivalent task from the list.
     *
     * @throws TaskNotFoundException if no such task could be found in the list.
     */
    public boolean remove(ReadOnlyTask toRemove) throws TaskNotFoundException {
        assert toRemove != null;

      //@@author A0164440M
        ObservableList<Task> stackList = FXCollections.observableArrayList();
        for (Task t : internalList) {
            stackList.add(t);
        }
        undoStack.push(stackList);
      //@@author

        final boolean taskFoundAndDeleted = internalList.remove(toRemove);
        if (!taskFoundAndDeleted) {
            throw new TaskNotFoundException();
        }
        return taskFoundAndDeleted;
    }

    /**
     * Completes the equivalent task from the list.
     *
     * @throws TaskNotFoundException if no such task could be found in the list.
     */
  //@@author A0130979U
    public boolean complete(int index, ReadOnlyTask toComplete) {
        assert toComplete != null;

        ObservableList<Task> stackList = FXCollections.observableArrayList();
        for (Task t : internalList) {
            stackList.add(t);
        }
        undoStack.push(stackList);
        Task taskToComplete = internalList.get(index);
        taskToComplete.markComplete();
        internalList.set(index, taskToComplete);
        return true;
    }
  //@@author

    /**
     *
     * @param replacement
     */
  //@@author A0164440M
    public void undo() throws EmptyStackException {
        ObservableList<Task> replacement = undoStack.pop();
        ObservableList<Task> redoTemp = FXCollections.observableArrayList();
        for (Task t : internalList) {
            redoTemp.add(t);
        }
        redoStack.push(redoTemp);
        this.internalList.setAll(replacement);
    }

    public void redo() throws EmptyStackException {
        ObservableList<Task> replacement = redoStack.pop();
        ObservableList<Task> undoTemp = FXCollections.observableArrayList();
        for (Task t : internalList) {
            undoTemp.add(t);
        }
        undoStack.push(undoTemp);
        this.internalList.setAll(replacement);
    }
  //@@author

    public void setTasks(UniqueTaskList replacement) {

      //@@author A0164440M
        ObservableList<Task> stackList = FXCollections.observableArrayList();
        if (!initialize) {
            for (Task t : internalList) {
                stackList.add(t);
            }
            undoStack.push(stackList);
        } else {
            initialize = !initialize;
        }

      //@@author

        this.internalList.setAll(replacement.internalList);
    }

    public void setTasks(List<? extends ReadOnlyTask> tasks) throws DuplicateTaskException, IllegalTimeException {
        final UniqueTaskList replacement = new UniqueTaskList();

        for (final ReadOnlyTask task : tasks) {
            replacement.add(new Task(task));
        }
        setTasks(replacement);
    }

    public UnmodifiableObservableList<Task> asObservableList() {
        return new UnmodifiableObservableList<>(internalList);
    }

    @Override
    public Iterator<Task> iterator() {
        return internalList.iterator();
    }

    @Override
    public boolean equals(Object other) {
        return other == this // short circuit if same object
                || (other instanceof UniqueTaskList // instanceof handles nulls
                && this.internalList.equals(
                ((UniqueTaskList) other).internalList));
    }

    @Override
    public int hashCode() {
        return internalList.hashCode();
    }

    /**
     * Signals that an operation would have violated the 'no duplicates' property of the list.
     */
    public static class DuplicateTaskException extends DuplicateDataException {
        protected DuplicateTaskException() {
            super("Operation would result in duplicate persons");
        }
    }

    /**
     * Signals that an operation targeting a specified person in the list would fail because
     * there is no such matching person in the list.
     */
    public static class TaskNotFoundException extends Exception {}

    public static class IllegalTimeException extends CommandException {

        public IllegalTimeException(String message) {
            super(message);
        }
    }

}

@@ -1053,16 +1053,18 @@
 oolean i
+sI
 nitializ
@@ -1064,16 +1064,17 @@
 itialize
+d
  = true;
@@ -5672,32 +5672,122 @@
 bleArrayList();%0A
+        %0A        //To prevent empty list been pushed into undoStack during initialization%0A
         if (!ini
@@ -5784,16 +5784,18 @@
   if (!i
+sI
 nitializ
@@ -5795,16 +5795,17 @@
 itialize
+d
 ) %7B%0A    
@@ -5959,16 +5959,18 @@
        i
+sI
 nitializ
@@ -5974,13 +5974,16 @@
 lize
+d
  = !i
+sI
 niti
@@ -5987,16 +5987,17 @@
 itialize
+d
 ;%0A      

Add some comment