Option Strict On
Option Explicit On

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

Namespace Nexamas.UI.TextInput

    Friend NotInheritable Class MASTextInputRenderer

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

        Friend Sub Render(canvas As SKCanvas,
                          snapshot As MASTextInputLayoutSnapshot,
                          caretVisible As Boolean)
            DrawFromSnapshot(canvas, snapshot, Nothing, caretVisible)
        End Sub

        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.InlineLayout 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 Shared Sub DrawSelection(canvas As SKCanvas,
                                         snap As MASTextInputLayoutSnapshot,
                                         theme As IMASInputTheme)

            If snap.SelectionLength <= 0 Then Return

            Dim selRect As SKRect =
                snap.InlineLayout.GetSelectionRect(
                    selectionStart:=snap.SelectionStart,
                    selectionLength:=snap.SelectionLength,
                    baselineY:=snap.BaselineY,
                    paint:=snap.Paint,
                    contentR:=snap.ContentRect
                )

            If selRect.IsEmpty OrElse selRect.Right <= selRect.Left Then Return

            Dim fillColor As SKColor = ResolveSelectionColor(theme)

            Using pSel As New SKPaint With {
                .IsAntialias = True,
                .Style = SKPaintStyle.Fill,
                .Color = fillColor
            }
                canvas.DrawRoundRect(selRect, SELECTION_RADIUS_PX, SELECTION_RADIUS_PX, pSel)
            End Using
        End Sub

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

            Dim text As String = If(snap.DisplayText, String.Empty)
            If text.Length = 0 Then Return

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

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

            Dim x As Single = If(snap.CrispRendering,
                                 MASTextCrispRendering.SnapTextX(snap.InlineLayout.TextX),
                                 snap.InlineLayout.TextX)

            If CanUseDirectDrawText(text) Then
                canvas.DrawText(
            text:=text,
            x:=x,
            y:=baselineY,
            paint:=snap.Paint
        )
                Return
            End If

            MASShapedText.Draw(
        canvas:=canvas,
        text:=text,
        x:=x,
        baselineY:=baselineY,
        paint:=snap.Paint
    )

        End Sub

        Private Shared Function CanUseDirectDrawText(text As String) As Boolean

            If String.IsNullOrEmpty(text) Then Return False

            For Each ch As Char In text

                Dim code As Integer = AscW(ch)

                If code < 0 Then
                    code += 65536
                End If

                ' Control characters should stay out of the direct path.
                If Char.IsControl(ch) Then
                    Return False
                End If

                ' Keep Arabic, Hebrew, and complex shaping scripts on MASShapedText.
                If code >= &H590 Then
                    Return False
                End If

            Next

            Return True

        End Function

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

            Dim caretX As Single = snap.InlineLayout.GetCaretX(snap.CaretIndex)

            If snap.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 fm As SKFontMetrics = MASTextMetrics.ResolveFontMetrics(snap.Paint)

            Dim topY As Single = Math.Max(snap.ContentRect.Top + 1.0F, snap.BaselineY + fm.Ascent)
            Dim botY As Single = Math.Min(snap.ContentRect.Bottom - 1.0F, snap.BaselineY + fm.Descent)

            If botY <= topY Then
                botY = Math.Min(snap.ContentRect.Bottom - 1.0F, topY + Math.Max(8.0F, snap.Paint.TextSize))
            End If

            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 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