context: //     This file is part of PapyrusDotNet.
  // 
  //     PapyrusDotNet is free software: you can redistribute it and/or modify
  //     it under the terms of the GNU General Public License as published by
  //     the Free Software Foundation, either version 3 of the License, or
  //     (at your option) any later version.
  // 
  //     PapyrusDotNet 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 General Public License for more details.
  // 
  //     You should have received a copy of the GNU General Public License
  //     along with PapyrusDotNet.  If not, see <http://www.gnu.org/licenses/>.
  //  
  //     Copyright 2015, Karl Patrik Johansson, zerratar@gmail.com
  
  #region
  
  using System;
  using System.Collections.Generic;
  using System.IO;
  using System.Linq;
  using System.Reflection;
  using System.Threading.Tasks;
  using Mono.Cecil;
  using Mono.Cecil.Cil;
  using PapyrusDotNet.Common.Interfaces;
  using PapyrusDotNet.CoreBuilder.Interfaces;
  using PapyrusDotNet.CoreBuilder.Papyrus.Assembly;
  using EventAttributes = Mono.Cecil.EventAttributes;
  using FieldAttributes = Mono.Cecil.FieldAttributes;
  using MethodAttributes = Mono.Cecil.MethodAttributes;
  using TypeAttributes = Mono.Cecil.TypeAttributes;
  
  #endregion
  
  namespace PapyrusDotNet.CoreBuilder.Implementation
  {
      /// <summary>
      ///     Used for building .NET Assemblies using Papyrus as input
      /// </summary>
      public abstract class PapyrusCilAssemblyBuilderBase : IPapyrusCilAssemblyBuilder
      {
          protected IAssemblyNameResolver AssemblyNameResolver;
          private int filesParsed;
          protected IPapyrusAssemblyParser PapyrusAssemblyParser;
  
          // public const string OutputFileName = "PapyrusDotNet.Core.dll";
          // public const string CoreNamespace = "PapyrusDotNet.Core";
  
          protected IPapyrusScriptParser PapyrusScriptParser;
          protected IStatusCallbackService StatusCallback;
  
          private int totalFilesToParse;
          protected IPapyrusTypeDefinitionResolver TypeDefinitionResolver;
          protected IPapyrusTypeReferenceResolver TypeReferenceResolver;
  
          protected PapyrusCilAssemblyBuilderBase()
          {
          }
  
          /// <summary>
          /// </summary>
          /// <param name="scriptParser"></param>
          /// <param name="assemblyParser"></param>
          /// <param name="typeDefinitionResolver"></param>
          /// <param name="typeReferenceResolver"></param>
          /// <param name="nameResolver"></param>
          /// <param name="callback"></param>
          protected PapyrusCilAssemblyBuilderBase(IPapyrusScriptParser scriptParser,
              IPapyrusAssemblyParser assemblyParser,
              IPapyrusTypeDefinitionResolver typeDefinitionResolver,
              IPapyrusTypeReferenceResolver typeReferenceResolver,
              IAssemblyNameResolver nameResolver,
              IStatusCallbackService callback)
          {
              AssemblyNameResolver = nameResolver;
              StatusCallback = callback;
              PapyrusScriptParser = scriptParser;
              PapyrusAssemblyParser = assemblyParser;
              TypeDefinitionResolver = typeDefinitionResolver;
              TypeReferenceResolver = typeReferenceResolver;
              TypeDefinitionResolver.Initialize(this);
              TypeReferenceResolver.Initialize(this);
          }
  
          public AssemblyDefinition CoreAssembly { get; set; }
          public ModuleDefinition MainModule { get; set; }
  
          public List<string> ReservedTypeNames { get; set; } = new List<string>();
  
          public List<TypeReference> AddedTypeReferences { get; set; } = new List<TypeReference>();
  
          /// <summary>
          ///     Builds a .NET Assembly using the input Papyrus Source
          /// </summary>
          /// <param name="inputSourceFiles"></param>
          public async void BuildAssembly(string[] inputSourceFiles)
          {
              // @"C:\The Elder Scrolls V Skyrim\Data\scripts\Source"
              var files = inputSourceFiles;
  
              var resolvedAssemblyName = AssemblyNameResolver.Resolve(null);
              CoreAssembly = AssemblyDefinition.CreateAssembly(
                  resolvedAssemblyName,
                  resolvedAssemblyName.Name, ModuleKind.Dll);
  
              MainModule = CoreAssembly.MainModule;
  
              var papyrusObjects = new List<PapyrusAssemblyObject>();
  
              StatusCallback.WriteLine("Parsing Papyrus... This usually takes about 30 seconds.");
  
              totalFilesToParse = files.Length;
  
              var asyncParse = files.Select(Parse);
              papyrusObjects.AddRange(await Task.WhenAll(asyncParse.ToArray()));
  
              StatusCallback.Title = "PapyrusDotNet";
  
              StatusCallback.WriteLine("Adding object references... This usually takes about a minute.");
              foreach (var pasObj in papyrusObjects)
                  AddedTypeReferences.Add(TypeReferenceResolver.Resolve(MainModule, null, pasObj.Name));
  
              foreach (var pas in papyrusObjects)
                  MainModule.Types.Add(TypeDefinitionResolver.Resolve(MainModule, pas));
  
              StatusCallback.WriteLine("Resolving object references... This usually takes about 30 seconds.");
              foreach (var t in MainModule.Types)
              {
                  foreach (var f in t.Methods)
                  {
                      var typeDefinition = MainModule.Types.FirstOrDefault(ty => ty.FullName == f.ReturnType.FullName);
                      f.ReturnType = TypeReferenceResolver.Resolve(MainModule, typeDefinition, f.ReturnType.FullName);
                      foreach (var p in f.Parameters)
                      {
                          var td = MainModule.Types.FirstOrDefault(ty => ty.FullName == p.ParameterType.FullName);
                          if (td != null)
                              /*	// Most likely a System object.							
                                  p.ParameterType = GetTypeReference(null, p.ParameterType.FullName);
                              else */
                              p.ParameterType = TypeReferenceResolver.Resolve(MainModule, typeDefinition, td.FullName);
                      }
                  }
              }
  
              StatusCallback.WriteLine("Importing Papyrus specific attributes...");
  
  
              var allAttributesToInclude = Assembly.GetExecutingAssembly().GetTypes().Where(
                  t => t.Name.ToLower().EndsWith("attribute")
                  //TODO: we should include the PapyrusDotNet.System here as well.
                  );
  
              foreach (var attr in allAttributesToInclude)
              {
                  ImportType(MainModule, attr);
              }
  
              CoreAssembly.Write(AssemblyNameResolver.OutputLibraryFilename);
  
              StatusCallback.ForegroundColor = ConsoleColor.Green;
              StatusCallback.WriteLine(AssemblyNameResolver.OutputLibraryFilename + " successefully generated.");
              StatusCallback.ResetColor();
          }
  
  
          public void CreateEmptyFunctionBody(ref MethodDefinition function)
          {
              var fnl = function.ReturnType.FullName.ToLower();
              if (fnl.Equals("system.void"))
              {
                  // Do nothing	
              }
  
              else if (fnl.Contains("[]"))
              {
                  function.Body.Instructions.Add(Instruction.Create(OpCodes.Ldnull));
              }
  
              else if (fnl.StartsWith("system.string") || fnl.StartsWith("system.object") ||
                       fnl.StartsWith(AssemblyNameResolver.BaseNamespace.ToLower()))
                  function.Body.Instructions.Add(Instruction.Create(OpCodes.Ldnull));
  
              else if (fnl.StartsWith("system.int") || fnl.StartsWith("system.bool") || fnl.StartsWith("system.long") ||
                       fnl.StartsWith("system.byte") || fnl.StartsWith("system.short"))
              {
                  function.Body.Instructions.Add(fnl.StartsWith("system.long")
                      ? Instruction.Create(OpCodes.Ldc_I8, 0L)
                      : Instruction.Create(OpCodes.Ldc_I4_0));
              }
  
              else if (fnl.StartsWith("system.float"))
                  function.Body.Instructions.Add(Instruction.Create(OpCodes.Ldc_R4, 0f));
  
              else if (fnl.StartsWith("system.double"))
                  function.Body.Instructions.Add(Instruction.Create(OpCodes.Ldc_R8, 0d));
  
  
              function.Body.Instructions.Add(Instruction.Create(OpCodes.Ret));
          }
  
          public void AddVirtualOnInit(TypeDefinition newType)
          {
              // ReSharper disable once SimplifyLinqExpression
  
gt:             if (!newType.Methods.Any(v => v.Name == "OnInit"))