Option Strict On
Option Explicit On

Imports System
Imports System.Drawing
Imports System.Windows.Forms
Imports Nexamas.UI.Components
Imports Nexamas.UI.Components.DropDownMenu
Imports Nexamas.UI.Layout
Imports SkiaSharp

Namespace Nexamas.UI.Application

    ''' <summary>
    ''' MAS-owned borderless settings window session. It uses MASApplicationWindow, MAS TopBar, MAS chrome,
    ''' and MAS Application settings page content; no consumer WinForms title bar is required.
    ''' </summary>
    Friend NotInheritable Class MASApplicationSettingsWindowSession
        Implements IDisposable


        Private ReadOnly _ownerWindow As MASApplicationWindow
        Private ReadOnly _pageOptions As MASApplicationSettingsPageOptions
        Private ReadOnly _form As Form
        Private ReadOnly _application As MASApplication
        Private ReadOnly _window As MASApplicationWindow
        Private ReadOnly _topBar As MASTopBarComponent

        Private _closeReason As MASApplicationSettingsCloseReason = MASApplicationSettingsCloseReason.None
        Private _disposed As Boolean

        Friend Sub New(ownerWindow As MASApplicationWindow,
                       pageOptions As MASApplicationSettingsPageOptions)
            If ownerWindow Is Nothing Then Throw New ArgumentNullException(NameOf(ownerWindow))
            If ownerWindow.IsDisposed Then Throw New ArgumentException("The owning MASApplicationWindow is disposed.", NameOf(ownerWindow))

            _ownerWindow = ownerWindow
            _pageOptions = PreparePageOptions(pageOptions)
            _pageOptions.CloseRequested = AddressOf RequestCloseFromPage
            _form = CreateOwnedForm(ownerWindow, _pageOptions)
            _application = MASApplication.Create()
            _window = _application.CreateWindow(_form, DockStyle.Fill, autoHookPointerInput:=True, setupImmediately:=True)

            _window.Chrome.UseSoftShadow(MASWindowChromeOptions.SoftRounded())

            _topBar = CreateTopBar(_form.Text)
            _window.Shell.AttachTopBar(_topBar, AddressOf ResolveTopBarLogicalRect, attachWindow:=True)
            AddHandler _window.Rendering.BeforeControls, AddressOf DrawTopBar
            AddHandler _form.FormClosed, AddressOf SettingsForm_FormClosed

            Dim result As MASApplicationSettingsPageResult = _window.Settings.ShowApplicationSettingsPage(_pageOptions)
            If result Is Nothing OrElse Not result.Succeeded Then
                Throw New InvalidOperationException("MAS Application settings window could not build its settings page.")
            End If
        End Sub

        Friend ReadOnly Property IsDisposed As Boolean
            Get
                Return _disposed
            End Get
        End Property

        Friend ReadOnly Property CloseReason As MASApplicationSettingsCloseReason
            Get
                Return _closeReason
            End Get
        End Property

        Friend ReadOnly Property Window As MASApplicationWindow
            Get
                Return _window
            End Get
        End Property

        Friend Sub Show()
            ThrowIfDisposed()
            Dim owner As Form = Nothing
            Try
                owner = _ownerWindow.OwnerForm
            Catch masCaughtException1 As Exception
                Nexamas.UI.Diagnostics.MASExceptionSilencer.SwallowBoundary(masCaughtException1, "ApplicationSettingsWindowSession.Show.ResolveOwner")
                owner = Nothing
            End Try

            If owner IsNot Nothing AndAlso Not owner.IsDisposed Then
                _form.Show(owner)
            Else
                _form.Show()
            End If
        End Sub


        ''' <summary>
        ''' Brings the owned MAS settings window to the front without creating a consumer WinForms settings form.
        ''' </summary>
        Friend Sub Activate()
            ThrowIfDisposed()

            If _form.WindowState = FormWindowState.Minimized Then
                _form.WindowState = FormWindowState.Normal
            End If

            _form.Activate()
            _form.BringToFront()
        End Sub

        Friend Sub Close()
            If _disposed Then Return
            _form.Close()
        End Sub

        Private Shared Function PreparePageOptions(options As MASApplicationSettingsPageOptions) As MASApplicationSettingsPageOptions
            Dim effectiveOptions As MASApplicationSettingsPageOptions = MASApplicationSettingsPageOptions.Normalize(options)
            effectiveOptions.ClearExistingControls = True
            effectiveOptions.IncludeSizingSection = True
            effectiveOptions.IncludeActionButtons = True
            effectiveOptions.CloseAfterApply = True
            effectiveOptions.CloseAfterCancel = True
            effectiveOptions.ChromeTopInsetLogical = MASApplicationSurfaceSizeTokens.SettingsWindowTopBarHeightDip
            Return effectiveOptions
        End Function

        Private Shared Function CreateOwnedForm(ownerWindow As MASApplicationWindow,
                                                options As MASApplicationSettingsPageOptions) As Form
            Dim owner As Form = Nothing
            Try
                owner = ownerWindow.OwnerForm
            Catch masCaughtException2 As Exception
                Nexamas.UI.Diagnostics.MASExceptionSilencer.SwallowBoundary(masCaughtException2, "ApplicationSettingsWindowSession.CreateOwnedForm.ResolveOwner")
                owner = Nothing
            End Try

            Dim title As String = If(options.Title, String.Empty).Trim()
            If title.Length = 0 Then title = "Application settings"

            Dim form As New Form() With {
                .Text = title,
                .FormBorderStyle = FormBorderStyle.None,
                .StartPosition = FormStartPosition.Manual,
                .Size = New Size(CInt(MASApplicationSurfaceSizeTokens.SettingsWindowPreferredWidthDip), CInt(MASApplicationSurfaceSizeTokens.SettingsWindowPreferredHeightDip)),
                .MinimumSize = New Size(CInt(MASApplicationSurfaceSizeTokens.SettingsWindowMinWidthDip), CInt(MASApplicationSurfaceSizeTokens.SettingsWindowMinHeightDip)),
                .ShowIcon = False,
                .ShowInTaskbar = False,
                .ControlBox = False,
                .MaximizeBox = False,
                .MinimizeBox = False,
                .KeyPreview = True
            }

            If owner IsNot Nothing AndAlso Not owner.IsDisposed Then
                form.Location = New Point(
                    owner.Left + Math.Max(0, (owner.Width - form.Width) \ 2),
                    owner.Top + Math.Max(0, (owner.Height - form.Height) \ 2))
            Else
                Dim working As Rectangle = Screen.PrimaryScreen.WorkingArea
                form.Location = New Point(
                    working.Left + Math.Max(0, (working.Width - form.Width) \ 2),
                    working.Top + Math.Max(0, (working.Height - form.Height) \ 2))
            End If

            AddHandler form.KeyDown,
                Sub(sender As Object, e As KeyEventArgs)
                    If e IsNot Nothing AndAlso e.KeyCode = Keys.Escape Then
                        e.Handled = True
                        form.Close()
                    End If
                End Sub

            Return form
        End Function

        Private Function CreateTopBar(title As String) As MASTopBarComponent
            Return MASTopBarComponent.Default().
                WithTitle(title).
                WithoutLogo().
                WithMenu(Sub(menu As MASDropDownMenuBuilder)
                             menu.AddAction("Close", "window.close")
                         End Sub)
        End Function

        Private Function ResolveTopBarLogicalRect() As SKRect
            Dim dpi As Single = ResolveDpi()
            Dim widthDip As Single = 0.0F

            If _window IsNot Nothing AndAlso Not _window.IsDisposed Then
                widthDip = _window.SurfaceSizePx.Width / dpi
            End If

            If widthDip <= 0.0F Then widthDip = CSng(Math.Max(1, _form.ClientSize.Width))
            Return New SKRect(0.0F, 0.0F, widthDip, MASApplicationSurfaceSizeTokens.SettingsWindowTopBarHeightDip)
        End Function

        Private Sub DrawTopBar(renderContext As MASApplicationRenderContext)
            If _disposed Then Return
            If renderContext Is Nothing Then Return
            If _topBar Is Nothing Then Return

            Dim dpi As Single = renderContext.Dpi
            If dpi <= 0.0F OrElse Single.IsNaN(dpi) OrElse Single.IsInfinity(dpi) Then dpi = 1.0F

            Dim boundsPx As New SKRect(
                0.0F,
                0.0F,
                CSng(Math.Max(1, renderContext.WidthPx)),
                MASApplicationSurfaceSizeTokens.SettingsWindowTopBarHeightDip * dpi)

            _window.Rendering.DrawTopBar(renderContext, _topBar, boundsPx, CSng(Nexamas.UI.Timing.MASVisualClock.Timestamp() / CDbl(Nexamas.UI.Timing.MASTimeProvider.TimestampFrequency)))
        End Sub

        Private Function ResolveDpi() As Single
            Dim dpi As Single = 1.0F
            Try
                If _window IsNot Nothing AndAlso Not _window.IsDisposed Then
                    dpi = _window.Dpi
                End If
            Catch masCaughtException3 As Exception
                Nexamas.UI.Diagnostics.MASExceptionSilencer.SwallowBoundary(masCaughtException3, "ApplicationSettingsWindowSession.ResolveDpi")
                dpi = 1.0F
            End Try

            If dpi <= 0.0F OrElse Single.IsNaN(dpi) OrElse Single.IsInfinity(dpi) Then dpi = 1.0F
            Return dpi
        End Function

        Friend Sub RequestCloseFromPage(reason As MASApplicationSettingsCloseReason)
            If _disposed Then Return
            _closeReason = reason
            _form.Close()
        End Sub

        Private Sub SettingsForm_FormClosed(sender As Object, e As FormClosedEventArgs)
            Dispose()
        End Sub

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

            Try
                RemoveHandler _form.FormClosed, AddressOf SettingsForm_FormClosed
            Catch masCaughtException1 As Exception
                Nexamas.UI.Diagnostics.MASExceptionSilencer.SwallowDisposeCleanup(masCaughtException1)
            End Try

            Try
                If _window IsNot Nothing Then
                    RemoveHandler _window.Rendering.BeforeControls, AddressOf DrawTopBar
                End If
            Catch masCaughtException2 As Exception
                Nexamas.UI.Diagnostics.MASExceptionSilencer.SwallowDisposeCleanup(masCaughtException2)
            End Try

            Try
                If _topBar IsNot Nothing Then _topBar.Dispose()
            Catch masCaughtException3 As Exception
                Nexamas.UI.Diagnostics.MASExceptionSilencer.SwallowDisposeCleanup(masCaughtException3)
            End Try

            Try
                If _application IsNot Nothing Then _application.Dispose()
            Catch masCaughtException4 As Exception
                Nexamas.UI.Diagnostics.MASExceptionSilencer.SwallowDisposeCleanup(masCaughtException4)
            End Try

            Try
                If _form IsNot Nothing AndAlso Not _form.IsDisposed Then _form.Dispose()
            Catch masCaughtException5 As Exception
                Nexamas.UI.Diagnostics.MASExceptionSilencer.SwallowDisposeCleanup(masCaughtException5)
            End Try
        End Sub

        Private Sub ThrowIfDisposed()
            If _disposed Then Throw New ObjectDisposedException(NameOf(MASApplicationSettingsWindowSession))
        End Sub

    End Class

End Namespace
