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

import net.noderunner.exml.Dtd;
import net.noderunner.exml.Element;
import net.noderunner.exml.NullReader;
import net.noderunner.exml.XmlException;
import net.noderunner.exml.XmlParser;
import net.noderunner.exml.XmlReader;

/**
 * This class provides a layer over a XmlParser object to read XML-RPC
 * data.
 *
 * @author Elias Ross
 * @version 1.0
 * @see XmlRpcWriter
 */

public class XmlRpcReaderImpl
	implements XmlRpcReader, XmlRpcTags
{
	/** Underlying reader object. */
	private XmlParser parser;

	/** Dtd for reading XmlRpc. */
	private static final Dtd dtd = makeDtd();

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

	/**
	 * Construct an XmlRpcReader object over a Reader.
	 * Also, adds in the DTD to the underlying XmlReader object.
	 * @param reader reader to wrap
	 */
	public XmlRpcReaderImpl(Reader r) {
		XmlReader xr = new XmlReader(r, dtd);
		this.parser = new XmlParser(xr);
	}

	public void setReader(Reader reader) {
		parser.setReader(reader);
		/*
		try {
			parser.startTag();
			Element content = parser.getContent();
			System.out.println(content);
		} catch (IOException e) {
			throw new RuntimeException(e);
		} catch (XmlException e) {
			throw new RuntimeException(e);
		}
		*/
	}

	public ParamIterator readMethodResponse() 
		throws XmlRpcException
	{
		try {
			parser.skipProlog();
			Element e;
			e = parser.startTag();
			if (e == null || e.getName() != ELEM_METHOD_RESPONSE.getName()) {
				throw new XmlRpcException("Expected " + ELEM_METHOD_RESPONSE + ", got " + e);
			}
		} catch (XmlException xre) {
			throw new XmlRpcException(0, "Xml could not be parsed", xre);
		} catch (IOException ioe) {
			throw new XmlRpcException(0, "Xml could not be read", ioe);
		} 
		return readMethodRequest();
	}

	public String readMethodName() 
		throws XmlRpcException
	{
		try {
			parser.skipProlog();
			Element e;
			e = parser.startTag();
			if (e.getName() != ELEM_METHOD_CALL.getName()) {
				throw new XmlRpcException("Expected " + ELEM_METHOD_CALL + ", got " + e);
			}
			e = parser.startTag();
			if (e.getName() != ELEM_METHOD_NAME.getName()) {
				throw new XmlRpcException("Expected " + ELEM_METHOD_NAME + ", got " + e);
			}
			String methodName = parser.getContent().getCharacterData();
			if (methodName.length() == 0) {
				throw new XmlRpcException(ELEM_METHOD_NAME + " has no length, got " + e);
			}
			parser.endTag();
			return methodName;
		} catch (XmlException xre) {
			throw new XmlRpcException(0, "Xml could not be parsed", xre);
		} catch (IOException ioe) {
			throw new XmlRpcException(0, "Xml could not be read", ioe);
		} 
	}

	public ParamIterator readMethodRequest() 
		throws XmlRpcException
	{
		return new XmlRpcParamIterator(parser, ParamIterator.PARAMS_ITERATOR);
	}

	/**
	 * This was taken and edited from the location
	 * <code>http://www.ontosys.com/xml-rpc/xml-rpc.dtd</code>.
	 * Note that, for now, the document element name is not
	 * methodCall or methodResponse, it is instead xmlrpc.
	 */
	private static Dtd makeDtd()
	{
		String dtdString = 
			"<!DOCTYPE xmlrpc [" +
			"<!ELEMENT i4 (#PCDATA)>" +
			"<!ELEMENT int (#PCDATA)>" +
			"<!ELEMENT boolean (#PCDATA)>" +
			"<!ELEMENT string (#PCDATA)>" +
			"<!ELEMENT double (#PCDATA)>" +
			"<!ELEMENT dateTime.iso8601 (#PCDATA)>" +
			"<!ELEMENT base64 (#PCDATA)>" +
			"<!ELEMENT data (value*)>" +
			"<!ELEMENT array (data)>" +
			"<!ELEMENT name (#PCDATA)>" +
			"<!ELEMENT member (name, value)>" +
			"<!ELEMENT struct (member*)>" +
			"<!ELEMENT nil EMPTY>" +
			"<!ELEMENT value ( i4 | int | boolean | string | dateTime.iso8601 | double | base64 | struct | array | nil)>" +
			"<!ELEMENT param (value)>" +
			"<!ELEMENT params (param*)>" +
			"<!ELEMENT methodName (#PCDATA)>" +
			"<!ELEMENT methodCall (methodName, params?)>" +
			"<!ELEMENT fault (value)>" +
			"<!ELEMENT methodResponse (params|fault)>]>";
		XmlReader r = new XmlReader();
		r.setReadString(dtdString);

		// Populates the string pool
		java.util.Collection c = r.getStringPool();
		c.add(ELEM_METHOD_CALL.getName());
		c.add(ELEM_METHOD_RESPONSE.getName());
		c.add(ELEM_FAULT.getName());
		c.add(ELEM_METHOD_NAME.getName());
		c.add(ELEM_PARAMS.getName());
		c.add(ELEM_PARAM.getName());
		c.add(ELEM_STRING.getName());
		c.add(ELEM_INT.getName());
		c.add(ELEM_BOOLEAN.getName());
		c.add(ELEM_DOUBLE.getName());
		c.add(ELEM_DATE.getName());
		c.add(ELEM_BASE64.getName());
		c.add(ELEM_NIL.getName());
		c.add(ELEM_VALUE.getName());
		c.add(ELEM_ARRAY.getName());
		c.add(ELEM_DATA.getName());
		c.add(ELEM_STRUCT.getName());
		c.add(ELEM_MEMBER.getName());
		c.add(ELEM_NAME.getName());
		try {
			r.doctypedecl();
		} catch (Exception e) {
			throw new RuntimeException("IOException", e);
		}
		return r.getDtd();
	}
}
