Option Strict On
Option Explicit On

Imports System
Imports SkiaSharp

Namespace Nexamas.UI.General

    Friend NotInheritable Class PixelSnap
        Private Sub New()
        End Sub

#Region "DPI normalization"

        Friend Shared Function SafeDpi(dpi As Single) As Single
            If Single.IsNaN(dpi) OrElse Single.IsInfinity(dpi) OrElse dpi <= 0.0F Then Return 1.0F
            If dpi < 1.0F Then Return 1.0F
            Return dpi
        End Function

        Friend Shared Function NormalizeDpiScale(dpi As Single) As Single
            Return CSng(Math.Round(SafeDpi(dpi), 2))
        End Function

        Friend Shared Function ToPx(dip As Single, dpi As Single) As Single
            If Single.IsNaN(dip) OrElse Single.IsInfinity(dip) Then Return 0.0F
            Return dip * SafeDpi(dpi)
        End Function

#End Region

#Region "Clamp helpers"

        Friend Shared Function Clamp01(value As Single) As Single
            If Single.IsNaN(value) OrElse Single.IsInfinity(value) Then Return 0.0F
            If value < 0.0F Then Return 0.0F
            If value > 1.0F Then Return 1.0F
            Return value
        End Function

        Friend Shared Function ClampByte(value As Integer) As Byte
            If value <= 0 Then Return CByte(0)
            If value >= 255 Then Return CByte(255)
            Return CByte(value)
        End Function

#End Region

#Region "Snap primitives"

        Friend Shared Function Snap(v As Single, grid As Single) As Single
            Dim g As Single = grid
            If g <= 0.0F OrElse Single.IsNaN(g) OrElse Single.IsInfinity(g) Then g = 0.5F
            Return CSng(Math.Round(v / g) * g)
        End Function

        Friend Shared Function SnapHalf(v As Single) As Single
            Return Snap(v, 0.5F)
        End Function

        Friend Shared Function Snap1(v As Single) As Single
            Return Snap(v, 1.0F)
        End Function

#End Region

#Region "Rect snapping"

        Friend Shared Function SnapRect(r As SKRect, grid As Single) As SKRect
            Dim g As Single = grid
            If g <= 0.0F OrElse Single.IsNaN(g) OrElse Single.IsInfinity(g) Then g = 0.5F

            Return New SKRect(
                Snap(r.Left, g),
                Snap(r.Top, g),
                Snap(r.Right, g),
                Snap(r.Bottom, g)
            )
        End Function

        Friend Shared Function SnapRectHalf(r As SKRect) As SKRect
            Return SnapRect(r, 0.5F)
        End Function

        Friend Shared Function SnapRect1(r As SKRect) As SKRect
            Return SnapRect(r, 1.0F)
        End Function

        Friend Shared Function InsetAll(r As SKRect, inset As Single) As SKRect
            If inset <= 0.0F OrElse Single.IsNaN(inset) OrElse Single.IsInfinity(inset) Then Return r
            Dim rr As SKRect = r
            rr.Inflate(-inset, -inset)
            Return rr
        End Function

        Friend Shared Function SquareInside(bounds As SKRect, sizePx As Single) As SKRect
            If Not HasArea(bounds) Then Return SKRect.Empty
            If Single.IsNaN(sizePx) OrElse Single.IsInfinity(sizePx) OrElse sizePx <= 1.0F Then Return SKRect.Empty

            Dim s As Single = Math.Min(sizePx, Math.Min(bounds.Width, bounds.Height))
            If s <= 1.0F Then Return SKRect.Empty

            Return New SKRect(
                bounds.MidX - (s * 0.5F),
                bounds.MidY - (s * 0.5F),
                bounds.MidX + (s * 0.5F),
                bounds.MidY + (s * 0.5F)
            )
        End Function

#End Region

#Region "Stroke math"

        Friend Shared Function PathInset(edgeInset As Single, strokeW As Single) As Single
            Dim w As Single = strokeW
            If Single.IsNaN(w) OrElse Single.IsInfinity(w) OrElse w < 0.0F Then w = 0.0F

            Dim i As Single = edgeInset
            If Single.IsNaN(i) OrElse Single.IsInfinity(i) OrElse i < 0.0F Then i = 0.0F

            Return i + (w / 2.0F)
        End Function

#End Region

#Region "Radius math"

        Friend Shared Function SafeRadius(r As Single) As Single
            If Single.IsNaN(r) OrElse Single.IsInfinity(r) OrElse r < 0.0F Then Return 0.0F
            Return r
        End Function

        Friend Shared Function RadiusMinus(rad As Single, inset As Single) As Single
            Dim r As Single = SafeRadius(rad)

            Dim i As Single = inset
            If Single.IsNaN(i) OrElse Single.IsInfinity(i) OrElse i < 0.0F Then i = 0.0F

            Return Math.Max(0.0F, r - i)
        End Function

#End Region

#Region "Guards"

        Friend Shared Function HasArea(r As SKRect, Optional eps As Single = 1.0F) As Boolean
            Return r.Width > eps AndAlso r.Height > eps
        End Function

#End Region

    End Class

End Namespace