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

Option Explicit On
Option Strict On

Imports System
Imports System.Globalization
Imports System.Collections.Generic
Imports System.Diagnostics

Imports Microsoft.VisualBasic.CompilerServices.Symbols
Imports Microsoft.VisualBasic.CompilerServices.Operators
Imports Microsoft.VisualBasic.CompilerServices.Utils
Imports System.Diagnostics.CodeAnalysis

Namespace Microsoft.VisualBasic.CompilerServices

    <System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Never)>
    Public NotInheritable Class LikeOperator

        Private Sub New()
        End Sub

        ' The list of ligatures
        '
        Private Enum Ligatures
            Invalid = 0
            Min = &HC6
            ssBeta = &HDF
            szBeta = &HDF
            aeUpper = &HC6
            ae = &HE6
            thUpper = &HDE
            th = &HFE
            oeUpper = &H152
            oe = &H153
            Max = &H153
        End Enum

        ' The expansions for the ligatures. Note that the order of these is the same as their
        ' order in the Ligatures enum
        Shared LigatureExpansions() As String = {"", "ss", "sz", "AE", "ae", "TH", "th", "OE", "oe"}

        Shared LigatureMap() As Byte

        Shared Sub New()
            LigatureMap = New Byte(Ligatures.Max - Ligatures.Min) {}

            LigatureMap(Ligatures.ssBeta - Ligatures.Min) = 1
            LigatureMap(Ligatures.szBeta - Ligatures.Min) = 2
            LigatureMap(Ligatures.aeUpper - Ligatures.Min) = 3
            LigatureMap(Ligatures.ae - Ligatures.Min) = 4
            LigatureMap(Ligatures.thUpper - Ligatures.Min) = 5
            LigatureMap(Ligatures.th - Ligatures.Min) = 6
            LigatureMap(Ligatures.oeUpper - Ligatures.Min) = 7
            LigatureMap(Ligatures.oe - Ligatures.Min) = 8

        End Sub

        Private Shared Function LigatureIndex(ByVal ch As Char) As Byte

            If AscW(ch) < Ligatures.Min OrElse AscW(ch) > Ligatures.Max Then
                Return 0
            End If

            Return LigatureMap(AscW(ch) - Ligatures.Min)
        End Function

        Private Shared Function CanCharExpand _
        (
            ByVal ch As Char,
            ByVal LocaleSpecificLigatureTable As Byte(),
            ByVal Comparer As CompareInfo,
            ByVal Options As CompareOptions
        ) As Integer

            Debug.Assert(Options <> CompareOptions.Ordinal, "Char expansion check unexpected during binary compare")

            Dim Index As Byte = LigatureIndex(ch)

            If Index = 0 Then
                Return 0
            End If

            If LocaleSpecificLigatureTable(Index) = 0 Then
                If Comparer.Compare(ch, LigatureExpansions(Index)) = 0 Then
                    LocaleSpecificLigatureTable(Index) = 1
                Else
                    LocaleSpecificLigatureTable(Index) = 2
                End If
            End If

            If LocaleSpecificLigatureTable(Index) = 1 Then
                Return Index
            End If

            Return 0
        End Function

        Private Shared Function GetCharExpansion _
        (
            ByVal ch As Char,
            ByVal LocaleSpecificLigatureTable As Byte(),
            ByVal Comparer As CompareInfo,
            ByVal Options As CompareOptions
        ) As String

            Dim Index As Integer = CanCharExpand(ch, LocaleSpecificLigatureTable, Comparer, Options)

            If Index = 0 Then
                Return ch
            End If

            Return LigatureExpansions(Index)
        End Function

        Private Enum CharKind
            None
            ExpandedChar1
            ExpandedChar2
        End Enum

        Private Structure LigatureInfo
            Friend Kind As CharKind
            Friend CharBeforeExpansion As Char
        End Structure

        ': What I've been able to divine about this function is that its purpose is to normalize the string 
        'that is going to be used in the Like operator.  The string may contain liguratures (two letters being represented by
        'a single glpyh) that need to be expanded.  It also may contain Katakana characters that need to be mapped to
        'narrow width characters.  
        Private Shared Sub ExpandString _
        (
            ByRef Input As String,
            ByRef Length As Integer,
            ByRef InputLigatureInfo As LigatureInfo(),
            ByVal LocaleSpecificLigatureTable As Byte(),
            ByVal Comparer As CompareInfo,
            ByVal Options As CompareOptions,
            ByRef WidthChanged As Boolean,
            ByVal UseFullWidth As Boolean
        )

            WidthChanged = False

            If Length = 0 Then Return
            Input = Input.ToLowerInvariant()

            Dim ExtraChars As Integer

            For i As Integer = 0 To Length - 1
                Dim ch As Char = Input.Chars(i)

                If CanCharExpand(ch, LocaleSpecificLigatureTable, Comparer, Options) <> 0 Then
                    ExtraChars += 1
                End If
            Next


            If ExtraChars > 0 Then
                InputLigatureInfo = New LigatureInfo(Length + ExtraChars - 1) {}
                Dim NewInput As New Text.StringBuilder(Length + ExtraChars - 1)


                Dim NewCharIndex As Integer = 0

                For i As Integer = 0 To Length - 1

                    Dim ch As Char = Input.Chars(i)

                    If CanCharExpand(ch, LocaleSpecificLigatureTable, Comparer, Options) <> 0 Then

                        Dim Expansion As String = GetCharExpansion(ch, LocaleSpecificLigatureTable, Comparer, Options)
                        NewInput.Append(Expansion)

                        InputLigatureInfo(NewCharIndex).Kind = CharKind.ExpandedChar1
                        InputLigatureInfo(NewCharIndex).CharBeforeExpansion = ch

                        NewCharIndex += 1

                        InputLigatureInfo(NewCharIndex).Kind = CharKind.ExpandedChar2
                        InputLigatureInfo(NewCharIndex).CharBeforeExpansion = ch

                    Else
                        NewInput.Append(ch)
                    End If

                    NewCharIndex += 1
                Next

                Input = NewInput.ToString()
                Length = NewInput.Length
            End If
        End Sub

        <RequiresUnreferencedCode("The types of source and pattern cannot be statically analyzed so the like operator may be trimmed")>
        Public Shared Function LikeObject(ByVal Source As Object, ByVal Pattern As Object, ByVal CompareOption As CompareMethod) As Object

            Dim conv1, conv2 As IConvertible
            Dim tc1, tc2 As TypeCode

            conv1 = TryCast(Source, IConvertible)
            If conv1 Is Nothing Then
                If Source Is Nothing Then
                    tc1 = TypeCode.Empty
                Else
                    tc1 = TypeCode.Object
                End If
            Else
                tc1 = conv1.GetTypeCode()
            End If

            conv2 = TryCast(Pattern, IConvertible)
            If conv2 Is Nothing Then
                If Pattern Is Nothing Then
                    tc2 = TypeCode.Empty
                Else
                    tc2 = TypeCode.Object
                End If
            Else
                tc2 = conv2.GetTypeCode()
            End If

            'Special cases for Char()
            If (tc1 = TypeCode.Object) AndAlso (TypeOf Source Is Char()) Then
                tc1 = TypeCode.String
            End If

            If (tc2 = TypeCode.Object) AndAlso (TypeOf Pattern Is Char()) Then
                tc2 = TypeCode.String
            End If

            If tc1 = TypeCode.Object OrElse tc2 = TypeCode.Object Then
                Return InvokeUserDefinedOperator(UserDefinedOperator.Like, Source, Pattern)
            End If

            Return LikeString(CStr(Source), CStr(Pattern), CompareOption)
        End Function

        Public Shared Function LikeString(ByVal Source As String, ByVal Pattern As String, ByVal CompareOption As CompareMethod) As Boolean
            Dim SourceIndex As Integer
            Dim PatternIndex As Integer

            Dim SourceLength As Integer
            Dim PatternLength As Integer

            Dim SourceLigatureInfo As LigatureInfo() = Nothing
            Dim PatternLigatureInfo As LigatureInfo() = Nothing

            Dim Options As CompareOptions
            Dim Comparer As CompareInfo

            If Pattern Is Nothing Then
                PatternLength = 0
            Else
                PatternLength = Pattern.Length
            End If

            If Source Is Nothing Then
                SourceLength = 0
            Else
                SourceLength = Source.Length
            End If

            '
            ' We expand ligatures up front, but we need to keep track of
            ' where they were.  We need the source ligature positions so
            ' that "?" in the pattern will match both characters of the
            ' ligature.  We need the pattern ligature positions for
            ' bracketed character lists (e.g. [abc0-9]), because a
            ' ligature would look like two separate characters. But note
            ' that we do this only for option compare text mode.
            '

            If CompareOption = CompareMethod.Binary Then
                Options = CompareOptions.Ordinal
                Comparer = Nothing
            Else
                Comparer = GetCultureInfo().CompareInfo
                Options = CompareOptions.IgnoreCase Or
                          CompareOptions.IgnoreWidth Or
                          CompareOptions.IgnoreKanaType

                Dim LocaleSpecificLigatureTable As Byte() = New Byte(LigatureExpansions.Length - 1) {}

                ExpandString(Source, SourceLength, SourceLigatureInfo, LocaleSpecificLigatureTable, Comparer, Options, False, False)
                ExpandString(Pattern, PatternLength, PatternLigatureInfo, LocaleSpecificLigatureTable, Comparer, Options, False, False)
            End If

            ' The first phase is an optimization for anything in the pattern
            ' before the first "*".  (If the pattern has no "*" in it, this
            ' will do the whole thing.)
            '
            ' Visit each character in the pattern, and see if it matches the
            ' source.
            '
            '
            Dim p As Char

            Do While (PatternIndex < PatternLength AndAlso SourceIndex < SourceLength)
                p = Pattern.Chars(PatternIndex)

                Select Case p
                    Case "?"c, ChrW(&HFF1F)
                        'AdvanceToNextChar(Source, SourceLength, SourceIndex, Options)
                        SkipToEndOfExpandedChar(SourceLigatureInfo, SourceLength, SourceIndex)

                    Case "#"c, ChrW(&HFF03)

                        If Not System.Char.IsDigit(Source.Chars(SourceIndex)) Then
                            Return False
                        End If

                    Case "["c, ChrW(&HFF3B)
                        'Match ranges like "[ACE-TZ]"

                        Dim RangePatternEmpty, RangeMismatch, RangePatternError As Boolean
                        MatchRange(
                            Source,
                            SourceLength,
                            SourceIndex,
                            SourceLigatureInfo,
                            Pattern,
                            PatternLength,
                            PatternIndex,
                            PatternLigatureInfo,
                            RangePatternEmpty,
                            RangeMismatch,
                            RangePatternError,
                            Comparer,
                            Options)

                        If RangePatternError Then
                            Throw New ArgumentException(SR.Format(SR.Argument_InvalidValue1, "Pattern"))
                        End If

                        If RangeMismatch Then
                            Return False
                        End If

                        If RangePatternEmpty Then
                            PatternIndex += 1
                            Continue Do
                        End If

                    Case "*"c, ChrW(&HFF0A)
                        Dim AsteriskMismatch, AsteriskPatternError As Boolean

                        MatchAsterisk(
                            Source,
                            SourceLength,
                            SourceIndex,
                            SourceLigatureInfo,
                            Pattern,
                            PatternLength,
                            PatternIndex,
                            PatternLigatureInfo,
                            AsteriskMismatch,
                            AsteriskPatternError,
                            Comparer,
                            Options)

                        If AsteriskPatternError Then
                            Throw New ArgumentException(SR.Format(SR.Argument_InvalidValue1, "Pattern"))
                        End If

                        Return Not AsteriskMismatch

                    Case Else
                        ' Not a special pattern character.  Just see if we have a match.
                        '
                        If CompareChars(
                                Source,
                                SourceLength,
                                SourceIndex,
                                SourceIndex,
                                SourceLigatureInfo,
                                Pattern,
                                PatternLength,
                                PatternIndex,
                                PatternIndex,
                                PatternLigatureInfo,
                                Comparer,
                                Options) <> 0 Then
                            Return False
                        End If

                End Select

                PatternIndex += 1
                SourceIndex += 1
            Loop

            ' Check for the special case that we're at the end of the source,
            ' and the pattern has nothing left in it but *'s or empty []'s.
            '
            While PatternIndex < PatternLength
                p = Pattern.Chars(PatternIndex)

                If p = "*"c OrElse p = ChrW(&HFF0A) Then
                    PatternIndex += 1

                ElseIf PatternIndex + 1 < PatternLength AndAlso
                        ((p = "["c AndAlso Pattern.Chars(PatternIndex + 1) = "]"c) OrElse
                         (p = ChrW(&HFF3B) AndAlso Pattern.Chars(PatternIndex + 1) = ChrW(&HFF3D))) Then

                    PatternIndex += 2
                Else
                    Exit While
                End If
            End While

            Return PatternIndex >= PatternLength AndAlso SourceIndex >= SourceLength
        End Function

        Private Shared Sub SkipToEndOfExpandedChar(ByVal InputLigatureInfo As LigatureInfo(), ByVal Length As Integer, ByRef Current As Integer)

            If InputLigatureInfo Is Nothing Then
                'Nothing to do for the option compare binary case or the simple option compare text case
            Else
                If Current < Length AndAlso InputLigatureInfo(Current).Kind = CharKind.ExpandedChar1 Then
                    Current = Current + 1
                End If
            End If
        End Sub

        Private Shared Function CompareChars _
        (
            ByVal Left As String,
            ByVal LeftLength As Integer,
            ByVal LeftStart As Integer,
            ByRef LeftEnd As Integer,
            ByVal LeftLigatureInfo As LigatureInfo(),
            ByVal Right As String,
            ByVal RightLength As Integer,
            ByVal RightStart As Integer,
            ByRef RightEnd As Integer,
            ByVal RightLigatureInfo As LigatureInfo(),
            ByVal Comparer As CompareInfo,
            ByVal Options As CompareOptions,
            Optional ByVal MatchBothCharsOfExpandedCharInRight As Boolean = False,
            Optional ByVal UseUnexpandedCharForRight As Boolean = False
        ) As Integer

            LeftEnd = LeftStart
            RightEnd = RightStart

            If Options = CompareOptions.Ordinal Then
                ' Ordinal compare
                '
                Return AscW(Left.Chars(LeftStart)) - AscW(Right.Chars(RightStart))
            End If

            Debug.Assert(Comparer IsNot Nothing, "Like Operator - Comparer expected for option compare text")
            Debug.Assert(Not MatchBothCharsOfExpandedCharInRight OrElse Not UseUnexpandedCharForRight, "Conflicting compare options")

            If UseUnexpandedCharForRight Then
                If RightLigatureInfo IsNot Nothing AndAlso RightLigatureInfo(RightEnd).Kind = CharKind.ExpandedChar1 Then

                    Right = Right.Substring(RightStart, RightEnd - RightStart)
                    Right = Right & RightLigatureInfo(RightEnd).CharBeforeExpansion
                    RightEnd += 1
                    Return CompareChars(Left.Substring(LeftStart, LeftEnd - LeftStart + 1), Right, Comparer, Options)

                End If

            ElseIf MatchBothCharsOfExpandedCharInRight Then

                Dim SavedRightEnd As Integer = RightEnd
                SkipToEndOfExpandedChar(RightLigatureInfo, RightLength, RightEnd)

                ' If matching both expanded characters on the right, then consider multiple characters on the left too
                '
                If SavedRightEnd < RightEnd Then

                    Dim NumberOfExtraCharsToCompare As Integer = 0
                    If LeftEnd + 1 < LeftLength Then
                        NumberOfExtraCharsToCompare = 1
                    End If

                    Dim MatchResult As Integer =
                        CompareChars(Left.Substring(LeftStart, LeftEnd - LeftStart + 1 + NumberOfExtraCharsToCompare), Right.Substring(RightStart, RightEnd - RightStart + 1), Comparer, Options)

                    If MatchResult = 0 Then
                        LeftEnd = LeftEnd + NumberOfExtraCharsToCompare
                    End If

                    Return MatchResult
                End If
            End If

            Debug.Assert(LeftEnd < LeftLength AndAlso RightEnd < RightLength, "Comparing chars beyond end of string")

            If LeftEnd = LeftStart AndAlso RightEnd = RightStart Then
                Return Comparer.Compare(Left.Chars(LeftStart), Right.Chars(RightStart), Options)
            End If

            Return CompareChars(Left.Substring(LeftStart, LeftEnd - LeftStart + 1), Right.Substring(RightStart, RightEnd - RightStart + 1), Comparer, Options)

        End Function

        Private Shared Function CompareChars _
        (
            ByVal Left As String,
            ByVal Right As String,
            ByVal Comparer As CompareInfo,
            ByVal Options As CompareOptions
        ) As Integer

            If Options = CompareOptions.Ordinal Then
                ' Ordinal compare
                '
                Return AscW(Left.Chars(0)) - AscW(Right.Chars(0))
            End If

            Debug.Assert(Comparer IsNot Nothing, "Like Operator - Comparer expected for option compare text")

            Return Comparer.Compare(Left, Right, Options)

        End Function

        Private Shared Function CompareChars _
        (
            ByVal Left As Char,
            ByVal Right As Char,
            ByVal Comparer As CompareInfo,
            ByVal Options As CompareOptions
        ) As Integer

            If Options = CompareOptions.Ordinal Then
                ' Ordinal compare
                '
                Return AscW(Left) - AscW(Right)
            End If

            Debug.Assert(Comparer IsNot Nothing, "Like Operator - Comparer expected for option compare text")

            Return Comparer.Compare(Left, Right, Options)

        End Function

        Private Shared Sub MatchRange _
        (
            ByVal Source As String,
            ByVal SourceLength As Integer,
            ByRef SourceIndex As Integer,
            ByVal SourceLigatureInfo As LigatureInfo(),
            ByVal Pattern As String,
            ByVal PatternLength As Integer,
            ByRef PatternIndex As Integer,
            ByVal PatternLigatureInfo As LigatureInfo(),
            ByRef RangePatternEmpty As Boolean,
            ByRef Mismatch As Boolean,
            ByRef PatternError As Boolean,
            ByVal Comparer As CompareInfo,
            ByVal Options As CompareOptions,
            Optional ByRef SeenNot As Boolean = False,
            Optional ByVal RangeList As List(Of Range) = Nothing,
            Optional ByVal ValidatePatternWithoutMatching As Boolean = False
        )

            Debug.Assert(PatternIndex <= PatternLength AndAlso
                         (Pattern.Chars(PatternIndex) = "[" OrElse Pattern.Chars(PatternIndex) = ChrW(&HFF3B)),
                            "Like operator - Unexpected range matching")

            Debug.Assert(RangeList Is Nothing OrElse ValidatePatternWithoutMatching, "Unexpected options to MatchRange")

            Dim RangeStart, RangeEnd As String
            Dim Range As Range

            RangePatternEmpty = False
            Mismatch = False
            PatternError = False
            SeenNot = False

            PatternIndex += 1

            If PatternIndex >= PatternLength Then
                PatternError = True
                Return
            End If

            Dim p As Char = Pattern.Chars(PatternIndex)

            If p = "!"c OrElse p = ChrW(&HFF01) Then
                SeenNot = True
                PatternIndex += 1

                If PatternIndex >= PatternLength Then
                    Mismatch = True
                    Return
                End If

                p = Pattern.Chars(PatternIndex)
            End If

            If p = "]"c OrElse p = ChrW(&HFF3D) Then

                If SeenNot Then
                    'We got "[!]" ?	Treat it as the single literal character "!".
                    '
                    SeenNot = False

                    If (Not ValidatePatternWithoutMatching) Then
                        Mismatch = Not (CompareChars(Source.Chars(SourceIndex), "!"c, Comparer, Options) = 0)
                    End If

                    If RangeList IsNot Nothing Then
                        Range.Start = PatternIndex - 1
                        Range.StartLength = 1
                        Range.End = -1
                        Range.EndLength = 0
                        RangeList.Add(Range)
                    End If

                    Return
                End If

                ' Ignore empty brackets
                RangePatternEmpty = True
                Return
            End If

            ' Scan through character list
            '
            Do
                RangeStart = Nothing
                RangeEnd = Nothing

                If p = "]"c OrElse p = ChrW(&HFF3D) Then
                    Mismatch = Not SeenNot
                    Return      'End of "[...]" match
                End If

                ' Try to match the expanded ligature
                '
                Dim SourceNextIndex, PatternNextIndex As Integer
                Dim CompareResult As Integer

                If Not ValidatePatternWithoutMatching AndAlso
                   PatternLigatureInfo IsNot Nothing AndAlso
                   PatternLigatureInfo(PatternIndex).Kind = CharKind.ExpandedChar1 Then

                    ' VB6 compat - Match expanded char and return in this case without even validating RangeStart > RangeEnd
                    '
                    CompareResult =
                        CompareChars(
                            Source,
                            SourceLength,
                            SourceIndex,
                            SourceNextIndex,
                            SourceLigatureInfo,
                            Pattern,
                            PatternLength,
                            PatternIndex,
                            PatternNextIndex,
                            PatternLigatureInfo,
                            Comparer,
                            Options,
                            MatchBothCharsOfExpandedCharInRight:=True)

                    If CompareResult = 0 Then
                        SourceIndex = SourceNextIndex
                        PatternIndex = PatternNextIndex
                        GoTo OneCharMatch
                    End If

                Else
                    PatternNextIndex = PatternIndex
                    SkipToEndOfExpandedChar(PatternLigatureInfo, PatternLength, PatternNextIndex)
                End If

                Range.Start = PatternIndex
                Range.StartLength = PatternNextIndex - PatternIndex + 1

                ' Store the range start char
                '
                If Options = CompareOptions.Ordinal Then
                    RangeStart = Pattern.Chars(PatternIndex)
                ElseIf PatternLigatureInfo IsNot Nothing AndAlso PatternLigatureInfo(PatternIndex).Kind = CharKind.ExpandedChar1 Then
                    RangeStart = PatternLigatureInfo(PatternIndex).CharBeforeExpansion
                    PatternIndex = PatternNextIndex
                Else
                    RangeStart = Pattern.Substring(PatternIndex, PatternNextIndex - PatternIndex + 1)
                    PatternIndex = PatternNextIndex
                End If


                If PatternNextIndex + 2 < PatternLength AndAlso
                   (Pattern.Chars(PatternNextIndex + 1) = "-"c OrElse Pattern.Chars(PatternNextIndex + 1) = ChrW(&HFF0D)) AndAlso
                   (Pattern.Chars(PatternNextIndex + 2) <> "]"c AndAlso Pattern.Chars(PatternNextIndex + 2) <> ChrW(&HFF3D)) Then

                    ' We're at the last character of a range.
                    '
                    PatternIndex += 2

                    ' Try to match one char
                    '
                    If Not ValidatePatternWithoutMatching AndAlso
                       PatternLigatureInfo IsNot Nothing AndAlso
                       PatternLigatureInfo(PatternIndex).Kind = CharKind.ExpandedChar1 Then

                        ' VB6 compat - Match expanded char and return in this case without even validating RangeStart > RangeEnd
                        '
                        CompareResult =
                           CompareChars(
                               Source,
                               SourceLength,
                               SourceIndex,
                               SourceNextIndex,
                               SourceLigatureInfo,
                               Pattern,
                               PatternLength,
                               PatternIndex,
                               PatternNextIndex,
                               PatternLigatureInfo,
                               Comparer,
                               Options,
                               MatchBothCharsOfExpandedCharInRight:=True)

                        If CompareResult = 0 Then
                            PatternIndex = PatternNextIndex
                            GoTo OneCharMatch
                        End If

                    Else
                        PatternNextIndex = PatternIndex
                        SkipToEndOfExpandedChar(PatternLigatureInfo, PatternLength, PatternNextIndex)
                    End If

                    Range.End = PatternIndex
                    Range.EndLength = PatternNextIndex - PatternIndex + 1

                    ' Store the range end char
                    '
                    If Options = CompareOptions.Ordinal Then
                        RangeEnd = Pattern.Chars(PatternIndex)
                    ElseIf PatternLigatureInfo IsNot Nothing AndAlso PatternLigatureInfo(PatternIndex).Kind = CharKind.ExpandedChar1 Then
                        RangeEnd = PatternLigatureInfo(PatternIndex).CharBeforeExpansion
                        PatternIndex = PatternNextIndex
                    Else
                        RangeEnd = Pattern.Substring(PatternIndex, PatternNextIndex - PatternIndex + 1)
                        PatternIndex = PatternNextIndex
                    End If


                    If CompareChars(RangeStart, RangeEnd, Comparer, Options) > 0 Then
                        PatternError = True
                        Return
                    End If

                    If Not ValidatePatternWithoutMatching AndAlso
                           CompareChars(
                               Source,
                               SourceLength,
                               SourceIndex,
                               SourceNextIndex,
                               SourceLigatureInfo,
                               Pattern,
                               Range.Start + Range.StartLength,
                               Range.Start,
                               Nothing,
                               PatternLigatureInfo,
                               Comparer,
                               Options,
                               UseUnexpandedCharForRight:=True) >= 0 AndAlso
                           CompareChars(
                               Source,
                               SourceLength,
                               SourceIndex,
                               SourceNextIndex,
                               SourceLigatureInfo,
                               Pattern,
                               Range.End + Range.EndLength,
                               Range.End,
                               Nothing,
                               PatternLigatureInfo,
                               Comparer,
                               Options,
                               UseUnexpandedCharForRight:=True) <= 0 Then
                        'Character was within range
OneCharMatch:
                        Debug.Assert(Not ValidatePatternWithoutMatching, "Unexpected string matching when validating pattern string")

                        If SeenNot Then
                            Mismatch = True
                            Return
                        End If

                        Do
                            PatternIndex += 1

                            If PatternIndex >= PatternLength Then
                                PatternError = True
                                Return
                            End If

                        Loop While Pattern.Chars(PatternIndex) <> "]"c AndAlso
                                   Pattern.Chars(PatternIndex) <> ChrW(&HFF3D)

                        SourceIndex = SourceNextIndex
                        Return  'Match
                    End If

                Else
                    ' Single character match
                    '
                    '
                    If Not ValidatePatternWithoutMatching AndAlso
                        CompareChars(
                            Source,
                            SourceLength,
                            SourceIndex,
                            SourceNextIndex,
                            SourceLigatureInfo,
                            Pattern,
                            Range.Start + Range.StartLength,
                            Range.Start,
                            Nothing,
                            PatternLigatureInfo,
                            Comparer,
                            Options,
                            UseUnexpandedCharForRight:=True) = 0 Then

                        GoTo OneCharMatch
                    End If

                    ' No range end for single characters in list
                    '
                    Range.End = -1
                    Range.EndLength = 0

                End If


                If RangeList IsNot Nothing Then
                    RangeList.Add(Range)
                End If

                PatternIndex += 1

                If PatternIndex >= PatternLength Then
                    PatternError = True
                    Return
                End If

                p = Pattern.Chars(PatternIndex)
            Loop

        End Sub

        Private Shared Function ValidateRangePattern _
        (
            ByVal Pattern As String,
            ByVal PatternLength As Integer,
            ByRef PatternIndex As Integer,
            ByVal PatternLigatureInfo As LigatureInfo(),
            ByVal Comparer As CompareInfo,
            ByVal Options As CompareOptions,
            ByRef SeenNot As Boolean,
            ByRef RangeList As List(Of Range)
        ) As Boolean

            Const ValidPatternWithoutMatching As Boolean = True

            Dim PatternError As Boolean

            MatchRange(
                Nothing,
                -1,
                -1,
                Nothing,
                Pattern,
                PatternLength,
                PatternIndex,
                PatternLigatureInfo,
                Nothing,
                Nothing,
                PatternError,
                Comparer,
                Options,
                SeenNot,
                RangeList,
                ValidPatternWithoutMatching)

            Return Not PatternError
        End Function

        Private Enum PatternType
            [STRING]
            EXCLIST
            INCLIST
            DIGIT
            ANYCHAR
            STAR
            NONE
        End Enum

        Private Structure PatternGroup
            Friend PatType As PatternType
            Friend MaxSourceIndex As Integer
            Friend CharCount As Integer

            ' StringPatternStart, StringPatternEnd - there are the indices into the original source string
            ' and are NOT indices into StringPattern.
            '
#If DEBUG Then
            Private m_StringPatternStart As Integer  'For PatternType.[STRING]
            Friend Property StringPatternStart() As Integer
                Get
                    Debug.Assert(PatType = PatternType.STRING, "Unexpected pattern group type")
                    Return m_StringPatternStart
                End Get
                Set(ByVal Value As Integer)
                    Debug.Assert(PatType = PatternType.STRING, "Unexpected pattern group type")
                    m_StringPatternStart = Value
                End Set
            End Property

            Private m_StringPatternEnd As Integer  'For PatternType.[STRING]
            Friend Property StringPatternEnd() As Integer
                Get
                    Debug.Assert(PatType = PatternType.STRING, "Unexpected pattern group type")
                    Return m_StringPatternEnd
                End Get
                Set(ByVal Value As Integer)
                    Debug.Assert(PatType = PatternType.STRING, "Unexpected pattern group type")
                    m_StringPatternEnd = Value
                End Set
            End Property

#Else
            Friend StringPatternStart As Integer
            Friend StringPatternEnd As Integer
#End If

#If DEBUG Then
            Private m_MinSourceIndex As Integer
            Friend Property MinSourceIndex() As Integer
                Get
                    Debug.Assert(PatType = PatternType.STAR OrElse PatType = PatternType.NONE, "Unexpected pattern group type")
                    Return m_MinSourceIndex
                End Get
                Set(ByVal Value As Integer)
                    Debug.Assert(PatType = PatternType.STAR OrElse PatType = PatternType.NONE, "Unexpected pattern group type")
                    m_MinSourceIndex = Value
                End Set
            End Property
#Else
            Friend MinSourceIndex As Integer
#End If

#If DEBUG Then
            Private m_RangeStarts As String()
            Property RangeStarts() As String()
                Get
                    Debug.Assert(PatType = PatternType.EXCLIST OrElse PatType = PatternType.INCLIST, "Unexpected pattern group type")
                    Return m_RangeStarts
                End Get
                Set(ByVal value As String())
                    Debug.Assert(PatType = PatternType.EXCLIST OrElse PatType = PatternType.INCLIST, "Unexpected pattern group type")
                    m_RangeStarts = value
                End Set
            End Property


            Private m_RangeList As List(Of Range)
            Property RangeList() As List(Of Range)
                Get
                    Debug.Assert(PatType = PatternType.EXCLIST OrElse PatType = PatternType.INCLIST, "Unexpected pattern group type")
                    Return m_RangeList
                End Get
                Set(ByVal Value As List(Of Range))
                    Debug.Assert(PatType = PatternType.EXCLIST OrElse PatType = PatternType.INCLIST, "Unexpected pattern group type")
                    m_RangeList = Value
                End Set
            End Property
#Else
            Friend RangeList As List(Of Range)
#End If

            Public StartIndexOfPossibleMatch As Integer
        End Structure

        Private Structure Range
            Friend Start As Integer     'Index into the pattern string
            Friend StartLength As Integer

            Friend [End] As Integer     'Index into the pattern string
            Friend EndLength As Integer
        End Structure

        Private Shared Sub BuildPatternGroups _
        (
            ByVal Source As String,
            ByVal SourceLength As Integer,
            ByRef SourceIndex As Integer,
            ByVal SourceLigatureInfo As LigatureInfo(),
            ByVal Pattern As String,
            ByVal PatternLength As Integer,
            ByRef PatternIndex As Integer,
            ByVal PatternLigatureInfo As LigatureInfo(),
            ByRef PatternError As Boolean,
            ByRef PGIndexForLastAsterisk As Integer,
            ByVal Comparer As CompareInfo,
            ByVal Options As CompareOptions,
            ByRef PatternGroups() As PatternGroup
        )
            ' Pattern groups:
            '
            ' 1. A string of characters not containing a special pattern
            ' character.
            '
            ' 2. Any number of consecutive "?".
            '
            ' 3. Any number of consecutive "#".
            '
            ' 4. A bracketed character list.
            '
            ' 5. Any number of consecutive "*" (collapsed together).
            '
            ' We have a local array that is good for small patterns.
            ' If the pattern gets large, we allocate additional memory.
            '
            ' PG - pattern group

            PatternError = False
            PGIndexForLastAsterisk = 0

            Dim PGIndex As Integer

            Const PGMaxCount As Integer = 16
            PatternGroups = New PatternGroup(PGMaxCount - 1) {}

            Dim PGLast As Integer = PGMaxCount - 1
            Dim PrevPatType As PatternType = PatternType.NONE
            PGIndex = 0

            Do
                ' Increase the size of the Pattern groups array if required
                '
                If PGIndex >= PGLast Then
                    Dim NewPatternGroups(PGLast + PGMaxCount) As PatternGroup
                    PatternGroups.CopyTo(NewPatternGroups, 0)
                    PatternGroups = NewPatternGroups
                    PGLast = PGLast + PGMaxCount
                End If

                Dim p As Char = Pattern.Chars(PatternIndex)

                Select Case p

                    Case "*"c, ChrW(&HFF0A)
                        ' Record the "*" pattern and collapse multiple contiguous "*"'s if possible
                        '
                        If PrevPatType <> PatternType.STAR Then
                            PrevPatType = PatternType.STAR
                            PatternGroups(PGIndex).PatType = PatternType.STAR
                            PGIndexForLastAsterisk = PGIndex
                            PGIndex += 1
                        End If

                    Case "["c, ChrW(&HFF3B)
                        Dim SeenNot As Boolean = False
                        Dim RangeList As New List(Of Range)

                        If Not ValidateRangePattern(Pattern, PatternLength, PatternIndex, PatternLigatureInfo, Comparer, Options, SeenNot, RangeList) Then
                            PatternError = True
                            Return
                        End If

                        ' Ignore empty "[]" and don't build a pattern group for it
                        '
                        If RangeList.Count = 0 Then
                            Exit Select
                        End If

                        If SeenNot Then
                            PrevPatType = PatternType.EXCLIST
                        Else
                            PrevPatType = PatternType.INCLIST
                        End If

                        PatternGroups(PGIndex).PatType = PrevPatType
                        PatternGroups(PGIndex).CharCount = 1
                        PatternGroups(PGIndex).RangeList = RangeList

                        PGIndex += 1

                    Case "#"c, ChrW(&HFF03)

                        If PrevPatType = PatternType.DIGIT Then
                            PatternGroups(PGIndex - 1).CharCount += 1
                        Else
                            PatternGroups(PGIndex).PatType = PatternType.DIGIT
                            PatternGroups(PGIndex).CharCount = 1
                            PGIndex += 1
                            PrevPatType = PatternType.DIGIT
                        End If

                    Case "?"c, ChrW(&HFF1F)

                        If PrevPatType = PatternType.ANYCHAR Then
                            PatternGroups(PGIndex - 1).CharCount += 1
                        Else
                            PatternGroups(PGIndex).PatType = PatternType.ANYCHAR
                            PatternGroups(PGIndex).CharCount = 1
                            PGIndex += 1
                            PrevPatType = PatternType.ANYCHAR
                        End If

                    Case Else

                        Dim StringPatternStart As Integer = PatternIndex

                        Dim StringPatternEnd As Integer = PatternIndex

                        If StringPatternEnd >= PatternLength Then
                            StringPatternEnd = PatternLength - 1
                        End If

                        If PrevPatType = PatternType.STRING Then
                            PatternGroups(PGIndex - 1).CharCount += 1
                            PatternGroups(PGIndex - 1).StringPatternEnd = StringPatternEnd
                        Else
                            PatternGroups(PGIndex).PatType = PatternType.STRING
                            PatternGroups(PGIndex).CharCount = 1
                            PatternGroups(PGIndex).StringPatternStart = StringPatternStart
                            PatternGroups(PGIndex).StringPatternEnd = StringPatternEnd

                            PGIndex += 1
                            PrevPatType = PatternType.STRING
                        End If

                End Select

                PatternIndex += 1

            Loop While PatternIndex < PatternLength

            'Add ending mark
            '
            PatternGroups(PGIndex).PatType = PatternType.NONE
            PatternGroups(PGIndex).MinSourceIndex = SourceLength

            ' Pattern is compiled into an array of Pattern groups.  Walk backward through list to assign max positions.
            '
            Dim MaxPossibleStart As Integer = SourceLength
            Do While PGIndex > 0
                Select Case PatternGroups(PGIndex).PatType
                    Case PatternType.STRING
                        MaxPossibleStart -= PatternGroups(PGIndex).CharCount

                    Case PatternType.DIGIT, PatternType.ANYCHAR
                        MaxPossibleStart -= PatternGroups(PGIndex).CharCount

                    Case PatternType.EXCLIST, PatternType.INCLIST
                        MaxPossibleStart -= 1

                    Case PatternType.STAR, PatternType.NONE
                        'Can start anywhere

                    Case Else
                        Debug.Assert(False, "Unexpected pattern kind")
                End Select

                PatternGroups(PGIndex).MaxSourceIndex = MaxPossibleStart
                PGIndex -= 1
            Loop

        End Sub

        Private Shared Sub MatchAsterisk _
        (
            ByVal Source As String,
            ByVal SourceLength As Integer,
            ByVal SourceIndex As Integer,
            ByVal SourceLigatureInfo As LigatureInfo(),
            ByVal Pattern As String,
            ByVal PatternLength As Integer,
            ByVal PatternIndex As Integer,
            ByVal PattternLigatureInfo As LigatureInfo(),
            ByRef Mismatch As Boolean,
            ByRef PatternError As Boolean,
            ByVal Comparer As CompareInfo,
            ByVal Options As CompareOptions
        )

            Debug.Assert(PatternIndex <= PatternLength AndAlso
                         (Pattern.Chars(PatternIndex) = "*"c OrElse Pattern.Chars(PatternIndex) = ChrW(&HFF0A)),
                            "Like operator - Unexpected asterisk matching")

            Mismatch = False
            PatternError = False

            If PatternIndex >= PatternLength Then
                Return  'Successful match
            End If

            ' We've found a "*" in the pattern that is not at the end.
            ' Now we need to scan ahead in the pattern and compile it
            ' into an array of structs describing each pattern group.
            '

            Dim PatternGroups() As PatternGroup = Nothing
            Dim PGIndex As Integer
            Dim PGIndexForLastAsterisk As Integer

            BuildPatternGroups(
                Source,
                SourceLength,
                SourceIndex,
                SourceLigatureInfo,
                Pattern,
                PatternLength,
                PatternIndex,
                PattternLigatureInfo,
                PatternError,
                PGIndexForLastAsterisk,
                Comparer,
                Options,
                PatternGroups)

            If PatternError Then
                Return
            End If

            Debug.Assert(PatternGroups IsNot Nothing AndAlso
                         PatternGroups.Length > 0 AndAlso
                         PatternGroups(0).PatType = PatternType.STAR, "Pattern parsing failed")

            ' Start the search
            '

            If PatternGroups(PGIndexForLastAsterisk + 1).PatType <> PatternType.NONE Then
                '
                ' Optimize for the "<AnyPattern>*<NonStarPatterns>" case
                ' Helps discard mismatches faster and in some cases, the match are also
                ' faster
                '
                Dim SavedSourceIndex As Integer = SourceIndex
                Dim NumberOfCharsToMatch As Integer

                PGIndex = PGIndexForLastAsterisk + 1
                Do
                    NumberOfCharsToMatch += PatternGroups(PGIndex).CharCount
                    PGIndex += 1
                Loop While PatternGroups(PGIndex).PatType <> PatternType.NONE

                SourceIndex = SourceLength
                SubtractChars(Source, SourceLength, SourceIndex, NumberOfCharsToMatch, SourceLigatureInfo, Options)

                MatchAsterisk(
                    Source,
                    SourceLength,
                    SourceIndex,
                    SourceLigatureInfo,
                    Pattern,
                    PattternLigatureInfo,
                    PatternGroups,
                    PGIndexForLastAsterisk,
                    Mismatch,
                    PatternError,
                    Comparer,
                    Options)

                If PatternError OrElse Mismatch Then
                    Return
                End If

                SourceLength = PatternGroups(PGIndexForLastAsterisk + 1).StartIndexOfPossibleMatch

                If SourceLength <= 0 Then
                    Return
                End If

                ' Move the end marker to just after the last asterisk because everything afterwards have been
                ' matched successfully.
                '
                Debug.Assert(PatternGroups(PGIndex).PatType = PatternType.NONE, "Unexpected pattern end")
                PatternGroups(PGIndex).MaxSourceIndex = SourceLength
                PatternGroups(PGIndex).MinSourceIndex = SourceLength
                PatternGroups(PGIndex).StartIndexOfPossibleMatch = 0
                PatternGroups(PGIndexForLastAsterisk + 1) = PatternGroups(PGIndex)

                ' Reset the pattern group corresponding to the last asterisk because it needs to be reused in
                ' the next phase of matching
                '
                PatternGroups(PGIndexForLastAsterisk).MinSourceIndex = 0
                PatternGroups(PGIndexForLastAsterisk).StartIndexOfPossibleMatch = 0

                PGIndex = PGIndexForLastAsterisk + 1
                Dim MaxPossibleStart As Integer = SourceLength
                Do While PGIndex > 0
                    Select Case PatternGroups(PGIndex).PatType
                        Case PatternType.STRING
                            MaxPossibleStart -= PatternGroups(PGIndex).CharCount

                        Case PatternType.DIGIT, PatternType.ANYCHAR
                            MaxPossibleStart -= PatternGroups(PGIndex).CharCount

                        Case PatternType.EXCLIST, PatternType.INCLIST
                            MaxPossibleStart -= 1

                        Case PatternType.STAR, PatternType.NONE
                            'Can start anywhere

                        Case Else
                            Debug.Assert(False, "Unexpected pattern kind")
                    End Select

                    PatternGroups(PGIndex).MaxSourceIndex = MaxPossibleStart
                    PGIndex -= 1
                Loop

                SourceIndex = SavedSourceIndex
            End If

            MatchAsterisk(
                Source,
                SourceLength,
                SourceIndex,
                SourceLigatureInfo,
                Pattern,
                PattternLigatureInfo,
                PatternGroups,
                0,
                Mismatch,
                PatternError,
                Comparer,
                Options)
        End Sub

        Private Shared Sub MatchAsterisk _
        (
            ByVal Source As String,
            ByVal SourceLength As Integer,
            ByVal SourceIndex As Integer,
            ByVal SourceLigatureInfo As LigatureInfo(),
            ByVal Pattern As String,
            ByVal PatternLigatureInfo As LigatureInfo(),
            ByVal PatternGroups() As PatternGroup,
            ByVal PGIndex As Integer,
            ByRef Mismatch As Boolean,
            ByRef PatternError As Boolean,
            ByVal Comparer As CompareInfo,
            ByVal Options As CompareOptions
        )

            Dim PGPrevMismatchIndex As Integer = PGIndex
            Dim PrevMismatchSourceIndex As Integer = SourceIndex
            Dim PGSaved As Integer = -1
            Dim PGRestartAsteriskIndex As Integer = -1

            Debug.Assert(PatternGroups(PGIndex).PatType = PatternType.STAR, "Unexpected start of pattern groups list")

            PatternGroups(PGIndex).MinSourceIndex = SourceIndex
            PatternGroups(PGIndex).StartIndexOfPossibleMatch = SourceIndex
            PGIndex += 1

            Do
                Dim PGCurrent As PatternGroup = PatternGroups(PGIndex)

                Select Case PGCurrent.PatType

                    Case PatternType.STRING
MatchString:
                        If SourceIndex > PGCurrent.MaxSourceIndex Then
                            Mismatch = True
                            Return
                        End If

                        PatternGroups(PGIndex).StartIndexOfPossibleMatch = SourceIndex

                        Dim StringPatternIndex As Integer = PGCurrent.StringPatternStart
                        Dim SourceSecondCharIndex As Integer = 0
                        Dim SourceMatchIndex As Integer = SourceIndex
                        Dim FirstIteration As Boolean = True

                        Do
                            Dim CompareResult As Integer =
                                CompareChars(
                                    Source,
                                    SourceLength,
                                    SourceMatchIndex,
                                    SourceMatchIndex,
                                    SourceLigatureInfo,
                                    Pattern,
                                    PGCurrent.StringPatternEnd + 1,
                                    StringPatternIndex,
                                    StringPatternIndex,
                                    PatternLigatureInfo,
                                    Comparer,
                                    Options)

                            If FirstIteration Then
                                FirstIteration = False
                                SourceSecondCharIndex = SourceMatchIndex + 1
                            End If

                            If CompareResult <> 0 Then
                                SourceIndex = SourceSecondCharIndex
                                PGPrevMismatchIndex = PGIndex - 1
                                PrevMismatchSourceIndex = SourceIndex
                                GoTo MatchString
                            End If

                            StringPatternIndex += 1
                            SourceMatchIndex += 1

                            If StringPatternIndex > PGCurrent.StringPatternEnd Then
                                SourceIndex = SourceMatchIndex
                                Exit Select
                            End If

                            If SourceMatchIndex >= SourceLength Then
                                Mismatch = True
                                Return
                            End If

                        Loop

                    Case PatternType.DIGIT
MatchDigits:
                        If SourceIndex > PGCurrent.MaxSourceIndex Then
                            Mismatch = True
                            Return
                        End If

                        PatternGroups(PGIndex).StartIndexOfPossibleMatch = SourceIndex

                        For i As Integer = 1 To PGCurrent.CharCount

                            Dim c As Char = Source.Chars(SourceIndex)
                            SourceIndex += 1

                            If Not Char.IsDigit(c) Then
                                PGPrevMismatchIndex = PGIndex - 1
                                PrevMismatchSourceIndex = SourceIndex
                                GoTo MatchDigits
                            End If

                        Next

                        'Match

                    Case PatternType.EXCLIST, PatternType.INCLIST
MatchList:
                        If SourceIndex > PGCurrent.MaxSourceIndex Then
                            Mismatch = True
                            Return
                        End If

                        PatternGroups(PGIndex).StartIndexOfPossibleMatch = SourceIndex

                        If Not MatchRangeAfterAsterisk(
                                    Source,
                                    SourceLength,
                                    SourceIndex,
                                    SourceLigatureInfo,
                                    Pattern,
                                    PatternLigatureInfo,
                                    PGCurrent,
                                    Comparer,
                                    Options) Then

                            PGPrevMismatchIndex = PGIndex - 1
                            PrevMismatchSourceIndex = SourceIndex
                            GoTo MatchList
                        End If

                        'Match

                    Case PatternType.ANYCHAR

                        If SourceIndex > PGCurrent.MaxSourceIndex Then
                            Mismatch = True
                            Return
                        End If

                        PatternGroups(PGIndex).StartIndexOfPossibleMatch = SourceIndex

                        For i As Integer = 1 To PGCurrent.CharCount
                            If SourceIndex >= SourceLength Then
                                Mismatch = True
                                Return
                            End If

                            SkipToEndOfExpandedChar(SourceLigatureInfo, SourceLength, SourceIndex)
                            SourceIndex += 1
                        Next

                    Case PatternType.NONE

                        PatternGroups(PGIndex).StartIndexOfPossibleMatch = PGCurrent.MaxSourceIndex

                        Debug.Assert(SourceIndex <= PGCurrent.MaxSourceIndex, "Pattern matching lost")

                        If SourceIndex < PGCurrent.MaxSourceIndex Then
                            PGPrevMismatchIndex = PGIndex - 1
                            PrevMismatchSourceIndex = PGCurrent.MaxSourceIndex
                        End If

                        If PatternGroups(PGPrevMismatchIndex).PatType <> PatternType.STAR AndAlso
                           PatternGroups(PGPrevMismatchIndex).PatType <> PatternType.NONE Then
                            GoTo ShiftPosition
                        End If

                        Return  'Match

                    Case PatternType.STAR

                        PatternGroups(PGIndex).StartIndexOfPossibleMatch = SourceIndex
                        PGCurrent.MinSourceIndex = SourceIndex

                        ' See if we've moved our starting point.  If so, we
                        ' back up from the last place it moved, assigning a
                        ' new minimum position in the source string.  Then
                        ' we can start the search over from the new minimum
                        ' position.
                        '

                        Debug.Assert(PatternGroups(PGPrevMismatchIndex).PatType <> PatternType.NONE, "Bad previous mismatch index")

                        If PatternGroups(PGPrevMismatchIndex).PatType <> PatternType.STAR Then

                            If SourceIndex > PGCurrent.MaxSourceIndex Then
                                Mismatch = True
                                Return
                            End If

ShiftPosition:
                            PGSaved = PGIndex
                            SourceIndex = PrevMismatchSourceIndex
                            PGIndex = PGPrevMismatchIndex

                            Do
                                SubtractChars(Source, SourceLength, SourceIndex, PatternGroups(PGIndex).CharCount, SourceLigatureInfo, Options)
                                PGIndex -= 1
                            Loop While PatternGroups(PGIndex).PatType <> PatternType.STAR

                            SourceIndex = Math.Max(SourceIndex, PatternGroups(PGIndex).MinSourceIndex + 1)
                            PatternGroups(PGIndex).MinSourceIndex = SourceIndex
                            PGRestartAsteriskIndex = PGIndex
                        End If

                        PGIndex += 1

                        Continue Do

                End Select

                If PGIndex = PGPrevMismatchIndex Then
                    ' Reached a point where we've matched before.

                    If SourceIndex = PrevMismatchSourceIndex Then
                        ' Reached a point where we've matched before.  Jump ahead
                        ' to where that left off.
                        '
                        SourceIndex = PatternGroups(PGSaved).MinSourceIndex
                        PGIndex = PGSaved
                        PGPrevMismatchIndex = PGSaved

                    ElseIf SourceIndex < PrevMismatchSourceIndex Then
                        ' In certain cases involving ligatures/modifiers, the source
                        ' index could be moved too far back and thus result in this
                        ' scenario.
                        '
                        PatternGroups(PGRestartAsteriskIndex).MinSourceIndex += 1
                        SourceIndex = PatternGroups(PGRestartAsteriskIndex).MinSourceIndex
                        PGIndex = PGRestartAsteriskIndex + 1

                    Else 'SourceIndex > PrevMismatchSourceIndex
                        ' In certain cases involving ligatures/modifiers, more source
                        ' chars than the number of chars between the previous match
                        ' corresponding to a "*" and the start of match for the pattern
                        ' groups where PGIndex > PGPrevMismatchIndex may be matched
                        ' against the pattern groups between the "*" and PGPrevMismatchIndex
                        ' and thus result in this scenario.
                        '
                        PGIndex += 1
                        PGPrevMismatchIndex = PGRestartAsteriskIndex
                    End If
                Else
                    PGIndex += 1
                End If

            Loop
        End Sub

        Private Shared Function MatchRangeAfterAsterisk _
        (
            ByVal Source As String,
            ByVal SourceLength As Integer,
            ByRef SourceIndex As Integer,
            ByVal SourceLigatureInfo As LigatureInfo(),
            ByVal Pattern As String,
            ByVal PatternLigatureInfo As LigatureInfo(),
            ByVal PG As PatternGroup,
            ByVal Comparer As CompareInfo,
            ByVal Options As CompareOptions
        ) As Boolean

            Debug.Assert(PG.PatType = PatternType.EXCLIST OrElse PG.PatType = PatternType.INCLIST, "Unexpected pattern group")

            Dim RangeList As List(Of Range) = PG.RangeList

            'empty [] match can be ignored
            '
            Debug.Assert(RangeList IsNot Nothing AndAlso RangeList.Count > 0, "Empty RangeList unexpected")

            Dim SourceNextIndex As Integer = SourceIndex
            Dim Match As Boolean = False

            For Each Range As Range In RangeList
                Debug.Assert(Range.Start >= 0, "NULL Range start unexpected")
                Dim CompareResultEnd As Integer = 1
                Dim CompareResultStart As Integer

                If PatternLigatureInfo IsNot Nothing AndAlso PatternLigatureInfo(Range.Start).Kind = CharKind.ExpandedChar1 Then
                    CompareResultStart =
                        CompareChars(
                            Source,
                            SourceLength,
                            SourceIndex,
                            SourceNextIndex,
                            SourceLigatureInfo,
                            Pattern,
                            Range.Start + Range.StartLength,
                            Range.Start,
                            0,
                            PatternLigatureInfo,
                            Comparer,
                            Options,
                            MatchBothCharsOfExpandedCharInRight:=True)

                    If CompareResultStart = 0 Then
                        Match = True
                        Exit For
                    End If
                End If

                CompareResultStart =
                    CompareChars(
                        Source,
                        SourceLength,
                        SourceIndex,
                        SourceNextIndex,
                        SourceLigatureInfo,
                        Pattern,
                        Range.Start + Range.StartLength,
                        Range.Start,
                        0,
                        PatternLigatureInfo,
                        Comparer,
                        Options,
                        UseUnexpandedCharForRight:=True)

                If CompareResultStart > 0 AndAlso Range.End >= 0 Then
                    CompareResultEnd =
                        CompareChars(
                            Source,
                            SourceLength,
                            SourceIndex,
                            SourceNextIndex,
                            SourceLigatureInfo,
                            Pattern,
                            Range.End + Range.EndLength,
                            Range.End,
                            0,
                            PatternLigatureInfo,
                            Comparer,
                            Options,
                            UseUnexpandedCharForRight:=True)
                End If

                If CompareResultStart = 0 OrElse
                   (CompareResultStart > 0 AndAlso CompareResultEnd <= 0) Then
                    Match = True
                    Exit For
                End If

            Next

            If PG.PatType = PatternType.EXCLIST Then
                Match = Not Match
            End If

            SourceIndex = SourceNextIndex + 1
            Return Match
        End Function

        Private Shared Sub SubtractChars _
        (
            ByVal Input As String,
            ByVal InputLength As Integer,
            ByRef Current As Integer,
            ByVal CharsToSubtract As Integer,
            ByVal InputLigatureInfo As LigatureInfo(),
            ByVal Options As CompareOptions
        )

            If Options = CompareOptions.Ordinal Then
                Current -= CharsToSubtract
                If Current < 0 Then Current = 0
                Return
            End If

            For i As Integer = 1 To CharsToSubtract
                SubtractOneCharInTextCompareMode(Input, InputLength, Current, InputLigatureInfo, Options)

                If Current < 0 Then
                    Current = 0
                    Exit For
                End If
            Next

        End Sub

        Private Shared Sub SubtractOneCharInTextCompareMode _
        (
            ByVal Input As String,
            ByVal InputLength As Integer,
            ByRef Current As Integer,
            ByVal InputLigatureInfo As LigatureInfo(),
            ByVal Options As CompareOptions
        )
            Debug.Assert(Options <> CompareOptions.Ordinal, "This method should not be invoked in Option compare binary mode")

            If Current >= InputLength Then
                Current -= 1
                Return
            End If

            If InputLigatureInfo IsNot Nothing AndAlso
               InputLigatureInfo(Current).Kind = CharKind.ExpandedChar2 Then
                Current -= 2
            Else
                Current -= 1
            End If

        End Sub

    End Class

End Namespace

