// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

#if !NET

using System.Buffers;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using System.Runtime.CompilerServices;

//#if !SYSTEM_PRIVATE_CORELIB
//#pragma warning disable CS3019 // CLS compliance checking will not be performed because it is not visible from outside this assembly
//#endif

namespace System.Text
{
    /// <summary>
    /// Represents a Unicode scalar value ([ U+0000..U+D7FF ], inclusive; or [ U+E000..U+10FFFF ], inclusive).
    /// </summary>
    /// <remarks>
    /// This type's constructors and conversion operators validate the input, so consumers can call the APIs
    /// assuming that the underlying <see cref="Rune"/> instance is well-formed.
    /// </remarks>
    [DebuggerDisplay("{DebuggerDisplay,nq}")]
#if SYSTEM_PRIVATE_CORELIB
    public
#else
    internal
#endif
    readonly struct Rune : IComparable, IComparable<Rune>, IEquatable<Rune>
#if SYSTEM_PRIVATE_CORELIB
#pragma warning disable SA1001 // Commas should be spaced correctly
        , ISpanFormattable
        , IUtf8SpanFormattable
        , IUtf8SpanParsable<Rune>
#pragma warning restore SA1001
#endif
    {
        internal const int MaxUtf16CharsPerRune = 2; // supplementary plane code points are encoded as 2 UTF-16 code units
        internal const int MaxUtf8BytesPerRune = 4; // supplementary plane code points are encoded as 4 UTF-8 code units

        private const char HighSurrogateStart = '\ud800';
        private const char LowSurrogateStart = '\udc00';
        private const int HighSurrogateRange = 0x3FF;

        private const byte IsWhiteSpaceFlag = 0x80;
        private const byte IsLetterOrDigitFlag = 0x40;
        private const byte UnicodeCategoryMask = 0x1F;

        // Contains information about the ASCII character range [ U+0000..U+007F ], with:
        // - 0x80 bit if set means 'is whitespace'
        // - 0x40 bit if set means 'is letter or digit'
        // - 0x20 bit is reserved for future use
        // - bottom 5 bits are the UnicodeCategory of the character
        private static ReadOnlySpan<byte> AsciiCharInfo =>
        [
            0x0E, 0x0E, 0x0E, 0x0E, 0x0E, 0x0E, 0x0E, 0x0E, 0x0E, 0x8E, 0x8E, 0x8E, 0x8E, 0x8E, 0x0E, 0x0E, // U+0000..U+000F
            0x0E, 0x0E, 0x0E, 0x0E, 0x0E, 0x0E, 0x0E, 0x0E, 0x0E, 0x0E, 0x0E, 0x0E, 0x0E, 0x0E, 0x0E, 0x0E, // U+0010..U+001F
            0x8B, 0x18, 0x18, 0x18, 0x1A, 0x18, 0x18, 0x18, 0x14, 0x15, 0x18, 0x19, 0x18, 0x13, 0x18, 0x18, // U+0020..U+002F
            0x48, 0x48, 0x48, 0x48, 0x48, 0x48, 0x48, 0x48, 0x48, 0x48, 0x18, 0x18, 0x19, 0x19, 0x19, 0x18, // U+0030..U+003F
            0x18, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, // U+0040..U+004F
            0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x14, 0x18, 0x15, 0x1B, 0x12, // U+0050..U+005F
            0x1B, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, // U+0060..U+006F
            0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x14, 0x19, 0x15, 0x19, 0x0E, // U+0070..U+007F
        ];

        private readonly uint _value;

        /// <summary>
        /// Creates a <see cref="Rune"/> from the provided UTF-16 code unit.
        /// </summary>
        /// <exception cref="ArgumentOutOfRangeException">
        /// If <paramref name="ch"/> represents a UTF-16 surrogate code point
        /// U+D800..U+DFFF, inclusive.
        /// </exception>
        public Rune(char ch)
        {
            uint expanded = ch;
            if (expanded is >= HighSurrogateStart and <= (LowSurrogateStart + HighSurrogateRange))
            {
                throw new ArgumentOutOfRangeException(nameof(ch));
            }
            _value = expanded;
        }

        /// <summary>
        /// Creates a <see cref="Rune"/> from the provided UTF-16 surrogate pair.
        /// </summary>
        /// <exception cref="ArgumentOutOfRangeException">
        /// If <paramref name="highSurrogate"/> does not represent a UTF-16 high surrogate code point
        /// or <paramref name="lowSurrogate"/> does not represent a UTF-16 low surrogate code point.
        /// </exception>
        public Rune(char highSurrogate, char lowSurrogate)
            : this((uint)char.ConvertToUtf32(highSurrogate, lowSurrogate), false)
        {
        }

        /// <summary>
        /// Creates a <see cref="Rune"/> from the provided Unicode scalar value.
        /// </summary>
        /// <exception cref="ArgumentOutOfRangeException">
        /// If <paramref name="value"/> does not represent a value Unicode scalar value.
        /// </exception>
        public Rune(int value)
            : this((uint)value)
        {
        }

        /// <summary>
        /// Creates a <see cref="Rune"/> from the provided Unicode scalar value.
        /// </summary>
        /// <exception cref="ArgumentOutOfRangeException">
        /// If <paramref name="value"/> does not represent a value Unicode scalar value.
        /// </exception>
        //[CLSCompliant(false)]
        public Rune(uint value)
        {
            if (!UnicodeUtility.IsValidUnicodeScalar(value))
            {
                throw new ArgumentOutOfRangeException(nameof(value));
            }
            _value = value;
        }

        // non-validating ctor
        private Rune(uint scalarValue, bool _)
        {
            UnicodeDebug.AssertIsValidScalar(scalarValue);
            _value = scalarValue;
        }

        public static bool operator ==(Rune left, Rune right) => left._value == right._value;

        public static bool operator !=(Rune left, Rune right) => left._value != right._value;

        public static bool operator <(Rune left, Rune right) => left._value < right._value;

        public static bool operator <=(Rune left, Rune right) => left._value <= right._value;

        public static bool operator >(Rune left, Rune right) => left._value > right._value;

        public static bool operator >=(Rune left, Rune right) => left._value >= right._value;

        // Operators below are explicit because they may throw.

        public static explicit operator Rune(char ch) => new Rune(ch);

        //[CLSCompliant(false)]
        public static explicit operator Rune(uint value) => new Rune(value);

        public static explicit operator Rune(int value) => new Rune(value);

        // Displayed as "'<char>' (U+XXXX)"; e.g., "'e' (U+0065)"
        private string DebuggerDisplay =>
#if SYSTEM_PRIVATE_CORELIB
            string.Create(
                CultureInfo.InvariantCulture,
#else
            FormattableString.Invariant(
#endif
                $"U+{_value:X4} '{(IsValid(_value) ? ToString() : "\uFFFD")}'");

        /// <summary>
        /// Returns true if and only if this scalar value is ASCII ([ U+0000..U+007F ])
        /// and therefore representable by a single UTF-8 code unit.
        /// </summary>
        public bool IsAscii => UnicodeUtility.IsAsciiCodePoint(_value);

        /// <summary>
        /// Returns true if and only if this scalar value is within the BMP ([ U+0000..U+FFFF ])
        /// and therefore representable by a single UTF-16 code unit.
        /// </summary>
        public bool IsBmp => UnicodeUtility.IsBmpCodePoint(_value);

        /// <summary>
        /// Returns the Unicode plane (0 to 16, inclusive) which contains this scalar.
        /// </summary>
        public int Plane => UnicodeUtility.GetPlane(_value);

        /// <summary>
        /// A <see cref="Rune"/> instance that represents the Unicode replacement character U+FFFD.
        /// </summary>
        public static Rune ReplacementChar => UnsafeCreate(UnicodeUtility.ReplacementChar);

        /// <summary>
        /// Returns the length in code units (<see cref="char"/>) of the
        /// UTF-16 sequence required to represent this scalar value.
        /// </summary>
        /// <remarks>
        /// The return value will be 1 or 2.
        /// </remarks>
        public int Utf16SequenceLength
        {
            get
            {
                int codeUnitCount = UnicodeUtility.GetUtf16SequenceLength(_value);
                Debug.Assert(codeUnitCount > 0 && codeUnitCount <= MaxUtf16CharsPerRune);
                return codeUnitCount;
            }
        }

        /// <summary>
        /// Returns the length in code units of the
        /// UTF-8 sequence required to represent this scalar value.
        /// </summary>
        /// <remarks>
        /// The return value will be 1 through 4, inclusive.
        /// </remarks>
        public int Utf8SequenceLength
        {
            get
            {
                int codeUnitCount = UnicodeUtility.GetUtf8SequenceLength(_value);
                Debug.Assert(codeUnitCount > 0 && codeUnitCount <= MaxUtf8BytesPerRune);
                return codeUnitCount;
            }
        }

        /// <summary>
        /// Returns the Unicode scalar value as an integer.
        /// </summary>
        public int Value => (int)_value;

#if SYSTEM_PRIVATE_CORELIB
        private static Rune ChangeCaseCultureAware(Rune rune, TextInfo textInfo, bool toUpper)
        {
            Debug.Assert(!GlobalizationMode.Invariant, "This should've been checked by the caller.");
            Debug.Assert(textInfo != null, "This should've been checked by the caller.");

            Span<char> original = stackalloc char[MaxUtf16CharsPerRune];
            Span<char> modified = stackalloc char[MaxUtf16CharsPerRune];

            int charCount = rune.EncodeToUtf16(original);
            original = original.Slice(0, charCount);
            modified = modified.Slice(0, charCount);

            if (toUpper)
            {
                textInfo.ChangeCaseToUpper(original, modified);
            }
            else
            {
                textInfo.ChangeCaseToLower(original, modified);
            }

            // We use simple case folding rules, which disallows moving between the BMP and supplementary
            // planes when performing a case conversion. The helper methods which reconstruct a Rune
            // contain debug asserts for this condition.

            if (rune.IsBmp)
            {
                return UnsafeCreate(modified[0]);
            }
            else
            {
                return UnsafeCreate(UnicodeUtility.GetScalarFromUtf16SurrogatePair(modified[0], modified[1]));
            }
        }
#else
        private static Rune ChangeCaseCultureAware(Rune rune, CultureInfo culture, bool toUpper)
        {
            Debug.Assert(culture != null, "This should've been checked by the caller.");

            Span<char> original = stackalloc char[MaxUtf16CharsPerRune]; // worst case scenario = 2 code units (for a surrogate pair)
            Span<char> modified = stackalloc char[MaxUtf16CharsPerRune]; // case change should preserve UTF-16 code unit count

            int charCount = rune.EncodeToUtf16(original);
            original = original.Slice(0, charCount);
            modified = modified.Slice(0, charCount);

            if (toUpper)
            {
                MemoryExtensions.ToUpper(original, modified, culture);
            }
            else
            {
                MemoryExtensions.ToLower(original, modified, culture);
            }

            // We use simple case folding rules, which disallows moving between the BMP and supplementary
            // planes when performing a case conversion. The helper methods which reconstruct a Rune
            // contain debug asserts for this condition.

            if (rune.IsBmp)
            {
                return UnsafeCreate(modified[0]);
            }
            else
            {
                return UnsafeCreate(UnicodeUtility.GetScalarFromUtf16SurrogatePair(modified[0], modified[1]));
            }
        }
#endif

        public int CompareTo(Rune other) => this.Value - other.Value; // values don't span entire 32-bit domain; won't integer overflow

        internal ReadOnlySpan<char> AsSpan(Span<char> buffer)
        {
            Debug.Assert(buffer.Length >= MaxUtf16CharsPerRune);
            int charsWritten = EncodeToUtf16(buffer);
            return buffer.Slice(0, charsWritten);
        }

        /// <summary>
        /// Decodes the <see cref="Rune"/> at the beginning of the provided UTF-16 source buffer.
        /// </summary>
        /// <returns>
        /// <para>
        /// If the source buffer begins with a valid UTF-16 encoded scalar value, returns <see cref="OperationStatus.Done"/>,
        /// and outs via <paramref name="result"/> the decoded <see cref="Rune"/> and via <paramref name="charsConsumed"/> the
        /// number of <see langword="char"/>s used in the input buffer to encode the <see cref="Rune"/>.
        /// </para>
        /// <para>
        /// If the source buffer is empty or contains only a standalone UTF-16 high surrogate character, returns <see cref="OperationStatus.NeedMoreData"/>,
        /// and outs via <paramref name="result"/> <see cref="ReplacementChar"/> and via <paramref name="charsConsumed"/> the length of the input buffer.
        /// </para>
        /// <para>
        /// If the source buffer begins with an ill-formed UTF-16 encoded scalar value, returns <see cref="OperationStatus.InvalidData"/>,
        /// and outs via <paramref name="result"/> <see cref="ReplacementChar"/> and via <paramref name="charsConsumed"/> the number of
        /// <see langword="char"/>s used in the input buffer to encode the ill-formed sequence.
        /// </para>
        /// </returns>
        /// <remarks>
        /// The general calling convention is to call this method in a loop, slicing the <paramref name="source"/> buffer by
        /// <paramref name="charsConsumed"/> elements on each iteration of the loop. On each iteration of the loop <paramref name="result"/>
        /// will contain the real scalar value if successfully decoded, or it will contain <see cref="ReplacementChar"/> if
        /// the data could not be successfully decoded. This pattern provides convenient automatic U+FFFD substitution of
        /// invalid sequences while iterating through the loop.
        /// </remarks>
        public static OperationStatus DecodeFromUtf16(ReadOnlySpan<char> source, out Rune result, out int charsConsumed)
        {
            if (!source.IsEmpty)
            {
                // First, check for the common case of a BMP scalar value.
                // If this is correct, return immediately.

                char firstChar = source[0];
                if (TryCreate(firstChar, out result))
                {
                    charsConsumed = 1;
                    return OperationStatus.Done;
                }

                // First thing we saw was a UTF-16 surrogate code point.
                // Let's optimistically assume for now it's a high surrogate and hope
                // that combining it with the next char yields useful results.

                if (source.Length > 1)
                {
                    char secondChar = source[1];
                    if (TryCreate(firstChar, secondChar, out result))
                    {
                        // Success! Formed a supplementary scalar value.
                        charsConsumed = 2;
                        return OperationStatus.Done;
                    }
                    else
                    {
                        // Either the first character was a low surrogate, or the second
                        // character was not a low surrogate. This is an error.
                        goto InvalidData;
                    }
                }
                else if (!char.IsHighSurrogate(firstChar))
                {
                    // Quick check to make sure we're not going to report NeedMoreData for
                    // a single-element buffer where the data is a standalone low surrogate
                    // character. Since no additional data will ever make this valid, we'll
                    // report an error immediately.
                    goto InvalidData;
                }
            }

            // If we got to this point, the input buffer was empty, or the buffer
            // was a single element in length and that element was a high surrogate char.

            charsConsumed = source.Length;
            result = ReplacementChar;
            return OperationStatus.NeedMoreData;

        InvalidData:

            charsConsumed = 1; // maximal invalid subsequence for UTF-16 is always a single code unit in length
            result = ReplacementChar;
            return OperationStatus.InvalidData;
        }

        /// <summary>
        /// Decodes the <see cref="Rune"/> at the beginning of the provided UTF-8 source buffer.
        /// </summary>
        /// <returns>
        /// <para>
        /// If the source buffer begins with a valid UTF-8 encoded scalar value, returns <see cref="OperationStatus.Done"/>,
        /// and outs via <paramref name="result"/> the decoded <see cref="Rune"/> and via <paramref name="bytesConsumed"/> the
        /// number of <see langword="byte"/>s used in the input buffer to encode the <see cref="Rune"/>.
        /// </para>
        /// <para>
        /// If the source buffer is empty or contains only a partial UTF-8 subsequence, returns <see cref="OperationStatus.NeedMoreData"/>,
        /// and outs via <paramref name="result"/> <see cref="ReplacementChar"/> and via <paramref name="bytesConsumed"/> the length of the input buffer.
        /// </para>
        /// <para>
        /// If the source buffer begins with an ill-formed UTF-8 encoded scalar value, returns <see cref="OperationStatus.InvalidData"/>,
        /// and outs via <paramref name="result"/> <see cref="ReplacementChar"/> and via <paramref name="bytesConsumed"/> the number of
        /// <see langword="char"/>s used in the input buffer to encode the ill-formed sequence.
        /// </para>
        /// </returns>
        /// <remarks>
        /// The general calling convention is to call this method in a loop, slicing the <paramref name="source"/> buffer by
        /// <paramref name="bytesConsumed"/> elements on each iteration of the loop. On each iteration of the loop <paramref name="result"/>
        /// will contain the real scalar value if successfully decoded, or it will contain <see cref="ReplacementChar"/> if
        /// the data could not be successfully decoded. This pattern provides convenient automatic U+FFFD substitution of
        /// invalid sequences while iterating through the loop.
        /// </remarks>
        public static OperationStatus DecodeFromUtf8(ReadOnlySpan<byte> source, out Rune result, out int bytesConsumed)
        {
            // This method follows the Unicode Standard's recommendation for detecting
            // the maximal subpart of an ill-formed subsequence. See The Unicode Standard,
            // Ch. 3.9 for more details. In summary, when reporting an invalid subsequence,
            // it tries to consume as many code units as possible as long as those code
            // units constitute the beginning of a longer well-formed subsequence per Table 3-7.

            // Try reading source[0].

            int index = 0;
            if (source.IsEmpty)
            {
                goto NeedsMoreData;
            }

            uint tempValue = source[0];
            if (UnicodeUtility.IsAsciiCodePoint(tempValue))
            {
                bytesConsumed = 1;
                result = UnsafeCreate(tempValue);
                return OperationStatus.Done;
            }

            // Per Table 3-7, the beginning of a multibyte sequence must be a code unit in
            // the range [C2..F4]. If it's outside of that range, it's either a standalone
            // continuation byte, or it's an overlong two-byte sequence, or it's an out-of-range
            // four-byte sequence.

            // Try reading source[1].

            index = 1;
            if (!UnicodeUtility.IsInRangeInclusive(tempValue, 0xC2, 0xF4))
            {
                goto Invalid;
            }

            tempValue = (tempValue - 0xC2) << 6;

            if (source.Length <= 1)
            {
                goto NeedsMoreData;
            }

            // Continuation bytes are of the form [10xxxxxx], which means that their two's
            // complement representation is in the range [-65..-128]. This allows us to
            // perform a single comparison to see if a byte is a continuation byte.

            int thisByteSignExtended = (sbyte)source[1];
            if (thisByteSignExtended >= -64)
            {
                goto Invalid;
            }

            tempValue += (uint)thisByteSignExtended;
            tempValue += 0x80; // remove the continuation byte marker
            tempValue += (0xC2 - 0xC0) << 6; // remove the leading byte marker

            if (tempValue < 0x0800)
            {
                Debug.Assert(UnicodeUtility.IsInRangeInclusive(tempValue, 0x0080, 0x07FF));
                goto Finish; // this is a valid 2-byte sequence
            }

            // This appears to be a 3- or 4-byte sequence. Since per Table 3-7 we now have
            // enough information (from just two code units) to detect overlong or surrogate
            // sequences, we need to perform these checks now.

            if (!UnicodeUtility.IsInRangeInclusive(tempValue, ((0xE0 - 0xC0) << 6) + (0xA0 - 0x80), ((0xF4 - 0xC0) << 6) + (0x8F - 0x80)))
            {
                // The first two bytes were not in the range [[E0 A0]..[F4 8F]].
                // This is an overlong 3-byte sequence or an out-of-range 4-byte sequence.
                goto Invalid;
            }

            if (UnicodeUtility.IsInRangeInclusive(tempValue, ((0xED - 0xC0) << 6) + (0xA0 - 0x80), ((0xED - 0xC0) << 6) + (0xBF - 0x80)))
            {
                // This is a UTF-16 surrogate code point, which is invalid in UTF-8.
                goto Invalid;
            }

            if (UnicodeUtility.IsInRangeInclusive(tempValue, ((0xF0 - 0xC0) << 6) + (0x80 - 0x80), ((0xF0 - 0xC0) << 6) + (0x8F - 0x80)))
            {
                // This is an overlong 4-byte sequence.
                goto Invalid;
            }

            // The first two bytes were just fine. We don't need to perform any other checks
            // on the remaining bytes other than to see that they're valid continuation bytes.

            // Try reading source[2].

            index = 2;
            if (source.Length <= 2)
            {
                goto NeedsMoreData;
            }

            thisByteSignExtended = (sbyte)source[2];
            if (thisByteSignExtended >= -64)
            {
                goto Invalid; // this byte is not a UTF-8 continuation byte
            }

            tempValue <<= 6;
            tempValue += (uint)thisByteSignExtended;
            tempValue += 0x80; // remove the continuation byte marker
            tempValue -= (0xE0 - 0xC0) << 12; // remove the leading byte marker

            if (tempValue <= 0xFFFF)
            {
                Debug.Assert(UnicodeUtility.IsInRangeInclusive(tempValue, 0x0800, 0xFFFF));
                goto Finish; // this is a valid 3-byte sequence
            }

            // Try reading source[3].

            index = 3;
            if (source.Length <= 3)
            {
                goto NeedsMoreData;
            }

            thisByteSignExtended = (sbyte)source[3];
            if (thisByteSignExtended >= -64)
            {
                goto Invalid; // this byte is not a UTF-8 continuation byte
            }

            tempValue <<= 6;
            tempValue += (uint)thisByteSignExtended;
            tempValue += 0x80; // remove the continuation byte marker
            tempValue -= (0xF0 - 0xE0) << 18; // remove the leading byte marker

            // Valid 4-byte sequence
            UnicodeDebug.AssertIsValidSupplementaryPlaneScalar(tempValue);

        Finish:

            bytesConsumed = index + 1;
            Debug.Assert(1 <= bytesConsumed && bytesConsumed <= 4); // Valid subsequences are always length [1..4]
            result = UnsafeCreate(tempValue);
            return OperationStatus.Done;

        NeedsMoreData:

            Debug.Assert(0 <= index && index <= 3); // Incomplete subsequences are always length 0..3
            bytesConsumed = index;
            result = ReplacementChar;
            return OperationStatus.NeedMoreData;

        Invalid:

            Debug.Assert(1 <= index && index <= 3); // Invalid subsequences are always length 1..3
            bytesConsumed = index;
            result = ReplacementChar;
            return OperationStatus.InvalidData;
        }

        /// <summary>
        /// Decodes the <see cref="Rune"/> at the end of the provided UTF-16 source buffer.
        /// </summary>
        /// <remarks>
        /// This method is very similar to <see cref="DecodeFromUtf16(ReadOnlySpan{char}, out Rune, out int)"/>, but it allows
        /// the caller to loop backward instead of forward. The typical calling convention is that on each iteration
        /// of the loop, the caller should slice off the final <paramref name="charsConsumed"/> elements of
        /// the <paramref name="source"/> buffer.
        /// </remarks>
        public static OperationStatus DecodeLastFromUtf16(ReadOnlySpan<char> source, out Rune result, out int charsConsumed)
        {
            int index = source.Length - 1;
            if ((uint)index < (uint)source.Length)
            {
                // First, check for the common case of a BMP scalar value.
                // If this is correct, return immediately.

                char finalChar = source[index];
                if (TryCreate(finalChar, out result))
                {
                    charsConsumed = 1;
                    return OperationStatus.Done;
                }

                if (char.IsLowSurrogate(finalChar))
                {
                    // The final character was a UTF-16 low surrogate code point.
                    // This must be preceded by a UTF-16 high surrogate code point, otherwise
                    // we have a standalone low surrogate, which is always invalid.

                    index--;
                    if ((uint)index < (uint)source.Length)
                    {
                        char penultimateChar = source[index];
                        if (TryCreate(penultimateChar, finalChar, out result))
                        {
                            // Success! Formed a supplementary scalar value.
                            charsConsumed = 2;
                            return OperationStatus.Done;
                        }
                    }

                    // If we got to this point, we saw a standalone low surrogate
                    // and must report an error.

                    charsConsumed = 1; // standalone surrogate
                    result = ReplacementChar;
                    return OperationStatus.InvalidData;
                }
            }

            // If we got this far, the source buffer was empty, or the source buffer ended
            // with a UTF-16 high surrogate code point. These aren't errors since they could
            // be valid given more input data.

            charsConsumed = (int)((uint)(-source.Length) >> 31); // 0 -> 0, all other lengths -> 1
            result = ReplacementChar;
            return OperationStatus.NeedMoreData;
        }

        /// <summary>
        /// Decodes the <see cref="Rune"/> at the end of the provided UTF-8 source buffer.
        /// </summary>
        /// <remarks>
        /// This method is very similar to <see cref="DecodeFromUtf8(ReadOnlySpan{byte}, out Rune, out int)"/>, but it allows
        /// the caller to loop backward instead of forward. The typical calling convention is that on each iteration
        /// of the loop, the caller should slice off the final <paramref name="bytesConsumed"/> elements of
        /// the <paramref name="source"/> buffer.
        /// </remarks>
        public static OperationStatus DecodeLastFromUtf8(ReadOnlySpan<byte> source, out Rune value, out int bytesConsumed)
        {
            int index = source.Length - 1;
            if ((uint)index < (uint)source.Length)
            {
                // The buffer contains at least one byte. Let's check the fast case where the
                // buffer ends with an ASCII byte.

                uint tempValue = source[index];
                if (UnicodeUtility.IsAsciiCodePoint(tempValue))
                {
                    bytesConsumed = 1;
                    value = UnsafeCreate(tempValue);
                    return OperationStatus.Done;
                }

                // If the final byte is not an ASCII byte, we may be beginning or in the middle of
                // a UTF-8 multi-code unit sequence. We need to back up until we see the start of
                // the multi-code unit sequence; we can detect the leading byte because all multi-byte
                // sequences begin with a byte whose 0x40 bit is set. Since all multi-byte sequences
                // are no greater than 4 code units in length, we only need to search back a maximum
                // of four bytes.

                if (((byte)tempValue & 0x40) != 0)
                {
                    // This is a UTF-8 leading byte. We'll do a forward read from here.
                    // It'll return invalid (if given C0, F5, etc.) or incomplete. Both are fine.

                    return DecodeFromUtf8(source.Slice(index), out value, out bytesConsumed);
                }

                // If we got to this point, the final byte was a UTF-8 continuation byte.
                // Let's check the three bytes immediately preceding this, looking for the starting byte.

                for (int i = 3; i > 0; i--)
                {
                    index--;
                    if ((uint)index >= (uint)source.Length)
                    {
                        goto Invalid; // out of data
                    }

                    // The check below will get hit for ASCII (values 00..7F) and for UTF-8 starting bytes
                    // (bits 0xC0 set, values C0..FF). In two's complement this is the range [-64..127].
                    // It's just a fast way for us to terminate the search.

                    if ((sbyte)source[index] >= -64)
                    {
                        goto ForwardDecode;
                    }
                }

            Invalid:

                // If we got to this point, either:
                // - the last 4 bytes of the input buffer are continuation bytes;
                // - the entire input buffer (if fewer than 4 bytes) consists only of continuation bytes; or
                // - there's no UTF-8 leading byte between the final continuation byte of the buffer and
                //   the previous well-formed subsequence or maximal invalid subsequence.
                //
                // In all of these cases, the final byte must be a maximal invalid subsequence of length 1.
                // See comment near the end of this method for more information.

                value = ReplacementChar;
                bytesConsumed = 1;
                return OperationStatus.InvalidData;

            ForwardDecode:

                // If we got to this point, we found an ASCII byte or a UTF-8 starting byte at position source[index].
                // Technically this could also mean we found an invalid byte like C0 or F5 at this position, but that's
                // fine since it'll be handled by the forward read. From this position, we'll perform a forward read
                // and see if we consumed the entirety of the buffer.

                source = source.Slice(index);
                Debug.Assert(!source.IsEmpty, "Shouldn't reach this for empty inputs.");

                OperationStatus operationStatus = DecodeFromUtf8(source, out Rune tempRune, out int tempBytesConsumed);
                if (tempBytesConsumed == source.Length)
                {
                    // If this forward read consumed the entirety of the end of the input buffer, we can return it
                    // as the result of this function. It could be well-formed, incomplete, or invalid. If it's
                    // invalid and we consumed the remainder of the buffer, we know we've found the maximal invalid
                    // subsequence, which is what we wanted anyway.

                    bytesConsumed = tempBytesConsumed;
                    value = tempRune;
                    return operationStatus;
                }

                // If we got to this point, we know that the final continuation byte wasn't consumed by the forward
                // read that we just performed above. This means that the continuation byte has to be part of an
                // invalid subsequence since there's no UTF-8 leading byte between what we just consumed and the
                // continuation byte at the end of the input. Furthermore, since any maximal invalid subsequence
                // of length > 1 must have a UTF-8 leading byte as its first code unit, this implies that the
                // continuation byte at the end of the buffer is itself a maximal invalid subsequence of length 1.

                goto Invalid;
            }
            else
            {
                // Source buffer was empty.
                value = ReplacementChar;
                bytesConsumed = 0;
                return OperationStatus.NeedMoreData;
            }
        }

        /// <summary>
        /// Encodes this <see cref="Rune"/> to a UTF-16 destination buffer.
        /// </summary>
        /// <param name="destination">The buffer to which to write this value as UTF-16.</param>
        /// <returns>The number of <see cref="char"/>s written to <paramref name="destination"/>.</returns>
        /// <exception cref="ArgumentException">
        /// If <paramref name="destination"/> is not large enough to hold the output.
        /// </exception>
        public int EncodeToUtf16(Span<char> destination)
        {
            if (!TryEncodeToUtf16(destination, out int charsWritten))
            {
                new ArgumentException("Destination is too short.", nameof(destination));
            }

            return charsWritten;
        }

        /// <summary>
        /// Encodes this <see cref="Rune"/> to a UTF-8 destination buffer.
        /// </summary>
        /// <param name="destination">The buffer to which to write this value as UTF-8.</param>
        /// <returns>The number of <see cref="byte"/>s written to <paramref name="destination"/>.</returns>
        /// <exception cref="ArgumentException">
        /// If <paramref name="destination"/> is not large enough to hold the output.
        /// </exception>
        public int EncodeToUtf8(Span<byte> destination)
        {
            if (!TryEncodeToUtf8(destination, out int bytesWritten))
            {
                new ArgumentException("Destination is too short.", nameof(destination));
            }

            return bytesWritten;
        }

        public override bool Equals([NotNullWhen(true)] object? obj) => (obj is Rune other) && Equals(other);

        public bool Equals(Rune other) => this == other;

        /// <summary>
        /// Returns a value that indicates whether the current instance and a specified rune are equal using the specified comparison option.
        /// </summary>
        /// <param name="other">The rune to compare with the current instance.</param>
        /// <param name="comparisonType">One of the enumeration values that specifies the rules to use in the comparison.</param>
        /// <returns><see langword="true"/> if the current instance and <paramref name="other"/> are equal; otherwise, <see langword="false"/>.</returns>
        public bool Equals(Rune other, StringComparison comparisonType)
        {
            if (comparisonType is StringComparison.Ordinal)
            {
                return this == other;
            }

            // Convert this to span
            ReadOnlySpan<char> thisChars = AsSpan(stackalloc char[MaxUtf16CharsPerRune]);

            // Convert other to span
            ReadOnlySpan<char> otherChars = other.AsSpan(stackalloc char[MaxUtf16CharsPerRune]);

            // Compare span equality
            return thisChars.Equals(otherChars, comparisonType);
        }

        public override int GetHashCode() => Value;

#if SYSTEM_PRIVATE_CORELIB
        /// <summary>
        /// Gets the <see cref="Rune"/> which begins at index <paramref name="index"/> in
        /// string <paramref name="input"/>.
        /// </summary>
        /// <remarks>
        /// Throws if <paramref name="input"/> is null, if <paramref name="index"/> is out of range, or
        /// if <paramref name="index"/> does not reference the start of a valid scalar value within <paramref name="input"/>.
        /// </remarks>
        public static Rune GetRuneAt(string input, int index)
        {
            int runeValue = ReadRuneFromString(input, index);
            if (runeValue < 0)
            {
                ThrowHelper.ThrowArgumentException_CannotExtractScalar(ExceptionArgument.index);
            }

            return UnsafeCreate((uint)runeValue);
        }
#endif

        /// <summary>
        /// Returns <see langword="true"/> iff <paramref name="value"/> is a valid Unicode scalar
        /// value, i.e., is in [ U+0000..U+D7FF ], inclusive; or [ U+E000..U+10FFFF ], inclusive.
        /// </summary>
        public static bool IsValid(int value) => IsValid((uint)value);

        /// <summary>
        /// Returns <see langword="true"/> iff <paramref name="value"/> is a valid Unicode scalar
        /// value, i.e., is in [ U+0000..U+D7FF ], inclusive; or [ U+E000..U+10FFFF ], inclusive.
        /// </summary>
        //[CLSCompliant(false)]
        public static bool IsValid(uint value) => UnicodeUtility.IsValidUnicodeScalar(value);

        // returns a negative number on failure
        internal static int ReadFirstRuneFromUtf16Buffer(ReadOnlySpan<char> input)
        {
            if (input.IsEmpty)
            {
                return -1;
            }

            // Optimistically assume input is within BMP.

            uint returnValue = input[0];
            if (UnicodeUtility.IsSurrogateCodePoint(returnValue))
            {
                if (!UnicodeUtility.IsHighSurrogateCodePoint(returnValue))
                {
                    return -1;
                }

                // Treat 'returnValue' as the high surrogate.

                if (input.Length <= 1)
                {
                    return -1; // not an argument exception - just a "bad data" failure
                }

                uint potentialLowSurrogate = input[1];
                if (!UnicodeUtility.IsLowSurrogateCodePoint(potentialLowSurrogate))
                {
                    return -1;
                }

                returnValue = UnicodeUtility.GetScalarFromUtf16SurrogatePair(returnValue, potentialLowSurrogate);
            }

            return (int)returnValue;
        }

#if SYSTEM_PRIVATE_CORELIB
        // returns a negative number on failure
        private static int ReadRuneFromString(string input, int index)
        {
            if (input is null)
            {
                ThrowHelper.ThrowArgumentNullException(ExceptionArgument.input);
            }

            if ((uint)index >= (uint)input.Length)
            {
                ThrowHelper.ThrowArgumentOutOfRange_IndexMustBeLessException();
            }

            // Optimistically assume input is within BMP.

            uint returnValue = input[index];
            if (UnicodeUtility.IsSurrogateCodePoint(returnValue))
            {
                if (!UnicodeUtility.IsHighSurrogateCodePoint(returnValue))
                {
                    return -1;
                }

                // Treat 'returnValue' as the high surrogate.
                //
                // If this becomes a hot code path, we can skip the below bounds check by reading
                // off the end of the string using unsafe code. Since strings are null-terminated,
                // we're guaranteed not to read a valid low surrogate, so we'll fail correctly if
                // the string terminates unexpectedly.

                index++;
                if ((uint)index >= (uint)input.Length)
                {
                    return -1; // not an argument exception - just a "bad data" failure
                }

                uint potentialLowSurrogate = input[index];
                if (!UnicodeUtility.IsLowSurrogateCodePoint(potentialLowSurrogate))
                {
                    return -1;
                }

                returnValue = UnicodeUtility.GetScalarFromUtf16SurrogatePair(returnValue, potentialLowSurrogate);
            }

            return (int)returnValue;
        }
#endif

        /// <summary>
        /// Returns a <see cref="string"/> representation of this <see cref="Rune"/> instance.
        /// </summary>
        public override string ToString()
        {
#if SYSTEM_PRIVATE_CORELIB
            if (IsBmp)
            {
                return string.CreateFromChar((char)_value);
            }
            else
            {
                UnicodeUtility.GetUtf16SurrogatesFromSupplementaryPlaneScalar(_value, out char high, out char low);
                return string.CreateFromChar(high, low);
            }
#else
            if (IsBmp)
            {
                return ((char)_value).ToString();
            }
            else
            {
                Span<char> buffer = stackalloc char[MaxUtf16CharsPerRune];
                UnicodeUtility.GetUtf16SurrogatesFromSupplementaryPlaneScalar(_value, out buffer[0], out buffer[1]);
                return buffer.ToString();
            }
#endif
        }

#if SYSTEM_PRIVATE_CORELIB
        bool ISpanFormattable.TryFormat(Span<char> destination, out int charsWritten, ReadOnlySpan<char> format, IFormatProvider? provider) =>
            TryEncodeToUtf16(destination, out charsWritten);

        bool IUtf8SpanFormattable.TryFormat(Span<byte> utf8Destination, out int bytesWritten, ReadOnlySpan<char> format, IFormatProvider? provider) =>
            TryEncodeToUtf8(utf8Destination, out bytesWritten);

        /// <inheritdoc cref="IUtf8SpanParsable{TSelf}.TryParse(ReadOnlySpan{byte}, IFormatProvider?, out TSelf)" />
        static bool IUtf8SpanParsable<Rune>.TryParse(ReadOnlySpan<byte> utf8Text, IFormatProvider? provider, out Rune result)
        {
            if (DecodeFromUtf8(utf8Text, out result, out int bytesConsumed) == OperationStatus.Done)
            {
                if (bytesConsumed == utf8Text.Length)
                {
                    return true;
                }

                result = ReplacementChar;
            }

            return false;
        }

        /// <inheritdoc cref="IUtf8SpanParsable{TSelf}.Parse(ReadOnlySpan{byte}, IFormatProvider?)" />
        static Rune IUtf8SpanParsable<Rune>.Parse(ReadOnlySpan<byte> utf8Text, System.IFormatProvider? provider)
        {
            if (DecodeFromUtf8(utf8Text, out Rune result, out int bytesConsumed) != OperationStatus.Done || bytesConsumed != utf8Text.Length)
            {
                ThrowHelper.ThrowFormatInvalidString();
            }

            return result;
        }

        string IFormattable.ToString(string? format, IFormatProvider? formatProvider) => ToString();
#endif

        /// <summary>
        /// Attempts to create a <see cref="Rune"/> from the provided input value.
        /// </summary>
        public static bool TryCreate(char ch, out Rune result)
        {
            uint extendedValue = ch;
            if (!UnicodeUtility.IsSurrogateCodePoint(extendedValue))
            {
                result = UnsafeCreate(extendedValue);
                return true;
            }
            else
            {
                result = default;
                return false;
            }
        }

        /// <summary>
        /// Attempts to create a <see cref="Rune"/> from the provided UTF-16 surrogate pair.
        /// Returns <see langword="false"/> if the input values don't represent a well-formed UTF-16surrogate pair.
        /// </summary>
        public static bool TryCreate(char highSurrogate, char lowSurrogate, out Rune result)
        {
            // First, extend both to 32 bits, then calculate the offset of
            // each candidate surrogate char from the start of its range.

            uint highSurrogateOffset = (uint)highSurrogate - HighSurrogateStart;
            uint lowSurrogateOffset = (uint)lowSurrogate - LowSurrogateStart;

            // This is a single comparison which allows us to check both for validity at once since
            // both the high surrogate range and the low surrogate range are the same length.
            // If the comparison fails, we call to a helper method to throw the correct exception message.

            if ((highSurrogateOffset | lowSurrogateOffset) <= HighSurrogateRange)
            {
                // The 0x40u << 10 below is to account for uuuuu = wwww + 1 in the surrogate encoding.
                result = UnsafeCreate((highSurrogateOffset << 10) + ((uint)lowSurrogate - LowSurrogateStart) + (0x40u << 10));
                return true;
            }
            else
            {
                // Didn't have a high surrogate followed by a low surrogate.
                result = default;
                return false;
            }
        }

        /// <summary>
        /// Attempts to create a <see cref="Rune"/> from the provided input value.
        /// </summary>
        public static bool TryCreate(int value, out Rune result) => TryCreate((uint)value, out result);

        /// <summary>
        /// Attempts to create a <see cref="Rune"/> from the provided input value.
        /// </summary>
        //[CLSCompliant(false)]
        public static bool TryCreate(uint value, out Rune result)
        {
            if (UnicodeUtility.IsValidUnicodeScalar(value))
            {
                result = UnsafeCreate(value);
                return true;
            }
            else
            {
                result = default;
                return false;
            }
        }

        /// <summary>
        /// Encodes this <see cref="Rune"/> to a UTF-16 destination buffer.
        /// </summary>
        /// <param name="destination">The buffer to which to write this value as UTF-16.</param>
        /// <param name="charsWritten">
        /// The number of <see cref="char"/>s written to <paramref name="destination"/>,
        /// or 0 if the destination buffer is not large enough to contain the output.</param>
        /// <returns>True if the value was written to the buffer; otherwise, false.</returns>
        /// <remarks>
        /// The <see cref="Utf16SequenceLength"/> property can be queried ahead of time to determine
        /// the required size of the <paramref name="destination"/> buffer.
        /// </remarks>
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public bool TryEncodeToUtf16(Span<char> destination, out int charsWritten)
        {
            // The Rune type fits cleanly into a register, so pass byval rather than byref
            // to avoid stack-spilling the 'this' parameter.
            return TryEncodeToUtf16(this, destination, out charsWritten);
        }

        private static bool TryEncodeToUtf16(Rune value, Span<char> destination, out int charsWritten)
        {
            if (!destination.IsEmpty)
            {
                if (value.IsBmp)
                {
                    destination[0] = (char)value._value;
                    charsWritten = 1;
                    return true;
                }
                else if (destination.Length > 1)
                {
                    UnicodeUtility.GetUtf16SurrogatesFromSupplementaryPlaneScalar((uint)value._value, out destination[0], out destination[1]);
                    charsWritten = 2;
                    return true;
                }
            }

            // Destination buffer not large enough

            charsWritten = default;
            return false;
        }

        /// <summary>
        /// Encodes this <see cref="Rune"/> to a destination buffer as UTF-8 bytes.
        /// </summary>
        /// <param name="destination">The buffer to which to write this value as UTF-8.</param>
        /// <param name="bytesWritten">
        /// The number of <see cref="byte"/>s written to <paramref name="destination"/>,
        /// or 0 if the destination buffer is not large enough to contain the output.</param>
        /// <returns>True if the value was written to the buffer; otherwise, false.</returns>
        /// <remarks>
        /// The <see cref="Utf8SequenceLength"/> property can be queried ahead of time to determine
        /// the required size of the <paramref name="destination"/> buffer.
        /// </remarks>
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public bool TryEncodeToUtf8(Span<byte> destination, out int bytesWritten)
        {
            // The Rune type fits cleanly into a register, so pass byval rather than byref
            // to avoid stack-spilling the 'this' parameter.
            return TryEncodeToUtf8(this, destination, out bytesWritten);
        }

        private static bool TryEncodeToUtf8(Rune value, Span<byte> destination, out int bytesWritten)
        {
            // The bit patterns below come from the Unicode Standard, Table 3-6.

            if (!destination.IsEmpty)
            {
                if (value.IsAscii)
                {
                    destination[0] = (byte)value._value;
                    bytesWritten = 1;
                    return true;
                }

                if (destination.Length > 1)
                {
                    if (value.Value <= 0x7FFu)
                    {
                        // Scalar 00000yyy yyxxxxxx -> bytes [ 110yyyyy 10xxxxxx ]
                        destination[0] = (byte)((value._value + (0b110u << 11)) >> 6);
                        destination[1] = (byte)((value._value & 0x3Fu) + 0x80u);
                        bytesWritten = 2;
                        return true;
                    }

                    if (destination.Length > 2)
                    {
                        if (value.Value <= 0xFFFFu)
                        {
                            // Scalar zzzzyyyy yyxxxxxx -> bytes [ 1110zzzz 10yyyyyy 10xxxxxx ]
                            destination[0] = (byte)((value._value + (0b1110 << 16)) >> 12);
                            destination[1] = (byte)(((value._value & (0x3Fu << 6)) >> 6) + 0x80u);
                            destination[2] = (byte)((value._value & 0x3Fu) + 0x80u);
                            bytesWritten = 3;
                            return true;
                        }

                        if (destination.Length > 3)
                        {
                            // Scalar 000uuuuu zzzzyyyy yyxxxxxx -> bytes [ 11110uuu 10uuzzzz 10yyyyyy 10xxxxxx ]
                            destination[0] = (byte)((value._value + (0b11110 << 21)) >> 18);
                            destination[1] = (byte)(((value._value & (0x3Fu << 12)) >> 12) + 0x80u);
                            destination[2] = (byte)(((value._value & (0x3Fu << 6)) >> 6) + 0x80u);
                            destination[3] = (byte)((value._value & 0x3Fu) + 0x80u);
                            bytesWritten = 4;
                            return true;
                        }
                    }
                }
            }

            // Destination buffer not large enough

            bytesWritten = default;
            return false;
        }

#if SYSTEM_PRIVATE_CORELIB
        /// <summary>
        /// Attempts to get the <see cref="Rune"/> which begins at index <paramref name="index"/> in
        /// string <paramref name="input"/>.
        /// </summary>
        /// <returns><see langword="true"/> if a scalar value was successfully extracted from the specified index,
        /// <see langword="false"/> if a value could not be extracted due to invalid data.</returns>
        /// <remarks>
        /// Throws only if <paramref name="input"/> is null or <paramref name="index"/> is out of range.
        /// </remarks>
        public static bool TryGetRuneAt(string input, int index, out Rune value)
        {
            int runeValue = ReadRuneFromString(input, index);
            if (runeValue >= 0)
            {
                value = UnsafeCreate((uint)runeValue);
                return true;
            }
            else
            {
                value = default;
                return false;
            }
        }
#endif

        // Allows constructing a Unicode scalar value from an arbitrary 32-bit integer without
        // validation. It is the caller's responsibility to have performed manual validation
        // before calling this method. If a Rune instance is forcibly constructed
        // from invalid input, the APIs on this type have undefined behavior, potentially including
        // introducing a security hole in the consuming application.
        //
        // An example of a security hole resulting from an invalid Rune value, which could result
        // in a stack overflow.
        //
        // public int GetMarvin32HashCode(Rune r) {
        //   Span<char> buffer = stackalloc char[r.Utf16SequenceLength];
        //   r.TryEncode(buffer, ...);
        //   return Marvin32.ComputeHash(buffer.AsBytes());
        // }

        /// <summary>
        /// Creates a <see cref="Rune"/> without performing validation on the input.
        /// </summary>
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        internal static Rune UnsafeCreate(uint scalarValue) => new Rune(scalarValue, false);

        // These are analogs of APIs on System.Char

        public static double GetNumericValue(Rune value)
        {
            if (value.IsAscii)
            {
                uint baseNum = value._value - '0';
                return (baseNum <= 9) ? (double)baseNum : -1;
            }
            else
            {
                // not an ASCII char; fall back to globalization table
#if SYSTEM_PRIVATE_CORELIB
                return CharUnicodeInfo.GetNumericValue(value.Value);
#else
                if (value.IsBmp)
                {
                    return CharUnicodeInfo.GetNumericValue((char)value._value);
                }
                return CharUnicodeInfo.GetNumericValue(value.ToString(), 0);
#endif
            }
        }

        public static UnicodeCategory GetUnicodeCategory(Rune value)
        {
            if (value.IsAscii)
            {
                return (UnicodeCategory)(AsciiCharInfo[value.Value] & UnicodeCategoryMask);
            }
            else
            {
                return GetUnicodeCategoryNonAscii(value);
            }
        }

        private static UnicodeCategory GetUnicodeCategoryNonAscii(Rune value)
        {
            Debug.Assert(!value.IsAscii, "Shouldn't use this non-optimized code path for ASCII characters.");
#if (!NETSTANDARD2_0 && !NETFRAMEWORK)
            return CharUnicodeInfo.GetUnicodeCategory(value.Value);
#else
            if (value.IsBmp)
            {
                return CharUnicodeInfo.GetUnicodeCategory((char)value._value);
            }
            return CharUnicodeInfo.GetUnicodeCategory(value.ToString(), 0);
#endif
        }

        // Returns true iff this Unicode category represents a letter
        private static bool IsCategoryLetter(UnicodeCategory category)
        {
            return UnicodeUtility.IsInRangeInclusive((uint)category, (uint)UnicodeCategory.UppercaseLetter, (uint)UnicodeCategory.OtherLetter);
        }

        // Returns true iff this Unicode category represents a letter or a decimal digit
        private static bool IsCategoryLetterOrDecimalDigit(UnicodeCategory category)
        {
            return UnicodeUtility.IsInRangeInclusive((uint)category, (uint)UnicodeCategory.UppercaseLetter, (uint)UnicodeCategory.OtherLetter)
                || (category == UnicodeCategory.DecimalDigitNumber);
        }

        // Returns true iff this Unicode category represents a number
        private static bool IsCategoryNumber(UnicodeCategory category)
        {
            return UnicodeUtility.IsInRangeInclusive((uint)category, (uint)UnicodeCategory.DecimalDigitNumber, (uint)UnicodeCategory.OtherNumber);
        }

        // Returns true iff this Unicode category represents a punctuation mark
        private static bool IsCategoryPunctuation(UnicodeCategory category)
        {
            return UnicodeUtility.IsInRangeInclusive((uint)category, (uint)UnicodeCategory.ConnectorPunctuation, (uint)UnicodeCategory.OtherPunctuation);
        }

        // Returns true iff this Unicode category represents a separator
        private static bool IsCategorySeparator(UnicodeCategory category)
        {
            return UnicodeUtility.IsInRangeInclusive((uint)category, (uint)UnicodeCategory.SpaceSeparator, (uint)UnicodeCategory.ParagraphSeparator);
        }

        // Returns true iff this Unicode category represents a symbol
        private static bool IsCategorySymbol(UnicodeCategory category)
        {
            return UnicodeUtility.IsInRangeInclusive((uint)category, (uint)UnicodeCategory.MathSymbol, (uint)UnicodeCategory.OtherSymbol);
        }

        public static bool IsControl(Rune value)
        {
            // Per the Unicode stability policy, the set of control characters
            // is forever fixed at [ U+0000..U+001F ], [ U+007F..U+009F ]. No
            // characters will ever be added to or removed from the "control characters"
            // group. See https://www.unicode.org/policies/stability_policy.html.

            // Logic below depends on Rune.Value never being -1 (since Rune is a validating type)
            // 00..1F (+1) => 01..20 (&~80) => 01..20
            // 7F..9F (+1) => 80..A0 (&~80) => 00..20

            return ((value._value + 1) & ~0x80u) <= 0x20u;
        }

        public static bool IsDigit(Rune value)
        {
            if (value.IsAscii)
            {
                return UnicodeUtility.IsInRangeInclusive(value._value, '0', '9');
            }
            else
            {
                return GetUnicodeCategoryNonAscii(value) == UnicodeCategory.DecimalDigitNumber;
            }
        }

        public static bool IsLetter(Rune value)
        {
            if (value.IsAscii)
            {
                return ((value._value - 'A') & ~0x20u) <= (uint)('Z' - 'A'); // [A-Za-z]
            }
            else
            {
                return IsCategoryLetter(GetUnicodeCategoryNonAscii(value));
            }
        }

        public static bool IsLetterOrDigit(Rune value)
        {
            if (value.IsAscii)
            {
                return (AsciiCharInfo[value.Value] & IsLetterOrDigitFlag) != 0;
            }
            else
            {
                return IsCategoryLetterOrDecimalDigit(GetUnicodeCategoryNonAscii(value));
            }
        }

        public static bool IsLower(Rune value)
        {
            if (value.IsAscii)
            {
                return UnicodeUtility.IsInRangeInclusive(value._value, 'a', 'z');
            }
            else
            {
                return GetUnicodeCategoryNonAscii(value) == UnicodeCategory.LowercaseLetter;
            }
        }

        public static bool IsNumber(Rune value)
        {
            if (value.IsAscii)
            {
                return UnicodeUtility.IsInRangeInclusive(value._value, '0', '9');
            }
            else
            {
                return IsCategoryNumber(GetUnicodeCategoryNonAscii(value));
            }
        }

        public static bool IsPunctuation(Rune value)
        {
            return IsCategoryPunctuation(GetUnicodeCategory(value));
        }

        public static bool IsSeparator(Rune value)
        {
            return IsCategorySeparator(GetUnicodeCategory(value));
        }

        public static bool IsSymbol(Rune value)
        {
            return IsCategorySymbol(GetUnicodeCategory(value));
        }

        public static bool IsUpper(Rune value)
        {
            if (value.IsAscii)
            {
                return UnicodeUtility.IsInRangeInclusive(value._value, 'A', 'Z');
            }
            else
            {
                return GetUnicodeCategoryNonAscii(value) == UnicodeCategory.UppercaseLetter;
            }
        }

        public static bool IsWhiteSpace(Rune value)
        {
            if (value.IsAscii)
            {
                return (AsciiCharInfo[value.Value] & IsWhiteSpaceFlag) != 0;
            }

            // Only BMP code points can be white space, so only call into CharUnicodeInfo
            // if the incoming value is within the BMP.

            return value.IsBmp &&
#if SYSTEM_PRIVATE_CORELIB
                CharUnicodeInfo.GetIsWhiteSpace((char)value._value);
#else
                char.IsWhiteSpace((char)value._value);
#endif
        }

        public static Rune ToLower(Rune value, CultureInfo culture)
        {
            if (culture is null)
            {
                throw new ArgumentNullException(nameof(culture));
            }

            // We don't want to special-case ASCII here since the specified culture might handle
            // ASCII characters differently than the invariant culture (e.g., Turkish I). Instead
            // we'll just jump straight to the globalization tables if they're available.

#if SYSTEM_PRIVATE_CORELIB
            if (GlobalizationMode.Invariant)
            {
                return ToLowerInvariant(value);
            }

            return ChangeCaseCultureAware(value, culture.TextInfo, toUpper: false);
#else
            return ChangeCaseCultureAware(value, culture, toUpper: false);
#endif
        }

        public static Rune ToLowerInvariant(Rune value)
        {
            // Handle the most common case (ASCII data) first. Within the common case, we expect
            // that there'll be a mix of lowercase & uppercase chars, so make the conversion branchless.

            if (value.IsAscii)
            {
                // It's ok for us to use the UTF-16 conversion utility for this since the high
                // 16 bits of the value will never be set so will be left unchanged.
                return UnsafeCreate(Utf16Utility.ConvertAllAsciiCharsInUInt32ToLowercase(value._value));
            }

#if SYSTEM_PRIVATE_CORELIB
            if (GlobalizationMode.Invariant)
            {
                return UnsafeCreate(CharUnicodeInfo.ToLower(value._value));
            }

            // Non-ASCII data requires going through the case folding tables.

            return ChangeCaseCultureAware(value, TextInfo.Invariant, toUpper: false);
#else
            return ChangeCaseCultureAware(value, CultureInfo.InvariantCulture, toUpper: false);
#endif
        }

        public static Rune ToUpper(Rune value, CultureInfo culture)
        {
            if (culture is null)
            {
                throw new ArgumentNullException(nameof(culture));
            }

            // We don't want to special-case ASCII here since the specified culture might handle
            // ASCII characters differently than the invariant culture (e.g., Turkish I). Instead
            // we'll just jump straight to the globalization tables if they're available.

#if SYSTEM_PRIVATE_CORELIB
            if (GlobalizationMode.Invariant)
            {
                return ToUpperInvariant(value);
            }

            return ChangeCaseCultureAware(value, culture.TextInfo, toUpper: true);
#else
            return ChangeCaseCultureAware(value, culture, toUpper: true);
#endif
        }

        private static class Utf16Utility
        {
            /// <summary>
            /// Returns true iff the UInt32 represents two ASCII UTF-16 characters in machine endianness.
            /// </summary>
            [MethodImpl(MethodImplOptions.AggressiveInlining)]
            internal static bool AllCharsInUInt32AreAscii(uint value)
            {
                return (value & ~0x007F_007Fu) == 0;
            }

            /// <summary>
            /// Given a UInt32 that represents two ASCII UTF-16 characters, returns the invariant
            /// lowercase representation of those characters. Requires the input value to contain
            /// two ASCII UTF-16 characters in machine endianness.
            /// </summary>
            /// <remarks>
            /// This is a branchless implementation.
            /// </remarks>
            [MethodImpl(MethodImplOptions.AggressiveInlining)]
            internal static uint ConvertAllAsciiCharsInUInt32ToLowercase(uint value)
            {
                // ASSUMPTION: Caller has validated that input value is ASCII.
                Debug.Assert(AllCharsInUInt32AreAscii(value));

                // the 0x80 bit of each word of 'lowerIndicator' will be set iff the word has value >= 'A'
                uint lowerIndicator = value + 0x0080_0080u - 0x0041_0041u;

                // the 0x80 bit of each word of 'upperIndicator' will be set iff the word has value > 'Z'
                uint upperIndicator = value + 0x0080_0080u - 0x005B_005Bu;

                // the 0x80 bit of each word of 'combinedIndicator' will be set iff the word has value >= 'A' and <= 'Z'
                uint combinedIndicator = (lowerIndicator ^ upperIndicator);

                // the 0x20 bit of each word of 'mask' will be set iff the word has value >= 'A' and <= 'Z'
                uint mask = (combinedIndicator & 0x0080_0080u) >> 2;

                return value ^ mask; // bit flip uppercase letters [A-Z] => [a-z]
            }

            /// <summary>
            /// Given a UInt32 that represents two ASCII UTF-16 characters, returns the invariant
            /// uppercase representation of those characters. Requires the input value to contain
            /// two ASCII UTF-16 characters in machine endianness.
            /// </summary>
            /// <remarks>
            /// This is a branchless implementation.
            /// </remarks>
            [MethodImpl(MethodImplOptions.AggressiveInlining)]
            internal static uint ConvertAllAsciiCharsInUInt32ToUppercase(uint value)
            {
                // Intrinsified in mono interpreter
                // ASSUMPTION: Caller has validated that input value is ASCII.
                Debug.Assert(AllCharsInUInt32AreAscii(value));

                // the 0x80 bit of each word of 'lowerIndicator' will be set iff the word has value >= 'a'
                uint lowerIndicator = value + 0x0080_0080u - 0x0061_0061u;

                // the 0x80 bit of each word of 'upperIndicator' will be set iff the word has value > 'z'
                uint upperIndicator = value + 0x0080_0080u - 0x007B_007Bu;

                // the 0x80 bit of each word of 'combinedIndicator' will be set iff the word has value >= 'a' and <= 'z'
                uint combinedIndicator = (lowerIndicator ^ upperIndicator);

                // the 0x20 bit of each word of 'mask' will be set iff the word has value >= 'a' and <= 'z'
                uint mask = (combinedIndicator & 0x0080_0080u) >> 2;

                return value ^ mask; // bit flip lowercase letters [a-z] => [A-Z]
            }
        }

        public static Rune ToUpperInvariant(Rune value)
        {
            // Handle the most common case (ASCII data) first. Within the common case, we expect
            // that there'll be a mix of lowercase & uppercase chars, so make the conversion branchless.

            if (value.IsAscii)
            {
                // It's ok for us to use the UTF-16 conversion utility for this since the high
                // 16 bits of the value will never be set so will be left unchanged.
                return UnsafeCreate(Utf16Utility.ConvertAllAsciiCharsInUInt32ToUppercase(value._value));
            }

#if SYSTEM_PRIVATE_CORELIB
            if (GlobalizationMode.Invariant)
            {
                return UnsafeCreate(CharUnicodeInfo.ToUpper(value._value));
            }

            // Non-ASCII data requires going through the case folding tables.

            return ChangeCaseCultureAware(value, TextInfo.Invariant, toUpper: true);
#else
            return ChangeCaseCultureAware(value, CultureInfo.InvariantCulture, toUpper: true);
#endif
        }

        /// <inheritdoc cref="IComparable.CompareTo" />
        int IComparable.CompareTo(object? obj)
        {
            if (obj is null)
            {
                return 1; // non-null ("this") always sorts after null
            }

            if (obj is Rune other)
            {
                return this.CompareTo(other);
            }

#if SYSTEM_PRIVATE_CORLIB
            throw new ArgumentException(SR.Arg_MustBeRune);
#else
            throw new ArgumentException();
#endif
        }
    }
}

#endif
