package seedu.address.model;

import javafx.collections.ObservableList;
import seedu.address.model.person.DatedTask;
import seedu.address.model.person.ReadOnlyDatedTask;
import seedu.address.model.person.UniquePersonList;
import seedu.address.model.tag.Tag;
import seedu.address.model.tag.UniqueTagList;

import java.util.*;
import java.util.stream.Collectors;

/**
 * Wraps all data at the address-book level
 * Duplicates are not allowed (by .equals comparison)
 */
public class AddressBook implements ReadOnlyAddressBook {

    private final UniquePersonList persons;
    private final UniquePersonList undatedList;
    private final UniqueTagList tags;

    {
        persons = new UniquePersonList();
        undatedList = new UniquePersonList();
        tags = new UniqueTagList();
    }

    public AddressBook() {}

    /**
     * Persons and Tags are copied into this addressbook
     */
    public AddressBook(ReadOnlyAddressBook toBeCopied) {
        this(toBeCopied.getUniquePersonList(), toBeCopied.getUniqueTagList());
    }

    /**
     * Persons and Tags are copied into this addressbook
     */
    public AddressBook(UniquePersonList persons, UniqueTagList tags) {
        resetData(persons.getInternalList(), tags.getInternalList());
    }

    public static ReadOnlyAddressBook getEmptyAddressBook() {
        return new AddressBook();
    }

//// list overwrite operations

    public ObservableList<DatedTask> getPersons() {
        return persons.getInternalList();
    }
    
    public ObservableList<DatedTask> getUndatedTasks() {
        return undatedList.getInternalList();
    }

    public void setPersons(List<DatedTask> persons) {
        this.persons.getInternalList().setAll(persons);
    }
    
    public void setUndatedTaskList(List<DatedTask> persons) {
        this.undatedList.getInternalList().setAll(persons);
    }

    public void setTags(Collection<Tag> tags) {
        this.tags.getInternalList().setAll(tags);
    }

    public void resetData(Collection<? extends ReadOnlyDatedTask> newPersons, Collection<Tag> newTags) {
        setPersons(newPersons.stream().map(DatedTask::new).collect(Collectors.toList()));
        setTags(newTags);
    }

    public void resetData(ReadOnlyAddressBook newData) {
        resetData(newData.getPersonList(), newData.getTagList());
    }

//// person-level operations

    /**
     * Adds a person to the address book.
     * Also checks the new person's tags and updates {@link #tags} with any new tags found,
     * and updates the Tag objects in the person to point to those in {@link #tags}.
     *
     * @throws UniquePersonList.DuplicatePersonException if an equivalent person already exists.
     */
    public void addPerson(DatedTask p) throws UniquePersonList.DuplicatePersonException {
        syncTagsWithMasterList(p);
        if (checkIfDated(p)){
            persons.add(p);
        }
        else{
            undatedList.add(p);
        }
    }
    
    private boolean checkIfDated(DatedTask d){
        if(d.getDate() == null && d.getTime() == null){
            return false;
        }
        else{
            return true;
        }
    }

    /**
     * Ensures that every tag in this person:
     *  - exists in the master list {@link #tags}
     *  - points to a Tag object in the master list
     */
    private void syncTagsWithMasterList(DatedTask person) {
        final UniqueTagList personTags = person.getTags();
        tags.mergeFrom(personTags);

        // Create map with values = tag object references in the master list
        final Map<Tag, Tag> masterTagObjects = new HashMap<>();
        for (Tag tag : tags) {
            masterTagObjects.put(tag, tag);
        }

        // Rebuild the list of person tags using references from the master list
        final Set<Tag> commonTagReferences = new HashSet<>();
        for (Tag tag : personTags) {
            commonTagReferences.add(masterTagObjects.get(tag));
        }
        person.setTags(new UniqueTagList(commonTagReferences));
    }

    public boolean removePerson(ReadOnlyDatedTask key) throws UniquePersonList.PersonNotFoundException {
        if (persons.remove(key)) {
            return true;
        } else {
            throw new UniquePersonList.PersonNotFoundException();
        }
    }
    
    public boolean removeUndatedTask(ReadOnlyDatedTask key) throws UniquePersonList.PersonNotFoundException {
        if (persons.remove(key)) {
            return true;
        } else {
            throw new UniquePersonList.PersonNotFoundException();
        }
    }

//// tag-level operations

    public void addTag(Tag t) throws UniqueTagList.DuplicateTagException {
        tags.add(t);
    }

//// util methods

    @Override
    public String toString() {
        return persons.getInternalList().size() + " persons, " + tags.getInternalList().size() +  " tags";
        // TODO: refine later
    }

    @Override
    public List<ReadOnlyDatedTask> getPersonList() {
        return Collections.unmodifiableList(persons.getInternalList());
    }
    
    @Override
    public List<ReadOnlyDatedTask> getUndatedTaskList() {
        return Collections.unmodifiableList(undatedList.getInternalList());
    }

    @Override
    public List<Tag> getTagList() {
        return Collections.unmodifiableList(tags.getInternalList());
    }

    @Override
    public UniquePersonList getUniquePersonList() {
        return this.persons;
    }
    
    @Override
    public UniquePersonList getUniqueUndatedTaskList() {
        return this.undatedList;
    }

    @Override
    public UniqueTagList getUniqueTagList() {
        return this.tags;
    }


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

    @Override
    public int hashCode() {
        // use this method for custom fields hashing instead of implementing your own
        return Objects.hash(persons, undatedList, tags);
    }
}

@@ -993,16 +993,55 @@
 nList(),
+ toBeCopied.getUniqueUndatedTaskList(),
  toBeCop
@@ -1193,16 +1193,47 @@
 persons,
+ UniquePersonList undatedTasks,
  UniqueT
@@ -1291,16 +1291,48 @@
 lList(),
+ undatedTasks.getInternalList(),
  tags.ge
@@ -2143,63 +2143,240 @@
 ns, 
-Collection%3CTag%3E newTags) %7B%0A        setPersons(newPerson
+%0A            Collection%3C? extends ReadOnlyDatedTask%3E newUndatedTasks,%0A            Collection%3CTag%3E newTags) %7B%0A        setPersons(newPersons.stream().map(DatedTask::new).collect(Collectors.toList()));%0A        setPersons(newUndatedTask
 s.st
@@ -2565,16 +2565,46 @@
 nList(),
+ newData.getUndatedTaskList(),
  newData
@@ -4705,39 +4705,43 @@
 n %7B%0A        if (
-persons
+undatedList
 .remove(key)) %7B%0A

add UndatedTasks in AddressBook (2)