Imports Microsoft.VisualBasic
Imports System.IO
Imports BaseClasses.Data
Imports Ciloci.Flee
Imports BaseClasses.Utils

Namespace Persons.Data

    ''' <summary>
    ''' The BaseFormulaEvaluator class evaluates a formula passed to the Evaluate function.
    ''' </summary>
    ''' <remarks></remarks>
    Public Class BaseFormulaEvaluator
        ''' <summary>
        ''' Evaluator class that actually evaluates the formula.
        ''' This is available as a public property, so additional options
        ''' can be added to the evaluator from the calling functions.
        ''' </summary>
        Protected _evaluator As ExpressionContext
        Public ReadOnly Property Evaluator() As ExpressionContext
            Get
                Return _evaluator
            End Get
        End Property

        ''' <summary>
        ''' The Variables collection allows the passing of the variables to the Evaluator
        ''' </summary>
        Public ReadOnly Property Variables() As Ciloci.Flee.VariableCollection
            Get
                Return Evaluator.Variables
            End Get
        End Property

        ''' <summary>
        ''' DataSource object from which each of the variables are
        ''' determined.  This allows direct referencing of the
        ''' fields in the DataSource.  For example, if the DataSource
        ''' is an Order_Details record, then the formula can use something like:
        '''     = UnitPrice * Quantity * (1 - Discount)
        ''' to calculate the Extended Price.
        ''' </summary>
        Private _dataSource As BaseRecord = Nothing
        Public Property DataSource() As BaseRecord
            Get
                Return _dataSource
            End Get
            Set(ByVal value As BaseRecord)
                _dataSource = value
            End Set
        End Property



        ''' <summary>
        ''' Create a new evaluator and prepare for evaluation.
        ''' </summary>
        Public Sub New(Optional ByVal mainObj As Object = Nothing)

            ' pass mainObj to contructor so that formula like = mainObj.<functionName> can be evaluated
            If mainObj Is Nothing Then
                _evaluator = New ExpressionContext()
            Else
                _evaluator = New ExpressionContext(mainObj)
            End If


            ' The order of adding types is important.  First we add our own
            ' formula functions, followed by the generic types.

            Evaluator.Imports.AddType(GetType(BaseFormulaUtils))

            ' ADVANCED.  For advanced usage, generic types can also be imported into
            ' the formula evaluator.  This is done by adding types of some generic types
            ' such as Math, DateTime, Convert, and String.  For example, if you add the
            ' Convert type, you can then use Convert.ToDecimal("string").  The second
            ' parameter to the AddType is the namespace that will be used in the formula.
            ' These functions expect a certain type.  For example, Math functions expect
            ' a Double for the most part.  If you pass a string, they will throw an exception.
            ' As such, we have written separate functions in FormulaUtils that are more
            ' loosely-typed than the standard libraries available here. 
            ' Examples:
            Evaluator.Imports.AddType(GetType(Math), "Math")
            Evaluator.Imports.AddType(GetType(DateTime), "DateTime")
            Evaluator.Imports.AddType(GetType(Convert), "Convert")
            Evaluator.Imports.AddType(GetType(String), "String")

            ' We want a loosely-typed evaluation language - so do not
            ' type-check any variables.
            Evaluator.Options.Checked = False

            ' Our policy is to always treat real numbers as Decimal instead of
            ' Double or Single to make it consistent across the entire
            ' evaluator.
            Evaluator.Options.RealLiteralDataType = RealLiteralDataType.Decimal

            ' The variable event handler handles the variables based on the DataSource.
            AddHandler _evaluator.Variables.ResolveVariableType, AddressOf variables_ResolveVariableType
            AddHandler _evaluator.Variables.ResolveVariableValue, AddressOf variables_ResolveVariableValue
        End Sub



        ''' <summary>
        ''' Evaluate the expression passed in as the string
        ''' </summary>
        ''' <param name="expression">The input whose absolute value is to be found.</param>
        ''' <returns>The result of the evaluation. Can we be any data type including string, datetime, decimal, etc.</returns>
        Public Overridable Function Evaluate(ByVal expression As String) As Object
            If IsNothing(expression) Then Return Nothing
			If expression = "" Then Return ""

            ' Strip of the = in the front of the forumula - the Expression evaluator
            ' does not need it.  Also, make sure to trim the result to remove any
            ' spaces in the front and the back.
            expression = expression.TrimStart(New Char() {"="c, " "c}).Trim()


            ' If there are any exceptions when parsing or evaluating, they are
            ' thrown so that the end user can see the error.  As such, there is no
            ' Try-Catch block here.
            Try
                Dim eDynamic As IDynamicExpression = _evaluator.CompileDynamic(expression)
                Return eDynamic.Evaluate()
            Catch ex As Exception
                Return "ERROR: " & ex.Message
            End Try
        End Function


        ''' <summary>
        ''' Return the type of the given variable if it exists in the data source.
        ''' If the Return Type is Nothing, the evaluator assumes this is an invalid
        ''' variable and tries other methods to get its value.
        ''' </summary>
        ''' <param name="sender">The sender that sent this event.</param>
        ''' <param name="e">The event argument.  Set e.VariableType.</param>
        Protected Sub variables_ResolveVariableType(ByVal sender As Object, ByVal e As ResolveVariableTypeEventArgs)
            Dim col As BaseColumn

            ' Returning Nothing indicates that we do not recognize this variable.
            e.VariableType = Nothing

            ' If no DataSource was set, we do not have variables that we can use
            ' directly.
            If IsNothing(DataSource) Then Return

            Try
                ' Find a column in the datasource using a variable name.
                col = DataSource.TableAccess.TableDefinition.ColumnList.GetByCodeName(e.VariableName)
                If IsNothing(col) Then
                    ' if the variable name ended with "DefaultValue", remmove it and then try to get the column name again.
                    If BaseClasses.Utils.InvariantLCase(e.VariableName).EndsWith("defaultvalue") Then
                        col = DataSource.TableAccess.TableDefinition.ColumnList.GetByCodeName(e.VariableName.Substring(0, e.VariableName.Length - 12))
                    End If
                End If

                If IsNothing(col) Then
                    Return
                End If

                Select Case col.ColumnType
                    Case BaseColumn.ColumnTypes.Number, _
                        BaseColumn.ColumnTypes.Percentage, _
                         BaseColumn.ColumnTypes.Star
                        ' By default, all our internal data types use Decimal.
                        ' This may result in problems when using the Math library
                        ' but it reduces the problem by allowing us to loosely-type
                        ' all data source properties.
                        e.VariableType = GetType(Decimal)

                    Case BaseColumn.ColumnTypes.Currency
                        ' Convert currency into decimal to allow easier use in formulas
                        e.VariableType = GetType(Decimal)

                    Case BaseColumn.ColumnTypes.Boolean
                        ' Boolean data types are maintained as Boolean and not
                        ' converted to Integer as the Binary data type is.
                        e.VariableType = GetType(Boolean)

                    Case BaseColumn.ColumnTypes.Credit_Card_Date, _
                        BaseColumn.ColumnTypes.Date
                        ' Use DateTme even for Credit Card Date.
                        e.VariableType = GetType(DateTime)

                    Case BaseColumn.ColumnTypes.Country, _
                        BaseColumn.ColumnTypes.Credit_Card_Number, _
                        BaseColumn.ColumnTypes.Email, _
                        BaseColumn.ColumnTypes.Password, _
                        BaseColumn.ColumnTypes.String, _
                        BaseColumn.ColumnTypes.Unique_Identifier, _
                        BaseColumn.ColumnTypes.USA_Phone_Number, _
                        BaseColumn.ColumnTypes.USA_State, _
                        BaseColumn.ColumnTypes.USA_Zip_Code, _
                        BaseColumn.ColumnTypes.Very_Large_String, _
                        BaseColumn.ColumnTypes.Web_Url
                        ' For the purpose of formula's, all of the above field types
                        ' are treated as strings.
                        e.VariableType = GetType(String)

                    Case BaseColumn.ColumnTypes.Binary, _
                        BaseColumn.ColumnTypes.File, _
                        BaseColumn.ColumnTypes.Image
                        ' For the purpose of formula's we ignore BLOB fields since they
                        ' cannot be used in any calculations or string functions.
                        e.VariableType = Nothing

                    Case Else
                        ' Unknown data type.
                        e.VariableType = Nothing
                End Select

            Catch ex As Exception
                ' Ignore the error in case we cannot find the variable or its type - simply say that
                ' the Variable Type is Nothing - implying that we do not recognize this variable.
            End Try
        End Sub


        ''' <summary>
        ''' Return the value of the given variable if it exists in the data source
        ''' </summary>
        ''' <param name="sender">The input whose absolute value is to be found.</param>
        ''' <param name="e">The input whose absolute value is to be found.</param>
        Protected Sub variables_ResolveVariableValue(ByVal sender As Object, ByVal e As ResolveVariableValueEventArgs)
            Dim col As BaseColumn

            ' Default value is Nothing
            e.VariableValue = Nothing

            ' If no DataSource was set, we do not have variables that we can use
            ' directly.  We should not get here since the request for Type should have
            ' caught this.
            If IsNothing(DataSource) Then Return

            Try

                ' Find a column in the datasource using a variable name.
                col = DataSource.TableAccess.TableDefinition.ColumnList.GetByCodeName(e.VariableName)
                If IsNothing(col) Then
                    ' if the variable name ended with "DefaultValue", remmove it and then try to get the column name again.
                    If BaseClasses.Utils.InvariantLCase(e.VariableName).EndsWith("defaultvalue") Then
                        col = DataSource.TableAccess.TableDefinition.ColumnList.GetByCodeName(e.VariableName.Substring(0, e.VariableName.Length - 12))

                        Select Case col.ColumnType
                            Case BaseColumn.ColumnTypes.Number, _
                                BaseColumn.ColumnTypes.Percentage, _
                                 BaseColumn.ColumnTypes.Star
                                ' The Number and Percentage values are saved as Single.  So we first
                                ' retrieve the Single value and then convert to Decimal.  Our policy is
                                ' always to return Decimal (never to return Single or Double) to be constent 
                                ' and avoid type conversion in the evaluator.
                                e.VariableValue = BaseFormulaUtils.ParseDecimal(col.DefaultValue)

                            Case BaseColumn.ColumnTypes.Currency
                                e.VariableValue = Me.DataSource.GetValue(col).ToDecimal

                            Case BaseColumn.ColumnTypes.Boolean
                                e.VariableValue = col.DefaultValue
                            Case BaseColumn.ColumnTypes.Credit_Card_Date, _
                                BaseColumn.ColumnTypes.Date
                                e.VariableValue = BaseFormulaUtils.ParseDate(col.DefaultValue)

                            Case BaseColumn.ColumnTypes.Country, _
                                BaseColumn.ColumnTypes.Credit_Card_Number, _
                                BaseColumn.ColumnTypes.Email, _
                                BaseColumn.ColumnTypes.Password, _
                                BaseColumn.ColumnTypes.String, _
                                BaseColumn.ColumnTypes.Unique_Identifier, _
                                BaseColumn.ColumnTypes.USA_Phone_Number, _
                                BaseColumn.ColumnTypes.USA_State, _
                                BaseColumn.ColumnTypes.USA_Zip_Code, _
                                BaseColumn.ColumnTypes.Very_Large_String, _
                                BaseColumn.ColumnTypes.Web_Url
                                e.VariableValue = col.DefaultValue

                            Case BaseColumn.ColumnTypes.File, _
                                BaseColumn.ColumnTypes.Image
                                ' Can't do anything here.
                                e.VariableValue = Nothing

                            Case Else
                                e.VariableValue = Nothing
                        End Select
                    End If


                Else


                    Select Case col.ColumnType
                        Case BaseColumn.ColumnTypes.Number, _
                            BaseColumn.ColumnTypes.Percentage, _
                             BaseColumn.ColumnTypes.Star
                            ' The Number and Percentage values are saved as Single.  So we first
                            ' retrieve the Single value and then convert to Decimal.  Our policy is
                            ' always to return Decimal (never to return Single or Double) to be constent 
                            ' and avoid type conversion in the evaluator.
                            e.VariableValue = Decimal.Parse(Me.DataSource.GetValue(col).ToDouble().ToString())

                        Case BaseColumn.ColumnTypes.Currency
                            e.VariableValue = Me.DataSource.GetValue(col).ToDecimal

                        Case BaseColumn.ColumnTypes.Boolean
                            e.VariableValue = Me.DataSource.GetValue(col).ToBoolean

                        Case BaseColumn.ColumnTypes.Credit_Card_Date, _
                            BaseColumn.ColumnTypes.Date
                            e.VariableValue = Me.DataSource.GetValue(col).ToDateTime

                        Case BaseColumn.ColumnTypes.Country, _
                            BaseColumn.ColumnTypes.Credit_Card_Number, _
                            BaseColumn.ColumnTypes.Email, _
                            BaseColumn.ColumnTypes.Password, _
                            BaseColumn.ColumnTypes.String, _
                            BaseColumn.ColumnTypes.Unique_Identifier, _
                            BaseColumn.ColumnTypes.USA_Phone_Number, _
                            BaseColumn.ColumnTypes.USA_State, _
                            BaseColumn.ColumnTypes.USA_Zip_Code, _
                            BaseColumn.ColumnTypes.Very_Large_String, _
                            BaseColumn.ColumnTypes.Web_Url
                            e.VariableValue = Me.DataSource.GetValue(col).ToString

                        Case BaseColumn.ColumnTypes.File, _
                            BaseColumn.ColumnTypes.Image
                            ' Can't do anything here.
                            e.VariableValue = Nothing

                        Case Else
                            e.VariableValue = Nothing
                    End Select


                End If


            Catch ex As Exception
                ' Ignore the error in case we cannot find the variable or its type - simply say that
                ' the Variable Type is Nothing - implying that we do not recognize this variable.
            End Try
        End Sub

        'This method returns true or false value stating whether to apply GlobalWhereClause to a particular page or not.
        'Use this method to exclude pages from applying Global Where Clauses sepcified in Batch Meister Wizard.
        Public Shared Function ShouldApplyGlobalWhereClause(ByVal globalWhereClauseFormula As String) As Boolean

            If System.Web.HttpContext.Current Is Nothing Then
                Return False
            End If

            'Comment out the following code if you want to apply GlobalWhereClause to SignIn and SignOut pages
            If Not (BaseClasses.Configuration.ApplicationSettings.Current.AuthenticationType = BaseClasses.Configuration.SecurityConstants.None) Then
                If Not BaseClasses.Configuration.ApplicationSettings.Current.SecurityDisabled Then
                    If globalWhereClauseFormula.ToLower().Contains("userid()") Or globalWhereClauseFormula.ToLower().Contains("username()") Or _
                       globalWhereClauseFormula.ToLower().Contains("roles()") Then

                        If (System.Web.HttpContext.Current.Request.Url.AbsolutePath.ToLower().Contains(BaseClasses.Configuration.ApplicationSettings.Current.SignInPageUrl.ToString().ToLower())) Then
                            Return False
                        End If
                        If (System.Web.HttpContext.Current.Request.Url.AbsolutePath.ToLower().Contains(BaseClasses.Configuration.ApplicationSettings.Current.SignedOutPageUrl.ToString().ToLower())) Then
                            Return False
                        End If
                        If (System.Web.HttpContext.Current.Request.Url.AbsolutePath.ToLower().Contains(BaseClasses.Configuration.ApplicationSettings.Current.ForgotUserPageUrl.ToString().ToLower())) Then
                            Return False
                        End If
                        If (System.Web.HttpContext.Current.Request.Url.AbsolutePath.ToLower().Contains(BaseClasses.Configuration.ApplicationSettings.Current.SendUserInfoEmailUrl.ToString().ToLower())) Then
                            Return False
                        End If

                    End If
                End If
            End If
            Return True
        End Function


    End Class

End Namespace
