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

    ''' <summary>
    ''' Optional renderer for syntax-colored single-line text.
    ''' It keeps the original selection/caret rendering by delegating first to
    ''' MASTextInputRenderer when highlighting is not safe/applicable.
    ''' </summary>
    Friend NotInheritable Class MASTextInputStyledRenderer

        Private ReadOnly _fallbackRenderer As New MASTextInputRenderer()

        Friend Sub DrawFromSnapshot(canvas As SKCanvas,
                                    snap As MASTextInputLayoutSnapshot,
                                    theme As IMASInputTheme,
                                    hasFocus As Boolean,
                                    highlighter As IMASTextSyntaxHighlighter,
                                    fontProfile As MASTextInputFontProfile)

            If canvas Is Nothing OrElse snap Is Nothing Then Return
            If highlighter Is Nothing Then
                _fallbackRenderer.DrawFromSnapshot(canvas, snap, theme, hasFocus)
                Return
            End If

            Dim text As String = If(snap.DisplayText, String.Empty)
            If text.Length = 0 OrElse snap.Paint Is Nothing OrElse snap.InlineLayout Is Nothing Then
                _fallbackRenderer.DrawFromSnapshot(canvas, snap, theme, hasFocus)
                Return
            End If

            ' Syntax highlighting is intentionally conservative in Stage 8A.
            ' RTL/code mixing stays on the stable renderer until a bidi-aware styled renderer is added.
            If snap.IsRtl Then
                _fallbackRenderer.DrawFromSnapshot(canvas, snap, theme, hasFocus)
                Return
            End If

            DrawSelectionOnly(canvas, snap, theme)
            DrawStyledText(canvas, snap, highlighter, fontProfile)

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

        Private Shared Sub DrawStyledText(canvas As SKCanvas,
                                          snap As MASTextInputLayoutSnapshot,
                                          highlighter As IMASTextSyntaxHighlighter,
                                          fontProfile As MASTextInputFontProfile)

            Dim text As String = If(snap.DisplayText, String.Empty)
            Dim spans As IReadOnlyList(Of MASTextSyntaxSpan) = Nothing

            Try
                spans = highlighter.GetSpans(text)
            Catch masCaughtException1 As Exception
                Nexamas.UI.Diagnostics.MASExceptionSilencer.SwallowRenderFallback(masCaughtException1)
                spans = Nothing
            End Try

            If spans Is Nothing OrElse spans.Count = 0 Then
                Dim x As Single = If(snap.CrispRendering,
                                     MASTextCrispRendering.SnapTextX(snap.InlineLayout.TextX),
                                     snap.InlineLayout.TextX)
                MASShapedText.Draw(canvas, text, x, If(snap.CrispRendering, MASTextCrispRendering.SnapBaseline(snap.BaselineY), snap.BaselineY), snap.Paint)
                Return
            End If

            Dim ordered As New List(Of MASTextSyntaxSpan)(spans)
            ordered.Sort(Function(a, b) a.Start.CompareTo(b.Start))

            Dim index As Integer = 0

            For Each span As MASTextSyntaxSpan In ordered
                If span Is Nothing OrElse span.Length <= 0 Then Continue For
                If span.Start >= text.Length Then Exit For

                Dim safeStart As Integer = Math.Max(0, span.Start)
                Dim safeEnd As Integer = Math.Min(text.Length, span.Start + span.Length)
                If safeEnd <= safeStart Then Continue For

                If safeStart > index Then
                    DrawRun(canvas, snap, text, index, safeStart - index, Nothing, fontProfile)
                End If

                DrawRun(canvas, snap, text, safeStart, safeEnd - safeStart, span.Style, fontProfile)
                index = Math.Max(index, safeEnd)
            Next

            If index < text.Length Then
                DrawRun(canvas, snap, text, index, text.Length - index, Nothing, fontProfile)
            End If
        End Sub

        Private Shared Sub DrawRun(canvas As SKCanvas,
                                   snap As MASTextInputLayoutSnapshot,
                                   text As String,
                                   start As Integer,
                                   length As Integer,
                                   style As MASTextSyntaxStyle,
                                   fontProfile As MASTextInputFontProfile)

            If length <= 0 Then Return
            If start < 0 OrElse start >= text.Length Then Return

            Dim safeLen As Integer = Math.Min(length, text.Length - start)
            If safeLen <= 0 Then Return

            Dim runText As String = text.Substring(start, safeLen)
            Dim runX As Single = snap.InlineLayout.GetCaretX(start)
            If snap.CrispRendering Then
                runX = MASTextCrispRendering.SnapTextX(runX)
            End If

            Using p As SKPaint = snap.Paint.Clone()
                If style IsNot Nothing Then
                    If style.Foreground.HasValue Then
                        p.Color = style.Foreground.Value
                    End If

                    If Not String.IsNullOrWhiteSpace(style.FontFamily) Then
                        Try
                            Dim tf As SKTypeface = SKTypeface.FromFamilyName(style.FontFamily)
                            If tf IsNot Nothing Then p.Typeface = tf
                        Catch masCaughtException2 As Exception
                            Nexamas.UI.Diagnostics.MASExceptionSilencer.SwallowRenderFallback(masCaughtException2)
                        End Try
                    ElseIf fontProfile IsNot Nothing Then
                        fontProfile.ApplyToPaint(p, runText)
                    End If
                ElseIf fontProfile IsNot Nothing Then
                    fontProfile.ApplyToPaint(p, runText)
                End If

                If snap.CrispRendering Then
                    MASTextCrispRendering.ConfigurePaint(p)
                End If
                MASShapedText.Draw(canvas, runText, runX, If(snap.CrispRendering, MASTextCrispRendering.SnapBaseline(snap.BaselineY), snap.BaselineY), p)
            End Using
        End Sub

        Private Shared Sub DrawSelectionOnly(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 = SKColors.DeepSkyBlue.WithAlpha(72)
            If theme IsNot Nothing AndAlso theme.States.Focused.Ring.Enabled Then
                fillColor = theme.States.Focused.Ring.Color.WithAlpha(72)
            End If

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

        Private Shared Sub DrawCaretOnly(canvas As SKCanvas,
                                         snap As MASTextInputLayoutSnapshot)
            If snap.Paint Is Nothing OrElse snap.InlineLayout Is Nothing Then Return

            Dim caretX As Single = snap.InlineLayout.GetCaretX(snap.CaretIndex)
            caretX = Math.Max(snap.ContentRect.Left, Math.Min(snap.ContentRect.Right - 1.0F, caretX))
            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 + 1.0F, botY), pCaret)
            End Using
        End Sub

    End Class

End Namespace
