//------------------------------------------------------------------------------
// 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.common.serviceability;

import java.io.File;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.MissingResourceException;
import java.util.ResourceBundle;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.eclipse.core.runtime.IConfigurationElement;
import org.eclipse.core.runtime.IExtension;
import org.eclipse.core.runtime.IExtensionPoint;
import org.eclipse.core.runtime.IExtensionRegistry;
import org.eclipse.core.runtime.Platform;
import org.eclipse.epf.common.CommonPlugin;
import org.eclipse.epf.common.utils.FileUtil;
import org.osgi.framework.Bundle;
import org.osgi.framework.Version;


/**
 * This class contains methods to query the schema & tool version
 * @author jhardy
 *
 */
public class VersionUtil {
	
	public static class VersionCheckInfo {
		public VersionCheckInfo(String id, String toolVersion, String currentMinToolVersion, int result) {
			this.toolID = id;
			this.toolVersion = toolVersion;
			this.currentMinToolVersion = currentMinToolVersion;
			this.result = result;
		}
		public String toolID;
		public String toolVersion;
		public String currentMinToolVersion;
		
		/**
		 *  < 0:	fileToolVersion is older than currentMinToolVersion <br/>
		 * == 0:	match <br/>
		 *  > 0 : 	fileToolVersion is newer than currentMinToolVersion <br/>
		 */
		public int result;		
	}

	/**
	 * The extension point namespace.
	 */
	public static final String EXTENSION_POINT_NAMESPACE = CommonPlugin.class
		.getPackage().getName();

	/**
	 * The extension point name.
	 */
	public static final String EXTENSION_POINT_NAME = "version"; //$NON-NLS-1$
	
	private static final String DEFAULT_TOOL_ID = "epf"; //$NON-NLS-1$
	
	public static Map versions = new LinkedHashMap();
	
	protected static boolean versionCheckingEnabled = true;
	
	public static final int FILETYPE_XML = 1;
	public static final int FILETYPE_XMI = 2;
	
	
	/**
	 * The tool id to check first
	 */
	private static String primaryToolID = DEFAULT_TOOL_ID;
	
	static {
		// read properties file
		try {
			ResourceBundle resourceBundle = ResourceBundle.getBundle(VersionUtil.class.getPackage().getName()
					+ ".VersionUtil"); //$NON-NLS-1$
			versionCheckingEnabled = Boolean.valueOf(resourceBundle.getString("versionCheckingEnabled")).booleanValue(); //$NON-NLS-1$
		}
		catch (MissingResourceException e) {
			versionCheckingEnabled = true;
		}
		// Process the "org.eclipse.epf.common.version" extension point
		// contributors.
		IExtensionRegistry extensionRegistry = Platform.getExtensionRegistry();
		IExtensionPoint extensionPoint = extensionRegistry.getExtensionPoint(
				EXTENSION_POINT_NAMESPACE, EXTENSION_POINT_NAME);
		if (extensionPoint != null) {
			IExtension[] extensions = extensionPoint.getExtensions();
			for (int i = 0; i < extensions.length; i++) {
				IExtension extension = extensions[i];
				initExtension(extension);
			}
		}
	}
	
	protected static void initExtension(IExtension extension) {
		String extensionID = extension.getSimpleIdentifier();
		String pluginId = extension.getNamespaceIdentifier();
		Bundle bundle = Platform.getBundle(pluginId);
		IConfigurationElement[] configElements = extension
			.getConfigurationElements();
		for (int j = 0; j < configElements.length; j++) {
			IConfigurationElement configElement = configElements[j];
			try {
				String className = configElement.getAttribute("class"); //$NON-NLS-1$
				if(className != null) {
					versions.put(extensionID, (EPFVersions)bundle.loadClass(className).newInstance());
					break;
				}
			} catch (Exception e) {
				CommonPlugin.getDefault().getLogger().logError(e);
			}
		}
	}

	/**
	 * 
	 * @return the map of Tool IDs-to-Versions class
	 */
	public static Map getVersionsMap() {
		return Collections.unmodifiableMap(versions);
	}
	
	/**
	 * 
	 * @return a Set of all known Tool IDs
	 */
	public static Set getAllToolIDs() {
		return Collections.unmodifiableSet(versions.keySet());
	}
	
	/**
	 * 
	 * @param toolID 
	 * @return The Versions class for the given Tool ID
	 */
	public static EPFVersions getVersions(String toolID) {
		return (EPFVersions)versions.get(toolID);
	}

	/**
	 * 
	 * @return true iff version checking is enabled
	 */
	public static boolean isVersionCheckingEnabled() {
		return versionCheckingEnabled;
	}
	
	public static Pattern p_versionPattern = Pattern.compile("(\\w+?):version=\"(.+?)\""); //$NON-NLS-1$
	public static final String XMI_ELEMENT_START_TAG = "<xmi:XMI"; //$NON-NLS-1$
	public static final String XML_ELEMENT_END_TAG = ">"; //$NON-NLS-1$
	public static final String XMI_ATTRIBUTE_TAG = "xmi"; //$NON-NLS-1$
	public static final String XML_ELEMENT_START_TAG = "<uma:MethodLibrary"; //$NON-NLS-1$
	
	/**
	 * Given a (XML) file, will read the foo:version="x.x.x" attributes
	 * and return a map of the id-to-versions.
	 * @param xmlFile file to process
	 * @param startTag String that starts the rootElement, where version info is found
	 * @param endTag String that ends the rootElement
	 * @param skipTag tag to skip (where tag:version=\"..\" is found in the rootElement)
	 * @return null if no versions found; a map of the id-to-versions otherwise
	 */
	public static Map readVersionsFromFile(File xmlFile, String startTag, String endTag,
			String skipTag) {
		Map versions = new LinkedHashMap();
		StringBuffer buf = null;
		try {
			buf = FileUtil.readFile(xmlFile, FileUtil.ENCODING_UTF_8);
		} catch (Exception ex) {
			CommonPlugin.getDefault().getLogger().logError(ex);
		}
		if (buf != null) {
			int xmiElementStartIdx = buf.indexOf(startTag);
			if (xmiElementStartIdx != -1) {
				int xmiElementEndIdx = buf.indexOf(endTag, xmiElementStartIdx + startTag.length());
				if (xmiElementEndIdx != -1) {
					String rootElement = buf.substring(xmiElementStartIdx, xmiElementEndIdx);
					Matcher m = p_versionPattern.matcher(rootElement);
					while (m.find()) {
						String toolID = m.group(1);
						if (toolID.equals(skipTag))
							continue;
						String toolVersion = m.group(2);
						if (toolID != null && toolID.trim().length() > 0 &&
								toolVersion != null && toolVersion.trim().length() > 0)
						versions.put(toolID, toolVersion);
					}
				}
			}
		}
		if (versions.size() == 0) {
			return null;
		} else {
			return versions;
		}
	}

	/**
	 * Given a file, will compare with current XML Schema version.
	 * @param file
	 * @param type one of XMI_FILE_TYPE or XML_FILE_TYPE
	 * @return null if file tool version can not be found; a VersionCheckInfo object otherwise
	 */
	public static VersionCheckInfo checkXMLVersion(File file, int type) {
		String startTag = XML_ELEMENT_START_TAG;
		String endTag = XML_ELEMENT_END_TAG;
		String skipTag = null;
		if (type == FILETYPE_XMI) {
			startTag = XMI_ELEMENT_START_TAG;
			skipTag = XMI_ATTRIBUTE_TAG;
		}
		Map versionMap = VersionUtil.readVersionsFromFile(file, 
				startTag, endTag, skipTag);
		if (versionMap == null) {
			return null;
		}
		else {
			VersionCheckInfo vci = null;
			// check primary tool ID
			if (versionMap.get(primaryToolID) != null) {
				String primaryToolVersion = (String)versionMap.get(primaryToolID);
				vci = _checkXMLVersion(primaryToolID, primaryToolVersion);
				if (vci != null)
					return vci;
			}
			// check default tool ID if it's not primary
			if (!DEFAULT_TOOL_ID.equals(primaryToolID)) {
				if (versionMap.get(DEFAULT_TOOL_ID) != null) {
					String defaultToolVersion = (String)versionMap.get(DEFAULT_TOOL_ID);
					vci = _checkXMLVersion(DEFAULT_TOOL_ID, defaultToolVersion);
					if (vci != null)
						return vci;
				}
			}
		}
		return new VersionCheckInfo(null, null, null, 0);
	}

	private static VersionCheckInfo _checkXMLVersion(String toolID, String toolVersion) {
		// get the class that stores all the version info for this tool ID
		EPFVersions versions = getVersions(toolID);
		if (versions != null) {
			EPFVersion minCurrVersion = versions.getMinToolVersionForCurrentXMLSchemaVersion();
			int result = minCurrVersion.compareToolVersionTo(new Version(toolVersion));
			String currentMinToolVersion = minCurrVersion.getToolVersion().toString();
			return new VersionCheckInfo(toolID, toolVersion, currentMinToolVersion, - result);
		}
		return null;
	}

	/**
	 * Given a file, compares with current library schema version
	 * @param file
	 * @param type one of XMI_FILE_TYPE or XML_FILE_TYPE
	 * @return null if file tool version can not be found; a VersionCheckInfo object otherwise
	 */
	public static VersionCheckInfo checkLibraryVersion(File file, int type) {
		String startTag = XML_ELEMENT_START_TAG;
		String endTag = XML_ELEMENT_END_TAG;
		String skipTag = null;
		if (type == FILETYPE_XMI) {
			startTag = XMI_ELEMENT_START_TAG;
			skipTag = XMI_ATTRIBUTE_TAG;
		}
		Map versionMap = VersionUtil.readVersionsFromFile(file, 
				startTag, endTag, skipTag);
		if (versionMap == null) {
			return null;
		}
		else {
			VersionCheckInfo vci = null;
			// check primary tool ID
			if (versionMap.get(primaryToolID) != null) {
				String defaultToolVersion = (String)versionMap.get(primaryToolID);
				vci = _checkLibVersion(primaryToolID, defaultToolVersion);
				if (vci != null)
					return vci;
			}
			// check default tool ID if it's not primary
			if (!DEFAULT_TOOL_ID.equals(primaryToolID)) {
				if (versionMap.get(DEFAULT_TOOL_ID) != null) {
					String primaryToolVersion = (String)versionMap.get(DEFAULT_TOOL_ID);
					vci = _checkLibVersion(DEFAULT_TOOL_ID, primaryToolVersion);
					if (vci != null)
						return vci;
				}
			}
		}
		return null;
	}

	private static VersionCheckInfo _checkLibVersion(String toolID, String toolVersion) {
		// get the class that stores all the version info for this tool ID
		EPFVersions versions = getVersions(toolID);
		if (versions != null) {
			EPFVersion minCurrVersion = versions.getMinToolVersionForCurrentLibraryVersion();
			int result = minCurrVersion.compareToolVersionTo(new Version(toolVersion));
			String currentMinToolVersion = minCurrVersion.getToolVersion().toString();
			return new VersionCheckInfo(toolID, toolVersion, currentMinToolVersion, - result);
		}
		return null;
	}

	public static String getPrimaryToolID() {
		return primaryToolID;
	}

	/**
	 * Sets the toolID that the check* methods check first. 
	 * If the primary tool ID is not found in the file or 
	 * has no Versions info, then "epf" tool ID is checked
	 * @param primaryToolID
	 */
	public static void setPrimaryToolID(String primaryToolID) {
		VersionUtil.primaryToolID = primaryToolID;
	}

}
