//
// Conversion.cs
//
// Author:
//   Chris J Breisch (cjbreisch@altavista.net) 
//
// (C) 2002 Chris J Breisch
//

using System;
using System.Text.RegularExpressions;

namespace Microsoft.VisualBasic {
	[Microsoft.VisualBasic.CompilerServices.StandardModuleAttribute] 
	sealed public class Conversion {
		//
		// <summary>
		//		Collection : The BASIC Collection Object
		//	</summary>
		//
		//	<remarks>
		//	</remarks>
		// Declarations
		private static readonly char[] _HexDigits = {
			'0', '1', '2', '3', '4', '5', '6', '7', 
			'8', '9', 'A', 'B', 'C', 'D', 'E', 'F'
		};
		private static readonly char[] _OctDigits = {
			'0', '1', '2', '3', '4', '5', '6', '7'
		};
		private static readonly long[] _Maxes = {
			32767, 2147483647, 9223372036854775807
		};
		private enum SizeIndexes {
			Int16 = 0,
			Int32 = 1,
			Int64 = 2
		};

		// Constructors
		// Properties
		// Methods
		[MonoTODO]
		public static string ErrorToString () { 
			throw new NotImplementedException (); 
		}
		[MonoTODO]
		public static string ErrorToString (
			System.Int32 ErrorNumber) { 
			throw new NotImplementedException (); 
		}

		// Return whether d is +/- Could do this with a macro, 
		// but this is cleaner
		private static int Sign(double d) { return d > 0 ? 1 : -1;}

		// try to cast an Object to a string...used in several places
		private static string CastToString (System.Object Expression) {
			try {
				return (string)Expression;
			}
			catch {
				throw new InvalidCastException();
			}
		}
		
		// Fix on Integer types doesn't do anything
		public static short Fix (short Number) { return Number; }
		public static int Fix (int Number) { return Number; }
		public static long Fix (long Number) { return Number; }

		// Fix on other numberic types = Sign(Number) * Int(Abs(Number))
		public static double Fix (double Number) { 
			return Sign(Number) * Int (Math.Abs (Number)); 
		}
		public static float Fix (float Number) { 
			return Sign(Number) * Int (Math.Abs (Number)); 
		}
		public static decimal Fix (decimal Number) { 
			return Sign((double)Number) * Int (Math.Abs (Number)); 
		}

		// Fix on an Object type is trickier
		// first we have to cast it to the right type
		public static System.Object Fix (System.Object Number)
		{
			// always start out by throwing an exception 
			// if Number is null
			if (Number == null) {
				throw new ArgumentNullException ("Number", 
					"Value cannot be null");
			}

			TypeCode TC = Type.GetTypeCode (Number.GetType ());

			// switch on TypeCode and call appropriate Fix method
			switch (TC) {
				case TypeCode.Decimal:
					return Fix (Convert.ToDecimal (Number));
				case TypeCode.Double:
					return Fix (Convert.ToDouble (Number));
				case TypeCode.Single:
					return Fix (Convert.ToSingle (Number));
				case TypeCode.String:
					return Fix (Double.Parse (
						CastToString (Number)));

				// for integer types, don't need to do anything
				case TypeCode.Byte:
				case TypeCode.Int16:
				case TypeCode.Int32:
				case TypeCode.Int64:
				case TypeCode.SByte:
				case TypeCode.UInt16:
				case TypeCode.UInt32:
				case TypeCode.UInt64:
					return Number;

				// spec defines Empty as returning 0
				case TypeCode.Empty:
					return 0;

				// we can't convert these types
				case TypeCode.Boolean:
				case TypeCode.Char:
				case TypeCode.DateTime:
				case TypeCode.DBNull:
				case TypeCode.Object:
				default:
					throw new ArgumentException (
						"Type of argument 'Number' is '"
						+ Number.GetType().FullName + 
						"', which is not numeric.");
			}
		}

		// Int on Integer types doesn't do anything
		public static short Int (short Number) { return Number; }
		public static int Int (int Number) { return Number; }
		public static long Int (long Number) { return Number; }

		// Int on other numberic types is same thing as "Floor"
		public static double Int (double Number) { 
			return (double) Math.Floor(Number); 
		}
		public static float Int (float Number) {
			return (float) Math.Floor(Number); 
		}
		public static decimal Int (decimal Number) {
			return Decimal.Floor(Number); 
		}

		// doing an Int on an Object is trickier
		// first we have to cast to the correct type
		public static System.Object Int (System.Object Number) {
			// always start out by throwing an exception 
			// if Number is null
			if (Number == null) {
				throw new ArgumentNullException("Number", 
					"Value cannot be null");
			}

			TypeCode TC = Type.GetTypeCode (Number.GetType ());

			// switch on TypeCode and call appropriate Int method
			switch (TC) {
				case TypeCode.Decimal:
					return Int (Convert.ToDecimal (Number));
				case TypeCode.Double:
					return Int (Convert.ToDouble (Number));
				case TypeCode.Single:
					return Int (Convert.ToSingle (Number));
				case TypeCode.String:
					return Int (Double.Parse (
					CastToString(Number)));

				// Int on integer types does nothing
				case TypeCode.Byte:
				case TypeCode.Int16:
				case TypeCode.Int32:
				case TypeCode.Int64:
				case TypeCode.SByte:
				case TypeCode.UInt16:
				case TypeCode.UInt32:
				case TypeCode.UInt64:
					return Number;

				// Spec defines Empty as returning 0
				case TypeCode.Empty:
					return 0;

				// otherwise, it's we can't cast to a numeric
				case TypeCode.Boolean:
				case TypeCode.Char:
				case TypeCode.DateTime:
				case TypeCode.DBNull:
				case TypeCode.Object:
				default:
					throw new ArgumentException (
					"Type of argument 'Number' is '" + 
					Number.GetType().FullName + 
					"', which is not numeric.");			
			}
		}

		// we use this internally to get a string 
		// representation of a number in a specific base
		private static string ToBase (ulong Number, int Length, 
			char[] BaseDigits, uint Base) {
			int i;
			ulong r;
			// we use a char array here for performance
			char [] c = new Char[Length];
			string s = null;
			
			
			for (i = Length - 1; i >= 0; i--) {
				r = Number % Base;
				Number = Number / Base;
				c[i] = BaseDigits[r];
				if (Number == 0) {
					s = new string (c, i, Length - i);
					break;
				}
			}
			if (s == null) {
				return new string (c);
			}
			else {
				return s;
			}
		}


		// convert a number to Hex
		// a little bit of magic goes on here with negative #'s
		private static string ToHex(long Number, int Digits, 
			SizeIndexes Size) {
			ulong UNumber;

			if (Number < 0) {
				// we add maxint of the Number's type 
				// twice and then 2 more...this has the
				// effect of turning it into a ulong 
				// that has the same hex representation
				UNumber = (ulong)((Number + 2) + 
					_Maxes[(int)Size]) + 
					(ulong)_Maxes[(int)Size];
			}
			else {
				UNumber = (ulong)Number;
			}
			return ToBase(UNumber, Digits, _HexDigits, 16);
		}

		// call our private function, 
		// passing it the size of the item to convert
		public static string Hex (short Number) { 
			return ToHex(Number, 4, SizeIndexes.Int16); 
		}
		public static string Hex (byte Number) { 
			return ToHex(Number, 2, SizeIndexes.Int16); 
		}
		public static string Hex (int Number) { 
			return ToHex(Number, 8, SizeIndexes.Int32); 
		}
		public static string Hex (long Number) { 
			return ToHex(Number, 16, SizeIndexes.Int64); 
		}

		// Objects are trickier
		// first we have to cast to appropriate type
		public static System.String Hex (System.Object Number) {
			// always start out by throwing an exception 
			// if Number is null
			if (Number == null) {
				throw new ArgumentNullException ("Number", 
					"Value cannot be null");
			}

			TypeCode TC = Type.GetTypeCode (Number.GetType ());

			switch (TC) {
				// try to parse the string as an Int32, 
				// then an Int64, if that fails
				case TypeCode.String:
					try {
						return Hex (
							Int32.Parse (
							CastToString (Number)));
					}
					catch {
						return Hex (
							Int64.Parse (
							CastToString (Number)));
					}

				// for the int types, 
				// just call the normal "Hex" for that type
				case TypeCode.Byte:
					return Hex ((byte)Number);
				case TypeCode.Int16:
					return Hex ((short)Number);
				case TypeCode.Int32:
					return Hex ((int)Number);
				case TypeCode.Int64:
					return Hex ((long)Number);

				// empty is defined as returning 0
				case TypeCode.Empty:
					return "0";

				// we can't do any of these types
				case TypeCode.Boolean:
				case TypeCode.Char:
				case TypeCode.DBNull:
				case TypeCode.DateTime:
				case TypeCode.Decimal:
				case TypeCode.Double:
				case TypeCode.Object:
				case TypeCode.SByte:
				case TypeCode.Single:
				case TypeCode.UInt16:
				case TypeCode.UInt32:
				case TypeCode.UInt64:
				default:
					throw new ArgumentException (
					"Type of argument 'Number' is '" + 
					Number.GetType().FullName + 
					"', which is not numeric.");			
			}
		}
		
		// ToOct works just like ToHex, only in Octal.
		private static string ToOct(long Number, int Digits, 
			SizeIndexes Size) {
			ulong UNumber;

			if (Number < 0) {
				// for neg numbers add the maxint of 
				// the appropriate size twice, and then two more
				// this has the effect of turning it 
				// into a ulong with the same oct representation
				UNumber = (ulong)((Number + 2) + 
					_Maxes[(int)Size]) + 
					(ulong)(_Maxes[(int)Size]);
			}
			else {
				UNumber = (ulong)Number;
			}
			return ToBase (UNumber, Digits, _OctDigits, 8);
		}

		// call ToOct with appropriate information
		public static string Oct (short Number) { 
			return ToOct(Number, 6, SizeIndexes.Int16); 
		}
		public static string Oct (byte Number) { 
			return ToOct(Number, 3, SizeIndexes.Int16); 
		}
		public static string Oct (int Number) { 
			return ToOct(Number, 11, SizeIndexes.Int32); 
		}
		public static string Oct (long Number) { 
			return ToOct(Number, 22, SizeIndexes.Int64); 
		}

		// Objects are always trickier
		// first need to cast to appropriate type
		public static string Oct (System.Object Number) {
			// first, always throw an exception if Number is null
			if (Number == null) {
				throw new ArgumentNullException("Number", 
					"Value cannot be null");
			}

			TypeCode TC = Type.GetTypeCode (Number.GetType ());

			switch (TC) {
				// try to parse a string as an Int32 
				// and then an Int64
				case TypeCode.String:
					try {
						return Oct (
							Int32.Parse (
							CastToString (Number)));
					}
					catch {
						return Oct (
							Int64.Parse (
							CastToString (Number)));
					}

				// integer types just call the appropriate "Oct"
				case TypeCode.Byte:
					return Oct ((byte)Number);
				case TypeCode.Int16:
					return Oct ((short)Number);
				case TypeCode.Int32:
					return Oct ((int)Number);
				case TypeCode.Int64:
					return Oct ((long)Number);

				// Empty is defined as returning 0
				case TypeCode.Empty:
					return "0";

				// We can't convert these to Octal
				case TypeCode.Boolean:
				case TypeCode.Char:
				case TypeCode.DBNull:
				case TypeCode.DateTime:
				case TypeCode.Decimal:
				case TypeCode.Double:
				case TypeCode.Object:
				case TypeCode.SByte:
				case TypeCode.Single:
				case TypeCode.UInt16:
				case TypeCode.UInt32:
				case TypeCode.UInt64:
				default:
					throw new ArgumentException (
					"Type of argument 'Number' is '" + 
					Number.GetType().FullName + 
					"', which is not numeric.");			
			}
		}

		// Str is pretty easy now that we have a language 
		// with a ToString method()
		public static string Str (System.Object Number) {

			// check for null as always and throw an exception
			if (Number == null) {
				throw new ArgumentNullException("Number");
			}

			switch (Type.GetTypeCode (Number.GetType ())) {
				// for unsigned types, just call ToString
				case TypeCode.Byte:
				case TypeCode.UInt16:
				case TypeCode.UInt32:
				case TypeCode.UInt64:
					return Number.ToString();

				// for signed types, we have to leave a 
				// space for the missing + sign
				case TypeCode.Decimal:
					return ((decimal)Number > 0 ? " " : "")
						+ Number.ToString();
				case TypeCode.Double:
					return ((double)Number > 0 ? " " : "") 
						+ Number.ToString();
				case TypeCode.Int16:
					return ((short)Number > 0 ? " " : "") 
						+ Number.ToString();
				case TypeCode.Int32:
					return ((int)Number > 0 ? " " : "") 
						+ Number.ToString();
				case TypeCode.Int64:
					return ((long)Number > 0 ? " " : "") 
						+ Number.ToString();
				case TypeCode.SByte:
					return ((sbyte)Number > 0 ? " " : "") 
						+ Number.ToString();
				case TypeCode.Single:
					return ((float)Number > 0 ? " " : "") 
						+ Number.ToString();

				// can't cast anything else to a Number
				default:
					throw new InvalidCastException(
					"Argument 'Number' cannot be converted to a numeric value.");			
			}
		}

		// The Val function is pretty bizarre
		// Val ("&HFF") = 255
		// Val ("&O377") = 255
		// Val ("1234 Any Street") = 1234
		// Val ("     12   45    .   90  7   E    +   0 0 2  ") = 1245.907e+002 = 124590.7
		public static double Val (string InputStr) {
			int i;
			int Base; 
			int NumChars = 0;
			char c;
			int Length = InputStr.Length;
			char[] Number = new char[Length];
			bool FoundRadixPrefix = false;
			Regex NumberReg;
			Match NumberMatch;
			
			Base = 10;
			Number[0] = '\0';

			// go through string
			for (i = 0; i < Length; i++) {
				c = InputStr[i];

				// look for Radix prefix "&"
				if (i == 0 && c == '&') {
					FoundRadixPrefix = true;
				}
			
				// look for an H or O following the prefix
				else if (FoundRadixPrefix && i == 1 && 
					(char.ToLower(c) == 'h' || 
					char.ToLower(c) == 'o')) {
					if (c == 'H') {
						Base = 16;
					}
					else {
						Base = 8;
					}
				}

				// if we didn't find a radix prefix, 
				// ignore whitespace
				else if (char.IsWhiteSpace(c) && (Base == 10)) {
					continue;
				}

				// mash what's left together
				else {
					Number[NumChars++] = c;
				}
			}

			// now we have a string to parse
			switch (Base) {
				// FIXME : for Octal and Hex, 
				// Regex is probably overkill
				// Even for base 10, it might be faster 
				// to write our own parser
				case 8:
					NumberReg = new Regex ("^[0-7]*");
					NumberMatch = NumberReg.Match (
					new string(Number, 0, NumChars));
					break;
				case 16:
					NumberReg = new Regex ("^[0-9a-f]*", 
						RegexOptions.IgnoreCase);
					NumberMatch = NumberReg.Match (
					new string(Number, 0, NumChars));
					break;
				case 10:
				default:
					NumberReg = new Regex (
					"^[+-]?\\d*\\.?\\d*(e?[+-]?\\d*)", 
					RegexOptions.IgnoreCase);
					NumberMatch = NumberReg.Match (
					new string(Number, 0, NumChars));
					break;
				

			}

			// we found a match, try to convert it
			if (NumberMatch.Success) {
				try {
					switch (Base) {
						case 10:
							return 
							Convert.ToDouble (
							NumberMatch.Value);
						case 8:
						case 16:
							return (double)
							Convert.ToInt64 (
							NumberMatch.Value, 
								Base);
						default:
							return (double)0;
					}
				} 
				catch {
					throw new OverflowException();
				}
			}
			else {
				return (double)0;
			}
		}

		// Val on a char type is pretty simple  '9' = 9, 'a' = exception
		public static int Val (char Expression) {
			if (char.IsDigit(Expression)) {
				return Expression - '0';
			} 
			else {
				throw new ArgumentException();
			}
		}

		// if it's an object, and we can't convert 
		// it to a string, it's an exception
		public static double Val (System.Object Expression) {
			// always check for null first
			if (Expression == null) {
				throw new ArgumentNullException ("Expression", 
					"Value cannot be null");
			}

			try {
				return Val (CastToString (Expression)); 
			} 
			catch {
				throw new ArgumentException(
				"Type of argument 'Expression' is '" + 
				Expression.GetType().FullName + 
				"', which can nt be converted to numeric.");			
			}
		}
		// Events
	}
}
