Imports DigitalData.Modules.Logging
Imports Oracle.ManagedDataAccess.Client

Public Class Dispatcher
    Public ReadOnly Property Connections As New List(Of DispatcherConnection)

    Public ReadOnly Logger As Logger
    Public ReadOnly LogConfig As LogConfig

    ''' <summary>
    ''' Create a new instance of Dispatcher. This is the preferred way to create the dispatcher.
    ''' </summary>
    ''' <param name="pLogConfig">An instance of LogConfig</param>
    ''' <param name="pConnectionString">Initial connectionstring for connecting to DD_ECM database.</param>
    ''' <returns>An instance of Dispatcher with connections</returns>
    Public Shared Function Create(pLogConfig As LogConfig, pConnectionString As String) As Dispatcher
        Dim oDecryptedConnectionString As String = MSSQLServer.DecryptConnectionString(pConnectionString)
        Dim oDatabase As MSSQLServer = New MSSQLServer(pLogConfig, oDecryptedConnectionString)
        Dim oTable As DataTable = oDatabase.GetDatatable(Queries.DD_ECM.Connections.AllConnections)

        Dim oConnections As New List(Of DispatcherConnection)
        For Each oRow As DataRow In oTable.Rows
            Dim oConnection As DispatcherConnection = CreateFromDataRow(oRow)

            If oConnection IsNot Nothing Then
                oConnections.Add(oConnection)
            End If
        Next

        Return New Dispatcher(pLogConfig, oConnections)
    End Function

    ''' <summary>
    ''' Create a new instance of Dispatcher. Needs a manually constructed list of connection objects.
    ''' </summary>
    ''' <param name="pLogConfig">An instance of LogConfig</param>
    ''' <param name="pConnections">A list of DispatcherConnection objects</param>
    ''' <seealso cref="Create"/>
    Public Sub New(pLogConfig As LogConfig, pConnections As List(Of DispatcherConnection))
        LogConfig = pLogConfig
        Logger = pLogConfig.GetLogger()
        Connections = pConnections
    End Sub

    Public Enum ConnectionType
        MSSQL
        Oracle
        ODBC
        Firebird
    End Enum

    Public Class DispatcherOptions
        Public Property QueryTimeout As Integer = Constants.DEFAULT_TIMEOUT
    End Class

    Public Class DispatcherConnection
        Public Property Id As Integer
        Public Property Name As String
        Public Property ConnectionString As String
        Public Property ConnectionType As ConnectionType
    End Class

    ''' <summary>
    ''' Returns a Datatable from the database with the specified connection id
    ''' </summary>
    ''' <param name="pSQLCommand">The SQL query</param>
    ''' <param name="pConnectionId">The connection id</param>
    ''' <returns>A datatable with the results or nothing if an error occurred</returns>
    Public Function GetDatatable(pSQLCommand As String, pConnectionId As Integer) As DataTable
        Dim oAdapter As IDatabase = GetAdapterClass(pConnectionId)
        Return oAdapter.GetDatatable(pSQLCommand)
    End Function

    ''' <summary>
    ''' Returns a Datatable from the database
    ''' </summary>
    ''' <param name="pSQLCommand">The SQL query</param>
    ''' <returns>A datatable with the results or nothing if an error occurred</returns>
    Public Function GetDatatable(pSQLCommand As String) As DataTable
        Return GetDatatable(pSQLCommand, Constants.DEFAULT_CONNECTION_ID)
    End Function

    ''' <summary>
    ''' Executes a query without return value like INSERT or UPDATE from the database with the specified connection id and 
    ''' returns a boolean value indicating success or failure of the query
    ''' </summary>
    ''' <param name="pSQLCommand">The SQL query</param>
    ''' <param name="pConnectionId">The connection id</param>
    ''' <returns>True if the query was successful, otherwise false</returns>
    Public Function ExecuteNonQuery(pSQLCommand As String, pConnectionId As Integer) As Boolean
        Dim oAdapter As IDatabase = GetAdapterClass(pConnectionId)
        Return oAdapter.ExecuteNonQuery(pSQLCommand)
    End Function

    ''' <summary>
    ''' Executes a query without return value like INSERT or UPDATE from the database and 
    ''' returns a boolean value indicating success or failure of the query
    ''' </summary>
    ''' <param name="pSQLCommand">The SQL query</param>
    ''' <returns>True if the query was successful, otherwise false</returns>
    Public Function ExecuteNonQuery(pSQLCommand As String) As Boolean
        Return ExecuteNonQuery(pSQLCommand, Constants.DEFAULT_CONNECTION_ID)
    End Function

    ''' <summary>
    ''' Returns a single value from the database specified by the connection id
    ''' </summary>
    ''' <param name="pSQLCommand">The SQL query</param>
    ''' <param name="pConnectionId">The connection id</param>
    ''' <returns>A value of type object</returns>
    Public Function GetScalarValue(pSQLCommand As String, pConnectionId As Integer) As Object
        Dim oAdapter As IDatabase = GetAdapterClass(pConnectionId)
        Return oAdapter.GetScalarValue(pSQLCommand)
    End Function

    ''' <summary>
    ''' Returns a single value from the database
    ''' </summary>
    ''' <param name="pSQLCommand">The SQL query</param>
    ''' <returns>A value of type object</returns>
    Public Function GetScalarValue(pSQLCommand As String) As Object
        Return GetScalarValue(pSQLCommand, Constants.DEFAULT_CONNECTION_ID)
    End Function

    Private Function GetConnection(pConnectionId As Integer) As DispatcherConnection
        Dim oConnection As DispatcherConnection = Connections.
            Where(Function(conn) conn.Id = pConnectionId).
            FirstOrDefault()

        If oConnection IsNot Nothing Then
            Logger.Debug("Resolved ConnectionId [{0}] into Connection [{1}]", pConnectionId, oConnection.Name)
        End If

        Return oConnection
    End Function

    Private Function GetAdapterClass(pConnectionId As Integer) As IDatabase
        Dim oConnection = GetConnection(pConnectionId)
        Dim oArgs As New List(Of Object) From {LogConfig, oConnection.ConnectionString, Constants.DEFAULT_TIMEOUT}
        Logger.Debug("Creating database adapter object for type [{0}]", ConnectionType.MSSQL)

        ' TODO: Cache Database instances to avoid constructing them for every call

        Select Case oConnection.ConnectionType
            Case ConnectionType.MSSQL
                Dim oConnectionString = MSSQLServer.DecryptConnectionString(oConnection.ConnectionString)
                Return New MSSQLServer(LogConfig, oConnectionString, Constants.DEFAULT_TIMEOUT)

            Case ConnectionType.Oracle
                Return New Oracle(LogConfig, oConnection.ConnectionString, Constants.DEFAULT_TIMEOUT)

            Case ConnectionType.Firebird
                Dim oBuilder As New FirebirdSql.Data.FirebirdClient.FbConnectionStringBuilder(oConnection.ConnectionString)
                Return New Firebird(LogConfig, oBuilder.DataSource, oBuilder.Database, oBuilder.UserID, oBuilder.Password)

            Case ConnectionType.ODBC
                'Dim oBuilder As New Data.Odbc.OdbcConnectionStringBuilder(pConnection.ConnectionString)
                'Return New ODBC(LogConfig)
                Return Nothing

            Case Else
                Return Nothing
        End Select
    End Function

    Private Shared Function CreateFromDataRow(pConnectionDataRow As DataRow) As DispatcherConnection
        Try
            Dim oGuid = pConnectionDataRow.Item("GUID")
            Dim oName = pConnectionDataRow.Item("BEZEICHNUNG")
            Dim oUser = pConnectionDataRow.Item("USERNAME")
            Dim oPassword = pConnectionDataRow.Item("PASSWORD")
            Dim oServer = pConnectionDataRow.Item("SERVER")
            Dim oDatabase = pConnectionDataRow.Item("DATENBANK")

            Dim oConnectionType As ConnectionType
            Dim oConnectionString As String

            Select Case pConnectionDataRow.Item("SQL_PROVIDER")
                Case "Oracle"
                    ' TODO: Test Oracle Connection
                    oConnectionType = ConnectionType.Oracle
                    oConnectionString = New OracleConnectionStringBuilder() With {
                        .UserID = oUser,
                        .Password = oPassword,
                        .DataSource = oServer
                    }.ToString()

                Case "ODBC"
                    ' TODO: Test ODBC Connection
                    oConnectionType = ConnectionType.ODBC
                    oConnectionString = New Data.Odbc.OdbcConnectionStringBuilder() With {
                        .ConnectionString = oServer
                    }.ToString()

                Case "Firebird"
                    oConnectionType = ConnectionType.Firebird
                    oConnectionString = New FirebirdSql.Data.FirebirdClient.FbConnectionStringBuilder() With {
                        .UserID = oUser,
                        .Password = oPassword,
                        .DataSource = oServer,
                        .Database = oDatabase
                    }.ToString()

                Case Else ' MSSQL
                    oConnectionType = ConnectionType.MSSQL
                    oConnectionString = New SqlClient.SqlConnectionStringBuilder() With {
                        .UserID = oUser,
                        .Password = oPassword,
                        .DataSource = oServer,
                        .InitialCatalog = oDatabase
                    }.ToString()

            End Select

            Return New DispatcherConnection With {
                    .Id = oGuid,
                    .Name = oName,
                    .ConnectionType = oConnectionType,
                    .ConnectionString = oConnectionString
                }
        Catch ex As Exception
            Return Nothing
        End Try
    End Function


End Class

