package be.ac.ulb.code.wit.resources.serializers;

import java.io.Reader;

import org.apache.abdera.model.Entry;
import org.apache.abdera.parser.ParseException;
import org.apache.commons.lang3.StringUtils;

import be.ac.ulb.code.wit.contacts.Contact;
import be.ac.ulb.code.wit.contacts.Contacts;
import be.ac.ulb.code.wit.contacts.Contacts.IdentifiedContact;

/**
 * An atom deserializer that create, update or delete 
 * contacts, using data from atom entries.
 * 
 * @author fpicalausa
 *
 */
public class ContactDeserializer {
	private final Contacts contacts;
	private IdentifiedContact identifiedContact;
	private Contact contact;
	private boolean hasContact;
	private String group = null;
	
	/**
	 * Set the contact referred to by this deserializer. 
	 * @throws IllegalStateException if a contact had already been set.
	 */
	private void setContact(IdentifiedContact identifiedContact) throws IllegalStateException {
		if (hasContact) {
			throw new IllegalStateException();
		}
		
		this.identifiedContact = identifiedContact;
		this.contact = identifiedContact.getContact();
		this.hasContact = true;
	}

	/**
	 * Constructs an atom deserializer that update an existing contact 
	 * with data from atom entries.
	 * @param contacts The contacts list to be updated
	 * @param existingContact An existing contact to be updated.
	 * @param group The group of contacts from which the atom document is deserialized.
	 * @throws NotFoundException if the specified group is not valid
	 */
	public ContactDeserializer(final Contacts contacts, 
			final IdentifiedContact existingContact,
			final String group) throws NotFoundException {
		super();
		
		ContactSerializer.checkGroup(group);
		
		this.contacts = contacts;
		this.group = group;

		if (existingContact != null) {
			setContact(existingContact);
		}
	}

	/**
	 * Constructs an atom deserializer that creates new contacts
	 * with data from atom entries.
	 * @param contacts The contacts list to be updated
	 * @param existingContact An existing contact to be updated.
	 * @param group The group of contacts from which the atom document is deserialized.
	 * @throws NotFoundException if the specified group is not valid
	 */
	public ContactDeserializer(final Contacts contacts, final String group) {
		this(contacts, (IdentifiedContact)null, group);
	}

	/**
	 * Returns the contact under update, or the newly create contact if
	 * no contact had been specified when constructing the ContactDeserializer.
	 * @return the contact under update, or the newly create contact.
	 * @throws IllegalStateException if no contact has been provided to the
	 * 				deserializer, and no contact has been created from
	 * 				an atom entry after.
	 */
	public IdentifiedContact getIdentifiedContact() throws IllegalStateException {
		if (hasContact) {
			return identifiedContact;
		}
		throw new IllegalStateException("No contact has been created!");
	}

	/**
	 * Update our current contact with the given information
	 */
	private void setContactDetails(String firstName, String lastName, String email) {
		contact.setFirstName(firstName);
		contact.setLastName(lastName);
		contact.setEmail(email);
	}

	/**
	 * Add our current contact to the group
	 */
	public void setContactGroup() {
		contacts.addContactToGroup(identifiedContact, group);
	}

	/**
	 * Remove our current contact from the group
	 */
	public void removeContact() {
		contacts.removeFromGroup(identifiedContact, group);
	}
    
	/**
	 * Parse the given atom:id to retrieve an existing instance of
	 * the contact.
	 */
    private boolean updateContactFromId(String id) {
    	boolean result;
    	
    	try {
    		if (!hasContact) {
    			this.identifiedContact = 
    					ContactSerializer.getContactFromId(id, contacts);
    			this.contact = this.identifiedContact.getContact();
    			this.hasContact = true;
    			result = true;
    		}
    		else {
    			result = false;
    		}
		}
		catch (IllegalArgumentException e) { 
			result = false;
		}
    	
    	return result;
    }

    /**
     * Update the current contact with data from the given atom entry.
     * If checkid is <tt>true</tt> the atom:id field of the entry
     * if checked for correspondence with an existing contact. If
     * no contact had been created and the atom:id corresponds to
     * a contact, that contact is update instead of creating a new one.
     * 
     * @param entry The atom entry to be parsed
     * @param checkid Whether the atom:id field should be parsed or not.
     */
	public void updateContact(Entry entry, boolean checkid) {
		String firstName, lastName, email, displayName;
        boolean hasDisplayName;

		//Fetch the data for building a new contact
		firstName = AtomHelpers.getExtensionElementText(entry, AtomHelpers.ExtensionElements.CONTACT_FIRSTNAME);
		lastName = AtomHelpers.getExtensionElementText(entry, AtomHelpers.ExtensionElements.CONTACT_LASTNAME);
		email = AtomHelpers.getExtensionElementText(entry, AtomHelpers.ExtensionElements.CONTACT_EMAIL);
        hasDisplayName = !StringUtils.isBlank(entry.getTitle());
		displayName = hasDisplayName ? entry.getTitle().trim() : null;

		if (checkid && entry.getId() != null) {
			updateContactFromId(entry.getId().toString());
		}
		
		//Create/Update the contact from the parsed information
		updateContact(firstName, lastName, email, displayName, hasDisplayName);
		
	}

    /**
     * Update the current contact with the given parsed data
     */
	private void updateContact(String firstName, String lastName, String email,
			String displayName, boolean hasDisplayName) {
		try {
			if (!hasContact) {
				if (!hasDisplayName) {
					displayName = Contact.computeDisplayName(firstName, lastName, email);
				}
				setContact(contacts.addContact(displayName));
			}
			else if (hasDisplayName) {
	            contact.setDisplayName(displayName);
	        }
		}
		catch (IllegalArgumentException e) {
			//Bad display name
			throw new MalformedRequestException(e);
		}
		
		//Update all information
        setContactDetails(firstName, lastName, email);
		
        //Ensure that the contact is in the right group
		contacts.addContactToGroup(identifiedContact, group);
	}

    /**
     * Update the current contact with data from the given atom entry.
     * If checkid is <tt>true</tt> the atom:id field of the entry
     * if checked for correspondence with an existing contact. If
     * no contact had been created and the atom:id corresponds to
     * a contact, that contact is update instead of creating a new one.
     * 
     * @param entry The atom entry to be parsed
     * @param checkid Whether the atom:id field should be parsed or not.
     */
	public void updateContact(Reader data, boolean checkid) {
		try {
			updateContact(AtomHelpers.parseEntry(data), checkid);
		}
		catch (ParseException e) {
			throw new MalformedRequestException(e);
		}
	}
}
