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

namespace COM3D2.CacheEditMenu.Patcher
{
    public static class Patcher
    {
        public static readonly string[] TargetAssemblyNames = { "Assembly-CSharp.dll" };

        public static readonly string SybarisPath = Path.GetDirectoryName(typeof(Patcher).Assembly.Location);
        
        public static void Patch(AssemblyDefinition ad)
        {
            var hookAd = AssemblyDefinition.ReadAssembly(
                typeof(Patcher).Assembly.GetManifestResourceStream(
                    "COM3D2.CacheEditMenu.Patcher.COM3D2.CacheEditMenu.dll"));

            var sceneEdit = ad.MainModule.GetType("SceneEdit");
            var getMenuItemSetUp = sceneEdit.Methods.FirstOrDefault(m => m.Name == "GetMenuItemSetUP");

            var hookType = hookAd.MainModule.GetType("COM3D2.CacheEditMenu.Hooks");
            var getMenuItemSetUpPrefix = hookType.Methods.FirstOrDefault(m => m.Name == "Prefix");
            var getMenuItemSetUpPostfix = hookType.Methods.FirstOrDefault(m => m.Name == "Postfix");

            var il = getMenuItemSetUp.Body.GetILProcessor();
            var resultVar = new VariableDefinition(ad.MainModule.ImportReference(typeof(bool)));
            getMenuItemSetUp.Body.Variables.Add(resultVar);
            var ins = getMenuItemSetUp.Body.Instructions[0];
            
            il.InsertBefore(ins, il.Create(OpCodes.Ldloca, resultVar));
            il.InsertBefore(ins, il.Create(OpCodes.Ldarg_0));
            il.InsertBefore(ins, il.Create(OpCodes.Ldarg_1));
            il.InsertBefore(ins, il.Create(OpCodes.Call, ad.MainModule.ImportReference(getMenuItemSetUpPrefix)));
            il.InsertBefore(ins, il.Create(OpCodes.Brtrue, ins));
            il.InsertBefore(ins, il.Create(OpCodes.Ldloc, resultVar));
            il.InsertBefore(ins, il.Create(OpCodes.Ret));
            
            ins = il.Create(OpCodes.Nop);
            
            foreach (var bodyInstruction in getMenuItemSetUp.Body.Instructions.Where(bodyInstruction => bodyInstruction.OpCode == OpCodes.Ret).Skip(1))
            {
                bodyInstruction.OpCode = OpCodes.Br;
                bodyInstruction.Operand = ins;
            }
            
            il.Append(ins);
            ins = il.Create(OpCodes.Ret);
            il.Append(ins);
            
            il.InsertBefore(ins, il.Create(OpCodes.Ldarg_0));
            il.InsertBefore(ins, il.Create(OpCodes.Ldarg_1));
            il.InsertBefore(ins, il.Create(OpCodes.Call, ad.MainModule.ImportReference(getMenuItemSetUpPostfix)));
            

            var buffer = new byte[4096];
            using(var s = typeof(Patcher).Assembly.GetManifestResourceStream(
                "COM3D2.CacheEditMenu.Patcher.COM3D2.CacheEditMenu.dll"))
            using (var ms = new MemoryStream())
            {
                int read;
                while ((read = s.Read(buffer, 0, buffer.Length)) > 0)
                    ms.Write(buffer, 0, read);

                Assembly.Load(ms.ToArray());
            }
        }
    }
}