using System;
using System.Collections;
using System.Collections.Generic;
using System.Globalization;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Web;

using miew.Enumerable;

namespace miew.String
{
	using Array = System.Array;
	using String = System.String;

	[DebuggerDisplay("{ToString(),nq}")]
	public sealed class ImmutableStringArray : IList<String>, IEquatable<ImmutableStringArray>
	{
		readonly String[] arr;
		readonly int hc = 0;
		readonly IEqualityComparer<String> comparer;

		public ImmutableStringArray(IEnumerable<String> rgs, IEqualityComparer<String> comparer)
		{
			this.arr = rgs as String[] ?? rgs.ToArray();
			this.comparer = comparer ?? StringComparer.Ordinal;

			hc = arr.Length;
			for (int i = 0; i < arr.Length; i++)
				hc = hc ^ (i + arr[i].GetHashCode());
		}

		public String this[int index]
		{
			get { return arr[index]; }
			set { throw new InvalidOperationException(); }
		}

		public int IndexOf(String item) { return Array.IndexOf<String>(arr, item); }
		public bool Contains(String item) { return Array.IndexOf<String>(arr, item) != -1; }
		public void CopyTo(String[] array, int arrayIndex) { Array.Copy(arr, array, arr.Length); }
		public int Count { get { return arr.Length; } }
		public bool IsReadOnly { get { return true; } }

		public IEnumerator<String> GetEnumerator() { return arr.AsEnumerable<String>().GetEnumerator(); }
		IEnumerator IEnumerable.GetEnumerator() { return arr.GetEnumerator(); }
		public void Insert(int index, string item) { throw new InvalidOperationException(); }
		public void RemoveAt(int index) { throw new InvalidOperationException(); }
		public void Add(String item) { throw new InvalidOperationException(); }
		public void Clear() { throw new InvalidOperationException(); }
		public bool Remove(String item) { throw new InvalidOperationException(); }

		public bool Equals(ImmutableStringArray other)
		{
			return hc == other.hc && arr.SequenceEqual(other.arr, comparer);
		}

		public override bool Equals(Object obj)
		{
			ImmutableStringArray o = obj as ImmutableStringArray;
			return o != null && hc == o.hc && arr.SequenceEqual(o.arr, comparer);
		}

		public override int GetHashCode() { return hc; }

		public override String ToString()
		{
			return String.Join(" ", arr.Select(s => "[" + s + "]"));
		}
	};


	public static class Extensions
	{
		public static String NewString(this IEnumerable<Char> iech)
		{
			return new String(iech as Char[] ?? iech.ToArray());
		}

		public static String Quotes(this String s)
		{
			return "\"" + s + "\"";
		}

		public static String RightEllipses(this String s, int c)
		{
			if (s.Length < c)
				return s;
			return s.Remove(c - 3) + "...";
		}
		public static String LeftEllipses(this String s, int c)
		{
			if (s.Length < c)
				return s;
			return "..." + s.Substring(3);
		}

		static String[] nl_sep = { Environment.NewLine };
		public static String Indent(this String s, int c)
		{
			String ind = new String(' ', c);
			if (!s.Contains(Environment.NewLine))
				return ind + s;
			return s.Split(nl_sep, StringSplitOptions.RemoveEmptyEntries).Select(x => ind + x).StringJoin(Environment.NewLine);
		}

#if false
		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		/// <summary>
		///
		/// </summary>
		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		public static String StringJoin(this IEnumerable<String> ie)
		{
			return String.Join(String.Empty, ie.ToArray());
		}

		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		/// <summary>
		///
		/// </summary>
		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		public static String StringJoin(this IEnumerable<String> ie, String sep)
		{
			return String.Join(sep, ie.ToArray());
		}

	public static Dictionary<String, int> Tally(this IEnumerable<String> ies)
	{
		Dictionary<String, int> d = new Dictionary<String, int>();
		foreach (String s in ies)
			if (d.ContainsKey(s))
				d[s]++;
			else
				d.Add(s, 1);
		return d;
	}

#endif

		public static String Intern(this String s)
		{
			return String.Intern(s);
		}

		public static IEnumerable<String> Lines(this String s)
		{
			return miew.IO.Extensions.Lines(new StringReader(s));
		}

		public static String RemoveMatchedParentheses(this String s, out bool f_all_matched)
		{
			f_all_matched = true;
			int nest = 0, i_start = 0;
			Char prev = default(Char);
			for (int i = 0; i < s.Length; i++)
			{
				Char ch = s[i];
				if (ch == '(' && prev != '\\')
				{
					if (nest == 0)
						i_start = i;
					nest++;
				}
				if (ch == ')' && prev != '\\')
				{
					nest--;
					if (nest < 0)
						break;
					if (nest == 0)
					{
						s = s.Remove(i, 1).Remove(i_start, 1);
						i = i_start;
					}
				}
				prev = ch;
			}
			f_all_matched = nest == 0;
			return s;
		}

		public static String ExtractParenthesized(this String s)
		{
			int j, k;
			if ((j = s.IndexOf('(')) >= 0)
				if ((k = s.IndexOf(')', j)) > 0)
					s = s.Substring(j + 1, k - j - 1);
			return s;
		}
		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		/// <summary>
		/// 
		/// </summary>
		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		public static String RemoveParenthesized(this String s)
		{
			int j, k = 0;
			while ((j = s.IndexOf('(', k)) >= 0)
			{
				if ((k = s.IndexOf(')', j)) == -1)
					break;
				s = s.Remove(j, k + 1 - j);
				k = j;
			}
			return s;
		}

		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		/// <summary>
		/// 
		/// </summary>
		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		public static String RemoveBracketed(this String s)
		{
			int j, k = 0;
			while ((j = s.IndexOf('[', k)) >= 0)
			{
				if ((k = s.IndexOf(']', j)) == -1)
					break;
				s = s.Remove(j, k + 1 - j);
				k = j;
			}
			return s;
		}


		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		/// <summary>
		/// 
		/// </summary>
		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		public static String RemoveAngleTagged(this String s)
		{
			int j, k = 0;
			while ((j = s.IndexOf('<', k)) >= 0)
			{
				if ((k = s.IndexOf('>', j)) == -1)
					break;
				s = s.Remove(j, k + 1 - j);
				k = j;
			}
			return s;
		}

		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		/// <summary>
		/// 
		/// </summary>
		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		public static String ExtractAngleTagged(this String s)
		{
			int j, k;
			return (j = s.IndexOf('<')) >= 0 && (k = s.IndexOf('>', j)) > 0 ? s.Substring(j + 1, k - j - 1) : String.Empty;
		}


		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		/// <summary>
		/// 
		/// </summary>
		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		public static unsafe int CopyToNativeW(this String s, Char* pbuf, int cwch_max)
		{
			int c = s.Length;
			if (c + 1 > cwch_max)
				return 0;
			fixed (Char* p = s)
				pbuf[Encoding.Unicode.GetBytes(p, c, (byte*)pbuf, cwch_max * sizeof(Char)) >> 1] = '\0';
			return c + 1;
		}

		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		/// <summary>
		/// '&apos;' was introduced as a standard entity in XML, and thus is also standard in XHTML. For maximum compatibility, 
		/// author &#39; instead.
		/// </summary>
		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		public static String XhtmlEncode(this String s)
		{
			return HttpUtility.HtmlEncode(s).Replace("\'", "&#39;");
		}

		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		/// <summary>
		/// '&apos;' was introduced as a standard entity in XML, and thus is also standard in XHTML. For maximum 
		/// compatibility, author &#39; instead.
		/// </summary>
		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		public static String XhtmlDecode(this String s)
		{
			return HttpUtility.HtmlDecode(s).Replace("&apos;", "'");
		}


		/// <summary>
		/// Probably not for use with LISP
		/// </summary>
		public static int QuoteInsulatedIndexOf(this String s, Char ch_find)
		{
			Stack<Char> stk = new Stack<Char>();
			for (int i = 0; i < s.Length; i++)
			{
				Char ch = s[i];
				if ((ch == '\'' || ch == '\"') && (i < 1 || s[i - 1] != '\\' || i < 2 || s[i - 2] != '\\'))
				{
					if (stk.Count > 0 && ch == stk.Peek())
						stk.Pop();
					else
						stk.Push(ch);
				}
				else if (stk.Count == 0 && ch == ch_find)
					return i;
			}
			return -1;
		}

		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		/// <summary>
		/// 
		/// </summary>
		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		public static IEnumerable<String> QuoteInsulatedSplit(this String s, Char ch_sep)
		{
			int cb, i, i_last = 0;
			Stack<Char> stk = new Stack<Char>();
			for (i = 0; i < s.Length; i++)
			{
				Char ch = s[i];
				if (ch == '\'' || ch == '\"')
				{
					if (stk.Count > 0 && ch == stk.Peek())
						stk.Pop();
					else
						stk.Push(ch);
				}
				else if (stk.Count == 0 && ch == ch_sep)
				{
					if ((cb = i - i_last) > 0)
						yield return s.Substring(i_last, cb);
					i_last = i + 1;
				}
			}
			if ((cb = i - i_last) > 0)
				yield return s.Substring(i_last, cb);

			if (stk.Count != 0)
				throw new Exception();
		}

#if false
		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		/// <summary>
		/// 
		/// </summary>
		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		public static IEnumerable<String> QuoteInsulatedSplit(this String s, params Char[] rgch)
		{
			int cb, i, i_last = 0;
			Stack<Char> stk = new Stack<Char>();
			for (i = 0; i < s.Length; i++)
			{
				Char ch = s[i];
				if (ch == '\'' || ch == '\"')
				{
					if (stk.Count > 0 && ch == stk.Peek())
						stk.Pop();
					else
						stk.Push(ch);
				}
				else if (stk.Count == 0 && Array.IndexOf<Char>(rgch, ch) != -1)
				{
					if ((cb = i - i_last) > 0)
						yield return s.Substring(i_last, cb);
					i_last = i + 1;
				}
			}
			if ((cb = i - i_last) > 0)
				yield return s.Substring(i_last, cb);
#if (DEBUG)
			if (stk.Count != 0)
				throw new Exception();
#endif
		}
#endif


		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		/// <summary>
		/// 
		/// </summary>
		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		public static IEnumerable<String> InsulatedSplit(this String s, Char ins, params Char[] split_chars)
		{
			bool f_enable = true;
			int cb, i, i_last = 0;
			for (i = 0; i < s.Length; i++)
			{
				if (s[i] == ins)
					f_enable = !f_enable;
				else if (f_enable && Array.IndexOf<Char>(split_chars, s[i]) != -1)
				{
					if ((cb = i - i_last) > 0)
						yield return s.Substring(i_last, cb);
					i_last = i + 1;
				}
			}
			if ((cb = i - i_last) > 0)
				yield return s.Substring(i_last, cb);
		}

		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		/// <summary>
		/// 
		/// </summary>
		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		public static IEnumerable<String> InsulatedSplitWithEscape(this String s, Char ins, params Char[] split_chars)
		{
			bool f_enable = true;
			int cb, i, i_last = 0;
			for (i = 0; i < s.Length; i++)
			{
				if (s[i] == ins && (i == 0 || s[i - 1] != '\\'))
					f_enable = !f_enable;
				else if (f_enable && Array.IndexOf<Char>(split_chars, s[i]) != -1)
				{
					if ((cb = i - i_last) > 0)
						yield return s.Substring(i_last, cb);
					i_last = i + 1;
				}
			}
			if ((cb = i - i_last) > 0)
				yield return s.Substring(i_last, cb);
		}

#if false
		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		/// <summary>
		/// 
		/// </summary>
		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		public static IEnumerable<String> InsulatedSplit(this String s, Char[] ins, params Char[] split_chars)
		{
			Char wait_for = default(Char);
			int cb, i, i_last = 0;
			for (i = 0; i < s.Length; i++)
			{
				Char ch = s[i];
				int v = Array.IndexOf<Char>(ins, ch);
				if (v != -1)
				{
					if (ch == wait_for)
						wait_for = default(Char);
					else
						wait_for = ch;
				}
				else if (wait_for == default(Char) && Array.IndexOf<Char>(split_chars, ch) != -1)
				{
					if ((cb = i - i_last) > 0)
						yield return s.Substring(i_last, cb);
					i_last = i + 1;
				}
			}
			if ((cb = i - i_last) > 0)
				yield return s.Substring(i_last, cb);
		}
#endif


		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		/// <summary>
		/// 
		/// </summary>
		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		public static String Left(this String s, int cch)
		{
			if (s == null || s.Length == 0)
				return String.Empty;
			return (s.Length <= cch) ? s : s.Substring(0, cch);
		}

		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		/// <summary>
		/// 
		/// </summary>
		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		public static String TrimEndOrPadRight(this String s, int cch)
		{
			if (s == null)
				return cch == 0 ? String.Empty : new String(' ', cch);
			int c = s.Length;
			if (c == cch)
				return s;
			if (c == 0)
				return new String(' ', cch);
			if (c < cch)
				return s.PadRight(cch);
			return s.Substring(0, cch);
		}

		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		/// <summary>
		/// 
		/// </summary>
		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		public static String TrimEndOrPadLeft(this String s, int cch)
		{
			if (s == null)
				return cch == 0 ? String.Empty : new String(' ', cch);
			int c = s.Length;
			if (c == cch)
				return s;
			if (c == 0)
				return new String(' ', cch);
			if (c < cch)
				return s.PadLeft(cch);
			return s.Remove(cch);
		}

		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		/// <summary>
		/// 
		/// </summary>
		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		public static String Right(this String s, int cch)
		{
			return (s.Length <= cch) ? s : s.Substring(s.Length - cch);
		}

		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		/// <summary>
		/// 
		/// </summary>
		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		public static String PadCenter(this String s, int width, char pad_char)
		{
			if (s == null || width <= s.Length)
				return s;
			return s.PadLeft(s.Length + (width - s.Length) / 2, pad_char).PadRight(width, pad_char);
		}

		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		/// <summary>
		/// 
		/// </summary>
		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		public static String SubstringOrLess(this String s, int idx, int cch)
		{
			if (String.IsNullOrEmpty(s))
				return String.Empty;
			int c = s.Length;
			if (idx >= c)
				return String.Empty;
			if (idx + cch > c)
				cch = c - idx;
			return s.Substring(idx, cch);
		}

		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		/// <summary>
		/// It's important to not spuriously create new strings
		/// </summary>
		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		public static bool SubstringStartsWith(this String s, int i, String s_compare)
		{
			if (s_compare.Length > s.Length - i)
				return false;
			for (int j = 0; j < s_compare.Length; j++)
				if (s_compare[j] != s[i++])
					return false;
			return true;
		}

		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		/// <summary>
		/// It's important to not spuriously create new strings
		/// </summary>
		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		public static bool ToLowerSubstringStartsWith(this String s, int i, String s_compare)
		{
			if (s_compare.Length > s.Length - i)
				return false;
			for (int j = 0; j < s_compare.Length; j++)
				if (s_compare[j] != Char.ToLower(s[i++]))
					return false;
			return true;
		}

		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		/// <summary>
		/// 
		/// </summary>
		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		public static bool HasNonSpaceChars(this String s)
		{
			foreach (Char ch in s)
				if (ch != ' ')
					return true;
			return false;
		}

		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		/// <summary>
		/// 
		/// </summary>
		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		public static bool IsAllWhitespace(this String s)
		{
			int c = s.Length;
			for (int i = 0; i < c; i++)
				if (!Char.IsWhiteSpace(s[i]))
					return false;
			return true;
		}

		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		/// <summary>
		/// 
		/// </summary>
		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		public static String RemoveSpaces(this String s)
		{
			return new String(s.Where(ch => ch != ' ').ToArray());
		}

		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		/// <summary>
		/// 
		/// </summary>
		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		public static String WhitespaceToSpace(this String s)
		{
			return new String(s.Select(e => Char.IsWhiteSpace(e) ? ' ' : e).ToArray());
		}

		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		/// <summary>
		/// 
		/// </summary>
		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		public static String CondenseSpaces(this String s_in)
		{
			if (s_in.Length < 2)
				return s_in;

			int j = 0;
			while ((j = s_in.IndexOf(' ', j)) != -1)
			{
				int k = ++j;
				while (k < s_in.Length && s_in[k] == ' ')
					k++;
				int c = k - j;
				if (c > 0)
					s_in = s_in.Remove(j, c);
			}
			return s_in;
		}

		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		/// <summary>
		/// 
		/// </summary>
		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		public static String SmartQuotes(this String s)
		{
			int i = 0;
			return new String(s.Select(ch => ch == '\"' ? ((i++ & 0x01) == 0 ? '“' : '”') : ch).ToArray());
		}
		public static String SmartQuotes(this String s, bool f_span)
		{
			int i = 0;
			Char[] rgch = s.ToCharArray();
			StringBuilder sb = new StringBuilder();
			foreach (Char ch in rgch)
			{
				if (ch == '\"')
				{
					if ((i++ & 0x01) == 0)
						sb.Append("<span style='font-family:cambria;'>“</span>");
					else
						sb.Append("<span style='font-family:cambria;'>”</span>");
				}
				else
					sb.Append(ch);
			}
			return sb.ToString();
		}

		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		/// <summary>
		/// 
		/// </summary>
		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		public static bool IsAllDigits(this String s)
		{
			int i;
			for (i = 0; i < s.Length; i++)
				if (!miew.Character.Extensions.IsDigit(s[i]))
					return false;
			return (i > 0);
		}


		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		/// <summary>
		/// 
		/// </summary>
		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		public static String RemoveZWSP(this String s)
		{
			return new String(s.Where(e => e != '\x200b').ToArray());
		}
	}

	namespace Thai
	{
		using String = System.String;
		using miew.Character.Thai;

		public static class Extensions
		{
			static Char[] digits = "๐๑๒๓๔๕๖๗๘๙".ToCharArray();
			public static String ToThaiDigits(this String s)
			{
				return new String(s.Select(ch => '0' <= ch && ch <= '9' ? digits[ch - '0'] : ch).ToArray());
			}

			///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
			/// <summary>
			/// 
			/// </summary>
			///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
			public static bool IsTIS620(this String s)
			{
				for (int i = 0; i < s.Length; i++)
					if (s[i] < 161)
						return false;
				return true;
			}


			///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
			/// <summary>
			/// 
			/// </summary>
			///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
			static readonly Encoding enc_874 = Encoding.GetEncoding(874);
			public static String ToThin874(this String s)
			{
				return Encoding.GetEncoding(1252).GetString(enc_874.GetBytes(s));
			}

			///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
			/// <summary>
			/// 
			/// </summary>
			///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
			public static unsafe int EncodeToNative874(this String s, byte* pbuf, int cch_max)
			{
				int c = s.Length;
				if (c + 1 > cch_max)
					return 0;
				fixed (Char* p = s)
					pbuf[enc_874.GetBytes(p, c, (byte*)pbuf, cch_max)] = 0;
				return c + 1;
			}

			///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
			/// <summary>
			/// 
			/// </summary>
			///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
			static readonly HashSet<Char> ok_874_chars =
				new HashSet<Char>(enc_874.GetString(System.Linq.Enumerable.Range(0, 255).Select(e => (byte)e).ToArray()));

			static String EscapeNonTis620(this String s)
			{
				String[] rgs = s.Select(e =>
				{
					if (ok_874_chars.Contains(e))
						return e.ToString();
					else
						return String.Format("&#{0};", (int)e);
				}).ToArray();
				return String.Join(String.Empty, rgs);
			}

			///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
			/// <summary>
			/// 
			/// </summary>
			///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
			public static Char FirstThaiCons(this String s)
			{
				foreach (Char c in s.ToCharArray())
					if (c.IsThaiCons())
						return c;
				return default(Char);
			}


			///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
			/// <summary>
			/// 
			/// </summary>
			///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
			public static String NormalizeYamok(this String s)
			{
				int j = 0;
				while ((j = s.IndexOf(" ๆ", j)) >= 0)
					s = s.Remove(j, 1);
				return s;
			}

			///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
			/// <summary>
			/// 
			/// </summary>
			///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
			public static bool ContainsThai(this String s)
			{
				return s.Any(ch => ch.IsThai());
			}



			///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
			/// <summary>
			/// 
			/// </summary>
			///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
			public static String StripDotAndPhinthu(this String s_in)
			{
				return new String(s_in.Where(e => e != 'ฺ' && e != '•').ToArray());
			}

			///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
			/// <summary>
			/// 
			/// </summary>
			///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
			static readonly Char[] DotPhinthu = { '•', 'ฺ' };
			public static bool ContainsDotOrPhinthu(this String s_in)
			{
				return s_in.IndexOfAny(DotPhinthu) != -1;
			}

			///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
			/// <summary>
			/// 
			/// </summary>
			///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
			public static unsafe String RemoveDiacritics(this String s_in)
			{
				String norm = s_in.Normalize(NormalizationForm.FormD);
				fixed (Char* pin_s = norm)
				{
					Char* p_end = pin_s + norm.Length;
					Char* p = pin_s;
					while (p < p_end)
					{
						if (!(*p).IsThai() && CharUnicodeInfo.GetUnicodeCategory(*p) == UnicodeCategory.NonSpacingMark)
							goto yes_convert;
						p++;
					}
					return s_in;
				yes_convert:
					StringBuilder sb = new StringBuilder(norm.Substring(0, (int)(p - pin_s)), norm.Length);
					p++;	// skip the one detected above
					while (p < p_end)
					{
						if ((*p).IsThai() || CharUnicodeInfo.GetUnicodeCategory(*p) != UnicodeCategory.NonSpacingMark)
							sb.Append(*p);
						p++;
					}
					return sb.ToString();
				}
			}

			///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
			/// <summary>
			/// 
			/// </summary>
			///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
			public static String Reinterpret(this String s_in, bool f_bullet)
			{
				return new String(s_in.Select(e => e.Reinterpret(f_bullet)).ToArray());
			}
		};
	};

	namespace Builder
	{

		public static class Extensions
		{
			///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
			/// <summary>
			/// 
			/// </summary>
			///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
			public static StringBuilder AppendFormatLine(this StringBuilder sb, String format, params Object[] args)
			{
				return sb.AppendLine(String.Format(format, args));
			}
		};

#if false

		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		/// <summary>
		/// 
		/// </summary>
		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		public static unsafe void Append(this StringBuilder sb, Char* p, int cch)
		{
#if true
		// this tests much faster than what follows
		sb.Append(new String(p, 0, cch));
#else
		// seems to be no way to blt the internal contents
		if (cch == 0 || p == null)
			return;
		int idx = sb.Length;
		sb.Length += cch;
		Char* p_end = p + cch;
		while (p < p_end)
			sb[idx++] = *p++;
#endif
		}

		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		/// <summary>
		/// 
		/// </summary>
		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		public static int IndexOf(this StringBuilder sb, String s)
		{
			return IndexOf(sb, s, 0);
		}

		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		/// <summary>
		/// 
		/// </summary>
		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		public static int IndexOf(this StringBuilder sb, String s, int startIndex)
		{
			if (s == null)
				s = String.Empty;

			for (int i = startIndex; i < sb.Length; i++)
			{
				int j;
				for (j = 0; j < s.Length && i + j < sb.Length && sb[i + j] == s[j]; j++)
					;
				if (j == s.Length)
					return i;
			}
			return -1;
		}
#endif
	}

	namespace Thin
	{
		using miew.Array;
		using String = System.String;

		public static class Extensions
		{
			///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
			/// <summary>
			/// Convert byte array to its thin string 
			/// </summary>
			///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
			public static unsafe String ToThin(this Byte[] rgb)
			{
				fixed (Byte* pb = rgb)
					return Marshal.PtrToStringAnsi(new IntPtr(pb), rgb.Length);
			}

			///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
			/// <summary>
			/// Split the array of bytes into portions delimited by multibyte separator 'sep'
			/// </summary>
			///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
			public static IEnumerable<Byte[]> Split(this Byte[] rgb, Byte[] sep)
			{
				int cb, i, i_prev = 0;
				for (i = 0; i < rgb.Length; )
				{
					if (rgb.ValueCompare<Byte>(i, sep))
					{
						if ((cb = i - i_prev) != 0)
						{
							Byte[] ret = new Byte[cb];
							Buffer.BlockCopy(rgb, i_prev, ret, 0, cb);
							yield return ret;
						}
						i += sep.Length;
						i_prev = i;
					}
					else
						i++;
				}
				if ((cb = i - i_prev) > 0)
				{
					Byte[] ret = new Byte[cb];
					Buffer.BlockCopy(rgb, i_prev, ret, 0, cb);
					yield return ret;
				}
			}

			///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
			/// <summary>
			/// Split the array of bytes into portions delimited by thin string 'thin'
			/// </summary>
			///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
			public static IEnumerable<Byte[]> Split(this Byte[] src, String thin)
			{
				foreach (Byte[] ret in src.Split(thin.ToByteArr()))
					yield return ret;
			}

			///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
			/// <summary>
			/// 
			/// </summary>
			///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
			public static int IndexOf(this Byte[] a, Byte[] b)
			{
				if (b.Length > a.Length)
					return -1;
				int term = a.Length - b.Length + 1;
				for (int i = 0; i < term; i++)
				{
					if (a.ValueCompare<Byte>(i, b))
						return i;
				}
				return -1;
			}

			///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
			/// <summary>
			/// Convert a thin string to its byte array
			/// </summary>
			///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
			public static unsafe Byte[] ToByteArr(this String thin)
			{
				Byte[] rgb = new Byte[thin.Length];
				fixed (Char* _p_src = thin)
				fixed (Byte* _p_dst = rgb)
				{
					Char* p = _p_src;
					Char* p_end = p + thin.Length;
					Byte* p_dst = _p_dst;
					while (p < p_end)
						*p_dst++ = (Byte)(*p++);
				}
				return rgb;
			}

			///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
			/// <summary>
			/// Append the thin string's bytes to the list of bytes
			/// </summary>
			///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
			public static void Append(this List<Byte> l, String thin)
			{
				l.AddRange(thin.ToByteArr());
			}

		};

	}
}