Option Strict On
Option Explicit On

Imports System
Imports System.Globalization
Imports System.Windows.Forms
Imports Nexamas.UI.Architecture
Imports Nexamas.UI.Components.DropDownMenu
Imports Nexamas.UI.Controls
Imports Nexamas.UI.FloatRuntime
Imports Nexamas.UI.General
Imports Nexamas.UI.Layout
Imports Nexamas.UI.Motion
Imports Nexamas.UI.Rendering
Imports Nexamas.UI.TextRendering
Imports Nexamas.UI.Theming
Imports Nexamas.UI.Composition
Imports Nexamas.UI.Values
Imports SkiaSharp

Namespace Nexamas.UI.Components

    ''' <summary>
    ''' Official Nexamas UI split button: a primary MAS action surface plus a dedicated
    ''' dropdown affordance.  Menu content is always hosted through MASDropDownMenuBuilder
    ''' and FloatRuntime; the control does not create a parallel popup/menu renderer.
    ''' </summary>
    Partial Public NotInheritable Class MASSplitButton
        Inherits MASControlBase
        Implements IMASTooltipProvider
        Implements IMASMenuTrigger
        Implements IMASControlRuntimeServicesConsumer
        Implements IMASLayoutParticipant
        Implements IMASIntrinsicSizeContract
        Implements IMASLayoutSemanticBoundsProvider

#Region "Fields"

        Private Shared ReadOnly _splitContract As MASResolvedComponentContract =
            MASArchitectureRuntime.ResolveContractOrThrow(GetType(MASSplitButton))

        Private Shared ReadOnly _buttonRenderContract As MASResolvedComponentContract =
            MASArchitectureRuntime.ResolveContractOrThrow(GetType(MASButton))

        Private ReadOnly _renderer As MASButtonRenderer

        Private _text As String
        Private _tooltip As String
        Private _intent As MASButtonIntent
        Private _mainCommand As Action(Of MASSplitButton)
        Private _menuBuilder As Action(Of MASDropDownMenuBuilder)
        Private _commandHandler As Action(Of String)
        Private _drawIcon As Action(Of SKCanvas, SKRect, Single, SKColor)
        Private _drawIconEmphasis As Action(Of SKCanvas, SKRect, Single, SKColor)
        Private _iconPlacement As MASButtonIconPlacement

        Private _hoverMain As Boolean
        Private _hoverMenu As Boolean
        Private _pressedMain As Boolean
        Private _pressedMenu As Boolean
        Private _isMenuOpen As Boolean
        Private _keyboardSegment As MASSplitButtonSegment
        Private _pressPulseRunner As MASMotionTimelineRunner
        Private _motionPressedSegment As MASSplitButtonSegment

        Private _lastPixelBounds As SKRect
        Private _lastContext As MASThemeContext
        Private _runtimeServices As MASControlRuntimeServices
        Private _menuService As DropDownMenuPopupService
        Private _menuRuntime As MASFloatRuntime

#End Region


#Region "Events"

        Public Event Click As EventHandler
        Public Event CommandChanged As EventHandler
        Public Event MenuOpening As EventHandler
        Public Event MenuClosed As EventHandler

#End Region


#Region "Constructor / Factory"

        Public Sub New()
            MyBase.New()

            _renderer = New MASButtonRenderer()
            _text = String.Empty
            _tooltip = String.Empty
            _intent = MASButtonIntent.Default
            _mainCommand = Nothing
            _menuBuilder = Nothing
            _commandHandler = Nothing
            _drawIcon = Nothing
            _drawIconEmphasis = Nothing
            _iconPlacement = MASButtonIconPlacement.Left
            _keyboardSegment = MASSplitButtonSegment.Main
            _motionPressedSegment = MASSplitButtonSegment.None
        End Sub

        Public Sub New(text As String)
            Me.New()
            Me.Text = text
        End Sub

        Public Shared Function Create(Optional text As String = Nothing) As MASSplitButton
            Dim button As New MASSplitButton()
            If text IsNot Nothing Then button.Text = text
            Return button
        End Function

        Friend Shared Function [Default]() As MASSplitButton
            Return Create()
        End Function

#End Region


#Region "Public API"

        Public Property Text As String
            Get
                Return _text
            End Get
            Set(value As String)
                Dim normalized As String = MASSplitButtonSegmentPolicy.NormalizeText(value)
                If String.Equals(_text, normalized, StringComparison.Ordinal) Then Return

                _text = normalized
                InvalidateLayoutSlotInternal()
                InvalidateVisual()
            End Set
        End Property

        Public Property Tooltip As String Implements IMASTooltipProvider.Tooltip
            Get
                Return _tooltip
            End Get
            Set(value As String)
                Dim normalized As String = MASSplitButtonSegmentPolicy.NormalizeTooltip(value)
                If String.Equals(_tooltip, normalized, StringComparison.Ordinal) Then Return

                _tooltip = normalized
            End Set
        End Property

        Public Property Intent As MASButtonIntent
            Get
                Return _intent
            End Get
            Set(value As MASButtonIntent)
                Dim normalized As MASButtonIntent = MASSplitButtonSegmentPolicy.NormalizeIntent(value)
                If _intent = normalized Then Return

                _intent = normalized
                InvalidateVisual()
            End Set
        End Property

        Public Property MainCommand As Action(Of MASSplitButton)
            Get
                Return _mainCommand
            End Get
            Set(value As Action(Of MASSplitButton))
                If Object.ReferenceEquals(_mainCommand, value) Then Return

                _mainCommand = value
                RaiseEvent CommandChanged(Me, EventArgs.Empty)
            End Set
        End Property

        Public Property MenuBuilder As Action(Of MASDropDownMenuBuilder) Implements IMASMenuTrigger.MenuBuilder
            Get
                Return _menuBuilder
            End Get
            Set(value As Action(Of MASDropDownMenuBuilder))
                If Object.ReferenceEquals(_menuBuilder, value) Then Return
                If IsMenuOpen Then CloseMenu()
                _menuBuilder = value
                InvalidateVisual()
            End Set
        End Property

        Public Property CommandHandler As Action(Of String) Implements IMASMenuTrigger.CommandHandler
            Get
                Return _commandHandler
            End Get
            Set(value As Action(Of String))
                If Object.ReferenceEquals(_commandHandler, value) Then Return
                _commandHandler = value
            End Set
        End Property

        Public ReadOnly Property IsMenuOpen As Boolean
            Get
                Return _isMenuOpen AndAlso _menuService IsNot Nothing AndAlso _menuService.IsOpen
            End Get
        End Property

        Public ReadOnly Property HasIcon As Boolean
            Get
                Return _drawIcon IsNot Nothing OrElse _drawIconEmphasis IsNot Nothing
            End Get
        End Property

        Public Property IconPlacement As MASButtonIconPlacement
            Get
                Return _iconPlacement
            End Get
            Set(value As MASButtonIconPlacement)
                Dim normalized As MASButtonIconPlacement = MASSplitButtonSegmentPolicy.NormalizeIconPlacement(value)
                If _iconPlacement = normalized Then Return

                _iconPlacement = normalized
                InvalidateLayoutSlotInternal()
                InvalidateVisual()
            End Set
        End Property

        Public Sub PerformClick()
            If Not CanActivate() Then Return

            If _mainCommand IsNot Nothing Then
                Try
                    _mainCommand.Invoke(Me)
                Catch masCaughtException1 As Exception
                    Nexamas.UI.Diagnostics.MASExceptionSilencer.SwallowBoundary(masCaughtException1, "MASSplitButton.MainCommand")
                End Try
            End If

            Try
                RaiseEvent Click(Me, EventArgs.Empty)
            Catch masCaughtException2 As Exception
                Nexamas.UI.Diagnostics.MASExceptionSilencer.SwallowBoundary(masCaughtException2, "MASSplitButton.Click")
            End Try

            InvalidateVisual()
        End Sub

        Public Function CanActivate() As Boolean
            Return MASSplitButtonSegmentPolicy.CanActivate(Enabled, Visible)
        End Function

        Public Sub SetIcon(drawIcon As Action(Of SKCanvas, SKRect, Single, SKColor),
                           Optional drawIconEmphasis As Action(Of SKCanvas, SKRect, Single, SKColor) = Nothing)
            _drawIcon = drawIcon
            _drawIconEmphasis = drawIconEmphasis
            InvalidateLayoutSlotInternal()
            InvalidateVisual()
        End Sub

        Public Sub ClearIcon()
            If _drawIcon Is Nothing AndAlso _drawIconEmphasis Is Nothing Then Return

            _drawIcon = Nothing
            _drawIconEmphasis = Nothing
            InvalidateLayoutSlotInternal()
            InvalidateVisual()
        End Sub

        Public Sub ShowMenu() Implements IMASMenuTrigger.ShowMenu
            If Not Enabled OrElse Not Visible Then Return
            If MenuBuilder Is Nothing Then Return

            _keyboardSegment = MASSplitButtonSegment.Menu

            Dim service As DropDownMenuPopupService = EnsureMenuService()
            If service Is Nothing Then Return

            If service.IsOpen Then
                CloseMenu()
                Return
            End If

            Dim anchor As SKRect = GetMenuAnchorRectPx()
            If anchor.IsEmpty OrElse anchor.Width <= 1.0F OrElse anchor.Height <= 1.0F Then Return

            Try
                RaiseEvent MenuOpening(Me, EventArgs.Empty)
            Catch masCaughtException3 As Exception
                Nexamas.UI.Diagnostics.MASExceptionSilencer.SwallowBoundary(masCaughtException3, "MASSplitButton.MenuOpening")
            End Try

            Try
                _isMenuOpen = True
                service.Show(
                    anchorRectPx:=anchor,
                    configure:=MenuBuilder,
                    fallbackCommand:=AddressOf InvokeMenuCommand)
                If Not service.IsOpen Then
                    _isMenuOpen = False
                End If
                InvalidateVisual()
            Catch masCaughtException4 As Exception
                _isMenuOpen = False
                Nexamas.UI.Diagnostics.MASExceptionSilencer.SwallowInputBoundary(masCaughtException4, "MASSplitButton.ShowMenu")
                InvalidateVisual()
            End Try
        End Sub

        Public Sub CloseMenu() Implements IMASMenuTrigger.CloseMenu
            If _menuService Is Nothing Then Return

            Try
                _menuService.Close()
            Catch masCaughtException5 As Exception
                Nexamas.UI.Diagnostics.MASExceptionSilencer.SwallowInputBoundary(masCaughtException5, "MASSplitButton.CloseMenu")
            Finally
                OnMenuClosedInternal()
            End Try
        End Sub

        Public Function GetMenuAnchorRectPx() As SKRect Implements IMASMenuTrigger.GetMenuAnchorRectPx
            Dim dpi As Single = ResolveDpi()
            Dim slotAnchorPx As SKRect = GetLayoutFloatAnchorBoundsPxInternal(dpi)
            If slotAnchorPx.Width > 1.0F AndAlso slotAnchorPx.Height > 1.0F Then Return slotAnchorPx
            If _lastPixelBounds.Width > 1.0F AndAlso _lastPixelBounds.Height > 1.0F Then Return _lastPixelBounds
            Return GetPixelBoundsSafe()
        End Function

#End Region


#Region "Fluent API"

        Public Function WithText(value As String) As MASSplitButton
            Text = value
            Return Me
        End Function

        Public Function WithTooltip(value As String) As MASSplitButton
            Tooltip = value
            Return Me
        End Function

        Public Function WithIntent(value As MASButtonIntent) As MASSplitButton
            Intent = value
            Return Me
        End Function

        Public Function WithPrimaryCommand(action As Action(Of MASSplitButton)) As MASSplitButton
            MainCommand = action
            Return Me
        End Function

        Public Function WithMenu(buildMenu As Action(Of MASDropDownMenuBuilder)) As MASSplitButton
            MenuBuilder = buildMenu
            Return Me
        End Function

        Public Function OnCommand(commandHandler As Action(Of String)) As MASSplitButton
            Me.CommandHandler = commandHandler
            Return Me
        End Function

        Public Function WithAction(text As String,
                                   commandId As String,
                                   Optional handler As Action = Nothing,
                                   Optional enabled As Boolean = True,
                                   Optional shortcutText As String = "",
                                   Optional intent As MASDropDownMenuItemIntent = MASDropDownMenuItemIntent.Default) As MASSplitButton
            Dim previousBuilder As Action(Of MASDropDownMenuBuilder) = Me.MenuBuilder

            Me.MenuBuilder =
                Sub(menu As MASDropDownMenuBuilder)
                    previousBuilder?.Invoke(menu)
                    menu.AddAction(text, commandId, handler, enabled, shortcutText, intent)
                End Sub

            Return Me
        End Function

        Public Function WithSeparator() As MASSplitButton
            Dim previousBuilder As Action(Of MASDropDownMenuBuilder) = Me.MenuBuilder

            Me.MenuBuilder =
                Sub(menu As MASDropDownMenuBuilder)
                    previousBuilder?.Invoke(menu)
                    menu.AddSeparator()
                End Sub

            Return Me
        End Function

        Public Function WithSubMenu(text As String,
                                    configure As Action(Of MASDropDownMenuBuilder),
                                    Optional enabled As Boolean = True) As MASSplitButton
            Dim previousBuilder As Action(Of MASDropDownMenuBuilder) = Me.MenuBuilder

            Me.MenuBuilder =
                Sub(menu As MASDropDownMenuBuilder)
                    previousBuilder?.Invoke(menu)
                    menu.AddSubMenu(text, configure, enabled)
                End Sub

            Return Me
        End Function

        Public Function WithIcon(drawIcon As Action(Of SKCanvas, SKRect, Single, SKColor),
                                 Optional drawIconEmphasis As Action(Of SKCanvas, SKRect, Single, SKColor) = Nothing) As MASSplitButton
            SetIcon(drawIcon, drawIconEmphasis)
            Return Me
        End Function

        Public Function WithoutIcon() As MASSplitButton
            ClearIcon()
            Return Me
        End Function

        Public Function WithIconPlacement(value As MASButtonIconPlacement) As MASSplitButton
            IconPlacement = value
            Return Me
        End Function

        Public Shadows Function WithSize(sizeIntent As MASSize) As MASSplitButton
            MyBase.SetSize(sizeIntent)
            Return Me
        End Function

        Friend Function SetBounds(bounds As SKRect) As MASSplitButton
            SetLocalLayoutBoundsInternal(bounds)
            Return Me
        End Function

        Friend Function WithBounds(bounds As SKRect) As MASSplitButton
            Return SetBounds(bounds)
        End Function

#End Region

    End Class

End Namespace
