Option Strict On
Option Explicit On

Imports System
Imports Nexamas.UI.Architecture
Imports Nexamas.UI.Components
Imports Nexamas.UI.Controls
Imports Nexamas.UI.General
Imports Nexamas.UI.Theming
Imports Nexamas.UI.Values
Imports SkiaSharp

Namespace Nexamas.UI.Rendering

    Friend NotInheritable Class ProgressBarRenderer
        Implements IDisposable

        Private ReadOnly _shadow As New MASShadowPainter()
        Private _disposed As Boolean

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

        Friend Sub Draw(canvas As SKCanvas,
                ctx As MASThemeContext,
                r As SKRect,
                minimum As Double,
                maximum As Double,
                value As Double,
                visualState As MASProgressBarState,
                frameMode As MASProgressBarFrameMode,
                containerSurfaceMaterialKey As String,
                containerBorderStrength As MASSurfaceStrength,
                containerSurfaceStrength As MASSurfaceStrength,
                contract As MASResolvedComponentContract)

            If _disposed OrElse canvas Is Nothing OrElse ctx Is Nothing Then Return
            If contract Is Nothing Then Throw New ArgumentNullException(NameOf(contract))

            Dim scope As MASContractRenderScope =
    MASArchitectureRuntime.CreateRenderScopeForComponent(Of MASProgressBar)(
        NameOf(ProgressBarRenderer),
        contract,
        MASRenderLayer.Surface,
        MASRenderLayer.Border,
        MASRenderLayer.Shadow)

            scope.RequireAnyLayer(
    MASRenderLayer.Surface,
    MASRenderLayer.Border,
    MASRenderLayer.Shadow)

            DrawCore(
    canvas,
    ctx,
    ctx.Dpi,
    ctx.ProgressTheme,
    r,
    minimum,
    maximum,
    value,
    visualState,
    NormalizeFrameMode(frameMode),
    containerSurfaceMaterialKey,
    containerBorderStrength,
    containerSurfaceStrength,
    contract,
    _shadow)
        End Sub

#Region "Core"

        Private Shared Sub DrawCore(canvas As SKCanvas,
                            ctx As MASThemeContext,
                            dpi As Single,
                            theme As IMASProgressTheme,
                            r As SKRect,
                            minimum As Double,
                            maximum As Double,
                            value As Double,
                            visualState As MASProgressBarState,
                            frameMode As MASProgressBarFrameMode,
                            containerSurfaceMaterialKey As String,
                            containerBorderStrength As MASSurfaceStrength,
                            containerSurfaceStrength As MASSurfaceStrength,
                            contract As MASResolvedComponentContract,
                            shadow As MASShadowPainter)

            If canvas Is Nothing OrElse ctx Is Nothing OrElse theme Is Nothing Then Return
            If r.Width <= 6.0F OrElse r.Height <= 6.0F Then Return

            Dim dpiSafe As Single = PixelSnap.SafeDpi(dpi)
            Dim containerRect As SKRect = PixelSnap.SnapRectHalf(r)

            Dim containerRadius As Single =
    MASRadiusResolver.ResolveSurfacePx(
        contract:=contract,
        rPx:=containerRect,
        dpi:=dpiSafe)

            containerRadius = PixelSnap.SafeRadius(containerRadius)

            Dim trackRect As SKRect = ResolveTrackRect(containerRect, dpiSafe, frameMode)
            If trackRect.Width <= 1.0F OrElse trackRect.Height <= 1.0F Then Return

            Dim trackRadius As Single =
    MASRadiusResolver.ResolveSurfacePx(
        contract:=contract,
        rPx:=trackRect,
        dpi:=dpiSafe)

            trackRadius = PixelSnap.SafeRadius(trackRadius)

            If frameMode = MASProgressBarFrameMode.ContainedField Then
                DrawContainer(
                    canvas:=canvas,
                    ctx:=ctx,
                    rect:=containerRect,
                    radius:=containerRadius,
                    materialKey:=containerSurfaceMaterialKey,
                    borderStrength:=containerBorderStrength,
                    surfaceStrength:=containerSurfaceStrength,
                    contract:=contract,
                    shadow:=shadow,
                    dpi:=dpiSafe)
            End If

            Dim frac As Single = ResolveFraction(minimum, maximum, value)

            DrawTrack(canvas, trackRect, trackRadius, theme.Track)

            If frac > 0.0F Then
                DrawFill(canvas, trackRect, trackRadius, frac, theme, visualState, dpiSafe)
            End If

        End Sub

#End Region

#Region "Container"

        Private Shared Sub DrawContainer(canvas As SKCanvas,
                                         ctx As MASThemeContext,
                                         rect As SKRect,
                                         radius As Single,
                                         materialKey As String,
                                         borderStrength As MASSurfaceStrength,
                                         surfaceStrength As MASSurfaceStrength,
                                         contract As MASResolvedComponentContract,
                                         shadow As MASShadowPainter,
                                         dpi As Single)

            If canvas Is Nothing OrElse ctx Is Nothing OrElse contract Is Nothing Then Return
            If rect.Width <= 1.0F OrElse rect.Height <= 1.0F Then Return

            Dim surfaceTheme As MASSurfaceThemeProfile =
                MASSurfaceThemeProfileResolver.Resolve(ctx)

            Dim borderStrengthLevel As Integer = ResolveStrengthLevel(borderStrength, surfaceTheme, True)
            Dim surfaceStrengthLevel As Integer = ResolveStrengthLevel(surfaceStrength, surfaceTheme, False)
            Dim requestedFrameStrength As MASSurfaceFrameStrength = ToFrameStrength(borderStrengthLevel)
            Dim hasExplicitMaterialOverride As Boolean = HasExplicitMaterialKey(materialKey)
            Dim normalizedMaterialKey As String = NormalizeMaterialKey(materialKey)

            If shadow IsNot Nothing Then
                DrawContainerShadow(
                    canvas:=canvas,
                    rect:=rect,
                    radius:=radius,
                    contract:=contract,
                    shadow:=shadow,
                    dpi:=dpi,
                    surfaceTheme:=surfaceTheme)
            End If

            MASSurfaceMaterialComponentGateway.TryDrawProgressSurface(
                canvas:=canvas,
                ctx:=ctx,
                boundsPx:=rect,
                materialKey:=normalizedMaterialKey,
                dpi:=dpi,
                cornerRadiusPx:=radius,
                hasExplicitMaterialOverride:=hasExplicitMaterialOverride,
                surfaceStrengthLevel:=surfaceStrengthLevel,
                borderStrengthLevel:=borderStrengthLevel,
                frameStrength:=requestedFrameStrength)

        End Sub

        Private Shared Sub DrawContainerShadow(canvas As SKCanvas,
                                               rect As SKRect,
                                               radius As Single,
                                               contract As MASResolvedComponentContract,
                                               shadow As MASShadowPainter,
                                               dpi As Single,
                                               surfaceTheme As MASSurfaceThemeProfile)

            If canvas Is Nothing OrElse shadow Is Nothing Then Return
            If contract Is Nothing Then Return

            Dim spec As MASLayeredShadowSpec =
                MASShadowResolver.ResolveLayered(contract)

            Dim shadowBase As SKColor = ResolveContainerShadowBase(surfaceTheme)

            shadow.DrawLayeredShadow(
                canvas:=canvas,
                r:=rect,
                radiusPx:=radius,
                dpi:=dpi,
                shadowBase:=shadowBase,
                spec:=spec)

        End Sub

        Private Shared Function ResolveContainerShadowBase(surfaceTheme As MASSurfaceThemeProfile) As SKColor
            If surfaceTheme IsNot Nothing Then
                Dim c As SKColor = surfaceTheme.Chrome.ShadowBase
                If c.Alpha <> 0 Then Return c

                c = surfaceTheme.Chrome.ShadowAmbient
                If c.Alpha <> 0 Then Return c

                c = surfaceTheme.Chrome.ShadowContact
                If c.Alpha <> 0 Then Return c

                c = surfaceTheme.Frame.ShadowBase
                If c.Alpha <> 0 Then Return c
            End If

            Return Nexamas.UI.Values.Color.Core.ShadowBase
        End Function

        Private Shared Function ResolveStrengthLevel(value As MASSurfaceStrength,
                                                     surfaceTheme As MASSurfaceThemeProfile,
                                                     isBorder As Boolean) As Integer

            If value <> MASSurfaceStrength.ThemeDefault Then
                Return ClampStrength(CInt(value))
            End If

            If surfaceTheme IsNot Nothing Then
                If isBorder Then
                    Return ClampStrength(surfaceTheme.Chrome.DefaultBorderStrengthLevel)
                End If

                Return ClampStrength(surfaceTheme.Chrome.DefaultSurfaceStrengthLevel)
            End If

            Return 3
        End Function

        Private Shared Function ClampStrength(value As Integer) As Integer
            If value < 1 Then Return 1
            If value > 6 Then Return 6
            Return value
        End Function

        Private Shared Function NormalizeMaterialKey(value As String) As String
            Dim key As String = If(value, String.Empty).Trim()
            If key.Length = 0 Then Return MASSurfaceMaterialIds.PlatformDefault
            Return key
        End Function

        Private Shared Function HasExplicitMaterialKey(value As String) As Boolean
            Dim key As String = If(value, String.Empty).Trim()
            Return key.Length > 0 AndAlso Not String.Equals(key, MASSurfaceMaterialIds.Auto, StringComparison.OrdinalIgnoreCase)
        End Function

        Private Shared Function ToFrameStrength(value As Integer) As MASSurfaceFrameStrength
            Select Case ClampStrength(value)
                Case 1
                    Return MASSurfaceFrameStrength.Level1
                Case 2
                    Return MASSurfaceFrameStrength.Level2
                Case 3
                    Return MASSurfaceFrameStrength.Level3
                Case 4
                    Return MASSurfaceFrameStrength.Level4
                Case 5
                    Return MASSurfaceFrameStrength.Level5
                Case Else
                    Return MASSurfaceFrameStrength.Level6
            End Select
        End Function

#End Region

#Region "Track"

        Private Shared Sub DrawTrack(canvas As SKCanvas,
                                     rect As SKRect,
                                     radius As Single,
                                     theme As MASProgressTrackTheme)

            Using p As New SKPaint With {
                .IsAntialias = True,
                .Color = theme.Background
            }
                canvas.DrawRoundRect(rect, radius, radius, p)
            End Using

            Using p As New SKPaint With {
                .IsAntialias = True,
                .Style = SKPaintStyle.Stroke,
                .StrokeWidth = 1.0F,
                .Color = theme.Border
            }
                canvas.DrawRoundRect(rect, radius, radius, p)
            End Using

        End Sub

#End Region

#Region "Fill"

        Private Shared Sub DrawFill(canvas As SKCanvas,
                                    rect As SKRect,
                                    radius As Single,
                                    frac As Single,
                                    theme As IMASProgressTheme,
                                    state As MASProgressBarState,
                                    dpi As Single)

            frac = Math.Max(0.0F, Math.Min(1.0F, frac))

            Dim fillWidth As Single = rect.Width * frac
            Dim minimumVisibleWidth As Single = ResolveMinimumVisibleFillWidthPx(theme, dpi)
            If frac > 0.0F AndAlso frac < 1.0F AndAlso minimumVisibleWidth > 0.0F Then
                fillWidth = Math.Min(rect.Width, Math.Max(fillWidth, minimumVisibleWidth))
            End If

            If fillWidth <= 0.25F OrElse rect.Height <= 0.25F Then Return

            Dim fillRect As New SKRect(
                rect.Left,
                rect.Top,
                rect.Left + fillWidth,
                rect.Bottom
            )

            Dim fillTheme As ProgressFillStateTheme = ResolveFill(theme.Fill, state)

            Using shader = SKShader.CreateLinearGradient(
    New SKPoint(fillRect.Left, fillRect.Top),
    New SKPoint(fillRect.Left, fillRect.Bottom),
                New SKColor() {fillTheme.Top, fillTheme.Mid, fillTheme.Bottom},
                Nothing,
                SKShaderTileMode.Clamp)

                Using paint As New SKPaint With {
                    .IsAntialias = True,
                    .Shader = shader
                }

                    Using clip = New SKPath()
                        clip.AddRoundRect(rect, radius, radius)
                        canvas.Save()
                        canvas.ClipPath(clip)
                        canvas.DrawRect(fillRect, paint)
                        canvas.Restore()
                    End Using

                End Using
            End Using

        End Sub

#End Region

#Region "Geometry"

        Private Shared Function ResolveTrackRect(containerRect As SKRect,
                                                 dpi As Single,
                                                 frameMode As MASProgressBarFrameMode) As SKRect

            If frameMode <> MASProgressBarFrameMode.ContainedField Then Return containerRect

            Dim pad As Single = ResolveContainerPaddingPx(containerRect, dpi)
            If pad <= 0.0F Then Return containerRect

            Dim track As SKRect = containerRect
            track.Inflate(-pad, -pad)

            If track.Width <= 1.0F OrElse track.Height <= 1.0F Then Return containerRect
            Return PixelSnap.SnapRectHalf(track)

        End Function

        Private Shared Function ResolveContainerPaddingPx(containerRect As SKRect,
                                                          dpi As Single) As Single

            dpi = PixelSnap.SafeDpi(dpi)

            Dim requested As Single = ProgressBarTokens.ContainerPadding * dpi
            Dim maxByHeight As Single = Math.Max(0.0F, (containerRect.Height - ProgressBarTokens.MinTrackThickness * dpi) * 0.5F)
            Dim maxByWidth As Single = Math.Max(0.0F, (containerRect.Width - ProgressBarTokens.MinWidth * dpi) * 0.5F)

            Return Math.Max(0.0F, Math.Min(requested, Math.Min(maxByHeight, maxByWidth)))

        End Function

#End Region

#Region "Helpers"

        Private Shared Function NormalizeFrameMode(value As MASProgressBarFrameMode) As MASProgressBarFrameMode
            Select Case value
                Case MASProgressBarFrameMode.None,
                     MASProgressBarFrameMode.ContainedField
                    Return value
                Case Else
                    Return MASProgressBarFrameMode.None
            End Select
        End Function

        Private Shared Function ResolveMinimumVisibleFillWidthPx(theme As IMASProgressTheme,
                                                                  dpi As Single) As Single
            If theme Is Nothing Then Return 0.0F

            Dim requestedDip As Single = theme.Metrics.MinimumVisibleFillWidthDip
            If Single.IsNaN(requestedDip) OrElse Single.IsInfinity(requestedDip) OrElse requestedDip <= 0.0F Then Return 0.0F

            Return requestedDip * PixelSnap.SafeDpi(dpi)
        End Function

        Private Shared Function ResolveFill(fill As MASProgressFillTheme,
                                            state As MASProgressBarState) As ProgressFillStateTheme
            Select Case state
                Case MASProgressBarState.Success : Return fill.Success
                Case MASProgressBarState.Warning : Return fill.Warning
                Case MASProgressBarState.Danger : Return fill.Danger
                Case Else : Return fill.Normal
            End Select
        End Function

        Private Shared Function ResolveFraction(minimum As Double,
                                                maximum As Double,
                                                value As Double) As Single

            If Double.IsNaN(minimum) OrElse Double.IsInfinity(minimum) Then Return 0.0F
            If Double.IsNaN(maximum) OrElse Double.IsInfinity(maximum) Then Return 0.0F
            If Double.IsNaN(value) OrElse Double.IsInfinity(value) Then value = minimum
            If maximum <= minimum Then Return 0.0F

            If value < minimum Then value = minimum
            If value > maximum Then value = maximum

            Dim fraction As Double = (value - minimum) / (maximum - minimum)
            If Double.IsNaN(fraction) OrElse Double.IsInfinity(fraction) Then Return 0.0F
            If fraction < 0.0R Then Return 0.0F
            If fraction > 1.0R Then Return 1.0F
            Return CSng(fraction)
        End Function

#End Region

    End Class

End Namespace
