Option Strict On
Option Explicit On

Imports System
Imports System.Collections.Generic
Imports System.Collections.ObjectModel
Imports System.Windows.Forms

Namespace Nexamas.UI.Components

    Partial Public NotInheritable Class MASTreeGrid

#Region "Editing"

        Public Event RowEditStarted As EventHandler
        Public Event RowEditCommitted As EventHandler
        Public Event RowEditCanceled As EventHandler

        Public ReadOnly Property IsEditingRow As Boolean
            Get
                Return _editingRowKey.Length > 0
            End Get
        End Property

        Public ReadOnly Property EditingRowKey As String
            Get
                Return _editingRowKey
            End Get
        End Property

        Public ReadOnly Property EditingColumnKey As String
            Get
                Return _editingColumnKey
            End Get
        End Property

        Public ReadOnly Property IsEditingLabel As Boolean
            Get
                Return IsEditingRow AndAlso _editingColumnKey.Length = 0
            End Get
        End Property

        Public ReadOnly Property EditingDraftText As String
            Get
                Return _editingDraftText
            End Get
        End Property

        Public ReadOnly Property EditingOriginalText As String
            Get
                Return _editingOriginalText
            End Get
        End Property

        Public ReadOnly Property EditRevision As Integer
            Get
                Return _editRevision
            End Get
        End Property

        Public Function CanEditRow(rowKey As String) As Boolean
            Return FindRow(rowKey) IsNot Nothing
        End Function

        Public Function BeginEditSelectedLabel() As Boolean
            If _selectedRowKey.Length = 0 Then Return False
            Return BeginEdit(_selectedRowKey)
        End Function

        Public Function BeginEditSelectedValue(columnKey As String) As Boolean
            If _selectedRowKey.Length = 0 Then Return False
            Return BeginEdit(_selectedRowKey, columnKey)
        End Function

        Public Function BeginEdit(rowKey As String,
                                  Optional columnKey As String = Nothing) As Boolean
            Dim row As TreeGridRowState = FindRow(rowKey)
            If row Is Nothing Then Return False

            Dim normalizedColumnKey As String = If(columnKey, String.Empty).Trim()
            Dim original As String = row.Label
            If normalizedColumnKey.Length > 0 Then
                Dim columnIndex As Integer = FindColumnIndex(normalizedColumnKey)
                If columnIndex < 0 Then Return False
                normalizedColumnKey = _columns(columnIndex).Key
                If columnIndex < row.Values.Count Then original = row.Values(columnIndex) Else original = String.Empty
            End If

            _editingRowKey = row.Key
            _editingColumnKey = normalizedColumnKey
            _editingOriginalText = original
            _editingDraftText = original
            AdvanceEditRevision()
            If EnsureAncestorsExpanded(row) Then InvalidateTreeProjection("MASTreeGrid.BeginEdit")
            SetSelection(row.Key, False)
            EnsureRowVisible(row.Key)
            InvalidateVisual()
            RaiseRowEditStartedSafe()
            Return True
        End Function

        Public Function UpdateEditDraft(value As String) As Boolean
            If Not IsEditingRow Then Return False
            Dim normalized As String = NormalizeEditText(value, _editingColumnKey.Length = 0)
            If String.Equals(_editingDraftText, normalized, StringComparison.Ordinal) Then Return True
            _editingDraftText = normalized
            AdvanceEditRevision()
            InvalidateVisual()
            Return True
        End Function

        Public Function CommitEdit(Optional value As String = Nothing) As Boolean
            If Not IsEditingRow Then Return False
            If value IsNot Nothing Then UpdateEditDraft(value)

            Dim rowKey As String = _editingRowKey
            Dim columnKey As String = _editingColumnKey
            Dim draft As String = _editingDraftText
            Dim committed As Boolean

            If columnKey.Length = 0 Then
                committed = TrySetRowLabelCore(rowKey, draft, detachSourceForManualMutation:=True)
            Else
                committed = TrySetRowValueCore(rowKey, columnKey, draft, detachSourceForManualMutation:=True)
            End If

            ClearEditState()
            AdvanceEditRevision()
            InvalidateVisual()
            If committed Then RaiseRowEditCommittedSafe()
            Return committed
        End Function

        Public Function CancelEdit() As Boolean
            If Not IsEditingRow Then Return False
            ClearEditState()
            AdvanceEditRevision()
            InvalidateVisual()
            RaiseRowEditCanceledSafe()
            Return True
        End Function

        Public Function TrySetRowLabel(rowKey As String,
                                      label As String) As Boolean
            Return TrySetRowLabelCore(rowKey, label, detachSourceForManualMutation:=True)
        End Function

        Public Function TrySetRowValue(rowKey As String,
                                      columnKey As String,
                                      value As String) As Boolean
            Return TrySetRowValueCore(rowKey, columnKey, value, detachSourceForManualMutation:=True)
        End Function

        Public Function GetRowLabel(rowKey As String) As String
            Dim row As TreeGridRowState = FindRow(rowKey)
            If row Is Nothing Then Return String.Empty
            Return row.Label
        End Function

        Public Function GetRowValue(rowKey As String,
                                    columnKey As String) As String
            Dim row As TreeGridRowState = FindRow(rowKey)
            If row Is Nothing Then Return String.Empty
            Dim columnIndex As Integer = FindColumnIndex(columnKey)
            If columnIndex < 0 OrElse columnIndex >= row.Values.Count Then Return String.Empty
            Return row.Values(columnIndex)
        End Function

        Public Function GetRowValues(rowKey As String) As IReadOnlyList(Of String)
            Dim row As TreeGridRowState = FindRow(rowKey)
            Dim values As New List(Of String)()
            If row IsNot Nothing Then values.AddRange(row.Values)
            Return New ReadOnlyCollection(Of String)(values)
        End Function

        Friend Function HasActiveEditingContract() As Boolean
            Return MASTreeGridPolicy.HasEditingContractPath()
        End Function

        Private Function TrySetRowLabelCore(rowKey As String,
                                           label As String,
                                           detachSourceForManualMutation As Boolean) As Boolean
            Dim row As TreeGridRowState = FindRow(rowKey)
            If row Is Nothing Then Return False
            Dim normalized As String = MASTreeGridPolicy.NormalizeRowLabel(label)
            If String.Equals(row.Label, normalized, StringComparison.Ordinal) Then Return True

            Dim oldSelectedKey As String = _selectedRowKey
            If detachSourceForManualMutation Then ClearItemsSourceLinkForManualMutation()
            row.Label = normalized
            AfterRowContentMutation("MASTreeGrid.TrySetRowLabel", oldSelectedKey)
            Return True
        End Function

        Private Function TrySetRowValueCore(rowKey As String,
                                           columnKey As String,
                                           value As String,
                                           detachSourceForManualMutation As Boolean) As Boolean
            Dim row As TreeGridRowState = FindRow(rowKey)
            If row Is Nothing Then Return False
            Dim columnIndex As Integer = FindColumnIndex(columnKey)
            If columnIndex < 0 Then Return False

            Dim normalized As String = MASTreeGridPolicy.NormalizeCellText(value)
            EnsureValueCapacity(row, columnIndex + 1)
            If String.Equals(row.Values(columnIndex), normalized, StringComparison.Ordinal) Then Return True

            Dim oldSelectedKey As String = _selectedRowKey
            If detachSourceForManualMutation Then ClearItemsSourceLinkForManualMutation()
            row.Values(columnIndex) = normalized
            AfterRowContentMutation("MASTreeGrid.TrySetRowValue", oldSelectedKey)
            Return True
        End Function

        Private Sub AfterRowContentMutation(reason As String,
                                            oldSelectedKey As String)
            ApplyActiveSortToTree()
            InvalidateTreeProjection(reason)
            NormalizeViewState()
            RequestSizeLayoutRefreshForSizeAffectingChange(reason)
            InvalidateVisual()
            RaiseTreeGridChangedSafe()
            If Not String.Equals(oldSelectedKey, _selectedRowKey, StringComparison.Ordinal) Then RaiseSelectedRowChangedSafe()
        End Sub

        Private Shared Sub EnsureValueCapacity(row As TreeGridRowState,
                                               count As Integer)
            If row Is Nothing Then Return
            While row.Values.Count < count
                row.Values.Add(String.Empty)
            End While
        End Sub

        Private Shared Function NormalizeEditText(value As String,
                                                  isLabel As Boolean) As String
            If isLabel Then Return MASTreeGridPolicy.NormalizeRowLabel(value)
            Return MASTreeGridPolicy.NormalizeCellText(value)
        End Function

        Private Sub NormalizeEditStateForRows()
            If Not IsEditingRow Then Return
            Dim row As TreeGridRowState = FindRow(_editingRowKey)
            If row Is Nothing Then
                ClearEditState()
                AdvanceEditRevision()
                Return
            End If
            If _editingColumnKey.Length > 0 AndAlso FindColumnIndex(_editingColumnKey) < 0 Then
                ClearEditState()
                AdvanceEditRevision()
            End If
        End Sub

        Private Sub ClearEditState()
            _editingRowKey = String.Empty
            _editingColumnKey = String.Empty
            _editingOriginalText = String.Empty
            _editingDraftText = String.Empty
        End Sub

        Private Sub AdvanceEditRevision()
            If _editRevision = Integer.MaxValue Then
                _editRevision = 0
            Else
                _editRevision += 1
            End If
        End Sub

        Private Sub RaiseRowEditStartedSafe()
            Try
                RaiseEvent RowEditStarted(Me, EventArgs.Empty)
            Catch ex As Exception
                Nexamas.UI.Diagnostics.MASExceptionSilencer.SwallowInputBoundary(ex, "MASTreeGrid.RowEditStarted")
            End Try
        End Sub

        Private Sub RaiseRowEditCommittedSafe()
            Try
                RaiseEvent RowEditCommitted(Me, EventArgs.Empty)
            Catch ex As Exception
                Nexamas.UI.Diagnostics.MASExceptionSilencer.SwallowInputBoundary(ex, "MASTreeGrid.RowEditCommitted")
            End Try
        End Sub

        Private Sub RaiseRowEditCanceledSafe()
            Try
                RaiseEvent RowEditCanceled(Me, EventArgs.Empty)
            Catch ex As Exception
                Nexamas.UI.Diagnostics.MASExceptionSilencer.SwallowInputBoundary(ex, "MASTreeGrid.RowEditCanceled")
            End Try
        End Sub

#End Region

    End Class

End Namespace
