/* 
 * 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.Writer;
import java.util.Collection;
import java.util.Date;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;

import net.noderunner.exml.NullWriter;
import net.noderunner.exml.XmlWriter;

/**
 * This class provides basic capabilities for writing
 * data to an Xml-Rpc server or client.  Output is automatically
 * flushed when completely written out.
 *
 * @see XmlRpcReader
 */
public class XmlRpcWriterImpl
	implements XmlRpcWriter, XmlRpcTags
{
	/**  
	 * Wrapper around "Writer" class 
	 */
	XmlWriter writer;

	/**
	 * Date formatter.
	 */
	XmlRpcDateFormat dateFormat;

	/**
	 * Construct a dummy <code>XmlRpcWriterImpl</code>.
	 */
	public XmlRpcWriterImpl() {
		this(NullWriter.getInstance());
	}

	/**
	 * Construct a XmlRpcWriterImpl which will wrap a Writer output stream.
	 * Should be a buffered stream for better performance.
	 */
	public XmlRpcWriterImpl(Writer w) {
		this.writer = new XmlWriter(w);
		this.dateFormat = new XmlRpcDateFormat();
	}

	/**
	 * Sets the writer to output with.
	 * @param writer writer to wrap
	 */
	public void setWriter(Writer writer) {
		this.writer.setWriter(writer);
	}

	public void writeRequest(String method, ParamIterator i) 
		throws XmlRpcException
	{
		try {
			writer.startElement(ELEM_METHOD_CALL);
			writer.startElement(ELEM_METHOD_NAME);
			writer.write(method);
			writer.endElement();
			writer.startElement(ELEM_PARAMS);
			while (i.hasNext()) {
				writer.startElement(ELEM_PARAM);
				writeObject(i.next());
				writer.endElement();
			}
			doneWriting();
		} catch (IOException ioe) {
			throw new XmlRpcException(0, "Could not writeRequest " + method + " " + i, ioe);
		}
	}

	private void doneWriting()
		throws IOException
	{
		writer.up(0);
		writer.write("\r\n");
		writer.flush();
	}

	public void writeResponse(ParamIterator i) 
		throws XmlRpcException
	{
		try {
			writer.startElement(ELEM_METHOD_RESPONSE);
			if (i != null) {
				writer.startElement(ELEM_PARAMS);
				while (i.hasNext()) {
					writer.startElement(ELEM_PARAM);
					writeObject(i.next());
					writer.endElement();
				}
				writer.endElement();
			}
			doneWriting();
		} catch (IOException ioe) {
			throw new XmlRpcException(0, "Could not writeResponse " + i, ioe);
		}
	}

	public void writeError(XmlRpcException xre) 
		throws XmlRpcException
	{
		try {
			HashMap h = new HashMap();
			h.put(FAULT_CODE, new Integer(xre.getCode()));
			h.put(FAULT_STRING, xre.getMessage());
				// anything else is not Xml-Rpc compliant
				// h.put("faultException", xre.getCause().toString());
			writer.startElement(ELEM_METHOD_RESPONSE);
			writer.startElement(ELEM_FAULT);
			writeObject(h);
			doneWriting();
		} catch (IOException ioe) {
			throw new XmlRpcException(0, "Could not writeError " + xre, ioe);
		}
	}

	/**
	 * Writes the Xml-Rpc representation of a supported Java object to
	 * the Xml writer.
	 * @see ParamIterator
	 * @throws XmlRpcException if a ParamIterator was passed in and a
	 * call failed;  an unknown object type was found
	 * @throws IOException if an error occured writing with XmlWriter
	 */
	void writeObject(Object what)
		throws IOException, XmlRpcException
	{
		// don't want the start and end value tags for these objects
		if (what instanceof Collection) {
			Collection c = (Collection) what;
			writeObject(c.iterator());
			return;
		} 
		if (what instanceof Map) {
			writeObject(new MapAdapter((Map)what));
			return;
		} 
		if (what instanceof Iterator) {
			writeObject(new IteratorAdapter((Iterator)what));
			return;
		} 
		if (what instanceof StructPair) {
			writer.startElement(ELEM_MEMBER);
			writer.startElement(ELEM_NAME);
			StructPair sp = (StructPair)what;
			writer.write(sp.getName());
			writer.endElement();
			writeObject(sp.getValue());
			writer.endElement();
			return;
		}
		writer.startElement(ELEM_VALUE);
		if (what == null) {
			writer.emptyElement (ELEM_NIL);
		} else if (what instanceof String) {
			writer.startElement(ELEM_STRING);
			writer.writeCData(what.toString());
			writer.endElement();
		} else if (what instanceof Integer) {
			writer.startElement(ELEM_INT);
			writer.write(what.toString());
			writer.endElement();
		} else if (what instanceof Boolean) {
			writer.startElement(ELEM_BOOLEAN);
			writer.write(((Boolean) what).booleanValue () ? "1" : "0");
			writer.endElement();
		} else if (what instanceof Double || what instanceof Float) {
			writer.startElement(ELEM_DOUBLE);
			writer.write(what.toString());
			writer.endElement();
		} else if (what instanceof Date) {
			writer.startElement(ELEM_DATE);
			Date d = (Date) what;
			writer.write(dateFormat.format(d));
			writer.endElement();
		} else if (what instanceof byte[]) {
			writer.startElement(ELEM_BASE64);
			writer.write(Base64.getInstance().encode((byte[]) what));
			writer.endElement();
		} else if (what instanceof ParamIterator) {
			ParamIterator i = (ParamIterator) what;
			int type = i.getIteratorType();
			if (type == ParamIterator.PARAMS_ITERATOR
				|| type == ParamIterator.ARRAY_ITERATOR)
			{ 
				writer.startElement(ELEM_ARRAY);
				writer.startElement(ELEM_DATA);
				while (i.hasNext())
					writeObject(i.next());
				writer.endElement();
				writer.endElement();
			}
			else
			if (type == ParamIterator.STRUCT_ITERATOR) {
				writer.startElement(ELEM_STRUCT);
				while (i.hasNext())
					writeObject(i.next());
				writer.endElement();
			}
		} 
		else if (what instanceof Enumeration) {
			writer.startElement(ELEM_ARRAY);
			writer.startElement(ELEM_DATA);
			Enumeration e = (Enumeration) what;
			while (e.hasMoreElements())
				writeObject(e.nextElement());
			writer.endElement();
			writer.endElement();
		} 
		else {
			throw new XmlRpcException("Unsupported Java type: " + what.getClass());
		}
		writer.endElement();
	}
}
