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

import java.net.URI;
import java.net.URISyntaxException;
import java.util.HashMap;
import java.util.Map;

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;
import be.ac.ulb.code.wit.resources.ServicePaths;

import com.sun.jersey.api.uri.UriTemplate;

public abstract class ContactSerializer {
	protected final Contacts contacts;
	protected String group = null;

	private static final UriTemplate ContactIdTempate = new UriTemplate(
			ServicePaths.getFullPath(ServicePaths.CONTACT));

	/**
	 * Retrieves a contact in a contacts list by its identifier.
	 * 
	 * @param contactId
	 *            The identifier of the contact to be retrieved.
	 * @param contacts
	 *            The contacts list in which the specified contact is to be
	 *            found.
	 * @return The contact as it appears in the contacts list.
	 * @throws NotFoundException
	 *             The specified contact could not be found in the contacts
	 *             list.
	 */
	private static IdentifiedContact getContact(final int contactId,
			Contacts contacts) throws NotFoundException {
		IdentifiedContact result = contacts.getContact(contactId);
		if (result == null) {
			throw new NotFoundException(
					"The specified id could not be matched to a contact.");
		}
		return result;
	}

	/**
	 * Retrieves a contact in a contacts list by its identifier.
	 * 
	 * @param contactId
	 *            The identifier of the contact to be retrieved.
	 * @param contacts
	 *            The contacts list in which the specified contact is to be
	 *            found.
	 * @return The contact as it appears in the contacts list.
	 * @throws NotFoundException
	 *             The specified contact could not be found in the contacts
	 *             list.
	 */
	public static IdentifiedContact getContact(final String contactId,
			Contacts contacts) throws NotFoundException {
		try {
			final int cid = Integer.parseInt(contactId);
			return getContact(cid, contacts);
		} catch (NumberFormatException e) {
			throw new NotFoundException(
					"The specified id has an incorrect format.", e);
		}
	}

	/**
	 * Retrieves a contact in a contacts list by and url-identifier, as it
	 * occurs in atom entry documents.
	 * 
	 * @param url
	 *            The url-identifier of the contact to be retrieved.
	 * @param contacts
	 *            The contacts list in which the specified contact is to be
	 *            found.
	 * @return The contact as it appears in the contacts list.
	 * @throws NotFoundException
	 *             The specified contact could not be found in the contacts
	 *             list.
	 */
	public static IdentifiedContact getContactFromId(final String url,
			Contacts contacts) throws NotFoundException {
		Map<String, String> groupValues = new HashMap<String, String>();
		if (ContactIdTempate.match(url, groupValues)) {
			return getContact(groupValues.get("contactid"), contacts);
		} else {
			throw new NotFoundException(
					"The specified id has an incorrect format.");
		}
	}

	/**
	 * Ensures that specified contact belongs to the group used by this
	 * serializer.
	 * 
	 * @param contact
	 *            The contact to be checked
	 * @return The contact passed as an argument if it belongs to the group of
	 *         the serializer. Throws a NotFoundException otherwise.
	 * @throws NotFoundException
	 *             The specified contact does not belong to the group.
	 */
	private IdentifiedContact checkContactInGroup(IdentifiedContact contact)
			throws NotFoundException {
		if (group != Contacts.GROUP_ALLCONTACTS) {
			if (!contacts.getContactGroups(contact).contains(group)) {
				throw new NotFoundException(
						"The specified contact does no belong to the requested group");
			}
		}
		return contact;
	}

	/**
	 * Retrieves a contact in a contacts list by its identifier.
	 * 
	 * @param contactId
	 *            The identifier of the contact to be retrieved.
	 * @return The contact as it appears in the contacts list.
	 * @throws NotFoundException
	 *             The specified contact could not be found in the contacts
	 *             list.
	 */
	public IdentifiedContact getContact(final String contactId)
			throws NotFoundException {
		return checkContactInGroup(getContact(contactId, contacts));
	}

	/**
	 * Construct a new ContactSerializer, using information from the given
	 * Contacts list and working in the context of the given group.
	 * 
	 * @param contacts
	 *            The contacts list to be used.
	 * @param group
	 *            The contacts group in which the serialization occurs.
	 * @throws NotFoundException
	 *             if the specified group is not valid.
	 */
	public ContactSerializer(final Contacts contacts, final String group)
			throws NotFoundException {
		super();
		this.contacts = contacts;
		checkGroup(group);

		this.group = group;
	}

	/**
	 * Check that a given group has a valid name, or is the default group with
	 * all contacts. Otherwise, throw a NotFoundException.
	 * 
	 * @param group
	 *            The group to be checked.
	 * @throws NotFoundException
	 *             if the specified group is not valid.
	 */
	public static void checkGroup(String group) {
		if (group != Contacts.GROUP_ALLCONTACTS) {
			if (!Contact.validGroupName(group)) {
				throw new NotFoundException();
			}
		}
	}

	public URI getContactURL(IdentifiedContact contact) {
		return getContactURL(contact, false, false);
	}

	public URI getContactURL(IdentifiedContact contact, boolean relative,
			boolean canonical) {
		return getContactURL(contact, relative,
				canonical ? Contacts.GROUP_ALLCONTACTS : group);
	}

	/**
	 * Retrieves the URI corresponding to the given contact, in the context
	 * group.
	 * 
	 * @param contact
	 *            The contact whose URI is to be retrieved.
	 * @return The URI of the contact.
	 */
	public URI getContactURL(IdentifiedContact contact, boolean relative,
			String group) {
		String uri;

		if (group == Contacts.GROUP_ALLCONTACTS) {
			uri = ServicePaths.getURI(ServicePaths.CONTACT, relative,
					"contactid", Integer.toString(contact.getContactId()));
		} else {
			uri = ServicePaths.getURI(ServicePaths.GROUP_CONTACT, relative,
					"contactid", Integer.toString(contact.getContactId()),
					"group", group);
		}
		try {
			return new URI(uri);
		} catch (URISyntaxException e) {
			// Should not happen because the URI are well know. Cannot recover
			// from this
			throw new IllegalStateException(e);
		}
	}

	protected String getGroupURL() {
		if (group == Contacts.GROUP_ALLCONTACTS) {
			return ServicePaths.getURI(ServicePaths.CONTACTS);
		}
		return ServicePaths.getURI(ServicePaths.GROUP, "group", group);
	}

	public abstract String makeStringEntry(IdentifiedContact contact);

	public abstract String makeFeed();
}
