Option Strict On
Option Explicit On

Imports System
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.Theming
Imports Nexamas.UI.Values
Imports SkiaSharp

Namespace Nexamas.UI.Controls

    ''' <summary>
    ''' Official Nexamas UI wizard container. It composes the official MASStepper for
    ''' progress state and hosts one MAS child-content surface per step. Navigation is
    ''' exposed as narrow MoveNext/MovePrevious/SetCurrentStep commands so applications
    ''' may bind their own official MAS buttons or command bars without another action path.
    ''' </summary>
    Public NotInheritable Class MASWizard
        Inherits MASControlBase
        Implements IMASLayoutParticipant
        Implements IMASIntrinsicSizeContract

#Region "Fields"

        Private Shared ReadOnly _contract As MASResolvedComponentContract =
            MASArchitectureRuntime.ResolveContractOrThrow(GetType(MASWizard))

        Private ReadOnly _primitives As New MASVisualPrimitivesPainter()
        Private ReadOnly _stepper As MASStepper
        Private ReadOnly _contents As New List(Of MASControlBase)()
        Private _disposedLocal As Boolean

#End Region

#Region "Events"

        ''' <summary>
        ''' Raised after the wizard current step changes through the official MASStepper route.
        ''' </summary>
        Public Event CurrentStepChanged As EventHandler

#End Region

#Region "Constructor / Factory"

        Public Sub New()
            MyBase.New()
            _stepper = MASStepper.Create(MASWizardFlowPolicy.NormalizeOrientation(MASStepperOrientation.Horizontal))
            AddHandler _stepper.CurrentStepChanged, AddressOf OnStepperCurrentStepChanged
            ChildLayerInternal.Add(_stepper)
        End Sub

        Public Shared Function Create(Optional orientation As MASStepperOrientation = MASStepperOrientation.Horizontal) As MASWizard
            Dim wizard As New MASWizard()
            wizard.Orientation = orientation
            Return wizard
        End Function

        Friend Shared Function [Default]() As MASWizard
            Return Create()
        End Function

#End Region

#Region "Public API"

        Public Property Orientation As MASStepperOrientation
            Get
                Return _stepper.Orientation
            End Get
            Set(value As MASStepperOrientation)
                _stepper.Orientation = MASWizardFlowPolicy.NormalizeOrientation(value)
                RequestSizeLayoutRefreshForSizeAffectingChange("MASWizard.Orientation")
                ApplyChildLayout()
                InvalidateVisual()
            End Set
        End Property

        Public ReadOnly Property CurrentIndex As Integer
            Get
                Return _stepper.CurrentIndex
            End Get
        End Property

        Public ReadOnly Property StepCount As Integer
            Get
                Return _stepper.StepCount
            End Get
        End Property

        Public ReadOnly Property CanMoveNext As Boolean
            Get
                Return MASWizardFlowPolicy.CanMoveNext(CurrentIndex, StepCount)
            End Get
        End Property

        Public ReadOnly Property CanMovePrevious As Boolean
            Get
                Return MASWizardFlowPolicy.CanMovePrevious(CurrentIndex, StepCount)
            End Get
        End Property

        Public Function AddStep(title As String,
                                Optional description As String = Nothing,
                                Optional content As MASControlBase = Nothing) As MASWizard
            Dim index As Integer = _stepper.AddStep(title, description)
            _contents.Add(Nothing)
            If content IsNot Nothing Then SetStepContent(index, content)
            UpdateContentVisibilityAndLayout()
            RequestSizeLayoutRefreshForSizeAffectingChange("MASWizard.AddStep")
            InvalidateVisual()
            Return Me
        End Function

        Public Function SetStepContent(index As Integer,
                                       content As MASControlBase) As MASWizard
            ValidateIndex(index)

            Dim oldContent As MASControlBase = _contents(index)
            If oldContent IsNot Nothing AndAlso HasChildLayerInternal() Then
                ChildLayerInternal.Remove(oldContent)
            End If

            _contents(index) = content
            If content IsNot Nothing Then
                ChildLayerInternal.Add(content)
            End If

            UpdateContentVisibilityAndLayout()
            RequestSizeLayoutRefreshForSizeAffectingChange("MASWizard.SetStepContent")
            InvalidateVisual()
            Return Me
        End Function

        Public Function GetStepContent(index As Integer) As MASControlBase
            ValidateIndex(index)
            Return _contents(index)
        End Function

        Public Function RemoveStep(index As Integer) As MASWizard
            ValidateIndex(index)

            Dim oldContent As MASControlBase = _contents(index)
            If oldContent IsNot Nothing AndAlso HasChildLayerInternal() Then
                ChildLayerInternal.Remove(oldContent)
            End If

            _contents.RemoveAt(index)
            _stepper.RemoveStep(index)
            UpdateContentVisibilityAndLayout()
            RequestSizeLayoutRefreshForSizeAffectingChange("MASWizard.RemoveStep")
            InvalidateVisual()
            Return Me
        End Function

        Public Function ClearSteps() As MASWizard
            If _contents.Count = 0 AndAlso _stepper.StepCount = 0 Then Return Me

            For Each content As MASControlBase In _contents
                If content Is Nothing Then Continue For
                If HasChildLayerInternal() Then ChildLayerInternal.Remove(content)
            Next

            _contents.Clear()
            _stepper.ClearSteps()
            UpdateContentVisibilityAndLayout()
            RequestSizeLayoutRefreshForSizeAffectingChange("MASWizard.ClearSteps")
            InvalidateVisual()
            Return Me
        End Function

        Public Sub SetCurrentStep(index As Integer)
            _stepper.SetCurrentStep(index)
        End Sub

        Public Sub MoveNext()
            _stepper.MoveNext()
        End Sub

        Public Sub MovePrevious()
            _stepper.MovePrevious()
        End Sub

        Public Function WithOrientation(value As MASStepperOrientation) As MASWizard
            Orientation = value
            Return Me
        End Function

        Public Function WithCurrentStep(index As Integer) As MASWizard
            SetCurrentStep(index)
            Return Me
        End Function

        Public Shadows Function WithSize(sizeIntent As MASSize) As MASWizard
            MyBase.SetSize(sizeIntent)
            Return Me
        End Function

#End Region

#Region "Layout / size"

        Friend Function GetPreferredLayoutSize(context As MASLayoutMeasureContext) As SKSize Implements IMASLayoutParticipant.GetPreferredLayoutSize
            Return MeasureIntrinsicSize(CreateSizeContext(context), SizeIntent).DesiredSize
        End Function

        Friend Function GetMinLayoutSize(context As MASLayoutMeasureContext) As SKSize Implements IMASLayoutParticipant.GetMinLayoutSize
            Return MeasureIntrinsicSize(CreateSizeContext(context), SizeIntent).MinSize
        End Function

        Friend Function MeasureIntrinsicSize(context As MASSizeContext,
                                             intent As MASSize) As MASSizeResult Implements IMASIntrinsicSizeContract.MeasureIntrinsicSize
            Dim safeContext As MASSizeContext = MASIntrinsicControlSizeMetrics.EnsureContext(context)
            Dim desired As New SKSize(StepperTokens.WizardDefaultWidthDip, StepperTokens.WizardDefaultHeightDip)
            Dim minimum As New SKSize(StepperTokens.WizardMinWidthDip, StepperTokens.WizardMinHeightDip)
            Dim contentInset As New MASLayoutInset(StepperTokens.WizardPaddingLeftDip,
                                                   StepperTokens.WizardPaddingTopDip,
                                                   StepperTokens.WizardPaddingRightDip,
                                                   StepperTokens.WizardPaddingBottomDip)

            Return MASSizeResolver.FromMeasured(
                desiredSize:=desired,
                minSize:=minimum,
                maxSize:=New SKSize(Single.PositiveInfinity, Single.PositiveInfinity),
                intent:=MASSize.Normalize(intent),
                context:=safeContext,
                canGrowWidth:=True,
                canGrowHeight:=True,
                contentInset:=contentInset,
                visualOverflowInset:=MASLayoutInset.Empty,
                hitOverflowInset:=MASLayoutInset.Empty,
                isFallback:=False)
        End Function

        Private Shared Function CreateSizeContext(context As MASLayoutMeasureContext) As MASSizeContext
            If context Is Nothing Then Return MASIntrinsicControlSizeMetrics.EnsureContext(Nothing)
            Return context.ToSizeContext()
        End Function

        Protected Overrides Sub OnBoundsChanged()
            MyBase.OnBoundsChanged()
            ApplyChildLayout()
        End Sub

        Private Sub ApplyChildLayout()
            Dim bounds As SKRect = BoundsLogical
            If bounds.Width <= 1.0F OrElse bounds.Height <= 1.0F Then
                _stepper.SetLocalLayoutBoundsInternal(SKRect.Empty)
                ClearContentBounds()
                Return
            End If

            Dim plan As MASWizardLayoutPlan = MASWizardLayoutPlan.CreateForLogical(New SKSize(bounds.Width, bounds.Height), SizeIntent)
            Dim content As New SKRect(bounds.Left + plan.PaddingLeftDip,
                                      bounds.Top + plan.PaddingTopDip,
                                      bounds.Right - plan.PaddingRightDip,
                                      bounds.Bottom - plan.PaddingBottomDip)
            If content.Width <= 1.0F OrElse content.Height <= 1.0F Then
                _stepper.SetLocalLayoutBoundsInternal(SKRect.Empty)
                ClearContentBounds()
                Return
            End If

            Dim stepperBounds As SKRect
            Dim contentBounds As SKRect
            If _stepper.Orientation = MASStepperOrientation.Vertical AndAlso Not plan.ForceStacked Then
                Dim stepperWidth As Single = Math.Min(plan.VerticalStepperWidthDip, Math.Max(96.0F, content.Width * 0.42F))
                stepperBounds = New SKRect(content.Left, content.Top, content.Left + stepperWidth, content.Bottom)
                contentBounds = New SKRect(stepperBounds.Right + plan.ContentGapDip,
                                           content.Top,
                                           content.Right,
                                           content.Bottom)
            Else
                Dim stepperHeight As Single = Math.Min(plan.StepperSlotHeightDip, Math.Max(44.0F, content.Height * 0.34F))
                stepperBounds = New SKRect(content.Left, content.Top, content.Right, content.Top + stepperHeight)
                contentBounds = New SKRect(content.Left,
                                           stepperBounds.Bottom + plan.ContentGapDip,
                                           content.Right,
                                           content.Bottom)
            End If

            If plan.HideContent Then
                contentBounds = SKRect.Empty
            End If

            _stepper.SetLocalLayoutBoundsInternal(stepperBounds)

            If contentBounds.Width <= 1.0F OrElse contentBounds.Height <= 1.0F Then
                ClearContentBounds()
                Return
            End If

            For i As Integer = 0 To _contents.Count - 1
                Dim child As MASControlBase = _contents(i)
                If child Is Nothing Then Continue For
                If i = _stepper.CurrentIndex Then
                    child.SetLocalLayoutBoundsInternal(contentBounds)
                Else
                    child.SetLocalLayoutBoundsInternal(SKRect.Empty)
                End If
            Next
        End Sub

        Private Sub ClearContentBounds()
            For Each child As MASControlBase In _contents
                If child Is Nothing Then Continue For
                child.SetLocalLayoutBoundsInternal(SKRect.Empty)
            Next
        End Sub

#End Region

#Region "Rendering"

        Protected Overrides Sub Render(canvas As SKCanvas,
                                       ctx As MASThemeContext,
                                       pixelBounds As SKRect)
            If canvas Is Nothing OrElse ctx Is Nothing Then Return
            If pixelBounds.Width <= 1.0F OrElse pixelBounds.Height <= 1.0F Then Return
            _contract.AssertConsumable()

            Dim dpi As Single = PixelSnap.SafeDpi(ctx.Dpi)
            Dim bounds As SKRect = PixelSnap.SnapRect(pixelBounds, dpi)
            If bounds.Width <= 1.0F OrElse bounds.Height <= 1.0F Then Return
            _primitives.DrawCardSurface(canvas, ctx, bounds, dpi, _contract, MASCardVisualStyle.Secondary)
        End Sub

#End Region

#Region "State"

        Private Sub OnStepperCurrentStepChanged(sender As Object,
                                                e As EventArgs)
            UpdateContentVisibilityAndLayout()
            RaiseCurrentStepChangedSafe()
        End Sub

        Private Sub UpdateContentVisibilityAndLayout()
            For i As Integer = 0 To _contents.Count - 1
                Dim child As MASControlBase = _contents(i)
                If child Is Nothing Then Continue For
                child.Visible = (i = _stepper.CurrentIndex)
            Next

            ApplyChildLayout()
        End Sub

        Friend Function CreateWizardReadinessManifest() As MASWizardReadinessManifest
            Return MASWizardReadinessManifest.CreateCurrent()
        End Function

        Private Sub ValidateIndex(index As Integer)
            If index < 0 OrElse index >= _contents.Count Then Throw New ArgumentOutOfRangeException(NameOf(index))
        End Sub

        Private Sub RaiseCurrentStepChangedSafe()
            Try
                RaiseEvent CurrentStepChanged(Me, EventArgs.Empty)
            Catch masCaughtException1 As Exception
                Nexamas.UI.Diagnostics.MASExceptionSilencer.SwallowBoundary(masCaughtException1, "MASWizard.CurrentStepChanged")
            End Try
        End Sub

        Protected Overrides Function WantsKeyboardFocus() As Boolean
            Return False
        End Function

        Protected Overrides Function WantsPointerFocus() As Boolean
            Return False
        End Function

#End Region

#Region "Dispose"

        Protected Overrides Sub Dispose(disposing As Boolean)
            If _disposedLocal Then Return
            _disposedLocal = True

            If disposing Then
                Try
                    RemoveHandler _stepper.CurrentStepChanged, AddressOf OnStepperCurrentStepChanged
                Catch masCaughtException2 As Exception
                    Nexamas.UI.Diagnostics.MASExceptionSilencer.SwallowDisposeCleanup(masCaughtException2)
                End Try

                Try
                    _primitives.Dispose()
                Catch masCaughtException3 As Exception
                    Nexamas.UI.Diagnostics.MASExceptionSilencer.SwallowDisposeCleanup(masCaughtException3)
                End Try
            End If

            MyBase.Dispose(disposing)
        End Sub

#End Region

    End Class

End Namespace
