using System.Reflection;
using Mono.Cecil;
using Mono.Cecil.Cil;

public class Program
{
    public static void Main(string[] args)
    {
        string dllPath = ".";
        if (args.Length != 0)
        {
            Console.WriteLine(args[0]);
            dllPath = args[0];
        }
        if (dllPath.EndsWith("/"))
            dllPath = $"{dllPath}GodotTools.ProjectEditor.dll";
        else if (!dllPath.EndsWith("GodotTools.ProjectEditor.dll"))
            dllPath = $"{dllPath}/GodotTools.ProjectEditor.dll";
        
        if (!File.Exists(dllPath))
        {
            Console.WriteLine(dllPath);
            Console.WriteLine("GodotTools.ProjectEditor.dll not found");
            return;
        }

        string backupPath = $"{dllPath}.bak";
        if (!File.Exists(backupPath))
            File.Copy(dllPath, backupPath);
        string? version = Assembly.GetEntryAssembly()?
            .GetCustomAttribute<AssemblyInformationalVersionAttribute>()?
            .InformationalVersion;
        AssemblyDefinition assembly = AssemblyDefinition.ReadAssembly(dllPath, new ReaderParameters { });
        TypeDefinition targetType = assembly.MainModule.Types.First(t => t.Name == "ProjectUtils");
        MethodDefinition method = targetType.Methods.First(m => m.Name == "EnsureGodotSdkIsUpToDate");
        
        
        TypeReference? stringType = assembly.MainModule.ImportReference(typeof(string));
        VariableDefinition? root = method.Body.Variables.FirstOrDefault(t => t.VariableType.FullName == "Microsoft.Build.Construction.ProjectRootElement");
        
        
        TypeDefinition msBuildProjectTypeDef = assembly.MainModule.GetType("GodotTools.ProjectEditor.MSBuildProject");
        TypeDefinition projectGeneratorTypeDef = assembly.MainModule.GetType("GodotTools.ProjectEditor.ProjectGenerator");
        
        MethodDefinition? msBuildProjectGetRoot = msBuildProjectTypeDef.Methods
            .FirstOrDefault(m => m.Name == "get_Root");
        MethodDefinition? projectGeneratorGetGodotSdk = projectGeneratorTypeDef.Methods
            .FirstOrDefault(m => m.Name == "get_GodotSdkAttrValue");

        MethodDefinition? msBuildProjectSetHasUnsavedChanges  = msBuildProjectTypeDef.Methods
            .FirstOrDefault(m => m.Name == "set_HasUnsavedChanges");
        
        
        MethodReference? preGetSdk = method.Body.Instructions
            .Select(i => i.Operand)
            .OfType<MethodReference>()
            .FirstOrDefault(m => m.Name == "get_Sdk");
        
        
        MethodReference? preSetSdk = method.Body.Instructions
            .Select(i => i.Operand)
            .OfType<MethodReference>()
            .FirstOrDefault(m => m.Name == "set_Sdk");


        method.Body.Instructions.Clear();
        method.Body.ExceptionHandlers.Clear();
        method.Body.Variables.Clear();
        ILProcessor il = method.Body.GetILProcessor();

        
        //VariableDefinition root = new VariableDefinition(projectRootElementTypeDef);
        method.Body.Variables.Add(root);
        VariableDefinition godotSdk = new VariableDefinition(stringType);
        method.Body.Variables.Add(godotSdk);
        method.Body.InitLocals = true;
        il.Append(il.Create(OpCodes.Ldarg_0));
        il.Append(il.Create(OpCodes.Callvirt, msBuildProjectGetRoot));
        il.Append(il.Create(OpCodes.Stloc_0));
        
        il.Append(il.Create(OpCodes.Call, projectGeneratorGetGodotSdk));
        il.Append(il.Create(OpCodes.Stloc_1));
        
        il.Append(il.Create(OpCodes.Ldloc_0));
        il.Append(il.Create(OpCodes.Callvirt, preGetSdk));


        il.Append(il.Create(OpCodes.Call, method.Module
                .ImportReference(
                    typeof(string).GetMethod("IsNullOrEmpty", [typeof(string)]))
            )
        );

        Instruction setSdk = il.Create(OpCodes.Nop);
        il.Append(il.Create(OpCodes.Brtrue_S, setSdk));
        
        il.Append(il.Create(OpCodes.Ldloc_0));
        il.Append(il.Create(OpCodes.Callvirt, preGetSdk));
        il.Append(il.Create(OpCodes.Callvirt, method.Module
            .ImportReference(typeof(string).GetMethod("Trim", Type.EmptyTypes)))
        );
        il.Append(il.Create(OpCodes.Ldloc_1));
        il.Append(il.Create(OpCodes.Ldc_I4_5));
        il.Append(il.Create(OpCodes.Callvirt, method.Module
                .ImportReference(
                    typeof(string).GetMethod("Equals", [typeof(string), typeof(StringComparison)]))
            )
        );
        
        Instruction notMatch = il.Create(OpCodes.Nop);
        il.Append(il.Create(OpCodes.Brfalse_S, notMatch));
        il.Append(il.Create(OpCodes.Ret));
        il.Append(notMatch);
        
        il.Append(il.Create(OpCodes.Ldloc_0));
        il.Append(il.Create(OpCodes.Callvirt, preGetSdk));
        il.Append(il.Create(OpCodes.Callvirt, method.Module
            .ImportReference(typeof(string).GetMethod("Trim", Type.EmptyTypes)))
        );
        il.Append(il.Create(OpCodes.Ldstr, $"Polonium.Sdk/{version}"));
        il.Append(il.Create(OpCodes.Ldc_I4_5));
        il.Append(il.Create(OpCodes.Callvirt, method.Module
                .ImportReference(
                    typeof(string).GetMethod("Equals", [typeof(string), typeof(StringComparison)]))
            )
        );
        il.Append(il.Create(OpCodes.Brfalse_S, setSdk));
        il.Append(il.Create(OpCodes.Ret));
        il.Append(setSdk);
        il.Append(il.Create(OpCodes.Ldloc_0));
        il.Append(il.Create(OpCodes.Ldloc_1));
        il.Append(il.Create(OpCodes.Callvirt, preSetSdk));
        
        il.Append(il.Create(OpCodes.Ldarg_0));
        il.Append(il.Create(OpCodes.Ldc_I4_1));
        il.Append(il.Create(OpCodes.Callvirt, msBuildProjectSetHasUnsavedChanges));
        
        il.Append(il.Create(OpCodes.Ret));
        
        assembly.Write($"{dllPath}.2.dll");
        File.Replace($"{dllPath}.2.dll", dllPath, null);
    }
}
