Option Strict On
Option Explicit On

Imports System
Imports System.Collections.Generic
Imports Nexamas.UI.Architecture
Imports Nexamas.UI.Components.ListPopup
Imports Nexamas.UI.Components.ListRendering
Imports Nexamas.UI.General
Imports Nexamas.UI.Icons
Imports Nexamas.UI.Rendering
Imports Nexamas.UI.SkiaScroll
Imports Nexamas.UI.Theming
Imports Nexamas.UI.Values
Imports SkiaSharp

Namespace Nexamas.UI.Components

    Friend NotInheritable Class MASTreeRenderer
        Implements IDisposable

        Friend Enum TreeRowState
            Normal = 0
            Hover = 1
            Selected = 2
            Pressed = 3
            Disabled = 4
        End Enum

        Friend Structure TreeRowVisual
            Friend Index As Integer
            Friend Node As MASTreeNode
            Friend Depth As Integer

            Friend IconRect As SKRect
            Friend Icon As MASIconResult

            Friend Metrics As ListRowLayout.RowMetrics
            Friend ExpanderRect As SKRect
            Friend TextRect As SKRect
            Friend HasExpander As Boolean

            Friend State As TreeRowState
        End Structure

        Private ReadOnly _contract As MASResolvedComponentContract
        Private ReadOnly _scope As MASContractRenderScope

        Private ReadOnly _surface As New MASSurfaceFramePainter()
        Private ReadOnly _rowPainter As ListRowPainter
        Private ReadOnly _shadow As New MASShadowPainter()

        Private ReadOnly _glyph As New SKPaint With {
            .IsAntialias = True,
            .IsDither = True,
            .Style = SKPaintStyle.Stroke,
            .StrokeCap = SKStrokeCap.Round,
            .StrokeJoin = SKStrokeJoin.Round
        }

        Private _disposed As Boolean

        Friend Sub New(typography As MASTypography,
                       contract As MASResolvedComponentContract)

            If contract Is Nothing Then Throw New ArgumentNullException(NameOf(contract))

            _contract = contract

            _scope =
    MASArchitectureRuntime.CreateRenderScopeForComponent(Of MASTreeRowContract)(
        NameOf(MASTreeRenderer),
        _contract,
        MASRenderLayer.Surface,
        MASRenderLayer.Shadow,
        MASRenderLayer.Text,
        MASRenderLayer.Hover,
        MASRenderLayer.Pressed)


            _rowPainter = New ListRowPainter(typography)

        End Sub

        Friend Sub Render(canvas As SKCanvas,
                          ctx As MASThemeContext,
                          hostRectPx As SKRect,
                          contentRectPx As SKRect,
                          railRectPx As SKRect,
                          visibleRows As IReadOnlyList(Of TreeRowVisual),
                          scroll As SkiaScrollAdapter,
                          isScrollbarHover As Boolean)

            If _disposed Then Return
            If canvas Is Nothing OrElse ctx Is Nothing Then Return
            If hostRectPx.Width <= 1.0F OrElse hostRectPx.Height <= 1.0F Then Return

            _scope.RequireAnyLayer(
    MASRenderLayer.Surface,
    MASRenderLayer.Shadow,
    MASRenderLayer.Text,
    MASRenderLayer.Hover,
    MASRenderLayer.Pressed)

            Dim dpi As Single = PixelSnap.SafeDpi(ctx.Dpi)

            DrawContainer(canvas, ctx, hostRectPx, dpi)

            Dim save As Integer = canvas.Save()
            Try
                If contentRectPx.Width > 1.0F AndAlso contentRectPx.Height > 1.0F Then
                    canvas.ClipRect(contentRectPx)
                End If

                If visibleRows IsNot Nothing Then
                    For Each row As TreeRowVisual In visibleRows
                        DrawRow(canvas, ctx, row, dpi)
                    Next
                End If
            Finally
                canvas.RestoreToCount(save)
            End Try

            If scroll IsNot Nothing AndAlso railRectPx.Width > 1.0F AndAlso railRectPx.Height > 1.0F Then
                scroll.Draw(canvas, isScrollbarHover)
            End If

        End Sub

        Private Sub DrawContainer(canvas As SKCanvas,
                                  ctx As MASThemeContext,
                                  hostRectPx As SKRect,
                                  dpi As Single)

            Dim appTheme As IMASAppTheme = ctx.Theme
            If appTheme Is Nothing Then Return
            If MASAppThemeContracts.Families(appTheme).FrameSurface Is Nothing Then Return

            Dim surfaceTheme As IMASFrameSurfaceTheme = MASAppThemeContracts.Families(appTheme).FrameSurface

            Dim radius As Single =
                CornerRadii.ResolveFamilyPx(
                    CommonEnums.SurfaceFamily.ListSurface,
                    hostRectPx,
                    dpi)

            If Not _surface.TryDrawSwitchableViewportCardSurface(
                canvas:=canvas,
                ctx:=ctx,
                r:=hostRectPx,
                radius:=radius,
                dpi:=dpi) Then

                _surface.Draw(
                    canvas:=canvas,
                    r:=hostRectPx,
                    radius:=radius,
                    dpi:=dpi,
                    surfaceTheme:=surfaceTheme)
            End If

            Using wash As New SKPaint With {
                .IsAntialias = True,
                .IsDither = True,
                .Style = SKPaintStyle.Fill,
                .Color = surfaceTheme.SurfaceTop.WithAlpha(CollectionTokens.TreeContainerWashAlpha)
            }
                canvas.DrawRoundRect(hostRectPx, radius, radius, wash)
            End Using

        End Sub

        Private Sub DrawRow(canvas As SKCanvas,
                            ctx As MASThemeContext,
                            row As TreeRowVisual,
                            dpi As Single)

            _rowPainter.DrawRow(
                canvas:=canvas,
                ctx:=ctx,
                metrics:=row.Metrics,
                dpi:=dpi,
                text:=If(row.Node.Text, String.Empty),
                state:=MapRowState(row.State),
                contract:=_contract)

            DrawExpander(canvas, ctx, row, dpi)
            DrawIcon(canvas, row, dpi)

        End Sub

        Private Shared Sub DrawIcon(canvas As SKCanvas,
                                    row As TreeRowVisual,
                                    dpi As Single)

            If canvas Is Nothing Then Return
            If row.Icon Is Nothing OrElse Not row.Icon.HasIcon Then Return
            If row.IconRect.Width <= 1.0F OrElse row.IconRect.Height <= 1.0F Then Return

            MASIconResultPainter.Draw(
                canvas:=canvas,
                result:=row.Icon,
                bounds:=row.IconRect,
                dpi:=dpi)

        End Sub

        Private Sub DrawExpander(canvas As SKCanvas,
                                 ctx As MASThemeContext,
                                 row As TreeRowVisual,
                                 dpi As Single)

            If Not row.HasExpander Then Return
            If row.ExpanderRect.Width <= 1.0F OrElse row.ExpanderRect.Height <= 1.0F Then Return

            Dim col As SKColor = ctx.ListTheme.RowText.Normal

            If row.State = TreeRowState.Disabled Then
                col = ctx.ListTheme.RowText.Disabled
            End If

            If col.Alpha = 0 Then
                col = ctx.TextColorTheme.PrimaryText
            End If

            _glyph.Color = col.WithAlpha(CollectionTokens.TreeExpanderAlpha)
            _glyph.StrokeWidth =
                Math.Max(1.0F, CollectionTokens.TreeExpanderStrokeDip * dpi)

            Dim cx As Single = row.ExpanderRect.MidX
            Dim cy As Single = row.ExpanderRect.MidY
            Dim s As Single =
                Math.Max(
                    CollectionTokens.TreeExpanderArmMinPx,
                    CollectionTokens.TreeExpanderArmDip * dpi)

            Using path As New SKPath()
                If row.Node.IsExpanded Then
                    path.MoveTo(cx - s, cy - (s * 0.45F))
                    path.LineTo(cx, cy + (s * 0.55F))
                    path.LineTo(cx + s, cy - (s * 0.45F))
                Else
                    path.MoveTo(cx - (s * 0.45F), cy - s)
                    path.LineTo(cx + (s * 0.55F), cy)
                    path.LineTo(cx - (s * 0.45F), cy + s)
                End If

                canvas.DrawPath(path, _glyph)
            End Using

        End Sub

        Private Shared Function MapRowState(state As TreeRowState) As MASInteractionState

            Select Case state
                Case TreeRowState.Hover
                    Return MASInteractionState.Hovered

                Case TreeRowState.Selected
                    Return MASInteractionState.Selected

                Case TreeRowState.Pressed
                    Return MASInteractionState.Pressed

                Case TreeRowState.Disabled
                    Return MASInteractionState.Disabled

                Case Else
                    Return MASInteractionState.None
            End Select

        End Function

        Public Sub Dispose() Implements IDisposable.Dispose
            If _disposed Then Return
            _disposed = True

            Try
                _surface.Dispose()
            Catch masCaughtException1 As Exception
                Nexamas.UI.Diagnostics.MASExceptionSilencer.SwallowDisposeCleanup(masCaughtException1)
            End Try
            Try
                _rowPainter.Dispose()
            Catch masCaughtException2 As Exception
                Nexamas.UI.Diagnostics.MASExceptionSilencer.SwallowDisposeCleanup(masCaughtException2)
            End Try
            Try
                _shadow.Dispose()
            Catch masCaughtException3 As Exception
                Nexamas.UI.Diagnostics.MASExceptionSilencer.SwallowDisposeCleanup(masCaughtException3)
            End Try
            Try
                _glyph.Dispose()
            Catch masCaughtException4 As Exception
                Nexamas.UI.Diagnostics.MASExceptionSilencer.SwallowDisposeCleanup(masCaughtException4)
            End Try

        End Sub

    End Class

End Namespace
