package org.soap.sax;

import java.io.IOException;
import java.io.InputStream;
import java.io.Reader;
import java.util.HashSet;

import org.biomoby.shared.MobyException;
import org.soap.SOAPFault;
import org.tulsoft.shared.GException;
import org.tulsoft.tools.debug.DGUtils;
import org.tulsoft.tools.xml.XMLUtils2;
import org.xml.sax.Attributes;
import org.xml.sax.InputSource;
import org.xml.sax.Locator;
import org.xml.sax.SAXException;
import org.xml.sax.SAXParseException;
import org.xml.sax.XMLReader;
import org.xml.sax.helpers.DefaultHandler;

public class SOAPFaultParser extends DefaultHandler implements FaultTags {

	private static String[] faultNameArray = { FAULT, FAULT_CODE, FAULT_DETAIL,
			FAULT_STRING, TIMESTAMP, DESCRIPTION };

	private static String[] wsrfFaultNameArray = { RESOURCE_UNAVAIL,
			RESOURCE_UNKNOWN, INVALID_PROP_QNAME, };

	private static HashSet<String> faultNames = new HashSet<String>();

	private static HashSet<String> wsrfFaultNames = new HashSet<String>();
	static {
		for (int i = 0; i < faultNameArray.length; i++) {
			faultNames.add(faultNameArray[i]);
		}
		for (int i = 0; i < wsrfFaultNameArray.length; i++) {
			wsrfFaultNames.add(wsrfFaultNameArray[i]);
		}
	}

	// parsing helpers

	private Locator locator;

	private XMLReader parser = null;

	// the SOAPFault object
	private SOAPFault result = null;

	private boolean inSoapFault = false;

	private boolean inWsrfFault = false;

	private boolean readingFaultCode = false;

	private boolean readingFaultString = false;

	private boolean readingFaultDetail = false;

	private boolean readingWsrfTimestamp = false;

	private boolean readingWsrfDescription = false;

	// StringBuffers for holding content
	private StringBuffer faultCodeBuffer, faultStringBuffer, wsrfDescBuffer, wsrfTimeBuffer; 
	
	public SOAPFaultParser() {
		inSoapFault = false;
		inWsrfFault = false;
		readingFaultCode = false;
		readingFaultString = false;
		readingFaultDetail = false;
		readingWsrfTimestamp = false;
		readingWsrfDescription = false;
	}

	/***************************************************************************
	 * 
	 * XML-SAX 2.0 handler routines.
	 * 
	 **************************************************************************/

	/***************************************************************************
	 * Called at the beginning of the parsed document.
	 **************************************************************************/
	public void startDocument() throws SAXException {
		inSoapFault = false;
		inWsrfFault = false;
		readingFaultCode = false;
		readingFaultString = false;
		readingFaultDetail = false;
		readingWsrfTimestamp = false;
		readingWsrfDescription = false;
		this.result = new SOAPFault();
		faultCodeBuffer = new StringBuffer();
		faultStringBuffer = new StringBuffer();
		wsrfDescBuffer = new StringBuffer();
		wsrfTimeBuffer = new StringBuffer();
	}

	/***************************************************************************
	 * Called at the end of the parsed document.
	 **************************************************************************/
	public void endDocument() throws SAXException {
		inSoapFault = false;
		inWsrfFault = false;
		readingFaultCode = false;
		readingFaultString = false;
		readingFaultDetail = false;
		readingWsrfTimestamp = false;
		readingWsrfDescription = false;
		
		this.result.setCode(faultCodeBuffer.toString());
		this.result.setMsg(faultStringBuffer.toString());
		this.result.getWsrfFault().setDescription(wsrfDescBuffer.toString());
		this.result.getWsrfFault().setTimestamp(wsrfTimeBuffer.toString());
		
		faultCodeBuffer = null;
		faultStringBuffer = null;
		wsrfDescBuffer = null;
		wsrfTimeBuffer = null;
	}

	/***************************************************************************
	 * Called when an element starts.
	 **************************************************************************/
	public void startElement(String namespaceURI, String name, String qName,
			Attributes attrs) throws SAXException {
		if (!faultNames.contains(name) && !wsrfFaultNames.contains(name)) {
			return;
		}		
		
		if (name.equals(FAULT)) {
			this.inSoapFault = true;
			return;
		}
		
		if (inSoapFault) {
			if (name.equals(FAULT_CODE)) {
				readingFaultCode = true;
				return;
			}
			if (name.equals(FAULT_DETAIL)) {
				readingFaultDetail = true;
				return;
			}
			if (name.equals(FAULT_STRING)) {
				System.out.println("foo");
				readingFaultString = true;
				return;
			}
			if (readingFaultDetail) {
				if (wsrfFaultNames.contains(name)) {
					inWsrfFault = true;
					this.result.setWsrf(true);
					this.result.getWsrfFault().setName(name);
					return;
				}
				if (inWsrfFault) {
					if (name.equals(TIMESTAMP)) {
						readingWsrfTimestamp = true;
						return;
					}
					if (name.equals(DESCRIPTION)) {
						readingWsrfDescription = true;
						return;
					}
				}
				
			}
			
		}
		

	}

	/***************************************************************************
	 * Called when an element ends.
	 **************************************************************************/
	public void endElement(String namespaceURI, String name, String qName)
			throws SAXException {

		if (!faultNames.contains(name) && !wsrfFaultNames.contains(name))
			return;

		if (wsrfFaultNames.contains(name)) {
			inWsrfFault = false;
			readingWsrfTimestamp = false;
			readingWsrfDescription = false;
			return;
		}
		if (name.equals(FAULT)) {
			inSoapFault = false;
			inWsrfFault = false;
			readingFaultCode = false;
			readingFaultString = false;
			readingFaultDetail = false;
			readingWsrfTimestamp = false;
			readingWsrfDescription = false;
			return;
		}

		if (name.equals(FAULT_CODE)) {
			readingFaultCode = false;
			return;
		}
		if (name.equals(FAULT_DETAIL)) {
			readingFaultDetail = false;
			return;
		}
		if (name.equals(FAULT_STRING)) {
			readingFaultString = false;
			return;
		}
		if (name.equals(TIMESTAMP)) {
			readingWsrfTimestamp = false;
			return;
		}
		if (name.equals(DESCRIPTION)) {
			readingWsrfDescription = false;
			return;
		}

	}

	/***************************************************************************
	 * Set document locator. There is usually no need to use it from outside the
	 * parser.
	 **************************************************************************/
	public void setDocumentLocator(Locator l) {
		this.locator = l;
	}

	/***************************************************************************
	 * Called for #PCDATA.
	 **************************************************************************/
	public void characters(char[] ch, int start, int length) {
		StringBuffer sb = new StringBuffer();
		sb.append(ch, start, length);

		if (inSoapFault && readingFaultCode) {
			faultCodeBuffer.append(sb.toString().trim());
			return;
		}
		if (inSoapFault && readingFaultString) {
			faultStringBuffer.append(sb.toString().trim()+"\n");
			return;
		}
		if (inSoapFault && inWsrfFault && readingWsrfDescription) {
			wsrfDescBuffer.append(sb.toString().trim()+"\n");
			return;
		}
		if (inSoapFault && inWsrfFault && readingWsrfTimestamp) {
			wsrfTimeBuffer.append(sb.toString().trim());
			return;
		}

	}

	/***************************************************************************
	 * Called when an error occurs.
	 **************************************************************************/
	protected SAXParseException error(String message) {
		return new SAXParseException("", locator, new MobyException(message));
	}

	/***************************************************************************
	 * Parse the contents of the given file.
	 **************************************************************************/
	public SOAPFault parse(String xmlFilename) throws MobyException {
		return _parse(new InputSource(xmlFilename));
	}

	/***************************************************************************
	 * Parse the contents coming from the given input stream.
	 **************************************************************************/
	public SOAPFault parse(InputStream xml) throws MobyException {
		return _parse(new InputSource(xml));
	}

	/***************************************************************************
	 * Parse the contents coming from the given reader.
	 **************************************************************************/
	public SOAPFault parse(Reader xmlReader) throws MobyException {
		return _parse(new InputSource(xmlReader));
	}

	private synchronized SOAPFault _parse(InputSource xmlSource)
			throws MobyException {

		try {
			// Create a parser and register handlers (do it only once)
			if (parser == null)
				parser = XMLUtils2.makeXMLParser(this);

			// Parse it!
			parser.parse(xmlSource);
			if (result == null) {
				throw new MobyException(
						"Parsing XML failed, and I do not know why. \n"
								+ "Panic... (or send the XML to a jMoby developer)\n");
			}
			return result;

		} catch (GException e) {
			throw new MobyException("Error in creating XML parser "
					+ e.getMessage());

		} catch (SAXException e) {
			throw new MobyException("Error in the XML input.\n"
					+ XMLUtils2.getFormattedError(e));
		} catch (IOException e) {
			throw new MobyException("Error by reading XML input: "
					+ e.toString());

		} catch (Error e) {
			throw new MobyException("Serious or unexpected error!\n"
					+ e.toString() + "\n" + DGUtils.stackTraceToString(e));
		}
	}
}
