using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using TechnologyAssembler;

namespace HashCalculator.GUI
{
    internal class ScriptCompiler
    {
        private static readonly MetadataReference[] _references = new[]
        {
            typeof(object), // core
            typeof(Trace), // make sure trace is availaible
            typeof(Enumerable), // linq
            typeof(ScriptCompiler), // this
            typeof(TechnologyAssemblerCoreModule) // wed
        }.Select(t => MetadataReference.CreateFromFile(t.Assembly.Location)).ToArray();

        public static IEnumerable<(string Id, string Message)> CheckSyntax(string code)
        {
            var syntaxTree = CSharpSyntaxTree.ParseText(code);
            return syntaxTree.GetDiagnostics().Select(diagnostic => (diagnostic.Id, diagnostic.GetMessage()));
        }

        public static bool Compile(string code, object[] arguments, out IEnumerable<(string Id, string Message)> messages)
        {
            // define source code, then parse it (to the type used for compilation)
            var syntaxTree = CSharpSyntaxTree.ParseText(code);

            // define other necessary objects for compilation
            var assemblyName = $"Gen{Guid.NewGuid()}.dll";

            // analyse and generate IL code from syntax tree
            var compilation =
                CSharpCompilation.Create(assemblyName,
                                         syntaxTrees: new[] { syntaxTree },
                                         references: _references,
                                         options: new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary));

            using var ms = new MemoryStream();
            // write IL code into memory
            var result = compilation.Emit(ms);
            messages = result.Diagnostics.Select(diagnostic => (diagnostic.Id, diagnostic.GetMessage()));

            if (!result.Success)
            {
                return false;
            }

            // load this 'virtual' DLL so that we can use
            ms.Seek(0, SeekOrigin.Begin);
            var domain = AppDomain.CreateDomain($"runtime code {assemblyName}");
            var assembly = domain.Load(ms.ToArray());
            // create instance of the desired class and call the desired function
            var entryPoint = assembly.EntryPoint
                ?? throw new InvalidOperationException("Compiled assembly does not have an entry point");
            entryPoint.Invoke(null, arguments);
            return true;
        }
    }
}