/*******************************************************************************
 * Copyright (c) 2009, 2017 Cloudsmith Inc. 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:
 *     Cloudsmith Inc. - initial API and implementation
 *******************************************************************************/
package org.eclipse.equinox.internal.p2.metadata;

import java.util.List;
import org.eclipse.equinox.p2.metadata.IVersionFormat;
import org.eclipse.equinox.p2.metadata.Version;
import org.eclipse.osgi.util.NLS;

/**
 * @Immutable
 * @noextend This class is not intended to be subclassed by clients.
 */
public class OSGiVersion extends BasicVersion {

	private static final long serialVersionUID = -4530178927569560877L;

	private static final boolean[] allowedOSGiChars;

	private final int major;

	private final int minor;

	private final int micro;

	private final Comparable<?> qualifier;

	static {
		allowedOSGiChars = new boolean[128];
		for (int c = '0'; c <= '9'; ++c)
			allowedOSGiChars[c] = true;
		for (int c = 'A'; c <= 'Z'; ++c)
			allowedOSGiChars[c] = true;
		for (int c = 'a'; c <= 'z'; ++c)
			allowedOSGiChars[c] = true;
		allowedOSGiChars['_'] = true;
		allowedOSGiChars['-'] = true;
	}

	public static boolean isValidOSGiQualifier(Comparable<?> e) {
		if (e == VersionVector.MAXS_VALUE)
			return true;

		if (!(e instanceof String))
			return false;

		String s = (String) e;
		int idx = s.length();
		boolean[] allowed = allowedOSGiChars;
		while (--idx >= 0) {
			int c = s.charAt(idx);
			if (c < '-' || c > 'z' || !allowed[c])
				return false;
		}
		return true;
	}

	static BasicVersion fromVector(List<Comparable<?>> vector) {
		int vtop = vector.size() - 1;
		Comparable<?> pad = vector.get(vtop);
		if (vtop != 4) {
			if (vtop == 0) {
				if (pad == null)
					return (BasicVersion) emptyVersion;
				if (pad == VersionVector.MAX_VALUE)
					return (BasicVersion) MAX_VERSION;
			}
			throw new IllegalArgumentException();
		}
		int major = ((Integer) vector.get(0)).intValue();
		int minor = ((Integer) vector.get(1)).intValue();
		int micro = ((Integer) vector.get(2)).intValue();
		Comparable<?> qualifier = vector.get(3);
		return (major == 0 && minor == 0 && micro == 0 && qualifier == VersionVector.MINS_VALUE) ? (BasicVersion) emptyVersion : new OSGiVersion(major, minor, micro, qualifier);
	}

	public OSGiVersion(int major, int minor, int micro, Comparable<? extends Object> qualifier) {
		this.major = major;
		this.minor = minor;
		this.micro = micro;
		if (!isValidOSGiQualifier(qualifier))
			throw new IllegalArgumentException(NLS.bind(Messages._0_is_not_a_valid_qualifier_in_osgi_1, "qualifier", this)); //$NON-NLS-1$
		//intern the qualifier string to avoid duplication
		if (qualifier instanceof String)
			qualifier = ((String) qualifier).intern();
		this.qualifier = qualifier;
	}

	@Override
	public int compareTo(Version v) {
		int result;
		if (!(v instanceof OSGiVersion)) {
			BasicVersion ov = (BasicVersion) v;
			result = VersionVector.compare(getVector(), null, ov.getVector(), ov.getPad());
		} else {
			OSGiVersion ov = (OSGiVersion) v;
			result = major - ov.major;
			if (result == 0) {
				result = minor - ov.minor;
				if (result == 0) {
					result = micro - ov.micro;
					if (result == 0)
						result = VersionVector.compareSegments(qualifier, ov.qualifier);
				}
			}
		}
		return result;
	}

	@Override
	public boolean equals(Object object) {
		if (object == this)
			return true;

		if (!(object instanceof OSGiVersion)) {
			if (object instanceof BasicVersion) {
				BasicVersion ov = (BasicVersion) object;
				return VersionVector.equals(getVector(), null, ov.getVector(), ov.getPad());
			}
			return false;
		}

		OSGiVersion other = (OSGiVersion) object;
		return micro == other.micro && minor == other.minor && major == other.major && qualifier.equals(other.qualifier);
	}

	@Override
	public IVersionFormat getFormat() {
		return VersionFormat.OSGI_FORMAT;
	}

	@Override
	public int getMajor() {
		return major;
	}

	@Override
	public int getMicro() {
		return micro;
	}

	@Override
	public int getMinor() {
		return minor;
	}

	@Override
	public String getOriginal() {
		return toString();
	}

	@Override
	public String getQualifier() {
		return qualifier == VersionVector.MAXS_VALUE ? IVersionFormat.DEFAULT_MAX_STRING_TRANSLATION : (String) qualifier;
	}

	@Override
	public int hashCode() {
		return (major << 24) + (minor << 16) + (micro << 8) + qualifier.hashCode();
	}

	@Override
	public boolean isOSGiCompatible() {
		return true;
	}

	@Override
	public void originalToString(StringBuffer sb, boolean rangeSafe) {
		toString(sb);
	}

	@Override
	public void rawToString(StringBuffer sb, boolean rangeSafe) {
		sb.append(major);
		sb.append('.');
		sb.append(minor);
		sb.append('.');
		sb.append(micro);
		sb.append('.');
		sb.append('\'');
		sb.append(qualifier);
		sb.append('\'');
	}

	@Override
	public void toString(StringBuffer sb) {
		sb.append(major);
		sb.append('.');
		sb.append(minor);
		sb.append('.');
		sb.append(micro);
		if (qualifier != VersionVector.MINS_VALUE) {
			sb.append('.');
			sb.append(getQualifier());
		}
	}

	@Override
	public Comparable<?>[] getVector() {
		return new Comparable[] {VersionParser.valueOf(major), VersionParser.valueOf(minor), VersionParser.valueOf(micro), qualifier};
	}

	@Override
	public Comparable<?> getPad() {
		return null;
	}

	@Override
	public Comparable<?> getSegment(int index) {
		switch (index) {
			case 0 :
				return VersionParser.valueOf(major);
			case 1 :
				return VersionParser.valueOf(minor);
			case 2 :
				return VersionParser.valueOf(micro);
			case 3 :
				return qualifier;
		}
		throw new ArrayIndexOutOfBoundsException(index); // Not in the imaginary vector array
	}

	@Override
	public int getSegmentCount() {
		return 4;
	}

	private Object readResolve() {
		OSGiVersion v = this;
		// Preserve the empty string singleton.
		if (qualifier.equals(VersionVector.MINS_VALUE))
			v = new OSGiVersion(major, minor, micro, VersionVector.MINS_VALUE);
		return v;
	}
}
