//
// System.Double.cs
//
// Author:
//   Miguel de Icaza (miguel@ximian.com)
//   Bob Smith       (bob@thestuff.net)
//
// (C) Ximian, Inc.  http://www.ximian.com
// (C) Bob Smith.    http://www.thestuff.net
//

//
// 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.Globalization;
using System.Runtime.CompilerServices;

#if NET_2_0
using System.Runtime.ConstrainedExecution;
#endif

namespace System {
	
	[Serializable]
#if NET_2_0
	[System.Runtime.InteropServices.ComVisible (true)]
#endif
	public struct Double : IComparable, IFormattable, IConvertible
#if NET_2_0
		, IComparable <double>, IEquatable <double>
#endif
	{
		public const double Epsilon = 4.9406564584124650e-324;
		public const double MaxValue =  1.7976931348623157e308;
		public const double MinValue = -1.7976931348623157e308;
		public const double NaN = 0.0d / 0.0d;
		public const double NegativeInfinity = -1.0d / 0.0d;
		public const double PositiveInfinity = 1.0d / 0.0d;
		
		internal double m_value;

		public int CompareTo (object v)
		{
			if (v == null)
				return 1;
			
			if (!(v is System.Double))
				throw new ArgumentException (Locale.GetText ("Value is not a System.Double"));

			double dv = (double)v;

			if (IsPositiveInfinity(m_value) && IsPositiveInfinity(dv))
				return 0;

			if (IsNegativeInfinity(m_value) && IsNegativeInfinity(dv))
				return 0;

			if (IsNaN(dv))
				if (IsNaN(m_value))
					return 0;
				else
					return 1;

			if (IsNaN(m_value))
				if (IsNaN(dv))
					return 0;
				else
					return -1;

			if (m_value > dv) return 1;
			else if (m_value < dv) return -1;
			else return 0;
		}

		public override bool Equals (object o)
		{
			if (!(o is System.Double))
				return false;

			if (IsNaN ((double)o)) {
				if (IsNaN(m_value))
					return true;
				else
					return false;
			}

			return ((double) o) == m_value;
		}

#if NET_2_0
		public int CompareTo (double value)
		{
			if (IsPositiveInfinity(m_value) && IsPositiveInfinity(value))
				return 0;

			if (IsNegativeInfinity(m_value) && IsNegativeInfinity(value))
				return 0;

			if (IsNaN(value))
				if (IsNaN(m_value))
					return 0;
				else
					return 1;

			if (IsNaN(m_value))
				if (IsNaN(value))
					return 0;
				else
					return -1;

			if (m_value > value) return 1;
			else if (m_value < value) return -1;
			else return 0;
		}

		public bool Equals (double value)
		{
			if (IsNaN (value)) {
				if (IsNaN(m_value))
					return true;
				else
					return false;
			}

			return value == m_value;
		}
#endif

		public override unsafe int GetHashCode ()
		{
			double d = m_value;
			return (*((long*)&d)).GetHashCode ();
		}

		public static bool IsInfinity (double d)
		{
			return (d == PositiveInfinity || d == NegativeInfinity);
		}

#if NET_2_0
		[ReliabilityContractAttribute (Consistency.WillNotCorruptState, Cer.Success)]
#endif
		public static bool IsNaN (double d)
		{
#pragma warning disable 1718
			return (d != d);
#pragma warning restore
		}

		public static bool IsNegativeInfinity (double d)
		{
			return (d < 0.0d && (d == NegativeInfinity || d == PositiveInfinity));
		}

		public static bool IsPositiveInfinity (double d)
		{
			return (d > 0.0d && (d == NegativeInfinity || d == PositiveInfinity));
		}

		public static double Parse (string s)
		{
			return Parse (s, (NumberStyles.Float | NumberStyles.AllowThousands), null);
		}

		public static double Parse (string s, IFormatProvider fp)
		{
			return Parse (s, (NumberStyles.Float | NumberStyles.AllowThousands), fp);
		}

		public static double Parse (string s, NumberStyles style) 
		{
			return Parse (s, style, null);
		}

		// We're intentionally using constants here to avoid some bigger headaches in mcs.
		// This struct must be compiled before System.Enum so we can't use enums here.
		private const int State_AllowSign = 1;
		private const int State_Digits = 2;
		private const int State_Decimal = 3;
		private const int State_ExponentSign = 4;
		private const int State_Exponent = 5;
		private const int State_ConsumeWhiteSpace = 6;
		private const int State_Exit = 7;
		
		public static double Parse (string s, NumberStyles style, IFormatProvider provider)
		{
			Exception exc;
			double result;
			
			if (!Parse (s, style, provider, false, out result, out exc))
				throw exc;

			return result;
		}
		
		// FIXME: check if digits are group in correct numbers between the group separators
		internal static bool Parse (string s, NumberStyles style, IFormatProvider provider, bool tryParse, out double result, out Exception exc)
		{
			result = 0;
			exc = null;
			
			if (s == null) {
				if (!tryParse)
					exc = new ArgumentNullException ("s");
				return false;
			}
			if (s.Length == 0) {
				if (!tryParse)
					exc = new FormatException ();
				return false;
			}
#if NET_2_0
			// yes it's counter intuitive (buggy?) but even TryParse actually throws in this case
			if ((style & NumberStyles.AllowHexSpecifier) != 0) {
				string msg = Locale.GetText ("Double doesn't support parsing with '{0}'.", "AllowHexSpecifier");
				throw new ArgumentException (msg);
			}
#endif
			if (style > NumberStyles.Any) {
				if (!tryParse)
					exc = new ArgumentException();
				return false;
			}

			NumberFormatInfo format = NumberFormatInfo.GetInstance(provider);
			if (format == null) throw new Exception("How did this happen?");
			
			if (s == format.NaNSymbol) {
				result = Double.NaN;
				return true;
			}
			if (s == format.PositiveInfinitySymbol) {
				result = Double.PositiveInfinity;
				return true;
			}
			if (s == format.NegativeInfinitySymbol) {
				result = Double.NegativeInfinity;
				return true;
			}

			//
			// validate and prepare string for C
			//
			int len = s.Length;
			byte [] b = new byte [len + 1];
			int didx = 0;
			int sidx = 0;
			char c;
			
			if ((style & NumberStyles.AllowLeadingWhite) != 0){
				while (sidx < len && Char.IsWhiteSpace (c = s [sidx]))
				       sidx++;

				if (sidx == len) {
					if (!tryParse)
						exc = Int32.GetFormatException ();
					return false;
				}
			}

			bool allow_trailing_white = ((style & NumberStyles.AllowTrailingWhite) != 0);

			//
			// Machine state
			//
			int state = State_AllowSign;

			//
			// Setup
			//
			string decimal_separator = null;
			string group_separator = null;
			string currency_symbol = null;
			int decimal_separator_len = 0;
			int group_separator_len = 0;
			int currency_symbol_len = 0;
			if ((style & NumberStyles.AllowDecimalPoint) != 0){
				decimal_separator = format.NumberDecimalSeparator;
				decimal_separator_len = decimal_separator.Length;
			}
			if ((style & NumberStyles.AllowThousands) != 0){
				group_separator = format.NumberGroupSeparator;
				group_separator_len = group_separator.Length;
			}
			if ((style & NumberStyles.AllowCurrencySymbol) != 0){
				currency_symbol = format.CurrencySymbol;
				currency_symbol_len = currency_symbol.Length;
			}
			string positive = format.PositiveSign;
			string negative = format.NegativeSign;
			
			for (; sidx < len; sidx++){
				c = s [sidx];

				if (c == '\0') {
					sidx = len;
					continue;
				}

				switch (state){
				case State_AllowSign:
					if ((style & NumberStyles.AllowLeadingSign) != 0){
						if (c == positive [0] &&
						    s.Substring (sidx, positive.Length) == positive){
							state = State_Digits;
							sidx += positive.Length-1;
							continue;
						}

						if (c == negative [0] &&
						    s.Substring (sidx, negative.Length) == negative){
							state = State_Digits;
							b [didx++] = (byte) '-';
							sidx += negative.Length-1;
							continue;
						}
					}
					state = State_Digits;
					goto case State_Digits;
					
				case State_Digits:
					if (Char.IsDigit (c)){
						b [didx++] = (byte) c;
						break;
					}
					if (c == 'e' || c == 'E')
						goto case State_Decimal;
					
					if (decimal_separator != null &&
					    decimal_separator [0] == c) {
						if (String.CompareOrdinal (s, sidx, decimal_separator, 0, decimal_separator_len) == 0) {
							b [didx++] = (byte) '.';
							sidx += decimal_separator_len-1;
							state = State_Decimal; 
							break;
						}
					}
					if (group_separator != null &&
					    group_separator [0] == c){
						if (s.Substring (sidx, group_separator_len) ==
						    group_separator){
							sidx += group_separator_len-1;
							state = State_Digits; 
							break;
						}
					}
					if (currency_symbol != null &&
					    currency_symbol [0] == c){
						if (s.Substring (sidx, currency_symbol_len) ==
						    currency_symbol){
							sidx += currency_symbol_len-1;
							state = State_Digits; 
							break;
						}
					}
					
					if (Char.IsWhiteSpace (c))
						goto case State_ConsumeWhiteSpace;

					if (!tryParse)
						exc = new FormatException ("Unknown char: " + c);
					return false;

				case State_Decimal:
					if (Char.IsDigit (c)){
						b [didx++] = (byte) c;
						break;
					}

					if (c == 'e' || c == 'E'){
						if ((style & NumberStyles.AllowExponent) == 0) {
							if (!tryParse)
								exc = new FormatException ("Unknown char: " + c);
							return false;
						}
						b [didx++] = (byte) c;
						state = State_ExponentSign;
						break;
					}
					
					if (Char.IsWhiteSpace (c))
						goto case State_ConsumeWhiteSpace;
					
					if (!tryParse)
						exc = new FormatException ("Unknown char: " + c);
					return false;

				case State_ExponentSign:
					if (Char.IsDigit (c)){
						state = State_Exponent;
						goto case State_Exponent;
					}

					if (c == positive [0] &&
					    s.Substring (sidx, positive.Length) == positive){
						state = State_Digits;
						sidx += positive.Length-1;
						continue;
					}

					if (c == negative [0] &&
					    s.Substring (sidx, negative.Length) == negative){
						state = State_Digits;
						b [didx++] = (byte) '-';
						sidx += negative.Length-1;
						continue;
					}

					if (Char.IsWhiteSpace (c))
						goto case State_ConsumeWhiteSpace;
					
					if (!tryParse)
						exc = new FormatException ("Unknown char: " + c);
					return false;
					
				case State_Exponent:
					if (Char.IsDigit (c)){
						b [didx++] = (byte) c;
						break;
					}
					
					if (Char.IsWhiteSpace (c))
						goto case State_ConsumeWhiteSpace;
					
					if (!tryParse)
						exc = new FormatException ("Unknown char: " + c);
					return false;

				case State_ConsumeWhiteSpace:
					if (allow_trailing_white && Char.IsWhiteSpace (c)) {
						state = State_ConsumeWhiteSpace;
						break;
					}
					
					if (!tryParse)
						exc = new FormatException ("Unknown char");
					return false;
				}

				if (state == State_Exit)
					break;
			}

			b [didx] = 0;
			unsafe {
				fixed (byte *p = &b [0]){
					double retVal;
					if (!ParseImpl (p, out retVal)) {
						if (!tryParse)
							exc = Int32.GetFormatException ();
						return false;
					}
					if (IsPositiveInfinity(retVal) || IsNegativeInfinity(retVal)) {
						if (!tryParse)
							exc = new OverflowException ();
						return false;
					}

					result = retVal;
					return true;
				}
			}
		}

		[MethodImplAttribute(MethodImplOptions.InternalCall)]
		unsafe private static extern bool ParseImpl (byte *byte_ptr, out double value);
		
		public static bool TryParse (string s,
					     NumberStyles style,
					     IFormatProvider provider,
					     out double result)
		{
			Exception exc;
			if (!Parse (s, style, provider, true, out result, out exc)) {
				result = 0;
				return false;
			}

			return true;
		}
#if NET_2_0
		public static bool TryParse (string s, out double result)
		{
			return TryParse (s, NumberStyles.Any, null, out result);
		}
#endif
		public override string ToString ()
		{
			return ToString (null, null);
		}

		public string ToString (IFormatProvider fp)
		{
			return ToString (null, fp);
		}

		public string ToString (string format)
		{
			return ToString (format, null);
		}

		public string ToString (string format, IFormatProvider fp)
		{
			NumberFormatInfo nfi = NumberFormatInfo.GetInstance (fp);
			return NumberFormatter.NumberToString (format, m_value, nfi);
		}

		// =========== IConvertible Methods =========== //

		public TypeCode GetTypeCode ()
		{
			return TypeCode.Double;
		}

		object IConvertible.ToType (Type conversionType, IFormatProvider provider)
		{
			return System.Convert.ToType(m_value, conversionType, provider);
		}
		
		bool IConvertible.ToBoolean (IFormatProvider provider)
		{
			return System.Convert.ToBoolean(m_value);
		}
		
		byte IConvertible.ToByte (IFormatProvider provider)
		{
			return System.Convert.ToByte(m_value);
		}
		
		char IConvertible.ToChar (IFormatProvider provider)
		{
			throw new InvalidCastException();
		}
		
		DateTime IConvertible.ToDateTime (IFormatProvider provider)
		{
			throw new InvalidCastException();
		}
		
		decimal IConvertible.ToDecimal (IFormatProvider provider)
		{
			return System.Convert.ToDecimal(m_value);
		}
		
		double IConvertible.ToDouble (IFormatProvider provider)
		{
			return System.Convert.ToDouble(m_value);
		}
		
		short IConvertible.ToInt16 (IFormatProvider provider)
		{
			return System.Convert.ToInt16(m_value);
		}
		
		int IConvertible.ToInt32 (IFormatProvider provider)
		{
			return System.Convert.ToInt32(m_value);
		}
		
		long IConvertible.ToInt64 (IFormatProvider provider)
		{
			return System.Convert.ToInt64(m_value);
		}
		
		sbyte IConvertible.ToSByte (IFormatProvider provider)
		{
			return System.Convert.ToSByte(m_value);
		}
		
		float IConvertible.ToSingle (IFormatProvider provider)
		{
			return System.Convert.ToSingle(m_value);
		}
		
/*
		string IConvertible.ToString (IFormatProvider provider)
		{
			return ToString(provider);
		}
*/

		ushort IConvertible.ToUInt16 (IFormatProvider provider)
		{
			return System.Convert.ToUInt16(m_value);
		}
		
		uint IConvertible.ToUInt32 (IFormatProvider provider)
		{
			return System.Convert.ToUInt32(m_value);
		}
		
		ulong IConvertible.ToUInt64 (IFormatProvider provider)
		{
			return System.Convert.ToUInt64(m_value);
		}
	}
}
