package be.ac.ulb.code.wit.contacts;

import java.util.Date;
import java.util.HashSet;
import java.util.Observable;
import java.util.Set;

import org.apache.commons.lang3.StringUtils;

/**
 * A particular Contact. The user can define the firstName, lastName
 * and email address of the contact. A displayName
 * intended for representation in a user interface can be defined.
 * Finally, various groups can be attributed to a given user.
 */
public class Contact extends Observable {
	private String firstName;
    private String lastName;
    private String displayName;
	private Date lastModified = new Date();
	private String email;
	private Set<String> groups = new HashSet<String>();
	
	/**
	 * Determines if the given displayName is valid for object construction 
	 * (i.e. is not empty, whitespaces only, or null). 
	 * @param displayName The displayName to be checked.
	 * @return Returns true if the given displayName can be used to
	 *   	   construct a contact.
	 */
	public static boolean validDisplayName(String displayName) {
        return !StringUtils.isBlank(displayName);
	}
    
	/**
	 * Append the given text to the stringBuilder, separated by the
	 * separator character if the given stringBuilder is not empty. 
	 */
    private static StringBuilder appendWithSeparator(StringBuilder stringBuilder, char separator, String text) {
        if (stringBuilder.length() != 0) {
            stringBuilder.append(separator);
        }
        return stringBuilder.append(text);
    }

    /**
     * Compute a display name, given a first and last name.
     * @param firstName The first name of the contact.
     * @param lastName The last name of the contact.
     * @return A displayName that can be used to construct a new Contact object.
     * @throws IllegalArgumentException if all arguments are
     * 			empty, whitespace only, or null Strings.
     */
    public static String computeDisplayName(final String firstName, final String lastName) throws IllegalArgumentException {
        final StringBuilder displayName = new StringBuilder();

        if (!StringUtils.isBlank(firstName)) {
            appendWithSeparator(displayName, ' ', firstName.trim());
        }

        if (!StringUtils.isBlank(lastName)) {
            appendWithSeparator(displayName, ' ', lastName.trim());
        }

        if (displayName.length() != 0) {
            return displayName.toString();
        }
        throw new IllegalArgumentException("The first name or last name must not be blank");
    }

    /**
     * Compute a display name, given a first and last name, and an email address.
     * @param firstName The first name of the contact.
     * @param lastName The last name of the contact.
     * @param email The email of the contact.
     * @return A displayName that can be used to construct a new Contact object.
     * @throws IllegalArgumentException if all arguments are
     * 			empty, whitespace only, or null Strings.
     */
    public static String computeDisplayName(final String firstName, final String lastName, final String email) throws IllegalArgumentException {
        String displayName;
        
        try {
            displayName = computeDisplayName(firstName,lastName);
        }
        catch (IllegalArgumentException e) {
            displayName = null;
        }
        
        if (displayName == null && !StringUtils.isBlank(email)) {
            displayName = email.trim();
        }

        if (displayName.isEmpty()) {
            throw new IllegalArgumentException("At least one of the first name, last name, or email must not be blank.");
        }

        return displayName;
    }
    
    /**
     * Constructs a new Contact with the given display name.
     * @param displayName A name that can be used to represent the contact in a
     * 					  user interface. This name cannot be blank.
     * @throws IllegalArgumentException if the given display name
     * 		   is empty, whitespaces only, or null.
     */
	public Contact(final String displayName) throws IllegalArgumentException {
		super();
		setDisplayNameInternal(displayName);
	}

	/**
	 * Returns a name that can be used to represent the contact in a user interface.
	 * @return a name that can be used to represent the contact in a user interface.
	 */
	public String getDisplayName() {
		return displayName;
	}
	
	/**
	 * Returns the email address of the contact. 
	 * @return the email address of the contact.
	 */
	public String getEmail() {
		return email;
	}

	/**
	 * Returns the first name of the contact. 
	 * @return the first name of the contact.
	 */
	public String getFirstName() {
		return firstName;
	}

	/**
	 * Returns the date at which this contact was last modified.
	 * @return Returns the date at which this contact was last modified.
	 */
	public Date getLastModified() {
		return new Date(lastModified.getTime());
	}

	/**
	 * Returns the last name of the contact. 
	 * @return the last name of the contact.
	 */
	public String getLastName() {
		return lastName;
	}
	
	/**
	 * Retrieves the groups to which the contact belong.
	 * @return The groups to which the contact belong.
	 */
	public Set<String> getGroups() {
		return new HashSet<String>(groups);
	}
	
	/**
	 * Sets the display name of this contact after checking its validity.
	 * @throws IllegalArgumentException if the display name is blank.
	 */
	private void setDisplayNameInternal(final String displayName) throws IllegalArgumentException {
        if (StringUtils.equals(displayName, this.displayName)) { return; }

		if (!validDisplayName(displayName)) {
			throw new IllegalArgumentException("Display name cannot be blank");
		}
		
		this.displayName = displayName.trim();
	}

	/**
	 * Set a new name that represents the contact in a user interface.
	 * @param displayName The new name that represents the contact in a user interface.
	 */
	public void setDisplayName(final String displayName) {
		setDisplayNameInternal(displayName);
		setChanged();
	}

	/**
	 * Set the email of the contact.
	 * @param displayName The new email of the contact.
	 */
	public void setEmail(String email) {
		if (StringUtils.isBlank(email)) { email = null; }
		if (StringUtils.equals(email, this.email)) { return; }
		this.email = email;
		setChanged();
	}
	
	/**
	 * Set the first name of the contact.
	 * @param displayName The new first name of the contact.
	 */
	public void setFirstName(String firstName) {
		if (StringUtils.isBlank(firstName)) { firstName = null; }
		if (StringUtils.equals(firstName, this.firstName)) { return; }
		this.firstName = firstName;
		setChanged();
	}
	
	/**
	 * Set the last name of the contact.
	 * @param displayName The new first name of the contact.
	 */
	public void setLastName(String lastName) {
		if (StringUtils.isBlank(lastName)) { lastName = null; }
		if (StringUtils.equals(lastName, this.lastName)) { return; } 
		this.lastName = lastName;
		setChanged();
	}
	
	/**
	 * Determines if the given group name is valid (i.e. if can be used, not if it exists).
	 * @param group The name of the group to be checked.
	 * @return <tt>true</tt> if the given String is valid as a group name .
	 */
	public static boolean validGroupName(String group) {
		return !StringUtils.isBlank(group);
	}
	
	/**
	 * Returns a group name where blanks have been trimmed.
	 * @throws IllegalArgumentException if the group name is blank.
	 */
	private String normalizeGroupName(String group) throws IllegalArgumentException {
		if (!validGroupName(group)) {
			throw new IllegalArgumentException("Invalid group name");
		}
		
		return group.trim();
	}
	
	/**
	 * Add the contact to the specified group
	 * @param group The (non-empty) name of the group to which the user is to be added.
	 * @throws IllegalArgumentException if the group name is empty.
	 */
	public void addGroup(String group) throws IllegalArgumentException {
		group = normalizeGroupName(group);
		if (!groups.contains(group)) {
			groups.add(group);
			setChanged();
		}
	}
	
	/**
	 * Remove the contact from the specified group
	 * @param group The (non-empty) name of the group from which the user is to be removed.
	 * @throws IllegalArgumentException if the group name is empty.
	 */
	public void deleteGroup(String group) throws IllegalArgumentException {
		group = normalizeGroupName(group);
		if (groups.contains(group)) {
			groups.remove(group);
			setChanged();
		}
	}
	
	/**
	 * Determines if the contact pertains to a given group.
	 * @param group The name of the group to test. 
	 * @return <tt>true</tt> if the Contact pertains to the given <tt>group</tt>
	 * @throws IllegalArgumentException if the group name is empty.
	 */
	public boolean isInGroup(String group) throws IllegalArgumentException {
		group = normalizeGroupName(group);
		return groups.contains(group);
	}
	
	/**
	 * Indicates that this instance has been modified. 
	 * Update the lastModified date accordingly, and notify
	 * observers.
	 */
	@Override
	protected void setChanged() {
		this.lastModified = new Date();
		super.setChanged();
		super.notifyObservers();
	}
}
