package com.enderio.core.common.transform;

import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;

import javax.annotation.Nonnull;

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.objectweb.asm.Type;

import com.google.common.base.MoreObjects;

import net.minecraftforge.fml.common.Loader;
import net.minecraftforge.fml.common.ModAPIManager;
import net.minecraftforge.fml.common.discovery.ASMDataTable;
import net.minecraftforge.fml.common.discovery.ASMDataTable.ASMData;
import net.minecraftforge.fml.common.discovery.ModDiscoverer;
import net.minecraftforge.fml.relauncher.IFMLLoadingPlugin;
import net.minecraftforge.fml.relauncher.IFMLLoadingPlugin.MCVersion;

@MCVersion("1.12.2")
//we want deobf no matter what
@IFMLLoadingPlugin.SortingIndex(Integer.MAX_VALUE)
public class EnderCorePlugin implements IFMLLoadingPlugin {
  
  static final Logger mainLogger = LogManager.getLogger("EnderCore ASM");
  static final Logger mixinLogger = LogManager.getLogger("EnderCore Mixins");

  public static boolean runtimeDeobfEnabled = false;
  
  private static EnderCorePlugin instance;
  
  static class MixinData {
    public final String source, target;

    MixinData(String source, String target) {
      this.source = source;
      this.target = target;
    }
    
    void initialize() {
      try {
        Class<?> cls = Class.forName(source);
        cls.getName(); // Make sure it's loaded
        cls = Class.forName(target);
        cls.getName();
      } catch (ClassNotFoundException e) {
        throw new RuntimeException(e);
      }
    }
  }
  
  List<MixinData> mixins = new ArrayList<>();
  
  public EnderCorePlugin() {
    if (instance != null) {
      throw new IllegalStateException("EnderCorePlugin was instantiated twice?");
    }
    instance = this;
  }
  
  @Nonnull
  public static EnderCorePlugin instance() {
    EnderCorePlugin inst = instance;
    if (inst == null) {
      throw new IllegalStateException("EnderCorePlugin accessed too early!");
    }
    return inst;
  }
  
  public void addMixin(String source, String target) {
    addMixin(new MixinData(source, target));
  }
  
  private void addMixin(MixinData mixin) {
    mixins.add(mixin);
    mixin.initialize();
  }
  
  public void loadMixinSources() {
    List<MixinData> fromAnnotations = findAnnotationMixins();
    fromAnnotations.forEach(this::addMixin);
  }
  
  private List<MixinData> findAnnotationMixins() {
    List<MixinData> ret = new ArrayList<>();
    try {
      Field fDiscoverer = Loader.class.getDeclaredField("discoverer");
      fDiscoverer.setAccessible(true);
      
      ModDiscoverer discoverer = (ModDiscoverer) fDiscoverer.get(Loader.instance());
      ASMDataTable asmData = discoverer.getASMTable();
      
      Set<ASMData> data = asmData.getAll(SimpleMixin.class.getName());
      for (ASMData d : data) {
        mixinLogger.info("Found annotation mixin: {}", d.getClassName());
        @SuppressWarnings("unchecked")
        List<String> dependencies = MoreObjects.firstNonNull((List<String>) d.getAnnotationInfo().get("dependencies"), Collections.emptyList());
        List<String> missingDependencies = dependencies.stream().filter(m -> !Loader.isModLoaded(m) && !ModAPIManager.INSTANCE.hasAPI(m)).collect(Collectors.toList());
        if (missingDependencies.size() == 0) {
          ret.add(new MixinData(d.getClassName(), ((Type) d.getAnnotationInfo().get("value")).getClassName()));
          mixinLogger.info("Registered mixin.");
        } else {
          mixinLogger.info("Skipping mixin due to missing dependencies: {}", missingDependencies);
        }
      }
    } catch (Exception e) {
      e.printStackTrace();
    }
    return ret;
  }

  @Override
  public String[] getASMTransformerClass() {
    return new String[] { 
        "com.enderio.core.common.transform.EnderCoreTransformer",
        "com.enderio.core.common.transform.SimpleMixinPatcher" };
  }

  @Override
  public String getModContainerClass() {
    return null;
  }

  @Override
  public String getSetupClass() {
    return null;
  }

  @Override
  public void injectData(Map<String, Object> data) {
    runtimeDeobfEnabled = (Boolean) data.get("runtimeDeobfuscationEnabled");
  }

  @Override
  public String getAccessTransformerClass() {
    return null;
  }
}

@@ -1467,16 +1467,28 @@
 tialize(
+String clazz
 ) %7B%0A    
@@ -1532,22 +1532,21 @@
 forName(
-source
+clazz
 );%0A     
@@ -1592,68 +1592,8 @@
 ded%0A
-        cls = Class.forName(target);%0A        cls.getName();%0A
     
@@ -2156,292 +2156,188 @@
 oid 
+lo
 a
-d
 dMixin
-(String source, String target) %7B%0A    addMixin(new MixinData(source, target));%0A  %7D%0A  %0A  private void addMixin(MixinData mixin) %7B%0A    mixins.add(mixin);%0A    mixin.initialize();%0A  %7D%0A  %0A  public void loadMixinSources() %7B%0A    List%3CMixinData%3E fromAnnotations = findAnnotationMixins(
+Sources() %7B%0A    List%3CMixinData%3E fromAnnotations = findAnnotationMixins();%0A    fromAnnotations.forEach(mixins::add);%0A    fromAnnotations.forEach(m -%3E m.initialize(m.source)
 );%0A 
@@ -2367,22 +2367,35 @@
 ach(
-this::addMixin
+m -%3E m.initialize(m.target)
 );%0A 

Make sure to classload all sources before targets