Option Strict On
Option Explicit On

Imports System
Imports System.Collections.Generic
Imports System.Collections.ObjectModel
Imports Nexamas.UI.Values

Namespace Nexamas.UI.Components

    ''' <summary>
    ''' One realized PivotTable matrix-cell coordinate inside a bounded diagnostic
    ''' window. This is friend-only evidence for rendering/input coverage; it is not an
    ''' export cell, not an external data adapter, and not a public matrix API.
    ''' </summary>
    Friend NotInheritable Class MASPivotMatrixCellWindowEntry

        Friend Sub New(rowKey As String,
                       columnKey As String)
            Me.RowKey = Normalize(rowKey)
            Me.ColumnKey = Normalize(columnKey)
        End Sub

        Friend ReadOnly Property RowKey As String
        Friend ReadOnly Property ColumnKey As String

        Friend ReadOnly Property IsValid As Boolean
            Get
                Return RowKey.Length > 0 AndAlso ColumnKey.Length > 0
            End Get
        End Property

        Private Shared Function Normalize(value As String) As String
            If value Is Nothing Then Return String.Empty
            Return value.Trim()
        End Function

    End Class

    ''' <summary>
    ''' Friend-only PivotTable matrix-window snapshot. It proves the control can keep a
    ''' large row/column pivot projection while rendering and input diagnostics realize
    ''' only a bounded cell window. It does not run a query engine, does not export all
    ''' cells, and does not promote the control beyond CompactPreview.
    ''' </summary>
    Friend NotInheritable Class MASPivotMatrixWindowSnapshot

        Private ReadOnly _cells As IReadOnlyList(Of MASPivotMatrixCellWindowEntry)

        Friend Sub New(firstRowIndex As Integer,
                       lastRowIndex As Integer,
                       firstColumnIndex As Integer,
                       lastColumnIndex As Integer,
                       totalRows As Integer,
                       totalColumns As Integer,
                       requestedRowCapacity As Integer,
                       requestedColumnCapacity As Integer,
                       projectionRevision As Integer,
                       cells As IEnumerable(Of MASPivotMatrixCellWindowEntry))
            Me.FirstRowIndex = Math.Max(0, firstRowIndex)
            Me.LastRowIndex = lastRowIndex
            Me.FirstColumnIndex = Math.Max(0, firstColumnIndex)
            Me.LastColumnIndex = lastColumnIndex
            Me.TotalRows = Math.Max(0, totalRows)
            Me.TotalColumns = Math.Max(0, totalColumns)
            Me.RequestedRowCapacity = Math.Max(1, requestedRowCapacity)
            Me.RequestedColumnCapacity = Math.Max(1, requestedColumnCapacity)
            Me.ProjectionRevision = projectionRevision

            Dim normalized As New List(Of MASPivotMatrixCellWindowEntry)()
            If cells IsNot Nothing Then
                For Each cell As MASPivotMatrixCellWindowEntry In cells
                    If cell IsNot Nothing AndAlso cell.IsValid Then normalized.Add(cell)
                Next
            End If
            _cells = New ReadOnlyCollection(Of MASPivotMatrixCellWindowEntry)(normalized.ToArray())
        End Sub

        Friend ReadOnly Property FirstRowIndex As Integer
        Friend ReadOnly Property LastRowIndex As Integer
        Friend ReadOnly Property FirstColumnIndex As Integer
        Friend ReadOnly Property LastColumnIndex As Integer
        Friend ReadOnly Property TotalRows As Integer
        Friend ReadOnly Property TotalColumns As Integer
        Friend ReadOnly Property RequestedRowCapacity As Integer
        Friend ReadOnly Property RequestedColumnCapacity As Integer
        Friend ReadOnly Property ProjectionRevision As Integer

        Friend ReadOnly Property RealizedCells As IReadOnlyList(Of MASPivotMatrixCellWindowEntry)
            Get
                Return _cells
            End Get
        End Property

        Friend ReadOnly Property TotalCellCount As Long
            Get
                Return CLng(TotalRows) * CLng(TotalColumns)
            End Get
        End Property

        Friend ReadOnly Property RealizedRowCount As Integer
            Get
                If TotalRows <= 0 OrElse LastRowIndex < FirstRowIndex Then Return 0
                Return LastRowIndex - FirstRowIndex + 1
            End Get
        End Property

        Friend ReadOnly Property RealizedColumnCount As Integer
            Get
                If TotalColumns <= 0 OrElse LastColumnIndex < FirstColumnIndex Then Return 0
                Return LastColumnIndex - FirstColumnIndex + 1
            End Get
        End Property

        Friend ReadOnly Property RealizedCellCount As Integer
            Get
                Return _cells.Count
            End Get
        End Property

        Friend ReadOnly Property RequestedCellCapacity As Integer
            Get
                Return RequestedRowCapacity * RequestedColumnCapacity
            End Get
        End Property

        Friend ReadOnly Property IsEmpty As Boolean
            Get
                Return TotalRows <= 0 OrElse TotalColumns <= 0 OrElse RealizedCellCount <= 0
            End Get
        End Property

        Friend ReadOnly Property IsBounded As Boolean
            Get
                Return RealizedRowCount <= RequestedRowCapacity AndAlso
                       RealizedColumnCount <= RequestedColumnCapacity AndAlso
                       RealizedCellCount <= RequestedCellCapacity
            End Get
        End Property

        Friend ReadOnly Property CoversPartialMatrix As Boolean
            Get
                Return TotalRows > RealizedRowCount OrElse TotalColumns > RealizedColumnCount OrElse TotalCellCount > CLng(RealizedCellCount)
            End Get
        End Property

        Friend Function ContainsCell(rowKey As String,
                                     columnKey As String) As Boolean
            Dim normalizedRow As String = If(rowKey, String.Empty).Trim()
            Dim normalizedColumn As String = If(columnKey, String.Empty).Trim()
            If normalizedRow.Length = 0 OrElse normalizedColumn.Length = 0 Then Return False

            For Each cell As MASPivotMatrixCellWindowEntry In _cells
                If cell Is Nothing Then Continue For
                If String.Equals(cell.RowKey, normalizedRow, StringComparison.OrdinalIgnoreCase) AndAlso
                   String.Equals(cell.ColumnKey, normalizedColumn, StringComparison.OrdinalIgnoreCase) Then Return True
            Next
            Return False
        End Function

    End Class

    Partial Public NotInheritable Class MASPivotTable

#Region "Friend matrix-window diagnostics"

        Private _diagnosticRowViewportCapacity As Integer = PivotTableTokens.MaxVisibleRows
        Private _diagnosticColumnViewportCapacity As Integer = PivotTableTokens.MaxVisibleColumns

        Friend ReadOnly Property PivotProjectionRevision As Integer
            Get
                Return _pivotSourceRevision
            End Get
        End Property

        Friend Function CreateMatrixWindowSnapshot(Optional requestedRowCapacity As Integer = 0,
                                                   Optional requestedColumnCapacity As Integer = 0) As MASPivotMatrixWindowSnapshot
            Dim rowCapacity As Integer = ResolveDiagnosticRowCapacity(requestedRowCapacity)
            Dim columnCapacity As Integer = ResolveDiagnosticColumnCapacity(requestedColumnCapacity)

            If _rows.Count <= 0 OrElse _columns.Count <= 0 Then
                Return New MASPivotMatrixWindowSnapshot(0, -1, 0, -1, _rows.Count, _columns.Count, rowCapacity, columnCapacity, _pivotSourceRevision, Array.Empty(Of MASPivotMatrixCellWindowEntry)())
            End If

            Dim firstRow As Integer = Math.Max(0, Math.Min(_topRowIndex, _rows.Count - 1))
            Dim lastRow As Integer = Math.Min(_rows.Count - 1, firstRow + rowCapacity - 1)
            Dim firstColumn As Integer = Math.Max(0, Math.Min(_leftColumnIndex, _columns.Count - 1))
            Dim lastColumn As Integer = Math.Min(_columns.Count - 1, firstColumn + columnCapacity - 1)
            Dim cells As New List(Of MASPivotMatrixCellWindowEntry)()

            For rowIndex As Integer = firstRow To lastRow
                Dim row As PivotRowState = _rows(rowIndex)
                If row Is Nothing Then Continue For
                For columnIndex As Integer = firstColumn To lastColumn
                    Dim column As PivotColumnState = _columns(columnIndex)
                    If column IsNot Nothing Then cells.Add(New MASPivotMatrixCellWindowEntry(row.Key, column.Key))
                Next
            Next

            Return New MASPivotMatrixWindowSnapshot(firstRow,
                                                    lastRow,
                                                    firstColumn,
                                                    lastColumn,
                                                    _rows.Count,
                                                    _columns.Count,
                                                    rowCapacity,
                                                    columnCapacity,
                                                    _pivotSourceRevision,
                                                    cells)
        End Function

        Friend Function GetRealizedCellKeys(Optional requestedRowCapacity As Integer = 0,
                                            Optional requestedColumnCapacity As Integer = 0) As IReadOnlyList(Of String)
            Dim snapshot As MASPivotMatrixWindowSnapshot = CreateMatrixWindowSnapshot(requestedRowCapacity, requestedColumnCapacity)
            Dim keys As New List(Of String)()
            For Each cell As MASPivotMatrixCellWindowEntry In snapshot.RealizedCells
                If cell IsNot Nothing Then keys.Add(BuildCellKey(cell.RowKey, cell.ColumnKey))
            Next
            Return New ReadOnlyCollection(Of String)(keys.ToArray())
        End Function

        Friend Function HasBoundedMatrixWindow(totalCellThreshold As Integer,
                                               maxRealizedRows As Integer,
                                               maxRealizedColumns As Integer) As Boolean
            Dim snapshot As MASPivotMatrixWindowSnapshot = CreateMatrixWindowSnapshot(maxRealizedRows, maxRealizedColumns)
            Dim rowCapacity As Integer = Math.Max(1, maxRealizedRows)
            Dim columnCapacity As Integer = Math.Max(1, maxRealizedColumns)
            Return snapshot.TotalCellCount >= CLng(Math.Max(1, totalCellThreshold)) AndAlso
                   snapshot.RealizedRowCount <= rowCapacity AndAlso
                   snapshot.RealizedColumnCount <= columnCapacity AndAlso
                   snapshot.RealizedCellCount <= rowCapacity * columnCapacity AndAlso
                   snapshot.CoversPartialMatrix
        End Function

        Friend Function SetDiagnosticRowViewportCapacity(capacity As Integer) As Boolean
            Dim normalized As Integer = Math.Max(1, Math.Min(Math.Max(1, capacity), PivotTableTokens.MaxProjectedSourceRows))
            If _diagnosticRowViewportCapacity = normalized Then Return True
            _diagnosticRowViewportCapacity = normalized
            _topRowIndex = Math.Max(0, Math.Min(_topRowIndex, ResolveRowViewportMaxTopForCapacity(normalized)))
            InvalidateVisual()
            Return True
        End Function

        Friend Function SetDiagnosticColumnViewportCapacity(capacity As Integer) As Boolean
            Dim normalized As Integer = Math.Max(1, Math.Min(Math.Max(1, capacity), PivotTableTokens.MaxProjectedSourceColumns))
            If _diagnosticColumnViewportCapacity = normalized Then Return True
            _diagnosticColumnViewportCapacity = normalized
            _leftColumnIndex = Math.Max(0, Math.Min(_leftColumnIndex, ResolveColumnViewportMaxLeftForCapacity(normalized)))
            InvalidateVisual()
            Return True
        End Function

        Friend Function EnsureCellVisible(rowKey As String,
                                          columnKey As String) As Boolean
            Dim row As PivotRowState = FindRow(rowKey)
            Dim column As PivotColumnState = FindColumn(columnKey)
            If row Is Nothing OrElse column Is Nothing Then Return False

            Dim rowIndex As Integer = -1
            Dim columnIndex As Integer = -1
            If Not _rowIndexByKey.TryGetValue(row.Key, rowIndex) Then Return False
            If Not _columnIndexByKey.TryGetValue(column.Key, columnIndex) Then Return False

            Dim changed As Boolean = Not String.Equals(_selectedRowKey, row.Key, StringComparison.Ordinal) OrElse
                                     Not String.Equals(_selectedColumnKey, column.Key, StringComparison.Ordinal)
            _selectedRowKey = row.Key
            _selectedColumnKey = column.Key

            Dim rowCapacity As Integer = ResolveDiagnosticRowCapacity(0)
            Dim maxTop As Integer = ResolveRowViewportMaxTopForCapacity(rowCapacity)
            If rowIndex < _topRowIndex Then
                _topRowIndex = Math.Max(0, rowIndex)
            ElseIf rowIndex >= _topRowIndex + rowCapacity Then
                _topRowIndex = Math.Max(0, Math.Min(maxTop, rowIndex - rowCapacity + 1))
            Else
                _topRowIndex = Math.Max(0, Math.Min(_topRowIndex, maxTop))
            End If

            Dim columnCapacity As Integer = ResolveDiagnosticColumnCapacity(0)
            Dim maxLeft As Integer = ResolveColumnViewportMaxLeftForCapacity(columnCapacity)
            If columnIndex < _leftColumnIndex Then
                _leftColumnIndex = Math.Max(0, columnIndex)
            ElseIf columnIndex >= _leftColumnIndex + columnCapacity Then
                _leftColumnIndex = Math.Max(0, Math.Min(maxLeft, columnIndex - columnCapacity + 1))
            Else
                _leftColumnIndex = Math.Max(0, Math.Min(_leftColumnIndex, maxLeft))
            End If

            InvalidateVisual()
            If changed Then RaiseSelectedCellChangedSafe()
            Return True
        End Function

        Private Function ResolveDiagnosticRowCapacity(requestedCapacity As Integer) As Integer
            If requestedCapacity > 0 Then Return Math.Max(1, Math.Min(requestedCapacity, Math.Max(1, _rows.Count)))
            Return Math.Max(1, Math.Min(_diagnosticRowViewportCapacity, Math.Max(1, _rows.Count)))
        End Function

        Private Function ResolveDiagnosticColumnCapacity(requestedCapacity As Integer) As Integer
            If requestedCapacity > 0 Then Return Math.Max(1, Math.Min(requestedCapacity, Math.Max(1, _columns.Count)))
            Return Math.Max(1, Math.Min(_diagnosticColumnViewportCapacity, Math.Max(1, _columns.Count)))
        End Function

        Private Function ResolveRowViewportMaxTopForCapacity(capacity As Integer) As Integer
            Return Math.Max(0, _rows.Count - Math.Max(1, capacity))
        End Function

        Private Function ResolveColumnViewportMaxLeftForCapacity(capacity As Integer) As Integer
            Return Math.Max(0, _columns.Count - Math.Max(1, capacity))
        End Function

#End Region

    End Class

End Namespace
