Option Strict On
Option Explicit On

Imports System
Imports System.Drawing
Imports System.Windows.Forms
Imports Nexamas.UI.Layout

Namespace Nexamas.UI.Application

    ''' <summary>
    ''' Internal planner for host-window presentation bounds.
    ''' It is not a layout engine: it only plans Form bounds/state from profile, screen work area, and validated user state.
    ''' </summary>
    Friend NotInheritable Class MASWindowPresentationPlanner

        Private Sub New()
        End Sub

        Friend Shared Function Plan(owner As Form,
                                    profile As MASWindowPresentationProfile,
                                    options As MASWindowPresentationOptions,
                                    storedState As MASWindowPresentationState) As MASWindowPresentationState
            If owner Is Nothing Then Throw New ArgumentNullException(NameOf(owner))

            Dim safeProfile As MASWindowPresentationProfile = MASWindowPresentationProfile.Normalize(profile)
            Dim safeOptions As MASWindowPresentationOptions = MASWindowPresentationOptions.Normalize(options, safeProfile)
            Dim result As New MASWindowPresentationState() With {
                .ProfileKind = safeProfile.Kind,
                .WindowState = FormWindowState.Normal
            }

            If safeOptions.StartupPlacement = MASWindowStartupPlacement.PreserveCurrent Then
                result.Bounds = SanitizeAgainstWorkArea(owner.Bounds, ResolveWorkingArea(owner), safeProfile)
                result.WindowState = owner.WindowState
                Return result
            End If

            If safeOptions.StartupPlacement = MASWindowStartupPlacement.Maximized Then
                result.Bounds = PlanProfileBounds(owner, safeProfile)
                result.WindowState = FormWindowState.Maximized
                Return result
            End If

            If safeOptions.RestoreUserChoice AndAlso storedState IsNot Nothing Then
                Dim restored As MASWindowPresentationState = TryRestoreStoredState(owner, safeProfile, storedState)
                If restored IsNot Nothing Then Return restored
            End If

            result.Bounds = PlanProfileBounds(owner, safeProfile)
            If safeOptions.StartupPlacement = MASWindowStartupPlacement.Maximized Then
                result.WindowState = FormWindowState.Maximized
            End If

            Return result
        End Function

        Friend Shared Function Capture(owner As Form,
                                       profile As MASWindowPresentationProfile) As MASWindowPresentationState
            If owner Is Nothing Then Return Nothing

            Dim safeProfile As MASWindowPresentationProfile = MASWindowPresentationProfile.Normalize(profile)
            Dim state As New MASWindowPresentationState() With {
                .ProfileKind = safeProfile.Kind,
                .WindowState = owner.WindowState
            }

            If owner.WindowState = FormWindowState.Normal Then
                state.Bounds = SanitizeAgainstWorkArea(owner.Bounds, ResolveWorkingArea(owner), safeProfile)
            Else
                state.Bounds = SanitizeAgainstWorkArea(owner.RestoreBounds, ResolveWorkingArea(owner), safeProfile)
            End If

            Return state
        End Function

        Private Shared Function TryRestoreStoredState(owner As Form,
                                                      profile As MASWindowPresentationProfile,
                                                      storedState As MASWindowPresentationState) As MASWindowPresentationState
            If storedState Is Nothing Then Return Nothing

            Dim safeProfile As MASWindowPresentationProfile = MASWindowPresentationProfile.Normalize(profile)
            Dim candidateBounds As Rectangle = storedState.Bounds

            If storedState.WindowState = FormWindowState.Maximized AndAlso storedState.HasBounds Then
                Dim restoredMaximized As New MASWindowPresentationState() With {
                    .ProfileKind = safeProfile.Kind,
                    .WindowState = FormWindowState.Maximized,
                    .Bounds = SanitizeAgainstWorkArea(candidateBounds, ResolveWorkingAreaForBounds(owner, candidateBounds), safeProfile)
                }
                Return restoredMaximized
            End If

            If Not storedState.HasBounds Then Return Nothing
            If Not IntersectsAnyWorkingArea(candidateBounds) Then Return Nothing

            Dim workArea As Rectangle = ResolveWorkingAreaForBounds(owner, candidateBounds)
            Dim safeBounds As Rectangle = SanitizeAgainstWorkArea(candidateBounds, workArea, safeProfile)

            Return New MASWindowPresentationState() With {
                .ProfileKind = safeProfile.Kind,
                .WindowState = FormWindowState.Normal,
                .Bounds = safeBounds
            }
        End Function

        Private Shared Function PlanProfileBounds(owner As Form,
                                                  profile As MASWindowPresentationProfile) As Rectangle
            Dim safeProfile As MASWindowPresentationProfile = MASWindowPresentationProfile.Normalize(profile)
            Dim workArea As Rectangle = ResolveWorkingArea(owner)
            Dim available As Rectangle = ApplyGuardInset(workArea)

            Dim width As Integer = CInt(Math.Round(available.Width * safeProfile.WidthRatio))
            Dim height As Integer = CInt(Math.Round(available.Height * safeProfile.HeightRatio))

            width = Math.Max(safeProfile.MinimumWidth, width)
            height = Math.Max(safeProfile.MinimumHeight, height)
            width = Math.Min(available.Width, width)
            height = Math.Min(available.Height, height)
            width = Math.Max(MASWindowPresentationSizeTokens.MinimumVisibleWidth, width)
            height = Math.Max(MASWindowPresentationSizeTokens.MinimumVisibleHeight, height)

            Dim x As Integer = available.Left + ((available.Width - width) \ 2)
            Dim y As Integer = available.Top + ((available.Height - height) \ 2)

            Return SanitizeAgainstWorkArea(New Rectangle(x, y, width, height), workArea, safeProfile)
        End Function

        Private Shared Function SanitizeAgainstWorkArea(bounds As Rectangle,
                                                        workArea As Rectangle,
                                                        profile As MASWindowPresentationProfile) As Rectangle
            Dim safeProfile As MASWindowPresentationProfile = MASWindowPresentationProfile.Normalize(profile)
            Dim safeWorkArea As Rectangle = If(workArea.Width > 0 AndAlso workArea.Height > 0, workArea, Screen.PrimaryScreen.WorkingArea)
            Dim available As Rectangle = ApplyGuardInset(safeWorkArea)

            Dim width As Integer = bounds.Width
            Dim height As Integer = bounds.Height

            If width <= 0 Then width = Math.Min(available.Width, safeProfile.MinimumWidth)
            If height <= 0 Then height = Math.Min(available.Height, safeProfile.MinimumHeight)

            width = Math.Max(MASWindowPresentationSizeTokens.MinimumVisibleWidth, width)
            height = Math.Max(MASWindowPresentationSizeTokens.MinimumVisibleHeight, height)
            width = Math.Min(available.Width, width)
            height = Math.Min(available.Height, height)

            Dim x As Integer = bounds.X
            Dim y As Integer = bounds.Y

            If x < available.Left Then x = available.Left
            If y < available.Top Then y = available.Top
            If x + width > available.Right Then x = available.Right - width
            If y + height > available.Bottom Then y = available.Bottom - height

            If x < available.Left Then x = available.Left
            If y < available.Top Then y = available.Top

            Return New Rectangle(x, y, width, height)
        End Function

        Private Shared Function ResolveWorkingArea(owner As Form) As Rectangle
            Try
                If owner IsNot Nothing Then Return Screen.FromControl(owner).WorkingArea
            Catch masCaughtException1 As Exception
                Nexamas.UI.Diagnostics.MASExceptionSilencer.SwallowBoundary(masCaughtException1, "WindowPresentation.ResolveWorkingArea")
            End Try

            Return Screen.PrimaryScreen.WorkingArea
        End Function

        Private Shared Function ResolveWorkingAreaForBounds(owner As Form,
                                                           bounds As Rectangle) As Rectangle
            Try
                If bounds.Width > 0 AndAlso bounds.Height > 0 Then
                    Return Screen.FromRectangle(bounds).WorkingArea
                End If
            Catch masCaughtException2 As Exception
                Nexamas.UI.Diagnostics.MASExceptionSilencer.SwallowBoundary(masCaughtException2, "WindowPresentation.ResolveWorkingAreaForBounds")
            End Try

            Return ResolveWorkingArea(owner)
        End Function

        Private Shared Function ApplyGuardInset(workArea As Rectangle) As Rectangle
            Dim inset As Integer = MASWindowPresentationSizeTokens.WorkAreaGuardInset
            If workArea.Width <= inset * 2 OrElse workArea.Height <= inset * 2 Then Return workArea
            Return New Rectangle(workArea.Left + inset,
                                 workArea.Top + inset,
                                 workArea.Width - (inset * 2),
                                 workArea.Height - (inset * 2))
        End Function

        Private Shared Function IntersectsAnyWorkingArea(bounds As Rectangle) As Boolean
            If bounds.Width <= 0 OrElse bounds.Height <= 0 Then Return False

            Try
                For Each screen As Screen In Screen.AllScreens
                    If screen Is Nothing Then Continue For
                    If screen.WorkingArea.IntersectsWith(bounds) Then Return True
                Next
            Catch masCaughtException3 As Exception
                Nexamas.UI.Diagnostics.MASExceptionSilencer.SwallowBoundary(masCaughtException3, "WindowPresentation.IntersectsAnyWorkingArea")
                Return True
            End Try

            Return False
        End Function

    End Class

End Namespace
