using ICSharpCode.Decompiler.Ast;
using ICSharpCode.Decompiler.Tests.Helpers;
using ICSharpCode.NRefactory.CSharp;
using Mono.Cecil;
using NUnit.Framework;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;

namespace ICSharpCode.Decompiler.Tests.FS2CS
{
	public class TestRunner
	{
		public static string FuzzyReadResource(string resourceName)
		{
			var asm = Assembly.GetExecutingAssembly();
			var allResources = asm.GetManifestResourceNames();
			var fullResourceName = allResources.Single(r => r.ToLowerInvariant().EndsWith(resourceName.ToLowerInvariant()));
			return new StreamReader(asm.GetManifestResourceStream(fullResourceName)).ReadToEnd();
		}

		// see https://fsharp.github.io/FSharp.Compiler.Service/compiler.html
		public static string CompileFsToAssembly(string source, bool optimize)
		{
			var tmp = Path.GetTempFileName();
			File.Delete(tmp);
			var sourceFile = Path.ChangeExtension(tmp, ".fs");
			File.WriteAllText(sourceFile, source);
			var asmFile = Path.ChangeExtension(sourceFile, ".dll");
			var sscs = new Microsoft.FSharp.Compiler.SimpleSourceCodeServices.SimpleSourceCodeServices();
			var result = sscs.Compile(new[] { "fsc.exe", "--debug:full", $"--optimize{(optimize?"+" :"-")}", "--target:library", "-o", asmFile, sourceFile });
			File.Delete(sourceFile);
			Assert.AreEqual(0, result.Item1.Length);
			Assert.AreEqual(0, result.Item2);
			Assert.True(File.Exists(asmFile), "Assembly File does not exist");
			return asmFile;
		}

		public static void Run(string fsharpCode, string expectedCSharpCode, bool optimize)
		{
			var asmFilePath = CompileFsToAssembly(fsharpCode, optimize);
			var assembly = AssemblyDefinition.ReadAssembly(asmFilePath);
			try
			{
				assembly.MainModule.ReadSymbols();
				AstBuilder decompiler = new AstBuilder(new DecompilerContext(assembly.MainModule));
				decompiler.AddAssembly(assembly);
				new Helpers.RemoveCompilerAttribute().Run(decompiler.SyntaxTree);
				StringWriter output = new StringWriter();

				// the F# assembly contains a namespace `<StartupCode$tmp6D55>` where the part after tmp is randomly generated.
				// remove this from the ast to simplify the diff
				var startupCodeNode = decompiler.SyntaxTree.Children.Single(d => (d as NamespaceDeclaration)?.Name?.StartsWith("<StartupCode$") ?? false);
				startupCodeNode.Remove();

				decompiler.GenerateCode(new PlainTextOutput(output));
				var fullCSharpCode = output.ToString();

				CodeAssert.AreEqual(expectedCSharpCode, output.ToString());
			}
			finally
			{
				File.Delete(asmFilePath);
				File.Delete(Path.ChangeExtension(asmFilePath, ".pdb"));
			}
		}
	}
}