// ==++==
//
//   Copyright (c) Microsoft Corporation.  All rights reserved.
//
// ==--==
//============================================================
//
// Class:  ParseNumbers
//
// Purpose: Methods for Parsing numbers and Strings.
// All methods are implemented in native.
//
//===========================================================  
namespace System
{

    using System;
    using System.Runtime.CompilerServices;
    internal class ParseNumbers {
        internal const int PrintAsI1=0x40;
        internal const int PrintAsI2=0x80;
        internal const int PrintAsI4=0x100;
        internal const int TreatAsUnsigned=0x200;
        internal const int TreatAsI1=0x400;
        internal const int TreatAsI2=0x800;
        internal const int IsTight=0x1000;
        internal static readonly int[] ZeroStart = {0};

        //
        // NATIVE METHODS
        // For comments on these methods please see $\src\vm\COMUtilNative.cpp
        //
        public static long StringToLong(System.String s, int radix, int flags) {
            int [] zeroStart = {0};
            return StringToLong(s,radix,flags,zeroStart);
        }

        public static int StringToInt(System.String s, int radix, int flags) {
            int [] zeroStart = {0};
            return StringToInt(s,radix,flags,zeroStart);
        }

        // From Lightning\Src\VM\COMUtilNative.cpp:: enum FmtFlags
        internal const int LeftAlign = 0x1;
        internal const int CenterAlign = 0x2;
        internal const int RightAlign = 0x4;
        internal const int PrefixSpace = 0x8;
        internal const int PrintSign = 0x10;
        internal const int PrintBase = 0x20;
        //internal const int TreatAsUnsigned = 0x10;
        //internal const int PrintAsI1 = 0x40;
        //internal const int PrintAsI2 = 0x80;
        //internal const int PrintAsI4 = 0x100;
        internal const int PrintRadixBase = 0x200;
        internal const int AlternateForm = 0x400;

        internal const int MinRadix=2;
        internal const int MaxRadix=36;

        static private int EatWhiteSpace(String buffer, int length, int i) {
            for (; i < length && Char.IsWhiteSpace(buffer[i]); i++);
            return i;
        }

        static private uint GrabInts(uint radix, String buffer, int length,
                                     ref int i,bool isUnsigned) {
            uint result=0;
            uint value;
            uint maxVal;
#if DEBUG
            if (!(radix == 2 || radix == 8 || radix == 10 || radix == 16)) {
                VTable.DebugBreak();
            }
#endif
            // Allow all non-decimal numbers to set the sign bit.
            if (radix == 10 && !isUnsigned) {
                maxVal = (0x7FFFFFFF / 10);
                while (i < length && IsDigit(buffer[i], radix, out value)) {
                    // Read all of the digits and convert to a number
                    // Check for overflows - this is sufficient & correct.
                    if (result > maxVal || ((int)result) < 0) {
                        throw new OverflowException("Overflow_Int32");
                    }
                    result = result * radix + value;
                    i++;
                }
                if ((int)result < 0 && result != 0x80000000) {
                    throw new OverflowException("Overflow_Int32");
                }
            }
            else {
                maxVal = UInt32.MaxValue / radix;
                while (i < length && IsDigit(buffer[i], radix, out value)) {
                    // Read all of the digits and convert to a number
                    // Check for overflows - this is sufficient & correct.
                    if (result > maxVal) {
                        throw new OverflowException("Overflow_UInt32");
                    }
                    result = result * radix + value;
                    i++;
                }
            }
            return result;
        }

        static private ulong GrabLongs(uint radix, String buffer, int length,
                                       ref int i, bool isUnsigned) {
            ulong result = 0;
            uint value;
            ulong maxVal;
            // Allow all non-decimal numbers to set the sign bit.
            if (radix == 10 && !isUnsigned) {
                maxVal = (0x7FFFFFFFFFFFFFFF / 10);
                while (i < length && IsDigit(buffer[i], radix, out value)) {
                    // Read all of the digits and convert to a number
                    // Check for overflows - this is sufficient & correct.
                    if (result > maxVal || ((long) result) < 0) {
                        throw new OverflowException("Overflow_Int64");
                    }
                    result = result * radix + value;
                    i++;
                }
                if ((long) result < 0 && result != 0x8000000000000000) {
                    throw new OverflowException("Overflow_Int64");
                }
            }
            else {
                maxVal = unchecked((ulong) -1L) / radix;
                while (i < length && IsDigit(buffer[i], radix, out value)) {
                    // Read all of the digits and convert to a number
                    // Check for overflows - this is sufficient & correct.
                    if (result > maxVal) {
                        throw new OverflowException("Overflow_Int64");
                    }
                    result = result * radix + value;
                    i++;
                }
            }
            return result;
        }

        static bool IsDigit(char c, uint radix, out uint result) {
            if (c >='0' && c <='9') {
                result = (uint) (c-'0');
            }
            else {
                char d = Char.ToLower(c);
                if (d >='a' && d <='z') {
                    //+10 is necessary because a is actually 10, etc.
                    result = (uint) (d-'a'+10);
                }
                else {
                    result = 0;
                    return false;
                }
            }
            return (result < radix);
        }

        public static int StringToInt(String s, int radix, int flags,
                                      int[] currPos)
        {
            if (s == null) {
                return 0;
            }
            int i = currPos[0];
            //Do some radix checking.
            //A radix of -1 says to use whatever base is spec'd on the number.
            //Parse in Base10 until we figure out what the base actually is.
            uint r = (-1==radix)?10U:(uint)radix;
            if (r != 2 && r != 10 && r != 8 && r != 16) {
                throw new ArgumentException("Arg_InvalidBase");
            }
            int length = s.Length;
            if (i < 0 || i >= length) {
                throw new ArgumentOutOfRangeException("startIndex(currPos[0])");
            }
            // Get rid of the whitespace and then check that we've still got
            // some digits to parse.
            if ((flags & IsTight) != 0) {
                i = EatWhiteSpace(s,length,i);
                if (i == length) {
                    throw new FormatException("Format_EmptyInputString");
                }
            }
            int sign = 1;
            bool isUnsigned = ((flags & TreatAsUnsigned) != 0);
            if (s[i] =='-') { // Check for a sign
                if (r != 10) {
                    throw new ArgumentException("ArgCannotHaveNegativeValue");
                }
                if (isUnsigned) {
                    throw new OverflowException("Overflow_NegativeUnsigned");
                }
                sign = -1;
                i++;
            }
            else if (s[i] =='+') {
                i++;
            }
            //Consume the 0x if we're in an unknown base or in base 16.
            if ((radix ==- 1 || radix == 16) && (i + 1 < length) && s[i] =='0') {
                if (s[i + 1] =='x' || s[i + 1] =='X') {
                    r=16;
                    i+=2;
                }
            }
            int grabNumbersStart=i;
            uint result = GrabInts(r,s,length,ref i, isUnsigned);
            if (i == grabNumbersStart) {
                throw new FormatException("Format_NoParsibleDigits");
            }
            if ((flags & IsTight) != 0) {
                //   i = EatWhiteSpace(s,length,i);
                //If we've got effluvia left at the end of the string, complain.
                if (i <(length)) {
                    throw new FormatException("Format_ExtraJunkAtEnd");
                }
            }
            //Put the current index back into the correct place.
            currPos[0]=i;
            //Return the value properly signed.
            if ((flags & TreatAsI1) != 0) {
                if (result > 0xFF) {
                    throw new OverflowException("Overflow_SByte");
                }
                // _ASSERTE(sign==1 || r==10);  // result looks positive when parsed as an I4
                if (result >= 0x80) {
                    sign = -1;
                }
            }
            else if ((flags & TreatAsI2) != 0) {
                if (result > 0xFFFF) {
                    throw new OverflowException("Overflow_Int16");
                }
                // _ASSERTE(sign==1 || r==10);  // result looks positive when parsed as an I4
                if (result >= 0x8000) {
                    sign = -1;
                }
            }
            else if (result == 0x80000000 && sign == 1 && r == 10) {
                throw new OverflowException("Overflow_Int32");
            }
            if (r == 10) {
                return unchecked((int)result) * sign;
            }
            else {
                return unchecked((int) result);
            }
        }

        public static long StringToLong(String s, int radix, int flags, int[] currPos) {
            if (s == null) {
                return 0L;
            }
            int i = currPos[0];
            //Do some radix checking.
            //A radix of -1 says to use whatever base is spec'd on the number.
            //Parse in Base10 until we figure out what the base actually is.
            uint r = (-1==radix)?10U:(uint)radix;
            if (r != 2 && r != 10 && r != 8 && r != 16) {
                throw new ArgumentException("Arg_InvalidBase");
            }
            int length = s.Length;
            if (i < 0 || i >= length) {
                throw new ArgumentOutOfRangeException("startIndex(currPos[0])");
            }
            // Get rid of the whitespace and then check that we've still
            // got some digits to parse.
            if ((flags & IsTight) != 0) {
                i = EatWhiteSpace(s,length,i);
                if (i == length) {
                    throw new FormatException("EmptyInputString");
                }
            }
            int sign = 1;
            bool isUnsigned = ((flags & TreatAsUnsigned) != 0);
            if (s[i] == '-') {
                if (r != 10) {
                    throw new ArgumentException("Negative non-base-10 number");
                }
                if (isUnsigned) {
                    throw new OverflowException("Negative unsigned number");
                }
                sign = -1;
                i++;
            }
            else if (s[i] == '+') {
                i++;
            }
            //Consume the 0x if we're in an unknown base or in base 16.
            if ((radix ==- 1 || radix == 16) && (i + 1 < length) && s[i] =='0') {
                if (s[i + 1] =='x' || s[i + 1] =='X') {
                    r=16;
                    i+=2;
                }
            }
            int grabNumbersStart = i;
            ulong result = GrabLongs(r,s,length,ref i,isUnsigned);
            if (i == grabNumbersStart) {
                throw new FormatException("No parsable digits");
            }
            if ((flags & IsTight) != 0) {
                if (i < length) {
                    throw new FormatException("Extra junk at end");
                }
            }
            //Put the current index back into the correct place.
            currPos[0]=i;
            //Return the value properly signed.
            if (result == 0x8000000000000000 && sign == 1 && r == 10) {
                throw new OverflowException("Overflow_Int64");
            }
            if (r == 10) {
                return unchecked((long) result) * sign;
            }
            else {
                return unchecked((long) result);
            }
        }

        public static String IntToDecimalString(int i) {
            // See also Lightning\Src\VM\COMUtilNative.cpp::IntToDecimalString
            int count;
            char[] buffer = IntToDecimalChars(i, out count);
            return String.StringCTOR(buffer, 0, count);
        }

        internal static char[] IntToDecimalChars(int value, out int count) {
            char[] result = new char[66];
            bool isNegative;
            uint uValue;
            if (value == 0) {
                result[0] = '0';
                count = 1;
                return result;
            }
            if (value < 0) {
                isNegative = true;
                uValue = (uint) -value;
            }
            else {
                isNegative = false;
                uValue =  (uint) value;
            }
            int index = 0;
            do {
                result[index] = (char) ((uValue % 10) + '0');
                index++;
                uValue = uValue / 10;
            } while (uValue != 0);
            if (isNegative) {
                result[index] = '-';
                index++;
            }
            // Reverse the characters in the buffer
            int limit = index / 2;
            count = limit;
            int i = 0;
            index--;
            while (i < index) {
                char t = result[i];
                result[i] = result[index];
                result[index] = t;
                i++;
                index--;
            }
            return result;
        }

        //FCIMPL5(LPVOID, ParseNumbers::IntToString, INT32 n, INT32 radix,
        //        INT32 width, WCHAR paddingChar, INT32 flags);
        public static String IntToString(int n, int radix, int width,
                                         char paddingChar, int flags) {
            // See also Lightning\Src\VM\COMUtilNative.cpp::IntToString

            // changed to fill the char[] backwards to call a normal
            // stringCTOR and not have an additional copy

            bool isNegative = false;
            int charVal;
            int buffLength;
            uint l;

            // Longest possible string length for an integer in
            // binary notation with prefix
            int bufSize = 66;
            char[] buffer = new char[bufSize];
            int index=bufSize;

            if (radix < MinRadix || radix > MaxRadix) {
                throw new ArgumentException("Argument_InvalidBase");
                //COMPlusThrowArgumentException(L"radix", L"Arg_InvalidBase");
            }

            //If the number is negative, make it positive and remember the sign.
            //If the number is MIN_VALUE, this will still be negative,
            //so we'll have to special case this later.
            if (n < 0) {
                isNegative=true;
                // For base 10, write out -num, but other bases write out the
                // 2's complement bit pattern
                if (10 == radix)
                    l = (uint)(-n);
                else
                    l = (uint)n; // REVIEW: comment suggests ~n wanted
            }
            else {
                l=(uint)n;
            }

            //The conversion to a UINT will sign extend the number.  In order to
            //ensure that we only get as many bits as we expect, we chop the
            //number.
            if ((flags & PrintAsI1) != 0) {
                l = l&0xFF;
            }
            else if ((flags & PrintAsI2) != 0) {
                l = l&0xFFFF;
            }
            else if ((flags & PrintAsI4) != 0) {
                l=l&0xFFFFFFFF;
            }

            if (0 == l) { //Special case the 0.
                buffer[--index]='0';
            }
            else {
                do {
                    charVal = (int) (l%(uint)radix);
                    l=l/(uint)radix;
                    if (charVal < 10) {
                        buffer[--index] = (char)(charVal + '0');
                    }
                    else {
                        buffer[--index] = (char)(charVal + 'a' - 10);
                    }
                } while (l != 0);
            }
            //If they want the base, append that to the string (in reverse order)
            if (radix != 10 && ((flags & PrintBase) != 0)) {
                if (16 == radix) {
                    buffer[--index]='x';
                    buffer[--index]='0';
                }
                else if (8 == radix) {
                    buffer[--index]='0';
                }
            }

            if (10 == radix) {
                if (isNegative) {
                    //If it was negative, append the sign.
                    buffer[--index]='-';
                }
                else if ((flags & PrintSign) != 0) {
                    //else if they requested, add the '+';
                    buffer[--index]='+';
                }
                else if ((flags & PrefixSpace) != 0) {
                    //If they requested a leading space, put it on.
                    buffer[--index]=' ';
                }
            }

            //Figure out the size of our string.
            if (width <= bufSize - index) {
                buffLength=bufSize-index;
            }
            else {
                buffLength=width;
            }

            String local = String.StringCTOR(buffer, index, bufSize-index);

            // Pad if necessary, open up access to String if you
            //don't want to make a new string here
            if ((flags & LeftAlign) != 0) {
                local = local.PadRight(buffLength,paddingChar);
            }
            else {
                local = local.PadLeft(buffLength,paddingChar);
            }

            return local;
        }

        //FCIMPL5(LPVOID, ParseNumbers::LongToString, INT32 radix, INT32 width,
        //        INT64 n, WCHAR paddingChar, INT32 flags)
        public static String LongToString(long n, int radix, int width,
                                          char paddingChar, int flags) {
            // See also Lightning\Src\VM\COMUtilNative.cpp::LongToString,
            bool isNegative = false;
            int charVal;
            ulong l;
            int buffLength=0;

            //Longest possible string length for an integer in
            //binary notation with prefix
            int bufSize = 67;
            char[] buffer = new char[67];
            int index=bufSize;

            if (radix < MinRadix || radix > MaxRadix) {
                throw new ArgumentException("Argument_InvalidBase");
                //COMPlusThrowArgumentException(L"radix", L"Arg_InvalidBase");
            }

            //If the number is negative, make it positive and remember the sign.
            if (n < 0) {
                isNegative=true;
                // For base 10, write out -num, but other bases write out the
                // 2's complement bit pattern
                if (10 == radix)
                    l = (ulong)(-n);
                else
                    l = (ulong)n; // REVIEW: comment suggests ~n wanted
            }
            else {
                l=(ulong)n;
            }

            if ((flags & PrintAsI1) != 0) {
                l = l&0xFF;
            }
            else if ((flags & PrintAsI2) != 0) {
                l = l&0xFFFF;
            }
            else if ((flags & PrintAsI4) != 0) {
                l=l&0xFFFFFFFF;
            }

            //Special case the 0.
            if (0 == l) {
                buffer[--index]='0';
            }
            else {
                //Pull apart the number and put the digits (in
                //reverse order) into the buffer.
                for (; l > 0; l = l/(ulong)radix) {
                    if ((charVal =(int)(l%(ulong)radix)) < 10) {
                        buffer[--index] = (char)(charVal + '0');
                    }
                    else {
                        buffer[--index] = (char)(charVal + 'a' - 10);
                    }
                }
            }

            //If they want the base, append that to the string (in reverse order)
            if (radix != 10 && ((flags & PrintBase) != 0)) {
                if (16 == radix) {
                    buffer[--index]='x';
                    buffer[--index]='0';
                }
                else if (8 == radix) {
                    buffer[--index]='0';
                }
                else if ((flags & PrintRadixBase) != 0) {
                    buffer[--index]='#';
                    buffer[--index]=(char)((radix%10)+'0');
                    buffer[--index]=(char)((radix/10)+'0');
                }
            }

            if (10 == radix) {
                if (isNegative) {
                    //If it was negative, append the sign.
                    buffer[--index]='-';
                }
                else if ((flags & PrintSign) != 0) {
                    //else if they requested, add the '+';
                    buffer[--index]='+';
                }
                else if ((flags & PrefixSpace) != 0) {
                    //If they requested a leading space, put it on.
                    buffer[--index]=' ';
                }
            }

            //Figure out the size of our string.
            if (width <= bufSize - index) {
                buffLength=bufSize-index;
            }
            else {
                buffLength=width;
            }

            String local = String.StringCTOR(buffer, index, bufSize-index);

            //Pad if necessary, open up access to String if you
            //don't want to make a new string here
            if ((flags & LeftAlign) != 0) {
                local = local.PadRight(buffLength,paddingChar);
            }
            else {
                local = local.PadLeft(buffLength,paddingChar);
            }

            return local;
        }

        private static String LongToString(int radix, int width, long l, char paddingChar, int flags) {
            // Just in case the C# compiler gets smart enough to inline the
            // original version of the above method (which calls the original
            // version of this method to avoid a problem with fastcall not
            // liking int64 as a first argument).
            return LongToString(l, radix, width, paddingChar, flags);
        }
    }
}
