Imports System.Threading
Imports System.Runtime.InteropServices
Imports System.ComponentModel
Imports System.Drawing.Design
Imports System.Reflection
Imports System.Text

Imports Stax.UI
Imports Stax.Win32
Imports Stax.ComponentModel

Namespace UI
    <Serializable()> _
    Public Class GenericMenuItem
        Public Sub New(ByVal text As String)
            Me.Text = text
            SubItemsValue = New List(Of GenericMenuItem)
            Parameters = New List(Of Object)
        End Sub

        <NonSerialized()> _
        Private GenericMenuValue As GenericMenu

        Public Property GenericMenu() As GenericMenu
            Get
                Return GenericMenuValue
            End Get
            Set(ByVal Value As GenericMenu)
                GenericMenuValue = Value
            End Set
        End Property

        <NonSerialized()> _
        Private MethodItemValue As MethodItem

        Public ReadOnly Property MethodItem() As MethodItem
            Get
                If Not MethodItemValue Is Nothing Then
                    Return MethodItemValue
                End If

                For Each i As MethodItem In GenericMenu.MethodItems
                    If i.MethodInfo.Name = MethodName Then
                        MethodItemValue = i
                        Return i
                    End If
                Next

                Return Nothing
            End Get
        End Property

        Private TextValue As String

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

        Private SubItemsValue As List(Of GenericMenuItem)

        Public Property SubItems() As List(Of GenericMenuItem)
            Get
                Return SubItemsValue
            End Get
            Set(ByVal Value As List(Of GenericMenuItem))
                SubItemsValue = Value
            End Set
        End Property

        Private KeyDataValue As Keys

        Public Property KeyData() As Keys
            Get
                Return KeyDataValue
            End Get
            Set(ByVal Value As Keys)
                KeyDataValue = Value
            End Set
        End Property

        Private MethodNameValue As String = ""

        Public Property MethodName() As String
            Get
                Return MethodNameValue
            End Get
            Set(ByVal Value As String)
                MethodNameValue = Value
            End Set
        End Property

        Private ParametersValue As List(Of Object)

        Public Property Parameters() As List(Of Object)
            Get
                Return ParametersValue
            End Get
            Set(ByVal Value As List(Of Object))
                ParametersValue = Value
            End Set
        End Property

        Public Function GetClone() As GenericMenuItem
            Return CType(ObjectHelp.GetCopy(Me), GenericMenuItem)
        End Function
    End Class

    Public Class GenericMenu
        Private Items As New List(Of GenericMenuItem)
        Private TipTarget As TipTarget

        Public Event Trigger(ByVal item As GenericMenuItem)
        Public Event Topic(ByVal item As GenericMenuItem)

        Public Sub New( _
            ByVal defaultMenu As FunctionDelegate(Of GenericMenuItem), _
            ByVal menuItem As GenericMenuItem, _
            ByVal form As Form, _
            ByVal toolStrip As ToolStrip, _
            ByVal tipTarget As TipTarget)

            Me.DefaultMenu = defaultMenu
            Me.MenuItem = menuItem
            Me.Form = form
            Me.TipTarget = tipTarget
            Me.ToolStrip = toolStrip

            AddHandler form.KeyDown, AddressOf HandleFormKeyDown

            For Each i As MethodInfo In form.GetType.GetMethods
                Dim attributes As GenericMenuMethodAttribute() = CType(i.GetCustomAttributes(GetType(GenericMenuMethodAttribute), False), GenericMenuMethodAttribute())

                If attributes.Length > 0 Then
                    Dim mi As New MethodItem

                    mi.MethodInfo = i
                    mi.Attribute = attributes(0)
                    mi.DefaultParams = New List(Of Object)

                    For Each iParameterInfo As ParameterInfo In i.GetParameters
                        If Not iParameterInfo.ParameterType.IsValueType AndAlso _
                            Not iParameterInfo.ParameterType Is GetType(String) Then

                            Throw New Exception("GenericMenu methods must have string or value type params.")
                        End If

                        Dim a As DefaultValueAttribute() = CType(iParameterInfo.GetCustomAttributes(GetType(DefaultValueAttribute), False), DefaultValueAttribute())

                        If a.Length > 0 Then
                            mi.DefaultParams.Add(a(0).Value)
                        Else
                            If iParameterInfo.ParameterType Is GetType(String) Then
                                mi.DefaultParams.Add("")
                            ElseIf iParameterInfo.ParameterType.IsValueType Then
                                mi.DefaultParams.Add(Activator.CreateInstance(iParameterInfo.ParameterType))
                            End If
                        End If
                    Next

                    MethodItems.Add(mi)
                End If
            Next
        End Sub

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

            For Each i As ExtendedToolStripMenuItem In MenuItems
                If i.ShortcutKeyDisplayString <> "" Then
                    Dim sp As New StringPair

                    If i.Text.EndsWith("...") Then
                        sp.Name = i.Text.TrimEnd("."c)
                    Else
                        sp.Name = i.Text
                    End If

                    sp.Value = i.ShortcutKeyDisplayString
                    ret.Add(sp)
                End If
            Next

            Return ret
        End Function

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

            For Each i As ExtendedToolStripMenuItem In MenuItems
                If Not i.GenericMenuItem.MethodItem Is Nothing Then
                    Dim sp As New StringPair

                    If i.Text.EndsWith("...") Then
                        sp.Name = i.Text.TrimEnd("."c)
                    Else
                        sp.Name = i.Text
                    End If

                    sp.Value = i.GenericMenuItem.MethodItem.Attribute.Description

                    If i.GenericMenuItem.Parameters.Count > 0 Then
                        Dim paramList As New List(Of String)
                        Dim params As ParameterInfo() = i.GenericMenuItem.MethodItem.MethodInfo.GetParameters

                        For iParams As Integer = 0 To params.Length - 1
                            Dim paramInfo As ParameterInfo = params(iParams)
                            Dim attributs As Object() = paramInfo.GetCustomAttributes(GetType(NameAttribute), False)

                            If attributs.Length > 0 Then
                                paramList.Add(DirectCast(attributs(0), NameAttribute).Value + " = " + i.GenericMenuItem.Parameters(iParams).ToString)
                            End If
                        Next

                        sp.Value += " (" + String.Join(", ", paramList.ToArray) + ")"
                    End If

                    ret.Add(sp)
                End If
            Next

            Return ret
        End Function

        Public Function Edit() As GenericMenuItem
            Dim f As New GenericMenuEditor(Me)

            If f.ShowDialog = DialogResult.OK Then
                MenuItem = f.GetState
                BuildMenu()
            End If

            f.Dispose()

            Return MenuItem
        End Function

        Public Sub Invoke(ByVal item As GenericMenuItem)
            If Not item.MethodName Is Nothing AndAlso item.MethodName <> "" Then
                If item.Parameters.Count = 0 Then
                    CType([Delegate].CreateDelegate(GetType(SubDelegate), Form, item.MethodName), SubDelegate).Invoke()
                Else
                    GetMethod(item.MethodName).Invoke(Form, item.Parameters.ToArray)
                End If
            End If
        End Sub

        Public Sub HandleFormKeyDown(ByVal sender As Object, ByVal e As KeyEventArgs)
            For Each i As GenericMenuItem In Items
                If i.KeyData = e.KeyData Then
                    RaiseEvent Trigger(i)
                End If
            Next
        End Sub

        Public Sub HandleMenuClick(ByVal sender As Object, ByVal e As EventArgs)
            If TypeOf sender Is ExtendedToolStripMenuItem Then
                RaiseEvent Trigger(DirectCast(sender, ExtendedToolStripMenuItem).GenericMenuItem)
            End If
        End Sub

        Public Sub HandleMouseDown(ByVal sender As Object, ByVal e As MouseEventArgs)
            If TypeOf sender Is ExtendedToolStripMenuItem AndAlso _
                e.Button = MouseButtons.Right Then
                RaiseEvent Topic(DirectCast(sender, ExtendedToolStripMenuItem).GenericMenuItem)
            End If
        End Sub

        Private MenuValue As Menu

        Public Property Menu() As Menu
            Get
                Return MenuValue
            End Get
            Set(ByVal Value As Menu)
                MenuValue = Value
            End Set
        End Property

        Private MenuStripValue As MenuStrip

        Public Property MenuStrip() As MenuStrip
            Get
                Return MenuStripValue
            End Get
            Set(ByVal Value As MenuStrip)
                MenuStripValue = Value
            End Set
        End Property

        Private ToolStripValue As ToolStrip

        Public Property ToolStrip() As ToolStrip
            Get
                Return ToolStripValue
            End Get
            Set(ByVal Value As ToolStrip)
                ToolStripValue = Value
            End Set
        End Property

        Private MethodItemsValue As New List(Of MethodItem)

        Public Property MethodItems() As List(Of MethodItem)
            Get
                Return MethodItemsValue
            End Get
            Set(ByVal Value As List(Of MethodItem))
                MethodItemsValue = Value
            End Set
        End Property

        Private MenuItemsValue As New List(Of ExtendedToolStripMenuItem)

        Public Property MenuItems() As List(Of ExtendedToolStripMenuItem)
            Get
                Return MenuItemsValue
            End Get
            Set(ByVal Value As List(Of ExtendedToolStripMenuItem))
                MenuItemsValue = Value
            End Set
        End Property

        Private FormValue As Form

        Public Property Form() As Form
            Get
                Return FormValue
            End Get
            Set(ByVal Value As Form)
                FormValue = Value
            End Set
        End Property

        Private DefaultMenuValue As FunctionDelegate(Of GenericMenuItem)

        Public Property DefaultMenu() As FunctionDelegate(Of GenericMenuItem)
            Get
                Return DefaultMenuValue
            End Get
            Set(ByVal Value As FunctionDelegate(Of GenericMenuItem))
                DefaultMenuValue = Value
            End Set
        End Property

        Private MenuItemValue As GenericMenuItem

        Public Property MenuItem() As GenericMenuItem
            Get
                Return MenuItemValue
            End Get
            Set(ByVal Value As GenericMenuItem)
                MenuItemValue = Value
            End Set
        End Property

        Public Function GetMethod(ByVal name As String) As MethodInfo
            For Each i As MethodItem In MethodItems
                If i.MethodInfo.Name = name Then
                    Return i.MethodInfo
                End If
            Next

            Return Nothing
        End Function

        Public Sub BuildMenu()
            ToolStrip.Items.Clear()
            Items.Clear()
            MenuItems.Clear()

            BuildMenu(ToolStrip, MenuItem)
        End Sub

        Private Sub BuildMenu(ByVal menu As Object, ByVal item As GenericMenuItem)
            For Each i As GenericMenuItem In item.SubItems
                Dim mi As ToolStripItem

                If i.Text = "-" Then
                    mi = New ToolStripSeparator
                Else
                    Dim emi As New ExtendedToolStripMenuItem
                    MenuItems.Add(emi)
                    mi = emi
                    emi.GenericMenuItem = i
                    emi.TipTarget = TipTarget

                    Dim keys As String = KeysHelp.GetKeyString(i.KeyData)

                    If keys <> "" Then
                        emi.ShortcutKeyDisplayString = keys
                    End If
                End If

                Items.Add(i)
                i.GenericMenu = Me
                mi.Text = i.Text

                AddHandler mi.Click, AddressOf HandleMenuClick

                If Not i.MethodItem Is Nothing AndAlso _
                    Not i.MethodItem.Attribute.Topic Is Nothing Then

                    AddHandler mi.MouseDown, AddressOf HandleMouseDown
                End If

                If TypeOf menu Is ToolStripMenuItem Then
                    CType(menu, ToolStripMenuItem).DropDownItems.Add(mi)
                Else
                    CType(menu, ToolStrip).Items.Add(mi)
                End If

                BuildMenu(mi, i)
            Next
        End Sub
    End Class

    Public Class MethodItem
        Private FunctionAttributeValue As GenericMenuMethodAttribute

        Public Property Attribute() As GenericMenuMethodAttribute
            Get
                Return FunctionAttributeValue
            End Get
            Set(ByVal Value As GenericMenuMethodAttribute)
                FunctionAttributeValue = Value
            End Set
        End Property

        Private MethodInfoValue As MethodInfo

        Public Property MethodInfo() As MethodInfo
            Get
                Return MethodInfoValue
            End Get
            Set(ByVal Value As MethodInfo)
                MethodInfoValue = Value
            End Set
        End Property

        Private DefaultParamsValue As List(Of Object)

        Public Property DefaultParams() As List(Of Object)
            Get
                Return DefaultParamsValue
            End Get
            Set(ByVal Value As List(Of Object))
                DefaultParamsValue = Value
            End Set
        End Property

        Public Overrides Function ToString() As String
            If Not Attribute Is Nothing Then
                Return Attribute.Name
            End If

            Return ""
        End Function
    End Class

    <AttributeUsage(AttributeTargets.Method)> _
    Public Class GenericMenuMethodAttribute
        Inherits Attribute

        Public Sub New(ByVal name As String, ByVal description As String)
            Me.Name = name
            Me.Description = description
        End Sub

        Public Sub New(ByVal name As String, ByVal description As String, ByVal topic As String)
            Me.Name = name
            Me.Description = description
            Me.Topic = topic
        End Sub

        Private NameValue As String

        Public Property Name() As String
            Get
                Return NameValue
            End Get
            Set(ByVal Value As String)
                NameValue = 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 TopicValue As String

        Public Property Topic() As String
            Get
                Return TopicValue
            End Get
            Set(ByVal Value As String)
                TopicValue = Value
            End Set
        End Property
    End Class

    Public Class ExtendedToolStripMenuItem
        Inherits ToolStripMenuItem

        Private Shared Cache As StringPair

        Private TipTargetValue As TipTarget

        Public Property TipTarget() As TipTarget
            Get
                Return TipTargetValue
            End Get
            Set(ByVal Value As TipTarget)
                TipTargetValue = Value
            End Set
        End Property

        Private GenericMenuItemValue As GenericMenuItem

        Public Property GenericMenuItem() As GenericMenuItem
            Get
                Return GenericMenuItemValue
            End Get
            Set(ByVal Value As GenericMenuItem)
                GenericMenuItemValue = Value
            End Set
        End Property

        Private DescriptionValue As String = ""

        <DefaultValue(""), _
        Category("ExtendedToolStripMenuItem"), _
        Editor(GetType(StringEditor), GetType(UITypeEditor))> _
        Public Property Description() As String
            Get
                Return DescriptionValue
            End Get
            Set(ByVal Value As String)
                DescriptionValue = Value
            End Set
        End Property

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

            If Not GenericMenuItem Is Nothing AndAlso _
                Not GenericMenuItem.MethodItem Is Nothing Then

                If Description = "" Then
                    Description = GenericMenuItem.MethodItem.Attribute.Description
                End If
            End If

            If Description <> "" AndAlso Not TipTarget Is Nothing Then
                If Cache Is Nothing Then
                    Cache = TipTarget.GetTip
                End If

                TipTarget.Title = Text
                TipTarget.Description = Description
            End If
        End Sub

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

            If Not Cache Is Nothing AndAlso Not TipTarget Is Nothing Then
                TipTarget.SetTarget(Cache)
                Cache = Nothing
            End If
        End Sub
    End Class

    Public Class SubToolStripMenuItem
        Inherits ToolStripMenuItem

        Private SD As SubDelegate

        Public Sub New(ByVal text As String, ByVal sd As SubDelegate)
            Me.Text = text
            Me.SD = sd
        End Sub

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

    Public Class ParamToolStripMenuItem(Of T)
        Inherits ToolStripMenuItem

        Private PD As ParamDelegate(Of T)
        Private Param As T

        Public Sub New(ByVal text As String, ByVal pd As ParamDelegate(Of T), ByVal param As T)
            Me.Text = text
            Me.PD = pd
            Me.Param = param
        End Sub

        Protected Overrides Sub OnClick(ByVal e As EventArgs)
            MyBase.OnClick(e)
            PD(Param)
        End Sub
    End Class
End Namespace