/* 
 * E-XML Library:  For XML, XML-RPC, HTTP, and related.
 * Copyright (C) 2002-2008  Elias Ross
 * 
 * genman@noderunner.net
 * http://noderunner.net/~genman
 * 
 * 1025 NE 73RD ST
 * SEATTLE WA 98115
 * USA
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 * 
 * $Id$
 */

package net.noderunner.http;

import java.net.URL;
import java.util.Locale;

/**
 * This is an immutable implementation of the <code>MessageHeader</code> interface.
 */
public class MessageHeader
{
	/**
	 * Field name for the host header.
	 */
	public static final String FN_HOST = "host";

	/**
	 * Field name for the connection header.
	 */
	public static final String FN_CONNECTION = "connection";

	/**
	 * Field name for the content-length header.
	 */
	public static final String FN_CONTENT_LENGTH = "content-length";

	/**
	 * Field name for the content-type header.
	 */
	public static final String FN_CONTENT_TYPE = "content-type";

	/**
	 * Field name for the location header.  
	 * Typically used in redirects.
	 */
	public static final String FN_LOCATION = "location";

	/**
	 * Field name for the referer header.
	 */
	public static final String FN_REFERER = "referer";

	/**
	 * Field name for the user agent header.
	 */
	public static final String FN_TRANSFER_ENCODING = "transfer-encoding";

	/**
	 * Field name for the user agent header.
	 */
	public static final String FN_USER_AGENT = "user-agent";

	/**
	 * Field name for the server header.
	 */
	public static final String FN_SERVER = "server";

	/**
	 * Use this <code>MessageHeader</code> for sending chunked data.
	 */
	public static final MessageHeader MH_TRANSFER_ENCODING_CHUNKED = new MessageHeader(FN_TRANSFER_ENCODING, "chunked");

	/**
	 * Use this <code>MessageHeader</code> for indicating a keep-alive
	 * connection.
	 */
	public static final MessageHeader MH_CONNECTION_KEEP_ALIVE = new MessageHeader(FN_CONNECTION, "Keep-Alive");

	/**
	 * Use this <code>MessageHeader</code> for indicating connection close.
	 */
	public static final MessageHeader MH_CONNECTION_CLOSE = new MessageHeader(FN_CONNECTION, "close");

	/**
	 * Use this <code>MessageHeader</code> for indicating a URL encoded
	 * content type.
	 */
	public static final MessageHeader MH_URL_ENCODED = new MessageHeader(FN_CONTENT_TYPE, "application/x-www-form-urlencoded");

	/**
	 * Default user agent string for this library.
	 */
	public static final MessageHeader MH_USER_AGENT = new MessageHeader(FN_USER_AGENT, "ehttp/1.0");

	private String fieldName;
	private String fieldContent;
	private String toString;

	/**
	 * Parse using a <code>message-header</code> string.
	 * @throws HttpException if invalid HTTP message header data was used
	 * in initialization
	 */
	public static MessageHeader parse(String messageHeader)
		throws HttpException
	{
		if (messageHeader == null)
			throw new IllegalArgumentException("Null messageHeader string");
		int colonAt = messageHeader.indexOf(':');
		if (colonAt == -1)
			throw new HttpException("No message-header colon found");
		String fieldName = messageHeader.substring(0, colonAt).toLowerCase();
		String fieldValue;
		if (colonAt + 1 < messageHeader.length())
			fieldValue = messageHeader.substring(colonAt + 1).trim();
		else
			fieldValue = "";
		try {
    		return new MessageHeader(fieldName, fieldValue);
		} catch (RuntimeException e) {
			throw new HttpException(e);
		}
	}

	private void init(String fieldName, String fieldValue)
	{
		if (fieldName == null)
			throw new IllegalArgumentException("Null fieldName");
		if (fieldValue == null)
			throw new IllegalArgumentException("Null fieldValue");
		this.fieldName = fieldName.toLowerCase(Locale.ENGLISH);
		fieldContent = fieldValue.trim();
		if (!TokenUtil.isValidToken(fieldName))
			throw new IllegalArgumentException("Invalid characters in field-name " + fieldName);
	}

	/**
	 * Initialize using a <code>field-name</code> and <code>field-value</code>.
	 */
	public MessageHeader(String fieldName, String fieldValue)
	{
		init(fieldName, fieldValue);
	}

	public boolean equals(Object other) {
		if (this == other)
			return true;
		if (!(other instanceof MessageHeader))
			return false;
		MessageHeader h = (MessageHeader)other;
		return h.getFieldName().equals(getFieldName()) &&
			h.getFieldContent().equals(getFieldContent());
	}

	/**
	 * Returns the name of the header, which for consistency is in
	 * lower-case form.
	 */
	public String getFieldName() {
		return fieldName;
	}

	/**
	 * Returns the value of this header.
	 * In terms of RFC 2616, this is the header's <code>field-content</code>,
	 * which excludes trailing and following white-space.
	 */
	public String getFieldContent() {
		return fieldContent;
	}

	/**
	 * Returns the hashcode.
	 */
	public int hashCode() {
		return fieldName.hashCode() ^ fieldContent.hashCode();
	}
	
	/**
	 * Returns a new <code>Host</code> header, appropriate to the
	 * given URL.
	 */
	public static MessageHeader makeHostHeader(URL url) {
		if (url == null)
			throw new IllegalArgumentException("Null url");
		int port = url.getPort();
		String s;
		if (port == -1)
			s = url.getHost();
		else
			s = url.getHost() + ":" + port;
		return new MessageHeader(MessageHeader.FN_HOST, s);
	}


	/**
	 * Should return this <code>MessageHeader</code> as:
	 * <pre>
	 * getFieldName() + ':' + getFieldContent();
	 * </pre>
	 */
	public String toString() {
		if (toString == null)
			toString = getFieldName() + ':' + getFieldContent();
		return toString;
	}
}
