/* 
 * 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.io.IOException;
import java.io.InputStream;
import java.io.Writer;
import java.net.URL;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Locale;

/**
 * Contains a list of message headers.
 * 
 * @author Elias Ross
 */
public class MessageHeaders {

	private List<MessageHeader> headers;
	
	/**
	 * Constructs a new MessageHeaders.
	 * @param h array of headers
	 */
	public MessageHeaders(MessageHeader h[]) {
		this(new LinkedList<MessageHeader>(Arrays.asList(h)));
	}

	/**
	 * Constructs a new MessageHeaders.
	 * @param headers list of headers
	 */
	public MessageHeaders(List<MessageHeader> headers) {
		if (headers == null)
			throw new IllegalArgumentException("null headers");
		this.headers = headers;
	}
	
	/**
	 * Constructs a new MessageHeaders with no headers.
	 */
	public MessageHeaders() {
		this(new ArrayList<MessageHeader>());
	}
	
	/**
	 * Creates and returns default HTTP headers based on a URL.
	 * By default, there are only two headers.
	 * The first is the <code>host</code> header, the second is a
	 * connection keep-alive header.
	 *
	 * @see MessageHeader#makeHostHeader
	 * @see MessageHeader#MH_CONNECTION_KEEP_ALIVE
	 */
	public static MessageHeaders defaultHeaders(URL url) {
		MessageHeaders mh = new MessageHeaders();
		mh.add(MessageHeader.MH_USER_AGENT);
		mh.add(MessageHeader.makeHostHeader(url));
		mh.add(MessageHeader.MH_CONNECTION_KEEP_ALIVE);
		return mh;
	}
	
	private static boolean lws(char c) {
		return c == ' ' || c == '\t';
	}

	/**
	 * Returns a list of headers from a binary input stream.
	 * For every line read, until a blank line is reached,
	 * a new <code>MessageHeader</code> instance is created.
	 *
	 * @throws HttpException if invalid HTTP message header data was found
	 */
	public static MessageHeaders readHeaders(InputStream is)
		throws IOException
	{
		List<MessageHeader> hl = new ArrayList<MessageHeader>(); 
		String curhead = null;
		while (true) {
			String line = HttpUtil.readHttpLine(is);
			if (line.length() == 0)
				break;
			if (lws(line.charAt(0))) { // continuation
				if (curhead == null)
					throw new IOException("Malformed header: " + line);
				curhead = curhead + line;
			} else {
				if (curhead != null)
            		hl.add(MessageHeader.parse(curhead));
				curhead = line;
			}
		}
		if (curhead != null)
    		hl.add(MessageHeader.parse(curhead));
		return new MessageHeaders(hl);
	}
	
	/**
	 * Sets a new header to the existing list array.
	 * If the field-name of <code>newHeader</code> already exists,
	 * the existing field-value is replaced.
	 */
	public void set(MessageHeader newHeader) {
		if (newHeader == null)
			throw new IllegalArgumentException("null newHeader");
		for (Iterator<MessageHeader> i = headers.iterator(); i.hasNext(); ) {
			MessageHeader h = i.next();
			if (h.getFieldName().equals(newHeader.getFieldName())) {
				i.remove();
			}
		}
		
		headers.add(newHeader);
	}

	/**
	 * Adds a new header to the existing list array.
	 * If the field-name of <code>newHeader</code> already exists,
	 * the existing field-value is replaced.
	 */
	public void add(MessageHeader newHeader) {
		if (newHeader == null)
			throw new IllegalArgumentException("null newHeader");
		headers.add(newHeader);
	}

	/**
	 * Adds a new header to the existing list array.
	 */
	public void add(String field, String value) {
		add(new MessageHeader(field, value));
	}

	/**
	 * Sets a new header in the existing list array.
	 * If the field-name of <code>field</code> already exists,
	 * the existing field-value is replaced.
	 */
	public void set(String field, String value) {
		set(new MessageHeader(field, value));
	}
	
	/**
	 * Removes a header by field name.
	 * Returns true if the field name was found.
	 */
	public boolean remove(String fieldName) {
		fieldName = fieldName.toLowerCase(Locale.ENGLISH);
		boolean removed = false;
		for (Iterator<MessageHeader> i = headers.iterator(); i.hasNext(); ) {
			MessageHeader h = i.next();
			if (h.getFieldName().equals(fieldName)) {
				i.remove();
				removed = true;
			}
		}
		return removed;
	}

	/**
	 * Returns true if <code>header</code> is within the headers.
	 */
	public boolean contains(MessageHeader header) {
		return headers.contains(header);
	}

	/**
	 * Returns the field content of the first header matching
	 * a given field name.
	 */
	public String getFieldContent(String fieldName) {
		fieldName = fieldName.toLowerCase(Locale.ENGLISH);
		for (int i = 0; i < headers.size(); i++) {
			MessageHeader h = headers.get(i);
			if (h.getFieldName().equals(fieldName))
				return h.getFieldContent();
		}
		return null;
	}

	/**
	 * Writes these headers to output.
	 * @throws IOException if writing fails
	 */
	public void write(Writer writer) throws IOException {
		for (int i = 0; i < headers.size(); i++) {
			MessageHeader h = headers.get(i);
			writer.write(h.toString());
			writer.write(HttpUtil.CRLF);
		}
	}
	
	/**
	 * Returns the number of headers.
	 */
	public int count() {
		return headers.size();
	}
	
	/**
	 * Returns the headers as a read-only list.
	 */
	public List<MessageHeader> asList() {
		return Collections.unmodifiableList(headers);
	}
	
	/**
	 * Returns a list of field names, read-only.
	 */
	public List<String> getNames() {
		List<String> nl = new ArrayList<String>(headers.size());
		for (MessageHeader h : headers)
			nl.add(h.getFieldName());
		return Collections.unmodifiableList(nl);
	}

	/**
	 * Returns a debug string.
	 */
	public String toString() {
		return headers.toString();
	}

}
