/* 
 * 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.BufferedInputStream;
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.Writer;
import java.net.Socket;

/**
 * A very basic HTTP client implementation.
 */
public class BasicHttpClient
	implements HttpClient
{
	enum State {
		INITIAL,
		SENT_REQUEST,
		GOT_INPUT,
		CONTINUE,
		CLOSED,
	};

	private InputStream is;
	private OutputStream os;
	private Socket socket;
	private Writer writer;
	private State state;

	/**
	 * Constructs a BasicHttpClient that communicates over
	* a socket.  By default, wraps the input stream created
	* in a BufferedInputStream.  The output stream is unbuffered.
	 */
	public BasicHttpClient(Socket socket)
		throws IOException
	{
		this(socket.getOutputStream(), 
			new BufferedInputStream(socket.getInputStream()));
		this.socket = socket;
	}

	/**
	 * Constructs a BasicHttpClient that communicates over
	 * input and output streams.
	 */
	public BasicHttpClient(OutputStream os, InputStream is) {
		if (os == null)
			throw new IllegalArgumentException("Null OutputStream");
		if (is == null)
			throw new IllegalArgumentException("Null InputStream");
		this.os = os;
		this.writer = new BufferedWriter(new OutputStreamWriter(os));
		this.is = is;
		this.state = State.INITIAL;
	}

	public void writeRequest(ClientRequest request)
		throws IOException
	{
		if (state != State.INITIAL)
			throw new IllegalHttpStateException("Invalid HTTP state: " + this);
		if (request == null)
			throw new IllegalArgumentException("Null ClientRequest");
		writer.write(request.getRequestLine().toString());
		writer.write(HttpUtil.CRLF);
		request.getHeaders().write(writer);
		writer.write(HttpUtil.CRLF);
		writer.flush();
		state = State.SENT_REQUEST;
		if (request.getDataPoster() != null) {
			request.getDataPoster().sendData(getOutputStream());
			// This is forbidden by the specification
			// writer.write(HttpUtil.CRLF);
			writer.flush();
		}
	}

	public OutputStream getOutputStream() {
		if (state != State.SENT_REQUEST)
			throw new IllegalHttpStateException("Invalid HTTP state");
		state = State.GOT_INPUT;
		return os;
	}

	public ClientResponse readResponse()
		throws IOException
	{
		if (state != State.SENT_REQUEST && state != State.GOT_INPUT && state != State.CONTINUE)
			throw new IllegalHttpStateException("Invalid HTTP state: " + state);
		if (state == State.GOT_INPUT) {
			os.flush();
		}
		State done = State.INITIAL; // READ_RESPONSE;
		try {
			ClientResponse response = new ClientResponse(is);
			if (response.isContinue()) 
				done = State.CONTINUE;
			return response;
		} finally {
			state = done;
		}
	}

	/**
	 * Closes the underlying input and output streams.
	 */
	public void close()
		throws IOException
	{
		state = State.CLOSED;
		if (socket != null)
			socket.close();
		if (os != null)
			os.close();
		if (is != null)
			is.close();
		socket = null;
		os = null;
		is = null;
	}

	/**
	 * Returns debug information.
	 */
	public String toString() {
		return "BasicHttpClient state=" + state + " socket=" + socket;
	}

}
