/*******************************************************************************
 * Copyright (c) 2001, 2012 IBM Corporation and others.
 * All rights reserved. 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
 *     Jens Lukowski/Innoopract - initial renaming/restructuring
 *     David Carver (Intalio) - bug 300434 - Make inner classes static where possible
 *******************************************************************************/
package org.eclipse.wst.sse.internal.contentproperties;

import java.io.File;
import java.io.IOException;
import java.io.OutputStream;
import java.util.HashMap;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;

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

import org.eclipse.wst.sse.core.internal.Logger;
import org.w3c.dom.Attr;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;

/**
* @deprecated This is package protected so no one cares anyways.
*/
class SimpleNodeOperator {

	static class CreateContentSettingsFailureException extends Exception {
		/**
		 * Comment for <code>serialVersionUID</code>
		 */
		private static final long serialVersionUID = 1L;

		public CreateContentSettingsFailureException(String reason) {
			super(reason);
		}
	}


	static class ReadContentSettingsFailureException extends Exception {
		/**
		 * Comment for <code>serialVersionUID</code>
		 */
		private static final long serialVersionUID = 1L;

		public ReadContentSettingsFailureException(String reason) {
			super(reason);
		}
	}

	static class WriteContentSettingsFailureException extends Exception {
		/**
		 * Comment for <code>serialVersionUID</code>
		 */
		private static final long serialVersionUID = 1L;

		public WriteContentSettingsFailureException(String reason) {
			super(reason);
		}
	}

	// writer class for .contentSettings.
	static class XMLDocumentWriter {
		OutputStream fOut;

		protected XMLDocumentWriter(OutputStream out) {
			this.fOut = out;
		}

		protected final void close() {
			try {
				fOut.close();
			} catch (IOException e) {
				// do nothing, shouldn't matter
			}
		}

		protected void serialize(Document sourceDocument) throws WriteContentSettingsFailureException {
			// JAXP transformation
			Source domSource = new DOMSource(sourceDocument);
			try {
				Transformer serializer = TransformerFactory.newInstance().newTransformer();
				try {
					serializer.setOutputProperty(OutputKeys.INDENT, "yes"); //$NON-NLS-1$
					serializer.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "4"); //$NON-NLS-1$ //$NON-NLS-2$
				} catch (IllegalArgumentException e) {
					// unsupported properties
				}
				serializer.transform(domSource, new StreamResult(fOut));
			} catch (TransformerConfigurationException e) {
				throw new WriteContentSettingsFailureException(e.getMessage());
			} catch (TransformerFactoryConfigurationError e) {
				throw new WriteContentSettingsFailureException(e.getMessage());
			} catch (TransformerException e) {
				throw new WriteContentSettingsFailureException(e.getMessage());
			}
		}
	}

	public static void main(String[] args) {
		SimpleNodeOperator a = null;
		try {
			a = new SimpleNodeOperator("workspace/org.eclipse.examples.contentsettings/.contentsettings.xml");//$NON-NLS-1$
		} catch (Exception e) {
			System.exit(0);
		}

		// print all Elements
		//a.printTree(iter);

		// add Element
		Map attMap = new Hashtable();
		attMap.put("path", "hogepath");//$NON-NLS-1$ //$NON-NLS-2$
		attMap.put("fDocument-type", "documenthogehoge");//$NON-NLS-1$ //$NON-NLS-2$
		a.addElementUnderRoot("file", attMap);//$NON-NLS-1$

		try {
			a.writeDocument(System.out);
		} catch (Exception e) {
			System.err.println(e.toString());
		}

	}

	//never used
	//private DOMParser parser;
	private Document fDocument;
	private Node root;

	private String settingsFileName;


	public SimpleNodeOperator(Document doc) throws CreateContentSettingsFailureException {

		if (doc == null)
			throw new CreateContentSettingsFailureException("Document doc==null");//$NON-NLS-1$
		fDocument = doc;
		root = fDocument.getLastChild();
		if (root == null)
			throw new CreateContentSettingsFailureException("Node root==null");//$NON-NLS-1$
	}

	public SimpleNodeOperator(String fullPath) throws ReadContentSettingsFailureException {
		this.settingsFileName = fullPath;
		createObjectOfDocument();
	}

	// add attribute(attName=attValue) of ele without checking overlapping of
	// another attributes of ele.
	// if overlapping ,override
	protected Node addAttributeAt(Element ele, String attName, String attValue) {
		Attr att = fDocument.createAttribute(attName);
		att.setValue(attValue);
		if (ele != null)
			ele.setAttributeNode(att);
		return ele;
	}

	protected Node addElementUnder(Node parent, String tagName, Map attMap) {
		if (parent == null || tagName == null)
			return null;
		Element e = fDocument.createElement(tagName);
		if (attMap != null) {
			if (!attMap.isEmpty()) {
				Set attKeys = attMap.keySet();
				Iterator iter = attKeys.iterator();
				while (iter.hasNext()) {
					String key = (String) iter.next();
					e.setAttribute(key, (String) attMap.get(key));
				}
			}
		}
		parent.appendChild(e);
		return e;
	}

	protected final Node addElementUnderRoot(String tagName) {
		return addElementUnder(root, tagName, null);
	}

	// add element with attMap as attribute without checking overlapping.
	protected final Node addElementUnderRoot(String tagName, Map attMap) {
		return addElementUnder(root, tagName, attMap);
	}

	private void createObjectOfDocument() throws ReadContentSettingsFailureException {
		try {
			fDocument = DocumentBuilderFactory.newInstance().newDocumentBuilder().parse(new File(settingsFileName));
		} catch (SAXException e) {
			Logger.logException("exception parsing" + settingsFileName, e); //$NON-NLS-1$
		} catch (IOException e) {
			Logger.logException("I/O exception parsing" + settingsFileName, e); //$NON-NLS-1$
		} catch (ParserConfigurationException e) {
			Logger.logException("exception accessing DOMImplementation", e); //$NON-NLS-1$
		} catch (FactoryConfigurationError e) {
			Logger.logException("exception accessing DOMImplementation", e); //$NON-NLS-1$
		}
		//get the root of the XML fDocument
		root = fDocument.getLastChild();
		if (root == null) {
			throw new ReadContentSettingsFailureException("Error: Node root==null");//$NON-NLS-1$
		}
	}

	protected Map getAttributesOf(Node node) {
		if (!node.hasAttributes())
			return null;
		Map map = new HashMap();
		NamedNodeMap attrs = node.getAttributes();
		int size = attrs.getLength();
		for (int i = 0; i < size; i++) {
			Attr attr = (Attr) attrs.item(i);
			map.put(attr.getName(), attr.getValue());
		}
		return (map);
	}

	private Node getElementWithAttribute(Node first, String attName, String attValue) {
		Node navpoint = first;
		while (navpoint != null) {
			if (navpoint.getNodeType() == Node.ELEMENT_NODE) {
				NamedNodeMap m = navpoint.getAttributes();
				if (m == null)
					continue;
				if (m.getNamedItem(attName) != null) {
					if (attValue.equals(((Attr) m.getNamedItem(attName)).getNodeValue()))
						return navpoint;
				}
				NodeList childNodes = navpoint.getChildNodes();
				if (childNodes != null && childNodes.getLength() > 0) {
					Node holdNode = getElementWithAttribute(navpoint.getFirstChild(), attName, attValue);
					if (holdNode != null) {
						return holdNode;
					}
				}
			}
			navpoint = navpoint.getNextSibling();
		}
		return null;
	}


	// return a (first) Element with attr(attName=attValue) it if exists,
	// otherwise return null
	protected Node getElementWithAttribute(String attName, String attValue) {
		if (attName == null || attValue == null || !fDocument.hasChildNodes())
			return null;
		return getElementWithAttribute(fDocument.getFirstChild(), attName, attValue);
	}

	// retrun Element which has nodeName as Node Name
	protected Node getElementWithNodeName(String nodeName) {
		if (nodeName == null)
			return null;
		NodeList nodes = fDocument.getElementsByTagName(nodeName);
		if (nodes.getLength() > 0) {
			return nodes.item(0);
		}
		return null;
	}

	// remove attribute(attName) at ele.
	protected Attr removeAttributeAt(Element ele, String attName) {
		if (ele == null || attName == null)
			return null;
		Attr att = ele.getAttributeNode(attName);
		ele.removeAttribute(attName);
		return att;
	}

	protected Element removeElementWith(String nodeName) {
		NodeList nodes = fDocument.getElementsByTagName(nodeName);
		for (int i = 0; i < nodes.getLength(); i++) {
			nodes.item(i).getParentNode().removeChild(nodes.item(i));
		}
		return null;
	}

	// remove a (first) Element with attr(attName=attValue) and return it if
	// exists, otherwise return null
	protected Element removeElementWith(String attName, String attValue) {
		if (fDocument.hasChildNodes()) {
			Node element = getElementWithAttribute(attName, attValue);
			if (element != null && element.getNodeType() == Node.ELEMENT_NODE) {
				element.getParentNode().removeChild(element);
				return (Element) element;
			}
		}
		return null;

	}

	// update attribute(attName=newValue) at ele if both ele and attribute of
	// ele exist
	protected void updateAttributeAt(Element ele, String attName, String newValue) {
		Attr att = null;
		if (ele != null)
			if ((att = ele.getAttributeNode(attName)) != null)
				att.setValue(newValue);
	}

	protected void writeDocument(OutputStream out) throws WriteContentSettingsFailureException {
		XMLDocumentWriter writer = new XMLDocumentWriter(out);
		try {
			writer.serialize(fDocument);
		} finally {
			writer.close();
		}
	}


}
