Imports System.IO
Imports System.ServiceModel
Imports DigitalData.Modules.EDMI.API.Constants
Imports DigitalData.Modules.EDMI.API.EDMIServiceReference
Imports DigitalData.Modules.EDMI.API.Rights
Imports DigitalData.Modules.Language.Utils
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 FileEx As Filesystem.File

    ' Runtime Variables
    Private ReadOnly ServerAddress As String
    Private ReadOnly ServerPort As Integer
    Private ClientConfig As ConfigClientConfiguration

    ' Channel
    Private ReadOnly ChannelFactory As ChannelFactory(Of IEDMIServiceChannel)
    Private Channel As IEDMIServiceChannel

    ' Update Timer
    Private WithEvents UpdateTimer As New Timers.Timer

    ' Public Variables
    Public CachedTables As New List(Of String)


    ''' <summary>
    ''' Parse a IPAddress:Port String into its parts
    ''' </summary>
    ''' <param name="AddressWithOptionalPort"></param>
    Public Shared Function ParseServiceAddress(AddressWithOptionalPort As String) As Tuple(Of String, Integer)
        Dim oSplit() As String = AddressWithOptionalPort.Split(":"c)
        Dim oAppServerAddress As String = oSplit(0)
        Dim oAppServerPort As Integer

        If oSplit.Length = 2 Then
            If Integer.TryParse(oSplit(1), oAppServerPort) Then
                oAppServerPort = DEFAULT_SERVICE_PORT
            End If
        Else
            oAppServerPort = DEFAULT_SERVICE_PORT
        End If

        Return New Tuple(Of String, Integer)(oAppServerAddress, oAppServerPort)
    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()
        FileEx = New Filesystem.File(pLogConfig)

        Dim oServiceAddress As String = pServiceAdress
        Dim oAddressArray() As String

        If oServiceAddress.Contains(";") Then
            oAddressArray = oServiceAddress.Split(";")
        Else
            oAddressArray = oServiceAddress.Split(":")
        End If

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

        Try
            ServerAddress = oAddressArray(0)
            ServerPort = oAddressArray(1)

            Dim oBinding = API.Channel.GetBinding()
            Dim oAddress = New EndpointAddress($"net.tcp://{ServerAddress}:{ServerPort}/DigitalData/Services/Main")
            Dim oFactory = New ChannelFactory(Of IEDMIServiceChannel)(oBinding, oAddress)

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

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

        Try
            ServerAddress = IPAddress
            Dim oBinding = API.Channel.GetBinding()
            Dim oAddress = New EndpointAddress($"net.tcp://{IPAddress}:{PortNumber}/DigitalData/Services/Main")
            Dim oFactory = New ChannelFactory(Of IEDMIServiceChannel)(oBinding, oAddress)

            Logger.Debug("Connecting to Service at: [{0}]", oAddress)

            ChannelFactory = oFactory
        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 = 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!")

            Return True
        Catch ex As Exception
            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 = GetChannel()
            Channel.Open()
        Catch ex As Exception
            Logger.Error(ex)
        End Try
    End Sub

    Public Function TryGetClientConfig() As ConfigClientConfiguration
        If ClientConfig Is Nothing Then
            Throw New ApplicationException("ClientConfig is empty! Please connect to the service before calling this function")
        End If

        Return ClientConfig
    End Function

    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="pBusinessEntity">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, pBusinessEntity As String, 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, pBusinessEntity, 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 Globix_ImportFileAsync(
        pFilePath As String,
        pProfileId As Integer,
        pAttributeValues As List(Of UserAttributeValue),
        pObjectStoreName As String,
        pObjectKind As String,
        pBusinessEntity As String,
        Optional pImportOptions As Options.ImportFileOptions = Nothing) As Task(Of ImportFileResponse)
        Try
            Dim oImportFile As New Modules.Globix.ImportFile(LogConfig, Channel)
            Return Await oImportFile.RunAsync(pFilePath, pProfileId, pAttributeValues, pObjectStoreName, pObjectKind, pBusinessEntity, 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 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

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

            End If

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

        End Try

        Try
            ' Get Attributes and Values from Database
            Dim oAttributes As List(Of ObjectAttribute) = GetAttributesForObject(pObjectId, pOptions.Language)

            If oAttributes Is Nothing Then
                Return New VariableValue()
            End If

            Dim oValues = oAttributes.AsEnumerable().
                Where(Function(pAttr)
                          Return pAttr.Title.ToUpper = pAttributeName.ToUpper
                      End Function).
                Select(Function(pAttr) pAttr.Value).
                ToList()

            ' Either return a list or a single value or nothing, always wrapped in VariableValue
            If oValues.Count > 1 Then
                Return New VariableValue(oValues)

            ElseIf oValues.Count = 1 Then
                Return New VariableValue(oValues.First())

            Else
                Return New VariableValue()

            End If

        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 = NotNull(oRow.Item("ValueBigInt"), Nothing),
                    .ValueDate = NotNull(oRow.Item("ValueDate"), Nothing),
                    .ValueDecimal = NotNull(oRow.Item("ValueDecimal"), Nothing),
                    .ValueText = NotNull(oRow.Item("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

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

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

    Public Async Function GetDatatableFromIDBAsync(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.IDB,
                .ConnectionId = pConnectionId
            })
            Return oResponse
        Catch ex As Exception
            Logger.Error(ex)
            Throw ex
        End Try
    End Function

    Public Async Function GetDatatableFromECMAsync(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.ECM,
                .ConnectionId = pConnectionId
            })
            Return oResponse
        Catch ex As Exception
            Logger.Error(ex)
            Throw ex
        End Try
    End Function

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

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

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

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

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

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

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

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

    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"
    ''' <summary>
    ''' Creates a channel and adds a Faulted-Handler
    ''' </summary>
    ''' <returns>A channel object</returns>
    Private Function GetChannel() As IEDMIServiceChannel
        Try
            Logger.Debug("...Creating channel..")
            Dim oChannel = ChannelFactory.CreateChannel()

            AddHandler oChannel.Faulted, AddressOf Reconnect

            Return oChannel
        Catch ex As Exception
            Logger.Error(ex)
            Throw ex
        End Try
    End Function

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

    Private Function GetUserName(pOverrideName) As String
        Return 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
