/* 
 * 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.xmlrpc;

import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.StringWriter;
import java.net.URL;
import java.util.Vector;

import net.noderunner.http.ChunkedOutputStream;
import net.noderunner.http.ClientRequest;
import net.noderunner.http.ClientResponse;
import net.noderunner.http.HttpClient;
import net.noderunner.http.HttpException;
import net.noderunner.http.HttpUtil;
import net.noderunner.http.MessageHeader;
import net.noderunner.http.MessageHeaders;
import net.noderunner.http.Method;
import net.noderunner.http.RequestLine;
import net.noderunner.http.RetryHttpClient;

/**
 * This is the implementation of XmlRpcClient. Use one instance per thread. It
 * does not use the URLConnection class, which does not support chunking. It
 * does support HTTP keep-alive.
 */
public class XmlRpcClientImpl implements XmlRpcClient {
	
	private URL url;

	private String encoding = "UTF-8";

	// this is re-used per send
	private XmlRpcReader reader;

	// this is re-used per send
	private XmlRpcHttpClient httpClient;

	/**
	 * Constructs a new Xml-Rpc client.
	 */
	public XmlRpcClientImpl() {
		reader = new XmlRpcReaderImpl();
	}

	public void setServerURL(URL url) {
		this.url = url;
	}

	/*
	 * public void setBasicAuthentication(String user, String password) { char[]
	 * basicAuth = Base64.getInstance().encode((user+":"+password).getBytes());
	 * auth = new String (basicAuth).trim(); }
	 */

	private static class XmlRpcHttpClient {
		HttpClient client;

		RequestLine requestLine;

		MessageHeaders headers;

		ChunkedOutputStream cos;

		public XmlRpcHttpClient(URL url) {
			this.client = new RetryHttpClient(url);
			requestLine = RequestLine.create(url, Method.POST);
			headers = MessageHeaders.defaultHeaders(url);
			headers.add(MessageHeader.MH_TRANSFER_ENCODING_CHUNKED);
			headers.add(MessageHeader.FN_CONTENT_TYPE, "text/xml");
		}

		public void writeRequest() throws IOException {
			client.writeRequest(new ClientRequest(requestLine, headers));
		}

		public OutputStream getOutputStream() {
			cos = new ChunkedOutputStream(client.getOutputStream());
			return cos;
		}

		public InputStream readResponse() throws IOException {
			cos.doneOutput();
			ClientResponse r = client.readResponse();
			if (r.getStatusLine().getStatusCode() != 200)
				throw new HttpException("Bad HTTP Status, expected 200 OK " + r);
			MessageHeaders hl = r.getHeaders();
			return HttpUtil.wrapInputStream(r.getInputStream(), hl);
		}
	}

	/**
	 * This does not use URLConnection, but instead uses raw sockets.
	 * Apparently, URLConnection does not support sending chunked data, which is
	 * needed for streaming Xml support. The server must support
	 * <code>HTTP/1.1</code>, but even without a streaming response, this
	 * implementation will still function correctly.
	 * <p>
	 * Note that this implementation does not support HTTP redirects or possibly
	 * proxy servers as well. The supplied <code>ParamIterator</code> will be
	 * closed regardless of any exceptions.
	 * </p>
	 */
	public ParamIterator execute(String method, ParamIterator params) throws XmlRpcException {
		if (httpClient == null)
			httpClient = new XmlRpcHttpClient(url);
		try {
			httpClient.writeRequest();
			OutputStream out = httpClient.getOutputStream();
			OutputStreamWriter outw = new OutputStreamWriter(out, encoding);
			XmlRpcWriter writer = new XmlRpcWriterImpl(outw);
			writer.writeRequest(method, params);
		} catch (IOException ioe) {
			throw new XmlRpcException(0, "IOException sending HTTP request", ioe);
		} finally {
			params.close();
		}
		try {
			InputStream in = httpClient.readResponse();
			reader.setReader(new InputStreamReader(in));
			ParamIterator i = reader.readMethodResponse();
			return i;
		} catch (IOException ioe) {
			throw new XmlRpcException(0, "IOException reading HTTP response", ioe);
		}
	}

	private static void iterate(ParamIterator pi) throws XmlRpcException {
		while (pi.hasNext()) {
			Object o = pi.next();
			System.out.println("Parameter: " + o);
			if (o instanceof ParamIterator) {
				iterate((ParamIterator) o);
			}
		}
	}

	/**
	 * Tests the XmlRpcClientImpl class by connecting to a server.
	 */
	public static void main(String[] args) throws Exception {
		if (args.length < 2) {
			System.err
					.println("Usage: net.noderunner.xmlrpc.XmlRpcClientImpl URL method.sub");
			return;
		}

		// create client and message
		XmlRpcClient client = new XmlRpcClientImpl();
		client.setServerURL(new java.net.URL(args[0]));
		String method = args[1];
		ParamIterator pia;
		Vector v = new Vector();
		for (int i = 2; i < args.length; i++) {
			try {
				v.add(Integer.valueOf(args[i]));
			} catch (NumberFormatException nfe) {
				v.add(args[i]);
			}
		}
		pia = new IteratorAdapter(v.iterator());

		// format message
		StringWriter out = new StringWriter();
		XmlRpcWriter writer = new XmlRpcWriterImpl(out);
		writer.writeRequest(method, pia);

		// print message
		System.out.println("Client will write this message:");
		System.out.println(out);
		System.out.println();

		// do actual call
		pia = new IteratorAdapter(v.iterator());
		ParamIterator result = client.execute(method, pia);
		System.out.println("Got result:");
		iterate(result);
		System.out.println();
		result.close();
	}

}
