// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

#nullable disable

using System.CodeDom;
using System.CodeDom.Compiler;
using System.Drawing;
using System.Reflection;
using System.Runtime.Loader;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CSharp;

namespace System.Resources.Tools.Tests;

internal static class CodeDomCompileHelper
{
    private static readonly CodeDomProvider s_cSharpProvider = new CSharpCodeProvider();

    private static MetadataReference[] References { get; } = CreateReferences();

    private static MetadataReference[] CreateReferences()
    {
        string corelibPath = typeof(object).Assembly.Location;
        return new[]
        {
            MetadataReference.CreateFromFile(corelibPath),
            MetadataReference.CreateFromFile(Path.Join(Path.GetDirectoryName(corelibPath), "System.Runtime.dll")),
            MetadataReference.CreateFromFile(typeof(Bitmap).Assembly.Location),
        };
    }

    private static Stream CreateAssemblyImage(string source, string assemblyName, string resourceName, Stream resource)
    {
        CSharpCompilation compilation = CSharpCompilation.Create(
            assemblyName,
            new[] { CSharpSyntaxTree.ParseText(source) },
            References,
            new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary));

        ResourceDescription[] description = resource is null
            ? null
            : [new(resourceName, () => resource, isPublic: true)];

        MemoryStream stream = new();
        var result = compilation.Emit(stream, manifestResources: description);
        if (result.Success)
        {
            stream.Position = 0;
            return stream;
        }
        else
        {
            throw new InvalidOperationException(string.Join('\n', result.Diagnostics.Select(d => d.GetMessage())));
        }
    }

    internal static Type CompileClass(
        CodeCompileUnit compileUnit,
        string baseName,
        string nameSpace = null,
        Stream resource = null)
    {
        AssemblyLoadContext context = new(name: null, isCollectible: true);
        string fullName = nameSpace is null ? baseName : $"{nameSpace}.{baseName}";

        using StringWriter writer = new();
        s_cSharpProvider.GenerateCodeFromCompileUnit(compileUnit, writer, new());
        Assembly assembly = context.LoadFromStream(CreateAssemblyImage(
            writer.ToString(),
            fullName,
            $"{fullName}.resources",
            resource));
        Type type = assembly.GetType(fullName, throwOnError: true);

        // Once all references are collected, the assembly will unload.
        context.Unload();
        return type;
    }
}
