package com.enderio.core.common.transform;

import static org.objectweb.asm.Opcodes.ACC_ABSTRACT;

import java.util.HashMap;
import java.util.Map;
import java.util.stream.Collectors;

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.objectweb.asm.ClassReader;
import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.tree.ClassNode;

import com.enderio.core.common.transform.EnderCorePlugin.InterfacePatchData;

import net.minecraft.launchwrapper.IClassTransformer;

public class SimpleMixinPatcher implements IClassTransformer {
  
  public static final Logger logger = LogManager.getLogger();
  
  private final EnderCorePlugin plugin = EnderCorePlugin.instance();
  
  private final Map<String, byte[]> capturedSources = new HashMap<>();

  @Override
  public byte[] transform(String name, String transformedName, byte[] targetClass) {
    InterfacePatchData data = null;
    for (InterfacePatchData d : plugin.ifacePatches) {
      if (d.target.equals(transformedName)) {
        data = d;
        break;
      }
      if (d.source.equals(transformedName)) {
        capturedSources.put(transformedName, targetClass);
        break;
      }
    }
    if (data != null) {
      byte[] sourceClass = capturedSources.get(data.source);
      if (sourceClass == null) {
        logger.error("[EnderCore ASM] Skipping interface patch due to unloaded class: " + data.source);
      } else {
        ClassNode targetNode = new ClassNode();
        ClassReader targetReader = new ClassReader(targetClass);
        targetReader.accept(targetNode, 0);

        ClassNode sourceNode = new ClassNode();
        ClassReader sourceReader = new ClassReader(sourceClass);
        sourceReader.accept(sourceNode, 0);
        
        targetNode.interfaces.addAll(sourceNode.interfaces);
        targetNode.methods.addAll(sourceNode.methods.stream()
            .filter(m -> !m.name.equals("<init>"))
            .filter(m -> (m.access & ACC_ABSTRACT) == 0)
            .collect(Collectors.toList()));

        ClassWriter cw = new ClassWriter(0);
        targetNode.accept(cw);
        logger.info("Patched interfaces from {} onto {}.", data.source, data.target);
        return cw.toByteArray();
      }
    }
    return targetClass;
  }

}

@@ -73,55 +73,316 @@
 asm.
-Opcodes.ACC_ABSTRACT;%0A%0Aimport java.util.HashMap
+ClassWriter.COMPUTE_FRAMES;%0Aimport static org.objectweb.asm.ClassWriter.COMPUTE_MAXS;%0Aimport static org.objectweb.asm.Opcodes.ACC_ABSTRACT;%0Aimport static org.objectweb.asm.Opcodes.INVOKEINTERFACE;%0Aimport static org.objectweb.asm.Opcodes.INVOKEVIRTUAL;%0A%0Aimport java.util.HashMap;%0Aimport java.util.ListIterator
 ;%0Aim
@@ -628,21 +628,206 @@
 sm.tree.
-Class
+AbstractInsnNode;%0Aimport org.objectweb.asm.tree.ClassNode;%0Aimport org.objectweb.asm.tree.LocalVariableNode;%0Aimport org.objectweb.asm.tree.MethodInsnNode;%0Aimport org.objectweb.asm.tree.Method
 Node;%0A%0Ai
@@ -2215,60 +2215,1067 @@
     
-targetNode.interfaces.addAll(sourceNode.interfaces);
+for (MethodNode m : sourceNode.methods) %7B%0A          ListIterator%3CAbstractInsnNode%3E instructions = m.instructions.iterator();%0A          while (instructions.hasNext()) %7B%0A            AbstractInsnNode node = instructions.next();%0A            if (node instanceof MethodInsnNode) %7B%0A              MethodInsnNode call = (MethodInsnNode) node;%0A              if (call.owner.replace('/', '.').equals(data.source)) %7B%0A                call.owner = data.target.replace('.', '/');%0A                if (call.getOpcode() == INVOKEINTERFACE) %7B%0A                  call.setOpcode(INVOKEVIRTUAL);%0A                  call.itf = false;%0A                %7D%0A              %7D%0A            %7D%0A          %7D%0A          %0A          if (m.localVariables.size() %3E 0) %7B%0A            LocalVariableNode n = m.localVariables.get(0);%0A            n.desc = %22L%22 + data.target.replace('.', '/') + %22;%22;%0A          %7D%0A        %7D%0A        %0A        targetNode.interfaces.addAll(sourceNode.interfaces.stream()%0A            .filter(s -%3E !targetNode.interfaces.contains(s))%0A            .collect(Collectors.toList()));%0A        
 %0A   
@@ -3527,17 +3527,45 @@
 sWriter(
-0
+COMPUTE_MAXS %7C COMPUTE_FRAMES
 );%0A     

Fix issues with SimpleMixin and invocations of member methods