/*******************************************************************************
 * Copyright (c) 2004, 2016 IBM Corporation and others.
 *
 * This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License 2.0
 * which accompanies this distribution, and is available at
 * https://www.eclipse.org/legal/epl-2.0/
 *
 * SPDX-License-Identifier: EPL-2.0
 *
 * Contributors:
 *     IBM Corporation - initial API and implementation
 *******************************************************************************/

package org.eclipse.ui.internal.intro.impl.model.loader;

import java.io.IOException;
import java.io.StringReader;
import java.io.StringWriter;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerConfigurationException;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;

import org.eclipse.ui.internal.intro.impl.util.Log;
import org.w3c.dom.Document;
import org.w3c.dom.DocumentType;
import org.w3c.dom.Element;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import org.xml.sax.SAXParseException;


/**
 *
 */
public class IntroContentParser {

	private static String TAG_INTRO_CONTENT = "introContent"; //$NON-NLS-1$
	private static String TAG_HTML = "html"; //$NON-NLS-1$

	private Document document;
	private boolean hasXHTMLContent;

	/**
	 * Creates a config parser assuming that the passed content represents a URL
	 * to the content file.
	 */
	public IntroContentParser(String content) {
		try {
			document = parse(content);
			if (document != null) {
				// xml file is loaded. It can be either XHTML or intro XML.
				Element rootElement = document.getDocumentElement();
				// DocumentType docType = document.getDoctype();
				if (rootElement.getTagName().equals(TAG_INTRO_CONTENT)) {
					// intro xml file.
					hasXHTMLContent = false;
				} else if (rootElement.getTagName().equals(TAG_HTML)) {
					// rely on root element to detect if we have an XHTML file
					// and not on doctype. We need to support xhtml files with
					// no doctype.
					hasXHTMLContent = true;
				} else
					// not intro XML nor XHTML.
					document = null;
			}
		} catch (Exception e) {
			Log.error("Could not load Intro content file: " + content, e); //$NON-NLS-1$
		}
	}


	private Document parse(String fileURI) {
		try {
			@SuppressWarnings("restriction")
			DocumentBuilderFactory docFactory = org.eclipse.core.internal.runtime.XmlProcessorFactory.createDocumentBuilderFactoryIgnoringDOCTYPE();
			// if this is not set, Document.getElementsByTagNameNS() will fail.
			docFactory.setNamespaceAware(true);
			docFactory.setExpandEntityReferences(false);
			DocumentBuilder parser = docFactory.newDocumentBuilder();
			parser.setEntityResolver((publicId, systemId) -> new InputSource(new StringReader(""))); //$NON-NLS-1$
			Document document = parser.parse(fileURI);
			return document;

		} catch (SAXParseException spe) {
			StringBuilder buffer = new StringBuilder("IntroParser error in line "); //$NON-NLS-1$
			buffer.append(spe.getLineNumber());
			buffer.append(", uri "); //$NON-NLS-1$
			buffer.append(spe.getSystemId());
			buffer.append("\n"); //$NON-NLS-1$
			buffer.append(spe.getMessage());

			// Use the contained exception.
			Exception x = spe;
			if (spe.getException() != null)
				x = spe.getException();
			Log.error(buffer.toString(), x);

		} catch (SAXException sxe) {
			Exception x = sxe;
			if (sxe.getException() != null)
				x = sxe.getException();
			Log.error(x.getMessage(), x);

		} catch (ParserConfigurationException pce) {
			// Parser with specified options can't be built
			Log.error(pce.getMessage(), pce);

		} catch (IOException ioe) {
			Log.error(ioe.getMessage(), ioe);
		}
		return null;
	}


	/**
	 * Returned the DOM representing the intro xml content file. May return null
	 * if parsing the file failed.
	 *
	 * @return Returns the document.
	 */
	public Document getDocument() {
		return document;
	}

	public boolean hasXHTMLContent() {
		return hasXHTMLContent;
	}


	public static String convertToString(Document document) {
		try {
			// identity xslt.
			@SuppressWarnings("restriction")
			TransformerFactory tFactory = org.eclipse.core.internal.runtime.XmlProcessorFactory.createTransformerFactoryWithErrorOnDOCTYPE();
			Transformer transformer = tFactory.newTransformer();
			DOMSource source = new DOMSource(document);

			StringWriter stringWriter = new StringWriter();
			StreamResult result = new StreamResult(stringWriter);

			// setup properties, for doctype.
			DocumentType docType = document.getDoctype();
			if (docType != null) {
				String value = docType.getSystemId();
				// transformer.clearParameters();
				transformer.setOutputProperty(OutputKeys.DOCTYPE_SYSTEM, value);
				value = document.getDoctype().getPublicId();
				transformer.setOutputProperty(OutputKeys.DOCTYPE_PUBLIC, value);
				transformer.setOutputProperty(OutputKeys.METHOD, "xml"); //$NON-NLS-1$
				transformer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION,
					"yes"); //$NON-NLS-1$
				// transformer.setOutputProperty(OutputKeys.MEDIA_TYPE,
				// "text/html");
				// transformer
				// .setOutputProperty(OutputKeys.ENCODING, "iso-8859-1");
			} else
				Log
					.warning("XHTML file used to display this Intro page does not have a Document type defined. " //$NON-NLS-1$
							+ "XHTML requires document types to be defined."); //$NON-NLS-1$

			transformer.transform(source, result);
			return stringWriter.toString();

		} catch (TransformerConfigurationException tce) {
			// Error generated by the parser
			Log.error("Transformer Config error: " + tce.getMessage(), null); //$NON-NLS-1$
			// Use the contained exception, if any
			Throwable x = tce;
			if (tce.getException() != null)
				x = tce.getException();
			Log.error("Transformer Stack trace: ", x); //$NON-NLS-1$

		} catch (TransformerException te) {
			// Error generated by the parser
			Log.error("Transformer error: " + te.getMessage(), te); //$NON-NLS-1$
			// Use the contained exception, if any
			Throwable x = te;
			if (te.getException() != null)
				x = te.getException();
			Log.error("Transformer Stack trace: ", x); //$NON-NLS-1$

		}
		return null;

	}



}
