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.Type;
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 LOTRDimensionAdder implements IClassTransformer {
    public static List<DimensionInfo> custom_dimensions = new ArrayList<>();

    // This is where you add new dimensions
    public void registerDimensions() {
        System.out.println("Registering dimensions");
        custom_dimensions = new ArrayList<>();
        //register("ISLAND", "Island", 102, CinderWorldProviderIsland.class, 100);
    }

    // The ASM code you shouldn't touch
    @Override
    public byte[] transform(String name, String transformedName, byte[] basicClass) {
        if ("lotr.common.LOTRDimension".equals(transformedName)) {
            registerDimensions();
            if (custom_dimensions.isEmpty()) return basicClass;

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


            // Add the new enum constant
            for (DimensionInfo dimension : custom_dimensions) {
                FieldNode newEnumConstant = new FieldNode(
                        Opcodes.ACC_PUBLIC + Opcodes.ACC_STATIC + Opcodes.ACC_FINAL + Opcodes.ACC_ENUM,
                        dimension.enum_name,
                        "Llotr/common/LOTRDimension;",
                        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 (AbstractInsnNode insn : clinit.instructions.toArray()) {
                if (insn.getOpcode() == Opcodes.ICONST_2) {
                    constructor_injection_point = insn;
                    int list_size = 2 + custom_dimensions.size();
                    IntInsnNode push_size = new IntInsnNode(Opcodes.BIPUSH, 2 + custom_dimensions.size());
                    clinit.instructions.insert(insn, push_size);
                    clinit.instructions.remove(insn);
                    constructor_injection_point = push_size;
                    System.out.println("LOTRDimension list size: " + list_size);
                    break;
                }
            }


            // Create the constructor instructions to add new dimension
            InsnList constructor_injection = new InsnList();
            int i = 2;
            for (DimensionInfo dimension : custom_dimensions) {
                System.out.println("Registering with enum " + dimension.enum_name);
                System.out.println("Registering with ordinal " + i);
                System.out.println("Registering with name " + dimension.dimension_name);
                System.out.println("Registering with ID " + dimension.dimension_id);

                constructor_injection.add(new TypeInsnNode(Opcodes.NEW, "lotr/common/LOTRDimension"));
                constructor_injection.add(new InsnNode(Opcodes.DUP));
                constructor_injection.add(new LdcInsnNode(dimension.enum_name));
                constructor_injection.add(new IntInsnNode(Opcodes.BIPUSH, i));
                constructor_injection.add(new LdcInsnNode(dimension.dimension_name));
                constructor_injection.add(new IntInsnNode(Opcodes.BIPUSH, dimension.dimension_id));
                constructor_injection.add(new LdcInsnNode(Type.getType(dimension.world_provider))); // World provider class
                constructor_injection.add(new InsnNode(Opcodes.ICONST_0)); // Do not load spawn persistently
                constructor_injection.add(new IntInsnNode(Opcodes.BIPUSH, dimension.spawn_limit)); // Set spawn cap for dimension

                // Add no DimensionRegions
                constructor_injection.add(new LdcInsnNode(Type.getType("Llotr/common/LOTRDimension$DimensionRegion;")));
                constructor_injection.add(new MethodInsnNode(Opcodes.INVOKESTATIC, "java/util/EnumSet", "noneOf", "(Ljava/lang/Class;)Ljava/util/EnumSet;", false));

                // Add PUTSTATIC
                constructor_injection.add(new MethodInsnNode(Opcodes.INVOKESPECIAL, "lotr/common/LOTRDimension", "<init>", "(Ljava/lang/String;ILjava/lang/String;ILjava/lang/Class;ZILjava/util/EnumSet;)V", false));
                constructor_injection.add(new FieldInsnNode(Opcodes.PUTSTATIC, "lotr/common/LOTRDimension", dimension.enum_name, "Llotr/common/LOTRDimension;"));
                i++;
            }
            // 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
            i = 2;
            for (DimensionInfo dimension : custom_dimensions) {
                values_array_injection.add(new InsnNode(Opcodes.DUP));
                values_array_injection.add(new IntInsnNode(Opcodes.BIPUSH, i));
                values_array_injection.add(new FieldInsnNode(Opcodes.GETSTATIC, "lotr/common/LOTRDimension", dimension.enum_name, "Llotr/common/LOTRDimension;"));
                values_array_injection.add(new InsnNode(Opcodes.AASTORE));
                i++;
            }

            // 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;

            AbstractInsnNode cursor = constructor_injection_point;
            while (cursor != null) {
                if (cursor.getOpcode() == Opcodes.PUTSTATIC) {
                    values_injection_point = cursor;
                    break;
                }
                cursor = cursor.getNext();
            }
            if (values_injection_point == null) {
                throw new RuntimeException("[CinderLoE] Could not locate $VALUES PUTSTATIC injection point for LOTRDimension!");
            }

            // 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 dimension_name, int dimension_id, Class<?> world_provider, int spawn_limit) {
        System.out.println("Registering dimension " + enum_name + " " + dimension_name + " with ID " + dimension_id);
        custom_dimensions.add(new DimensionInfo(enum_name, dimension_name, dimension_id, world_provider, spawn_limit));
    }

    public class DimensionInfo {
        String enum_name;
        String dimension_name;
        int dimension_id;
        Class<?> world_provider;
        int spawn_limit;

        public DimensionInfo(String enum_name, String dimension_name, int dimension_id, Class<?> world_provider, int spawn_limit) {
            this.enum_name = enum_name;
            this.dimension_name = dimension_name;
            this.dimension_id = dimension_id;
            this.world_provider = world_provider;
            this.spawn_limit = spawn_limit;
        }
    }

}

/* Reference


L0:     new lotr/common/LOTRDimension
L3:     dup
L4:     ldc 'MIDDLE_EARTH'
L6:     iconst_0
L7:     ldc 'MiddleEarth'
L9:     bipush 100
L11:    ldc Class lotr/common/world/LOTRWorldProviderMiddleEarth
L13:    iconst_1
L14:    bipush 100
L16:    getstatic Field lotr/common/LOTRDimension$DimensionRegion WEST Llotr/common/LOTRDimension$DimensionRegion;
L19:    getstatic Field lotr/common/LOTRDimension$DimensionRegion EAST Llotr/common/LOTRDimension$DimensionRegion;
L22:    getstatic Field lotr/common/LOTRDimension$DimensionRegion SOUTH Llotr/common/LOTRDimension$DimensionRegion;
L25:    invokestatic Method java/util/EnumSet of (Ljava/lang/Enum;Ljava/lang/Enum;Ljava/lang/Enum;)Ljava/util/EnumSet;
L28:    invokespecial Method lotr/common/LOTRDimension <init> (Ljava/lang/String;ILjava/lang/String;ILjava/lang/Class;ZILjava/util/EnumSet;)V
L31:    putstatic Field lotr/common/LOTRDimension MIDDLE_EARTH Llotr/common/LOTRDimension;
L34:    new lotr/common/LOTRDimension

*/
