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
    Public Const INVALID_OBEJCT_ID As Long = 0

    Private Const KIND_TYPE_DOC = "DOC"

    Private ReadOnly _logger As Logger
    Private ReadOnly _channelFactory As ChannelFactory(Of IEDMIServiceChannel)
    Private ReadOnly _IPAddressServer As String

    Private _dummy_table_attributes As DataTable
    Private _channel As IEDMIServiceChannel
    Private _FileEx As Filesystem.File


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

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

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

        Try
            _IPAddressServer = oAddressArray(0)
            Dim oBinding = Channel.GetBinding()
            Dim oAddress = New EndpointAddress($"net.tcp://{oAddressArray(0)}:{oAddressArray(1)}/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)
        _logger = LogConfig.GetLogger()
        _FileEx = New Filesystem.File(LogConfig)

        Try
            _IPAddressServer = IPAddress
            Dim oBinding = Channel.GetBinding()
            Dim oAddress = New EndpointAddress($"net.tcp://{IPAddress}:{PortNumber}/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>
    ''' Parse a IPAddress:Port String into its parts
    ''' </summary>
    ''' <param name="AddressWithOptionalPort"></param>
    ''' <returns></returns>
    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>
    ''' 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()

            _logger.Info($"Connection to AppService {_IPAddressServer} successfully established!")

            Return True
        Catch ex As Exception
            _logger.Error(ex)
            Return False
        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
            ' Set default options
            If pImportOptions Is Nothing Then
                pImportOptions = New Options.NewFileOptions()
            End If

            ' Check if file exists
            If File.Exists(pFilePath) = False Then
                Throw New FileNotFoundException("Path does not exist")
            End If

            Dim oFileInfo As New FileInfo(pFilePath)
            Dim oExtension As String = oFileInfo.Extension

            Dim oFileName As String = oFileInfo.Name
            Dim oFileCreatedAt As Date = oFileInfo?.CreationTime
            Dim oFileModifiedAt As Date = oFileInfo?.LastWriteTime
            Dim oFileHash As String = _FileEx.GetChecksum(oFileInfo.FullName)

            ' Importing the file now
            Using oFileStream As New FileStream(pFilePath, FileMode.Open, FileAccess.Read)
                Using oMemoryStream As New MemoryStream()
                    oFileStream.CopyTo(oMemoryStream)
                    Dim oContents = oMemoryStream.ToArray()

                    Dim oFileImportResponse = Await _channel.NewFileAsync(New NewFileRequest With {
                        .BusinessEntity = pBusinessEntity,
                        .File = New FileProperties With {
                            .FileName = oFileInfo.Name,
                            .FileCreatedAt = oFileCreatedAt,
                            .FileChangedAt = oFileModifiedAt,
                            .FileContents = oContents,
                            .FileImportedAt = pImportOptions.DateImported,
                            .FileChecksum = oFileHash
                        },
                        .KindType = pObjectKind,
                        .StoreName = pObjectStoreName,
                        .User = New UserState With {
                            .Language = pImportOptions.Language,
                            .UserName = pImportOptions.Username
                        }
                    })

                    If oFileImportResponse.OK = False Then
                        Throw New ApplicationException("Could not Import File Contents!")
                    End If

                    Return oFileImportResponse.ObjectId
                End Using
            End Using
        Catch ex As Exception
            _logger.Error(ex)
            Return INVALID_OBEJCT_ID
        End Try
    End Function

    Public Async Function 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 Long)
        Try
            ' Set default options
            If pImportOptions Is Nothing Then
                pImportOptions = New Options.ImportFileOptions()
            End If

            ' Check if file exists
            If File.Exists(pFilePath) = False Then
                Throw New FileNotFoundException("Path does not exist")
            End If

            Dim oFileInfo As New FileInfo(pFilePath)
            Dim oExtension As String = oFileInfo.Extension

            Dim oFileName As String = oFileInfo.Name
            Dim oFileCreatedAt As Date = oFileInfo?.CreationTime
            Dim oFileModifiedAt As Date = oFileInfo?.LastWriteTime
            Dim oFileHash As String = _FileEx.GetChecksum(oFileInfo.FullName)

            ' Importing the file now
            Using oFileStream As New FileStream(pFilePath, FileMode.Open, FileAccess.Read)
                Using oMemoryStream As New MemoryStream()
                    oFileStream.CopyTo(oMemoryStream)
                    Dim oContents = oMemoryStream.ToArray()

                    Dim oFileImportResponse = Await _channel.ImportFileAsync(New ImportFileRequest With {
                        .BusinessEntity = pBusinessEntity,
                        .File = New FileProperties With {
                            .FileName = oFileInfo.Name,
                            .FileCreatedAt = oFileCreatedAt,
                            .FileChangedAt = oFileModifiedAt,
                            .FileContents = oContents,
                            .FileImportedAt = pImportOptions.DateImported,
                            .FileChecksum = oFileHash,
                            .FileInfoRaw = oFileInfo
                        },
                        .KindType = pObjectKind,
                        .StoreName = pObjectStoreName,
                        .User = New UserState() With {
                            .UserName = pImportOptions.Username,
                            .Language = pImportOptions.Language
                        },
                        .ProfileId = pProfileId,
                        .AttributeValues = pAttributeValues.ToArray
                    })
                    If oFileImportResponse.OK = False Then
                        Throw New ApplicationException("Could not Import File Contents!")
                    End If

                    Return oFileImportResponse.ObjectId
                End Using
            End Using
        Catch ex As Exception
            _logger.Error(ex)
            Return INVALID_OBEJCT_ID
        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(SQL As String) As TableResult
        Try
            Dim oResponse = _channel.ReturnDatatable_MSSQL_IDB(SQL)
            Return oResponse
        Catch ex As Exception
            _logger.Error(ex)
            Throw ex
        End Try
    End Function

    Public Async Function GetDatatableFromIDBAsync(SQL As String) As Task(Of TableResult)
        Try
            Dim oResponse = Await _channel.ReturnDatatable_MSSQL_IDBAsync(SQL)
            Return oResponse
        Catch ex As Exception
            _logger.Error(ex)
            Throw ex
        End Try
    End Function

    Public Function GetScalarValueFromIDB(SQL As String) As ScalarResult
        Try
            Dim oResponse = _channel.ReturnScalar_MSSQL_IDB(SQL)
            Return oResponse
        Catch ex As Exception
            _logger.Error(ex)
            Throw ex
        End Try
    End Function

    Public Async Function GetScalarValueFromIDBAsync(SQL As String) As Task(Of ScalarResult)
        Try
            Dim oResponse = Await _channel.ReturnScalar_MSSQL_IDBAsync(SQL)
            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 = _channel.ReturnDatatableFromCache(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

    ''' <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

    ''' <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



#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 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
