Imports DevExpress.XtraGrid
Imports DevExpress.XtraGrid.Views.Grid
Imports DevExpress.XtraTreeList.Nodes
Imports DigitalData.GUIs.Common
Imports DigitalData.Modules.Config
Imports DigitalData.Modules.Database
Imports DigitalData.Modules.Logging
Imports DigitalData.Modules.Language.Utils
Imports DigitalData.Controls.SQLConfig
Imports DevExpress.XtraTab
Imports DigitalData.Controls.DocumentViewer
Imports DevExpress.XtraEditors
Imports DevExpress.XtraRichEdit
Imports DevExpress.XtraTreeList.Columns
Imports DevExpress.XtraEditors.Repository
Imports DevExpress.Utils
Imports DevExpress.XtraEditors.Controls

Public Class frmMonitor
    Public Property LogConfig As LogConfig
    Public Property ConfigManager As ConfigManager(Of Config)
    Public Property Database As MSSQLServer

    Private ReadOnly SearchKeys As New List(Of SearchKey)

    Private ReadOnly SQLColumns As New List(Of String) From {"SELECT1", "SELECT2", "SELECT3", "SELECT4"}
    Private ReadOnly DocViewColumns As New List(Of String) From {"DOCVIEW1", "DOCVIEW2"}
    Private ReadOnly HtmlViewColumns As New List(Of String) From {"HTML1", "HTML2"}
    Private ReadOnly DataColumns As List(Of String) = SQLColumns.
        Concat(DocViewColumns).
        Concat(HtmlViewColumns).
        ToList

    Private ReadOnly DisplayColumns As New List(Of String) From {"COLUMN1", "COLUMN2", "COLUMN3", "ADDED_WHEN", "STATE", "ICON"}




    Private SQLResultGrids As List(Of GridControl)
    Private SQLResultTabs As List(Of XtraTabPage)
    Private ActiveSQLResultGrid As GridControl

    Private FileResultViewers As List(Of DocumentViewer)
    Private FileResultTabs As List(Of XtraTabPage)

    Private HtmlResultViewers As List(Of RichEditControl)
    Private HtmlResultTabs As List(Of XtraTabPage)

    Private Const STATE_SUCCESS As String = "SUCCESS"
    Private Const STATE_FAILURE As String = "FAILURE"
    Private Const STATE_WARNING As String = "WARNING"
    Private Const STATE_WAITING As String = "WAITING"
    Private Const STATE_HIGHLIGHT As String = "HIGHLIGHT"


    Private Enum NodeImage
        [Default] = 0
        SQL = 1
        File = 2
        Mail = 3
        Success = 4
        Failure = 5
        Warning = 6
        Waiting = 7
        User = 8
        Highlight = 9
    End Enum

    Private ReadOnly StateIcons As New Dictionary(Of String, NodeImage) From {
        {STATE_SUCCESS, NodeImage.Success},
        {STATE_FAILURE, NodeImage.Failure}
    }

    Private GridBuilder As GridBuilder

    Private Sub frmStart_Load(sender As Object, e As EventArgs) Handles MyBase.Load
        Try
            LogConfig = New LogConfig(LogConfig.PathType.AppData, Nothing, Nothing, "Digital Data", "Monitor")
            ConfigManager = New ConfigManager(Of Config)(LogConfig, Application.UserAppDataPath, Application.UserAppDataPath, Application.StartupPath)
            InitializeBaseForm(LogConfig)

            If ConfigManager.Config.ConnectionString = String.Empty Then
                Dim oSQLConfig As New frmSQLConfig(LogConfig)
                If oSQLConfig.ShowDialog() = DialogResult.OK Then
                    ConfigManager.Config.ConnectionString = oSQLConfig.ConnectionString
                    ConfigManager.Save()
                    Application.Restart()
                Else
                    ShowErrorMessage("No Database configured. Application will close!")
                    Application.Exit()

                End If
            End If
            If ConfigManager.Config.IDB_Praefix <> "" Then
                If ConfigManager.Config.SearchKeySQL.Contains("@IDB_PRAEFIX") Then
                    Dim oREPLACE = ConfigManager.Config.SearchKeySQL.Replace("@IDB_PRAEFIX", ConfigManager.Config.IDB_Praefix)
                    ConfigManager.Config.SearchKeySQL = oREPLACE
                    ConfigManager.Save()
                End If
                Console.WriteLine(ConfigManager.Config.SearchKeySQL)

            End If
            Dim oConnectionString = MSSQLServer.DecryptConnectionString(ConfigManager.Config.ConnectionString)
            Database = New MSSQLServer(LogConfig, oConnectionString)
            GridBuilder = New GridBuilder(New List(Of GridView) From {GridView1, GridView2, GridView3, GridView4})
            GridBuilder.
                WithDefaults().
                WithDefaults(TreeListResults).
                WithReadOnlyOptions().
                WithReadOnlyOptions(TreeListResults)

            SQLResultGrids = New List(Of GridControl) From {GridControl1, GridControl2, GridControl3, GridControl4}
            SQLResultTabs = New List(Of XtraTabPage) From {XtraTabPageSQL1, XtraTabPageSQL2, XtraTabPageSQL3, XtraTabPageSQL4}

            FileResultViewers = New List(Of DocumentViewer) From {DocumentViewer1, DocumentViewer2}
            FileResultTabs = New List(Of XtraTabPage) From {XtraTabPageFile1, XtraTabPageFile2}

            HtmlResultViewers = New List(Of RichEditControl) From {RichEditControl1, RichEditControl2}
            HtmlResultTabs = New List(Of XtraTabPage) From {XtraTabPageHtml1, XtraTabPageHtml2}

            LoadSearchKeys()

            Dim oLicense = LoadGDPicture()

            For Each oGrid In SQLResultGrids
                AddHandler oGrid.Enter, Sub()
                                            ActiveSQLResultGrid = oGrid
                                            btnExportGrid.Visibility = DevExpress.XtraBars.BarItemVisibility.Always
                                        End Sub

                AddHandler oGrid.Leave, Sub()
                                            ActiveSQLResultGrid = Nothing
                                            btnExportGrid.Visibility = DevExpress.XtraBars.BarItemVisibility.Never
                                        End Sub
            Next

            AddHandler TreeListResults.Enter, Sub() btnExportTreeview.Visibility = DevExpress.XtraBars.BarItemVisibility.Always
            AddHandler TreeListResults.Leave, Sub() btnExportTreeview.Visibility = DevExpress.XtraBars.BarItemVisibility.Never

            For Each oViewer As DocumentViewer In FileResultViewers
                oViewer.Init(LogConfig, oLicense)
            Next
            SplitContainerControl3.Collapsed = True
            SplitContainerMain.Collapsed = True
        Catch ex As Exception
            ShowErrorMessage(ex)
        End Try
    End Sub

    Private Function LoadGDPicture() As String
        Dim oSQL = "SELECT LICENSE FROM TBDD_3RD_PARTY_MODULES WHERE NAME = 'GDPICTURE'"
        Return Database.GetScalarValue(oSQL)?.ToString
    End Function

    Private Sub buttonSearch_ItemClick(sender As Object, e As DevExpress.XtraBars.ItemClickEventArgs) Handles buttonSearch.ItemClick
        If cmbSearchKeys.EditValue Is Nothing Then
            Exit Sub
        End If
        Dim oAttributeId As Integer = cmbSearchKeys.EditValue
        Dim oAttributeValue As String = TextEdit1.EditValue.ToString

        LoadData(oAttributeId, oAttributeValue)
    End Sub

    Private Function LoadData(pAttribute As Integer, pValue As String) As Boolean
        Try
            TreeListResults.ShowLoadingPanel()
            SplitContainerContent.Enabled = False

            Dim oSQL As String = $"EXEC [{ConfigManager.Config.IDB_Praefix}].[dbo].[PRDD_MONITORING_GET_TREEVIEW_RESULT] '{pAttribute}','{pValue}',1"
            Dim oTable As DataTable = Database.GetDatatable(oSQL)
            Dim oStateColumn As TreeListColumn = TreeListResults.Columns.Item("STATE")

            TreeListResults.DataSource = oTable
            TreeListResults.PopulateColumns()

            InitTreeList()

            ' Show all columns in DisplayColumns List
            For Each oColumn In TreeListResults.Columns
                oColumn.Visible = DisplayColumns.Contains(oColumn.FieldName)
                If oColumn.FieldName = "ADDED_WHEN" Then
                    oColumn.Format.FormatType = FormatType.DateTime
                    oColumn.Format.FormatString = "dd.MM.yyyy  HH:mm:ss"
                End If

            Next

            For Each oNode As TreeListNode In TreeListResults.Nodes
                ExpandNodes(oNode, Function(n)
                                       Dim oObjectValue = n.GetValue(oStateColumn)?.ToString
                                       Dim oValue As String = NotNull(oObjectValue, String.Empty)
                                       Return oValue IsNot Nothing AndAlso (oValue = STATE_WARNING Or oValue = STATE_FAILURE)
                                   End Function)
            Next
            Return True
        Catch ex As Exception
            ShowErrorMessage(ex)
            Return False
        Finally
            SplitContainerContent.Enabled = False
            TreeListResults.HideLoadingPanel()
        End Try
    End Function

    Private Sub LoadSearchKeys()
        Try
            Dim oSQL = ConfigManager.Config.SearchKeySQL
            Dim oTable = Database.GetDatatable(oSQL)

            For Each oRow As DataRow In oTable.Rows
                SearchKeys.Add(New SearchKey With {
                    .Id = CInt(oRow.Item(0)),
                    .Title = oRow.Item(1).ToString,
                    .TypeName = "Varchar"'oRow.Item(2).ToString
                })
            Next

            cmbSearchKeys.Properties.Items.Clear()
            cmbSearchKeys.Properties.Items.AddRange(SearchKeys)
        Catch ex As Exception
            ShowErrorMessage(ex)
        End Try
    End Sub

    Private Sub InitTreeList()
        TreeListResults.KeyFieldName = "GUID"
        TreeListResults.ParentFieldName = "PARENT_ID"

        Dim oStateEdit As New RepositoryItemImageComboBox With {
            .SmallImages = SvgImageCollection1,
            .GlyphAlignment = HorzAlignment.Near
        }
        oStateEdit.Buttons.Clear()
        oStateEdit.Items.AddRange(New List(Of ImageComboBoxItem) From {
            New ImageComboBoxItem("Success", "SUCCESS", NodeImage.Success),
            New ImageComboBoxItem("Failure", "FAILURE", NodeImage.Failure),
            New ImageComboBoxItem("Warning", "WARNING", NodeImage.Warning),
            New ImageComboBoxItem("Waiting", "WAITING", NodeImage.Waiting),
            New ImageComboBoxItem("Default", "DEFAULT", NodeImage.Default),
            New ImageComboBoxItem("User", "USER", NodeImage.User),
            New ImageComboBoxItem("Highlight", "HIGHLIGHT", NodeImage.Highlight)
        })

        Dim oIconEdit As New RepositoryItemImageComboBox With {
            .SmallImages = SvgImageCollection1,
            .GlyphAlignment = HorzAlignment.Near
        }
        oStateEdit.Buttons.Clear()
        oStateEdit.Items.AddRange(New List(Of ImageComboBoxItem) From {
            New ImageComboBoxItem("Email", "MAIL", NodeImage.Mail),
            New ImageComboBoxItem("SQL", "SQL", NodeImage.SQL),
            New ImageComboBoxItem("File", "FILE", NodeImage.File)
        })

        Dim oColumn1 = TreeListResults.Columns.Item("COLUMN1")
        Dim oStateColumn = TreeListResults.Columns.Item("STATE")
        Dim oIconColumn = TreeListResults.Columns.Item("ICON")

        oColumn1.VisibleIndex = 0
        oStateColumn.VisibleIndex = 1
        oIconColumn.VisibleIndex = 2

        With oStateColumn
            .ColumnEdit = oStateEdit
            .MaxWidth = 25
            .MinWidth = 25
            .Caption = " "
            .ImageOptions.Image = SvgImageCollection1.GetImage(NodeImage.Success)
        End With

        With oIconColumn
            .ColumnEdit = oStateEdit
            .MaxWidth = 25
            .MinWidth = 25
            .Caption = " "
            .ImageOptions.Image = SvgImageCollection1.GetImage(NodeImage.SQL)
        End With

    End Sub

    Private Sub cmbSearchKeys_SelectedValueChanged(sender As Object, e As EventArgs) Handles cmbSearchKeys.SelectedValueChanged
        Dim oItem As SearchKey = CType(cmbSearchKeys.SelectedItem, SearchKey)

        Select Case oItem.TypeName
            Case "VARCHAR"
                LayoutItemSearchValue_Date.Visibility = DevExpress.XtraLayout.Utils.LayoutVisibility.Never
                LayoutItemSearchValue_Text.Visibility = DevExpress.XtraLayout.Utils.LayoutVisibility.Always
                TextEdit1.EditValue = String.Empty
            Case "DATE"
                LayoutItemSearchValue_Date.Visibility = DevExpress.XtraLayout.Utils.LayoutVisibility.Always
                LayoutItemSearchValue_Text.Visibility = DevExpress.XtraLayout.Utils.LayoutVisibility.Never

            Case Else
                LayoutItemSearchValue_Date.Visibility = DevExpress.XtraLayout.Utils.LayoutVisibility.Never
                LayoutItemSearchValue_Text.Visibility = DevExpress.XtraLayout.Utils.LayoutVisibility.Always
                BarStaticItem1.Caption = "oItem.TypeName=" + oItem.TypeName

        End Select
    End Sub

    Private Sub TreeListResults_FocusedNodeChanged(sender As Object, e As DevExpress.XtraTreeList.FocusedNodeChangedEventArgs) Handles TreeListResults.FocusedNodeChanged
        If e.Node Is Nothing Then
            Exit Sub
        End If

        Dim oValues As Dictionary(Of String, String) = GetValuesFromNode(e.Node, DataColumns)

        Dim oSQLCommands = oValues.
            Where(Function(v) v.Key.StartsWith("SELECT")).
            Where(Function(v) v.Value IsNot Nothing).
            ToDictionary(Function(v) v.Key, Function(v) v.Value)

        Dim oFilePaths = oValues.
            Where(Function(v) v.Key.StartsWith("DOCVIEW")).
            Where(Function(v) v.Value IsNot Nothing).
            ToDictionary(Function(v) v.Key, Function(v) v.Value)

        Dim oHtmlDocuments = oValues.
            Where(Function(v) v.Key.StartsWith("HTML")).
            Where(Function(v) v.Value IsNot Nothing).
            ToDictionary(Function(v) v.Key, Function(v) v.Value)

        Dim oAllTabs = SQLResultTabs.
            Concat(FileResultTabs).
            Concat(HtmlResultTabs)
        For Each oTabPage In oAllTabs
            oTabPage.PageVisible = False
        Next

        SplitContainerControl3.Collapsed = oSQLCommands.Count = 0
        SplitContainerMain.Collapsed = (oFilePaths.Count + oHtmlDocuments.Count) = 0

        For Each oSQLCommand As KeyValuePair(Of String, String) In oSQLCommands
            Try
                Dim oExtracted = ExtractTitle(oSQLCommand.Value)
                Dim oCommand = oExtracted.Item1
                Dim oTitle = oExtracted.Item2

                Dim oTable As DataTable = Database.GetDatatable(oCommand)

                If oTable Is Nothing Then
                    Continue For
                End If

                Dim oGridIndex = Integer.Parse(oSQLCommand.Key.Last()) - 1
                Dim oGridControl As GridControl = SQLResultGrids.Item(oGridIndex)

                FillResultGrid(oGridControl, oTable, oTitle)
            Catch ex As Exception
                Logger.Error(ex)
                Continue For
            End Try
        Next

        For Each oFile As KeyValuePair(Of String, String) In oFilePaths
            Try
                Dim oExtracted = ExtractTitle(oFile.Value)
                Dim oPath = oExtracted.Item1
                Dim oTitle = oExtracted.Item2

                If oPath Is Nothing Then
                    Continue For
                End If

                If Not IO.File.Exists(oPath) Then
                    Continue For
                End If

                Dim oViewerIndex = Integer.Parse(oFile.Key.Last()) - 1
                Dim oViewer As DocumentViewer = FileResultViewers.Item(oViewerIndex)

                FillResultViewer(oViewer, oPath, oTitle)
            Catch ex As Exception
                Logger.Error(ex)
                Continue For
            End Try
        Next

        For Each oFile As KeyValuePair(Of String, String) In oHtmlDocuments
            Try
                Dim oExtracted = ExtractTitle(oFile.Value)
                Dim oHtml = oExtracted.Item1
                Dim oTitle = oExtracted.Item2

                If oHtml Is Nothing Then
                    Continue For
                End If

                Dim oViewerIndex = Integer.Parse(oFile.Key.Last()) - 1
                Dim oViewer As RichEditControl = HtmlResultViewers.Item(oViewerIndex)

                FillResultHtmlViewer(oViewer, oHtml, oTitle)
            Catch ex As Exception
                Logger.Error(ex)
                Continue For
            End Try
        Next
    End Sub

    Private Function ExtractTitle(Value As String) As Tuple(Of String, String)
        If Value.Contains("|"c) Then
            Dim oSplit = Value.Split("|"c).ToList
            Dim oValue = oSplit.First()
            Dim oTitle = oSplit.Item(1)

            Return New Tuple(Of String, String)(oValue, oTitle)
        End If

        Return New Tuple(Of String, String)(Value, Nothing)
    End Function

    Private Sub ExpandNodes(RootNode As TreeListNode, Condition As Predicate(Of TreeListNode))
        For Each oNode As TreeListNode In RootNode.Nodes
            ExpandNodes(oNode, Condition)

            If Condition(oNode) = True Then
                oNode.Expand()
                ExpandParentNode(oNode)
            End If
        Next
    End Sub

    Private Sub ExpandParentNode(ChildNode As TreeListNode)
        If ChildNode.ParentNode IsNot Nothing Then
            ChildNode.ParentNode.Expand()
            ExpandParentNode(ChildNode.ParentNode)
        End If
    End Sub

    Private Sub FillResultHtmlViewer(RichEditControl As RichEditControl, Html As String, Title As String)
        RichEditControl.HtmlText = Html

        Dim oTabPage = DirectCast(RichEditControl.Parent, XtraTabPage)
        oTabPage.PageVisible = True
        oTabPage.Text = NotNull(Title, oTabPage.Text)

        XtraTabControl1.SelectedTabPage = oTabPage
    End Sub

    Private Sub FillResultViewer(DocumentViewer As DocumentViewer, Path As String, Title As String)
        DocumentViewer.LoadFile(Path)

        Dim oTabPage = DirectCast(DocumentViewer.Parent, XtraTabPage)
        oTabPage.PageVisible = True
        oTabPage.Text = NotNull(Title, oTabPage.Text)

        XtraTabControl1.SelectedTabPage = oTabPage
    End Sub

    Private Sub FillResultGrid(GridControl As GridControl, Table As DataTable, Title As String)
        GridControl.DataSource = Table

        Dim oTabPage = DirectCast(GridControl.Parent, XtraTabPage)
        oTabPage.PageVisible = True
        oTabPage.Text = NotNull(Title, oTabPage.Text)

        XtraTabControl3.SelectedTabPage = oTabPage
    End Sub

    Private Function GetValuesFromNode(Node As TreeListNode, ColumnNames As List(Of String)) As Dictionary(Of String, String)
        Dim oValues As New Dictionary(Of String, String)

        For Each oColumnName In ColumnNames
            Dim oValue = MaybeGetValueForColumn(Node, oColumnName)
            oValues.Add(oColumnName, oValue)
        Next

        Return oValues
    End Function

    Private Function MaybeGetValueForColumn(Node As TreeListNode, FieldName As String) As String
        Dim oColumn = TreeListResults.Columns.Item(FieldName)

        If oColumn Is Nothing Then
            Return Nothing
        End If

        Dim oValue = Node.GetValue(oColumn)?.ToString

        If oValue Is String.Empty Then
            Return Nothing
        Else
            Return oValue
        End If
    End Function

    Private Sub TreeListResults_CustomDrawNodeCell(sender As Object, e As DevExpress.XtraTreeList.CustomDrawNodeCellEventArgs) Handles TreeListResults.CustomDrawNodeCell
        Dim oColumn = TreeListResults.Columns.Item("STATE")
        Dim oState = NotNull(e.Node.GetValue(oColumn), Nothing)

        If oState Is Nothing Then
            Exit Sub
        End If

        Dim oColor As Color = Nothing

        Select Case oState.ToString
            Case STATE_SUCCESS
                oColor = Color.LightGreen
            Case STATE_FAILURE
                oColor = Color.LightCoral
            Case STATE_WARNING
                oColor = Color.Yellow
            Case STATE_WAITING
                oColor = Color.LightSkyBlue
        End Select

        e.Appearance.BackColor = oColor
        e.Appearance.Options.UseBackColor = True
        e.Handled = False
    End Sub

    Private Sub TreeListResults_GetStateImage(sender As Object, e As DevExpress.XtraTreeList.GetStateImageEventArgs) Handles TreeListResults.GetStateImage
        'Dim oValues As Dictionary(Of String, String) = GetValuesFromNode(e.Node, DataColumns)
        'Dim oSQLCommands = oValues.
        '    Where(Function(v) v.Key.StartsWith("SELECT")).
        '    Where(Function(v) v.Value IsNot Nothing).
        '    Count()

        'Dim oFilePaths = oValues.
        '    Where(Function(v) v.Key.StartsWith("DOCVIEW")).
        '    Where(Function(v) v.Value IsNot Nothing).
        '    Count()

        'Dim oHtmlDocuments = oValues.
        '    Where(Function(v) v.Key.StartsWith("HTML")).
        '    Where(Function(v) v.Value IsNot Nothing).
        '    Count()

        'Dim oIconColumn = TreeListResults.Columns.Item("ICON")
        'Dim oIcon = NotNull(e.Node.GetValue(oIconColumn), Nothing)
        'Dim oStateColumn = TreeListResults.Columns.Item("STATE")
        'Dim oState = NotNull(e.Node.GetValue(oStateColumn), Nothing)

        'If oSQLCommands > 0 Then
        '    e.NodeImageIndex = NodeImage.SQL
        'ElseIf oFilePaths > 0 Then
        '    e.NodeImageIndex = NodeImage.File
        'ElseIf oHtmlDocuments > 0 Then
        '    e.NodeImageIndex = NodeImage.Mail
        'Else
        '    If oState IsNot Nothing AndAlso StateIcons.ContainsKey(oState) Then
        '        Dim oIconIndex = StateIcons.Item(oState)
        '        e.NodeImageIndex = oIconIndex
        '    Else
        '        e.NodeImageIndex = NodeImage.Default
        '    End If
        'End If
    End Sub

    Private Sub BarButtonItem1_ItemClick(sender As Object, e As DevExpress.XtraBars.ItemClickEventArgs) Handles BarButtonItem1.ItemClick
        Dim oSQLConfig As New DigitalData.Controls.SQLConfig.frmSQLConfig(LogConfig)
        If oSQLConfig.ShowDialog() = DialogResult.OK Then
            ConfigManager.Config.ConnectionString = oSQLConfig.ConnectionString
            ConfigManager.Save()

            Database = New MSSQLServer(LogConfig, oSQLConfig.ConnectionString)

            LoadSearchKeys()
        End If
    End Sub

    Private Sub BarButtonItem2_ItemClick(sender As Object, e As DevExpress.XtraBars.ItemClickEventArgs) Handles btnExportGrid.ItemClick
        If ActiveSQLResultGrid IsNot Nothing Then
            XtraSaveFileDialog1.Filter = "Excel Files (*.xlsx)|*.xlsx"

            If XtraSaveFileDialog1.ShowDialog() = DialogResult.OK Then
                ActiveSQLResultGrid.ExportToXlsx(XtraSaveFileDialog1.FileName)
            End If
        End If
    End Sub

    Private Sub BarButtonItem3_ItemClick(sender As Object, e As DevExpress.XtraBars.ItemClickEventArgs) Handles btnExportTreeview.ItemClick
        XtraSaveFileDialog1.Filter = "Excel Files (*.xlsx)|*.xlsx"

        If XtraSaveFileDialog1.ShowDialog() = DialogResult.OK Then
            TreeListResults.ExportToXlsx(XtraSaveFileDialog1.FileName)
        End If
    End Sub

    Private Sub TextEdit1_KeyDown(sender As Object, e As KeyEventArgs) Handles TextEdit1.KeyDown
        If e.KeyCode = Keys.Enter Or e.KeyCode = Keys.F5 Then
            Dim oAttributeId As Integer = cmbSearchKeys.EditValue
            Dim oAttributeValue As String = TextEdit1.EditValue.ToString

            LoadData(oAttributeId, oAttributeValue)
        End If
    End Sub
End Class

Friend Class SearchKey
    Public Id As Integer
    Public Title As String
    Public TypeName As String

    Public Overrides Function ToString() As String
        Return Title
    End Function
End Class
