context: /*
   * This file is part of Schumix.
   * 
   * Copyright (C) 2010-2013 Megax <http://megax.yeahunter.hu/>
   * Copyright (C) 2013-2015 Schumix Team <http://schumix.eu/>
   * 
   * Schumix is free software: you can redistribute it and/or modify
   * it under the terms of the GNU General Public License as published by
   * the Free Software Foundation, either version 3 of the License, or
   * (at your option) any later version.
   * 
   * Schumix is distributed in the hope that it will be useful,
   * but WITHOUT ANY WARRANTY; without even the implied warranty of
   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   * GNU General Public License for more details.
   * 
   * You should have received a copy of the GNU General Public License
   * along with Schumix.  If not, see <http://www.gnu.org/licenses/>.
   */
  
  using System;
  using System.IO;
  using System.Text;
  using System.Text.RegularExpressions;
  using Schumix.Framework;
  using Schumix.Framework.Logger;
  using Schumix.Framework.Platforms;
  using Schumix.Framework.Extensions;
  
  namespace Schumix.Libraries
  {
  	public static class Tools
  	{
  		private static readonly Platform sPlatform = Singleton<Platform>.Instance;
  
  		#region Public Methods
  		#region IsNumericType
  		/// <summary>
  		/// Determines whether the specified value is of numeric type.
  		/// </summary>
  		/// <param name="o">The object to check.</param>
  		/// <returns>
  		/// 	<c>true</c> if o is a numeric type; otherwise, <c>false</c>.
  		/// </returns>
  		public static bool IsNumericType(object o)
  		{
  			return (o is byte ||
  				o is sbyte ||
  				o is short ||
  				o is ushort ||
  				o is int ||
  				o is uint ||
  				o is long ||
  				o is ulong ||
  				o is float ||
  				o is double ||
  				o is decimal);
  		}
  		#endregion
  
  		#region IsPositive
  		/// <summary>
  		/// Determines whether the specified value is positive.
  		/// </summary>
  		/// <param name="Value">The value.</param>
  		/// <param name="ZeroIsPositive">if set to <c>true</c> treats 0 as positive.</param>
  		/// <returns>
  		/// 	<c>true</c> if the specified value is positive; otherwise, <c>false</c>.
  		/// </returns>
  		public static bool IsPositive(object Value, bool ZeroIsPositive)
  		{
  			switch(Type.GetTypeCode(Value.GetType()))
  			{
  				case TypeCode.SByte:
  					return (ZeroIsPositive ? (sbyte)Value >= 0 : (sbyte)Value > 0);
  				case TypeCode.Int16:
  					return (ZeroIsPositive ? (short)Value >= 0 : (short)Value > 0);
  				case TypeCode.Int32:
  					return (ZeroIsPositive ? (int)Value >= 0 : (int)Value > 0);
  				case TypeCode.Int64:
  					return (ZeroIsPositive ? (long)Value >= 0 : (long)Value > 0);
  				case TypeCode.Single:
  					return (ZeroIsPositive ? (float)Value >= 0 : (float)Value > 0);
  				case TypeCode.Double:
  					return (ZeroIsPositive ? (double)Value >= 0 : (double)Value > 0);
  				case TypeCode.Decimal:
  					return (ZeroIsPositive ? (decimal)Value >= 0 : (decimal)Value > 0);
  				case TypeCode.Byte:
  					return (ZeroIsPositive ? true : (byte)Value > 0);
  				case TypeCode.UInt16:
  					return (ZeroIsPositive ? true : (ushort)Value > 0);
  				case TypeCode.UInt32:
  					return (ZeroIsPositive ? true : (uint)Value > 0);
  				case TypeCode.UInt64:
  					return (ZeroIsPositive ? true : (ulong)Value > 0);
  				case TypeCode.Char:
  					return (ZeroIsPositive ? true : (char)Value != '\0');
  				default:
  					return false;
  			}
  		}
  		#endregion
  
  		#region ToUnsigned
  		/// <summary>
  		/// Converts the specified values boxed type to its correpsonding unsigned
  		/// type.
  		/// </summary>
  		/// <param name="Value">The value.</param>
  		/// <returns>A boxed numeric object whos type is unsigned.</returns>
  		public static object ToUnsigned(object Value)
  		{
  			switch(Type.GetTypeCode(Value.GetType()))
  			{
  				case TypeCode.SByte:
  					return (byte)((sbyte)Value);
  				case TypeCode.Int16:
  					return (ushort)((short)Value);
  				case TypeCode.Int32:
  					return (uint)((int)Value);
  				case TypeCode.Int64:
  					return (ulong)((long)Value);
  				case TypeCode.Byte:
  					return Value;
  				case TypeCode.UInt16:
  					return Value;
  				case TypeCode.UInt32:
  					return Value;
  				case TypeCode.UInt64:
  					return Value;
  				case TypeCode.Single:
  					return (UInt32)((float)Value);
  				case TypeCode.Double:
  					return (ulong)((double)Value);
  				case TypeCode.Decimal:
  					return (ulong)((decimal)Value);
  				default:
  					return null;
  			}
  		}
  		#endregion
  
  		#region ToInteger
  		/// <summary>
  		/// Converts the specified values boxed type to its correpsonding integer
  		/// type.
  		/// </summary>
  		/// <param name="Value">The value.</param>
  		/// <returns>A boxed numeric object whos type is an integer type.</returns>
  		public static object ToInteger(object Value, bool Round)
  		{
  			switch(Type.GetTypeCode(Value.GetType()))
  			{
  				case TypeCode.SByte:
  					return Value;
  				case TypeCode.Int16:
  					return Value;
  				case TypeCode.Int32:
  					return Value;
  				case TypeCode.Int64:
  					return Value;
  				case TypeCode.Byte:
  					return Value;
  				case TypeCode.UInt16:
  					return Value;
  				case TypeCode.UInt32:
  					return Value;
  				case TypeCode.UInt64:
  					return Value;
  				case TypeCode.Single:
  					return (Round ? (int)Math.Round((float)Value) : (int)((float)Value));
  				case TypeCode.Double:
  					return (Round ? (long)Math.Round((double)Value) : (long)((double)Value));
  				case TypeCode.Decimal:
  					return (Round ? Math.Round((decimal)Value) : (decimal)Value);
  				default:
  					return null;
  			}
  		}
  		#endregion
  
  		#region UnboxToLong
  		public static long UnboxToLong(object Value, bool Round)
  		{
  			switch(Type.GetTypeCode(Value.GetType()))
  			{
  				case TypeCode.SByte:
  					return (long)((sbyte)Value);
  				case TypeCode.Int16:
  					return (long)((short)Value);
  				case TypeCode.Int32:
  					return (long)((int)Value);
  				case TypeCode.Int64:
  					return (long)Value;
  				case TypeCode.Byte:
  					return (long)((byte)Value);
  				case TypeCode.UInt16:
  					return (long)((ushort)Value);
  				case TypeCode.UInt32:
  					return (long)((uint)Value);
  				case TypeCode.UInt64:
  					return (long)((ulong)Value);
  				case TypeCode.Single:
  					return (Round ? (long)Math.Round((float)Value) : (long)((float)Value));
  				case TypeCode.Double:
  					return (Round ? (long)Math.Round((double)Value) : (long)((double)Value));
  				case TypeCode.Decimal:
  					return (Round ? (long)Math.Round((decimal)Value) : (long)((decimal)Value));
  				default:
  					return 0;
  			}
  		}
  		#endregion
  
  		#region ReplaceMetaChars
  		/// <summary>
  		/// Replaces the string representations of meta chars with their corresponding
  		/// character values.
  		/// </summary>
  		/// <param name="input">The input.</param>
  		/// <returns>A string with all string meta chars are replaced</returns>
  		public static string ReplaceMetaChars(string input)
  		{
  			return Regex.Replace(input, @"(\\)(\d{3}|[^\d])?", new MatchEvaluator(ReplaceMetaCharsMatch));
  		}
  
  		private static string ReplaceMetaCharsMatch(Match m)
  		{
  			// convert octal quotes (like \040)
  			if(m.Groups[2].Length == 3)
  				return m.Groups[2].Value.ToByte(8).ToChar().ToString();
  			else
  			{
  				// convert all other special meta characters
  				//TODO: \xhhh hex and possible dec !!
  				switch(m.Groups[2].Value)
  				{
  					case "0":           // null
  						return "\0";
  					case "a":           // alert (beep)
  						return "\a";
  					case "b":           // BS
  						return "\b";
  					case "f":           // FF
  						return "\f";
  					case "v":           // vertical tab
  						return "\v";
  					case "r":           // CR
  						return "\r";
  					case "n":           // LF
  						return "\n";
  					case "t":           // Tab
  						return "\t";
  					default:
  						// if neither an octal quote nor a special meta character
  						// so just remove the backslash
  						return m.Groups[2].Value;
  				}
  			}
  		}
  		#endregion
  
  		#region printf
  		public static void printf(string Format, params object[] Parameters)
  		{
  			Log.Write(Tools.sprintf(Format, Parameters));
  		}
  		#endregion
  
  		#region fprintf
  		public static void fprintf(TextWriter Destination, string Format, params object[] Parameters)
  		{
  			Destination.Write(Tools.sprintf(Format, Parameters));
  		}
  
  		internal static Regex r = new Regex(@"\%(\d*\$)?([\'\#\-\+ ]*)(\d*)(?:\.(\d+))?([hl])?([dioxXucsfeEgGpn%])");
  		#endregion
  
  		#region sprintf
  		public static string sprintf(string Format, params object[] Parameters)
  		{
  			#region Variables
  			var f = new StringBuilder();
  			//Regex r = new Regex(@"\%(\d*\$)?([\'\#\-\+ ]*)(\d*)(?:\.(\d+))?([hl])?([dioxXucsfeEgGpn%])");
  			//"%[parameter][flags][width][.precision][length]type"
  			Match m = null;
  			string w = string.Empty;
  			int defaultParamIx = 0;
  			int paramIx;
  			object o = null;
  
  			bool flagLeft2Right = false;
  			bool flagAlternate = false;
  			bool flagPositiveSign = false;
  			bool flagPositiveSpace = false;
  			bool flagZeroPadding = false;
  			bool flagGroupThousands = false;
  
  			int fieldLength = 0;
  			int fieldPrecision = 0;
  			char shortLongIndicator = '\0';
  			char formatSpecifier = '\0';
  			char paddingCharacter = SchumixBase.Space;
  			#endregion
  
  			// find all format parameters in format string
  			f.Append(Format);
  			m = r.Match(f.ToString());
  
  			while(m.Success)
  			{
  				#region parameter index
  				paramIx = defaultParamIx;
  
  				if(!m.Groups[1].IsNull() && m.Groups[1].Value.Length > 0)
  				{
  					string val = m.Groups[1].Value.Substring(0, m.Groups[1].Value.Length - 1);
  					paramIx = val.ToInt32() - 1;
  				}
  				#endregion
  
  				#region format flags
  				// extract format flags
  				flagAlternate = false;
  				flagLeft2Right = false;
  				flagPositiveSign = false;
  				flagPositiveSpace = false;
  				flagZeroPadding = false;
  				flagGroupThousands = false;
  
  				if(!m.Groups[2].IsNull() && m.Groups[2].Value.Length > 0)
  				{
  					string flags = m.Groups[2].Value;
  
  					flagAlternate = (flags.IndexOf('#') >= 0);
  					flagLeft2Right = (flags.IndexOf('-') >= 0);
  					flagPositiveSign = (flags.IndexOf('+') >= 0);
  					flagPositiveSpace = (flags.IndexOf(SchumixBase.Space) >= 0);
  					flagGroupThousands = (flags.IndexOf('\'') >= 0);
  
  					// positive + indicator overrides a
  					// positive space character
  					if(flagPositiveSign && flagPositiveSpace)
  						flagPositiveSpace = false;
  				}
  				#endregion
  
  				#region field length
  				// extract field length and 
  				// pading character
  				paddingCharacter = SchumixBase.Space;
  				fieldLength = int.MinValue;
  
  				if(!m.Groups[3].IsNull() && m.Groups[3].Value.Length > 0)
  				{
  					fieldLength = m.Groups[3].Value.ToInt32();
  					flagZeroPadding = (m.Groups[3].Value[0] == '0');
  				}
  				#endregion
  
  				if(flagZeroPadding)
  					paddingCharacter = '0';
  
  				// left2right allignment overrides zero padding
  				if(flagLeft2Right && flagZeroPadding)
  				{
  					flagZeroPadding = false;
  					paddingCharacter = SchumixBase.Space;
  				}
  
  				#region field precision
  				// extract field precision
  				fieldPrecision = int.MinValue;
  
  				if(!m.Groups[4].IsNull() && m.Groups[4].Value.Length > 0)
  					fieldPrecision = m.Groups[4].Value.ToInt32();
  				#endregion
  
  				#region short / long indicator
  				// extract short / long indicator
  				shortLongIndicator = Char.MinValue;
  
  				if(!m.Groups[5].IsNull() && m.Groups[5].Value.Length > 0)
  					shortLongIndicator = m.Groups[5].Value[0];
  				#endregion
  
  				#region format specifier
  				// extract format
  				formatSpecifier = Char.MinValue;
  
  				if(!m.Groups[6].IsNull() && m.Groups[6].Value.Length > 0)
  					formatSpecifier = m.Groups[6].Value[0];
  				#endregion
  
  				// default precision is 6 digits if none is specified except
  				if(fieldPrecision == int.MinValue &&
  					formatSpecifier != 's' &&
  					formatSpecifier != 'c' &&
  					Char.ToUpper(formatSpecifier) != 'X' &&
  					formatSpecifier != 'o')
  					fieldPrecision = 6;
  
  				#region get next value parameter
  				// get next value parameter and convert value parameter depending on short / long indicator
  				if(Parameters.IsNull() || paramIx >= Parameters.Length)
  					o = null;
  				else
  				{
  					o = Parameters[paramIx];
  
  					if(shortLongIndicator == 'h')
  					{
  						if(o is int)
  							o = (short)((int)o);
  						else if(o is long)
  							o = (short)((long)o);
  						else if(o is uint)
  							o = (ushort)((uint)o);
  						else if(o is ulong)
  							o = (ushort)((ulong)o);
  					}
  					else if(shortLongIndicator == 'l')
  					{
  						if(o is short)
  							o = (long)((short)o);
  						else if(o is int)
  							o = (long)((int)o);
  						else if(o is ushort)
  							o = (ulong)((ushort)o);
  						else if(o is uint)
  							o = (ulong)((uint)o);
  					}
  				}
  				#endregion
  
  				// convert value parameters to a string depending on the formatSpecifier
  				w = string.Empty;
  
  				switch(formatSpecifier)
  				{
  					#region % - character
  					case '%':   // % character
  						w = "%";
  						break;
  					#endregion
  					#region d - integer
  					case 'd':   // integer
  						w = FormatNumber((flagGroupThousands ? "n" : "d"), flagAlternate,
  										fieldLength, int.MinValue, flagLeft2Right,
  										flagPositiveSign, flagPositiveSpace,
  										paddingCharacter, o);
  						defaultParamIx++;
  						break;
  					#endregion
  					#region i - integer
  					case 'i':   // integer
  						goto case 'd';
  					#endregion
  					#region o - octal integer
  					case 'o':   // octal integer - no leading zero
  						w = FormatOct("o", flagAlternate,
  										fieldLength, int.MinValue, flagLeft2Right,
  										paddingCharacter, o);
  						defaultParamIx++;
  						break;
  					#endregion
  					#region x - hex integer
  
gt: 					case 'x':   // hex integer - no leading zero