// Copyright (c) 2012 DotNetAnywhere
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.

#if !LOCALTEST

namespace System {

	public sealed class Version : ICloneable, IComparable, IComparable<Version>, IEquatable<Version> {
		int _Major, _Minor, _Build, _Revision;

		private const int UNDEFINED = -1;

		private void CheckedSet(int defined, int major, int minor, int build, int revision) {
			// defined should be 2, 3 or 4

			if (major < 0) {
				throw new ArgumentOutOfRangeException("major");
			}
			this._Major = major;

			if (minor < 0) {
				throw new ArgumentOutOfRangeException("minor");
			}
			this._Minor = minor;

			if (defined == 2) {
				this._Build = UNDEFINED;
				this._Revision = UNDEFINED;
				return;
			}

			if (build < 0) {
				throw new ArgumentOutOfRangeException("build");
			}
			this._Build = build;

			if (defined == 3) {
				this._Revision = UNDEFINED;
				return;
			}

			if (revision < 0) {
				throw new ArgumentOutOfRangeException("revision");
			}
			this._Revision = revision;
		}

		public Version() {
			CheckedSet(2, 0, 0, -1, -1);
		}

		public Version(string version) {
			int n;
			string[] vals;
			int major = -1, minor = -1, build = -1, revision = -1;

			if (version == null) {
				throw new ArgumentNullException("version");
			}

			vals = version.Split('.');
			n = vals.Length;

			if (n < 2 || n > 4) {
				throw new ArgumentException("There must be 2, 3 or 4 components in the version string.");
			}

			if (n > 0) {
				major = int.Parse(vals[0]);
			}
			if (n > 1) {
				minor = int.Parse(vals[1]);
			}
			if (n > 2) {
				build = int.Parse(vals[2]);
			}
			if (n > 3) {
				revision = int.Parse(vals[3]);
			}

			CheckedSet(n, major, minor, build, revision);
		}

		public Version(int major, int minor) {
			CheckedSet(2, major, minor, 0, 0);
		}

		public Version(int major, int minor, int build) {
			CheckedSet(3, major, minor, build, 0);
		}

		public Version(int major, int minor, int build, int revision) {
			CheckedSet(4, major, minor, build, revision);
		}

		public int Build {
			get {
				return _Build;
			}
		}

		public int Major {
			get {
				return _Major;
			}
		}

		public int Minor {
			get {
				return _Minor;
			}
		}

		public int Revision {
			get {
				return _Revision;
			}
		}

		public short MajorRevision {
			get {
				return (short)(_Revision >> 16);
			}
		}

		public short MinorRevision {
			get {
				return (short)_Revision;
			}
		}

		public object Clone() {
			if (_Build == -1) {
				return new Version(_Major, _Minor);
			} else if (_Revision == -1) {
				return new Version(_Major, _Minor, _Build);
			} else {
				return new Version(_Major, _Minor, _Build, _Revision);
			}
		}

		public int CompareTo(object version) {
			if (version == null) {
				return 1;
			}
			if (!(version is Version)) {
				throw new ArgumentException("Argument to Version.CompareTo must be a Version.");
			}
			return this.CompareTo((Version)version);
		}

		public override bool Equals(object obj) {
			return this.Equals(obj as Version);
		}

		public int CompareTo(Version v) {
			if (v == null) {
				return 1;
			}
			if (this._Major > v._Major) {
				return 1;
			} else if (this._Major < v._Major) {
				return -1;
			}
			if (this._Minor > v._Minor) {
				return 1;
			} else if (this._Minor < v._Minor) {
				return -1;
			}
			if (this._Build > v._Build) {
				return 1;
			} else if (this._Build < v._Build) {
				return -1;
			}
			if (this._Revision > v._Revision) {
				return 1;
			} else if (this._Revision < v._Revision) {
				return -1;
			}
			return 0;
		}

		public bool Equals(Version x) {
			return ((x != null) &&
				(x._Major == _Major) &&
				(x._Minor == _Minor) &&
				(x._Build == _Build) &&
				(x._Revision == _Revision));
		}

		public override int GetHashCode() {
			return (_Revision << 24) | (_Build << 16) | (_Minor << 8) | _Major;
		}

		// <summary>
		//   Returns a stringified representation of the version, format:
		//   major.minor[.build[.revision]]
		// </summary>
		public override string ToString() {
			string mm = _Major.ToString() + "." + _Minor.ToString();

			if (_Build != UNDEFINED) {
				mm = mm + "." + _Build.ToString();
			}
			if (_Revision != UNDEFINED) {
				mm = mm + "." + _Revision.ToString();
			}

			return mm;
		}

		// <summary>
		//    LAME: This API is lame, since there is no way of knowing
		//    how many fields a Version object has, it is unfair to throw
		//    an ArgumentException, but this is what the spec claims.
		//
		//    ie, Version a = new Version (1, 2);  a.ToString (3) should
		//    throw the expcetion.
		// </summary>
		public string ToString(int fields) {
			if (fields == 0) {
				return String.Empty;
			}
			if (fields == 1) {
				return _Major.ToString();
			}
			if (fields == 2) {
				return _Major.ToString() + "." + _Minor.ToString();
			}
			if (fields == 3) {
				if (_Build == UNDEFINED) {
					throw new ArgumentException
						("fields is larger than the number of components defined in this instance.");
				}
				return _Major.ToString() + "." + _Minor.ToString() + "." + _Build.ToString();
			}
			if (fields == 4) {
				if (_Build == UNDEFINED || _Revision == UNDEFINED) {
					throw new ArgumentException
						("fields is larger than the number of components defined in this instance.");
				}
				return _Major.ToString() + "." + _Minor.ToString() + "." + _Build.ToString() + "." + _Revision.ToString();
			}
			throw new ArgumentException("Invalid fields parameter: " + fields.ToString());
		}

		public static bool operator ==(Version v1, Version v2) {
			return Equals(v1, v2);
		}

		public static bool operator !=(Version v1, Version v2) {
			return !Equals(v1, v2);
		}

		public static bool operator >(Version v1, Version v2) {
			return v1.CompareTo(v2) > 0;
		}

		public static bool operator >=(Version v1, Version v2) {
			return v1.CompareTo(v2) >= 0;
		}

		public static bool operator <(Version v1, Version v2) {
			return v1.CompareTo(v2) < 0;
		}

		public static bool operator <=(Version v1, Version v2) {
			return v1.CompareTo(v2) <= 0;
		}

		// a very gentle way to construct a Version object which takes 
		// the first four numbers in a string as the version
		internal static Version CreateFromString(string info) {
			int major = 0;
			int minor = 0;
			int build = 0;
			int revision = 0;
			int state = 1;
			int number = UNDEFINED; // string may not begin with a digit

			if (info == null) {
				return new Version(0, 0, 0, 0);
			}
			for (int i = 0; i < info.Length; i++) {
				char c = info[i];
				if (char.IsDigit(c)) {
					if (number < 0) {
						number = (c - '0');
					} else {
						number = (number * 10) + (c - '0');
					}
				} else if (number >= 0) {
					// assign
					switch (state) {
						case 1:
							major = number;
							break;
						case 2:
							minor = number;
							break;
						case 3:
							build = number;
							break;
						case 4:
							revision = number;
							break;
					}
					number = -1;
					state++;
				}
				// ignore end of string
				if (state == 5)
					break;
			}

			// Last number
			if (number >= 0) {
				switch (state) {
					case 1:
						major = number;
						break;
					case 2:
						minor = number;
						break;
					case 3:
						build = number;
						break;
					case 4:
						revision = number;
						break;
				}
			}
			return new Version(major, minor, build, revision);
		}
	}
}

#endif
