/*******************************************************************************
 * Copyright (c) 2007 Intel Corporation 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:
 * Intel Corporation - Initial API and implementation
 * James Blackburn (Broadcom Corp.)
 *******************************************************************************/
package org.eclipse.cdt.internal.core.settings.model.xml;

import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;

import org.eclipse.cdt.core.settings.model.ICSettingsStorage;
import org.eclipse.cdt.core.settings.model.ICStorageElement;
import org.eclipse.cdt.internal.core.settings.model.ExceptionFactory;
import org.eclipse.core.runtime.CoreException;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

/**
 * Concrete implementation of ICSettingsStorage backed by an XML document
 *
 * ICStorageElements are stored in the tree under a storageModule element.
 * (This class was previously called CStorage)
 * TODO JBB Make this independent of the Xml Element
 */
public class XmlStorage implements ICSettingsStorage {
	public static final String MODULE_ELEMENT_NAME = "storageModule";	//$NON-NLS-1$
	public static final String MODULE_ID_ATTRIBUTE = "moduleId";	//$NON-NLS-1$
	public Element fElement;
	private Map<String, InternalXmlStorageElement> fStorageElementMap = new HashMap<String, InternalXmlStorageElement>();
	private boolean fChildrenInited;
	private boolean fIsReadOnly;
	private boolean fIsDirty;

	public XmlStorage(Element element, boolean isReadOnly){
		fElement = element;
		fIsReadOnly = isReadOnly;
	}

	public XmlStorage(InternalXmlStorageElement element) throws CoreException {
		fElement = element.fElement;
		fIsReadOnly = element.isReadOnly();
		element.storageCreated(this);
		sanityCheck(element);
	}

	/**
	 * Check that the XmlStorageElement on which this SettingsStorage
	 * is based doesn't have any attributes, values or children which
	 * are invalid for a settings storage.
	 * @param element
	 * @throws CoreException
	 */
	public void sanityCheck(ICStorageElement element) throws CoreException {
		if (element.getValue() != null && element.getValue().trim().length() > 0)
			throw ExceptionFactory.createCoreException("XmlStorage '" + element.getName() + "' has unexpected child Value: " + element.getValue());
		for (ICStorageElement child : element.getChildren()) {
			if (!MODULE_ELEMENT_NAME.equals(child.getName()))
				throw ExceptionFactory.createCoreException("XmlStorage '" + element.getName() + "' has unexpected child element: " + child.getName());
			if (child.getAttribute(MODULE_ID_ATTRIBUTE) == null)
				throw ExceptionFactory.createCoreException("XmlStorage '" + element.getName() + "' has storageModule child without moduleId");
		}
	}

	public boolean isReadOnly(){
		return fIsReadOnly;
	}

	/**
	 * Initialise the set of storageModules of this XmlStorage
	 */
	private void initChildren(){
		if(fChildrenInited)
			return;
		fChildrenInited = true;

		NodeList children = fElement.getChildNodes();
		int size = children.getLength();
		for(int i = 0; i < size; i++){
			Node node = children.item(i);
			if(node.getNodeType() != Node.ELEMENT_NODE)
				continue;
			if(!MODULE_ELEMENT_NAME.equals(node.getNodeName()))
				continue;

			Element element = (Element)node;
			String moduleId = element.getAttribute(MODULE_ID_ATTRIBUTE).trim();
			if(moduleId.length() == 0)
				continue;

			createAddStorageElement(moduleId, element);
		}
	}

	private InternalXmlStorageElement createAddStorageElement(String id, Element element){
		InternalXmlStorageElement se = createStorageElement(element, fIsReadOnly);
		fStorageElementMap.put(id, se);
		return se;
	}

	public static InternalXmlStorageElement createStorageElement(Element el, boolean isReadOnly){
		return new InternalXmlStorageElement(el, null, new String[]{MODULE_ID_ATTRIBUTE}, null, isReadOnly);
	}

//	public ICStorageElement getStorage(String id){
//		return getStorage(id, true);
//	}


	public boolean containsStorage(String id) throws CoreException {
		return getStorage(id, false) != null;
	}

	public ICStorageElement importStorage(String id, ICStorageElement el) throws UnsupportedOperationException {
		if(fIsReadOnly)
			throw ExceptionFactory.createIsReadOnlyException();

		// Remove existing storage with this ID
		removeStorage(id);

		// Create the storage element for import
		Document thisDoc = fElement.getOwnerDocument();
		Element newEl = thisDoc.createElement(MODULE_ELEMENT_NAME);

		fIsDirty = true;

		if (el instanceof XmlStorageElement) {
			// If we're importing an XmlStorageElement use XML methods
			XmlStorageElement xmlStEl = (XmlStorageElement)el;
			Element xmlEl = xmlStEl.fElement;
			Document otherDoc = xmlEl.getOwnerDocument();
			if(!thisDoc.equals(otherDoc)){
				xmlEl = (Element)thisDoc.importNode(xmlEl, true);
			}

			NodeList nl = xmlEl.getChildNodes();
			for(int i = 0; i < nl.getLength(); i++){
				Node child = nl.item(i).cloneNode(true);
				newEl.appendChild(child);
			}
			newEl = (Element)fElement.appendChild(newEl);
			newEl.setAttribute(MODULE_ID_ATTRIBUTE, id);

			return createAddStorageElement(id, newEl);
		} else {
			// Otherwise importing generic ICStorageElement
			ICStorageElement storageEl = getStorage(id, true);
			for (String attrName: el.getAttributeNames())
				storageEl.setAttribute(attrName, el.getAttribute(attrName));
			for (ICStorageElement child: el.getChildren())
				storageEl.importChild(child);
			return storageEl;
		}
	}

	public ICStorageElement getStorage(String id, boolean create){
		initChildren();

		InternalXmlStorageElement se = fStorageElementMap.get(id);
		if(se == null && create){
//			if(fIsReadOnly)
//				throw ExceptionFactory.createIsReadOnlyException();

			fIsDirty = true;
			Document doc = fElement.getOwnerDocument();
			Element child = createStorageXmlElement(doc, id);
			fElement.appendChild(child);
			se = createAddStorageElement(id, child);
		}
		return se;
	}

	public static Element createStorageXmlElement(Document doc, String storageId){
		Element child = doc.createElement(MODULE_ELEMENT_NAME);
		child.setAttribute(MODULE_ID_ATTRIBUTE, storageId);

		return child;
	}

	public void removeStorage(String id){
		initChildren();
		InternalXmlStorageElement se = fStorageElementMap.remove(id);

		if(se != null){
			if(fIsReadOnly)
				throw ExceptionFactory.createIsReadOnlyException();

			fIsDirty = true;
			Node nextSibling = se.fElement.getNextSibling();
			fElement.removeChild(se.fElement);
			if (nextSibling != null && nextSibling.getNodeType() == Node.TEXT_NODE) {
				String value = nextSibling.getNodeValue();
				if (value != null && value.trim().length() == 0) {
					// remove whitespace
					fElement.removeChild(nextSibling);
				}
			}
		}
	}

	public boolean isModified(){
		if(fIsDirty)
			return true;

		for(Iterator<InternalXmlStorageElement> iter = fStorageElementMap.values().iterator(); iter.hasNext();){
			InternalXmlStorageElement el = iter.next();
			if(el.isModified())
				return true;
		}

		return false;
	}

	public void setReadOnly(boolean readOnly, boolean keepModify){
		fIsReadOnly = readOnly;
		fIsDirty &= keepModify;
		for(Iterator<InternalXmlStorageElement> iter = fStorageElementMap.values().iterator(); iter.hasNext();){
			InternalXmlStorageElement el = iter.next();
			el.setReadOnly(readOnly, keepModify);
		}
	}

	public void setDirty(boolean isDirty){
		fIsDirty = isDirty;

		if(!fIsDirty){
			for(Iterator<InternalXmlStorageElement> iter = fStorageElementMap.values().iterator(); iter.hasNext();){
				InternalXmlStorageElement el = iter.next();
				el.setDirty(false);
			}
		}
	}

	public void save() throws CoreException {
		throw new UnsupportedOperationException();
	}
}
