Option Strict On
Option Explicit On

Imports System
Imports System.Collections.Generic
Imports System.Globalization
Imports System.Collections.ObjectModel
Imports System.Windows.Forms
Imports Nexamas.UI.Architecture
Imports Nexamas.UI.Composition
Imports Nexamas.UI.Controls
Imports Nexamas.UI.General
Imports Nexamas.UI.Layout
Imports Nexamas.UI.Rendering
Imports Nexamas.UI.TextRendering
Imports Nexamas.UI.Theming
Imports Nexamas.UI.Values
Imports SkiaSharp

Namespace Nexamas.UI.Components

    Partial Public NotInheritable Class MASTransferList

#Region "Rendering"

        Protected Overrides Sub Render(canvas As SKCanvas,
                                       ctx As MASThemeContext,
                                       pixelBounds As SKRect)
            If canvas Is Nothing OrElse ctx Is Nothing Then Return
            If pixelBounds.Width <= 1.0F OrElse pixelBounds.Height <= 1.0F Then Return

            Dim dpi As Single = PixelSnap.SafeDpi(ctx.Dpi)
            _lastDpi = dpi
            Dim bounds As SKRect = PixelSnap.SnapRect(pixelBounds, dpi)
            If bounds.Width <= 1.0F OrElse bounds.Height <= 1.0F Then Return

            Dim responsiveProfile As MASResponsiveLayoutProfile = MASResponsiveLayoutResolver.FromTheme(ctx, bounds, SizeIntent, MASTransferListLayoutPlan.Thresholds)
            Dim plan As MASTransferListLayoutPlan = MASTransferListLayoutPlan.Create(responsiveProfile)
            _lastLayoutPlan = plan

            _primitives.DrawCardSurface(canvas, ctx, bounds, dpi, _contract, MASCardVisualStyle.Secondary)

            Dim content As SKRect = PixelSnap.SnapRect(Inset(bounds, plan.ContentInsetPx), dpi)
            If content.Width <= 1.0F OrElse content.Height <= 1.0F Then Return

            DrawTitle(canvas, ctx, content, dpi, plan)
            Dim body As New SKRect(content.Left,
                                   content.Top + plan.HeaderHeightPx + If(plan.HeaderHeightPx > 0.0F, plan.HeaderGapPx, 0.0F),
                                   content.Right,
                                   content.Bottom)
            ResolveRects(body, dpi, plan, MASTransferListPolicy.ResolveRightToLeft())

            DrawPanel(canvas, ctx, _availablePanelRect, dpi, plan, True)
            DrawPanel(canvas, ctx, _selectedPanelRect, dpi, plan, False)
            DrawRail(canvas, ctx, body, dpi, plan)
        End Sub

        Private Sub DrawTitle(canvas As SKCanvas,
                              ctx As MASThemeContext,
                              content As SKRect,
                              dpi As Single,
                              plan As MASTransferListLayoutPlan)
            If plan Is Nothing OrElse Not plan.ShowTitle OrElse plan.HeaderHeightPx <= 1.0F Then Return
            Dim titleRect As New SKRect(content.Left, content.Top, content.Right, content.Top + plan.HeaderHeightPx)
            TypographyTextPrimitives.DrawSingleLineText(canvas, ctx, _title, titleRect, dpi, MASTypography.MASTextStyle.Body, MASTransferListPolicy.ResolvePrimaryTextColor(ctx), TypographyTextPrimitives.TextAlign.Left, False)
        End Sub

        Private Sub ResolveRects(body As SKRect,
                                 dpi As Single,
                                 plan As MASTransferListLayoutPlan,
                                 isRtl As Boolean)
            _leftPanelRect = SKRect.Empty
            _rightPanelRect = SKRect.Empty
            _availablePanelRect = SKRect.Empty
            _selectedPanelRect = SKRect.Empty
            _railRect = SKRect.Empty

            If body.Width <= 1.0F OrElse body.Height <= 1.0F OrElse plan Is Nothing Then Return
            Dim gap As Single = plan.GapPx

            If plan.IsStacked Then
                Dim railHeight As Single = Math.Max(24.0F * dpi, plan.RailHeightPx)
                Dim panelHeight As Single = Math.Max(24.0F * dpi, (body.Height - railHeight - gap * 2.0F) * 0.5F)
                Dim availablePanel As SKRect = PixelSnap.SnapRect(New SKRect(body.Left, body.Top, body.Right, Math.Min(body.Bottom, body.Top + panelHeight)), dpi)
                Dim railTop As Single = availablePanel.Bottom + gap
                _railRect = PixelSnap.SnapRect(New SKRect(body.Left, railTop, body.Right, Math.Min(body.Bottom, railTop + railHeight)), dpi)
                Dim selectedTop As Single = _railRect.Bottom + gap
                Dim selectedPanel As SKRect = PixelSnap.SnapRect(New SKRect(body.Left, selectedTop, body.Right, body.Bottom), dpi)

                _leftPanelRect = availablePanel
                _rightPanelRect = selectedPanel
                _availablePanelRect = availablePanel
                _selectedPanelRect = selectedPanel
                Return
            End If

            Dim railW As Single = Math.Max(28.0F * dpi, plan.RailWidthPx)
            Dim panelW As Single = Math.Max(80.0F * dpi, (body.Width - railW - gap * 2.0F) * 0.5F)
            If panelW * 2.0F + railW + gap * 2.0F > body.Width Then panelW = Math.Max(24.0F * dpi, (body.Width - railW - gap * 2.0F) * 0.5F)
            Dim leftPanel As SKRect = PixelSnap.SnapRect(New SKRect(body.Left, body.Top, body.Left + panelW, body.Bottom), dpi)
            Dim railLeft As Single = leftPanel.Right + gap
            _railRect = PixelSnap.SnapRect(New SKRect(railLeft, body.Top, Math.Min(body.Right, railLeft + railW), body.Bottom), dpi)
            Dim rightPanel As SKRect = PixelSnap.SnapRect(New SKRect(_railRect.Right + gap, body.Top, body.Right, body.Bottom), dpi)
            _leftPanelRect = leftPanel
            _rightPanelRect = rightPanel
            _availablePanelRect = If(isRtl, rightPanel, leftPanel)
            _selectedPanelRect = If(isRtl, leftPanel, rightPanel)
        End Sub

        Private Sub DrawPanel(canvas As SKCanvas,
                              ctx As MASThemeContext,
                              rect As SKRect,
                              dpi As Single,
                              plan As MASTransferListLayoutPlan,
                              availablePanel As Boolean)
            If rect.Width <= 1.0F OrElse rect.Height <= 1.0F OrElse plan Is Nothing Then Return
            Dim accent As SKColor = MASTransferListPolicy.ResolveAccentColor(ctx)
            Dim primary As SKColor = MASTransferListPolicy.ResolvePrimaryTextColor(ctx)
            Dim muted As SKColor = MASTransferListPolicy.ResolveMutedTextColor(ctx)
            Dim radius As Single = plan.PanelRadiusPx

            Using fill As New SKPaint With {.IsAntialias = True, .Style = SKPaintStyle.Fill, .Color = accent.WithAlpha(TransferListTokens.PanelFillAlpha)}
                canvas.DrawRoundRect(rect, radius, radius, fill)
            End Using
            Using stroke As New SKPaint With {.IsAntialias = True, .Style = SKPaintStyle.Stroke, .StrokeWidth = Math.Max(1.0F, dpi), .Color = muted.WithAlpha(TransferListTokens.PanelStrokeAlpha)}
                canvas.DrawRoundRect(Inset(rect, 0.5F * dpi), Math.Max(2.0F * dpi, radius - 0.5F * dpi), Math.Max(2.0F * dpi, radius - 0.5F * dpi), stroke)
            End Using

            Dim pad As Single = plan.PanelPaddingPx
            Dim header As New SKRect(rect.Left + pad, rect.Top + pad, rect.Right - pad, rect.Top + pad + TransferListTokens.HeaderHeightDip * dpi * plan.ResponsiveProfile.ChromeScale)
            Dim title As String = If(availablePanel, _availableTitle, _selectedTitle)
            Dim visibleCount As Integer = BuildVisibleIndexes(If(availablePanel, _available, _selected), If(availablePanel, _availableFilterText, _selectedFilterText)).Count
            Dim totalCount As Integer = If(availablePanel, _available.Count, _selected.Count)
            Dim countText As String = visibleCount.ToString(CultureInfo.CurrentCulture) & " / " & totalCount.ToString(CultureInfo.CurrentCulture)
            TypographyTextPrimitives.DrawSingleLineText(canvas, ctx, title, header, dpi, MASTypography.MASTextStyle.Small, primary, TypographyTextPrimitives.TextAlign.Left, False)
            If plan.ShowPanelCounts Then
                TypographyTextPrimitives.DrawSingleLineText(canvas, ctx, countText, header, dpi, MASTypography.MASTextStyle.Micro, muted.WithAlpha(TransferListTokens.CountTextAlpha), TypographyTextPrimitives.TextAlign.Right, False)
            End If

            Dim rowsTop As Single = header.Bottom + plan.RowGapPx
            If plan.ShowSearch AndAlso plan.SearchHeightPx > 1.0F Then
                Dim searchRect As SKRect = PixelSnap.SnapRect(New SKRect(rect.Left + pad, rowsTop, rect.Right - pad, rowsTop + plan.SearchHeightPx), dpi)
                If availablePanel Then _availableSearchRect = searchRect Else _selectedSearchRect = searchRect
                DrawSearchBox(canvas, ctx, searchRect, dpi, plan, accent, muted, If(availablePanel, _availableFilterText, _selectedFilterText), availablePanel)
                rowsTop = searchRect.Bottom + plan.RowGapPx
            Else
                If availablePanel Then _availableSearchRect = SKRect.Empty Else _selectedSearchRect = SKRect.Empty
            End If

            Dim rowsRect As New SKRect(rect.Left + pad, rowsTop, rect.Right - pad, rect.Bottom - pad)
            DrawRows(canvas, ctx, rowsRect, dpi, plan, availablePanel, primary, muted, accent)
        End Sub

        Private Sub DrawSearchBox(canvas As SKCanvas,
                                  ctx As MASThemeContext,
                                  rect As SKRect,
                                  dpi As Single,
                                  plan As MASTransferListLayoutPlan,
                                  accent As SKColor,
                                  muted As SKColor,
                                  filterText As String,
                                  availablePanel As Boolean)
            Dim active As Boolean = _activeAvailablePanel = availablePanel
            Using fill As New SKPaint With {.IsAntialias = True, .Style = SKPaintStyle.Fill, .Color = accent.WithAlpha(If(active, CByte(34), CByte(22)))}
                canvas.DrawRoundRect(rect, rect.Height * 0.5F, rect.Height * 0.5F, fill)
            End Using
            Dim text As String = If(String.IsNullOrEmpty(filterText), "Search", filterText)
            Dim color As SKColor = If(String.IsNullOrEmpty(filterText), muted.WithAlpha(TransferListTokens.EmptyTextAlpha), MASTransferListPolicy.ResolvePrimaryTextColor(ctx))
            TypographyTextPrimitives.DrawSingleLineText(canvas, ctx, text, InsetHorizontal(rect, 10.0F * dpi * plan.ResponsiveProfile.GapScale), dpi, MASTypography.MASTextStyle.Micro, color, TypographyTextPrimitives.TextAlign.Left, False)
        End Sub

        Private Sub DrawRows(canvas As SKCanvas,
                             ctx As MASThemeContext,
                             rowsRect As SKRect,
                             dpi As Single,
                             plan As MASTransferListLayoutPlan,
                             availablePanel As Boolean,
                             primary As SKColor,
                             muted As SKColor,
                             accent As SKColor)
            Dim source As List(Of String) = If(availablePanel, _available, _selected)
            Dim selectedIndex As Integer = If(availablePanel, _availableSelectedIndex, _selectedSelectedIndex)
            Dim topIndex As Integer = If(availablePanel, _availableTopIndex, _selectedTopIndex)
            Dim filterText As String = If(availablePanel, _availableFilterText, _selectedFilterText)
            Dim emptyText As String = If(availablePanel, TransferListTokens.EmptyAvailableText, TransferListTokens.EmptySelectedText)
            Dim visibleIndexes As List(Of Integer) = BuildVisibleIndexes(source, filterText)
            If visibleIndexes.Count <= 0 Then
                Dim text As String = If(source.Count = 0, emptyText, "No matches")
                TypographyTextPrimitives.DrawSingleLineText(canvas, ctx, text, rowsRect, dpi, MASTypography.MASTextStyle.Small, muted.WithAlpha(TransferListTokens.EmptyTextAlpha), TypographyTextPrimitives.TextAlign.Center, False)
                Return
            End If

            Dim rowH As Single = plan.RowHeightPx
            Dim gap As Single = plan.RowGapPx
            Dim safeTop As Integer = Math.Max(0, Math.Min(topIndex, Math.Max(0, visibleIndexes.Count - 1)))
            Dim maxRowsBySpace As Integer = Math.Max(1, CInt(Math.Floor((rowsRect.Height + gap) / Math.Max(1.0F, rowH + gap))))
            Dim visibleRows As Integer = Math.Min(Math.Min(plan.MaxVisibleRows, maxRowsBySpace), visibleIndexes.Count - safeTop)
            For visual As Integer = 0 To visibleRows - 1
                Dim index As Integer = visibleIndexes(safeTop + visual)
                Dim top As Single = rowsRect.Top + CSng(visual) * (rowH + gap)
                If top + rowH > rowsRect.Bottom + 0.5F * dpi Then Exit For
                Dim rowRect As SKRect = PixelSnap.SnapRect(New SKRect(rowsRect.Left, top, rowsRect.Right, top + rowH), dpi)
                DrawRow(canvas, ctx, rowRect, dpi, plan, availablePanel, index, source(index), index = selectedIndex, primary, muted, accent)
            Next

            DrawTransferListMotionOverlays(canvas, dpi, plan, availablePanel, accent)
        End Sub

        Private Sub DrawRow(canvas As SKCanvas,
                            ctx As MASThemeContext,
                            rowRect As SKRect,
                            dpi As Single,
                            plan As MASTransferListLayoutPlan,
                            availablePanel As Boolean,
                            index As Integer,
                            text As String,
                            selected As Boolean,
                            primary As SKColor,
                            muted As SKColor,
                            accent As SKColor)
            Dim hover As Boolean = (_hoverRow.Index = index AndAlso _hoverRow.LeftList = availablePanel)
            Dim pressed As Boolean = (_pressedRow.Index = index AndAlso _pressedRow.LeftList = availablePanel)
            Dim radius As Single = Math.Max(6.0F * dpi, rowRect.Height * 0.34F)
            Dim drawSelectedFill As Boolean = selected AndAlso Not ShouldSuppressTransferListSelectionFill(availablePanel, index)
            If drawSelectedFill OrElse hover OrElse pressed Then
                Dim alpha As Byte = If(pressed, TransferListTokens.RowPressedAlpha, If(drawSelectedFill, TransferListTokens.RowSelectedAlpha, TransferListTokens.RowHoverAlpha))
                Using fill As New SKPaint With {.IsAntialias = True, .Style = SKPaintStyle.Fill, .Color = accent.WithAlpha(alpha)}
                    canvas.DrawRoundRect(rowRect, radius, radius, fill)
                End Using
            End If

            Dim textRect As SKRect = Inset(rowRect, 10.0F * dpi * plan.ResponsiveProfile.GapScale)
            TypographyTextPrimitives.DrawSingleLineText(canvas, ctx, text, textRect, dpi, MASTypography.MASTextStyle.Body, primary, TypographyTextPrimitives.TextAlign.Left, False)
        End Sub

        Private Sub DrawRail(canvas As SKCanvas,
                             ctx As MASThemeContext,
                             body As SKRect,
                             dpi As Single,
                             plan As MASTransferListLayoutPlan)
            If plan Is Nothing OrElse _railRect.Width <= 1.0F OrElse _railRect.Height <= 1.0F Then Return
            If plan.IsStacked Then
                DrawHorizontalRail(canvas, ctx, dpi, plan)
            Else
                DrawVerticalRail(canvas, ctx, dpi, plan)
            End If
        End Sub

        Private Sub DrawVerticalRail(canvas As SKCanvas,
                                     ctx As MASThemeContext,
                                     dpi As Single,
                                     plan As MASTransferListLayoutPlan)
            Dim centerY As Single = _railRect.MidY
            Dim buttonH As Single = plan.ButtonHeightPx
            Dim gap As Single = plan.ButtonGapPx
            Dim buttonW As Single = Math.Max(28.0F * dpi, _railRect.Width)
            Dim left As Single = _railRect.MidX - buttonW * 0.5F
            Dim top0 As Single = centerY - (buttonH * 4.0F + gap * 3.0F) * 0.5F
            _addOneRect = PixelSnap.SnapRect(New SKRect(left, top0, left + buttonW, top0 + buttonH), dpi)
            _addAllRect = PixelSnap.SnapRect(New SKRect(left, _addOneRect.Bottom + gap, left + buttonW, _addOneRect.Bottom + gap + buttonH), dpi)
            _removeOneRect = PixelSnap.SnapRect(New SKRect(left, _addAllRect.Bottom + gap, left + buttonW, _addAllRect.Bottom + gap + buttonH), dpi)
            _removeAllRect = PixelSnap.SnapRect(New SKRect(left, _removeOneRect.Bottom + gap, left + buttonW, _removeOneRect.Bottom + gap + buttonH), dpi)

            Dim isRtl As Boolean = MASTransferListPolicy.ResolveRightToLeft()
            DrawTransferButton(canvas, ctx, _addOneRect, dpi, plan, TransferAction.AddOne, If(isRtl, "‹", "›"), CanAddOne())
            DrawTransferButton(canvas, ctx, _addAllRect, dpi, plan, TransferAction.AddAll, If(isRtl, "‹‹", "››"), _available.Count > 0)
            DrawTransferButton(canvas, ctx, _removeOneRect, dpi, plan, TransferAction.RemoveOne, If(isRtl, "›", "‹"), CanRemoveOne())
            DrawTransferButton(canvas, ctx, _removeAllRect, dpi, plan, TransferAction.RemoveAll, If(isRtl, "››", "‹‹"), _selected.Count > 0)
        End Sub

        Private Sub DrawHorizontalRail(canvas As SKCanvas,
                                       ctx As MASThemeContext,
                                       dpi As Single,
                                       plan As MASTransferListLayoutPlan)
            Dim buttonH As Single = Math.Min(plan.ButtonHeightPx, Math.Max(20.0F * dpi, _railRect.Height))
            Dim gap As Single = plan.ButtonGapPx
            Dim buttonW As Single = Math.Min(54.0F * dpi * plan.ResponsiveProfile.ChromeScale, Math.Max(30.0F * dpi, (_railRect.Width - gap * 3.0F) / 4.0F))
            Dim totalW As Single = buttonW * 4.0F + gap * 3.0F
            Dim x As Single = _railRect.MidX - totalW * 0.5F
            Dim y As Single = _railRect.MidY - buttonH * 0.5F
            _addOneRect = PixelSnap.SnapRect(New SKRect(x, y, x + buttonW, y + buttonH), dpi)
            _addAllRect = PixelSnap.SnapRect(New SKRect(_addOneRect.Right + gap, y, _addOneRect.Right + gap + buttonW, y + buttonH), dpi)
            _removeOneRect = PixelSnap.SnapRect(New SKRect(_addAllRect.Right + gap, y, _addAllRect.Right + gap + buttonW, y + buttonH), dpi)
            _removeAllRect = PixelSnap.SnapRect(New SKRect(_removeOneRect.Right + gap, y, _removeOneRect.Right + gap + buttonW, y + buttonH), dpi)

            DrawTransferButton(canvas, ctx, _addOneRect, dpi, plan, TransferAction.AddOne, "↓", CanAddOne())
            DrawTransferButton(canvas, ctx, _addAllRect, dpi, plan, TransferAction.AddAll, "↓↓", _available.Count > 0)
            DrawTransferButton(canvas, ctx, _removeOneRect, dpi, plan, TransferAction.RemoveOne, "↑", CanRemoveOne())
            DrawTransferButton(canvas, ctx, _removeAllRect, dpi, plan, TransferAction.RemoveAll, "↑↑", _selected.Count > 0)
        End Sub

        Private Sub DrawTransferButton(canvas As SKCanvas,
                                       ctx As MASThemeContext,
                                       rect As SKRect,
                                       dpi As Single,
                                       plan As MASTransferListLayoutPlan,
                                       action As TransferAction,
                                       glyph As String,
                                       enabledAction As Boolean)
            Dim accent As SKColor = MASTransferListPolicy.ResolveAccentColor(ctx)
            Dim primary As SKColor = MASTransferListPolicy.ResolvePrimaryTextColor(ctx)
            Dim muted As SKColor = MASTransferListPolicy.ResolveMutedTextColor(ctx)
            Dim alpha As Byte
            If Not enabledAction Then
                alpha = TransferListTokens.ButtonDisabledAlpha
            ElseIf _pressedAction = action Then
                alpha = TransferListTokens.ButtonPressedAlpha
            ElseIf _hoverAction = action Then
                alpha = TransferListTokens.ButtonHoverAlpha
            Else
                alpha = TransferListTokens.ButtonFillAlpha
            End If

            Using fill As New SKPaint With {.IsAntialias = True, .Style = SKPaintStyle.Fill, .Color = accent.WithAlpha(alpha)}
                canvas.DrawRoundRect(rect, plan.ButtonRadiusPx, plan.ButtonRadiusPx, fill)
            End Using
            TypographyTextPrimitives.DrawSingleLineText(canvas, ctx, glyph, rect, dpi, MASTypography.MASTextStyle.Body, If(enabledAction, primary, muted), TypographyTextPrimitives.TextAlign.Center, False)
        End Sub

#End Region

    End Class

End Namespace
