package com.zivilon.cinder_loe.coremod;

import net.minecraft.launchwrapper.IClassTransformer;
import org.objectweb.asm.ClassReader;
import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.tree.ClassNode;
import org.objectweb.asm.tree.FieldNode;
import org.objectweb.asm.tree.InsnList;
import org.objectweb.asm.tree.AbstractInsnNode;
import org.objectweb.asm.tree.IntInsnNode;
import org.objectweb.asm.tree.TypeInsnNode;
import org.objectweb.asm.tree.InsnNode;
import org.objectweb.asm.tree.LdcInsnNode;
import org.objectweb.asm.tree.FieldInsnNode;
import org.objectweb.asm.tree.MethodInsnNode;
import org.objectweb.asm.tree.MethodNode;

import java.util.List;
import java.util.ArrayList;

public class LOTRShieldAdder implements IClassTransformer {
    public static List<BannerInfo> custom_banners = new ArrayList<>();

    public void registerShields() {
        custom_banners = new ArrayList<>();
        // Arguments: enum name, texture name, ID, faction name
        register("RED_DWARF", "redDwarf", 42, "DURINS_FOLK");
//        register("TEST", "test", 43, "GONDOR");
    }

    @Override
    public byte[] transform(String name, String transformedName, byte[] basicClass) {
        if ("lotr.common.LOTRShields".equals(transformedName)) {
            registerShields();

            // Get class
            ClassReader classReader = new ClassReader(basicClass);
            ClassNode classNode = new ClassNode();
            classReader.accept(classNode, 0);


            // Add the new enum constant
            for (BannerInfo banner : custom_banners) {
                FieldNode newEnumConstant = new FieldNode(
                        Opcodes.ACC_PUBLIC + Opcodes.ACC_STATIC + Opcodes.ACC_FINAL + Opcodes.ACC_ENUM,
                        banner.enum_name,
                        "Llotr/common/LOTRShields;",
                        null,
                        null
                );
                classNode.fields.add(newEnumConstant);
            }


            // Locate <clinit>
            MethodNode clinit = null;
            for (MethodNode method : classNode.methods) {
                if ("<clinit>".equals(method.name)) {
                    clinit = method;
                    break;
                }
            }

            InsnList insns = clinit.instructions;
            AbstractInsnNode constructor_injection_point = null;

            for (int i = 0; i < insns.size(); i++) {
                AbstractInsnNode insn = insns.get(i);
                // Check if the instruction is a BIPUSH
                if (insn.getOpcode() == Opcodes.BIPUSH) {
                    IntInsnNode intInsn = (IntInsnNode) insn;
                    // Check if the operand is 42, indicating the size of the $VALUES array
                    if (intInsn.operand == 42) {
                        // Found the instruction to modify
                        constructor_injection_point = insn;
                        // Modify the operand from 42 to 43 to account for the new enum constant
                        intInsn.operand = 42 + custom_banners.size();
                        System.out.println("Enum array length set to " + intInsn.operand);
                        System.out.println("Banner list size: " + custom_banners.size());
                        break;
                    }
                }
            }

            // Create the constructor instructions to add new banner
            InsnList constructor_injection = new InsnList();
            for (BannerInfo banner : custom_banners) {
                constructor_injection.add(new TypeInsnNode(Opcodes.NEW, "lotr/common/item/LOTRItemBanner$BannerType"));
                constructor_injection.add(new InsnNode(Opcodes.DUP));
                System.out.println("Registering with enum " + banner.enum_name);
                constructor_injection.add(new LdcInsnNode(banner.enum_name));
                System.out.println("Registering with identifier " + banner.identifier);
                constructor_injection.add(new LdcInsnNode(banner.identifier));
                constructor_injection.add(new FieldInsnNode(Opcodes.GETSTATIC, "lotr/common/fac/LOTRFaction", banner.faction, "Llotr/common/fac/LOTRFaction;"));
                constructor_injection.add(new MethodInsnNode(Opcodes.INVOKESPECIAL, "lotr/common/item/LOTRItemBanner$BannerType", "<init>", "(Ljava/lang/String;IILjava/lang/String;Llotr/common/fac/LOTRFaction;)V", false));
                constructor_injection.add(new FieldInsnNode(Opcodes.PUTSTATIC, "lotr/common/item/LOTRItemBanner$BannerType", banner.enum_name, "Llotr/common/item/LOTRItemBanner$BannerType;"));
            }
            // Insert the new instructions
            if (constructor_injection_point != null) {
                insns.insertBefore(constructor_injection_point, constructor_injection);
            }


            // Modifying the $VALUES array
            // Create new instruction list to be injected later
            InsnList values_array_injection = new InsnList();

            // Add instructions to the instruction list
            for (BannerInfo banner : custom_banners) {
                values_array_injection.add(new InsnNode(Opcodes.DUP));
                values_array_injection.add(new FieldInsnNode(Opcodes.GETSTATIC, "lotr/common/item/LOTRItemBanner$BannerType", banner.enum_name, "Llotr/common/item/LOTRItemBanner$BannerType;"));
                values_array_injection.add(new InsnNode(Opcodes.AASTORE));
            }

            // Find the putstatic instruction for $VALUES
            // This is where the fields are injected into a list, we want to inject our instructions before this
            AbstractInsnNode values_injection_point = null;

            while (constructor_injection_point != null) {
                constructor_injection_point = constructor_injection_point.getNext();
                if (constructor_injection_point.getOpcode() == Opcodes.PUTSTATIC) {
                    values_injection_point = constructor_injection_point;
                    break;
                }
            }

            // Insert the new instructions before the putstatic instruction
            if (values_injection_point != null) {
                insns.insertBefore(values_injection_point, values_array_injection);
            }

            // Write the modified class back to a byte array
            ClassWriter classWriter = new ClassWriter(ClassWriter.COMPUTE_FRAMES | ClassWriter.COMPUTE_MAXS);
            classNode.accept(classWriter);
            return classWriter.toByteArray(); // Return the modified class
        }
        return basicClass; // Return the unmodified class for all other classes
    }

    public void register(String enum_name, String identifier, int ordinal, String faction) {
        System.out.println("Registering banner " + enum_name + " " + identifier);
        custom_banners.add(new BannerInfo(enum_name, identifier, ordinal, faction));
    }

    public class BannerInfo {
        String enum_name;
        String identifier;
        int ordinal;
        String faction;

        public BannerInfo(String enum_name, String identifier, int ordinal, String faction) {
            this.enum_name = enum_name;
            this.identifier = identifier;
            this.ordinal = ordinal;
            this.faction = faction;
        }
    }

}
