// Backported to net35 from https://github.com/maxhauser/semver using System; using System.Globalization; using System.Runtime.Serialization; using System.Security.Permissions; using System.Text; using System.Text.RegularExpressions; namespace BepInEx.Core { internal static class IntExtensions { /// /// The number of digits in a non-negative number. Returns 1 for all /// negative numbers. That is ok because we are using it to calculate /// string length for a for numbers that /// aren't supposed to be negative, but when they are it is just a little /// slower. /// /// /// This approach is based on https://stackoverflow.com/a/51099524/268898 /// where the poster offers performance benchmarks showing this is the /// fastest way to get a number of digits. /// public static int Digits(this int n) { if (n < 10) return 1; if (n < 100) return 2; if (n < 1_000) return 3; if (n < 10_000) return 4; if (n < 100_000) return 5; if (n < 1_000_000) return 6; if (n < 10_000_000) return 7; if (n < 100_000_000) return 8; if (n < 1_000_000_000) return 9; return 10; } } [Serializable] public sealed class SemVersion : IComparable, IComparable, ISerializable { private static readonly Regex ParseEx = new Regex(@"^(?\d+)" + @"(?>\.(?\d+))?" + @"(?>\.(?\d+))?" + @"(?>\-(?
[0-9A-Za-z\-\.]+))?" +
					  @"(?>\+(?[0-9A-Za-z\-\.]+))?$",
				RegexOptions.CultureInvariant | RegexOptions.Compiled | RegexOptions.ExplicitCapture,
				TimeSpan.FromSeconds(0.5));

#pragma warning disable CA1801 // Parameter unused
		/// 
		/// Deserialize a .
		/// 
		/// The  parameter is null.
		private SemVersion(SerializationInfo info, StreamingContext context)
#pragma warning restore CA1801 // Parameter unused
		{
			if (info == null)
				throw new ArgumentNullException(nameof(info));
			var semVersion = Parse(info.GetString("SemVersion"));
			Major = semVersion.Major;
			Minor = semVersion.Minor;
			Patch = semVersion.Patch;
			Prerelease = semVersion.Prerelease;
			Build = semVersion.Build;
		}

		/// 
		/// Constructs a new instance of the  class.
		/// 
		/// The major version.
		/// The minor version.
		/// The patch version.
		/// The prerelease version (e.g. "alpha").
		/// The build metadata (e.g. "nightly.232").
		public SemVersion(int major, int minor = 0, int patch = 0, string prerelease = "", string build = "")
		{
			Major = major;
			Minor = minor;
			Patch = patch;

			Prerelease = prerelease ?? "";
			Build = build ?? "";
		}

		/// 
		/// Constructs a new instance of the  class from
		/// a .
		/// 
		/// The  that is used to initialize
		/// the Major, Minor, Patch and Build.
		/// A  with the same Major and Minor version.
		/// The Patch version will be the fourth part of the version number. The
		/// build meta data will contain the third part of the version number if
		/// it is greater than zero.
		public SemVersion(Version version)
		{
			if (version == null)
				throw new ArgumentNullException(nameof(version));

			Major = version.Major;
			Minor = version.Minor;

			if (version.Revision >= 0)
				Patch = version.Revision;

			Prerelease = "";

			Build = version.Build > 0 ? version.Build.ToString(CultureInfo.InvariantCulture) : "";
		}

		/// 
		/// Converts the string representation of a semantic version to its  equivalent.
		/// 
		/// The version string.
		/// If set to  minor and patch version are required,
		/// otherwise they are optional.
		/// The  object.
		/// The  is .
		/// The  has an invalid format.
		/// The  is missing Minor or Patch versions and  is .
		/// The Major, Minor, or Patch versions are larger than int.MaxValue.
		public static SemVersion Parse(string version, bool strict = false)
		{
			var match = ParseEx.Match(version);
			if (!match.Success)
				throw new ArgumentException($"Invalid version '{version}'.", nameof(version));

			var major = int.Parse(match.Groups["major"].Value, CultureInfo.InvariantCulture);

			var minorMatch = match.Groups["minor"];
			int minor = 0;
			if (minorMatch.Success)
				minor = int.Parse(minorMatch.Value, CultureInfo.InvariantCulture);
			else if (strict)
				throw new InvalidOperationException("Invalid version (no minor version given in strict mode)");

			var patchMatch = match.Groups["patch"];
			int patch = 0;
			if (patchMatch.Success)
				patch = int.Parse(patchMatch.Value, CultureInfo.InvariantCulture);
			else if (strict)
				throw new InvalidOperationException("Invalid version (no patch version given in strict mode)");

			var prerelease = match.Groups["pre"].Value;
			var build = match.Groups["build"].Value;

			return new SemVersion(major, minor, patch, prerelease, build);
		}

		/// 
		/// Converts the string representation of a semantic version to its 
		/// equivalent and returns a value that indicates whether the conversion succeeded.
		/// 
		/// The version string.
		/// When the method returns, contains a  instance equivalent
		/// to the version string passed in, if the version string was valid, or  if the
		/// version string was not valid.
		/// If set to  minor and patch version are required,
		/// otherwise they are optional.
		///  when a invalid version string is passed, otherwise .
		public static bool TryParse(string version, out SemVersion semver, bool strict = false)
		{
			semver = null;
			if (version is null)
				return false;

			var match = ParseEx.Match(version);
			if (!match.Success)
				return false;

			if (!int.TryParse(match.Groups["major"].Value, NumberStyles.Integer, CultureInfo.InvariantCulture, out var major))
				return false;

			var minorMatch = match.Groups["minor"];
			int minor = 0;
			if (minorMatch.Success)
			{
				if (!int.TryParse(minorMatch.Value, NumberStyles.Integer, CultureInfo.InvariantCulture, out minor))
					return false;
			}
			else if (strict)
				return false;

			var patchMatch = match.Groups["patch"];
			int patch = 0;
			if (patchMatch.Success)
			{
				if (!int.TryParse(patchMatch.Value, NumberStyles.Integer, CultureInfo.InvariantCulture, out patch))
					return false;
			}
			else if (strict)
				return false;

			var prerelease = match.Groups["pre"].Value;
			var build = match.Groups["build"].Value;

			semver = new SemVersion(major, minor, patch, prerelease, build);
			return true;
		}

		/// 
		/// Checks whether two semantic versions are equal.
		/// 
		/// The first version to compare.
		/// The second version to compare.
		///  if the two values are equal, otherwise .
		public static bool Equals(SemVersion versionA, SemVersion versionB)
		{
			if (ReferenceEquals(versionA, versionB))
				return true;
			if (versionA is null || versionB is null)
				return false;
			return versionA.Equals(versionB);
		}

		/// 
		/// Compares the specified versions.
		/// 
		/// The first version to compare.
		/// The second version to compare.
		/// A signed number indicating the relative values of  and .
		public static int Compare(SemVersion versionA, SemVersion versionB)
		{
			if (ReferenceEquals(versionA, versionB))
				return 0;
			if (versionA is null)
				return -1;
			if (versionB is null)
				return 1;
			return versionA.CompareTo(versionB);
		}

		/// 
		/// Make a copy of the current instance with changed properties.
		/// 
		/// The value to replace the major version or  to leave it unchanged.
		/// The value to replace the minor version or  to leave it unchanged.
		/// The value to replace the patch version or  to leave it unchanged.
		/// The value to replace the prerelease version or  to leave it unchanged.
		/// The value to replace the build metadata or  to leave it unchanged.
		/// The new version object.
		/// 
		/// The change method is intended to be called using named argument syntax, passing only
		/// those fields to be changed.
		/// 
		/// 
		/// To change only the patch version:
		/// version.Change(patch: 4)
		/// 
		public SemVersion Change(int? major = null, int? minor = null, int? patch = null,
			string prerelease = null, string build = null)
		{
			return new SemVersion(
				major ?? Major,
				minor ?? Minor,
				patch ?? Patch,
				prerelease ?? Prerelease,
				build ?? Build);
		}

		/// 
		/// Gets the major version.
		/// 
		/// 
		/// The major version.
		/// 
		public int Major { get; }

		/// 
		/// Gets the minor version.
		/// 
		/// 
		/// The minor version.
		/// 
		public int Minor { get; }

		/// 
		/// Gets the patch version.
		/// 
		/// 
		/// The patch version.
		/// 
		public int Patch { get; }

		/// 
		/// Gets the prerelease version.
		/// 
		/// 
		/// The prerelease version. Empty string if this is a release version.
		/// 
		public string Prerelease { get; }

		/// 
		/// Gets the build metadata.
		/// 
		/// 
		/// The build metadata. Empty string if there is no build metadata.
		/// 
		public string Build { get; }

		/// 
		/// Returns the  equivalent of this version.
		/// 
		/// 
		/// The  equivalent of this version.
		/// 
		public override string ToString()
		{
			// Assume all separators ("..-+"), at most 2 extra chars
			var estimatedLength = 4 + Major.Digits() + Minor.Digits() + Patch.Digits()
								  + Prerelease.Length + Build.Length;
			var version = new StringBuilder(estimatedLength);
			version.Append(Major);
			version.Append('.');
			version.Append(Minor);
			version.Append('.');
			version.Append(Patch);
			if (Prerelease.Length > 0)
			{
				version.Append('-');
				version.Append(Prerelease);
			}

			if (Build.Length > 0)
			{
				version.Append('+');
				version.Append(Build);
			}

			return version.ToString();
		}

		/// 
		/// Compares the current instance with another object of the same type and returns an integer that indicates
		/// whether the current instance precedes, follows, or occurs in the same position in the sort order as the
		/// other object.
		/// 
		/// An object to compare with this instance.
		/// 
		/// A value that indicates the relative order of the objects being compared.
		/// The return value has these meanings:
		///  Less than zero: This instance precedes  in the sort order.
		///  Zero: This instance occurs in the same position in the sort order as .
		///  Greater than zero: This instance follows  in the sort order.
		/// 
		/// The  is not a .
		public int CompareTo(object obj)
		{
			return CompareTo((SemVersion)obj);
		}

		/// 
		/// Compares the current instance with another object of the same type and returns an integer that indicates
		/// whether the current instance precedes, follows, or occurs in the same position in the sort order as the
		/// other object.
		/// 
		/// An object to compare with this instance.
		/// 
		/// A value that indicates the relative order of the objects being compared.
		/// The return value has these meanings:
		///  Less than zero: This instance precedes  in the sort order.
		///  Zero: This instance occurs in the same position in the sort order as .
		///  Greater than zero: This instance follows  in the sort order.
		/// 
		public int CompareTo(SemVersion other)
		{
			var r = CompareByPrecedence(other);
			if (r != 0)
				return r;

#pragma warning disable CA1062 // Validate arguments of public methods
			// If other is null, CompareByPrecedence() returns 1
			return CompareComponent(Build, other.Build);
#pragma warning restore CA1062 // Validate arguments of public methods
		}

		/// 
		/// Returns whether two semantic versions have the same precedence. Versions
		/// that differ only by build metadata have the same precedence.
		/// 
		/// The semantic version to compare to.
		///  if the version precedences are equal.
		public bool PrecedenceMatches(SemVersion other)
		{
			return CompareByPrecedence(other) == 0;
		}

		/// 
		/// Compares two semantic versions by precedence as defined in the SemVer spec. Versions
		/// that differ only by build metadata have the same precedence.
		/// 
		/// The semantic version.
		/// 
		/// A value that indicates the relative order of the objects being compared.
		/// The return value has these meanings:
		///  Less than zero: This instance precedes  in the sort order.
		///  Zero: This instance occurs in the same position in the sort order as .
		///  Greater than zero: This instance follows  in the sort order.
		/// 
		public int CompareByPrecedence(SemVersion other)
		{
			if (other is null)
				return 1;

			var r = Major.CompareTo(other.Major);
			if (r != 0)
				return r;

			r = Minor.CompareTo(other.Minor);
			if (r != 0)
				return r;

			r = Patch.CompareTo(other.Patch);
			if (r != 0)
				return r;

			return CompareComponent(Prerelease, other.Prerelease, true);
		}

		private static int CompareComponent(string a, string b, bool nonemptyIsLower = false)
		{
			var aEmpty = string.IsNullOrEmpty(a);
			var bEmpty = string.IsNullOrEmpty(b);
			if (aEmpty && bEmpty)
				return 0;

			if (aEmpty)
				return nonemptyIsLower ? 1 : -1;
			if (bEmpty)
				return nonemptyIsLower ? -1 : 1;

			var aComps = a.Split('.');
			var bComps = b.Split('.');

			var minLen = Math.Min(aComps.Length, bComps.Length);
			for (int i = 0; i < minLen; i++)
			{
				var ac = aComps[i];
				var bc = bComps[i];
				var aIsNum = int.TryParse(ac, out var aNum);
				var bIsNum = int.TryParse(bc, out var bNum);
				int r;
				if (aIsNum && bIsNum)
				{
					r = aNum.CompareTo(bNum);
					if (r != 0)
						return r;
				}
				else
				{
					if (aIsNum)
						return -1;
					if (bIsNum)
						return 1;
					r = string.CompareOrdinal(ac, bc);
					if (r != 0)
						return r;
				}
			}

			return aComps.Length.CompareTo(bComps.Length);
		}

		/// 
		/// Determines whether the specified  is equal to this instance.
		/// 
		/// The  to compare with this instance.
		/// 
		///    if the specified  is equal to this instance, otherwise .
		/// 
		/// The  is not a .
		public override bool Equals(object obj)
		{
			if (obj is null)
				return false;

			if (ReferenceEquals(this, obj))
				return true;

			var other = (SemVersion)obj;

			return Major == other.Major
				   && Minor == other.Minor
				   && Patch == other.Patch
				   && string.Equals(Prerelease, other.Prerelease, StringComparison.Ordinal)
				   && string.Equals(Build, other.Build, StringComparison.Ordinal);
		}

		/// 
		/// Returns a hash code for this instance.
		/// 
		/// 
		/// A hash code for this instance, suitable for use in hashing algorithms and data structures like a hash table.
		/// 
		public override int GetHashCode()
		{
			unchecked
			{
				// TODO verify this. Some versions start result = 17. Some use 37 instead of 31
				int result = Major.GetHashCode();
				result = result * 31 + Minor.GetHashCode();
				result = result * 31 + Patch.GetHashCode();
				result = result * 31 + Prerelease.GetHashCode();
				result = result * 31 + Build.GetHashCode();
				return result;
			}
		}

		/// 
		/// Populates a  with the data needed to serialize the target object.
		/// 
		/// The  to populate with data.
		/// The destination (see ) for this serialization.
		[SecurityPermission(SecurityAction.Demand, SerializationFormatter = true)]
		public void GetObjectData(SerializationInfo info, StreamingContext context)
		{
			if (info == null)
				throw new ArgumentNullException(nameof(info));
			info.AddValue("SemVersion", ToString());
		}

#pragma warning disable CA2225 // Operator overloads have named alternates
		/// 
		/// Implicit conversion from  to .
		/// 
		/// The semantic version.
		/// The  object.
		/// The  is .
		/// The version number has an invalid format.
		/// The Major, Minor, or Patch versions are larger than int.MaxValue.
		public static implicit operator SemVersion(string version)
#pragma warning restore CA2225 // Operator overloads have named alternates
		{
			return Parse(version);
		}

		/// 
		/// Compares two semantic versions for equality.
		/// 
		/// The left value.
		/// The right value.
		/// If left is equal to right , otherwise .
		public static bool operator ==(SemVersion left, SemVersion right)
		{
			return Equals(left, right);
		}

		/// 
		/// Compares two semantic versions for inequality.
		/// 
		/// The left value.
		/// The right value.
		/// If left is not equal to right , otherwise .
		public static bool operator !=(SemVersion left, SemVersion right)
		{
			return !Equals(left, right);
		}

		/// 
		/// Compares two semantic versions.
		/// 
		/// The left value.
		/// The right value.
		/// If left is greater than right , otherwise .
		public static bool operator >(SemVersion left, SemVersion right)
		{
			return Compare(left, right) > 0;
		}

		/// 
		/// Compares two semantic versions.
		/// 
		/// The left value.
		/// The right value.
		/// If left is greater than or equal to right , otherwise .
		public static bool operator >=(SemVersion left, SemVersion right)
		{
			return Equals(left, right) || Compare(left, right) > 0;
		}

		/// 
		/// Compares two semantic versions.
		/// 
		/// The left value.
		/// The right value.
		/// If left is less than right , otherwise .
		public static bool operator <(SemVersion left, SemVersion right)
		{
			return Compare(left, right) < 0;
		}

		/// 
		/// Compares two semantic versions.
		/// 
		/// The left value.
		/// The right value.
		/// If left is less than or equal to right , otherwise .
		public static bool operator <=(SemVersion left, SemVersion right)
		{
			return Equals(left, right) || Compare(left, right) < 0;
		}
	}
}