Option Strict On
Option Explicit On

Imports System
Imports System.Collections.Generic
Imports Nexamas.UI.TextRendering
Imports Nexamas.UI.Theming
Imports SkiaSharp

Namespace Nexamas.UI.TextInput

    ' CONTRACT:
    ' MASTextMultilineRenderer renders an already-built multiline paragraph snapshot.
    '
    ' Responsibilities:
    ' - Draw visible wrapped lines.
    ' - Draw multi-line selection rectangles.
    ' - Draw caret using paragraph layout caret geometry.
    '
    ' It must not:
    ' - Mutate state.
    ' - Build paragraph layout.
    ' - Handle keyboard/mouse input.
    ' - Own scroll state.
    Friend NotInheritable Class MASTextMultilineRenderer

        Private Const CARET_WIDTH_PX As Single = 1.0F
        Private Const SELECTION_RADIUS_PX As Single = 2.0F

        Private ReadOnly _selectionGeometry As New MASTextMultilineSelectionGeometry()

        Friend Sub DrawFromSnapshot(canvas As SKCanvas,
                                    snap As MASTextInputLayoutSnapshot,
                                    theme As IMASInputTheme,
                                    hasFocus As Boolean)

            If canvas Is Nothing OrElse snap Is Nothing Then Return
            If snap.Paint Is Nothing Then Return
            If snap.ParagraphLayout Is Nothing Then Return
            If snap.ContentRect.Width <= 0.0F OrElse snap.ContentRect.Height <= 0.0F Then Return

            DrawSelection(canvas, snap, theme)
            DrawText(canvas, snap)

            If hasFocus Then
                DrawCaret(canvas, snap)
            End If
        End Sub

        Private Sub DrawSelection(canvas As SKCanvas,
                                  snap As MASTextInputLayoutSnapshot,
                                  theme As IMASInputTheme)

            If snap.SelectionLength <= 0 Then Return

            Dim rects As IReadOnlyList(Of SKRect) =
                _selectionGeometry.BuildRects(
                    layout:=snap.ParagraphLayout,
                    selectionStart:=snap.SelectionStart,
                    selectionLength:=snap.SelectionLength,
                    paint:=snap.Paint)

            If rects Is Nothing OrElse rects.Count = 0 Then Return

            Dim fillColor As SKColor = ResolveSelectionColor(theme)

            Using pSel As New SKPaint With {
                .IsAntialias = True,
                .Style = SKPaintStyle.Fill,
                .Color = fillColor
            }
                For Each rect As SKRect In rects
                    If rect.IsEmpty OrElse rect.Right <= rect.Left Then Continue For
                    If rect.Bottom < snap.ContentRect.Top OrElse rect.Top > snap.ContentRect.Bottom Then Continue For

                    Dim clipped As SKRect = ClipToContent(rect, snap.ContentRect)
                    If clipped.IsEmpty OrElse clipped.Right <= clipped.Left Then Continue For

                    canvas.DrawRoundRect(clipped, SELECTION_RADIUS_PX, SELECTION_RADIUS_PX, pSel)
                Next
            End Using
        End Sub

        Private Shared Sub DrawText(canvas As SKCanvas,
                                    snap As MASTextInputLayoutSnapshot)

            Dim paragraph As MASTextParagraphLayout = snap.ParagraphLayout
            If paragraph Is Nothing Then Return

            For i As Integer = 0 To paragraph.LineCount - 1
                Dim line As MASTextWrappedLine = paragraph.Lines(i)
                If line Is Nothing Then Continue For
                If String.IsNullOrEmpty(line.Text) Then Continue For
                If line.Bounds.Bottom < snap.ContentRect.Top OrElse line.Bounds.Top > snap.ContentRect.Bottom Then Continue For

                Dim x As Single = If(line.InlineLayout IsNot Nothing, line.InlineLayout.TextX, line.Bounds.Left)
                If snap.CrispRendering Then
                    x = MASTextCrispRendering.SnapTextX(x)
                End If

                Dim baselineY As Single = If(snap.CrispRendering,
                                             MASTextCrispRendering.SnapBaseline(line.BaselineY),
                                             line.BaselineY)

                If snap.CrispRendering Then
                    MASTextCrispRendering.ConfigurePaint(snap.Paint)
                End If

                MASShapedText.Draw(
                    canvas:=canvas,
                    text:=line.Text,
                    x:=x,
                    baselineY:=baselineY,
                    paint:=snap.Paint)
            Next
        End Sub

        Private Shared Sub DrawCaret(canvas As SKCanvas,
                                     snap As MASTextInputLayoutSnapshot)

            Dim paragraph As MASTextParagraphLayout = snap.ParagraphLayout
            If paragraph Is Nothing OrElse snap.Paint Is Nothing Then Return

            Dim caretPoint As SKPoint = paragraph.GetCaretPoint(snap.CaretIndex)
            Dim line As MASTextWrappedLine = paragraph.GetLineFromTextIndex(snap.CaretIndex)
            If line Is Nothing Then Return

            Dim caretX As Single = caretPoint.X
            If line.IsRtl Then
                caretX -= CARET_WIDTH_PX
            End If

            caretX = ClampCaretX(caretX, snap.ContentRect, CARET_WIDTH_PX)
            caretX = If(snap.CrispRendering, MASTextCrispRendering.SnapCaretX(caretX), CSng(Math.Round(caretX, MidpointRounding.AwayFromZero)))

            Dim topY As Single = Math.Max(snap.ContentRect.Top + 1.0F, line.Bounds.Top + 1.0F)
            Dim botY As Single = Math.Min(snap.ContentRect.Bottom - 1.0F, line.Bounds.Bottom - 1.0F)

            If botY <= topY Then
                Dim fm As SKFontMetrics = MASTextMetrics.ResolveFontMetrics(snap.Paint)
                topY = Math.Max(snap.ContentRect.Top + 1.0F, line.BaselineY + fm.Ascent)
                botY = Math.Min(snap.ContentRect.Bottom - 1.0F, line.BaselineY + fm.Descent)
            End If

            If botY <= topY Then Return

            Using pCaret As New SKPaint With {
                .IsAntialias = False,
                .Style = SKPaintStyle.Fill,
                .Color = snap.Paint.Color.WithAlpha(235)
            }
                canvas.DrawRect(New SKRect(caretX, topY, caretX + CARET_WIDTH_PX, botY), pCaret)
            End Using
        End Sub

        Private Shared Function ClipToContent(rect As SKRect,
                                              contentR As SKRect) As SKRect
            Return New SKRect(
                Math.Max(rect.Left, contentR.Left),
                Math.Max(rect.Top, contentR.Top),
                Math.Min(rect.Right, contentR.Right),
                Math.Min(rect.Bottom, contentR.Bottom))
        End Function

        Private Shared Function ClampCaretX(value As Single,
                                            contentR As SKRect,
                                            caretWidth As Single) As Single

            Dim minX As Single = contentR.Left
            Dim maxX As Single = contentR.Right - caretWidth

            If maxX < minX Then Return minX
            If value < minX Then Return minX
            If value > maxX Then Return maxX

            Return value
        End Function

        Private Shared Function ResolveSelectionColor(theme As IMASInputTheme) As SKColor
            If theme IsNot Nothing AndAlso theme.States.Focused.Ring.Enabled Then
                Return theme.States.Focused.Ring.Color.WithAlpha(72)
            End If

            Return SKColors.DeepSkyBlue.WithAlpha(72)
        End Function

    End Class

End Namespace
