package joshie.mariculture.modules;
import com.google.common.collect.Lists;
import joshie.mariculture.Mariculture;
import joshie.mariculture.api.MaricultureAPI;
import joshie.mariculture.core.helpers.ConfigHelper;
import joshie.mariculture.core.helpers.MCReflectionHelper;
import joshie.mariculture.core.util.annotation.MCApiImpl;
import joshie.mariculture.core.util.annotation.MCEvents;
import joshie.mariculture.core.util.annotation.MCLoader;
import net.minecraftforge.common.MinecraftForge;
import net.minecraftforge.common.config.Configuration;
import net.minecraftforge.fml.common.Loader;
import net.minecraftforge.fml.common.discovery.ASMDataTable;
import net.minecraftforge.fml.common.discovery.ASMDataTable.ASMData;
import net.minecraftforge.fml.common.discovery.asm.ModAnnotation;
import net.minecraftforge.fml.relauncher.ReflectionHelper;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.text.WordUtils;
import org.apache.commons.lang3.tuple.Triple;
import org.apache.logging.log4j.Level;
import javax.annotation.Nonnull;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.*;
import java.util.Map.Entry;
import static joshie.mariculture.core.lib.MaricultureInfo.MODNAME;
public abstract class ModuleManager {
public static HashMap<String, Class<?>> enabled = new HashMap<>();
public static void loadModules(@Nonnull ASMDataTable table, boolean isClient) {
HashMap<String, Triple<String, String, String>> moduleData = new HashMap<>();
Set<String> enabled = new HashSet<>();
initModules(table, enabled, moduleData);
initAPI(table, enabled);
initClasses(enabled, moduleData);
registerEventHandlers(table, isClient);
}
private static void initModules(@Nonnull ASMDataTable table, Set<String> enabled, HashMap<String, Triple<String, String, String>> moduleData) {
//Load in all the data for each module
String annotationClassName = MCLoader.class.getCanonicalName();
Set<ASMData> asmDatas = new HashSet<>(table.getAll(annotationClassName));
asmLoop:
for (ASMDataTable.ASMData asmData : asmDatas) {
try {
Map<String, Object> data = asmData.getAnnotationInfo();
String name = WordUtils.uncapitalize(asmData.getClassName().substring(asmData.getClassName().lastIndexOf('.') + 1).trim());
boolean isEnabledByDefault = !asmData.getClassName().contains("mariculture.modules.hardcore") && !asmData.getClassName().contains("mariculture.modules.debug");
name = asmData.getClassName().contains("mariculture.modules.hardcore") ? "hardcore-" + name : name;
String dependencies = data.get("modules") != null ? (String) data.get("modules") : "";
String type = "modules";
//Mod Check
String mods = data.get("mods") != null ? (String)data.get("mods") : "";
if (mods != null && !mods.equals("")) {
String[] modList = mods.replace(" ", "").split(",");
for (String mod: modList) {
if (!Loader.isModLoaded(mod)) continue asmLoop;
}
//If we have any mod requirements, we're a plugin
type = "plugins";
}
//If the module is enabled
if (isModuleEnabled(type, name, isEnabledByDefault)) {
enabled.add(name);
moduleData.put(name, Triple.of(asmData.getClassName(), type, dependencies));
}
} catch (Exception e) { e.printStackTrace(); }
}
}
private static void initAPI(@Nonnull ASMDataTable table, Set<String> enabled) {
Set<ASMData> datas = new HashSet<>(table.getAll(MCApiImpl.class.getCanonicalName()));
for (ASMDataTable.ASMData data : datas) {
try {
Map<String, Object> theData = data.getAnnotationInfo();
String modules = theData.get("value") != null ? (String) theData.get("value") : "";
List<String> dependencies = Lists.newArrayList(modules.replace(" ", "").split(","));
if (modules.equals("") || enabled.containsAll(dependencies)) {
//Load in the api, after we've checked if the required module is loaded
Class clazz = Class.forName(data.getClassName());
Object instance = clazz.getField("INSTANCE").get(null);
Class[] interfaces = clazz.getInterfaces();
if (interfaces != null && interfaces.length > 0) {
for (Class inter : interfaces) {
for (Field f : MaricultureAPI.class.getFields()) {
if (f.getType().equals(inter)) {
MCReflectionHelper.setFinalStatic(instance, f);
}
}
}
}
}
} catch (Exception e) { e.printStackTrace(); }
}
}
private static void initClasses(Set<String> enabled, HashMap<String, Triple<String, String, String>> moduleData) {
for (String module: enabled) {
Triple<String, String, String> data = moduleData.get(module);
String dependencyList = data.getRight().replace(" ", "");
List<String> dependencies = Lists.newArrayList(dependencyList.split(","));
if (dependencyList.equals("") || enabled.containsAll(dependencies)) {
try {
ModuleManager.enabled.put(module, Class.forName(data.getLeft()));
Mariculture.logger.log(Level.INFO, "Enabling the "
+ StringUtils.join(StringUtils.splitByCharacterTypeCamelCase(WordUtils.capitalize(module)), ' ')
+ " " + data.getMiddle().replace("s", "") + "!");
} catch (ClassNotFoundException ex) { ex.printStackTrace(); }
}
}
}
private static void registerEventHandlers(@Nonnull ASMDataTable asmDataTable, boolean isClient) {
String annotationClassName = MCEvents.class.getCanonicalName();
Set<ASMData> asmDatas = new HashSet<>(asmDataTable.getAll(annotationClassName));
for (ASMDataTable.ASMData asmData : asmDatas) {
try {
Map<String, Object> data = asmData.getAnnotationInfo();
String side = data.get("value") != null ? ReflectionHelper.getPrivateValue(ModAnnotation.EnumHolder.class, (ModAnnotation.EnumHolder) data.get("value"), "value") : "";
String modules = data.get("modules") != null ? (String) data.get("modules") : "";
List<String> dependencies = Lists.newArrayList(modules.replace(" ", "").split(","));
if (modules.equals("") || enabled.keySet().containsAll(dependencies)) {
if ((side.equals("CLIENT") && isClient) || side.equals("")) {
Class clazz = Class.forName(asmData.getClassName());
Method register = MCReflectionHelper.getMethod(clazz, "register");
if (register == null || ((Boolean) register.invoke(null))) {
Field INSTANCE = MCReflectionHelper.getField(clazz, "INSTANCE");
if (INSTANCE == null) MinecraftForge.EVENT_BUS.register(clazz.newInstance());
else MinecraftForge.EVENT_BUS.register(INSTANCE.get(null));
}
}
}
} catch (ClassNotFoundException | IllegalAccessException | InvocationTargetException | InstantiationException ex) { ex.printStackTrace(); }
}
}
public static void loadConfigs() {
for (Entry<String, Class<?>> entry: enabled.entrySet()){
try {
Method configure = MCReflectionHelper.getMethod(entry.getValue(), "configure");
if (configure != null) {
Configuration config = ConfigHelper.getConfig();
ConfigHelper.setCategory(entry.getKey());
try {
config.load();
configure.invoke(null);
} finally {
config.save();
}
}
} catch (IllegalAccessException | InvocationTargetException ex) { /*Ignore errors! */ }
}
}
private static boolean isModuleEnabled(String type, String name, boolean isEnabledByDefault) {
Configuration config = ConfigHelper.getConfig();
try {
config.load();
return config.get(type, name, isEnabledByDefault).getBoolean(isEnabledByDefault);
} catch (Exception e) {
Mariculture.logger.log(Level.ERROR, MODNAME + " failed to load it's " + type.toLowerCase() + " config");
e.printStackTrace();
} finally {
config.save();
}
return false;
}
//Mostly used Externally
public static boolean isModuleEnabled(String name) {
return enabled.containsKey(name);
}
}