package org.omg.lsae.sax;

import java.io.IOException;
import java.io.InputStream;
import java.io.Reader;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;

import org.biomoby.shared.MobyException;
import org.omg.lsae.notifications.AnalysisEvent;
import org.omg.lsae.notifications.HeartBeatEvent;
import org.omg.lsae.notifications.PercentEvent;
import org.omg.lsae.notifications.StateEvent;
import org.omg.lsae.notifications.StepEvent;
import org.omg.lsae.notifications.TimeProgressEvent;
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 EventParser extends DefaultHandler implements LsaeTags {
	
	private static String[] eventNamesArray = { HEARTBEAT, PERCENT, STATE, STEP, TIME };

	private static HashSet<String> eventNames = new HashSet<String>();
	static {
		for (int i = 0; i < eventNamesArray.length; i++) {
			eventNames.add(eventNamesArray[i]);
		}
	}

	// parsing helpers
	private boolean inAnalysisEvent = false;

	private boolean parsingChild = false;

	private boolean parsingMessage = false;

	private Locator locator;

	private XMLReader parser = null;

	// the event object
	private AnalysisEvent[] result = null;
	
	private List<AnalysisEvent> events = new ArrayList<AnalysisEvent>();

	private String timestamp = "";

	private String eventId = "";
	
	private int index = 0;

	private StringBuffer msgBuffer = new StringBuffer();

	public EventParser() {
		inAnalysisEvent = false;
		parsingChild = false;
		parsingMessage = false;

	}

	/***************************************************************************
	 * 
	 * XML-SAX 2.0 handler routines.
	 * 
	 **************************************************************************/

	/***************************************************************************
	 * Called at the beginning of the parsed document.
	 **************************************************************************/
	public void startDocument() throws SAXException {
		inAnalysisEvent = false;
		parsingChild = false;
		parsingMessage = false;
		events = new ArrayList<AnalysisEvent>();
	}

	/***************************************************************************
	 * Called at the end of the parsed document.
	 **************************************************************************/
	public void endDocument() throws SAXException {
		eventId = null;
		timestamp = null;
		msgBuffer = null;
		result = events.toArray(new AnalysisEvent[]{});
	}

	/***************************************************************************
	 * Called when an element starts.
	 **************************************************************************/
	public void startElement(String namespaceURI, String name, String qName,
			Attributes attrs) throws SAXException {
		if (name.equals("analysis_event")) {
			inAnalysisEvent = true;
			for (int x = 0; x < attrs.getLength(); x++) {
				if (attrs.getLocalName(x).equals("timestamp")) {
					this.timestamp = attrs.getValue(x);
					continue;
				}
				if (attrs.getLocalName(x).equals("id"))
					this.eventId = attrs.getValue(x);
			}
			return;
		}

		if (inAnalysisEvent) {
			if (eventNames.contains(name)) {
				if (name.equals(HEARTBEAT)) {
					events.add(index, new HeartBeatEvent());
					parsingChild = true;
				} else if (name.equals(PERCENT)) {
					events.add(index, new PercentEvent());
					parsingChild = true;
					for (int x = 0; x < attrs.getLength(); x++) {
						if (attrs.getLocalName(x).equals(aPERCENTAGE)) {
							try {
								((PercentEvent) events.get(index))
										.setPercentCompleted(Integer
												.parseInt(attrs.getValue(x)));
							} catch (Exception e) {
								// silently catch bad numbers
							}
							return;
						}
					}
				} else if (name.equals(STATE)) {
					events.add(index, new StateEvent());
					parsingChild = true;
					for (int x = 0; x < attrs.getLength(); x++) {
						if (attrs.getLocalName(x).equals(aNEW_STATE)) {
							((StateEvent) events.get(index))
									.setNewState(((StateEvent) events.get(index))
											.getState(attrs.getValue(x)));
							continue;
						}
						if (attrs.getLocalName(x).equals(aPREV_STATE)) {
							((StateEvent) events.get(index))
									.setPreviousState(((StateEvent) events.get(index))
											.getState(attrs.getValue(x)));
							continue;
						}
					}

				} else if (name.equals(STEP)) {
					events.add(index, new StepEvent());
					parsingChild = true;
					for (int x = 0; x < attrs.getLength(); x++) {
						if (attrs.getLocalName(x).equals(aCOMPLETED_STEPS)) {
							((StepEvent) events.get(index)).setCompletedSteps(Integer
									.parseInt(attrs.getValue(x)));
							continue;
						}
						if (attrs.getLocalName(x).equals(aTOTAL_STEPS)) {
							((StepEvent) events.get(index)).setTotalSteps(Integer
									.parseInt(attrs.getValue(x)));
							continue;
						}
					}
				} else if (name.equals(TIME)) {
					events.add(index, new TimeProgressEvent());
					parsingChild = true;
					for (int x = 0; x < attrs.getLength(); x++) {
						if (attrs.getLocalName(x).equals(aREMAINING)) {
							try {
								((TimeProgressEvent) events.get(index)).setRemaining(Long
										.parseLong(attrs.getValue(x)));
							} catch (Exception e) {
								// catch invalid numbers
							}
							return;
						}
					}
				}
				return;
			}
			if (!parsingChild && inAnalysisEvent) {
				if (name.equals(MSG)) {
					parsingMessage = true;
					msgBuffer = new StringBuffer();
					return;
				}
			}
		}
	}

	/***************************************************************************
	 * Called when an element ends.
	 **************************************************************************/
	public void endElement(String namespaceURI, String name, String qName)
			throws SAXException {
		if (inAnalysisEvent && name.equals("analysis_event")) {
			inAnalysisEvent = false;
			parsingChild = false;
			parsingMessage = false;
			if (events.get(index) != null) {
				events.get(index).setQueryId(eventId);
				events.get(index).setTimestamp(timestamp);
				events.get(index).setMessage(msgBuffer.toString());
				index++;
			}
			return;
		}
		if (parsingChild && eventNames.contains(name)) {
			parsingChild = false;
			parsingMessage = false;
			return;
		}

		if (parsingMessage && name.equals(MSG)) {
			parsingMessage = false;
			parsingChild = 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) {
		if (parsingMessage)
			msgBuffer.append(ch, start, length);
	}

	/***************************************************************************
	 * 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 AnalysisEvent[] parse(String xmlFilename) throws MobyException {
		return _parse(new InputSource(xmlFilename));
	}

	/***************************************************************************
	 * Parse the contents coming from the given input stream.
	 **************************************************************************/
	public AnalysisEvent[] parse(InputStream xml) throws MobyException {
		return _parse(new InputSource(xml));
	}

	/***************************************************************************
	 * Parse the contents coming from the given reader.
	 **************************************************************************/
	public AnalysisEvent[] parse(Reader xmlReader) throws MobyException {
		return _parse(new InputSource(xmlReader));
	}

	private synchronized AnalysisEvent[] _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));
		}
	}
}
