Option Strict On
Option Explicit On

Imports System
Imports System.Collections.Generic
Imports SkiaSharp

Namespace Nexamas.UI.TextInput

    ''' <summary>
    ''' Small built-in highlighter useful for logs/code-like snippets.
    ''' It recognizes keywords, numbers, quoted strings, and line comments.
    ''' The scanner is line-aware so comments do not accidentally color the rest of a multiline document.
    ''' </summary>
    Friend NotInheritable Class MASTextBasicSyntaxHighlighter
        Implements IMASTextSyntaxHighlighter

        Private ReadOnly _keywords As New HashSet(Of String)(StringComparer.OrdinalIgnoreCase)
        Private _keywordsView As IReadOnlyCollection(Of String) = Nothing

        Public Property KeywordColor As SKColor = New SKColor(86, 156, 214)
        Public Property NumberColor As SKColor = New SKColor(181, 206, 168)
        Public Property StringColor As SKColor = New SKColor(206, 145, 120)
        Public Property CommentColor As SKColor = New SKColor(106, 153, 85)

        Public Sub New(Optional additionalKeywords As IEnumerable(Of String) = Nothing)
            For Each k As String In New String() {
                "if", "else", "for", "while", "return", "true", "false", "null", "nothing",
                "public", "private", "protected", "friend", "class", "interface", "function", "sub",
                "dim", "as", "new", "try", "catch", "finally", "select", "case", "imports",
                "namespace", "inherits", "implements", "readonly", "property", "get", "set",
                "option", "strict", "explicit", "on", "off", "and", "or", "not", "then", "end"
            }
                AddKeywordInternal(k)
            Next

            If additionalKeywords IsNot Nothing Then
                For Each keyword As String In additionalKeywords
                    AddKeywordInternal(keyword)
                Next
            End If
        End Sub

        Public ReadOnly Property Keywords As IReadOnlyCollection(Of String)
            Get
                If _keywordsView Is Nothing Then
                    _keywordsView = New List(Of String)(_keywords).AsReadOnly()
                End If

                Return _keywordsView
            End Get
        End Property

        Private Sub AddKeywordInternal(value As String)
            If String.IsNullOrWhiteSpace(value) Then Return
            _keywords.Add(value.Trim())
        End Sub

        Public Function GetSpans(text As String) As IReadOnlyList(Of MASTextSyntaxSpan) Implements IMASTextSyntaxHighlighter.GetSpans
            Dim s As String = If(text, String.Empty)
            Dim spans As New List(Of MASTextSyntaxSpan)()
            If s.Length = 0 Then Return spans

            Dim i As Integer = 0
            While i < s.Length
                Dim ch As Char = s(i)

                If ch = vbCr OrElse ch = vbLf Then
                    i += 1
                    Continue While
                End If

                If ch = "'"c Then
                    Dim endIndex As Integer = FindLineEnd(s, i)
                    spans.Add(New MASTextSyntaxSpan(i, endIndex - i, MASTextSyntaxStyle.FromColor(CommentColor)))
                    i = endIndex
                    Continue While
                End If

                If ch = "/"c AndAlso i + 1 < s.Length AndAlso s(i + 1) = "/"c Then
                    Dim endIndex As Integer = FindLineEnd(s, i)
                    spans.Add(New MASTextSyntaxSpan(i, endIndex - i, MASTextSyntaxStyle.FromColor(CommentColor)))
                    i = endIndex
                    Continue While
                End If

                If ch = """"c Then
                    Dim start As Integer = i
                    i += 1
                    While i < s.Length
                        If s(i) = vbCr OrElse s(i) = vbLf Then Exit While
                        If s(i) = """"c Then
                            i += 1
                            Exit While
                        End If
                        i += 1
                    End While
                    spans.Add(New MASTextSyntaxSpan(start, i - start, MASTextSyntaxStyle.FromColor(StringColor)))
                    Continue While
                End If

                If Char.IsDigit(ch) Then
                    Dim start As Integer = i
                    i += 1
                    While i < s.Length AndAlso (Char.IsDigit(s(i)) OrElse s(i) = "."c OrElse s(i) = ","c)
                        i += 1
                    End While
                    spans.Add(New MASTextSyntaxSpan(start, i - start, MASTextSyntaxStyle.FromColor(NumberColor)))
                    Continue While
                End If

                If Char.IsLetter(ch) OrElse ch = "_"c Then
                    Dim start As Integer = i
                    i += 1
                    While i < s.Length AndAlso (Char.IsLetterOrDigit(s(i)) OrElse s(i) = "_"c)
                        i += 1
                    End While

                    Dim word As String = s.Substring(start, i - start)
                    If _keywords.Contains(word) Then
                        Dim st As MASTextSyntaxStyle = MASTextSyntaxStyle.FromColor(KeywordColor)
                        st.Bold = True
                        spans.Add(New MASTextSyntaxSpan(start, i - start, st))
                    End If
                    Continue While
                End If

                i += 1
            End While

            Return spans
        End Function

        Private Shared Function FindLineEnd(value As String, start As Integer) As Integer
            If String.IsNullOrEmpty(value) Then Return 0

            Dim i As Integer = Math.Max(0, start)
            While i < value.Length
                If value(i) = vbCr OrElse value(i) = vbLf Then Exit While
                i += 1
            End While

            Return i
        End Function

    End Class

End Namespace
