// /***************************************************************************
// Aaru Data Preservation Suite
// ----------------------------------------------------------------------------
//
// Filename       : PluginRegisterGenerator.cs
// Author(s)      : Natalia Portillo <claunia@claunia.com>
//
// Component      : Aaru.Generators.
//
// --[ License ] --------------------------------------------------------------
//
//     This library is free software; you can redistribute it and/or modify
//     it under the terms of the GNU Lesser General Public License as
//     published by the Free Software Foundation; either version 2.1 of the
//     License, or (at your option) any later version.
//
//     This library is distributed in the hope that it will be useful, but
//     WITHOUT ANY WARRANTY; without even the implied warranty of
//     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
//     Lesser General Public License for more details.
//
//     You should have received a copy of the GNU Lesser General Public
//     License along with this library; if not, see <http://www.gnu.org/licenses/>.
//
// ----------------------------------------------------------------------------
// Copyright © 2011-2026 Natalia Portillo
// ****************************************************************************/

using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp.Syntax;

namespace Aaru.Generators;

[Generator]
public class PluginRegisterGenerator : ISourceGenerator
{
#region ISourceGenerator Members

    /// <inheritdoc />
    public void Initialize(GeneratorInitializationContext context) =>

        // Nothing to do
        context.RegisterForSyntaxNotifications(static () => new PluginFinder());

    /// <inheritdoc />
    public void Execute(GeneratorExecutionContext context)
    {
        /*
        #if DEBUG
            if(!Debugger.IsAttached)
            {
                Debugger.Launch();
            }
        #endif
        */

        ClassDeclarationSyntax pluginRegister = ((PluginFinder)context.SyntaxReceiver)?.Register;

        if(pluginRegister == null) return;

        var @namespace =
            (pluginRegister.Ancestors().FirstOrDefault(static x => x is FileScopedNamespaceDeclarationSyntax) as
                 FileScopedNamespaceDeclarationSyntax)?.Name.ToString();

        @namespace ??=
            (pluginRegister.Ancestors().FirstOrDefault(static x => x is NamespaceDeclarationSyntax) as
                 NamespaceDeclarationSyntax)?.ToString();

        string className = pluginRegister.Identifier.Text;

        List<string> archives                    = ((PluginFinder)context.SyntaxReceiver)?.Archives;
        List<string> checksums                   = ((PluginFinder)context.SyntaxReceiver)?.Checksums;
        List<string> fileSystems                 = ((PluginFinder)context.SyntaxReceiver)?.FileSystems;
        List<string> filters                     = ((PluginFinder)context.SyntaxReceiver)?.Filters;
        List<string> floppyImagePlugins          = ((PluginFinder)context.SyntaxReceiver)?.FloppyImagePlugins;
        List<string> partitionPlugins            = ((PluginFinder)context.SyntaxReceiver)?.PartitionPlugins;
        List<string> mediaImagePlugins           = ((PluginFinder)context.SyntaxReceiver)?.MediaImagePlugins;
        List<string> readOnlyFileSystems         = ((PluginFinder)context.SyntaxReceiver)?.ReadOnlyFileSystems;
        List<string> writableFloppyImagePlugins  = ((PluginFinder)context.SyntaxReceiver)?.WritableFloppyImagePlugins;
        List<string> writableImagePlugins        = ((PluginFinder)context.SyntaxReceiver)?.WritableImagePlugins;
        List<string> byteAddressableImagePlugins = ((PluginFinder)context.SyntaxReceiver)?.ByteAddressableImagePlugins;
        List<string> fluxImagePlugins            = ((PluginFinder)context.SyntaxReceiver)?.FluxImagePlugins;
        List<string> writableFluxImagePlugins    = ((PluginFinder)context.SyntaxReceiver)?.WritableFluxImagePlugins;

        StringBuilder sb = new();

        sb.AppendLine("""
                      // /***************************************************************************
                      // Aaru Data Preservation Suite
                      // ----------------------------------------------------------------------------
                      //
                      // Filename       : Register.g.cs
                      // Author(s)      : Natalia Portillo <claunia@claunia.com>
                      //
                      // --[ Description ] ----------------------------------------------------------
                      //
                      //     Registers all plugins in this assembly.
                      //
                      // --[ License ] --------------------------------------------------------------
                      //
                      //     Permission is hereby granted, free of charge, to any person obtaining a
                      //     copy of this software and associated documentation files (the
                      //     "Software"), to deal in the Software without restriction, including
                      //     without limitation the rights to use, copy, modify, merge, publish,
                      //     distribute, sublicense, and/or sell copies of the Software, and to
                      //     permit persons to whom the Software is furnished to do so, subject to
                      //     the following conditions:
                      //
                      //     The above copyright notice and this permission notice shall be included
                      //     in all copies or substantial portions of the Software.
                      //
                      //     THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
                      //     OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
                      //     MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
                      //     IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
                      //     CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
                      //     TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
                      //     SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
                      //
                      // ----------------------------------------------------------------------------
                      // Copyright © 2011-2026 Natalia Portillo
                      // ****************************************************************************/
                      """);

        sb.AppendLine();
        sb.AppendLine("using System;");
        sb.AppendLine("using System.Collections.Generic;");
        sb.AppendLine("using Aaru.CommonTypes.Interfaces;");
        sb.AppendLine("using Microsoft.Extensions.DependencyInjection;");
        sb.AppendLine();
        sb.AppendLine($"namespace {@namespace};");
        sb.AppendLine();
        sb.AppendLine($"public sealed partial class {className} : IPluginRegister");
        sb.AppendLine("{");

        if(archives?.Count > 0)
        {
            sb.AppendLine("    public void RegisterArchivePlugins(IServiceCollection services)");
            sb.AppendLine("    {");

            foreach(string plugin in archives.Distinct())
                sb.AppendLine($"        services.AddTransient<IArchive, {plugin}>();");

            sb.AppendLine("    }");
        }
        else
            sb.AppendLine("    public void RegisterArchivePlugins(IServiceCollection services) {}");

        sb.AppendLine();

        if(checksums?.Count > 0)
        {
            sb.AppendLine("    public void RegisterChecksumPlugins(IServiceCollection services)");
            sb.AppendLine("    {");

            foreach(string plugin in checksums.Distinct())
                sb.AppendLine($"        services.AddTransient<IChecksum, {plugin}>();");

            sb.AppendLine("    }");
        }
        else
            sb.AppendLine("    public void RegisterChecksumPlugins(IServiceCollection services) {}");

        sb.AppendLine();

        if(fileSystems?.Count > 0)
        {
            sb.AppendLine("    public void RegisterFilesystemPlugins(IServiceCollection services)");
            sb.AppendLine("    {");

            foreach(string plugin in fileSystems.Distinct())
                sb.AppendLine($"        services.AddTransient<IFilesystem, {plugin}>();");

            sb.AppendLine("    }");
        }
        else
            sb.AppendLine("    public void RegisterFilesystemPlugins(IServiceCollection services) {}");

        sb.AppendLine();

        if(filters?.Count > 0)
        {
            sb.AppendLine("    public void RegisterFilterPlugins(IServiceCollection services)");
            sb.AppendLine("    {");

            foreach(string plugin in filters.Distinct())
                sb.AppendLine($"        services.AddTransient<IFilter, {plugin}>();");

            sb.AppendLine("    }");
        }
        else
            sb.AppendLine("    public void RegisterFilterPlugins(IServiceCollection services) {}");

        sb.AppendLine();

        if(floppyImagePlugins?.Count > 0)
        {
            sb.AppendLine("    public void RegisterFloppyImagePlugins(IServiceCollection services)");
            sb.AppendLine("    {");

            foreach(string plugin in floppyImagePlugins.Distinct())
                sb.AppendLine($"        services.AddTransient<IFloppyImage, {plugin}>();");

            sb.AppendLine("    }");
        }
        else
            sb.AppendLine("    public void RegisterFloppyImagePlugins(IServiceCollection services) {}");

        sb.AppendLine();

        if(mediaImagePlugins?.Count > 0)
        {
            sb.AppendLine("    public void RegisterMediaImagePlugins(IServiceCollection services)");
            sb.AppendLine("    {");

            foreach(string plugin in mediaImagePlugins.Distinct())
                sb.AppendLine($"        services.AddTransient<IMediaImage, {plugin}>();");

            sb.AppendLine("    }");
        }
        else
            sb.AppendLine("    public void RegisterMediaImagePlugins(IServiceCollection services) {}");

        sb.AppendLine();

        if(partitionPlugins?.Count > 0)
        {
            sb.AppendLine("    public void RegisterPartitionPlugins(IServiceCollection services)");
            sb.AppendLine("    {");

            foreach(string plugin in partitionPlugins.Distinct())
                sb.AppendLine($"        services.AddTransient<IPartition, {plugin}>();");

            sb.AppendLine("    }");
        }
        else
            sb.AppendLine("    public void RegisterPartitionPlugins(IServiceCollection services) {}");

        sb.AppendLine();

        if(readOnlyFileSystems?.Count > 0)
        {
            sb.AppendLine("    public void RegisterReadOnlyFilesystemPlugins(IServiceCollection services)");
            sb.AppendLine("    {");

            foreach(string plugin in readOnlyFileSystems.Distinct())
                sb.AppendLine($"        services.AddTransient<IReadOnlyFilesystem, {plugin}>();");

            sb.AppendLine("    }");
        }
        else
            sb.AppendLine("    public void RegisterReadOnlyFilesystemPlugins(IServiceCollection services) {}");

        sb.AppendLine();

        if(writableFloppyImagePlugins?.Count > 0)
        {
            sb.AppendLine("    public void RegisterWritableFloppyImagePlugins(IServiceCollection services)");
            sb.AppendLine("    {");

            foreach(string plugin in writableFloppyImagePlugins.Distinct())
                sb.AppendLine($"        services.AddTransient<IWritableFloppyImage, {plugin}>();");

            sb.AppendLine("    }");
        }
        else
            sb.AppendLine("    public void RegisterWritableFloppyImagePlugins(IServiceCollection services) {}");

        sb.AppendLine();

        if(writableImagePlugins?.Count > 0)
        {
            sb.AppendLine("    public void RegisterWritableImagePlugins(IServiceCollection services)");
            sb.AppendLine("    {");

            foreach(string plugin in writableImagePlugins.Distinct())
                sb.AppendLine($"        services.AddTransient<IBaseWritableImage, {plugin}>();");

            sb.AppendLine("    }");
        }
        else
            sb.AppendLine("    public void RegisterWritableImagePlugins(IServiceCollection services) {}");

        sb.AppendLine();

        if(byteAddressableImagePlugins?.Count > 0)
        {
            sb.AppendLine("    public void RegisterByteAddressablePlugins(IServiceCollection services)");
            sb.AppendLine("    {");

            foreach(string plugin in byteAddressableImagePlugins.Distinct())
                sb.AppendLine($"        services.AddTransient<IByteAddressableImage, {plugin}>();");

            sb.AppendLine("    }");
        }
        else
            sb.AppendLine("    public void RegisterByteAddressablePlugins(IServiceCollection services) {}");

        if(fluxImagePlugins?.Count > 0)
        {
            sb.AppendLine("    public void RegisterFluxImagePlugins(IServiceCollection services)");
            sb.AppendLine("    {");

            foreach(string plugin in fluxImagePlugins.Distinct())
                sb.AppendLine($"        services.AddTransient<IFluxImage, {plugin}>();");

            sb.AppendLine("    }");
        }
        else
            sb.AppendLine("    public void RegisterFluxImagePlugins(IServiceCollection services) {}");

        if(writableFluxImagePlugins?.Count > 0)
        {
            sb.AppendLine("    public void RegisterWritableFluxImagePlugins(IServiceCollection services)");
            sb.AppendLine("    {");

            foreach(string plugin in writableFluxImagePlugins.Distinct())
                sb.AppendLine($"        services.AddTransient<IWritableFluxImage, {plugin}>();");

            sb.AppendLine("    }");
        }
        else
            sb.AppendLine("    public void RegisterWritableFluxImagePlugins(IServiceCollection services) {}");

        sb.AppendLine("}");

        context.AddSource("Register.g.cs", sb.ToString());
    }

#endregion

#region Nested type: PluginFinder

    sealed class PluginFinder : ISyntaxReceiver
    {
        public List<string>           Archives                    { get; } = [];
        public List<string>           Checksums                   { get; } = [];
        public List<string>           FileSystems                 { get; } = [];
        public List<string>           Filters                     { get; } = [];
        public List<string>           FloppyImagePlugins          { get; } = [];
        public List<string>           MediaImagePlugins           { get; } = [];
        public List<string>           PartitionPlugins            { get; } = [];
        public List<string>           ReadOnlyFileSystems         { get; } = [];
        public List<string>           WritableFloppyImagePlugins  { get; } = [];
        public List<string>           WritableImagePlugins        { get; } = [];
        public List<string>           ByteAddressableImagePlugins { get; } = [];
        public List<string>           FluxImagePlugins            { get; } = [];
        public List<string>           WritableFluxImagePlugins    { get; } = [];
        public ClassDeclarationSyntax Register                    { get; private set; }

#region ISyntaxReceiver Members

        public void OnVisitSyntaxNode(SyntaxNode syntaxNode)
        {
            if(syntaxNode is not ClassDeclarationSyntax plugin) return;

            if(plugin.BaseList?.Types.Any(static t => ((t as SimpleBaseTypeSyntax)?.Type as IdentifierNameSyntax)
                                                    ?.Identifier.ValueText ==
                                                      "IPluginRegister") ==
               true)
                Register = plugin;

            if(plugin.BaseList?.Types.Any(static t => ((t as SimpleBaseTypeSyntax)?.Type as IdentifierNameSyntax)
                                                    ?.Identifier.ValueText ==
                                                      "IArchive") ==
               true)
                if(!Archives.Contains(plugin.Identifier.Text))
                    Archives.Add(plugin.Identifier.Text);

            if(plugin.BaseList?.Types.Any(static t => ((t as SimpleBaseTypeSyntax)?.Type as IdentifierNameSyntax)
                                                    ?.Identifier.ValueText ==
                                                      "IChecksum") ==
               true)
                if(!Checksums.Contains(plugin.Identifier.Text))
                    Checksums.Add(plugin.Identifier.Text);

            if(plugin.BaseList?.Types.Any(static t => ((t as SimpleBaseTypeSyntax)?.Type as IdentifierNameSyntax)
                                                    ?.Identifier.ValueText ==
                                                      "IFilesystem") ==
               true)
                if(!FileSystems.Contains(plugin.Identifier.Text))
                    FileSystems.Add(plugin.Identifier.Text);

            if(plugin.BaseList?.Types.Any(static t => ((t as SimpleBaseTypeSyntax)?.Type as IdentifierNameSyntax)
                                                    ?.Identifier.ValueText ==
                                                      "IFilter") ==
               true)
                if(!Filters.Contains(plugin.Identifier.Text))
                    Filters.Add(plugin.Identifier.Text);

            if(plugin.BaseList?.Types.Any(static t => ((t as SimpleBaseTypeSyntax)?.Type as IdentifierNameSyntax)
                                                    ?.Identifier.ValueText ==
                                                      "IFloppyImage") ==
               true)
                if(!FloppyImagePlugins.Contains(plugin.Identifier.Text))
                    FloppyImagePlugins.Add(plugin.Identifier.Text);

            if(plugin.BaseList?.Types.Any(static t => ((t as SimpleBaseTypeSyntax)?.Type as IdentifierNameSyntax)
                                                    ?.Identifier.ValueText ==
                                                      "IFluxImage") ==
               true)
                if(!FluxImagePlugins.Contains(plugin.Identifier.Text))
                    FluxImagePlugins.Add(plugin.Identifier.Text);


            if(plugin.BaseList?.Types.Any(static t =>
                                              ((t as SimpleBaseTypeSyntax)?.Type as IdentifierNameSyntax)?.Identifier
                                             .ValueText is "IMediaImage"
                                                        or "IOpticalMediaImage"
                                                        or "IFloppyImage"
                                                        or "ITapeImage"
                                                        or "IFluxImage") ==
               true)
                if(!MediaImagePlugins.Contains(plugin.Identifier.Text))
                    MediaImagePlugins.Add(plugin.Identifier.Text);

            if(plugin.BaseList?.Types.Any(static t => ((t as SimpleBaseTypeSyntax)?.Type as IdentifierNameSyntax)
                                                    ?.Identifier.ValueText ==
                                                      "IPartition") ==
               true)
                if(!PartitionPlugins.Contains(plugin.Identifier.Text))
                    PartitionPlugins.Add(plugin.Identifier.Text);

            if(plugin.BaseList?.Types.Any(static t =>
                                              ((t as SimpleBaseTypeSyntax)?.Type as IdentifierNameSyntax)?.Identifier
                                             .ValueText ==
                                              "IReadOnlyFilesystem") ==
               true)
            {
                if(!ReadOnlyFileSystems.Contains(plugin.Identifier.Text))
                    ReadOnlyFileSystems.Add(plugin.Identifier.Text);
            }

            if(plugin.BaseList?.Types.Any(static t =>
                                              ((t as SimpleBaseTypeSyntax)?.Type as IdentifierNameSyntax)?.Identifier
                                             .ValueText ==
                                              "IWritableFloppyImage") ==
               true)
            {
                if(!WritableFloppyImagePlugins.Contains(plugin.Identifier.Text))
                    WritableFloppyImagePlugins.Add(plugin.Identifier.Text);
            }

            if(plugin.BaseList?.Types.Any(static t => ((t as SimpleBaseTypeSyntax)?.Type as IdentifierNameSyntax)
                                                    ?.Identifier.ValueText ==
                                                      "IWritableFluxImage") ==
               true)
            {
                if(!WritableFluxImagePlugins.Contains(plugin.Identifier.Text))
                    WritableFluxImagePlugins.Add(plugin.Identifier.Text);
            }

            if(plugin.BaseList?.Types.Any(static t =>
                                              ((t as SimpleBaseTypeSyntax)?.Type as IdentifierNameSyntax)?.Identifier
                                             .ValueText is "IWritableImage"
                                                        or "IWritableOpticalImage"
                                                        or "IWritableTapeImage"
                                                        or "IByteAddressableImage"
                                                        or "IWritableFluxImage") ==
               true)
            {
                if(!WritableImagePlugins.Contains(plugin.Identifier.Text))
                    WritableImagePlugins.Add(plugin.Identifier.Text);
            }

            if(plugin.BaseList?.Types.Any(static t =>
                                              ((t as SimpleBaseTypeSyntax)?.Type as IdentifierNameSyntax)?.Identifier
                                             .ValueText ==
                                              "IByteAddressableImage") ==
               true)
            {
                if(!ByteAddressableImagePlugins.Contains(plugin.Identifier.Text))
                    ByteAddressableImagePlugins.Add(plugin.Identifier.Text);
            }

            MediaImagePlugins.AddRange(WritableImagePlugins.Where(t => !ByteAddressableImagePlugins.Contains(t)));
            FileSystems.AddRange(ReadOnlyFileSystems);
        }

#endregion
    }

#endregion
}