Option Strict On
Option Explicit On

Imports System
Imports Nexamas.UI.Controls
Imports Nexamas.UI.General
Imports Nexamas.UI.Icons
Imports Nexamas.UI.Theming
Imports Nexamas.UI.Values
Imports SkiaSharp

Namespace Nexamas.UI.Rendering

    Friend NotInheritable Class GlyphButtonRenderer
        Implements IDisposable

#Region "Constants"

        Private Const IconPaddingDip As Single = 2.0F

#End Region

#Region "Fields"

        Private _disposed As Boolean

        Private ReadOnly _backgroundFillPaint As New SKPaint With {
            .IsAntialias = True,
            .IsDither = True,
            .Style = SKPaintStyle.Fill
        }

        Private ReadOnly _backgroundStrokePaint As New SKPaint With {
            .IsAntialias = True,
            .IsDither = True,
            .Style = SKPaintStyle.Stroke
        }

#End Region

#Region "Nested"

        Private Structure GlyphButtonPalette
            Friend FillColor As SKColor
            Friend StrokeColor As SKColor
            Friend GlyphColor As SKColor
        End Structure

#End Region

#Region "Draw"

        Friend Sub Draw(canvas As SKCanvas,
                        ctx As MASThemeContext,
                        boundsPx As SKRect,
                        state As GlyphButtonVisualState,
                        opacity As Single,
                        intent As GlyphButtonIntent,
                        iconKind As MASIconKind,
                        iconStyle As MASIconStyle,
                        Optional iconPaddingDip As Single = IconPaddingDip)

            If _disposed Then Return
            If canvas Is Nothing OrElse ctx Is Nothing Then Return
            If boundsPx.Width <= 1.0F OrElse boundsPx.Height <= 1.0F Then Return

            opacity = Clamp01(opacity)

            Dim dpi As Single = PixelSnap.SafeDpi(ctx.Dpi)
            Dim radius As Single = Math.Min(boundsPx.Width, boundsPx.Height) * 0.34F
            Dim palette As GlyphButtonPalette = ResolvePalette(ctx, state, opacity, intent)

            DrawBackground(canvas, boundsPx, radius, palette)
            DrawIcon(canvas, boundsPx, dpi, iconKind, iconStyle, palette.GlyphColor, iconPaddingDip)

        End Sub

        Private Shared Sub DrawIcon(canvas As SKCanvas,
                                    boundsPx As SKRect,
                                    dpi As Single,
                                    iconKind As MASIconKind,
                                    iconStyle As MASIconStyle,
                                    glyphColor As SKColor,
                                    iconPaddingDip As Single)

            If canvas Is Nothing Then Return
            If iconKind = MASIconKind.None Then Return
            If glyphColor.Alpha = 0 Then Return
            If boundsPx.Width <= 1.0F OrElse boundsPx.Height <= 1.0F Then Return

            dpi = PixelSnap.SafeDpi(dpi)

            If iconPaddingDip < 0.0F OrElse Single.IsNaN(iconPaddingDip) OrElse Single.IsInfinity(iconPaddingDip) Then
                iconPaddingDip = IconPaddingDip
            End If

            Dim paddingPx As Single = iconPaddingDip * dpi

            Dim iconBounds As New SKRect(
                boundsPx.Left + paddingPx,
                boundsPx.Top + paddingPx,
                boundsPx.Right - paddingPx,
                boundsPx.Bottom - paddingPx
            )

            If iconBounds.Width <= 1.0F OrElse iconBounds.Height <= 1.0F Then Return

            MASIconPainter.Draw(
                canvas:=canvas,
                bounds:=iconBounds,
                dpi:=dpi,
                kind:=iconKind,
                color:=glyphColor,
                style:=iconStyle
            )

        End Sub

#End Region

#Region "Palette"

        Private Shared Function ResolvePalette(ctx As MASThemeContext,
                                               state As GlyphButtonVisualState,
                                               opacity As Single,
                                               intent As GlyphButtonIntent) As GlyphButtonPalette

            Dim p As New GlyphButtonPalette With {
                .FillColor = SKColors.Transparent,
                .StrokeColor = SKColors.Transparent,
                .GlyphColor = ResolveNormalGlyph(ctx, opacity, intent)
            }

            Select Case state

                Case GlyphButtonVisualState.Normal
                    p.GlyphColor = ResolveNormalGlyph(ctx, opacity, intent)


                Case GlyphButtonVisualState.Hover
                    p.FillColor = ResolveHoverFill(ctx, opacity, intent)
                    p.StrokeColor = ResolveHoverStroke(ctx, opacity, intent)
                    p.GlyphColor = ResolveHoverGlyph(ctx, opacity, intent)

                Case GlyphButtonVisualState.Pressed
                    p.FillColor = ResolvePressedFill(ctx, opacity, intent)
                    p.StrokeColor = ResolvePressedStroke(ctx, opacity, intent)
                    p.GlyphColor = ResolvePressedGlyph(ctx, opacity, intent)

                Case GlyphButtonVisualState.Focused
                    p.FillColor = ResolveHoverFill(ctx, opacity, intent)
                    p.StrokeColor = ResolveFocusedStroke(ctx, opacity, intent)
                    p.GlyphColor = ResolveHoverGlyph(ctx, opacity, intent)

                Case GlyphButtonVisualState.Disabled
                    p.FillColor = ResolveDisabledFill(ctx, opacity)
                    p.StrokeColor = ResolveDisabledStroke(ctx, opacity)
                    p.GlyphColor = ResolveDisabledGlyph(ctx, opacity)

            End Select

            Return p

        End Function

        Private Shared Function ResolveNormalGlyph(ctx As MASThemeContext,
                                                   opacity As Single,
                                                   intent As GlyphButtonIntent) As SKColor

            If intent = GlyphButtonIntent.Toolbar Then
                Return ColorMath.WithAlpha(
                    ctx.TextColorTheme.InverseText,
                    ScaleAlphaByte(238, opacity)
                )
            End If

            Return ColorMath.WithAlpha(
                ctx.TextColorTheme.MutedText,
                ScaleAlphaByte(220, opacity)
            )

        End Function

        Private Shared Function ResolveHoverFill(ctx As MASThemeContext,
                                                 opacity As Single,
                                                 intent As GlyphButtonIntent) As SKColor

            If intent = GlyphButtonIntent.Toolbar Then
                Return ColorMath.WithAlpha(SKColors.White, ScaleAlphaByte(26, opacity))
            End If

            If intent = GlyphButtonIntent.Danger Then
                Return ColorMath.WithAlpha(MASAppThemeContracts.Foundation(ctx.Theme).Semantics.Danger, ScaleAlphaByte(34, opacity))
            End If

            Dim fillLayer As MASInteractionThemeLayer = ctx.InteractionTheme.Hover.Fill

            If fillLayer.Enabled Then
                Return ScaleColorAlpha(fillLayer.Color, 0.3F * opacity)
            End If

            Return ColorMath.WithAlpha(
                ColorMath.Mix(ctx.TextColorTheme.PrimaryText, SKColors.White, 0.12F),
                ScaleAlphaByte(26, opacity)
            )

        End Function

        Private Shared Function ResolveHoverStroke(ctx As MASThemeContext,
                                                   opacity As Single,
                                                   intent As GlyphButtonIntent) As SKColor

            If intent = GlyphButtonIntent.Toolbar Then
                Return ColorMath.WithAlpha(SKColors.White, ScaleAlphaByte(58, opacity))
            End If

            If intent = GlyphButtonIntent.Danger Then
                Dim danger As SKColor = MASAppThemeContracts.Foundation(ctx.Theme).Semantics.Danger
                Dim strokeCol As SKColor =
                    ColorMath.MixPerceptual(danger, ctx.TextColorTheme.PrimaryText, 0.18F)

                Return ColorMath.WithAlpha(strokeCol, ScaleAlphaByte(132, opacity))
            End If

            Dim ringLayer As MASInteractionThemeLayer = ctx.InteractionTheme.Hover.Ring

            If ringLayer.Enabled Then
                Return ScaleColorAlpha(ringLayer.Color, 0.42F * opacity)
            End If

            Return ColorMath.WithAlpha(ctx.TextColorTheme.MutedText, ScaleAlphaByte(44, opacity))

        End Function

        Private Shared Function ResolveHoverGlyph(ctx As MASThemeContext,
                                                  opacity As Single,
                                                  intent As GlyphButtonIntent) As SKColor

            If intent = GlyphButtonIntent.Toolbar Then
                Return ColorMath.WithAlpha(
                    ctx.TextColorTheme.InverseText,
                    ScaleAlphaByte(255, opacity)
                )
            End If

            If intent = GlyphButtonIntent.Danger Then
                Dim danger As SKColor = MASAppThemeContracts.Foundation(ctx.Theme).Semantics.Danger
                Dim glyphCol As SKColor =
                    ColorMath.MixPerceptual(danger, ctx.TextColorTheme.PrimaryText, 0.1F)

                Return ColorMath.WithAlpha(glyphCol, ScaleAlphaByte(248, opacity))
            End If

            Dim ringLayer As MASInteractionThemeLayer = ctx.InteractionTheme.Hover.Ring

            If ringLayer.Enabled Then
                Dim mixed As SKColor =
                    ColorMath.MixPerceptual(
                        ctx.TextColorTheme.PrimaryText,
                        ringLayer.Color,
                        0.18F
                    )

                Return ColorMath.WithAlpha(mixed, ScaleAlphaByte(236, opacity))
            End If

            Return ColorMath.WithAlpha(ctx.TextColorTheme.PrimaryText, ScaleAlphaByte(236, opacity))

        End Function

        Private Shared Function ResolvePressedFill(ctx As MASThemeContext,
                                                   opacity As Single,
                                                   intent As GlyphButtonIntent) As SKColor

            If intent = GlyphButtonIntent.Toolbar Then
                Return ColorMath.WithAlpha(SKColors.White, ScaleAlphaByte(40, opacity))
            End If

            If intent = GlyphButtonIntent.Danger Then
                Return ColorMath.WithAlpha(MASAppThemeContracts.Foundation(ctx.Theme).Semantics.Danger, ScaleAlphaByte(52, opacity))
            End If

            Dim fillLayer As MASInteractionThemeLayer = ctx.InteractionTheme.Pressed.Fill

            If fillLayer.Enabled Then
                Return ScaleColorAlpha(fillLayer.Color, 0.38F * opacity)
            End If

            Return ColorMath.WithAlpha(
                ColorMath.Mix(ctx.TextColorTheme.PrimaryText, SKColors.Black, 0.12F),
                ScaleAlphaByte(42, opacity)
            )

        End Function

        Private Shared Function ResolvePressedStroke(ctx As MASThemeContext,
                                                     opacity As Single,
                                                     intent As GlyphButtonIntent) As SKColor

            If intent = GlyphButtonIntent.Toolbar Then
                Return ColorMath.WithAlpha(SKColors.White, ScaleAlphaByte(86, opacity))
            End If

            If intent = GlyphButtonIntent.Danger Then
                Dim danger As SKColor = MASAppThemeContracts.Foundation(ctx.Theme).Semantics.Danger
                Dim strokeCol As SKColor =
                    ColorMath.MixPerceptual(danger, ctx.TextColorTheme.PrimaryText, 0.12F)

                Return ColorMath.WithAlpha(strokeCol, ScaleAlphaByte(156, opacity))
            End If

            Dim ringLayer As MASInteractionThemeLayer = ctx.InteractionTheme.Pressed.Ring

            If ringLayer.Enabled Then
                Return ScaleColorAlpha(ringLayer.Color, 0.52F * opacity)
            End If

            Return ColorMath.WithAlpha(ctx.TextColorTheme.MutedText, ScaleAlphaByte(66, opacity))

        End Function

        Private Shared Function ResolveFocusedStroke(ctx As MASThemeContext,
                                                     opacity As Single,
                                                     intent As GlyphButtonIntent) As SKColor

            If intent = GlyphButtonIntent.Toolbar Then
                Return ColorMath.WithAlpha(SKColors.White, ScaleAlphaByte(112, opacity))
            End If

            If intent = GlyphButtonIntent.Danger Then
                Dim danger As SKColor = MASAppThemeContracts.Foundation(ctx.Theme).Semantics.Danger
                Return ColorMath.WithAlpha(ColorMath.MixPerceptual(danger, ctx.TextColorTheme.PrimaryText, 0.1F), ScaleAlphaByte(184, opacity))
            End If

            Dim ringLayer As MASInteractionThemeLayer = ctx.InteractionTheme.Focused.Ring
            If ringLayer.Enabled Then
                Return ScaleColorAlpha(ringLayer.Color, 0.74F * opacity)
            End If

            Return ColorMath.WithAlpha(ctx.TextColorTheme.PrimaryText, ScaleAlphaByte(96, opacity))
        End Function

        Private Shared Function ResolvePressedGlyph(ctx As MASThemeContext,
                                                    opacity As Single,
                                                    intent As GlyphButtonIntent) As SKColor

            If intent = GlyphButtonIntent.Toolbar Then
                Return ColorMath.WithAlpha(
                    ctx.TextColorTheme.InverseText,
                    ScaleAlphaByte(255, opacity)
                )
            End If

            If intent = GlyphButtonIntent.Danger Then
                Dim danger As SKColor = MASAppThemeContracts.Foundation(ctx.Theme).Semantics.Danger
                Dim glyphCol As SKColor =
                    ColorMath.MixPerceptual(danger, ctx.TextColorTheme.PrimaryText, 0.06F)

                Return ColorMath.WithAlpha(glyphCol, ScaleAlphaByte(255, opacity))
            End If

            Dim ringLayer As MASInteractionThemeLayer = ctx.InteractionTheme.Pressed.Ring

            If ringLayer.Enabled Then
                Dim mixed As SKColor =
                    ColorMath.MixPerceptual(
                        ctx.TextColorTheme.PrimaryText,
                        ringLayer.Color,
                        0.24F
                    )

                Return ColorMath.WithAlpha(mixed, ScaleAlphaByte(250, opacity))
            End If

            Return ColorMath.WithAlpha(ctx.TextColorTheme.PrimaryText, ScaleAlphaByte(250, opacity))

        End Function

        Private Shared Function ResolveDisabledFill(ctx As MASThemeContext,
                                                    opacity As Single) As SKColor

            Dim fillLayer As MASInteractionThemeLayer = ctx.InteractionTheme.DisabledFill

            If fillLayer.Enabled Then
                Return ScaleColorAlpha(fillLayer.Color, 0.34F * opacity)
            End If

            Return ColorMath.WithAlpha(ctx.TextColorTheme.DisabledText, ScaleAlphaByte(14, opacity))

        End Function

        Private Shared Function ResolveDisabledStroke(ctx As MASThemeContext,
                                                      opacity As Single) As SKColor

            Return ColorMath.WithAlpha(ctx.TextColorTheme.DisabledText, ScaleAlphaByte(20, opacity))

        End Function

        Private Shared Function ResolveDisabledGlyph(ctx As MASThemeContext,
                                                     opacity As Single) As SKColor

            Return ColorMath.WithAlpha(ctx.TextColorTheme.DisabledText, ScaleAlphaByte(120, opacity))

        End Function

#End Region

#Region "Background"

        Private Sub DrawBackground(canvas As SKCanvas,
                                   boundsPx As SKRect,
                                   radius As Single,
                                   palette As GlyphButtonPalette)

            If canvas Is Nothing Then Return
            If boundsPx.Width <= 1.0F OrElse boundsPx.Height <= 1.0F Then Return

            If palette.FillColor.Alpha > 0 Then
                _backgroundFillPaint.IsAntialias = True
                _backgroundFillPaint.IsDither = True
                _backgroundFillPaint.Style = SKPaintStyle.Fill
                _backgroundFillPaint.Color = palette.FillColor
                _backgroundFillPaint.Shader = Nothing
                _backgroundFillPaint.ImageFilter = Nothing
                _backgroundFillPaint.BlendMode = SKBlendMode.SrcOver

                canvas.DrawRoundRect(boundsPx, radius, radius, _backgroundFillPaint)
            End If

            If palette.StrokeColor.Alpha > 0 Then
                _backgroundStrokePaint.IsAntialias = True
                _backgroundStrokePaint.IsDither = True
                _backgroundStrokePaint.Style = SKPaintStyle.Stroke
                _backgroundStrokePaint.StrokeWidth = 1.0F
                _backgroundStrokePaint.Color = palette.StrokeColor
                _backgroundStrokePaint.Shader = Nothing
                _backgroundStrokePaint.ImageFilter = Nothing
                _backgroundStrokePaint.BlendMode = SKBlendMode.SrcOver

                canvas.DrawRoundRect(boundsPx, radius, radius, _backgroundStrokePaint)
            End If

        End Sub

#End Region

#Region "Helpers"

        Private Shared Function ScaleColorAlpha(color As SKColor,
                                                mul As Single) As SKColor

            If mul <= 0.0F Then Return SKColors.Transparent

            Dim a As Integer = CInt(Math.Round(color.Alpha * mul))

            If a < 0 Then a = 0
            If a > 255 Then a = 255

            Return ColorMath.WithAlpha(color, CByte(a))

        End Function

        Private Shared Function ScaleAlphaByte(value As Byte,
                                               opacity As Single) As Byte

            If opacity <= 0.0F Then Return 0

            Dim v As Integer = CInt(Math.Round(value * opacity))

            If v < 0 Then v = 0
            If v > 255 Then v = 255

            Return CByte(v)

        End Function

        Private Shared Function Clamp01(value As Single) As Single

            If Single.IsNaN(value) OrElse Single.IsInfinity(value) Then Return 1.0F
            If value < 0.0F Then Return 0.0F
            If value > 1.0F Then Return 1.0F

            Return value

        End Function

#End Region

#Region "Dispose"

        Public Sub Dispose() Implements IDisposable.Dispose

            If _disposed Then Return
            _disposed = True

            SafeDispose(_backgroundFillPaint)
            SafeDispose(_backgroundStrokePaint)

        End Sub

        Private Shared Sub SafeDispose(resource As IDisposable)
            If resource Is Nothing Then Return

            Try
                resource.Dispose()
            Catch masCaughtException1 As Exception
                Nexamas.UI.Diagnostics.MASExceptionSilencer.SwallowDisposeCleanup(masCaughtException1)
            End Try
        End Sub

#End Region

    End Class

End Namespace