package ljfa.tntutils.asm;

import java.util.Iterator;

import net.minecraft.launchwrapper.IClassTransformer;

import org.apache.logging.log4j.Level;
import org.objectweb.asm.ClassReader;
import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.tree.AbstractInsnNode;
import org.objectweb.asm.tree.ClassNode;
import org.objectweb.asm.tree.InsnNode;
import org.objectweb.asm.tree.JumpInsnNode;
import org.objectweb.asm.tree.LabelNode;
import org.objectweb.asm.tree.MethodNode;

import cpw.mods.fml.common.FMLLog;

public class ExplosionTransformer implements IClassTransformer {
    @Override
    public byte[] transform(String name, String transformedName, byte[] basicClass) {
        if(name.equals("net.minecraft.world.Explosion")) {
            FMLLog.log("TNTUtils Core", Level.INFO, "About to patch class %s", name);
            return patchClassASM(name, basicClass, false);
        } else if(name.equals("agw")) {
            FMLLog.log("TNTUtils Core", Level.INFO, "About to patch obfuscated class %s", name);
            return patchClassASM(name, basicClass, true);
        } else
            return basicClass;
    }
    
    private byte[] patchClassASM(String name, byte[] basicClass, boolean obfuscated) {
        //ASM manipulation stuff
        ClassNode classNode = new ClassNode();
        ClassReader classReader = new ClassReader(basicClass);
        classReader.accept(classNode, 0);

        //Loop through the methods until we find our target
        for(MethodNode mn: classNode.methods) {
            if(mn.name.equals(obfuscated ? "func_77279_a" : "doExplosionB") && mn.desc.equals("(Z)V")) {
                FMLLog.log("TNTUtils Core", Level.INFO, "Found target method %s%s", mn.name, mn.desc);
                patchDoExplosionB(mn);
                break;
            }
        }

        //Write class
        ClassWriter writer = new ClassWriter(ClassWriter.COMPUTE_MAXS | ClassWriter.COMPUTE_FRAMES);
        classNode.accept(writer);
        return writer.toByteArray();
    }
    
    private void patchDoExplosionB(MethodNode mn) {
        //Loop through the instructions of the method
        Iterator<AbstractInsnNode> it = mn.instructions.iterator();
        boolean didInject = false;
        while(it.hasNext()) {
            AbstractInsnNode currentNode = it.next();
            /* In the Explosion class, line 221:
             * 
             * Currently, the division 1.0F / this.explosionSize for the drop chance is being performed.
             * We want to get rid of this and instead have 1.0F as drop chance.
             * 
             * So we search for the sequence "aload_0", "getfield", "fdiv" and skip over it.
             */
            //Search for "fdiv"
            if(currentNode.getOpcode() == Opcodes.FDIV) {
                InsnNode fdivNode = (InsnNode)currentNode;
                //Go one step back
                currentNode = currentNode.getPrevious();
                //Check if preceding instruction is "getfield"
                if(currentNode.getOpcode() == Opcodes.GETFIELD) {            
                    //Go another step back
                    currentNode = currentNode.getPrevious();
                    //Here should be a "aload_0"
                    if(currentNode.getOpcode() == Opcodes.ALOAD) {
                        FMLLog.log("TNTUtils Core", Level.INFO, "Found target instructions aload_0, getfield, fdiv");
                        
                        //Insert a label after "fdiv"
                        LabelNode label = new LabelNode();
                        mn.instructions.insert(fdivNode, label);
                        
                        //Insert a "goto" instruction before aload_0
                        mn.instructions.insertBefore(currentNode, new JumpInsnNode(Opcodes.GOTO, label));
                        
                        didInject = true;
                        break;
                    }
                }
            }
        }
        if(didInject)
            FMLLog.log("TNTUtils Core", Level.INFO, "Successfully injected into %s%s", mn.name, mn.desc);
        else
            FMLLog.log("TNTUtils Core", Level.ERROR, "Failed injection into %s%s", mn.name, mn.desc);
    }
}

@@ -253,16 +253,47 @@
 pcodes;%0A
+import org.objectweb.asm.Type;%0A
 import o
@@ -324,32 +324,32 @@
 stractInsnNode;%0A
-
 import org.objec
@@ -369,24 +369,64 @@
 .ClassNode;%0A
+import org.objectweb.asm.tree.InsnList;%0A
 import org.o
@@ -534,24 +534,24 @@
 .LabelNode;%0A
-
 import org.o
@@ -570,24 +570,113 @@
 .tree.Method
+InsnNode;%0Aimport org.objectweb.asm.tree.MethodNode;%0Aimport org.objectweb.asm.tree.VarInsn
 Node;%0A%0Aimpor
@@ -3287,20 +3287,17 @@
 /Go 
-another
+twp
  step
+s
  bac
@@ -3349,32 +3349,46 @@
 de.getPrevious()
+.getPrevious()
 ;%0A              
@@ -3413,23 +3413,24 @@
 d be a %22
-aload_0
+fconst_1
 %22%0A      
@@ -3485,13 +3485,16 @@
 des.
-ALOAD
+FCONST_1
 ) %7B%0A
@@ -3588,26 +3588,24 @@
 ons 
-aload_0, getfield,
+fconst_1 through
  fdi
@@ -3872,16 +3872,32 @@
 nsert a 
+hook call and a 
 %22goto%22 i
@@ -3914,23 +3914,24 @@
  before 
-aload_0
+fconst_1
 %0A       
@@ -3951,87 +3951,479 @@
     
-mn.instructions.insertBefore(currentNode, new JumpInsnNode(Opcodes.GOTO, label)
+InsnList toInject = new InsnList();%0A                        toInject.add(new VarInsnNode(Opcodes.ALOAD, 0));%0A                        toInject.add(new MethodInsnNode(Opcodes.INVOKESTATIC, Type.getInternalName(HooksExplosion.class), %22getDropChance%22, %22(Lnet/minecraft/world/Explosion;)F%22, false));%0A                        toInject.add(new JumpInsnNode(Opcodes.GOTO, label));%0A                        %0A                        mn.instructions.insertBefore(currentNode, toInject
 );%0A 

Insert hook call into explosion