Option Strict On
Option Explicit On

Imports System
Imports System.Collections.Generic
Imports Nexamas.UI.Controls
Imports Nexamas.UI.Host
Imports Nexamas.UI.Layout
Imports Nexamas.UI.Rendering
Imports Nexamas.UI.Theming
Imports Nexamas.UI.Components
Imports SkiaSharp

Namespace Nexamas.UI.Application

    Partial Public NotInheritable Class MASApplicationSurfaceLayoutBuilder
        Friend Sub CollectControls(target As IList(Of MASControlBase))
            If target Is Nothing Then Return
            CollectPageControls(_commandBarPage, target)
            CollectPageControls(_sidebarPage, target)
            CollectPageControls(_sidebarFooterPage, target)
            CollectPageControls(_contentPage, target)
            CollectPageControls(_inspectorPage, target)
            CollectPageControls(_footerPage, target)
        End Sub

        Friend Function BuildRegionEntries(viewportLogical As SKRect,
                                           sizeProfile As MASSizeProfile) As IReadOnlyList(Of MASApplicationLayoutRegionEntry)
            Return BuildLayoutModel(viewportLogical, sizeProfile).RegionEntries
        End Function

        Friend Function BuildLayoutModel(viewportLogical As SKRect,
                                         sizeProfile As MASSizeProfile) As MASApplicationSurfaceLayoutModel
            Dim entries As New List(Of MASApplicationLayoutRegionEntry)()
            Dim backgroundEntries As New List(Of MASApplicationSurfaceBackgroundEntry)()

            If viewportLogical.IsEmpty OrElse viewportLogical.Width <= 1.0F OrElse viewportLogical.Height <= 1.0F Then
                Return New MASApplicationSurfaceLayoutModel(entries, backgroundEntries)
            End If

            Dim profile As MASSizeProfile = MASSizeProfile.Normalize(sizeProfile)
            Dim commandBodyGap As Single = ResolveCommandBodyGap(profile)
            Dim bodyFooterGap As Single = ResolveBodyFooterGap(profile)
            Dim columnGap As Single = ResolveColumnGap(profile)
            Dim sidebarFooterGap As Single = ResolveSidebarFooterGap(profile)
            Dim contentViewport As SKRect = ResolveApplicationSurfaceContentViewport(viewportLogical)
            Dim outerBounds As SKRect = MASLayoutInset.ApplyInside(contentViewport, ResolveOuterPadding(profile))
            If outerBounds.IsEmpty OrElse outerBounds.Width <= 1.0F OrElse outerBounds.Height <= 1.0F Then
                Return New MASApplicationSurfaceLayoutModel(entries, backgroundEntries)
            End If

            Dim workBounds As SKRect = outerBounds
            Dim commandInMainArea As Boolean = ShouldPlaceCommandBarInMainArea()
            Dim footerInMainArea As Boolean = ShouldPlaceFooterInMainArea()

            If _commandBarPage IsNot Nothing AndAlso Not commandInMainArea Then
                Dim commandHeight As Single = Math.Min(ResolveCommandBarHeight(_commandBarSize, profile), workBounds.Height)
                If commandHeight > 0.0F Then
                    Dim commandBounds As New SKRect(workBounds.Left, workBounds.Top, workBounds.Right, workBounds.Top + commandHeight)
                    AddCommandRegion(entries, backgroundEntries, commandBounds, profile)
                    workBounds = MoveTop(workBounds, commandHeight + commandBodyGap)
                End If
            End If

            If _footerPage IsNot Nothing AndAlso Not footerInMainArea AndAlso workBounds.Height > 0.0F Then
                Dim footerHeight As Single = Math.Min(ResolveFooterHeight(_footerSize, profile), workBounds.Height)
                If footerHeight > 0.0F Then
                    Dim footerBounds As New SKRect(workBounds.Left, workBounds.Bottom - footerHeight, workBounds.Right, workBounds.Bottom)
                    AddSurfaceRegion(entries, backgroundEntries, footerBounds, _footerPage, profile)
                    workBounds = MoveBottom(workBounds, footerHeight + bodyFooterGap)
                End If
            End If

            If workBounds.IsEmpty OrElse workBounds.Width <= 1.0F OrElse workBounds.Height <= 1.0F Then
                Return New MASApplicationSurfaceLayoutModel(entries, backgroundEntries)
            End If

            AddBodyRegions(entries, backgroundEntries, workBounds, profile, commandInMainArea, commandBodyGap, bodyFooterGap, columnGap, sidebarFooterGap, footerInMainArea)
            Return New MASApplicationSurfaceLayoutModel(entries, backgroundEntries)
        End Function

        Private Sub AddBodyRegions(entries As IList(Of MASApplicationLayoutRegionEntry),
                                   backgroundEntries As IList(Of MASApplicationSurfaceBackgroundEntry),
                                   bodyBounds As SKRect,
                                   profile As MASSizeProfile,
                                   commandInMainArea As Boolean,
                                   commandBodyGap As Single,
                                   bodyFooterGap As Single,
                                   columnGap As Single,
                                   sidebarFooterGap As Single,
                                   footerInMainArea As Boolean)
            Dim hasSidebar As Boolean = (_sidebarPage IsNot Nothing OrElse _sidebarFooterPage IsNot Nothing)
            Dim hasContent As Boolean = (_contentPage IsNot Nothing)
            Dim hasInspector As Boolean = (_inspectorPage IsNot Nothing)
            Dim activeColumns As Integer = If(hasSidebar, 1, 0) + If(hasContent, 1, 0) + If(hasInspector, 1, 0)

            If activeColumns <= 0 Then
                If commandInMainArea AndAlso _commandBarPage IsNot Nothing Then
                    AddCommandRegion(entries, backgroundEntries, bodyBounds, profile)
                End If

                Return
            End If

            Dim columnGapTotal As Single = columnGap * CSng(Math.Max(0, activeColumns - 1))
            Dim minContentWidth As Single = If(hasContent, _minimumContentWidth * profile.WidthScale, 0.0F)
            Dim desiredSidebarWidth As Single = If(hasSidebar, ResolveSidebarWidth(_sidebarSize, profile), 0.0F)
            Dim desiredInspectorWidth As Single = If(hasInspector, ResolveSidebarWidth(_inspectorSize, profile), 0.0F)
            Dim availablePanelWidth As Single = Math.Max(0.0F, bodyBounds.Width - columnGapTotal - minContentWidth)
            Dim desiredPanelWidth As Single = desiredSidebarWidth + desiredInspectorWidth

            Dim panelScale As Single = 1.0F
            If desiredPanelWidth > availablePanelWidth AndAlso desiredPanelWidth > 0.0F Then
                panelScale = Math.Max(0.0F, availablePanelWidth / desiredPanelWidth)
            End If

            Dim sidebarWidth As Single = desiredSidebarWidth * panelScale
            Dim inspectorWidth As Single = desiredInspectorWidth * panelScale
            Dim cursorLeft As Single = bodyBounds.Left

            If hasSidebar AndAlso sidebarWidth > 0.0F Then
                Dim sidebarRect As New SKRect(cursorLeft, bodyBounds.Top, cursorLeft + sidebarWidth, bodyBounds.Bottom)
                AddSidebarRegions(entries, backgroundEntries, sidebarRect, profile, sidebarFooterGap)
                cursorLeft = sidebarRect.Right + columnGap
            End If

            Dim mainBounds As New SKRect(cursorLeft, bodyBounds.Top, bodyBounds.Right, bodyBounds.Bottom)
            If mainBounds.IsEmpty OrElse mainBounds.Width <= 0.0F OrElse mainBounds.Height <= 0.0F Then Return

            Dim mainContentBounds As SKRect = mainBounds
            If commandInMainArea AndAlso _commandBarPage IsNot Nothing Then
                Dim commandHeight As Single = Math.Min(ResolveCommandBarHeight(_commandBarSize, profile), mainBounds.Height)
                If commandHeight > 0.0F Then
                    Dim commandBounds As New SKRect(mainBounds.Left, mainBounds.Top, mainBounds.Right, mainBounds.Top + commandHeight)
                    AddCommandRegion(entries, backgroundEntries, commandBounds, profile)
                    mainContentBounds = MoveTop(mainBounds, commandHeight + commandBodyGap)
                End If
            End If

            If mainContentBounds.IsEmpty OrElse mainContentBounds.Width <= 0.0F OrElse mainContentBounds.Height <= 0.0F Then Return

            If footerInMainArea AndAlso _footerPage IsNot Nothing AndAlso mainContentBounds.Height > 0.0F Then
                Dim footerHeight As Single = Math.Min(ResolveFooterHeight(_footerSize, profile), mainContentBounds.Height)
                If footerHeight > 0.0F Then
                    Dim footerBounds As New SKRect(mainContentBounds.Left, mainContentBounds.Bottom - footerHeight, mainContentBounds.Right, mainContentBounds.Bottom)
                    AddSurfaceRegion(entries, backgroundEntries, footerBounds, _footerPage, profile)
                    mainContentBounds = MoveBottom(mainContentBounds, footerHeight + bodyFooterGap)
                End If
            End If

            If mainContentBounds.IsEmpty OrElse mainContentBounds.Width <= 0.0F OrElse mainContentBounds.Height <= 0.0F Then Return

            Dim inspectorRect As SKRect = SKRect.Empty
            If hasInspector AndAlso inspectorWidth > 0.0F Then
                inspectorRect = New SKRect(mainContentBounds.Right - inspectorWidth, mainContentBounds.Top, mainContentBounds.Right, mainContentBounds.Bottom)
            End If

            If hasContent Then
                Dim contentRight As Single = If(hasInspector AndAlso inspectorWidth > 0.0F, inspectorRect.Left - columnGap, mainContentBounds.Right)
                Dim contentRect As New SKRect(mainContentBounds.Left, mainContentBounds.Top, Math.Max(mainContentBounds.Left, contentRight), mainContentBounds.Bottom)
                AddSurfaceRegion(entries, backgroundEntries, contentRect, _contentPage, profile)
            End If

            If hasInspector AndAlso inspectorWidth > 0.0F Then
                AddSurfaceRegion(entries, backgroundEntries, inspectorRect, _inspectorPage, profile)
            End If
        End Sub

        Private Sub AddCommandRegion(entries As IList(Of MASApplicationLayoutRegionEntry),
                                     backgroundEntries As IList(Of MASApplicationSurfaceBackgroundEntry),
                                     commandBounds As SKRect,
                                     profile As MASSizeProfile)
            If commandBounds.IsEmpty OrElse commandBounds.Width <= 0.0F OrElse commandBounds.Height <= 0.0F Then Return

            Dim commandContentBounds As SKRect = commandBounds
            If IsCommandBackgroundVisible() Then
                Dim backgroundVisualStyle As MASCardVisualStyle = ResolveCommandBackgroundVisualStyle()
                AddBackground(backgroundEntries, commandBounds, backgroundVisualStyle, _commandBackgroundSurfaceMaterialKey)
                commandContentBounds = ResolveCommandRegionContentBounds(commandBounds, profile, _commandBarPage)
            End If

            AddRegion(entries, commandContentBounds, _commandBarPage)
        End Sub

        Private Sub AddSidebarRegions(entries As IList(Of MASApplicationLayoutRegionEntry),
                                      backgroundEntries As IList(Of MASApplicationSurfaceBackgroundEntry),
                                      sidebarRect As SKRect,
                                      profile As MASSizeProfile,
                                      sidebarFooterGap As Single)
            If sidebarRect.IsEmpty OrElse sidebarRect.Width <= 0.0F OrElse sidebarRect.Height <= 0.0F Then Return

            AddApplicationSurfaceRegionBackground(backgroundEntries, sidebarRect)
            Dim sidebarContentRect As SKRect = ResolveRegionContentBounds(sidebarRect, profile)

            If _sidebarFooterPage Is Nothing Then
                AddRegion(entries, sidebarContentRect, _sidebarPage)
                Return
            End If

            Dim footerHeight As Single = Math.Min(ResolveBarHeight(_sidebarFooterSize, profile), sidebarContentRect.Height)
            Dim footerRect As New SKRect(sidebarContentRect.Left, sidebarContentRect.Bottom - footerHeight, sidebarContentRect.Right, sidebarContentRect.Bottom)
            Dim mainBottom As Single = Math.Max(sidebarContentRect.Top, footerRect.Top - sidebarFooterGap)

            If _sidebarPage IsNot Nothing Then
                AddRegion(entries, New SKRect(sidebarContentRect.Left, sidebarContentRect.Top, sidebarContentRect.Right, mainBottom), _sidebarPage)
            End If

            AddRegion(entries, footerRect, _sidebarFooterPage)
        End Sub
    End Class

End Namespace
