Imports System.IO
Imports System.ServiceModel
Imports DigitalData.Modules.Base
Imports DigitalData.Modules.EDMI.API.Constants
Imports DigitalData.Modules.EDMI.API.EDMIServiceReference
Imports DigitalData.Modules.EDMI.API.Rights
Imports DigitalData.Modules.Logging

Public Class Client
    ' Constants
    Private Const UPDATE_INTERVAL_IN_MINUTES As Integer = 1

    Private Const KIND_TYPE_DOC = "DOC"

    ' Helper Classes
    Private ReadOnly LogConfig As LogConfig
    Private ReadOnly Logger As Logger
    Private ReadOnly ChannelManager As Channel

    ' Runtime Variables
    Private _ServerAddress As ServerAddressStruct
    Private _ClientConfig As GlobalStateClientConfiguration
    Private _CachedTables As New List(Of String)
    Private _IsOnline As Boolean
    Private _Channel As IEDMIServiceChannel

    ' Update Timer
    Private WithEvents UpdateTimer As New Timers.Timer

    ' Public Variables
    Public ReadOnly Property CachedTables
        Get
            Return _CachedTables
        End Get
    End Property

    Public ReadOnly Property ClientConfig As GlobalStateClientConfiguration
        Get
            If _ClientConfig Is Nothing Then
                Throw New ApplicationException("ClientConfig is empty! Please connect to the service before accessing this property")
            End If

            Return _ClientConfig
        End Get
    End Property

    Public ReadOnly Property IsOnline As Boolean
        Get
            Return _IsOnline
        End Get
    End Property

    Public ReadOnly Property ServerAddress As String
        Get
            Return $"{_ServerAddress.Host}:{_ServerAddress.Port}"
        End Get
    End Property

    ''' <summary>
    ''' Parse a IPAddress:Port String into its parts
    ''' </summary>
    ''' <param name="AddressWithOptionalPort">A Server Address, for example: 192.168.1.50, 192.168.1.50:9000, 192.168.1.50;9000</param>
    Public Shared Function ParseServiceAddress(AddressWithOptionalPort As String) As Tuple(Of String, Integer)
        Dim oConnection As New Connection()
        Dim oAddress As ServerAddressStruct = oConnection.ParseServiceAddress(AddressWithOptionalPort)

        Return New Tuple(Of String, Integer)(oAddress.Host, oAddress.Port)
    End Function

    ''' <summary>
    ''' Creates a new EDMI Client object
    ''' </summary>
    ''' <param name="pLogConfig">LogConfig object</param>
    ''' <param name="pServiceAdress">The IP address/hostname and port, separated by semicolon or colon, ex. 1.2.3.4:9000</param>
    Public Sub New(pLogConfig As LogConfig, pServiceAdress As String)
        LogConfig = pLogConfig
        Logger = pLogConfig.GetLogger()

        UpdateTimer.Interval = 60 * 1000 * UPDATE_INTERVAL_IN_MINUTES
        UpdateTimer.Start()

        Try
            Dim oConnection = New Connection()
            _ServerAddress = oConnection.ParseServiceAddress(pServiceAdress)
            ChannelManager = New Channel(pLogConfig, _ServerAddress)
            AddHandler ChannelManager.Reconnect, AddressOf Reconnect

            Logger.Debug("Ready for connection to Service at: [{0}:{1}]", _ServerAddress.Host, _ServerAddress.Port)

        Catch ex As Exception
            Logger.Error(ex)
        End Try
    End Sub

    ''' <summary>
    ''' Creates a new EDMI Client object
    ''' </summary>
    ''' <param name="pLogConfig">LogConfig object</param>
    ''' <param name="pIPAddress">The IP address to connect to</param>
    ''' <param name="pPortNumber">The Port number to use for the connection</param>
    Public Sub New(pLogConfig As LogConfig, pIPAddress As String, pPortNumber As Integer)
        LogConfig = pLogConfig
        Logger = pLogConfig.GetLogger()

        UpdateTimer.Interval = 60 * 1000 * UPDATE_INTERVAL_IN_MINUTES
        UpdateTimer.Start()

        Try
            Dim oConnection = New Connection()
            _ServerAddress = oConnection.ParseServiceAddress(pIPAddress, pPortNumber)
            ChannelManager = New Channel(pLogConfig, _ServerAddress)
            AddHandler ChannelManager.Reconnect, AddressOf Reconnect

            Logger.Debug("Ready for connection to Service at: [{0}:{1}]", _ServerAddress.Host, _ServerAddress.Port)

        Catch ex As Exception
            Logger.Error(ex)
        End Try
    End Sub

    ''' <summary>
    ''' Connect to the service
    ''' </summary>
    ''' <returns>True if connection was successful, false otherwise</returns>
    Public Function Connect() As Boolean
        Try
            _Channel = ChannelManager.GetChannel()

            Logger.Debug("Opening channel..")
            _Channel.Open()

            Dim oResponse = _Channel.GetClientConfig()
            If oResponse.OK Then
                _ClientConfig = oResponse.ClientConfig
            Else
                Logger.Warn("Client Configuration could not be loaded: [{0}]", oResponse.ErrorMessage)
            End If

            Logger.Info($"Connection to AppService [{ServerAddress}] successfully established!")

            _IsOnline = True

            Return True
        Catch ex As Exception
            _IsOnline = False

            Logger.Error(ex)
            Return False
        End Try
    End Function

    ''' <summary>
    ''' Aborts the channel and creates a new connection
    ''' </summary>
    Public Sub Reconnect()
        Logger.Warn("Connection faulted. Trying to reconnect..")

        Try
            _Channel.Abort()
            _Channel = ChannelManager.GetChannel()
            _Channel.Open()

            _IsOnline = True
        Catch ex As Exception
            Logger.Error(ex)
        End Try
    End Sub

    Private Async Function UpdateTimer_Elapsed(sender As Object, e As Timers.ElapsedEventArgs) As Task Handles UpdateTimer.Elapsed
        Try
            Dim oTables As String() = Await _Channel.GetCachedTablesAsync()
            _CachedTables = oTables.
                Select(Function(table) table.ToUpper).
                ToList()
        Catch ex As Exception
            Logger.Warn("Update of CachedTable was not successful!")
            Logger.Error(ex)
            _CachedTables = New List(Of String)
        End Try
    End Function

    ''' <summary>
    ''' Imports a file from a filepath, creating a IDB ObjectId and Filesystem Object
    ''' </summary>
    ''' <param name="pObjectStoreName">Type of ObjectStore. Can be WORK or ARCHIVE.</param>
    ''' <param name="pIDBDoctypeId">Business entity that the new file object should belong to.</param>
    ''' <param name="pImportOptions">Other file import options</param>
    ''' <exception cref="FileNotFoundException">When local filepath was not found</exception>
    ''' <exception cref="ApplicationException">When there was a error in the Service</exception>
    ''' <returns>The ObjectId of the newly generated filesystem object</returns>
    Public Async Function NewFileAsync(pFilePath As String, pObjectStoreName As String, pObjectKind As String, pIDBDoctypeId As Long, Optional pImportOptions As Options.NewFileOptions = Nothing) As Task(Of Long)
        Try
            Dim oNewFile As New Modules.IDB.NewFile(LogConfig, _Channel)
            Return Await oNewFile.RunAsync(pFilePath, pObjectStoreName, pObjectKind, pIDBDoctypeId, pImportOptions)

        Catch ex As Exception
            Logger.Error(ex)
            Return Nothing

        End Try
    End Function

    Public Async Function UpdateFileAsync(pObjectId As Long, pFilePath As String, Optional pImportOptions As Options.UpdateFileOptions = Nothing) As Task(Of Long)
        Try
            Dim oUpdateFile As New Modules.IDB.UpdateFile(LogConfig, _Channel)
            Return Await oUpdateFile.RunAsync(pFilePath, pObjectId, pImportOptions)

        Catch ex As Exception
            Logger.Error(ex)
            Return Nothing

        End Try
    End Function

    Public Async Function SetObjectStateAsync(pObjectId As Long, pState As String, Optional pOptions As Options.SetObjectStateOptions = Nothing) As Task(Of Boolean)
        Try
            Dim oSetObjectState As New Modules.IDB.SetObjectState(LogConfig, _Channel)
            Return Await oSetObjectState.RunAsync(pObjectId, pState, pOptions)

        Catch ex As Exception
            Logger.Error(ex)
            Return False

        End Try
    End Function

    Public Async Function SetAttributeValueAsync(pObjectId As Long, pName As String, pValue As Object, Optional pOptions As Options.SetAttributeValueOptions = Nothing) As Task(Of Boolean)
        Try
            Dim oSetAttributeValue As New Modules.IDB.SetAttributeValue(LogConfig, _Channel)
            Return Await oSetAttributeValue.RunAsync(pObjectId, pName, pValue, pOptions)

        Catch ex As Exception
            Logger.Error(ex)
            Return False

        End Try
    End Function

    Public Async Function CheckOutFile(pObjectId As Long, pComment As String, Optional pOptions As Options.CheckOutInOptions = Nothing) As Task(Of Long)
        Try
            Dim oCheckOutFile As New Modules.IDB.CheckOutFile(LogConfig, _Channel)
            Return Await oCheckOutFile.RunAsync(pObjectId, pComment, pOptions)

        Catch ex As Exception
            Logger.Error(ex)
            Return Nothing

        End Try
    End Function

    Public Async Function CheckOutFile(pObjectId As Long, Optional pOptions As Options.CheckOutInOptions = Nothing) As Task(Of Long)
        Try
            Dim oCheckOutFile As New Modules.IDB.CheckOutFile(LogConfig, _Channel)
            Return Await oCheckOutFile.RunAsync(pObjectId, String.Empty, pOptions)

        Catch ex As Exception
            Logger.Error(ex)
            Return Nothing

        End Try
    End Function

    Public Async Function CheckInFile(pObjectId As Long, Optional pOptions As Options.CheckOutInOptions = Nothing) As Task(Of Long)
        Try
            Dim oCheckInFile As New Modules.IDB.CheckInFile(LogConfig, _Channel)
            Return Await oCheckInFile.RunAsync(pObjectId, pOptions)

        Catch ex As Exception
            Logger.Error(ex)
            Return Nothing

        End Try
    End Function

    Public Async Function ImportFileAsync(
        pFilePath As String,
        pAttributeValues As List(Of UserAttributeValue),
        pObjectStoreName As String,
        pObjectKind As String,
        pIDBDoctypeId As String,
        Optional pImportOptions As Options.ImportFileOptions = Nothing) As Task(Of ImportFileResponse)
        Try
            Dim oImportFile As New Modules.IDB.ImportFile(LogConfig, _Channel)
            Return Await oImportFile.RunAsync(pFilePath, pAttributeValues, pObjectStoreName, pObjectKind, pIDBDoctypeId, pImportOptions)

        Catch ex As Exception
            Logger.Error(ex)
            Return Nothing

        End Try
    End Function

    Public Async Function Globix_ImportFileAsync(
        pFilePath As String,
        pProfileId As Integer,
        pAttributeValues As List(Of UserAttributeValue),
        pObjectStoreName As String,
        pObjectKind As String,
        pIDBDoctypeId As String,
        Optional pImportOptions As Options.ImportFileOptions = Nothing) As Task(Of Globix_ImportFileResponse)
        Try
            Dim oImportFile As New Modules.Globix.ImportFile(LogConfig, _Channel)
            Return Await oImportFile.RunAsync(pFilePath, pProfileId, pAttributeValues, pObjectStoreName, pObjectKind, pIDBDoctypeId, pImportOptions)

        Catch ex As Exception
            Logger.Error(ex)
            Return Nothing

        End Try
    End Function

    Public Function Zooflow_GetFileObject(pObjectId As Long, pLoadFileContents As Boolean) As FileObject
        Try
            Dim oGetFileObject As New Modules.Zooflow.GetFileObject(LogConfig, _Channel)
            Dim oFileObject = oGetFileObject.Run(pObjectId, pLoadFileContents)
            Return oFileObject
        Catch ex As Exception
            Logger.Error(ex)
            Return Nothing
        End Try
    End Function


    Public Async Function ZooFlow_GetFileObjectAsync(pObjectId As Long, pLoadFileContents As Boolean) As Task(Of FileObject)
        Try
            Dim oGetFileObject As New Modules.Zooflow.GetFileObject(LogConfig, Me)
            Dim oFileObject = Await oGetFileObject.RunAsync(pObjectId, pLoadFileContents)
            Return oFileObject
        Catch ex As Exception
            Logger.Error(ex)
            Return Nothing
        End Try
    End Function

    ''' <summary>
    ''' Sets a value to an attribute
    ''' </summary>
    ''' <param name="pObjectId">IDB ObjectId</param>
    ''' <param name="pAttributeName">Name of the attribute</param>
    ''' <param name="pValue"></param>
    ''' <param name="pOptions"></param>
    ''' <returns></returns>
    'Public Function SetVariableValue(pObjectId As Long, pAttributeName As String, pValue As Object, Optional pOptions As Options.SetVariableValueOptions = Nothing) As Boolean
    '    Try
    '        ' Set default options
    '        If pOptions Is Nothing Then
    '            pOptions = New Options.SetVariableValueOptions()
    '        End If

    '        Dim oOptions As New Options.GetVariableValueOptions With {
    '            .Language = pOptions.Language,
    '            .Username = pOptions.Username
    '        }

    '        Try
    '            Dim oResponse = Channel.TestObjectIdExists(New TestObjectIdExistsRequest With {.ObjectId = pObjectId})
    '            If oResponse.Exists = False Then
    '                Return False
    '            End If

    '        Catch ex As Exception
    '            Logger.Error(ex)
    '            Return False
    '        End Try

    '        Dim oType = pValue.GetType.Name

    '        If oType = GetType(DataTable).Name Then
    '            Dim oValueTable As DataTable = pValue
    '            Dim oCurrentValue As VariableValue

    '            ' Get current value
    '            oCurrentValue = GetVariableValue(pObjectId, pAttributeName, oOptions)

    '            ' If current value is datatable
    '            If oCurrentValue.Type = GetType(DataTable) Then

    '                ' Convert value to Datatable
    '                Dim oTable As DataTable = oCurrentValue.Value

    '                If oTable.Rows.Count > 1 Then

    '                    'now Checking whether the old row still remains in Vector? If not it will be deleted as it cannot be replaced in multivalues
    '                    For Each oRow As DataRow In oTable.Rows
    '                        Dim oExists As Boolean = False
    '                        For Each oNewValueRow As DataRow In oValueTable.Rows
    '                            Logger.Debug($"Checking oldValue[{oCurrentValue}] vs NewValue [{oNewValueRow.Item(1)}]")

    '                            If oNewValueRow.Item(1).ToString.ToUpper = oRow.Item(0).ToString.ToUpper Then
    '                                oExists = True
    '                                Exit For
    '                            End If
    '                        Next

    '                        If oExists = False Then
    '                            Logger.Debug($"Value [{oRow.Item(0)}] no longer existing in Vector-Attribute [{pAttributeName}] - will be deleted!")
    '                            DeleteTermObjectFromMetadata(pObjectId, pAttributeName, oRow.Item(0))
    '                        End If

    '                    Next
    '                End If
    '            Else
    '                If oValueTable.Rows.Count > 1 Then

    '                    'now Checking whether the old row still remains in Vector? If not it will be deleted as it cannot be replaced in multivalues
    '                    Dim oExists As Boolean = False
    '                    For Each oNewValueRow As DataRow In oValueTable.Rows
    '                        Logger.Debug($"Checking oldValue[{oCurrentValue}] vs NewValue [{oNewValueRow.Item(1)}]")

    '                        If oNewValueRow.Item(1).ToString.ToUpper = oCurrentValue.ToString.ToUpper Then
    '                            oExists = True
    '                            Exit For
    '                        End If

    '                    Next
    '                    If oExists = False Then
    '                        Logger.Debug($"Value [{oCurrentValue}] no longer existing in Vector-Attribute [{pAttributeName}] - will be deleted!")
    '                        DeleteTermObjectFromMetadata(pObjectId, pAttributeName, oCurrentValue.Value)
    '                    End If

    '                Else
    '                    Logger.Debug($"Value [{oCurrentValue}] of Attribute [{pAttributeName}] obviously was updated during runtime - will be deleted!")
    '                    DeleteTermObjectFromMetadata(pObjectId, pAttributeName, oCurrentValue.Value)

    '                End If

    '            End If

    '            For Each oNewValueRow As DataRow In oValueTable.Rows
    '                Dim oResult As Boolean = NewObjectData(pObjectId, pAttributeName, pValue, New Options.NewObjectOptions With {
    '                    .Language = pOptions.Language,
    '                    .Username = pOptions.Username
    '                })

    '                If oResult = False Then
    '                    Return False
    '                End If
    '            Next
    '            Return True
    '        Else
    '            Return NewObjectData(pObjectId, pAttributeName, pValue, New Options.NewObjectOptions With {
    '                .Language = pOptions.Language,
    '                .Username = pOptions.Username
    '            })
    '        End If

    '    Catch ex As Exception
    '        Logger.Error(ex)
    '        Return False
    '    End Try
    'End Function

    ''' <summary>
    ''' 
    ''' </summary>
    ''' <param name="pObjectId"></param>
    ''' <param name="pAttributeName"></param>
    ''' <param name="pOptions"></param>
    ''' <returns></returns>
    Public Function GetVariableValue(pObjectId As Long, pAttributeName As String, Optional pOptions As Options.GetVariableValueOptions = Nothing) As VariableValue
        If pOptions Is Nothing Then
            pOptions = New Options.GetVariableValueOptions()
        End If

        Try
            Dim oArgs = New GetAttributeValueRequest With {
                .ObjectId = pObjectId,
                .AttributeName = pAttributeName,
                .User = New UserState() With {
                    .UserName = pOptions.Username,
                    .Language = pOptions.Language
                }
            }
            Dim oResponse = _Channel.GetAttributeValue(oArgs)
            If oResponse.OK = False Then
                Return New VariableValue()

            End If

            Return New VariableValue(oResponse.Value)

        Catch ex As Exception
            Logger.Error(ex)
            Return New VariableValue()

        End Try
    End Function

    Private Function GetValueByType(pRow As DataRow, pTypeString As String) As Object
        Try
            Dim oAttributeValue As Object

            Select Case pTypeString
                Case Constants.AttributeTypeName.BIT
                    oAttributeValue = pRow.Item("ValueBigInt")

                Case Constants.AttributeTypeName.BIG_INTEGER
                    oAttributeValue = pRow.Item("ValueBigInt")

                Case Constants.AttributeTypeName.DATE
                    oAttributeValue = pRow.Item("ValueDate")

                Case Constants.AttributeTypeName.DATETIME
                    oAttributeValue = pRow.Item("ValueDate")

                Case Constants.AttributeTypeName.DECIMAL
                    oAttributeValue = pRow.Item("ValueDecimal")

                Case Constants.AttributeTypeName.FLOAT
                    oAttributeValue = pRow.Item("ValueDecimal")

                Case Constants.AttributeTypeName.VARCHAR
                    oAttributeValue = pRow.Item("ValueText")

                Case Constants.AttributeTypeName.VECTOR_INTEGER
                    oAttributeValue = pRow.Item("ValueBigInt")

                Case Constants.AttributeTypeName.VECTOR_STRING
                    oAttributeValue = pRow.Item("ValueText")

                Case Else
                    oAttributeValue = Nothing
            End Select

            Return oAttributeValue

        Catch ex As Exception
            Logger.Error(ex)
            Return Nothing

        End Try
    End Function

    Private Function GetAttributesForObject(pObjectId As Long, pLanguage As String) As List(Of ObjectAttribute)
        Dim oAttributes As New List(Of ObjectAttribute)

        Try
            Dim oResult As TableResult = _Channel.ReturnDatatable_MSSQL_IDB($"EXEC [PRIDB_GET_VALUE_DT] {pObjectId}, '{pLanguage}'")

            If oResult.OK = False Then
                Throw New ApplicationException(oResult.ErrorMessage)
            End If

            If oResult.Table Is Nothing OrElse oResult.Table.Rows.Count = 0 Then
                Return Nothing
            End If

            For Each oRow As DataRow In oResult.Table.Rows
                Dim oAttribute As New ObjectAttribute With {
                    .Id = oRow.Item("AttributeId"),
                    .Title = oRow.Item("AttributeTitle"),
                    .Type = oRow.Item("AttributeType"),
                    .ValueBigInt = oRow.ItemEx(Of Object)("ValueBigInt", Nothing),
                    .ValueDate = oRow.ItemEx(Of Object)("ValueDate", Nothing),
                    .ValueDecimal = oRow.ItemEx(Of Object)("ValueDecimal", Nothing),
                    .ValueText = oRow.ItemEx(Of Object)("ValueText", Nothing)
                }

                oAttributes.Add(oAttribute)
            Next

            Return oAttributes
        Catch ex As Exception
            Logger.Error(ex)
            Return Nothing
        End Try
    End Function

    Private Function NewObjectData(pObjectId As Long, pAttributeName As String, pValue As Object, pOptions As Options.NewObjectOptions)
        If pOptions Is Nothing Then
            pOptions = New Options.NewObjectOptions()
        End If

        Dim oLanguage = GetUserLanguage(pOptions.Language)
        Dim oUsername = GetUserName(pOptions.Username)

        Dim oSql = $"DECLARE @NEW_OBJ_MD_ID BIGINT
                    EXEC PRIDB_NEW_OBJ_DATA({pObjectId}, '{pAttributeName}', '{oUsername}', '{pValue}', '{oLanguage}', 0, @OMD_ID = @NEW_OBJ_MD_ID OUTPUT)"
        Dim oResult = _Channel.ExecuteNonQuery_MSSQL_IDB(oSql)

        If oResult.OK = False Then
            Logger.Warn("Error while deleting Term object")
            Logger.Error(oResult.ErrorMessage)
        End If

        Return oResult.OK
    End Function

    Private Function DeleteTermObjectFromMetadata(pObjectId As Long, pAttributeName As String, pTerm2Delete As String, Optional pUsername As String = "", Optional pLanguage As String = "") As Boolean
        Try
            Dim oLanguage = GetUserLanguage(pLanguage)
            Dim oUsername = GetUserName(pUsername)

            Dim oIdIsForeign As Integer = 1
            Dim oDELSQL = $"EXEC PRIDB_DELETE_TERM_OBJECT_METADATA {pObjectId},'{pAttributeName}','{pTerm2Delete}','{oUsername}','{oLanguage}',{oIdIsForeign}"
            Dim oResult = _Channel.ExecuteNonQuery_MSSQL_IDB(oDELSQL)

            If oResult.OK = False Then
                Logger.Warn("Error while deleting Term object")
                Logger.Error(oResult.ErrorMessage)
            End If

            Return oResult.OK
        Catch ex As Exception
            Logger.Error(ex)
            Return False
        End Try
    End Function

#Region "GetDatatable"
    Public Function GetDatatableFromIDB(pSQL As String) As GetDatatableResponse
        Try
            Dim oResponse = _Channel.ReturnDatatable(New GetDatatableRequest() With {
                .SqlCommand = pSQL,
                .NamedDatabase = DatabaseName.IDB
            })
            Return oResponse
        Catch ex As Exception
            Logger.Error(ex)
            Throw ex
        End Try
    End Function

    Public Function GetDatatableFromECM(pSQL As String) As GetDatatableResponse
        Try
            Dim oResponse = _Channel.ReturnDatatable(New GetDatatableRequest() With {
                .SqlCommand = pSQL,
                .NamedDatabase = DatabaseName.ECM
            })
            Return oResponse
        Catch ex As Exception
            Logger.Error(ex)
            Throw ex
        End Try
    End Function

    Public Function GetDatatableFromConnection(pSQL As String, pConnectionId As Integer) As GetDatatableResponse
        Try
            Dim oResponse = _Channel.ReturnDatatable(New GetDatatableRequest() With {
                .SqlCommand = pSQL,
                .NamedDatabase = DatabaseName.None,
                .ConnectionId = pConnectionId
            })
            Return oResponse
        Catch ex As Exception
            Logger.Error(ex)
            Throw ex
        End Try
    End Function

    Public Async Function GetDatatableFromIDBAsync(pSQL As String) As Task(Of GetDatatableResponse)
        Try
            Dim oResponse = Await _Channel.ReturnDatatableAsync(New GetDatatableRequest() With {
                .SqlCommand = pSQL,
                .NamedDatabase = DatabaseName.IDB
            })
            Return oResponse
        Catch ex As Exception
            Logger.Error(ex)
            Throw ex
        End Try
    End Function

    Public Async Function GetDatatableFromECMAsync(pSQL As String) As Task(Of GetDatatableResponse)
        Try
            Dim oResponse = Await _Channel.ReturnDatatableAsync(New GetDatatableRequest() With {
                .SqlCommand = pSQL,
                .NamedDatabase = DatabaseName.ECM
            })
            Return oResponse
        Catch ex As Exception
            Logger.Error(ex)
            Throw ex
        End Try
    End Function

    Public Async Function GetDatatableFromConnectionAsync(pSQL As String, Optional pConnectionId As Integer = 0) As Task(Of GetDatatableResponse)
        Try
            Dim oResponse = Await _Channel.ReturnDatatableAsync(New GetDatatableRequest() With {
                .SqlCommand = pSQL,
                .NamedDatabase = DatabaseName.None,
                .ConnectionId = pConnectionId
            })
            Return oResponse
        Catch ex As Exception
            Logger.Error(ex)
            Throw ex
        End Try
    End Function
#End Region

#Region "GetScalarValue"
    Public Function GetScalarValueFromIDB(pSQL As String) As GetScalarValueResponse
        Try
            Dim oResponse = _Channel.ReturnScalarValue(New GetScalarValueRequest() With {
                .SqlCommand = pSQL,
                .NamedDatabase = DatabaseName.IDB
            })
            Return oResponse
        Catch ex As Exception
            Logger.Error(ex)
            Throw ex
        End Try
    End Function

    Public Function GetScalarValueFromECM(pSQL As String) As GetScalarValueResponse
        Try
            Dim oResponse = _Channel.ReturnScalarValue(New GetScalarValueRequest() With {
                .SqlCommand = pSQL,
                .NamedDatabase = DatabaseName.ECM
            })
            Return oResponse
        Catch ex As Exception
            Logger.Error(ex)
            Throw ex
        End Try
    End Function

    Public Function GetScalarValueFromConnection(pSQL As String, pConnectionId As Integer) As GetScalarValueResponse
        Try
            Dim oResponse = _Channel.ReturnScalarValue(New GetScalarValueRequest() With {
                .SqlCommand = pSQL,
                .NamedDatabase = DatabaseName.None,
                .ConnectionId = pConnectionId
            })
            Return oResponse
        Catch ex As Exception
            Logger.Error(ex)
            Throw ex
        End Try
    End Function

    Public Async Function GetScalarValueFromIDBAsync(pSQL As String) As Task(Of GetScalarValueResponse)
        Try
            Dim oResponse = Await _Channel.ReturnScalarValueAsync(New GetScalarValueRequest() With {
                .SqlCommand = pSQL,
                .NamedDatabase = DatabaseName.IDB
            })
            Return oResponse
        Catch ex As Exception
            Logger.Error(ex)
            Throw ex
        End Try
    End Function

    Public Async Function GetScalarValueFromECMAsync(pSQL As String) As Task(Of GetScalarValueResponse)
        Try
            Dim oResponse = Await _Channel.ReturnScalarValueAsync(New GetScalarValueRequest() With {
                .SqlCommand = pSQL,
                .NamedDatabase = DatabaseName.ECM
            })
            Return oResponse
        Catch ex As Exception
            Logger.Error(ex)
            Throw ex
        End Try
    End Function

    Public Async Function GetScalarValueFromConnectionAsync(pSQL As String, pConnectionId As Integer) As Task(Of GetScalarValueResponse)
        Try
            Dim oResponse = Await _Channel.ReturnScalarValueAsync(New GetScalarValueRequest() With {
                .SqlCommand = pSQL,
                .NamedDatabase = DatabaseName.None,
                .ConnectionId = pConnectionId
            })
            Return oResponse
        Catch ex As Exception
            Logger.Error(ex)
            Throw ex
        End Try
    End Function
#End Region

#Region "ExecuteNonQuery"
    Public Function ExecuteNonQueryFromIDB(pSQL As String) As ExecuteNonQueryResponse
        Try
            Dim oResponse = _Channel.ExecuteNonQuery(New ExecuteNonQueryRequest() With {
                .SqlCommand = pSQL,
                .NamedDatabase = DatabaseName.IDB
            })
            Return oResponse
        Catch ex As Exception
            Logger.Error(ex)
            Throw ex
        End Try
    End Function

    Public Function ExecuteNonQueryFromECM(pSQL As String) As ExecuteNonQueryResponse
        Try
            Dim oResponse = _Channel.ExecuteNonQuery(New ExecuteNonQueryRequest() With {
                .SqlCommand = pSQL,
                .NamedDatabase = DatabaseName.ECM
            })
            Return oResponse
        Catch ex As Exception
            Logger.Error(ex)
            Throw ex
        End Try
    End Function

    Public Function ExecuteNonQueryFromConnection(pSQL As String, pConnectionId As Integer) As ExecuteNonQueryResponse
        Try
            Dim oResponse = _Channel.ExecuteNonQuery(New ExecuteNonQueryRequest() With {
                .SqlCommand = pSQL,
                .NamedDatabase = DatabaseName.None,
                .ConnectionId = pConnectionId
            })
            Return oResponse
        Catch ex As Exception
            Logger.Error(ex)
            Throw ex
        End Try
    End Function

    Public Async Function ExecuteNonQueryFromIDBAsync(pSQL As String) As Task(Of ExecuteNonQueryResponse)
        Try
            Dim oResponse = Await _Channel.ExecuteNonQueryAsync(New ExecuteNonQueryRequest() With {
                .SqlCommand = pSQL,
                .NamedDatabase = DatabaseName.IDB
            })
            Return oResponse
        Catch ex As Exception
            Logger.Error(ex)
            Throw ex
        End Try
    End Function

    Public Async Function ExecuteNonQueryFromECMAsync(pSQL As String) As Task(Of ExecuteNonQueryResponse)
        Try
            Dim oResponse = Await _Channel.ExecuteNonQueryAsync(New ExecuteNonQueryRequest() With {
                .SqlCommand = pSQL,
                .NamedDatabase = DatabaseName.ECM
            })
            Return oResponse
        Catch ex As Exception
            Logger.Error(ex)
            Throw ex
        End Try
    End Function

    Public Async Function ExecuteNonQueryFromConnectionAsync(pSQL As String, pConnectionId As Integer) As Task(Of ExecuteNonQueryResponse)
        Try
            Dim oResponse = Await _Channel.ExecuteNonQueryAsync(New ExecuteNonQueryRequest() With {
                .SqlCommand = pSQL,
                .NamedDatabase = DatabaseName.None,
                .ConnectionId = pConnectionId
            })
            Return oResponse
        Catch ex As Exception
            Logger.Error(ex)
            Throw ex
        End Try
    End Function
#End Region


    Public Function GetDatatableByName(DatatableName As String, Optional FilterExpression As String = "", Optional SortByColumn As String = "") As TableResult
        Try
            Dim oResponse = _Channel.ReturnDatatableFromCache(DatatableName, FilterExpression, SortByColumn)
            Return oResponse
        Catch ex As Exception
            Logger.Error(ex)
            Throw ex
        End Try
    End Function

    Public Async Function GetDatatableByNameAsync(DatatableName As String, Optional FilterExpression As String = "", Optional SortByColumn As String = "") As Task(Of TableResult)
        Try
            Dim oResponse = Await _Channel.ReturnDatatableFromCacheAsync(DatatableName, FilterExpression, SortByColumn)
            Return oResponse
        Catch ex As Exception
            Logger.Error(ex)
            Throw ex
        End Try
    End Function



    ''' <summary>
    ''' Return infos about a file object
    ''' </summary>
    ''' <param name="UserId"></param>
    ''' <param name="ObjectId"></param>
    ''' <returns></returns>
    Public Function GetDocumentInfo(UserId As Long, ObjectId As Long) As DocumentInfo
        Try
            Dim oResponse As DocumentInfoResponse = _Channel.GetFileInfoByObjectId(New DocumentInfoRequest With {
                .ObjectId = ObjectId,
                .UserId = UserId
            })

            Return New DocumentInfo With {
                .AccessRight = oResponse.FileRight,
                .FullPath = oResponse.FullPath
            }

        Catch ex As FaultException(Of ObjectDoesNotExistFault)
            Logger.Error(ex)
            Throw ex

        Catch ex As FaultException
            Logger.Error(ex)
            Throw ex

        Catch ex As Exception
            Logger.Error(ex)
            Throw ex

        End Try
    End Function

    Public Async Function GetDocumentInfoAsync(UserId As Long, ObjectId As Long) As Task(Of DocumentInfo)
        Try
            Dim oParams = New DocumentInfoRequest With {
                .ObjectId = ObjectId,
                .UserId = UserId
            }
            Dim oResponse As DocumentInfoResponse = Await _Channel.GetFileInfoByObjectIdAsync(oParams)

            Return New DocumentInfo With {
                .AccessRight = oResponse.FileRight,
                .FullPath = oResponse.FullPath
            }
        Catch ex As Exception
            Logger.Error(ex)
            Throw ex
        End Try
    End Function


#Region "Private Functions"


    Private Function GetUserLanguage(pOverrideLanguage As String) As String
        Return ObjectEx.NotNull(pOverrideLanguage, Threading.Thread.CurrentThread.CurrentUICulture.Name)
    End Function

    Private Function GetUserName(pOverrideName) As String
        Return ObjectEx.NotNull(pOverrideName, Environment.UserName)
    End Function
#End Region

#Region "Response Classes"
    Public Class StreamedFile
        Public Stream As MemoryStream
        Public FileName As String
    End Class

    Public Class FileList
        Public Datatable As DataTable
    End Class

    Public Class DocumentInfo
        Public Id As Long
        Public FullPath As String
        Public AccessRight As AccessRight
    End Class

    Public Class VariableValue
        Public ReadOnly Property IsVector As Boolean = False

        Public Property Value As Object
        Public Property Type As Type

        Public Sub New()
            MyClass.New(Nothing)
        End Sub

        Public Sub New(pValue As Object)
            ' Check if value is a collection
            If TypeOf pValue Is IEnumerable Then
                IsVector = True
            End If

            ' Try to determine the type
            If IsNothing(pValue) Then
                Type = Nothing
            Else
                Type = pValue.GetType
            End If

            Value = pValue
        End Sub
    End Class


#End Region

    Public Class ObjectAttribute
        Public Property Id As Long
        Public Property Title As String
        Public Property Type As String

        Public Property ValueBigInt As Long
        Public Property ValueText As String
        Public Property ValueDecimal As Decimal
        Public Property ValueDate As DateTime

        Public ReadOnly Property Value As Object
            Get
                Return GetValue()
            End Get
        End Property

        Private Function GetValue() As Object
            Select Case Type
                Case AttributeTypeName.VECTOR_INTEGER
                    Return ValueBigInt

                Case AttributeTypeName.BIG_INTEGER
                    Return ValueBigInt

                Case AttributeTypeName.VECTOR_STRING
                    Return ValueText

                Case AttributeTypeName.VARCHAR
                    Return ValueText

                Case AttributeTypeName.BIT
                    Return IIf(ValueBigInt = 1, True, False)

                Case AttributeTypeName.DATE
                    Return ValueDate

                Case AttributeTypeName.DATETIME
                    Return ValueDate

                Case AttributeTypeName.DECIMAL
                    Return ValueDecimal

                Case AttributeTypeName.FLOAT
                    Return ValueDecimal

                Case Else
                    Return Nothing
            End Select
        End Function
    End Class
End Class
