/**
 * Copyright 2010 JBoss Inc
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.eclipse.webdav.http.client;

import java.util.Enumeration;
import java.util.Hashtable;
import org.eclipse.webdav.client.Policy;
import org.eclipse.webdav.internal.kernel.utils.Assert;

/**
 * Parses an HTTP Content-Type entity-header field. See section 14.18 of
 * RFC2068 for more information on this field.
 * <p>
 * <b>Note:</b> This class/interface is part of an interim API that is still under 
 * development and expected to change significantly before reaching stability. 
 * It is being made available at this early stage to solicit feedback from pioneering 
 * adopters on the understanding that any code that uses this API will almost 
 * certainly be broken (repeatedly) as the API evolves.
 * </p>
 */
public class ContentType {
	private String contentType;
	private int position;
	private boolean foundDelim;

	private String type;
	private String subtype;
	private Hashtable parameters = new Hashtable(5);

	/**
	 * Parses the given HTTP Content-Type entity-header field. For example,
	 * if the content type is "text/xml; charset="ISO-8859-4"" the type is
	 * "text", the subtype is "xml", and the value of the attribute "charset"
	 * is "ISO-8859-4".
	 *
	 * @param contentType the value of the content type field to parse
	 * @throws            IllegalArgumentException if the content type is
	 *                    malformed
	 */
	public ContentType(String contentType) throws IllegalArgumentException {
		Assert.isNotNull(contentType);
		this.contentType = contentType;
		position = 0;
		parse();
	}

	private void checkPosition() throws IllegalArgumentException {
		if (position >= contentType.length())
			illegalArgument();
	}

	/**
	 * Returns an <code>Enumeration</code> of this content type's attributes.
	 * For example, if the content type is "text/xml; charset="ISO-8859-4"",
	 * it has one attribute namely, "charset".
	 *
	 * @return an <code>Enumeration</code> of <code>String</code>s
	 */
	public Enumeration getAttributes() {
		return parameters.keys();
	}

	/**
	 * Returns this content type's subtype. For example, if the content type
	 * is "text/xml; charset="ISO-8859-4"", the the subtype is "xml".
	 *
	 * @return this content type's subtype
	 */
	public String getSubtype() {
		return subtype;
	}

	/**
	 * Returns this content type's type. For example, if the content type is
	 * "text/xml; charset="ISO-8859-4"", the the type is "text".
	 *
	 * @return this content type's type
	 */
	public String getType() {
		return type;
	}

	/**
	 * Returns the value of the given attribute for this content type. For
	 * example, if the content type is "text/xml; charset="ISO-8859-4"", then
	 * the value for the attribute "charset" is "IS-8859-4".
	 *
	 * @return the value of the given attribute for this content type
	 */
	public String getValue(String attribute) {
		return (String) parameters.get(attribute);
	}

	private void illegalArgument() throws IllegalArgumentException {
		throw new IllegalArgumentException(Policy.bind("exception.malformedContentType", contentType)); //$NON-NLS-1$
	}

	private String nextToken(char delim, boolean trim) throws IllegalArgumentException {
		int start = position;
		int end = start;

		boolean done = false;
		boolean trimming = false;

		while (!done) {
			if (position == contentType.length()) {
				done = true;
				foundDelim = false;
			} else {
				char c = contentType.charAt(position);
				if (Character.isWhitespace(c)) {
					if (trim) {
						trimming = true;
					} else {
						illegalArgument();
					}
				} else {
					if (c == delim) {
						done = true;
						foundDelim = true;
					} else {
						if (trimming) {
							illegalArgument();
						}
						end = position + 1;
					}
				}
				++position;
			}
		}

		return contentType.substring(start, end);
	}

	private void parse() throws IllegalArgumentException {
		checkPosition();
		skipWhiteSpace();
		checkPosition();
		type = nextToken('/', false);
		checkPosition();
		subtype = nextToken(';', true);
		skipWhiteSpace();
		if (foundDelim) {
			checkPosition();
		}

		while (position < contentType.length()) {
			String attribute = nextToken('=', false);
			checkPosition();
			String value = nextToken(';', true);
			if (value.startsWith("\"") && value.endsWith("\"")) { //$NON-NLS-1$ //$NON-NLS-2$
				value = value.substring(1, value.length() - 1);
			}
			skipWhiteSpace();
			if (foundDelim) {
				checkPosition();
			}
			parameters.put(attribute, value);
		}
	}

	private void skipWhiteSpace() {
		while (position < contentType.length() && Character.isWhitespace(contentType.charAt(position))) {
			++position;
		}
	}
}
