Option Strict On
Option Explicit On

Imports System
Imports System.Windows.Forms
Imports System.Collections.Generic
Imports Nexamas.UI.Architecture
Imports Nexamas.UI.Composition
Imports Nexamas.UI.General
Imports Nexamas.UI.Layout
Imports Nexamas.UI.Rendering
Imports Nexamas.UI.TextRendering
Imports Nexamas.UI.Theming
Imports Nexamas.UI.Values
Imports SkiaSharp

Namespace Nexamas.UI.Controls

    Partial Public NotInheritable Class MASNotificationPanel

#Region "Row rendering"

        Private Structure NotificationRowSurfaceGeometry
            Friend Bounds As SKRect
            Friend Radius As Single
            Friend Dpi As Single
            Friend HasValue As Boolean
        End Structure

        Private Sub DrawRow(canvas As SKCanvas,
                            ctx As MASThemeContext,
                            row As SKRect,
                            dpi As Single,
                            item As NotificationInfo,
                            index As Integer)
            If item Is Nothing Then Return
            If row.Width <= 1.0F OrElse row.Height <= 1.0F Then Return

            Dim surface As NotificationRowSurfaceGeometry = ResolveNotificationRowSurfaceGeometry(row, dpi)
            If Not surface.HasValue Then Return

            Dim toneColor As SKColor = ResolveToneColor(ctx, item.Tone)
            Dim hovered As Boolean = (index = _hoverIndex)
            Dim pressed As Boolean = (index = _pressedIndex)
            DrawRowMaterial(canvas, ctx, surface, item.IsUnread, hovered, pressed)
            DrawIntegratedRowAccent(canvas, surface, toneColor)
            DrawRowFrame(canvas, surface, toneColor, item.IsUnread OrElse hovered)
            DrawRowMutationOverlay(canvas, surface, toneColor, ResolveNotificationPanelMutationAlphaBoost(index))

            Dim dotRadius As Single = If(item.IsUnread, NotificationPanelTokens.UnreadDotRadiusDip * surface.Dpi, NotificationPanelTokens.ReadDotRadiusDip * surface.Dpi)
            Using dot As New SKPaint With {.IsAntialias = True, .Style = SKPaintStyle.Fill, .Color = toneColor.WithAlpha(If(item.IsUnread, NotificationPanelTokens.UnreadDotAlpha, NotificationPanelTokens.ReadDotAlpha))}
                canvas.DrawCircle(surface.Bounds.Left + NotificationPanelTokens.RowDotCenterXDip * surface.Dpi, surface.Bounds.Top + NotificationPanelTokens.RowDotCenterYDip * surface.Dpi, dotRadius, dot)
            End Using

            Dim textLeft As Single = surface.Bounds.Left + NotificationPanelTokens.RowTextLeftDip * surface.Dpi
            Dim textRight As Single = surface.Bounds.Right - NotificationPanelTokens.RowPaddingRightDip * surface.Dpi
            If _showTimestamps AndAlso Not String.IsNullOrWhiteSpace(item.TimestampText) Then
                textRight -= NotificationPanelTokens.TimestampSlotWidthDip * surface.Dpi
            End If
            If Not String.IsNullOrWhiteSpace(item.ActionText) Then
                textRight -= NotificationPanelTokens.ActionSlotWidthDip * surface.Dpi
            End If

            Dim titleRect As New SKRect(textLeft,
                                        surface.Bounds.Top + NotificationPanelTokens.RowTitleTopDip * surface.Dpi,
                                        Math.Max(textLeft + 4.0F * surface.Dpi, textRight),
                                        surface.Bounds.Top + (NotificationPanelTokens.RowTitleTopDip + NotificationPanelTokens.RowTitleHeightDip) * surface.Dpi)
            Dim messageRect As New SKRect(textLeft,
                                          titleRect.Bottom + NotificationPanelTokens.RowMessageGapDip * surface.Dpi,
                                          Math.Max(textLeft + 4.0F * surface.Dpi, textRight),
                                          titleRect.Bottom + (NotificationPanelTokens.RowMessageGapDip + NotificationPanelTokens.RowMessageHeightDip) * surface.Dpi)

            Dim textIsRtl As Boolean = MASNotificationPanelContentPolicy.ResolveTextIsRtl(item.Title & " " & item.Message)
            Dim textAlign As TypographyTextPrimitives.TextAlign = If(textIsRtl, TypographyTextPrimitives.TextAlign.Right, TypographyTextPrimitives.TextAlign.Left)
            TypographyTextPrimitives.DrawSingleLineText(canvas, ctx, item.Title, titleRect, surface.Dpi, MASTypography.MASTextStyle.Small, ResolveTitleColor(ctx), textAlign, False)
            TypographyTextPrimitives.DrawSingleLineText(canvas, ctx, item.Message, messageRect, surface.Dpi, MASTypography.MASTextStyle.Body, ResolveMessageColor(ctx), textAlign, False)

            DrawRowMeta(canvas, ctx, surface.Bounds, surface.Dpi, item, toneColor)
            _hits.Add(New NotificationRowHit(index, surface.Bounds))
        End Sub

        Private Shared Function ResolveNotificationRowSurfaceGeometry(row As SKRect,
                                                                      dpi As Single) As NotificationRowSurfaceGeometry
            Dim geometry As New NotificationRowSurfaceGeometry With {
                .Bounds = SKRect.Empty,
                .Radius = 0.0F,
                .Dpi = PixelSnap.SafeDpi(dpi),
                .HasValue = False
            }

            If row.Width <= 1.0F OrElse row.Height <= 1.0F Then Return geometry

            Dim snapped As SKRect = PixelSnap.SnapRect(row, geometry.Dpi)
            If snapped.Width <= 1.0F OrElse snapped.Height <= 1.0F Then Return geometry

            geometry.Bounds = snapped
            geometry.Radius = PixelSnap.SafeRadius(
                Math.Min(NotificationPanelTokens.RowRadiusDip * geometry.Dpi, Math.Min(snapped.Width, snapped.Height) / 2.0F))
            geometry.HasValue = geometry.Radius > 0.0F
            Return geometry
        End Function

        Private Shared Sub DrawRowMaterial(canvas As SKCanvas,
                                           ctx As MASThemeContext,
                                           surface As NotificationRowSurfaceGeometry,
                                           unread As Boolean,
                                           hovered As Boolean,
                                           pressed As Boolean)
            If canvas Is Nothing Then Return
            If Not surface.HasValue Then Return

            Using rowPath As SKPath = BuildNotificationRowPath(surface.Bounds, surface.Radius)
                Using fill As New SKPaint With {
                    .IsAntialias = True,
                    .Style = SKPaintStyle.Fill,
                    .Color = ResolveRowFillColor(ctx, unread, hovered, pressed)
                }
                    canvas.DrawPath(rowPath, fill)
                End Using
            End Using
        End Sub

        Private Shared Sub DrawIntegratedRowAccent(canvas As SKCanvas,
                                                   surface As NotificationRowSurfaceGeometry,
                                                   toneColor As SKColor)
            If canvas Is Nothing Then Return
            If Not surface.HasValue Then Return

            Dim accentWidth As Single = Math.Min(NotificationPanelTokens.RowAccentPanelWidthDip * surface.Dpi, surface.Bounds.Width)
            If accentWidth <= 0.5F Then Return

            Dim accentRect As New SKRect(surface.Bounds.Left, surface.Bounds.Top, surface.Bounds.Left + accentWidth, surface.Bounds.Bottom)
            accentRect = PixelSnap.SnapRect(accentRect, surface.Dpi)
            If accentRect.Width <= 0.5F OrElse accentRect.Height <= 0.5F Then Return

            Dim save As Integer = canvas.Save()
            Try
                Using rowPath As SKPath = BuildNotificationRowPath(surface.Bounds, surface.Radius)
                    canvas.ClipPath(rowPath, SKClipOperation.Intersect, True)
                End Using

                Using shader As SKShader = SKShader.CreateLinearGradient(
                    New SKPoint(accentRect.Left, accentRect.MidY),
                    New SKPoint(accentRect.Right, accentRect.MidY),
                    New SKColor() {
                        ColorMath.Mix(toneColor, SKColors.White, 0.08F).WithAlpha(NotificationPanelTokens.RowAccentPanelFillAlpha),
                        toneColor.WithAlpha(NotificationPanelTokens.RowAccentPanelEndAlpha)
                    },
                    Nothing,
                    SKShaderTileMode.Clamp)

                    Using accentPaint As New SKPaint With {
                        .IsAntialias = True,
                        .Style = SKPaintStyle.Fill,
                        .Shader = shader
                    }
                        canvas.DrawRect(accentRect, accentPaint)
                    End Using
                End Using
            Finally
                canvas.RestoreToCount(save)
            End Try
        End Sub

        Private Shared Sub DrawRowFrame(canvas As SKCanvas,
                                        surface As NotificationRowSurfaceGeometry,
                                        toneColor As SKColor,
                                        unread As Boolean)
            If canvas Is Nothing Then Return
            If Not surface.HasValue Then Return

            Using rowPath As SKPath = BuildNotificationRowPath(surface.Bounds, surface.Radius)
                Using stroke As New SKPaint With {
                    .IsAntialias = True,
                    .Style = SKPaintStyle.Stroke,
                    .StrokeWidth = Math.Max(1.0F, surface.Dpi),
                    .Color = toneColor.WithAlpha(If(unread, NotificationPanelTokens.UnreadRowStrokeAlpha, NotificationPanelTokens.ReadRowStrokeAlpha))
                }
                    canvas.DrawPath(rowPath, stroke)
                End Using
            End Using
        End Sub


        Private Shared Sub DrawRowMutationOverlay(canvas As SKCanvas,
                                                  surface As NotificationRowSurfaceGeometry,
                                                  toneColor As SKColor,
                                                  alphaBoost As Integer)
            If canvas Is Nothing Then Return
            If Not surface.HasValue OrElse alphaBoost <= 0 Then Return

            Dim alpha As Byte = CByte(Math.Max(0, Math.Min(255, alphaBoost)))
            Using rowPath As SKPath = BuildNotificationRowPath(surface.Bounds, surface.Radius)
                Using stroke As New SKPaint With {
                    .IsAntialias = True,
                    .Style = SKPaintStyle.Stroke,
                    .StrokeWidth = Math.Max(1.0F, 1.4F * surface.Dpi),
                    .Color = toneColor.WithAlpha(alpha)
                }
                    canvas.DrawPath(rowPath, stroke)
                End Using
            End Using
        End Sub

        Private Shared Function BuildNotificationRowPath(bounds As SKRect,
                                                         radius As Single) As SKPath
            Dim path As New SKPath()
            path.AddRoundRect(bounds, radius, radius)
            Return path
        End Function

        Private Sub DrawRowMeta(canvas As SKCanvas,
                                ctx As MASThemeContext,
                                row As SKRect,
                                dpi As Single,
                                item As NotificationInfo,
                                toneColor As SKColor)
            Dim right As Single = row.Right - NotificationPanelTokens.RowPaddingRightDip * dpi
            If _showTimestamps AndAlso Not String.IsNullOrWhiteSpace(item.TimestampText) Then
                Dim timestamp As New SKRect(Math.Max(row.Left, right - NotificationPanelTokens.TimestampSlotWidthDip * dpi),
                                            row.Top + NotificationPanelTokens.RowTimestampTopDip * dpi,
                                            right,
                                            row.Top + (NotificationPanelTokens.RowTimestampTopDip + NotificationPanelTokens.TimestampHeightDip) * dpi)
                TypographyTextPrimitives.DrawSingleLineText(canvas, ctx, item.TimestampText, timestamp, dpi, MASTypography.MASTextStyle.Micro, ResolveMutedColor(ctx), TypographyTextPrimitives.TextAlign.Right, False)
                right = timestamp.Left - NotificationPanelTokens.MetaGapDip * dpi
            End If

            If Not String.IsNullOrWhiteSpace(item.ActionText) Then
                Dim action As New SKRect(Math.Max(row.Left, right - NotificationPanelTokens.ActionSlotWidthDip * dpi),
                                         row.Bottom - (NotificationPanelTokens.ActionBottomDip + NotificationPanelTokens.ActionHeightDip) * dpi,
                                         right,
                                         row.Bottom - NotificationPanelTokens.ActionBottomDip * dpi)
                Dim radius As Single = Math.Max(2.0F, action.Height / 2.0F)
                Using fill As New SKPaint With {.IsAntialias = True, .Style = SKPaintStyle.Fill, .Color = toneColor.WithAlpha(NotificationPanelTokens.ActionFillAlpha)}
                    canvas.DrawRoundRect(action, radius, radius, fill)
                End Using
                TypographyTextPrimitives.DrawSingleLineText(canvas, ctx, item.ActionText, action, dpi, MASTypography.MASTextStyle.Micro, ResolveTitleColor(ctx), TypographyTextPrimitives.TextAlign.Center, False)
            End If
        End Sub

#End Region
    End Class

End Namespace
