/*******************************************************************************
 * Copyright (c) 2000, 2015 IBM Corporation and others.
 *
 * 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
 *     Sergey Prigogin (Google) - use parameterized types (bug 442021)
 *******************************************************************************/
package org.eclipse.core.runtime;

import java.util.StringTokenizer;
import java.util.Vector;
import org.eclipse.core.internal.runtime.CommonMessages;
import org.eclipse.core.internal.runtime.IRuntimeConstants;
import org.eclipse.osgi.util.NLS;
import org.osgi.framework.Version;

/**
 * <p>
 * Version identifier for a plug-in. In its string representation, 
 * it consists of up to 4 tokens separated by a decimal point.
 * The first 3 tokens are positive integer numbers, the last token
 * is an uninterpreted string (no whitespace characters allowed).
 * For example, the following are valid version identifiers 
 * (as strings):
 * </p>
 * <ul>
 *   <li><code>0.0.0</code></li>
 *   <li><code>1.0.127564</code></li>
 *   <li><code>3.7.2.build-127J</code></li>
 *   <li><code>1.9</code> (interpreted as <code>1.9.0</code>)</li>
 *   <li><code>3</code> (interpreted as <code>3.0.0</code>)</li>
 * </ul>
 * <p>
 * The version identifier can be decomposed into a major, minor, 
 * service level component and qualifier components. A difference
 * in the major component is interpreted as an incompatible version
 * change. A difference in the minor (and not the major) component
 * is interpreted as a compatible version change. The service
 * level component is interpreted as a cumulative and compatible
 * service update of the minor version component. The qualifier is
 * not interpreted, other than in version comparisons. The 
 * qualifiers are compared using lexicographical string comparison.
 * </p>
 * <p>
 * Version identifiers can be matched as perfectly equal, equivalent,
 * compatible or greaterOrEqual.
 * </p><p>
 * This class can be used without OSGi running.
 * </p><p>
 * Clients may instantiate; not intended to be subclassed by clients.
 * </p>
 * @see java.lang.String#compareTo(java.lang.String)
 * @deprecated clients should use {@link org.osgi.framework.Version} instead
 */
@Deprecated
public final class PluginVersionIdentifier {

	private Version version;

	private static final String SEPARATOR = "."; //$NON-NLS-1$

	/**
	 * Creates a plug-in version identifier from its components.
	 * 
	 * @param major major component of the version identifier
	 * @param minor minor component of the version identifier
	 * @param service service update component of the version identifier
	 */
	public PluginVersionIdentifier(int major, int minor, int service) {
		this(major, minor, service, null);
	}

	/**
	 * Creates a plug-in version identifier from its components.
	 * 
	 * @param major major component of the version identifier
	 * @param minor minor component of the version identifier
	 * @param service service update component of the version identifier
	 * @param qualifier qualifier component of the version identifier. 
	 * Qualifier characters that are not a letter or a digit are replaced.
	 */
	public PluginVersionIdentifier(int major, int minor, int service, String qualifier) {
		// Do the test outside of the assert so that they 'Policy.bind' 
		// will not be evaluated each time (including cases when we would
		// have passed by the assert).

		if (major < 0)
			Assert.isTrue(false, NLS.bind(CommonMessages.parse_postiveMajor, major + SEPARATOR + minor + SEPARATOR + service + SEPARATOR + qualifier));
		if (minor < 0)
			Assert.isTrue(false, NLS.bind(CommonMessages.parse_postiveMinor, major + SEPARATOR + minor + SEPARATOR + service + SEPARATOR + qualifier));
		if (service < 0)
			Assert.isTrue(false, NLS.bind(CommonMessages.parse_postiveService, major + SEPARATOR + minor + SEPARATOR + service + SEPARATOR + qualifier));

		this.version = new Version(major, minor, service, qualifier);
	}

	/**
	 * Creates a plug-in version identifier from the given string.
	 * The string representation consists of up to 4 tokens 
	 * separated by decimal point.
	 * For example, the following are valid version identifiers 
	 * (as strings):
	 * <ul>
	 *   <li><code>0.0.0</code></li>
	 *   <li><code>1.0.127564</code></li>
	 *   <li><code>3.7.2.build-127J</code></li>
	 *   <li><code>1.9</code> (interpreted as <code>1.9.0</code>)</li>
	 *   <li><code>3</code> (interpreted as <code>3.0.0</code>)</li>
	 * </ul>
	 * 
	 * @param versionId string representation of the version identifier. 
	 * Qualifier characters that are not a letter or a digit are replaced.
	 */
	public PluginVersionIdentifier(String versionId) {
		Object[] parts = parseVersion(versionId);
		version = new Version(((Integer) parts[0]).intValue(), ((Integer) parts[1]).intValue(), ((Integer) parts[2]).intValue(), (String) parts[3]);
	}

	/**
	 * Validates the given string as a plug-in version identifier.
	 * 
	 * @param version the string to validate
	 * @return a status object with code <code>IStatus.OK</code> if
	 *		the given string is valid as a plug-in version identifier, otherwise a status
	 *		object indicating what is wrong with the string
	 * @since 2.0
	 */
	public static IStatus validateVersion(String version) {
		try {
			parseVersion(version);
		} catch (RuntimeException e) {
			return new Status(IStatus.ERROR, IRuntimeConstants.PI_RUNTIME, IStatus.ERROR, e.getMessage(), e);
		}
		return Status.OK_STATUS;
	}

	private static Object[] parseVersion(String versionId) {

		// Do the test outside of the assert so that they 'Policy.bind' 
		// will not be evaluated each time (including cases when we would
		// have passed by the assert).
		if (versionId == null)
			Assert.isNotNull(null, CommonMessages.parse_emptyPluginVersion);
		String s = versionId.trim();
		if (s.equals("")) //$NON-NLS-1$
			Assert.isTrue(false, CommonMessages.parse_emptyPluginVersion);
		if (s.startsWith(SEPARATOR))
			Assert.isTrue(false, NLS.bind(CommonMessages.parse_separatorStartVersion, s));
		if (s.endsWith(SEPARATOR))
			Assert.isTrue(false, NLS.bind(CommonMessages.parse_separatorEndVersion, s));
		if (s.contains(SEPARATOR + SEPARATOR))
			Assert.isTrue(false, NLS.bind(CommonMessages.parse_doubleSeparatorVersion, s));

		StringTokenizer st = new StringTokenizer(s, SEPARATOR);
		Vector<String> elements = new Vector<>(4);

		while (st.hasMoreTokens())
			elements.addElement(st.nextToken());

		int elementSize = elements.size();

		if (elementSize <= 0)
			Assert.isTrue(false, NLS.bind(CommonMessages.parse_oneElementPluginVersion, s));
		if (elementSize > 4)
			Assert.isTrue(false, NLS.bind(CommonMessages.parse_fourElementPluginVersion, s));

		int[] numbers = new int[3];
		try {
			numbers[0] = Integer.parseInt(elements.elementAt(0));
			if (numbers[0] < 0)
				Assert.isTrue(false, NLS.bind(CommonMessages.parse_postiveMajor, s));
		} catch (NumberFormatException nfe) {
			Assert.isTrue(false, NLS.bind(CommonMessages.parse_numericMajorComponent, s));
		}

		try {
			if (elementSize >= 2) {
				numbers[1] = Integer.parseInt(elements.elementAt(1));
				if (numbers[1] < 0)
					Assert.isTrue(false, NLS.bind(CommonMessages.parse_postiveMinor, s));
			} else
				numbers[1] = 0;
		} catch (NumberFormatException nfe) {
			Assert.isTrue(false, NLS.bind(CommonMessages.parse_numericMinorComponent, s));
		}

		try {
			if (elementSize >= 3) {
				numbers[2] = Integer.parseInt(elements.elementAt(2));
				if (numbers[2] < 0)
					Assert.isTrue(false, NLS.bind(CommonMessages.parse_postiveService, s));
			} else
				numbers[2] = 0;
		} catch (NumberFormatException nfe) {
			Assert.isTrue(false, NLS.bind(CommonMessages.parse_numericServiceComponent, s));
		}

		// "result" is a 4-element array with the major, minor, service, and qualifier
		Object[] result = new Object[4];
		result[0] = Integer.valueOf(numbers[0]);
		result[1] = Integer.valueOf(numbers[1]);
		result[2] = Integer.valueOf(numbers[2]);
		if (elementSize >= 4)
			result[3] = elements.elementAt(3);
		else
			result[3] = ""; //$NON-NLS-1$
		return result;
	}

	/**
	 * Compare version identifiers for equality. Identifiers are
	 * equal if all of their components are equal.
	 *
	 * @param object an object to compare
	 * @return whether or not the two objects are equal
	 */
	@Override
	public boolean equals(Object object) {
		if (!(object instanceof PluginVersionIdentifier))
			return false;
		PluginVersionIdentifier v = (PluginVersionIdentifier) object;
		return version.equals(v.version);
	}

	/**
	 * Returns a hash code value for the object. 
	 *
	 * @return an integer which is a hash code value for this object.
	 */
	@Override
	public int hashCode() {
		return version.hashCode();
	}

	/**
	 * Returns the major (incompatible) component of this 
	 * version identifier.
	 *
	 * @return the major version
	 */
	public int getMajorComponent() {
		return version.getMajor();
	}

	/**
	 * Returns the minor (compatible) component of this 
	 * version identifier.
	 *
	 * @return the minor version
	 */
	public int getMinorComponent() {
		return version.getMinor();
	}

	/**
	 * Returns the service level component of this 
	 * version identifier.
	 *
	 * @return the service level
	 */
	public int getServiceComponent() {
		return version.getMicro();
	}

	/**
	 * Returns the qualifier component of this 
	 * version identifier.
	 *
	 * @return the qualifier
	 */
	public String getQualifierComponent() {
		return version.getQualifier();
	}

	/**
	 * Compares two version identifiers to see if this one is
	 * greater than or equal to the argument.
	 * <p>
	 * A version identifier is considered to be greater than or equal
	 * if its major component is greater than the argument major 
	 * component, or the major components are equal and its minor component
	 * is greater than the argument minor component, or the
	 * major and minor components are equal and its service component is
	 * greater than the argument service component, or the major, minor and
	 * service components are equal and the qualifier component is
	 * greater than the argument qualifier component (using lexicographic
	 * string comparison), or all components are equal.
	 * </p>
	 *
	 * @param id the other version identifier
	 * @return <code>true</code> is this version identifier
	 *    is compatible with the given version identifier, and
	 *    <code>false</code> otherwise
	 * @since 2.0
	 */
	public boolean isGreaterOrEqualTo(PluginVersionIdentifier id) {
		if (id == null)
			return false;
		if (getMajorComponent() > id.getMajorComponent())
			return true;
		if ((getMajorComponent() == id.getMajorComponent()) && (getMinorComponent() > id.getMinorComponent()))
			return true;
		if ((getMajorComponent() == id.getMajorComponent()) && (getMinorComponent() == id.getMinorComponent()) && (getServiceComponent() > id.getServiceComponent()))
			return true;
		if ((getMajorComponent() == id.getMajorComponent()) && (getMinorComponent() == id.getMinorComponent()) && (getServiceComponent() == id.getServiceComponent()) && (getQualifierComponent().compareTo(id.getQualifierComponent()) >= 0))
			return true;
		return false;
	}

	/**
	 * Compares two version identifiers for compatibility.
	 * <p>
	 * A version identifier is considered to be compatible if its major 
	 * component equals to the argument major component, and its minor component
	 * is greater than or equal to the argument minor component.
	 * If the minor components are equal, than the service level of the
	 * version identifier must be greater than or equal to the service level
	 * of the argument identifier. If the service levels are equal, the two 
	 * version identifiers are considered to be equivalent if this qualifier is 
	 * greater or equal to the qualifier of the argument (using lexicographic
	 * string comparison).
	 * </p>
	 *
	 * @param id the other version identifier
	 * @return <code>true</code> is this version identifier
	 *    is compatible with the given version identifier, and
	 *    <code>false</code> otherwise
	 */
	public boolean isCompatibleWith(PluginVersionIdentifier id) {
		if (id == null)
			return false;
		if (getMajorComponent() != id.getMajorComponent())
			return false;
		if (getMinorComponent() > id.getMinorComponent())
			return true;
		if (getMinorComponent() < id.getMinorComponent())
			return false;
		if (getServiceComponent() > id.getServiceComponent())
			return true;
		if (getServiceComponent() < id.getServiceComponent())
			return false;
		if (getQualifierComponent().compareTo(id.getQualifierComponent()) >= 0)
			return true;
		return false;
	}

	/**
	 * Compares two version identifiers for equivalency.
	 * <p>
	 * Two version identifiers are considered to be equivalent if their major 
	 * and minor component equal and are at least at the same service level 
	 * as the argument. If the service levels are equal, the two version
	 * identifiers are considered to be equivalent if this qualifier is 
	 * greater or equal to the qualifier of the argument (using lexicographic
	 * string comparison).
	 * 
	 * </p>
	 *
	 * @param id the other version identifier
	 * @return <code>true</code> is this version identifier
	 *    is equivalent to the given version identifier, and
	 *    <code>false</code> otherwise
	 */
	public boolean isEquivalentTo(PluginVersionIdentifier id) {
		if (id == null)
			return false;
		if (getMajorComponent() != id.getMajorComponent())
			return false;
		if (getMinorComponent() != id.getMinorComponent())
			return false;
		if (getServiceComponent() > id.getServiceComponent())
			return true;
		if (getServiceComponent() < id.getServiceComponent())
			return false;
		if (getQualifierComponent().compareTo(id.getQualifierComponent()) >= 0)
			return true;
		return false;
	}

	/**
	 * Compares two version identifiers for perfect equality.
	 * <p>
	 * Two version identifiers are considered to be perfectly equal if their
	 * major, minor, service and qualifier components are equal
	 * </p>
	 *
	 * @param id the other version identifier
	 * @return <code>true</code> is this version identifier
	 *    is perfectly equal to the given version identifier, and
	 *    <code>false</code> otherwise
	 * @since 2.0
	 */
	public boolean isPerfect(PluginVersionIdentifier id) {
		if (id == null)
			return false;
		if ((getMajorComponent() != id.getMajorComponent()) || (getMinorComponent() != id.getMinorComponent()) || (getServiceComponent() != id.getServiceComponent()) || (!getQualifierComponent().equals(id.getQualifierComponent())))
			return false;
		return true;
	}

	/**
	 * Compares two version identifiers for order using multi-decimal
	 * comparison. 
	 *
	 * @param id the other version identifier
	 * @return <code>true</code> is this version identifier
	 *    is greater than the given version identifier, and
	 *    <code>false</code> otherwise
	 */
	public boolean isGreaterThan(PluginVersionIdentifier id) {

		if (id == null) {
			if (getMajorComponent() == 0 && getMinorComponent() == 0 && getServiceComponent() == 0 && getQualifierComponent().equals("")) //$NON-NLS-1$
				return false;
			return true;
		}

		if (getMajorComponent() > id.getMajorComponent())
			return true;
		if (getMajorComponent() < id.getMajorComponent())
			return false;
		if (getMinorComponent() > id.getMinorComponent())
			return true;
		if (getMinorComponent() < id.getMinorComponent())
			return false;
		if (getServiceComponent() > id.getServiceComponent())
			return true;
		if (getServiceComponent() < id.getServiceComponent())
			return false;
		if (getQualifierComponent().compareTo(id.getQualifierComponent()) > 0)
			return true;
		return false;

	}

	/**
	 * Returns the string representation of this version identifier. 
	 * The result satisfies
	 * <code>vi.equals(new PluginVersionIdentifier(vi.toString()))</code>.
	 *
	 * @return the string representation of this plug-in version identifier
	 */
	@Override
	public String toString() {
		return version.toString();
	}

}
