Option Strict On
Option Explicit On

Imports System
Imports System.Diagnostics

Namespace Nexamas.UI.TextInput

    ' CONTRACT:
    ' Every text input mutation must be represented as an IMASTextInputCommand.
    '
    ' AffectsText=True means:
    '   - The command is allowed to change State.Text.
    '   - The command must be routed through the undo manager.
    '
    ' AffectsText=False means:
    '   - The command is a navigation/selection/state command.
    '   - It must not change State.Text.
    '   - It must break undo merge boundaries.
    '
    ' Commands must not know UI, rendering, layout, mouse, keyboard, or Skia.

    Friend Interface IMASTextInputCommand
        ReadOnly Property Kind As MASTextEditKind
        ReadOnly Property AffectsText As Boolean

        Function Execute(state As MASTextInputState,
                         editor As MASTextInputEditor,
                         navigator As MASTextInputNavigator) As Boolean
    End Interface

    Friend NotInheritable Class MASTextCommandRunner

        Private ReadOnly _undo As MASTextUndoManager

        Friend Sub New(undo As MASTextUndoManager)
            If undo Is Nothing Then Throw New ArgumentNullException(NameOf(undo))
            _undo = undo
        End Sub

        Friend Function Run(state As MASTextInputState,
                      editor As MASTextInputEditor,
                      navigator As MASTextInputNavigator,
                      command As IMASTextInputCommand) As Boolean

            If state Is Nothing Then Return False
            If editor Is Nothing Then Return False
            If navigator Is Nothing Then Return False
            If command Is Nothing Then Return False

            Dim beforeText As String = state.Text
            Dim beforeVersion As Integer = state.Version

            Dim changed As Boolean

            If command.AffectsText Then
                changed = _undo.Execute(
                    state:=state,
                    kind:=command.Kind,
                    action:=Function()
                                Return command.Execute(state, editor, navigator)
                            End Function
                )
            Else
                _undo.BreakMerge()
                changed = command.Execute(state, editor, navigator)
            End If

            ValidateCommandContract(command, beforeText, beforeVersion, state, changed)

            Return changed
        End Function
        <Conditional("DEBUG")>
        Private Shared Sub ValidateCommandContract(command As IMASTextInputCommand,
                                           beforeText As String,
                                           beforeVersion As Integer,
                                           state As MASTextInputState,
                                           changed As Boolean)

            If command Is Nothing OrElse state Is Nothing Then Return

            Dim afterText As String = state.Text
            Dim textChanged As Boolean =
        Not String.Equals(beforeText, afterText, StringComparison.Ordinal)

            If command.AffectsText Then
                Debug.Assert(command.Kind <> MASTextEditKind.Programmatic,
                     "Text-affecting commands must provide a concrete MASTextEditKind.")

                Debug.Assert(Not textChanged OrElse state.Version <> beforeVersion,
                     "Text-affecting command changed Text without bumping State.Version.")
            Else
                Debug.Assert(Not textChanged,
                     "Non-text command changed State.Text. This violates the command contract.")

                Debug.Assert(Not changed OrElse state.Version <> beforeVersion,
                     "Non-text command reported changed=True without changing State.Version.")
            End If
        End Sub
    End Class
    Friend MustInherit Class MASTextStateCommandBase
        Implements IMASTextInputCommand

        Public Overridable ReadOnly Property Kind As MASTextEditKind Implements IMASTextInputCommand.Kind
            Get
                Return MASTextEditKind.Programmatic
            End Get
        End Property

        Public Overridable ReadOnly Property AffectsText As Boolean Implements IMASTextInputCommand.AffectsText
            Get
                Return False
            End Get
        End Property

        Friend Function Execute(state As MASTextInputState,
                                editor As MASTextInputEditor,
                                navigator As MASTextInputNavigator) As Boolean Implements IMASTextInputCommand.Execute

            If state Is Nothing Then Return False

            Dim beforeVersion As Integer = state.Version
            ExecuteCore(state, editor, navigator)
            Return beforeVersion <> state.Version
        End Function

        Protected MustOverride Sub ExecuteCore(state As MASTextInputState,
                                               editor As MASTextInputEditor,
                                               navigator As MASTextInputNavigator)

    End Class

    Friend NotInheritable Class MASTextInsertCommand
        Implements IMASTextInputCommand

        Private ReadOnly _value As String
        Private ReadOnly _kind As MASTextEditKind

        Friend Sub New(value As String, kind As MASTextEditKind)
            _value = If(value, String.Empty)
            _kind = kind
        End Sub

        Public ReadOnly Property Kind As MASTextEditKind Implements IMASTextInputCommand.Kind
            Get
                Return _kind
            End Get
        End Property

        Public ReadOnly Property AffectsText As Boolean Implements IMASTextInputCommand.AffectsText
            Get
                Return True
            End Get
        End Property

        Friend Function Execute(state As MASTextInputState,
                                editor As MASTextInputEditor,
                                navigator As MASTextInputNavigator) As Boolean Implements IMASTextInputCommand.Execute

            Return editor.InsertTextAtCaret(state, _value)
        End Function

    End Class

    Friend NotInheritable Class MASTextPasteCommand
        Implements IMASTextInputCommand

        Private ReadOnly _value As String

        Friend Sub New(value As String)
            _value = If(value, String.Empty)
        End Sub

        Public ReadOnly Property Kind As MASTextEditKind Implements IMASTextInputCommand.Kind
            Get
                Return MASTextEditKind.Paste
            End Get
        End Property

        Public ReadOnly Property AffectsText As Boolean Implements IMASTextInputCommand.AffectsText
            Get
                Return True
            End Get
        End Property

        Friend Function Execute(state As MASTextInputState,
                                editor As MASTextInputEditor,
                                navigator As MASTextInputNavigator) As Boolean Implements IMASTextInputCommand.Execute

            Return editor.InsertTextAtCaret(state, _value)
        End Function

    End Class

    Friend NotInheritable Class MASTextBackspaceCommand
        Implements IMASTextInputCommand

        Private ReadOnly _ctrl As Boolean

        Friend Sub New(ctrl As Boolean)
            _ctrl = ctrl
        End Sub

        Public ReadOnly Property Kind As MASTextEditKind Implements IMASTextInputCommand.Kind
            Get
                Return MASTextEditKind.Backspace
            End Get
        End Property

        Public ReadOnly Property AffectsText As Boolean Implements IMASTextInputCommand.AffectsText
            Get
                Return True
            End Get
        End Property

        Friend Function Execute(state As MASTextInputState,
                                editor As MASTextInputEditor,
                                navigator As MASTextInputNavigator) As Boolean Implements IMASTextInputCommand.Execute

            If _ctrl Then Return editor.BackspacePreviousWord(state)
            Return editor.BackspaceSelectionAware(state)
        End Function

    End Class

    Friend NotInheritable Class MASTextDeleteForwardCommand
        Implements IMASTextInputCommand

        Private ReadOnly _ctrl As Boolean

        Friend Sub New(ctrl As Boolean)
            _ctrl = ctrl
        End Sub

        Public ReadOnly Property Kind As MASTextEditKind Implements IMASTextInputCommand.Kind
            Get
                Return MASTextEditKind.Delete
            End Get
        End Property

        Public ReadOnly Property AffectsText As Boolean Implements IMASTextInputCommand.AffectsText
            Get
                Return True
            End Get
        End Property

        Friend Function Execute(state As MASTextInputState,
                                editor As MASTextInputEditor,
                                navigator As MASTextInputNavigator) As Boolean Implements IMASTextInputCommand.Execute

            If _ctrl Then Return editor.DeleteNextWord(state)
            Return editor.DeleteSelectionAware(state)
        End Function

    End Class

    Friend NotInheritable Class MASTextDeleteSelectionCommand
        Implements IMASTextInputCommand

        Public ReadOnly Property Kind As MASTextEditKind Implements IMASTextInputCommand.Kind
            Get
                Return MASTextEditKind.Cut
            End Get
        End Property

        Public ReadOnly Property AffectsText As Boolean Implements IMASTextInputCommand.AffectsText
            Get
                Return True
            End Get
        End Property

        Friend Function Execute(state As MASTextInputState,
                                editor As MASTextInputEditor,
                                navigator As MASTextInputNavigator) As Boolean Implements IMASTextInputCommand.Execute

            Return editor.DeleteSelectionCore(state)
        End Function

    End Class

    Friend NotInheritable Class MASTextCutCommand
        Implements IMASTextInputCommand

        Public ReadOnly Property Kind As MASTextEditKind Implements IMASTextInputCommand.Kind
            Get
                Return MASTextEditKind.Cut
            End Get
        End Property

        Public ReadOnly Property AffectsText As Boolean Implements IMASTextInputCommand.AffectsText
            Get
                Return True
            End Get
        End Property

        Friend Function Execute(state As MASTextInputState,
                                editor As MASTextInputEditor,
                                navigator As MASTextInputNavigator) As Boolean Implements IMASTextInputCommand.Execute

            If state Is Nothing OrElse state.SelectionLength <= 0 Then Return False
            Return editor.DeleteSelectionCore(state)
        End Function

    End Class

    Friend NotInheritable Class MASTextSetCaretCommand
        Inherits MASTextStateCommandBase

        Private ReadOnly _index As Integer

        Friend Sub New(index As Integer)
            _index = index
        End Sub

        Protected Overrides Sub ExecuteCore(state As MASTextInputState,
                                        editor As MASTextInputEditor,
                                        navigator As MASTextInputNavigator)
            state.MoveCaretWithoutSelection(_index)
        End Sub

    End Class

    Friend NotInheritable Class MASTextBeginSelectionCommand
        Inherits MASTextStateCommandBase

        Private ReadOnly _index As Integer

        Friend Sub New(index As Integer)
            _index = index
        End Sub

        Protected Overrides Sub ExecuteCore(state As MASTextInputState,
                                        editor As MASTextInputEditor,
                                        navigator As MASTextInputNavigator)
            state.BeginSelection(_index)
        End Sub

    End Class

    Friend NotInheritable Class MASTextExtendSelectionCommand
        Inherits MASTextStateCommandBase

        Private ReadOnly _index As Integer

        Friend Sub New(index As Integer)
            _index = index
        End Sub

        Protected Overrides Sub ExecuteCore(state As MASTextInputState,
                                        editor As MASTextInputEditor,
                                        navigator As MASTextInputNavigator)
            navigator.ExtendSelectionTo(state, _index)
        End Sub

    End Class

    Friend NotInheritable Class MASTextSelectWordCommand
        Inherits MASTextStateCommandBase

        Private ReadOnly _index As Integer

        Friend Sub New(index As Integer)
            _index = index
        End Sub

        Protected Overrides Sub ExecuteCore(state As MASTextInputState,
                                        editor As MASTextInputEditor,
                                        navigator As MASTextInputNavigator)
            navigator.SelectWordAt(state, _index)
        End Sub

    End Class

    Friend NotInheritable Class MASTextMoveCaretHorizontalCommand
        Inherits MASTextStateCommandBase

        Private ReadOnly _delta As Integer
        Private ReadOnly _extendSelection As Boolean

        Friend Sub New(delta As Integer, extendSelection As Boolean)
            _delta = delta
            _extendSelection = extendSelection
        End Sub

        Protected Overrides Sub ExecuteCore(state As MASTextInputState,
                                            editor As MASTextInputEditor,
                                            navigator As MASTextInputNavigator)
            navigator.MoveCaretHorizontal(state, _delta, _extendSelection)
        End Sub

    End Class

    Friend NotInheritable Class MASTextMoveCaretToCommand
        Inherits MASTextStateCommandBase

        Private ReadOnly _target As Integer
        Private ReadOnly _extendSelection As Boolean

        Friend Sub New(target As Integer, extendSelection As Boolean)
            _target = target
            _extendSelection = extendSelection
        End Sub

        Protected Overrides Sub ExecuteCore(state As MASTextInputState,
                                            editor As MASTextInputEditor,
                                            navigator As MASTextInputNavigator)
            navigator.MoveCaretTo(state, _target, _extendSelection)
        End Sub

    End Class

    Friend NotInheritable Class MASTextMovePreviousWordCommand
        Inherits MASTextStateCommandBase

        Private ReadOnly _extendSelection As Boolean

        Friend Sub New(extendSelection As Boolean)
            _extendSelection = extendSelection
        End Sub

        Protected Overrides Sub ExecuteCore(state As MASTextInputState,
                                            editor As MASTextInputEditor,
                                            navigator As MASTextInputNavigator)
            navigator.MoveCaretToPreviousWord(state, _extendSelection)
        End Sub

    End Class

    Friend NotInheritable Class MASTextMoveNextWordCommand
        Inherits MASTextStateCommandBase

        Private ReadOnly _extendSelection As Boolean

        Friend Sub New(extendSelection As Boolean)
            _extendSelection = extendSelection
        End Sub

        Protected Overrides Sub ExecuteCore(state As MASTextInputState,
                                            editor As MASTextInputEditor,
                                            navigator As MASTextInputNavigator)
            navigator.MoveCaretToNextWord(state, _extendSelection)
        End Sub

    End Class

    Friend NotInheritable Class MASTextSelectAllCommand
        Inherits MASTextStateCommandBase

        Protected Overrides Sub ExecuteCore(state As MASTextInputState,
                                            editor As MASTextInputEditor,
                                            navigator As MASTextInputNavigator)
            state.SelectAll()
        End Sub

    End Class

    Friend NotInheritable Class MASTextSetSelectionCommand
        Inherits MASTextStateCommandBase

        Private ReadOnly _start As Integer
        Private ReadOnly _length As Integer

        Friend Sub New(start As Integer, length As Integer)
            _start = start
            _length = length
        End Sub

        Protected Overrides Sub ExecuteCore(state As MASTextInputState,
                                            editor As MASTextInputEditor,
                                            navigator As MASTextInputNavigator)
            state.SetSelection(_start, _length)
        End Sub

    End Class

    Friend NotInheritable Class MASTextClearSelectionCommand
        Inherits MASTextStateCommandBase

        Protected Overrides Sub ExecuteCore(state As MASTextInputState,
                                            editor As MASTextInputEditor,
                                            navigator As MASTextInputNavigator)
            state.ClearSelection()
        End Sub

    End Class

    Friend NotInheritable Class MASTextEndSelectionGestureCommand
        Inherits MASTextStateCommandBase

        Protected Overrides Sub ExecuteCore(state As MASTextInputState,
                                            editor As MASTextInputEditor,
                                            navigator As MASTextInputNavigator)
            state.EndSelectionGesture()
        End Sub

    End Class

End Namespace
