/*******************************************************************************
 * Copyright (c) 2004, 2009 Brock Janiczak and others.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *     Brock Janiczak - initial API and implementation
 *     Tasktop Technologies - improvements
 *******************************************************************************/

package org.eclipse.mylyn.internal.context.core;

import java.io.IOException;
import java.io.OutputStream;
import java.text.SimpleDateFormat;
import java.util.Locale;

import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.sax.SAXSource;
import javax.xml.transform.stream.StreamResult;

import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;
import org.eclipse.mylyn.commons.core.StatusHandler;
import org.eclipse.mylyn.context.core.IInteractionContext;
import org.eclipse.mylyn.monitor.core.InteractionEvent;
import org.xml.sax.Attributes;
import org.xml.sax.ContentHandler;
import org.xml.sax.DTDHandler;
import org.xml.sax.EntityResolver;
import org.xml.sax.ErrorHandler;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import org.xml.sax.SAXNotRecognizedException;
import org.xml.sax.SAXNotSupportedException;
import org.xml.sax.XMLReader;
import org.xml.sax.helpers.AttributesImpl;

/**
 * @author Brock Janiczak
 * @author Mik Kersten (refactoring)
 */
public class SaxContextWriter implements IInteractionContextWriter {

	private OutputStream outputStream;

	public void setOutputStream(OutputStream outputStream) {
		this.outputStream = outputStream;
	}

	public void writeContextToStream(IInteractionContext context) throws IOException {
		if (outputStream == null) {
			IOException ioe = new IOException("OutputStream not set"); //$NON-NLS-1$
			throw ioe;
		}

		try {
			Transformer transformer = TransformerFactory.newInstance().newTransformer();
			transformer.transform(new SAXSource(new SaxWriter(), new InteractionContextInputSource(context)),
					new StreamResult(outputStream));
		} catch (TransformerException e) {
			StatusHandler.log(new Status(IStatus.ERROR, ContextCorePlugin.ID_PLUGIN, "Could not write context", e)); //$NON-NLS-1$
			throw new IOException(e.getMessage());
		}
	}

	private static class InteractionContextInputSource extends InputSource {

		private final IInteractionContext context;

		public InteractionContextInputSource(IInteractionContext context) {
			this.context = context;
		}

		public IInteractionContext getContext() {
			return this.context;
		}

	}

	private class SaxWriter implements XMLReader {

		private ContentHandler handler;

		private ErrorHandler errorHandler;

		public boolean getFeature(String name) throws SAXNotRecognizedException, SAXNotSupportedException {
			return false;
		}

		public void setFeature(String name, boolean value) throws SAXNotRecognizedException, SAXNotSupportedException {

		}

		public Object getProperty(String name) throws SAXNotRecognizedException, SAXNotSupportedException {
			return null;
		}

		public void setProperty(String name, Object value) throws SAXNotRecognizedException, SAXNotSupportedException {
		}

		public void setEntityResolver(EntityResolver resolver) {
		}

		public EntityResolver getEntityResolver() {
			return null;
		}

		public void setDTDHandler(DTDHandler handler) {
		}

		public DTDHandler getDTDHandler() {
			return null;
		}

		public void setContentHandler(ContentHandler handler) {
			this.handler = handler;

		}

		public ContentHandler getContentHandler() {
			return handler;
		}

		public void setErrorHandler(ErrorHandler handler) {
			this.errorHandler = handler;

		}

		public ErrorHandler getErrorHandler() {
			return errorHandler;
		}

		public void parse(InputSource input) throws IOException, SAXException {
			if (!(input instanceof InteractionContextInputSource)) {
				throw new SAXException("Can only parse writable input sources"); //$NON-NLS-1$
			}

			IInteractionContext context = ((InteractionContextInputSource) input).getContext();

			handler.startDocument();
			AttributesImpl rootAttributes = new AttributesImpl();
			rootAttributes.addAttribute("", InteractionContextExternalizer.ATR_ID, //$NON-NLS-1$
					InteractionContextExternalizer.ATR_ID, "", context.getHandleIdentifier()); //$NON-NLS-1$
			if (context.getContentLimitedTo() != null) {
				rootAttributes.addAttribute("", SaxContextContentHandler.ATTRIBUTE_CONTENT, //$NON-NLS-1$
						SaxContextContentHandler.ATTRIBUTE_CONTENT, "", context.getContentLimitedTo()); //$NON-NLS-1$
			}
			rootAttributes.addAttribute("", InteractionContextExternalizer.ATR_VERSION, //$NON-NLS-1$
					InteractionContextExternalizer.ATR_VERSION, "", "1"); //$NON-NLS-1$ //$NON-NLS-2$

			handler.startElement("", InteractionContextExternalizer.ELMNT_INTERACTION_HISTORY, //$NON-NLS-1$
					InteractionContextExternalizer.ELMNT_INTERACTION_HISTORY, rootAttributes);
			// List could get modified as we're writing
			for (InteractionEvent ie : context.getInteractionHistory()) {
				Attributes ieAttributes = createEventAttributes(ie);
				handler.startElement("", SaxContextContentHandler.ATTRIBUTE_INTERACTION_EVENT, //$NON-NLS-1$
						SaxContextContentHandler.ATTRIBUTE_INTERACTION_EVENT, ieAttributes);
				handler.endElement("", SaxContextContentHandler.ATTRIBUTE_INTERACTION_EVENT, //$NON-NLS-1$
						SaxContextContentHandler.ATTRIBUTE_INTERACTION_EVENT);
			}
			handler.endElement("", InteractionContextExternalizer.ELMNT_INTERACTION_HISTORY, //$NON-NLS-1$
					InteractionContextExternalizer.ELMNT_INTERACTION_HISTORY);

			handler.endDocument();
		}

		public void parse(String systemId) throws IOException, SAXException {
			throw new SAXException("Can only parse writable input sources"); //$NON-NLS-1$
		}
	}

	@SuppressWarnings({ "deprecation", "restriction" })
	private Attributes createEventAttributes(InteractionEvent ie) {
		SimpleDateFormat dateFormat = new SimpleDateFormat(InteractionContextExternalizer.DATE_FORMAT_STRING,
				Locale.ENGLISH);

		AttributesImpl ieAttributes = new AttributesImpl();

		ieAttributes.addAttribute("", InteractionContextExternalizer.ATR_DELTA, //$NON-NLS-1$
				InteractionContextExternalizer.ATR_DELTA, "", //$NON-NLS-1$
				org.eclipse.mylyn.internal.commons.core.XmlStringConverter.convertToXmlString(ie.getDelta()));
		ieAttributes.addAttribute("", InteractionContextExternalizer.ATR_END_DATE, //$NON-NLS-1$
				InteractionContextExternalizer.ATR_END_DATE, "", dateFormat.format(ie.getEndDate())); //$NON-NLS-1$
		ieAttributes.addAttribute("", InteractionContextExternalizer.ATR_INTEREST, //$NON-NLS-1$
				InteractionContextExternalizer.ATR_INTEREST, "", Float.toString(ie.getInterestContribution())); //$NON-NLS-1$
		ieAttributes.addAttribute("", InteractionContextExternalizer.ATR_KIND, InteractionContextExternalizer.ATR_KIND, //$NON-NLS-1$
				"", ie.getKind().toString()); //$NON-NLS-1$
		ieAttributes.addAttribute("", InteractionContextExternalizer.ATR_NAVIGATION, //$NON-NLS-1$
				InteractionContextExternalizer.ATR_NAVIGATION, "", //$NON-NLS-1$
				org.eclipse.mylyn.internal.commons.core.XmlStringConverter.convertToXmlString(ie.getNavigation()));
		ieAttributes.addAttribute("", InteractionContextExternalizer.ATR_ORIGIN_ID, //$NON-NLS-1$
				InteractionContextExternalizer.ATR_ORIGIN_ID, "", //$NON-NLS-1$
				org.eclipse.mylyn.internal.commons.core.XmlStringConverter.convertToXmlString(ie.getOriginId()));
		ieAttributes.addAttribute("", InteractionContextExternalizer.ATR_START_DATE, //$NON-NLS-1$
				InteractionContextExternalizer.ATR_START_DATE, "", dateFormat.format(ie.getDate())); //$NON-NLS-1$
		ieAttributes.addAttribute("", InteractionContextExternalizer.ATR_STRUCTURE_HANDLE, //$NON-NLS-1$
				InteractionContextExternalizer.ATR_STRUCTURE_HANDLE, "", //$NON-NLS-1$
				org.eclipse.mylyn.internal.commons.core.XmlStringConverter.convertToXmlString(ie.getStructureHandle()));
		ieAttributes.addAttribute("", InteractionContextExternalizer.ATR_STRUCTURE_KIND, //$NON-NLS-1$
				InteractionContextExternalizer.ATR_STRUCTURE_KIND, "", //$NON-NLS-1$
				org.eclipse.mylyn.internal.commons.core.XmlStringConverter.convertToXmlString(ie.getStructureKind()));

		if (ie instanceof AggregateInteractionEvent) {
			// keep the state of the element (how it was collapsed and when it was created) to ensure that the context is the same after writing
			ieAttributes.addAttribute("", InteractionContextExternalizer.ATR_NUM_EVENTS, //$NON-NLS-1$
					InteractionContextExternalizer.ATR_NUM_EVENTS, "", //$NON-NLS-1$
					Integer.toString(((AggregateInteractionEvent) ie).getNumCollapsedEvents()));
			ieAttributes.addAttribute("", InteractionContextExternalizer.ATR_CREATION_COUNT, //$NON-NLS-1$
					InteractionContextExternalizer.ATR_CREATION_COUNT, "", //$NON-NLS-1$
					Integer.toString(((AggregateInteractionEvent) ie).getEventCountOnCreation()));
		}
		return ieAttributes;
	}
}
