//
// System.Globalization.CompareInfo
//
// Authors:
//   Rodrigo Moya (rodrigo@ximian.com)
//   Dick Porter (dick@ximian.com)
//
// (C) Ximian, Inc. 2002
//

//
// Copyright (C) 2004 Novell, Inc (http://www.novell.com)
//
// 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.
//

using System.Reflection;
using System.Runtime.Serialization;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using Mono.Globalization.Unicode;

namespace System.Globalization
{
#if NET_2_0
	[ComVisible (true)]
#endif
	[Serializable]
	public class CompareInfo : IDeserializationCallback
	{
		static readonly bool useManagedCollation =
			Environment.internalGetEnvironmentVariable ("MONO_DISABLE_MANAGED_COLLATION")
			!= "yes" && MSCompatUnicodeTable.IsReady;

		internal static bool UseManagedCollation {
			get { return useManagedCollation; }
		}

		// Keep in synch with MonoCompareInfo in the runtime. 
		private int culture;
		[NonSerialized]
		private string icu_name;
		[NonSerialized]
		private IntPtr ICU_collator;
		private int win32LCID;	// Unused, but MS.NET serializes this
#if NET_2_0
		private string m_name; // Unused, but MS.NET serializes this
#endif

		[NonSerialized]
		SimpleCollator collator;
		
		/* Hide the .ctor() */
		CompareInfo() {}
		
		[MethodImplAttribute (MethodImplOptions.InternalCall)]
		private extern void construct_compareinfo (string locale);
		
		internal CompareInfo (CultureInfo ci)
		{
			this.culture = ci.LCID;
			if (UseManagedCollation) 
				collator = new SimpleCollator (ci);
			else {
				this.icu_name = ci.IcuName;
				this.construct_compareinfo (icu_name);
			}
		}
		
		[MethodImplAttribute (MethodImplOptions.InternalCall)]
		private extern void free_internal_collator ();

		~CompareInfo ()
		{
			free_internal_collator ();
		}

		[MethodImplAttribute (MethodImplOptions.InternalCall)]
		private extern int internal_compare (string str1, int offset1,
						     int length1, string str2,
						     int offset2, int length2,
						     CompareOptions options);

		private int internal_compare_managed (string str1, int offset1,
						int length1, string str2,
						int offset2, int length2,
						CompareOptions options)
		{
			return collator.Compare (str1, offset1, length1,
				str2, offset2, length2, options);
		}

		private int internal_compare_switch (string str1, int offset1,
						int length1, string str2,
						int offset2, int length2,
						CompareOptions options)
		{
			return UseManagedCollation ?
				internal_compare_managed (str1, offset1, length1,
				str2, offset2, length2, options) :
				internal_compare (str1, offset1, length1,
				str2, offset2, length2, options);
		}

		public virtual int Compare (string string1, string string2)
		{
			if (string1 == null) {
				if (string2 == null)
					return 0;
				return -1;
			}
			if (string2 == null)
				return 1;
				
			/* Short cut... */
			if(string1.Length == 0 && string2.Length == 0)
				return(0);

			return(internal_compare_switch (string1, 0, string1.Length,
						 string2, 0, string2.Length,
						 CompareOptions.None));
		}

		public virtual int Compare (string string1, string string2,
					    CompareOptions options)
		{
			if (string1 == null) {
				if (string2 == null)
					return 0;
				return -1;
			}
			if (string2 == null)
				return 1;

			/* Short cut... */
			if(string1.Length == 0 && string2.Length == 0)
				return(0);

			return(internal_compare_switch (string1, 0, string1.Length,
						 string2, 0, string2.Length,
						 options));
		}

		public virtual int Compare (string string1, int offset1,
					    string string2, int offset2)
		{
			if (string1 == null) {
				if (string2 == null)
					return 0;
				return -1;
			}
			if (string2 == null)
				return 1;

			/* Not in the spec, but ms does these short
			 * cuts before checking the offsets (breaking
			 * the offset >= string length specified check
			 * in the process...)
			 */
			if ((string1.Length == 0 || offset1 == string1.Length) &&
				(string2.Length == 0 || offset2 == string2.Length))
				return(0);

			if(offset1 < 0 || offset2 < 0) {
				throw new ArgumentOutOfRangeException ("Offsets must not be less than zero");
			}
			
			if(offset1 > string1.Length) {
				throw new ArgumentOutOfRangeException ("Offset1 is greater than or equal to the length of string1");
			}
			
			if(offset2 > string2.Length) {
				throw new ArgumentOutOfRangeException ("Offset2 is greater than or equal to the length of string2");
			}
			
			return(internal_compare_switch (string1, offset1,
						 string1.Length-offset1,
						 string2, offset2,
						 string2.Length-offset2,
						 CompareOptions.None));
		}

		public virtual int Compare (string string1, int offset1,
					    string string2, int offset2,
					    CompareOptions options)
		{
			if (string1 == null) {
				if (string2 == null)
					return 0;
				return -1;
			}
			if (string2 == null)
				return 1;

			/* Not in the spec, but ms does these short
			 * cuts before checking the offsets (breaking
			 * the offset >= string length specified check
			 * in the process...)
			 */
			if((string1.Length == 0 || offset1 == string1.Length) &&
				(string2.Length == 0 || offset2 == string2.Length))
				return(0);

			if(offset1 < 0 || offset2 < 0) {
				throw new ArgumentOutOfRangeException ("Offsets must not be less than zero");
			}
			
			if(offset1 > string1.Length) {
				throw new ArgumentOutOfRangeException ("Offset1 is greater than or equal to the length of string1");
			}
			
			if(offset2 > string2.Length) {
				throw new ArgumentOutOfRangeException ("Offset2 is greater than or equal to the length of string2");
			}
			
			return(internal_compare_switch (string1, offset1,
						 string1.Length-offset1,
						 string2, offset2,
						 string2.Length-offset1,
						 options));
		}

		public virtual int Compare (string string1, int offset1,
					    int length1, string string2,
					    int offset2, int length2)
		{
			if (string1 == null) {
				if (string2 == null)
					return 0;
				return -1;
			}
			if (string2 == null)
				return 1;

			/* Not in the spec, but ms does these short
			 * cuts before checking the offsets (breaking
			 * the offset >= string length specified check
			 * in the process...)
			 */
			if((string1.Length == 0 ||
				offset1 == string1.Length ||
				length1 == 0) &&
				(string2.Length == 0 ||
				offset2 == string2.Length ||
				length2 == 0))
				return(0);

			if(offset1 < 0 || length1 < 0 ||
			   offset2 < 0 || length2 < 0) {
				throw new ArgumentOutOfRangeException ("Offsets and lengths must not be less than zero");
			}
			
			if(offset1 > string1.Length) {
				throw new ArgumentOutOfRangeException ("Offset1 is greater than or equal to the length of string1");
			}
			
			if(offset2 > string2.Length) {
				throw new ArgumentOutOfRangeException ("Offset2 is greater than or equal to the length of string2");
			}
			
			if(length1 > string1.Length-offset1) {
				throw new ArgumentOutOfRangeException ("Length1 is greater than the number of characters from offset1 to the end of string1");
			}
			
			if(length2 > string2.Length-offset2) {
				throw new ArgumentOutOfRangeException ("Length2 is greater than the number of characters from offset2 to the end of string2");
			}
			
			return(internal_compare_switch (string1, offset1, length1,
						 string2, offset2, length2,
						 CompareOptions.None));
		}

		public virtual int Compare (string string1, int offset1,
					    int length1, string string2,
					    int offset2, int length2,
					    CompareOptions options)
		{
			if (string1 == null) {
				if (string2 == null)
					return 0;
				return -1;
			}
			if (string2 == null)
				return 1;

			/* Not in the spec, but ms does these short
			 * cuts before checking the offsets (breaking
			 * the offset >= string length specified check
			 * in the process...)
			 */
			if((string1.Length == 0 ||
				offset1 == string1.Length ||
				length1 == 0) &&
				(string2.Length == 0 ||
				offset2 == string2.Length ||
				length2 == 0))
					return(0);

			if(offset1 < 0 || length1 < 0 ||
			   offset2 < 0 || length2 < 0) {
				throw new ArgumentOutOfRangeException ("Offsets and lengths must not be less than zero");
			}
			
			if(offset1 > string1.Length) {
				throw new ArgumentOutOfRangeException ("Offset1 is greater than or equal to the length of string1");
			}
			
			if(offset2 > string2.Length) {
				throw new ArgumentOutOfRangeException ("Offset2 is greater than or equal to the length of string2");
			}
			
			if(length1 > string1.Length-offset1) {
				throw new ArgumentOutOfRangeException ("Length1 is greater than the number of characters from offset1 to the end of string1");
			}
			
			if(length2 > string2.Length-offset2) {
				throw new ArgumentOutOfRangeException ("Length2 is greater than the number of characters from offset2 to the end of string2");
			}
			
			return(internal_compare_switch (string1, offset1, length1,
						 string2, offset2, length2,
						 options));
		}

		public override bool Equals(object value)
		{
			CompareInfo other=value as CompareInfo;
			if(other==null) {
				return(false);
			}
			
			return(other.culture==culture);
		}

		public static CompareInfo GetCompareInfo(int culture)
		{
			return(new CultureInfo (culture).CompareInfo);
		}

		public static CompareInfo GetCompareInfo(string name)
		{
			if(name == null) {
				throw new ArgumentNullException("name");
			}
			return(new CultureInfo (name).CompareInfo);
		}

		public static CompareInfo GetCompareInfo(int culture,
							 Assembly assembly)
		{
			/* The assembly parameter is supposedly there
			 * to allow some sort of compare algorithm
			 * versioning.
			 */
			if(assembly == null) {
				throw new ArgumentNullException("assembly");
			}
			if(assembly!=typeof (Object).Module.Assembly) {
				throw new ArgumentException ("Assembly is an invalid type");
			}
			return(GetCompareInfo (culture));
		}

		public static CompareInfo GetCompareInfo(string name,
							 Assembly assembly)
		{
			/* The assembly parameter is supposedly there
			 * to allow some sort of compare algorithm
			 * versioning.
			 */
			if(name == null) {
				throw new ArgumentNullException("name");
			}
			if(assembly == null) {
				throw new ArgumentNullException("assembly");
			}
			if(assembly!=typeof (Object).Module.Assembly) {
				throw new ArgumentException ("Assembly is an invalid type");
			}
			return(GetCompareInfo (name));
		}

		public override int GetHashCode()
		{
			return(LCID);
		}

		[MethodImplAttribute (MethodImplOptions.InternalCall)]
		private extern void assign_sortkey (object key, string source,
						    CompareOptions options);
		
		public virtual SortKey GetSortKey(string source)
		{
			return(GetSortKey (source, CompareOptions.None));
		}

		public virtual SortKey GetSortKey(string source,
						  CompareOptions options)
		{
#if NET_2_0
			switch (options) {
			case CompareOptions.Ordinal:
			case CompareOptions.OrdinalIgnoreCase:
				throw new ArgumentException ("Now allowed CompareOptions.", "options");
			}
#endif
			if (UseManagedCollation)
				return collator.GetSortKey (source, options);
			SortKey key=new SortKey (culture, source, options);

			/* Need to do the icall here instead of in the
			 * SortKey constructor, as we need access to
			 * this instance's collator.
			 */
			assign_sortkey (key, source, options);
			
			return(key);
		}

		public virtual int IndexOf (string source, char value)
		{
			return(IndexOf (source, value, 0, source.Length,
					CompareOptions.None));
		}

		public virtual int IndexOf (string source, string value)
		{
			return(IndexOf (source, value, 0, source.Length,
					CompareOptions.None));
		}

		public virtual int IndexOf (string source, char value,
					    CompareOptions options)
		{
			return(IndexOf (source, value, 0, source.Length,
					options));
		}

		public virtual int IndexOf (string source, char value,
					    int startIndex)
		{
			return(IndexOf (source, value, startIndex,
					source.Length - startIndex,
					CompareOptions.None));
		}
		
		public virtual int IndexOf (string source, string value,
					    CompareOptions options)
		{
			return(IndexOf (source, value, 0, source.Length,
					options));
		}

		public virtual int IndexOf (string source, string value,
					    int startIndex)
		{
			return(IndexOf (source, value, startIndex,
					source.Length - startIndex,
					CompareOptions.None));
		}

		public virtual int IndexOf (string source, char value,
					    int startIndex,
					    CompareOptions options)
		{
			return(IndexOf (source, value, startIndex,
					source.Length - startIndex, options));
		}

		public virtual int IndexOf (string source, char value,
					    int startIndex, int count)
		{
			return IndexOf (source, value, startIndex, count,
					CompareOptions.None);
		}

		public virtual int IndexOf (string source, string value,
					    int startIndex,
					    CompareOptions options)
		{
			return(IndexOf (source, value, startIndex,
					source.Length - startIndex, options));
		}

		public virtual int IndexOf (string source, string value,
					    int startIndex, int count)
		{
			return(IndexOf (source, value, startIndex, count,
					CompareOptions.None));
		}

		[MethodImplAttribute (MethodImplOptions.InternalCall)]
		private extern int internal_index (string source, int sindex,
						   int count, char value,
						   CompareOptions options,
						   bool first);

		private int internal_index_managed (string s, int sindex,
			int count, char c, CompareOptions opt,
			bool first)
		{
			return first ?
				collator.IndexOf (s, c, sindex, count, opt) :
				collator.LastIndexOf (s, c, sindex, count, opt);
		}

		private int internal_index_switch (string s, int sindex,
			int count, char c, CompareOptions opt,
			bool first)
		{
			// - forward IndexOf() icall is much faster than
			//   manged version, so always use icall. However,
			//   it does not work for OrdinalIgnoreCase, so
			//   do not avoid managed collator for that option.
			return UseManagedCollation && ! (first && opt == CompareOptions.Ordinal) ?
				internal_index_managed (s, sindex, count, c, opt, first) :
				internal_index (s, sindex, count, c, opt, first);
		}

		public virtual int IndexOf (string source, char value,
					    int startIndex, int count,
					    CompareOptions options)
		{
			if(source==null) {
				throw new ArgumentNullException ("source");
			}
			if(startIndex<0) {
				throw new ArgumentOutOfRangeException ("startIndex");
			}
			if(count<0 || (source.Length - startIndex) < count) {
				throw new ArgumentOutOfRangeException ("count");
			}
			if((options & CompareOptions.StringSort)!=0) {
				throw new ArgumentException ("StringSort is not a valid CompareOption for this method");
			}
			
			if(count==0) {
				return(-1);
			}

			if((options & CompareOptions.Ordinal)!=0) {
				for(int pos=startIndex;
				    pos < startIndex + count;
				    pos++) {
					if(source[pos]==value) {
						return(pos);
					}
				}
				return(-1);
			} else {
				return (internal_index_switch (source, startIndex,
							count, value, options,
							true));
			}
		}

		[MethodImplAttribute (MethodImplOptions.InternalCall)]
		private extern int internal_index (string source, int sindex,
						   int count, string value,
						   CompareOptions options,
						   bool first);

		private int internal_index_managed (string s1, int sindex,
			int count, string s2, CompareOptions opt,
			bool first)
		{
			return first ?
				collator.IndexOf (s1, s2, sindex, count, opt) :
				collator.LastIndexOf (s1, s2, sindex, count, opt);
		}

		private int internal_index_switch (string s1, int sindex,
			int count, string s2, CompareOptions opt,
			bool first)
		{
			// - forward IndexOf() icall is much faster than
			//   manged version, so always use icall. However,
			//   it does not work for OrdinalIgnoreCase, so
			//   do not avoid managed collator for that option.
			return UseManagedCollation && ! (first && opt == CompareOptions.Ordinal) ?
				internal_index_managed (s1, sindex, count, s2, opt, first) :
				internal_index (s1, sindex, count, s2, opt, first);
		}

		public virtual int IndexOf (string source, string value,
					    int startIndex, int count,
					    CompareOptions options)
		{
			if(source==null) {
				throw new ArgumentNullException ("source");
			}
			if(value==null) {
				throw new ArgumentNullException ("value");
			}
			if(startIndex<0) {
				throw new ArgumentOutOfRangeException ("startIndex");
			}
			if(count<0 || (source.Length - startIndex) < count) {
				throw new ArgumentOutOfRangeException ("count");
			}
			if(count==0) {
				return(-1);
			}

			return (internal_index_switch (source, startIndex, count,
						value, options, true));
		}

		public virtual bool IsPrefix(string source, string prefix)
		{
			return(IsPrefix (source, prefix, CompareOptions.None));
		}

		public virtual bool IsPrefix(string source, string prefix,
					     CompareOptions options)
		{
			if(source == null) {
				throw new ArgumentNullException("source");
			}
			if(prefix == null) {
				throw new ArgumentNullException("prefix");
			}

			if (UseManagedCollation)
				return collator.IsPrefix (source, prefix, options);

			if(source.Length < prefix.Length) {
				return(false);
			} else {
				return(Compare (source, 0, prefix.Length,
						prefix, 0, prefix.Length,
						options)==0);
			}
		}

		public virtual bool IsSuffix(string source, string suffix)
		{
			return(IsSuffix (source, suffix, CompareOptions.None));
		}

		public virtual bool IsSuffix(string source, string suffix,
					     CompareOptions options)
		{
			if(source == null) {
				throw new ArgumentNullException("source");
			}
			if(suffix == null) {
				throw new ArgumentNullException("suffix");
			}

			if (UseManagedCollation)
				return collator.IsSuffix (source, suffix, options);

			if(source.Length < suffix.Length) {
				return(false);
			} else {
				return(Compare (source,
						source.Length - suffix.Length,
						suffix.Length, suffix, 0,
						suffix.Length, options)==0);
			}
		}

		public virtual int LastIndexOf(string source, char value)
		{
			return(LastIndexOf (source, value, source.Length - 1,
					    source.Length, CompareOptions.None));
		}

		public virtual int LastIndexOf(string source, string value)
		{
			return(LastIndexOf (source, value, source.Length - 1,
					    source.Length, CompareOptions.None));
		}

		public virtual int LastIndexOf(string source, char value,
					       CompareOptions options)
		{
			return(LastIndexOf (source, value, source.Length - 1,
					    source.Length, options));
		}

		public virtual int LastIndexOf(string source, char value,
					       int startIndex)
		{
			return(LastIndexOf (source, value, startIndex,
					    startIndex + 1,
					    CompareOptions.None));
		}

		public virtual int LastIndexOf(string source, string value,
					       CompareOptions options)
		{
			return(LastIndexOf (source, value, source.Length - 1,
					    source.Length, options));
		}

		public virtual int LastIndexOf(string source, string value,
					       int startIndex)
		{
			return(LastIndexOf (source, value, startIndex,
					    startIndex + 1,
					    CompareOptions.None));
		}

		public virtual int LastIndexOf(string source, char value,
					       int startIndex,
					       CompareOptions options)
		{
			return(LastIndexOf (source, value, startIndex,
					    startIndex + 1,
					    options));
		}

		public virtual int LastIndexOf(string source, char value,
					       int startIndex, int count)
		{
			return(LastIndexOf (source, value, startIndex, count,
					    CompareOptions.None));
		}

		public virtual int LastIndexOf(string source, string value,
					       int startIndex,
					       CompareOptions options)
		{
			return(LastIndexOf (source, value, startIndex,
					    startIndex + 1,
					    options));
		}

		public virtual int LastIndexOf(string source, string value,
					       int startIndex, int count)
		{
			return(LastIndexOf (source, value, startIndex, count,
					    CompareOptions.None));
		}

		public virtual int LastIndexOf(string source, char value,
					       int startIndex, int count,
					       CompareOptions options)
		{
			if(source == null) {
				throw new ArgumentNullException("source");
			}
			if(startIndex < 0) {
				throw new ArgumentOutOfRangeException ("startIndex");
			}
			if(count < 0 || (startIndex - count) < -1) {
				throw new ArgumentOutOfRangeException("count");
			}
			if((options & CompareOptions.StringSort)!=0) {
				throw new ArgumentException ("StringSort is not a valid CompareOption for this method");
			}
			
			if(count==0) {
				return(-1);
			}

			if((options & CompareOptions.Ordinal)!=0) {
				for(int pos=startIndex;
				    pos > startIndex - count;
				    pos--) {
					if(source[pos]==value) {
						return(pos);
					}
				}
				return(-1);
			} else {
				return (internal_index_switch (source, startIndex,
							count, value, options,
							false));
			}
		}

		public virtual int LastIndexOf(string source, string value,
					       int startIndex, int count,
					       CompareOptions options)
		{
			if(source == null) {
				throw new ArgumentNullException("source");
			}
			if(value == null) {
				throw new ArgumentNullException("value");
			}
			if(startIndex < 0) {
				throw new ArgumentOutOfRangeException ("startIndex");
			}
			if(count < 0 || (startIndex - count) < -1) {
				throw new ArgumentOutOfRangeException("count");
			}
			if(count == 0) {
				return(-1);
			}

			int valuelen=value.Length;
			if(valuelen==0) {
				return(0);
			}

			return(internal_index_switch (source, startIndex, count,
					       value, options, false));
		}

#if NET_2_0
		[ComVisible (false)]
		public static bool IsSortable (char c)
		{
			return MSCompatUnicodeTable.IsSortable (c);
		}

		[ComVisible (false)]
		public static bool IsSortable (string s)
		{
			return MSCompatUnicodeTable.IsSortable (s);
		}
#endif

		public override string ToString()
		{
			return("CompareInfo - "+culture);
		}

		void IDeserializationCallback.OnDeserialization(object sender)
		{
			if (UseManagedCollation) {
				collator = new SimpleCollator (new CultureInfo (culture));
			} else {
				/* This will build the ICU collator, and store
				 * the pointer in ICU_collator
				 */
				try {
					this.construct_compareinfo (icu_name);
				} catch {
					ICU_collator=IntPtr.Zero;
				}
			}
		}

		/* LAMESPEC: not mentioned in the spec, but corcompare
		 * shows it.  Some documentation about what it does
		 * would be nice.
		 */
		public int LCID
		{
			get {
				return(culture);
			}
		}

#if NET_2_0
		[ComVisible (false)]
		public virtual string Name {
			get { return icu_name; }
		}
#endif
	}
}
