Imports System.Reflection
Imports System.ComponentModel
Imports System.IO

Imports Stax.Win32
Imports Stax.ComponentModel

Namespace UI
    Public Class SettingTreeView
        Inherits ExtendedTreeView

        Private LastSelected As TreeNode

        Public Sub New()
            HideSelection = False
            SetStyle(ControlStyles.EnableNotifyMessage, True)
        End Sub

        Private SettingsValue As New List(Of TreeNodeSetting)

        <Browsable(False), _
        DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)> _
        Public Property Settings() As List(Of TreeNodeSetting)
            Get
                Return SettingsValue
            End Get
            Set(ByVal Value As List(Of TreeNodeSetting))
                SettingsValue = Value
            End Set
        End Property

        Public Sub AddSetting(ByVal setting As TreeNodeSetting)
            If ImageList Is Nothing Then 'in ctor will crash IDE
                Dim il As New ImageList
                il.Images.Add("CheckBoxChecked16", My.Resources.CheckBoxChecked16)
                il.Images.Add("CheckBoxUnchecked16", My.Resources.CheckBoxUnchecked16)
                il.Images.Add("Dropdown16", My.Resources.Dropdown16)
                il.Images.Add("FolderGrey16", My.Resources.FolderGrey16)
                il.Images.Add("FolderYellow16", My.Resources.FolderYellow16)
                il.Images.Add("NumericTextBox16", My.Resources.NumericTextBox16)
                il.Images.Add("OpenFile16", My.Resources.OpenFile16)
                il.Images.Add("TextBox16", My.Resources.TextBox16)

                ImageKey = "FolderGrey16"
                SelectedImageKey = "FolderGrey16"

                ImageList = il
            End If

            Settings.Add(setting)
            setting.Add(Me)
        End Sub

        Public Sub ExpandNode(ByVal name As String)
            For Each i As TreeNode In Nodes
                If i.Text = name Then
                    i.Expand()
                    Exit Sub
                End If
            Next

            If Nodes.Count > 0 Then
                Nodes(0).ExpandAll()
            End If
        End Sub

        Public Sub Apply()
            For Each i As TreeNodeSetting In Settings
                i.Apply()
            Next
        End Sub

        Protected Overrides Sub OnHelpRequested(ByVal e As HelpEventArgs)
            MyBase.OnHelpRequested(e)

            If Not e.Handled Then
                Dim n As TreeNode = GetNodeAt(PointToClient(e.MousePos))

                If Not n Is Nothing AndAlso Not n.Tag Is Nothing Then
                    Dim s As TreeNodeSetting = DirectCast(n.Tag, TreeNodeSetting)

                    If Not s.Description Is Nothing Then
                        Dim p As New PopUp
                        p.Title = s.Text
                        p.Text = s.Description
                        p.Show()
                    End If
                End If
            End If
        End Sub

        Protected Overrides Sub OnClick(ByVal e As EventArgs)
            MyBase.OnClick(e)

            Dim n As TreeNode = GetNodeAt(PointToClient(Control.MousePosition))

            If Not n Is Nothing Then
                DirectCast(n.Tag, TreeNodeSetting).Click()
            End If
        End Sub

        Protected Overrides Sub OnKeyDown(ByVal e As KeyEventArgs)
            MyBase.OnKeyDown(e)

            If Not SelectedNode Is Nothing Then
                DirectCast(SelectedNode.Tag, TreeNodeSetting).KeyDown(e)
            End If
        End Sub

        Protected Overrides Sub OnAfterSelect(ByVal e As TreeViewEventArgs)
            MyBase.OnAfterSelect(e)

            If Not LastSelected Is Nothing Then
                DirectCast(LastSelected.Tag, TreeNodeSetting).Update(Nothing)
            End If

            DirectCast(e.Node.Tag, TreeNodeSetting).Update(Nothing)
            LastSelected = e.Node
        End Sub

        Protected Overrides Sub WndProc(ByRef m As Message)
            If m.Msg = WM.ERASEBKGND Then
                m.Msg = 0
            End If

            MyBase.WndProc(m)

            If m.Msg = WM.HSCROLL OrElse m.Msg = WM.VSCROLL Then
                If Not SelectedNode Is Nothing Then
                    DirectCast(SelectedNode.Tag, TreeNodeSetting).Update(Nothing)
                End If
            End If
        End Sub

        Protected Overrides Sub OnResize(ByVal e As System.EventArgs)
            MyBase.OnResize(e)

            If Not SelectedNode Is Nothing Then
                DirectCast(SelectedNode.Tag, TreeNodeSetting).Update(Nothing)
            End If
        End Sub

        Public Function GetTips() As StringPairList
            Dim ret As New StringPairList

            For Each i As TreeNodeSetting In Settings
                If Not i.Description Is Nothing Then
                    ret.Add(i.Text, i.Description)
                End If
            Next

            Return ret
        End Function
    End Class

    <Serializable()> _
    Public MustInherit Class TreeNodeSetting
        Protected ImageKey As String
        Protected Path As String
        Protected UpdatingControls As Boolean

        Public MustOverride Sub Apply()

        Public Sub New(ByVal text As String, ByVal path As String, ByVal description As String, ByVal imageKey As String)
            Me.Path = path
            Me.Text = text
            Me.Description = description
            Me.ImageKey = imageKey
        End Sub

        Public Overridable Sub Add(ByVal tv As TreeView)
            Dim tn As New TreeNode
            Node = tn
            tn.Tag = Me
            tn.Text = Text
            tn.ImageKey = ImageKey
            tn.SelectedImageKey = ImageKey

            Dim dirNode As TreeNode = TreeViewHelp.AddNode(Path, tv)
            dirNode.Nodes.Add(tn)

            While Not dirNode Is Nothing
                If dirNode.Tag Is Nothing Then
                    Dim setting As New DirTreeNodeSetting
                    setting.Node = dirNode
                    dirNode.Tag = setting
                End If

                dirNode = dirNode.Parent
            End While

            Update(Nothing)
        End Sub

        Private TextValue As String

        Public Property Text() As String
            Get
                Return TextValue
            End Get
            Set(ByVal Value As String)
                TextValue = Value
            End Set
        End Property

        Private DescriptionValue As String

        Public Property Description() As String
            Get
                Return DescriptionValue
            End Get
            Set(ByVal Value As String)
                DescriptionValue = Value
            End Set
        End Property

        Private NodeValue As TreeNode

        Public Property Node() As TreeNode
            Get
                Return NodeValue
            End Get
            Set(ByVal Value As TreeNode)
                NodeValue = Value
            End Set
        End Property

        Public Overridable Sub Click()
        End Sub

        Public Overridable Sub KeyDown(ByVal e As KeyEventArgs)
        End Sub

        Public Overridable Sub Update(ByVal g As Graphics)
        End Sub
    End Class

    Public Class DirTreeNodeSetting
        Inherits TreeNodeSetting

        Public Sub New()
            MyBase.New(Nothing, Nothing, Nothing, "FolderGrey16")
        End Sub

        Public Overrides Sub Apply()
        End Sub
    End Class

    Public Class CheckBoxTreeNodeSetting
        Inherits TreeNodeSetting

        Private Setting As SettingBag(Of Boolean)
        Private Checked As Boolean

        Public Sub New(ByVal text As String, ByVal path As String, ByVal setting As SettingBag(Of Boolean), ByVal description As String)
            MyBase.New(text, path, description, "CheckBoxChecked16")
            Me.Setting = setting
            Checked = setting.Value
        End Sub

        Public Overrides Sub Click()
            Checked = Not Checked
            SetImage()
        End Sub

        Public Overrides Sub KeyDown(ByVal e As KeyEventArgs)
            If e.KeyData = Keys.Space Then
                Click()
            End If
        End Sub

        Public Overrides Sub Add(ByVal tv As TreeView)
            MyBase.Add(tv)
            SetImage()
        End Sub

        Private Sub SetImage()
            If Checked Then
                Node.ImageKey = "CheckBoxChecked16"
                Node.SelectedImageKey = "CheckBoxChecked16"
            Else
                Node.ImageKey = "CheckBoxUnchecked16"
                Node.SelectedImageKey = "CheckBoxUnchecked16"
            End If
        End Sub

        Public Overrides Sub Apply()
            Setting.Value = Checked
        End Sub
    End Class

    Public MustInherit Class TextBoxButtonTreeNodeSetting
        Inherits TextBoxTreeNodeSetting

        Private Button As Button

        Public Sub New(ByVal text As String, ByVal path As String, ByVal setting As SettingBag(Of String), ByVal description As String)
            MyBase.New(text, path, setting, Description)
        End Sub

        Public Overrides Sub Update(ByVal g As Graphics)
            MyBase.Update(g)

            If Not UpdatingControls Then
                UpdatingControls = True

                If Node.IsSelected AndAlso Node.Bounds.Top > 0 Then
                    Dim disposeNeeded As Boolean

                    If g Is Nothing Then
                        g = Node.TreeView.CreateGraphics
                        disposeNeeded = True
                    End If

                    Dim size As SizeF = g.MeasureString(Text, Node.TreeView.Font)

                    If Button Is Nothing Then
                        Button = New Button
                        Button.Width = 24
                        Button.Height = 24
                        Button.Image = Node.TreeView.ImageList.Images(ImageKey)
                        Button.Visible = False
                        AddHandler Button.Click, AddressOf ButtonClick
                        Node.TreeView.Controls.Add(Button)
                    End If

                    Button.Left = TextBox.Left + TextBox.Width + 4
                    Button.Top = Node.Bounds.Top - 4
                    Button.BringToFront()

                    Button.Visible = True
                    Button.Refresh()

                    If disposeNeeded Then
                        g.Dispose()
                    End If
                Else
                    If Not TextBox Is Nothing Then
                        Button.Visible = False
                    End If
                End If

                UpdatingControls = False
            End If
        End Sub

        Private Sub ButtonClick(ByVal sender As Object, ByVal e As EventArgs)
            ButtonClick()
        End Sub

        Protected MustOverride Sub ButtonClick()

        Protected Overrides Function GetTextBoxWidth() As Integer
            Return MyBase.GetTextBoxWidth() - 28
        End Function

        Protected Overrides Sub OnDoubleClick(ByVal sender As Object, ByVal e As EventArgs)
            MyBase.OnDoubleClick(sender, e)
            Button.PerformClick()
        End Sub
    End Class

    Public Class FileBrowserTreeNodeSetting
        Inherits TextBoxButtonTreeNodeSetting

        Private Filter As String

        Public Sub New(ByVal text As String, ByVal path As String, ByVal setting As SettingBag(Of String), ByVal description As String, ByVal filter As String)
            MyBase.New(text, path, setting, description)
            ImageKey = "OpenFile16"
            Me.Filter = filter
        End Sub

        Protected Overrides Sub ButtonClick()
            Dim d As New OpenFileDialog
            d.Filter = Filter

            If File.Exists(Value) Then
                d.FileName = Value
            End If

            If d.ShowDialog = DialogResult.OK Then
                TextBox.Text = d.FileName
            End If
        End Sub
    End Class

    Public Class StringEditorTreeNodeSetting
        Inherits TextBoxButtonTreeNodeSetting

        Public Sub New(ByVal text As String, ByVal path As String, ByVal setting As SettingBag(Of String), ByVal description As String)
            MyBase.New(text, path, setting, description)
            ImageKey = "TextBox16"
        End Sub

        Protected Overrides Sub ButtonClick()
            Dim f As New StringEditorForm
            f.tb.Text = Value

            If f.ShowDialog() = DialogResult.OK Then
                TextBox.Text = StringHelp.FixBreak(f.tb.Text)
            End If
        End Sub
    End Class

    Public Class FolderBrowserTreeNodeSetting
        Inherits TextBoxButtonTreeNodeSetting

        Public Sub New(ByVal text As String, ByVal path As String, ByVal setting As SettingBag(Of String), ByVal description As String)
            MyBase.New(text, path, setting, description)
            ImageKey = "FolderYellow16"
        End Sub

        Protected Overrides Sub ButtonClick()
            Dim d As New FolderBrowserDialog
            d.Description = "Please select a directory."
            d.SelectedPath = Value

            If d.ShowDialog = DialogResult.OK Then
                TextBox.Text = d.SelectedPath
            End If
        End Sub
    End Class

    Public Class TextBoxTreeNodeSetting
        Inherits TreeNodeSetting

        Private Setting As SettingBag(Of String)
        Protected Value As String
        Protected TextBox As TextBox

        Public Sub New(ByVal text As String, ByVal path As String, ByVal setting As SettingBag(Of String), ByVal description As String)
            MyBase.New(text, path, description, "TextBox16")
            Me.Setting = setting
            Value = setting.Value
        End Sub

        Public Overrides Sub Update(ByVal g As Graphics)
            If Not UpdatingControls Then
                UpdatingControls = True

                If Node.IsSelected Then
                    If Node.Text <> Text + ":" Then
                        Node.Text = Text + ":"
                    End If
                End If

                If Node.IsSelected AndAlso Node.Bounds.Top > 0 Then
                    Dim disposeNeeded As Boolean

                    If g Is Nothing Then
                        g = Node.TreeView.CreateGraphics
                        disposeNeeded = True
                    End If

                    Dim size As SizeF = g.MeasureString(Text, Node.TreeView.Font)

                    If TextBox Is Nothing Then
                        TextBox = New TextBox
                        TextBox.Height = 20
                        TextBox.Text = Value.ToString
                        TextBox.Visible = False
                        AddHandler TextBox.TextChanged, AddressOf OnTextChanged
                        AddHandler TextBox.DoubleClick, AddressOf OnDoubleClick
                        Node.TreeView.Controls.Add(TextBox)
                    End If

                    TextBox.Left = CInt(Node.Bounds.Left + size.Width + 6)
                    TextBox.Top = Node.Bounds.Top - 2
                    TextBox.Width = GetTextBoxWidth()
                    TextBox.Visible = True
                    TextBox.Refresh()

                    If disposeNeeded Then
                        g.Dispose()
                    End If
                Else
                    If Node.Text <> Text + ": " + Value.ToString Then
                        Node.Text = Text + ": " + Value.ToString
                    End If

                    If Not TextBox Is Nothing Then
                        TextBox.Visible = False
                    End If
                End If

                UpdatingControls = False
            End If
        End Sub

        Private Sub OnTextChanged(ByVal sender As Object, ByVal e As EventArgs)
            Value = TextBox.Text
        End Sub

        Protected Overridable Sub OnDoubleClick(ByVal sender As Object, ByVal e As EventArgs)
        End Sub

        Protected Overridable Function GetTextBoxWidth() As Integer
            Return Node.TreeView.ClientSize.Width - TextBox.Left - 4
        End Function

        Public Overrides Sub Apply()
            Setting.Value = Value
        End Sub
    End Class

    Public Class NumericTreeNodeSetting(Of T)
        Inherits TreeNodeSetting

        Private Setting As SettingBag(Of T)
        Private Value As T
        Private NumericUpDown As NumericUpDown
        Private Min As Integer
        Private Max As Integer
        Private [Step] As Single

        Public Sub New(ByVal text As String, ByVal path As String, ByVal setting As SettingBag(Of T), ByVal description As String)
            Me.New(text, path, setting, description, Int32.MinValue, Int32.MaxValue, 1)
        End Sub

        Public Sub New(ByVal text As String, ByVal path As String, ByVal setting As SettingBag(Of T), ByVal description As String, ByVal min As Integer, ByVal max As Integer, ByVal [step] As Single)
            MyBase.New(text, path, description, "NumericTextBox16")

            If Not GetType(T) Is GetType(Integer) AndAlso _
                Not GetType(T) Is GetType(Double) Then

                Throw New Exception("integer or double required as type")
            End If

            Me.Setting = setting
            Me.Max = max
            Me.Min = min
            Me.Step = [step]

            Value = setting.Value
        End Sub

        Public Overrides Sub Update(ByVal g As Graphics)
            If Not UpdatingControls Then
                UpdatingControls = True

                If Node.IsSelected Then
                    If Node.Text <> Text + ":" Then
                        Node.Text = Text + ":"
                    End If
                End If

                If Node.IsSelected AndAlso Node.Bounds.Top > 0 Then
                    Dim disposeNeeded As Boolean

                    If g Is Nothing Then
                        g = Node.TreeView.CreateGraphics
                        disposeNeeded = True
                    End If

                    Dim size As SizeF

                    If NumericUpDown Is Nothing Then
                        NumericUpDown = New NumericUpDown

                        NumericUpDown.Width = CInt(g.MeasureString(Max.ToString, Node.TreeView.Font).Width + 25)
                        NumericUpDown.Height = 20
                        NumericUpDown.Maximum = Max
                        NumericUpDown.Minimum = Min
                        NumericUpDown.Increment = CDec(Me.Step)
                        NumericUpDown.Visible = False

                        Dim o As Object = Value
                        NumericUpDown.Value = CDec(o)
                        Node.TreeView.Controls.Add(NumericUpDown)

                        AddHandler NumericUpDown.ValueChanged, AddressOf ValueChanged
                        AddHandler NumericUpDown.KeyUp, AddressOf KeyUp
                    End If

                    size = g.MeasureString(Text, Node.TreeView.Font)

                    NumericUpDown.Left = CInt(Node.Bounds.Left + size.Width + 6)
                    NumericUpDown.Top = Node.Bounds.Top - 2

                    NumericUpDown.Visible = True
                    NumericUpDown.Refresh()

                    If disposeNeeded Then
                        g.Dispose()
                    End If
                Else
                    If Node.Text <> Text + ": " + Value.ToString Then
                        Node.Text = Text + ": " + Value.ToString
                    End If

                    If Not NumericUpDown Is Nothing Then
                        NumericUpDown.Visible = False
                    End If
                End If

                UpdatingControls = False
            End If
        End Sub

        Private Sub ValueChanged(ByVal sender As Object, ByVal e As EventArgs)
            Dim o As Object = NumericUpDown.Value
            Value = CType(o, T)
        End Sub

        <DebuggerNonUserCode()> _
        Private Sub KeyUp(ByVal sender As Object, ByVal e As KeyEventArgs)
            Try
                Dim o As Object = NumericUpDown.Text
                Value = CType(o, T)
            Catch ex As Exception
            End Try
        End Sub

        Public Overrides Sub Apply()
            Setting.Value = Value
        End Sub
    End Class

    Public Class ComboboxTreeNodeSetting(Of T)
        Inherits TreeNodeSetting

        Private Items As New List(Of Object)
        Private ComboBox As ComboBox
        Private Value As T
        Private Setting As SettingBag(Of T)

        Public Sub New(ByVal text As String, ByVal path As String, ByVal setting As SettingBag(Of T), ByVal description As String, ByVal ParamArray values As T())
            MyBase.New(text, path, description, "Dropdown16")

            Value = setting.Value
            Me.Setting = setting

            If values.Length > 0 Then
                For Each i As T In values
                    Items.Add(i)
                Next
            ElseIf GetType(T).IsEnum Then
                For Each i As FieldInfo In GetType(T).GetFields
                    Dim attributes As Object() = i.GetCustomAttributes(GetType(NameAttribute), True)

                    If attributes.Length > 0 Then
                        AddItem(DirectCast(attributes(0), NameAttribute).Value, DirectCast(i.GetValue(Value), T))
                    Else
                        If i.Name <> "value__" Then
                            AddItem(i.Name, DirectCast(i.GetValue(Value), T))
                        End If
                    End If
                Next
            End If
        End Sub

        Public Overrides Sub Update(ByVal g As Graphics)
            If Not UpdatingControls Then
                UpdatingControls = True

                Dim displayText As String = Nothing

                If Node.IsSelected Then
                    If Node.Text <> Text + ":" Then
                        Node.Text = Text + ":"
                    End If
                End If

                If Node.IsSelected AndAlso Node.Bounds.Top > 0 Then
                    Dim disposeNeeded As Boolean

                    If g Is Nothing Then
                        g = Node.TreeView.CreateGraphics
                        disposeNeeded = True
                    End If

                    Dim size As SizeF

                    If ComboBox Is Nothing Then
                        ComboBox = New ComboBox
                        ComboBox.Visible = False
                        ComboBox.Height = 20
                        ComboBox.Width = 50
                        ComboBox.MaxDropDownItems = 20
                        ComboBox.DropDownStyle = ComboBoxStyle.DropDownList

                        If Items.Count > 0 AndAlso TypeOf Items(0) Is ListBag(Of T) Then
                            Dim sel As ListBag(Of T) = Nothing

                            For Each i As ListBag(Of T) In Items
                                ComboBox.Items.Add(i)

                                size = g.MeasureString(i.Text, Node.TreeView.Font)

                                If size.Width + 25 > ComboBox.Width Then
                                    ComboBox.Width = CInt(size.Width) + 25
                                End If

                                If Value.Equals(i.Value) Then
                                    sel = i
                                End If
                            Next

                            If Not sel Is Nothing Then
                                ComboBox.SelectedItem = sel
                            End If
                        Else
                            For Each i As T In Items
                                size = g.MeasureString(i.ToString, Node.TreeView.Font)

                                If size.Width + 25 > ComboBox.Width Then
                                    ComboBox.Width = CInt(size.Width) + 25
                                End If

                                ComboBox.Items.Add(i)
                            Next

                            ComboBox.SelectedItem = Value
                        End If

                        AddHandler ComboBox.TextChanged, AddressOf TextChanged
                        AddHandler ComboBox.SelectedIndexChanged, AddressOf SelectedIndexChanged
                        Node.TreeView.Controls.Add(ComboBox)
                    End If

                    size = g.MeasureString(Text, Node.TreeView.Font)

                    ComboBox.Left = CInt(Node.Bounds.Left + size.Width + 6)
                    ComboBox.Top = Node.Bounds.Top - 2

                    ComboBox.Visible = True
                    ComboBox.Refresh()

                    If disposeNeeded Then
                        g.Dispose()
                    End If
                Else
                    If Items.Count > 0 AndAlso TypeOf Items(0) Is ListBag(Of T) Then
                        For Each i As ListBag(Of T) In Items
                            If i.Value.Equals(Value) Then
                                displayText = i.Text
                            End If
                        Next
                    Else
                        displayText = Value.ToString
                    End If

                    If Node.Text <> Text + ": " + displayText Then
                        Node.Text = Text + ": " + displayText
                    End If

                    If Not ComboBox Is Nothing Then
                        ComboBox.Visible = False
                    End If
                End If

                UpdatingControls = False
            End If
        End Sub

        Private Sub SelectedIndexChanged(ByVal sender As Object, ByVal e As EventArgs)
            If Not ComboBox.SelectedItem Is Nothing Then
                If TypeOf ComboBox.SelectedItem Is ListBag(Of T) Then
                    Value = DirectCast(ComboBox.SelectedItem, ListBag(Of T)).Value
                Else
                    Value = DirectCast(ComboBox.SelectedItem, T)
                End If
            End If
        End Sub

        <DebuggerNonUserCode()> _
        Private Sub TextChanged(ByVal sender As Object, ByVal e As EventArgs)
            Try
                Dim o As Object = ComboBox.Text
                Value = CType(o, T)
            Catch ex As Exception
            End Try
        End Sub

        Public Sub ClearItems()
            Items.Clear()
        End Sub

        Public Sub AddItem(ByVal name As String, ByVal value As T)
            Items.Add(New ListBag(Of T)(name, value))
        End Sub

        Public Overrides Sub Apply()
            Setting.Value = Value
        End Sub
    End Class
End Namespace