Imports System.Globalization

Namespace Data

    ''' <summary>
    ''' Represents a set of data for a filter.
    ''' </summary>
    ''' <remarks></remarks>
    Public Class FilterInfo

        ' Data
        Private _pattern As PatternInfo
        Private _isCaseSensitive As Boolean = False

        ''' <summary>
        ''' Static wildcard definition.
        ''' </summary>
        ''' <remarks></remarks>
        Public Shared ReadOnly Wildcard As String = "*"
        Private Shared ReadOnly Culture As CultureInfo = CultureInfo.InvariantCulture

#Region "Constructors"

        ''' <summary>
        ''' Initializes a default instance of the <c>FilterInfo</c> class.
        ''' </summary>
        Public Sub New()
        End Sub

        ''' <summary>
        ''' Initializes a default instance of the <c>FilterInfo</c> class with the specified property name.
        ''' </summary>
        ''' <param name="propertyName">The property name on which to filter.</param>
        Public Sub New(ByVal propertyName As String)
            Me.New(propertyName, String.Empty)
        End Sub

        ''' <summary>
        ''' Initializes a default instance of the <c>FilterInfo</c> class with the specified property name and expression.
        ''' </summary>
        ''' <param name="propertyName">The property name on which to filter.</param>
        ''' <param name="expression">The expression string.</param>
        Public Sub New(ByVal propertyName As String, ByVal expression As String)
            Me.New(propertyName, expression, FilterComparisonMode.Equal)
        End Sub

        ''' <summary>
        ''' Initializes a default instance of the <c>FilterInfo</c> 
        ''' class with the specified property name, expression, and 
        ''' <c>FilterComparisonMode</c>.
        ''' </summary>
        ''' <param name="propertyName">The property name on which to filter.</param>
        ''' <param name="expression">The expression string.</param>
        ''' <param name="comparisonMode">The <c>FilterComparisonMode</c> to be used.</param>
        ''' <remarks></remarks>
        Public Sub New(ByVal propertyName As String, ByVal expression As String, ByVal comparisonMode As FilterComparisonMode)
            Me.New(propertyName, expression, comparisonMode, FilterConjunctionMode.AndFilter)
        End Sub

        ''' <summary>
        ''' Initializes a default instance of the <c>FilterInfo</c> 
        ''' class with the specified property name, expression,  
        ''' <c>FilterComparisonMode</c>, and <c>FilterConjunctionMode</c>.
        ''' </summary>
        ''' <param name="propertyName">The property name on which to filter.</param>
        ''' <param name="expression">The expression string.</param>
        ''' <param name="comparisonMode">The <c>FilterComparisonMode</c> to be used.</param>
        ''' <param name="conjunctionMode">The <c>FilterConjunctionMode</c> to be used.</param>
        ''' <remarks></remarks>
        Public Sub New(ByVal propertyName As String, ByVal expression As String, ByVal comparisonMode As FilterComparisonMode, _
                       ByVal conjunctionMode As FilterConjunctionMode)
            Me.PropertyName = propertyName
            Me.ComparisonMode = comparisonMode
            Me.ConjunctionMode = conjunctionMode
            SetPattern(expression)
        End Sub

#End Region

#Region "Properties"

        ''' <summary>
        ''' Gets or sets the filter expression string.
        ''' </summary>
        ''' <value>The filter expression string.</value>
        ''' <returns>The filter expression string.</returns>
        Public Property Expression() As String
            Get
                If Pattern IsNot Nothing Then _
                    Return Pattern.Expression
                Return String.Empty
            End Get
            Set(ByVal value As String)
                If Pattern.Expression <> value Then
                    SetPattern(value)
                End If
            End Set
        End Property

        ''' <summary>
        ''' Gets or sets a value indicating whether the pattern matching is case sensitive.
        ''' </summary>
        ''' <returns>True if the pattern matching is case sensitive; otherwise, false.</returns>
        ''' <remarks></remarks>
        Public Property IsCaseSensitive() As Boolean
            Get
                Return Me._isCaseSensitive
            End Get
            Set(ByVal value As Boolean)
                Me._isCaseSensitive = value
            End Set
        End Property

        ''' <summary>
        ''' Gets the internal <c>PatternInfo</c> object.
        ''' </summary>
        Private ReadOnly Property Pattern() As PatternInfo
            Get
                Return Me._pattern
            End Get
        End Property

        Private _propertyName As String

        ''' <summary>
        ''' Gets or sets the filter property name.
        ''' </summary>
        ''' <value>The filter property name.</value>
        ''' <returns>The filter property name.</returns>
        Public Property PropertyName() As String
            Get
                Return Me._propertyName
            End Get
            Set(ByVal value As String)
                Me._propertyName = value
            End Set
        End Property

        Private _comparisonMode As FilterComparisonMode

        ''' <summary>
        ''' Gets or sets the <c>FilterComparisonMode</c> for this <c>FilterInfo</c>.
        ''' </summary>
        ''' <returns>The <c>FilterComparisonMode</c> for this <c>FilterInfo</c>.</returns>
        Public Property ComparisonMode() As FilterComparisonMode
            Get
                Return Me._comparisonMode
            End Get
            Set(ByVal value As FilterComparisonMode)
                Me._comparisonMode = value
            End Set
        End Property

        Private _conjunctionMode As FilterConjunctionMode

        ''' <summary>
        ''' Gets or sets the <c>FilterConjunctionMode</c> for this <c>FilterInfo</c>.
        ''' </summary>
        ''' <returns>The <c>FilterConjunctionMode</c> for this <c>FilterInfo</c>.</returns>
        Public Property ConjunctionMode() As FilterConjunctionMode
            Get
                Return Me._conjunctionMode
            End Get
            Set(ByVal value As FilterConjunctionMode)
                Me._conjunctionMode = value
            End Set
        End Property

        ''' <summary>
        ''' Gets a value indicating whether the current filter expression contains a wildcard character.
        ''' </summary>
        ''' <returns>True if the current filter expression contains a wildcard character; otherwise, false.</returns>
        ''' <remarks></remarks>
        Public ReadOnly Property HasWildcard As Boolean
            Get
                If Me.Pattern IsNot Nothing Then _
                    Return Me.Pattern.HasWildcard
                Return False
            End Get
        End Property

#End Region

#Region "Match Methods"

        ''' <summary>
        ''' Returns a value indicating whether the specified string 
        ''' is a match to the filter expression with the comparison 
        ''' mode.  This procedure utilizes the IsMatch() procedure 
        ''' for equality and inequality comparisons.  All other 
        ''' comparison types are performed directly.
        ''' </summary>
        ''' <param name="x">The value string to match.</param>
        ''' <returns>True if the specified string matches the filter expression 
        ''' with the current comparison mode; otherwise, false.</returns>
        ''' <remarks></remarks>
        Public Function IsComparisonMatch(ByVal x As String) As Boolean
            If ComparisonMode = FilterComparisonMode.Equal Then
                Return IsMatch(x)
            ElseIf ComparisonMode = FilterComparisonMode.NotEqual Then
                Return Not (IsMatch(x))
            ElseIf ComparisonMode = FilterComparisonMode.GreaterThan Then
                If IsNumeric(x) AndAlso IsNumeric(Expression) Then _
                    Return (CDbl(x) > CDbl(Expression))
                Return (x > Expression)
            ElseIf ComparisonMode = FilterComparisonMode.GreaterThanOrEqual Then
                If IsNumeric(x) AndAlso IsNumeric(Expression) Then _
                    Return (CDbl(x) >= CDbl(Expression))
                Return (x >= Expression)
            ElseIf ComparisonMode = FilterComparisonMode.LessThan Then
                If IsNumeric(x) AndAlso IsNumeric(Expression) Then _
                    Return (CDbl(x) < CDbl(Expression))
                Return (x < Expression)
            ElseIf ComparisonMode = FilterComparisonMode.LessThanOrEqual Then
                If IsNumeric(x) AndAlso IsNumeric(Expression) Then _
                    Return (CDbl(x) <= CDbl(Expression))
                Return (x <= Expression)
            End If
            Return False
        End Function

        ''' <summary>
        ''' Returns a value indicating whether the specified string is a match to the filter expression.
        ''' </summary>
        ''' <param name="x">The value string to match.</param>
        ''' <returns>True if the specified string matches the filter expression; otherwise, false.</returns>
        ''' <remarks></remarks>
        Public Function IsMatch(ByVal x As String) As Boolean
            ' Get local pattern values
            Dim lPattern As String = Me.Pattern.LeftPattern
            Dim mPattern As String = Me.Pattern.MiddlePattern
            Dim rPattern As String = Me.Pattern.RightPattern

            ' Set case insensitive
            If Not (IsCaseSensitive) Then
                x = x.ToUpper(FilterInfo.Culture)
                lPattern = lPattern.ToUpper(FilterInfo.Culture)
                mPattern = mPattern.ToUpper(FilterInfo.Culture)
                rPattern = rPattern.ToUpper(FilterInfo.Culture)
            End If

            ' If no wildcard specified.
            If Not (Me.Pattern.HasWildcard) Then
                Return IsPatternMatch(lPattern, x)
            ElseIf Pattern.HasWildcard AndAlso Pattern.Expression.Length = 1 Then
                Return True
            Else    ' Match all patterns
                ' Get comparison mode
                Dim comparison = Me.ComparisonMode
                If Me.ComparisonMode = FilterComparisonMode.NotEqual Then _
                    comparison = FilterComparisonMode.Equal

                ' Set flag
                Dim flag As Boolean = (IsPatternMatch(comparison, lPattern, Left(x, lPattern.Length)) AndAlso _
                                       IsPatternMatch(comparison, rPattern, Right(x, rPattern.Length)))

                ' If middle pattern is specified...
                If Not (String.IsNullOrEmpty(mPattern)) Then
                    Dim mExp As String = x
                    If x.Length >= (lPattern.Length + mPattern.Length + rPattern.Length) Then _
                        mExp = Mid(x, lPattern.Length + 1, (x.Length - rPattern.Length) - (lPattern.Length))

                    ' Test for equality
                    If comparison = FilterComparisonMode.Equal Then
                        flag = (flag AndAlso mExp.Contains(mPattern))
                    End If
                End If

                ' Test for inequality and reverse flag.
                If Me.ComparisonMode = FilterComparisonMode.NotEqual Then _
                    Return Not (flag)
                Return flag
            End If
        End Function

        Protected Overridable Function IsPatternMatch(ByVal pattern As String, ByVal test As String) As Boolean
            Return IsPatternMatch(Me.ComparisonMode, pattern, test)
        End Function

        Private Shared Function IsPatternMatch(ByVal comparisonMode As FilterComparisonMode, ByVal pattern As String, ByVal test As String) As Boolean
            Select Case comparisonMode
                Case FilterComparisonMode.Equal
                    Return (test = pattern)
                Case FilterComparisonMode.GreaterThan
                    Return (test > pattern)
                Case FilterComparisonMode.GreaterThanOrEqual
                    Return (test >= pattern)
                Case FilterComparisonMode.LessThan
                    Return (test < pattern)
                Case FilterComparisonMode.LessThanOrEqual
                    Return (test <= pattern)
                Case FilterComparisonMode.NotEqual
                    Return (test <> pattern)
                Case Else
                    Return False
            End Select
        End Function

#End Region

#Region "Helpers"

        Protected Overridable Sub SetPattern(ByVal expression As String)
            Me._pattern = New PatternInfo(expression)
        End Sub

        ''' <summary>
        ''' Retrieves the FilterInfo.Expression as a numeric value.
        ''' </summary>
        ''' <param name="result">The FilterInfo.Expression expressed as a System.Double value.</param>
        ''' <returns>True if the FilterInfo.Expression value can be converted 
        ''' into a numeric value; otherwise false.</returns>
        ''' <remarks></remarks>
        Public Function TryGetNumeric(ByRef result As Double) As Boolean
            If IsNumeric(Me.Expression) Then
                result = CDbl(Me.Expression)
                Return True
            End If
            Return False
        End Function

#End Region

#Region "PatternInfo Class"

        ''' <summary>
        ''' Represents pattern data for a simple wildcard expression.
        ''' </summary>
        ''' <remarks></remarks>
        Private Class PatternInfo
            ' Data
            Private _expression As String
            Private _leftPattern As String
            Private _middlePattern As String
            Private _rightPattern As String
            Private _wildcardPosition As Integer
            Private _hasWildcard As Boolean

            Public Sub New(ByVal expression As String)
                SetPattern(expression)
            End Sub

            Public ReadOnly Property Expression() As String
                Get
                    Return Me._expression
                End Get
            End Property

            Public ReadOnly Property HasWildcard() As Boolean
                Get
                    Return Me._hasWildcard
                End Get
            End Property

            Public ReadOnly Property LeftPattern() As String
                Get
                    Return Me._leftPattern
                End Get
            End Property

            Public ReadOnly Property MiddlePattern() As String
                Get
                    Return Me._middlePattern
                End Get
            End Property

            Public ReadOnly Property RightPattern() As String
                Get
                    Return Me._rightPattern
                End Get
            End Property

            ''' <summary>
            ''' Gets the position of the first wildcard in the string.
            ''' </summary>
            Public ReadOnly Property WildcardPosition() As Integer
                Get
                    Return Me._wildcardPosition
                End Get
            End Property

            Public Sub SetPattern(ByVal expression As String)
                Me._expression = expression
                ResetPattern()

                ' Get wildcard position
                Me._wildcardPosition = InStr(Me.Expression, FilterInfo.Wildcard)
                If Me._wildcardPosition > 0 Then _
                    Me._hasWildcard = True

                ' Split expression and set patterns
                Dim exp() As String = Split(Me.Expression, FilterInfo.Wildcard)
                If exp.Length = 1 Then
                    Me._leftPattern = exp(0)
                ElseIf exp.Length = 2 Then
                    Me._leftPattern = exp(0)
                    Me._rightPattern = exp(1)
                ElseIf exp.Length = 3 Then
                    Me._leftPattern = exp(0)
                    Me._middlePattern = exp(1)
                    Me._rightPattern = exp(2)
                End If
            End Sub

            Private Sub ResetPattern()
                Me._leftPattern = String.Empty
                Me._middlePattern = String.Empty
                Me._rightPattern = String.Empty
                Me._wildcardPosition = 0
                Me._hasWildcard = False
            End Sub

        End Class

#End Region

#Region "Overrides Equals() Method"

        Public Overrides Function Equals(ByVal obj As Object) As Boolean
            If TypeOf obj Is FilterInfo Then _
                Return MyBase.Equals(obj)
            Return False
        End Function

        Public Shared Function IsEquivalent(ByVal obj1 As FilterInfo, ByVal obj2 As FilterInfo) As Boolean
            Return (obj1.PropertyName = obj2.PropertyName AndAlso obj1.Expression = obj2.Expression)
        End Function

#End Region

    End Class

End Namespace