/* 
 * 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 net.noderunner.exml.Element;
import net.noderunner.exml.XmlException;
import net.noderunner.exml.XmlParser;

/**
 * This class provides a layer over a XmlParser object.  It has methods
 * appropriate for reading method invocations and responses from a
 * data stream.
 *
 * @see XmlRpcWriter
 */

public class XmlRpcParamIterator
	implements ParamIterator, XmlRpcTags
{
	XmlParser parser;
	Element startTag; // should be a <params> tag
	Element current; // should be a <param> tag
	int depth; // for remembering where we were...
	int type;
	XmlRpcDateFormat dateFormat;

	/**
	 * Returns the data being iterated over.
	 */
	public int getIteratorType() {
		return type;
	}

	/**
	 * Start this at the &lt;params&gt; element, or &lt;data&gt;
	 * element of arrays or &lt;struct&gt; element for structs.
	 * @param type one of PARAMS_ITERATOR, ARRAY_ITERATOR, or STRUCT_ITERATOR
	 */
	public XmlRpcParamIterator(XmlParser parser, int type) 
		throws XmlRpcException
	{
		this.dateFormat = new XmlRpcDateFormat();
		this.parser = parser;
		this.type = type;
		try {
			if (type != STRUCT_ITERATOR) {
				startTag = parser.startTag();
				if (startTag.getName() == ELEM_FAULT.getName()) {
					this.type = FAULT_ITERATOR;
				}
			}
			depth = parser.getDepth();
		} catch (XmlException xre) {
			// try {
    			// close();
			// } catch (XmlRpcException e) {}
			throw new XmlRpcException(0, "Could not parse start of parameters", xre);
		} catch (IOException ioe) {
			close();
			throw new XmlRpcException(0, "Could not read parameters from stream", ioe);
		}
	}

	/**
	 * Call this when located past the &lt;params&gt; element (or
	 * &lt;data&gt; element) first.  The constructor will take care
	 * of that.
	 * @return some sort of Object.  This must be checked using
	 * <code>instanceOf</code> expressions.
	 * @see XmlRpcWriter#writeResponse(ParamIterator)
	 */
	public Object next()
		throws XmlRpcException
	{
		try {
			if (current == null)
				throw new XmlRpcException("Must call hasNext() first");
			Object o = getObject();
			current = null;
			return o;
		} catch (Exception e) {
			throw new XmlRpcException(0, "Error reading Xml element", e);
		}
	}

	public boolean hasNext()
		throws XmlRpcException
	{
		try {
			// go back to our correct tree depth
			parser.up(depth);
			current = parser.startTag();
			return (current != null);
		} catch (Exception e) {
			throw new XmlRpcException(0, "Error reading Xml element", e);
		}
	}

	public void close() 
		throws XmlRpcException
	{
		try {
			if (type == PARAMS_ITERATOR || type == FAULT_ITERATOR) {
				// ensure whole document is completely read
				try {
					parser.up(0);
					parser.skipMisc();
				} catch (XmlException xre) {
					throw new XmlRpcException(0, "Error closing XmlParser", xre);
				}
				// Don't close the stream
				// parser.close();
			}
		} catch (IOException e) {
			throw new XmlRpcException(0, "Error closing XmlParser", e);
		}
	}

	/**
	 * Expects &lt;value&gt; tag to be read.  Does not read
	 * &lt;/value&gt; tag.
	 * @throws Exception which might be anything to do with the
	 * parse value or input stream.  We defer exception handling to the
	 * caller.
	 */
	Object getObject()
		throws Exception
	{
		if (type == PARAMS_ITERATOR) 
			parser.startTag();
		Element what = parser.startTag();

		String kind = what.getName();
		if (kind == ELEM_STRING.getName()) {
			parser.getContent();
			return what.getCharacterData();
		}
		if (kind == ELEM_NAME.getName()) {
			parser.getContent();
			String name = what.getCharacterData().trim();
			parser.endTag();
			parser.startTag();
			return new StructPairImpl(name, getObject());
		} 
		if (kind == ELEM_NIL.getName()) {
			parser.emptyContent();
			return null;
		}
		if (kind == ELEM_INT.getName()) {
			parser.getContent();
			return new Integer(what.getCharacterData().trim());
		}
		if (kind == ELEM_BOOLEAN.getName()) {
			parser.getContent();
			return new Boolean(what.getCharacterData().trim().equals("1"));
		}
		if (kind == ELEM_DOUBLE.getName()) {
			parser.getContent();
			return new Double(what.getCharacterData().trim());
		}
		if (kind == ELEM_DATE.getName()) {
			parser.getContent();
			String dateString = what.getCharacterData().trim();
			return dateFormat.parse(dateString);
		}
		if (kind == ELEM_BASE64.getName()) {
			parser.getContent();
			char [] stuff = what.getCharacterData().trim().toCharArray();
			return Base64.getInstance().decode(stuff);
		}
		if (kind == ELEM_ARRAY.getName()) {
			XmlRpcParamIterator pi = new XmlRpcParamIterator(parser, ARRAY_ITERATOR);
			return pi;
		}
		if (kind == ELEM_STRUCT.getName()) {
			XmlRpcParamIterator pi = new XmlRpcParamIterator(parser, STRUCT_ITERATOR);
			return pi;
		}
		throw new XmlRpcException("Unknown element type '" + kind + "'");
	}

	public String toString() {
		return "XmlRpcParamIterator [parser=" + parser
			+ " startTag="+startTag
			+ " current="+current
			+ " depth="+depth + "]";
	}
}
