/*******************************************************************************
 * Copyright (c) 2007, 2008 Symbian Software Limited 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:
 * Bala Torati (Symbian) - Initial API and implementation
 *******************************************************************************/
package org.eclipse.cdt.core.templateengine;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.Result;
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.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;


/**
 * Processes the shared default values. Updates and Persists new key - value (default) pair
 */
public class SharedDefaults extends HashMap<String, String> {
	private static final long serialVersionUID = 0000000000L;

	public Document document;
	private File parsedXML;
	private File backUpSharedXML;

	/**
	 * HashMap's for persistence
	 */
	private HashMap<String, String> sharedDefaultsMap;
	private HashMap<String, String> persistDataMap;
	private HashMap<String, String> tableDataMap;

	/**
	 * Two XML files here supports to provide consistent writing of data into
	 * them even during some destructive events which can happen during data
	 * persistence
	 */
	private static final String SHARED_DEFAULTS_DOT_XML = "shareddefaults.xml"; //$NON-NLS-1$
	private static final String SHARED_DEFAULTS_DOT_BACKUP_DOT_XML = "shareddefaults.backup.xml"; //$NON-NLS-1$

	/**
	 * Static reference string for getting (GET) and storing (SET)
	 * shareddefaults.xml
	 */

	public static final String SET = "SET"; //$NON-NLS-1$ 
	public static final String GET = "GET"; //$NON-NLS-1$

	/**
	 * Specifies the folder name present in the plugin
	 */
	public static final String ResourceFolder = "resources"; //$NON-NLS-1$

	/**
	 * Static reference of Singleton SharedDefault Instance
	 */
	private static SharedDefaults SHAREDDEFAULTS = new SharedDefaults();

	/**
	 * @return the shared SharedDefaults Instance
	 */
	public static SharedDefaults getInstance() {
		return SHAREDDEFAULTS;
	}

	/**
	 * Default Constructor for creating and instantiating objects. On the
	 * startup of Template Engine, if it checks for the existence of
	 * TempSharedDefaultsXML file, then it is determined that the last Template
	 * Engine process under went some System destructive events and takes up
	 * reconstructive process to regain the consistent data by persisting all
	 * information first into temporary file and then into actual file.
	 */

	public SharedDefaults() {
		sharedDefaultsMap = new HashMap<String, String>();
		persistDataMap = new HashMap<String, String>();
		tableDataMap = new HashMap<String, String>();

		// The conditional controls here is provided to have consistent
		// data storage in the file during System crash or
		// Power shutdown during data persistence into the file.

		parsedXML = TemplateEngineHelper.getSharedDefaultLocation(SHARED_DEFAULTS_DOT_XML);
		backUpSharedXML = TemplateEngineHelper.getSharedDefaultLocation(SHARED_DEFAULTS_DOT_BACKUP_DOT_XML);

		if (backUpSharedXML.exists())
			swapXML();

		initSharedDefaults();
	}

	/**
	 * This method instantiates the SharedDefaults process by gathering XML
	 * document and creating shared key-value pair in HashMap. Also creates a
	 * new XML file if none exists and adds the default XML format.
	 */

	private void initSharedDefaults() {
		String key = null;
		String value = null;

		try {
			long length = parsedXML.length();
			// Adds defaultXML format if the file length is zero
			if (length == 0) {
				parsedXML = createDefaultXMLFormat(parsedXML);
			}
			document = DocumentBuilderFactory.newInstance().newDocumentBuilder().parse(parsedXML.toURI().toURL().openStream());
		} catch (Exception exp) {
			TemplateEngineUtil.log(exp);
		}

		List<Element> sharedElementList = TemplateEngine.getChildrenOfElement(document.getDocumentElement());
		int listSize = sharedElementList.size();
		for (int i = 0; i < listSize; i++) {
			Element xmlElement = sharedElementList.get(i);
			key = xmlElement.getAttribute(TemplateEngineHelper.ID);
			value = xmlElement.getAttribute(TemplateEngineHelper.VALUE);
			if (key != null && !key.trim().equals("")) { //$NON-NLS-1$
				sharedDefaultsMap.put(key, value);
			}
		}
	}

	/**
	 * This method updates the HashMap with new key-value pair into the XML file
	 * 
	 * @param sharedMap
	 */

	public void updateShareDefaultsMap(Map<String, String> sharedMap) {
		sharedDefaultsMap.putAll(sharedMap);
		persistSharedValueMap();
	}

	/**
	 * This method persists the latest data (HashMap) in the XML file New data
	 * obtained from the PreferencePage GUI.
	 */

	public void persistSharedValueMap() {
		generateSharedXML(backUpSharedXML);
		generateSharedXML(parsedXML);
		swapXML();
	}

	/**
	 * This method returns the latest key value pair (HashMap)
	 * 
	 * @return HashMap
	 */

	public Map<String, String> getSharedDefaultsMap() {
		return sharedDefaultsMap;
	}

	/**
	 * Adds data to the backend XML (persistence) Data obtained from the
	 * PreferencePage GUI.
	 */

	public void addToBackEndStorage(String name, String value) {
		if (sharedDefaultsMap != null) {
			tableDataMap.putAll(sharedDefaultsMap);
		}

		tableDataMap.put(name, value);
		updateShareDefaultsMap(tableDataMap);
	}

	/**
	 * Updates backend with changed value for a specific key(name)
	 * 
	 * @param updateName
	 * @param updateValue
	 */
	public void updateToBackEndStorage(String updateName, String updateValue) {
		try {
			document = DocumentBuilderFactory.newInstance().newDocumentBuilder().parse(parsedXML.toURI().toURL().openStream());
		} catch (Exception exp) {
			TemplateEngineUtil.log(exp);
		}

		persistDataMap.putAll(sharedDefaultsMap);
		List<Element> sharedElementList = TemplateEngine.getChildrenOfElement(document.getDocumentElement());
		int elementListSize = sharedElementList.size();

		for (int i = 0; i < elementListSize; i++) {
			Element xmlElement = sharedElementList.get(i);
			String name = xmlElement.getAttribute(TemplateEngineHelper.ID);

			if (updateName.equals(name)) {
				persistDataMap.put(updateName, updateValue);
			}
		}

		updateShareDefaultsMap(persistDataMap);
	}

	/**
	 * Deletes the key-value pair from the backend with Key as identifier.
	 * 
	 * @param deleteName
	 */
	public void deleteBackEndStorage(String[] deleteName) {
		try {
			document = DocumentBuilderFactory.newInstance().newDocumentBuilder().parse(parsedXML.toURI().toURL().openStream());
		} catch (Exception exp) {
			TemplateEngineUtil.log(exp);
		}

		List<Element> sharedElementList = TemplateEngine.getChildrenOfElement(document.getDocumentElement());
		int elementListSize = sharedElementList.size();
		for (int i = 0; i < elementListSize; i++) {

			Element xmlElement = sharedElementList.get(i);
			String name = xmlElement.getAttribute(TemplateEngineHelper.ID);

			for (int k = 0; k < deleteName.length; k++) {
				if (deleteName[k].equals(name)) {
					xmlElement.removeAttribute(name);
					sharedDefaultsMap.remove(name);
				}
			}
		}

		updateShareDefaultsMap(sharedDefaultsMap);
	}

	/**
	 * This method returns the default XMLFormat for the newly created XML file
	 * 
	 * @param parsedXML
	 * @return
	 */

	private File createDefaultXMLFormat(File xmlFile) {
		Document d;
		try {
			d = DocumentBuilderFactory.newInstance().newDocumentBuilder().newDocument();
		} catch (ParserConfigurationException e) {
			TemplateEngineUtil.log(e);
			return xmlFile;
		}
		Node rootElement = d.appendChild(d.createElement("SharedRoot")); //$NON-NLS-1$
		Element element = (Element) rootElement.appendChild(d.createElement("SharedProperty")); //$NON-NLS-1$
		element.setAttribute(TemplateEngineHelper.ID, ""); //$NON-NLS-1$
		element.setAttribute(TemplateEngineHelper.VALUE, ""); //$NON-NLS-1$

		DOMSource domSource = new DOMSource(d);
		TransformerFactory transFactory = TransformerFactory.newInstance();
		
		try {
			FileOutputStream fos= null;
			try {
				fos= new FileOutputStream(xmlFile);
				Result fileResult = new StreamResult(fos);
				transFactory.newTransformer().transform(domSource, fileResult);
			} finally {
				if(fos!=null) {
					fos.close();
				}
			}
		} catch(IOException ioe) {
			TemplateEngineUtil.log(ioe);
		} catch(TransformerConfigurationException tce) {
			TemplateEngineUtil.log(tce);
		} catch(TransformerException te) {
			TemplateEngineUtil.log(te);
		}
		return xmlFile;
	}

	/**
	 * This method generates XML file for backupshareddefaults and
	 * shareddefaults to support consistent persistency
	 */

	private void generateSharedXML(File xmlFile) {
		Document d;
		try {
			d = DocumentBuilderFactory.newInstance().newDocumentBuilder().newDocument();
		} catch (ParserConfigurationException e) {
			TemplateEngineUtil.log(e);
			return;
		}
		Node rootElement = d.appendChild(d.createElement("SharedRoot"));  //$NON-NLS-1$
		
		for(String key : sharedDefaultsMap.keySet()) {		
			Element element = (Element) rootElement.appendChild(d.createElement("SharedProperty"));  //$NON-NLS-1$
			element.setAttribute(TemplateEngineHelper.ID, key);
			element.setAttribute(TemplateEngineHelper.VALUE, sharedDefaultsMap.get(key));
		}

		DOMSource domSource = new DOMSource(d);
		TransformerFactory transFactory = TransformerFactory.newInstance();
		Result fileResult = new StreamResult(xmlFile);
		try {
			transFactory.newTransformer().transform(domSource, fileResult);
		} catch (Throwable t) {
			TemplateEngineUtil.log(t);
		}
	}

	/**
	 * This method swaps the backup file name to XML file containing latest or
	 * persisted data
	 */

	private void swapXML() {
		if (parsedXML.exists())
			parsedXML.delete();
		backUpSharedXML.renameTo(parsedXML);
	}
}
