//------------------------------------------------------------------------------
// Copyright (c) 2005, 2006 IBM 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:
// IBM Corporation - initial implementation
//------------------------------------------------------------------------------
package org.eclipse.epf.export.services;

import java.io.File;
import java.io.FileFilter;
import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;

import org.eclipse.epf.common.utils.XMLUtil;
import org.eclipse.epf.export.ExportPlugin;
import org.eclipse.epf.persistence.MultiFileSaveUtil;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;


/**
 * Encapsulates a method library using a DOM document.
 * 
 * @author Jinhua Xi
 * @since 1.0
 */
public class LibraryDocument {

	public static final String TAG_methodPlugins = "methodPlugins"; //$NON-NLS-1$

	public static final String TAG_predefinedConfigurations = "predefinedConfigurations"; //$NON-NLS-1$

	public static final String TAG_resourceDescriptors = "resourceDescriptors"; //$NON-NLS-1$

	public static final String TAG_resourceSubManagers = "subManagers"; //$NON-NLS-1$

	public static final String ATTR_href = "href"; //$NON-NLS-1$

	public static final String ATTR_id = "id"; //$NON-NLS-1$

	public static final String ATTR_uri = "uri"; //$NON-NLS-1$

	public static final String ATTR_guid = "guid"; //$NON-NLS-1$

	public static final String exportFile = "export.xmi"; //$NON-NLS-1$

	public static final String libraryFile = "library.xmi"; //$NON-NLS-1$

	protected File libFile;

	protected Document document;

	protected Element libTag = null;

	protected Element resTag = null;
	
	private HashMap guidToUriMap = null;

	public LibraryDocument(File libFile) throws Exception {
		this.libFile = libFile;
		init();
	}

	private void init() throws Exception {
		this.document = XMLUtil.loadXml(libFile);

		Element root = document.getDocumentElement();
		NodeList nodes = root.getElementsByTagName("org.eclipse.epf.uma:MethodLibrary"); //$NON-NLS-1$
		if (nodes != null && nodes.getLength() > 0) {
			libTag = (Element) nodes.item(0);
		}

		nodes = root
				.getElementsByTagName("org.eclipse.epf.uma.resourcemanager:ResourceManager"); //$NON-NLS-1$
		if (nodes != null && nodes.getLength() > 0) {
			resTag = (Element) nodes.item(0);
		}
	}

	public Document getDocument() {
		return this.document;
	}

	public File getFile() {
		return libFile;
	}

	public Element getLibTag() {
		return libTag;
	}

	public String getLibraryName() {
		return libTag.getAttribute("name"); //$NON-NLS-1$
	}

	public String getLibraryGuid() {
		return libTag.getAttribute("guid"); //$NON-NLS-1$
	}

	public Element getResourceTag() {
		return resTag;
	}

	public void removePlugin(Element node) {
		libTag.removeChild(node);
	}

	public void removeConfiguration(Element node) {
		libTag.removeChild(node);
	}

	public void removeResourceDescriptor(Element node) {
		resTag.removeChild(node);
	}

	public NodeList getPlugins() {
		return libTag.getElementsByTagName(TAG_methodPlugins);
	}

	public NodeList getConfigurations() {
		return libTag.getElementsByTagName(TAG_predefinedConfigurations);
	}

	public NodeList getResourceDescriptors() {
		return resTag.getElementsByTagName(TAG_resourceDescriptors);
	}

	public NodeList getResourceSubManagers() {
		return resTag.getElementsByTagName(TAG_resourceSubManagers);
	}

	public void addPlugin(Element node) {
		libTag.appendChild(getValidNode(node));
	}

	public void addConfiguration(Element node) {
		libTag.appendChild(getValidNode(node));
	}

	public void addResource(Element node) {
		resTag.appendChild(getValidNode(node));
	}

	public Node getValidNode(Node node) {
		if (node.getOwnerDocument() == document) {
			return node;
		}

		return document.importNode(node, true);
	}

	/**
	 * remove plugins by guid
	 * 
	 * @param removeList
	 *            List a list of guids
	 */
	public void removePlugins(List removeList) {
		// RATLC00382319 - Importing a package of plgins twice generates 2
		// identical plugins
		// remove the node will cause the node list to shrink, so don't increase
		// the index
		NodeList nodes = getPlugins();
		int i = 0;
		while (i < nodes.getLength()) {
			Element node = (Element) nodes.item(i);
			String guid = getGuid(node);
			if (removeList.contains(guid)) {
				libTag.removeChild(node);
			} else {
				i++;
			}
		}
	}

	public void removeConfigurations(List removeList) {
		// remove the unneeded configurations
		NodeList nodes = getConfigurations();
		int i = 0;
		while (i < nodes.getLength()) {
			Element node = (Element) nodes.item(i);
			String guid = getGuid(node);
			if (removeList.contains(guid)) {
				libTag.removeChild(node);
			} else {
				i++;
			}
		}
	}

	public void removeResourceEntries(List removeList) {
		NodeList nodes = getResourceDescriptors();
		int i = 0;
		while (i < nodes.getLength()) {
			Element node = (Element) nodes.item(i);
			String guid = node.getAttribute(ATTR_id);
			String uri = node.getAttribute(ATTR_uri);
			if (removeList.contains(guid)) {
				resTag.removeChild(node);

				// check the plugin xmi file, if exists, delete the folder
				File plugn_file = getFileFromUri(uri);
				if (plugn_file.exists()) {
					// delete the folder ???
				}
			} else {
				i++;
			}
		}

		// also remove the sub managers
		nodes = getResourceSubManagers();
		i = 0;
		while (i < nodes.getLength()) {
			Element node = (Element) nodes.item(i);
			String guid = getSubManagerBaseGuid(node.getAttribute(ATTR_href));
			if (removeList.contains(guid)) {
				resTag.removeChild(node);
			} else {
				i++;
			}
		}
	}

	/**
	 * get the resource uri for the resource guid
	 * @param guid
	 * @return String the uri
	 */
	public String getResourceUri(String guid) {
		String uri = getUriFromGuidToUriMap(guid);
		if (uri == null) {
			uri = getResourceUri_(guid);
			if (uri != null) {
				//addToGuidToUriMap(guid, uri);
			}
		}
		return uri;
	}
	
	private String getResourceUri_(String guid) {
	
		NodeList nodes = getResourceDescriptors();
		for (int i = 0; i < nodes.getLength(); i++ ) {
			Element node = (Element) nodes.item(i);
			String id = node.getAttribute(ATTR_id);
			String uri = node.getAttribute(ATTR_uri);
			if ( guid.equals(id) ) {
				return decodeUri(uri);
			}
		}
		
		return null;
	}

	public String decodeUri(String uri) {
		try {
			uri = URLDecoder.decode(uri, "UTF-8"); //$NON-NLS-1$
		} catch (UnsupportedEncodingException e) {
			e.printStackTrace();
		}
		
		return uri;
	}
	
	public File getFileFromUri(String uri) {
		uri = decodeUri(uri);
		
		int i = uri.indexOf("#"); //$NON-NLS-1$
		if (i > 0) {
			uri = uri.substring(0, i);
		}

		return new File(libFile.getParentFile(), uri);
	}

	public void save() throws Exception {
		saveAs(libFile.getAbsolutePath());
	}

	public void saveAs(String filePathName) throws Exception {
		XMLUtil.saveDocument(this.document, filePathName);
	}

	public boolean isConfigSpecsOnly() {
		NodeList nodes = getPlugins();
		if (nodes == null || nodes.getLength() == 0) {
			return true;
		}

		for (int i = 0; i < nodes.getLength(); i++) {
			Element node = (Element) nodes.item(i);
			String href = node.getAttribute(LibraryDocument.ATTR_href);
			String guid = getGuidFromHref(href);
			String uri = getResourceUri(guid);
			
			// check if the resource files are there
			File plugn_file = getFileFromUri(uri);
			if (plugn_file.exists()) {
				return false;
			}
		}

		return true;
	}

	// static hlper methods /////////////////////////////////////
	public static String getSubManagerBaseGuid(String href) {
		final Pattern p = Pattern.compile(
				"uma://(.*?)#(.*?)", Pattern.CASE_INSENSITIVE | Pattern.DOTALL); //$NON-NLS-1$
		Matcher m = p.matcher(href);
		if (m.find()) {
			return m.group(1);
		}

		return href;
	}

	public static String getChildValue(Element tag, String childTagName) {
		NodeList nodes = tag.getChildNodes();
		if (nodes == null || nodes.getLength() == 0) {
			return ""; //$NON-NLS-1$
		}

		int size = nodes.getLength();
		for (int i = 0; i < size; i++) {
			Node node = nodes.item(i);
			if ((node instanceof Element)
					&& ((Element) node).getTagName().equals(childTagName)) {
				return getNodeText((Element) node);
			}
		}

		return ""; //$NON-NLS-1$
	}

	/**
	 * text of a leaf node, without child element
	 * 
	 * @param tag
	 * @return
	 */
	public static String getNodeText(Element tag) {

		NodeList nodes = tag.getChildNodes();
		if (nodes == null || nodes.getLength() == 0) {
			return ""; //$NON-NLS-1$
		}

		int size = nodes.getLength();
		StringBuffer buffer = new StringBuffer();
		for (int i = 0; i < size; i++) {
			Node node = nodes.item(i);
			if (node.getNodeType() == Node.TEXT_NODE) {
				buffer.append(node.getNodeValue());
			}
		}

		return buffer.toString();
	}

	public static String getGuidFromHref(String href) {
		int i = href.indexOf("#"); //$NON-NLS-1$
		if (i > 0) {
			return href.substring(i + 1);
		}

		return href;
	}

	public static String getGuid(Element node) {
		String id = node.getAttribute("xmi:id"); //$NON-NLS-1$
		if ( id == null || id.length() == 0 ) {
			String href = node.getAttribute(ATTR_href);
			id = getGuidFromHref(href);
		}
		
		return id;
	}
	
	// 143033 - update config specs' importing and exporting to match with the new file format
	protected Element getConfigNode(Element configNode) {
		try {
			
			// new model defines href for configuration 
			String href = configNode.getAttribute(ATTR_href);
			if (href == null || href.length() == 0 ) {
				return configNode;
			} 

			String guid = getGuidFromHref(href);
			String uri = getResourceUri(guid);
			if ( uri == null ) {
				return configNode;
			}
			
			File source = getFileFromUri(uri);
			Document document = XMLUtil.loadXml(source);
			Element root = document.getDocumentElement();

			Element configTag = null;
			if (root.getTagName().equals("org.eclipse.epf.uma:MethodConfiguration")) //$NON-NLS-1$
			{
				configTag = root;
			} else {
				NodeList nodes = root
						.getElementsByTagName("org.eclipse.epf.uma:MethodConfiguration"); //$NON-NLS-1$
				if (nodes.getLength() > 0) {
					configTag = (Element) nodes.item(0);
				}
			}
			
			return configTag;

		} catch (Exception e) {
			ExportPlugin.getDefault().getLogger().logError(e);
		}
		
		return configNode;
	}
	
	
	public ConfigurationSpec getConfigurationSpec(Element config) {
		ConfigurationSpec spec = new ConfigurationSpec();
		
		Element configNode = getConfigNode(config);
		spec.guid = configNode.getAttribute("xmi:id"); //$NON-NLS-1$
		spec.name = configNode.getAttribute("name"); //$NON-NLS-1$
		spec.brief_desc = configNode.getAttribute("briefDescription"); //$NON-NLS-1$
		
		// get plugins
		NodeList nodes = configNode.getElementsByTagName("methodPluginSelection"); //$NON-NLS-1$
		if (nodes != null) {
			for (int i = 0; i < nodes.getLength(); i++) {
				Element node = (Element) nodes.item(i);
				String guid = getGuidFromHref(node.getAttribute(ATTR_href));
				spec.pluginIds.add(guid);
			}
		}

		// get packages
		nodes = configNode.getElementsByTagName("methodPackageSelection"); //$NON-NLS-1$
		if (nodes != null) {
			for (int i = 0; i < nodes.getLength(); i++) {
				Element node = (Element) nodes.item(i);
				String guid = getGuidFromHref(node.getAttribute(ATTR_href));
				spec.packageIds.add(guid);
			}
		}

		// get views
		nodes = configNode.getElementsByTagName("processViews"); //$NON-NLS-1$
		if (nodes != null) {
			for (int i = 0; i < nodes.getLength(); i++) {
				Element node = (Element) nodes.item(i);
				String guid = getGuidFromHref(node.getAttribute(ATTR_href));
				spec.viewIds.add(guid);
			}
		}

		return spec;
	}
	
	public void addToGuidToUriMap(String guid, String uri) {
		if (guidToUriMap == null) {
			guidToUriMap = new HashMap();
		}
		guidToUriMap.put(guid, uri);
	}
	
	public String getUriFromGuidToUriMap(String guid) {
		return guidToUriMap == null ? null : (String) guidToUriMap.get(guid);
	}
	
	public static class ConfigDocVisitor {
		public void visit(File file, Element node) {			
		}
	}
	
	public static void visitConfigFiles(File configDir, ConfigDocVisitor visitor) {	
		FileFilter filter = new FileFilter() {
			public boolean accept(File pathname) {
				return pathname.isDirectory() || pathname.getName().endsWith(MultiFileSaveUtil.DEFAULT_FILE_EXTENSION);
			}			
		};		
		File[] files = configDir.listFiles(filter);
		if (files == null) {
			return;
		}
		for (int i=0; i<files.length; i++) {
			File file = files[i];
			try {
				DocumentBuilder builder = DocumentBuilderFactory.newInstance()
						.newDocumentBuilder();
				Document doc = builder.parse(file);
				Element root = doc.getDocumentElement();
				visitor.visit(file, root);				
			} catch (Throwable e) {
				e.printStackTrace();
			}
		}		
	}
	
	public Map buildPluginGuidToResMgrGuidMap(List pluginGuids) {
		if (pluginGuids == null || pluginGuids.size() == 0) {
			return null;
		}
		NodeList subMgrs = getResourceSubManagers();
		if (subMgrs == null || subMgrs.getLength() == 0) {
			return null;
		}				
		HashMap pluginGuidToResMgrGuidMap = new HashMap();
		for (int i=0; i<pluginGuids.size(); i++) {			
			String pluginGuid = (String) pluginGuids.get(i);
			pluginGuidToResMgrGuidMap.put(pluginGuid, null);
		}
		boolean isEmpty = true;
		for (int i=0; i<subMgrs.getLength(); i++) {			
			Element mgr = (Element) subMgrs.item(i);
			String  href = mgr.getAttribute(ATTR_href);
			String pluginGuid = getSubManagerBaseGuid(href);
			if (pluginGuidToResMgrGuidMap.containsKey(pluginGuid)) {
				pluginGuidToResMgrGuidMap.put(pluginGuid, getGuidFromHref(href));
				isEmpty = false;
			}
		}		
		return isEmpty ?  null : pluginGuidToResMgrGuidMap;
	}
	
}
