Option Strict On
Option Explicit On

Imports System
Imports System.Collections.Generic
Imports System.Linq
Imports Nexamas.UI.CommandActions

Namespace Nexamas.UI.Modules

    ''' <summary>
    ''' Small internal module registry for Nexamas UI extension metadata.
    ''' </summary>
    ''' <remarks>
    ''' This registry does not load assemblies, scan folders, create pages, draw UI, mutate themes, own settings UI,
    ''' or execute commands directly. Command contributions are registered into the official MASCommandRegistry route.
    ''' </remarks>
    Friend NotInheritable Class MASModuleRegistry

        Friend Const OfficialSystemName As String = "Nexamas UI Plugin / Module System Foundation"

        Private ReadOnly _modules As New Dictionary(Of String, MASModuleDescriptor)(StringComparer.OrdinalIgnoreCase)
        Private ReadOnly _moduleOrder As New List(Of String)()
        Private ReadOnly _contributions As New Dictionary(Of String, MASModuleContribution)(StringComparer.OrdinalIgnoreCase)
        Private _duplicateContributionCount As Integer

        Private Sub New()
        End Sub

        Friend Shared Function Create() As MASModuleRegistry
            Return New MASModuleRegistry()
        End Function

        Friend ReadOnly Property ModuleCount As Integer
            Get
                Return _modules.Count
            End Get
        End Property

        Friend ReadOnly Property ContributionCount As Integer
            Get
                Return _contributions.Count
            End Get
        End Property

        Friend Function RegisterModule(moduleId As String,
                                       displayName As String,
                                       ownerSystem As String,
                                       Optional version As String = "") As MASModuleRegistry
            Dim descriptor As New MASModuleDescriptor(moduleId, displayName, ownerSystem, version)
            RequireValidModule(descriptor)
            RememberModule(descriptor)
            Return Me
        End Function

        Friend Function RegisterPage(moduleId As String,
                                     pageId As String,
                                     displayName As String,
                                     Optional routeName As String = "MASApplicationWindow.Pages") As MASModuleRegistry
            Return RegisterContribution(moduleId, MASModuleContributionKind.Page, pageId, displayName, "ApplicationSystem/PresentationSystem", routeName)
        End Function

        Friend Function RegisterSettingsSection(moduleId As String,
                                                sectionId As String,
                                                displayName As String,
                                                Optional routeName As String = "MASApplicationSettingsPage") As MASModuleRegistry
            Return RegisterContribution(moduleId, MASModuleContributionKind.SettingsSection, sectionId, displayName, "ApplicationSystem.Settings", routeName)
        End Function

        Friend Function RegisterTheme(moduleId As String,
                                      themeId As String,
                                      displayName As String,
                                      Optional routeName As String = "ThemeSystem.ReadyThemes") As MASModuleRegistry
            Return RegisterContribution(moduleId, MASModuleContributionKind.Theme, themeId, displayName, "ThemeSystem", routeName)
        End Function

        Friend Function RegisterTool(moduleId As String,
                                     toolId As String,
                                     displayName As String,
                                     Optional routeName As String = "MASCommandRegistry.Tool") As MASModuleRegistry
            Return RegisterContribution(moduleId, MASModuleContributionKind.Tool, toolId, displayName, "CommandActionSystem", routeName)
        End Function

        Friend Function RegisterCommand(moduleId As String,
                                        command As MASCommandDefinition,
                                        commandRegistry As MASCommandRegistry,
                                        Optional sourceName As String = "ModuleSystem") As MASModuleRegistry
            If command Is Nothing Then Throw New ArgumentNullException(NameOf(command))
            If commandRegistry Is Nothing Then Throw New ArgumentNullException(NameOf(commandRegistry))

            commandRegistry.Register(
                command.CommandId,
                command.Text,
                Sub(context As MASCommandExecutionContext)
                    command.Execute(context)
                End Sub,
                Function(context As MASCommandExecutionContext) command.CanExecute(context),
                command.Shortcut,
                command.Category,
                command.Description)

            Dim route As String = "MASCommandRegistry"
            If Not String.IsNullOrWhiteSpace(sourceName) Then route &= ":" & sourceName.Trim()

            Return RegisterContribution(moduleId, MASModuleContributionKind.Command, command.CommandId, command.Text, "CommandActionSystem", route)
        End Function

        Friend Function RegisterContribution(moduleId As String,
                                             kind As MASModuleContributionKind,
                                             contributionId As String,
                                             displayName As String,
                                             ownerSystem As String,
                                             routeName As String) As MASModuleRegistry
            Dim id As String = MASModuleContribution.NormalizeId(moduleId)
            If Not _modules.ContainsKey(id) Then Throw New InvalidOperationException("Module is not registered: " & id)

            Dim contribution As New MASModuleContribution(id, kind, contributionId, displayName, ownerSystem, routeName)
            If Not contribution.IsValid Then Throw New ArgumentException("A valid module contribution requires module id, contribution id, display name, owner system, and route name.", NameOf(contributionId))

            Dim key As String = contribution.Key
            If _contributions.ContainsKey(key) Then
                _duplicateContributionCount += 1
                Throw New InvalidOperationException("Module contribution is already registered: " & key)
            End If

            _contributions.Add(key, contribution)
            Return Me
        End Function

        Friend Function HasModule(moduleId As String) As Boolean
            Return _modules.ContainsKey(MASModuleContribution.NormalizeId(moduleId))
        End Function

        Friend Function HasContribution(moduleId As String,
                                        kind As MASModuleContributionKind,
                                        contributionId As String) As Boolean
            Dim key As String = MASModuleContribution.NormalizeId(moduleId) & "|" & kind.ToString() & "|" & MASModuleContribution.NormalizeId(contributionId)
            Return _contributions.ContainsKey(key)
        End Function

        Friend Function CreateSnapshot() As MASModuleRegistrySnapshot
            Dim contributionRows As MASModuleContribution() = _contributions.Values.OrderBy(Function(contribution) contribution.ModuleId, StringComparer.OrdinalIgnoreCase).ThenBy(Function(contribution) contribution.Kind).ThenBy(Function(contribution) contribution.ContributionId, StringComparer.OrdinalIgnoreCase).ToArray()
            Dim moduleRows As MASModuleDescriptor() = _moduleOrder.
                Where(Function(id) _modules.ContainsKey(id)).
                Select(Function(id)
                           Dim moduleContributions As MASModuleContribution() = contributionRows.Where(Function(contribution) String.Equals(contribution.ModuleId, id, StringComparison.OrdinalIgnoreCase)).ToArray()
                           Return _modules(id).WithContributions(moduleContributions)
                       End Function).
                ToArray()

            Return New MASModuleRegistrySnapshot(moduleRows, contributionRows, OfficialSystemName, _duplicateContributionCount)
        End Function

        Private Sub RememberModule(descriptor As MASModuleDescriptor)
            If Not _modules.ContainsKey(descriptor.ModuleId) Then
                _moduleOrder.Add(descriptor.ModuleId)
            End If

            _modules(descriptor.ModuleId) = descriptor
        End Sub

        Private Shared Sub RequireValidModule(descriptor As MASModuleDescriptor)
            If descriptor Is Nothing OrElse Not descriptor.IsValid Then Throw New ArgumentException("A valid module requires module id, display name, and owner system.")
        End Sub

    End Class

End Namespace
