package seedu.address.model.task;

import java.util.Collections;
import java.util.Iterator;
import java.util.List;

import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import seedu.address.commons.core.UnmodifiableObservableList;
import seedu.address.commons.exceptions.DuplicateDataException;
import seedu.address.commons.exceptions.IllegalValueException;
import seedu.address.commons.util.CollectionUtil;

/**
 * A list of tasks 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();
    private static UniqueTaskList instance;

//@@author A0147996E
    public static UniqueTaskList getInstance() {
        if (instance == null) {
            instance = new UniqueTaskList();
        }
        return instance;
    }

    public static UniqueTaskList getStub() {
        return new UniqueTaskList();
    }

    private UniqueTaskList() {}
//@@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);
    }

    /**
     * Returns the Task of the given index
     */
    public ReadOnlyTask get(int index) {
        assert internalList.size() >= index + 1;
        assert index >= 0;
        return internalList.get(index);
    }

    /**
     * Adds a task to the list.
     *
     * @throws DuplicateTaskException if the task to add is a duplicate of an existing task in the list.
     */
    public void add(Task toAdd) throws DuplicateTaskException {
        assert toAdd != null;
        if (contains(toAdd)) {
            throw new DuplicateTaskException();
        }
        internalList.add(toAdd);
    }

    /**
     * Deletes a task in the list.
     */
    public void delete(ReadOnlyTask toDelete) {
        assert toDelete != null;
        internalList.remove(toDelete);
    }

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

        Task taskToUpdate = internalList.get(index);
        if (!taskToUpdate.equals(editedTask) && internalList.contains(editedTask)) {
            throw new DuplicateTaskException();
        }
        if (editedTask.isEvent()) {
            try {
                if (taskToUpdate.isRecurring() || editedTask.isRecurring()) {
                    taskToUpdate = new RecurringEvent(editedTask);
                } else {
                    taskToUpdate = new Event(editedTask);
                }
            } catch (IllegalValueException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        } else {
            if (taskToUpdate.isRecurring() || editedTask.isRecurring()) {
                taskToUpdate = new RecurringTask(editedTask);
            } else {
                taskToUpdate = new Task(editedTask);
            }
        }
        // TODO: The code below is just a workaround to notify observers of the updated task.
        // 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;
        final boolean taskFoundAndDeleted = internalList.remove(toRemove);
        if (!taskFoundAndDeleted) {
            throw new TaskNotFoundException();
        }
        return taskFoundAndDeleted;
    }

    /**
     * clear the UniqueTaskList
     */
    public void clear() {
        internalList.clear();
    }

    public void setTasks(UniqueTaskList replacement) {
        this.internalList.setAll(replacement.internalList);
    }

    public void setTasks(List<? extends ReadOnlyTask> tasks) throws DuplicateTaskException {
        final UniqueTaskList replacement = new UniqueTaskList();
        for (final ReadOnlyTask task : tasks) {
            if (task.isEvent()) {
                try {
                    replacement.add(new Event(task));
                } catch (IllegalValueException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
            } else if (task.isRecurring()) {
                try {
                    replacement.add(new RecurringTask(task));
                } catch (IllegalValueException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
            } else {
                replacement.add(new Task(task));
            }
        }
        setTasks(replacement);
    }

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

    //@@author A0147984L
    /**
     * Sort the UniqueTaskList. In the priority of
     * due date > priority > due time > name > tag
     */
    public void sort() {
        Collections.sort(internalList, (Task t1, Task t2) -> t1.getTag().compareTo(t2.getTag()));
        Collections.sort(internalList, (Task t1, Task t2) -> t1.getName().compareTo(t2.getName()));
        Collections.sort(internalList, (Task t1, Task t2) -> t1.getTime().compareTo(t2.getTime()));
        Collections.sort(internalList, (Task t1, Task t2) -> -t1.getPriority().compareTo(t2.getPriority()));
        Collections.sort(internalList, (Task t1, Task t2) -> t1.getDate().compareTo(t2.getDate()));
    }
    //@@author

    @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 {
        public DuplicateTaskException() {
            super("Operation would result in duplicate tasks");
        }
    }

    /**
     * Signals that an operation targeting a specified task in the list would fail because
     * there is no such matching task in the list.
     */
    public static class TaskNotFoundException extends Exception {
        public TaskNotFoundException() {
            super("The task requested is not found");
        }
    }
}

@@ -4840,32 +4840,346 @@
  (task.isEvent()
+ && task.isRecurring()) %7B%0A                try %7B%0A                    replacement.add(new RecurringEvent(task));%0A                %7D catch (IllegalValueException e) %7B%0A                    // TODO Auto-generated catch block%0A                    e.printStackTrace();%0A                %7D%0A            %7D else if (task.isEvent()
 ) %7B%0A            

support adding of recurring event