package me.km.utils;

import cpw.mods.modlauncher.api.INameMappingService;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Map;
import java.util.Set;
import java.util.function.Function;
import net.minecraft.command.Commands;
import net.minecraft.entity.Entity;
import net.minecraft.entity.ai.goal.GoalSelector;
import net.minecraft.entity.ai.goal.PrioritizedGoal;
import net.minecraft.entity.item.ItemEntity;
import net.minecraft.entity.player.PlayerAbilities;
import net.minecraft.entity.player.ServerPlayerEntity;
import net.minecraft.server.MinecraftServer;
import net.minecraft.server.management.PlayerInteractionManager;
import net.minecraft.server.management.PlayerList;
import net.minecraft.util.FoodStats;
import net.minecraft.util.ResourceLocation;
import net.minecraft.world.Explosion;
import net.minecraft.world.GameRules;
import net.minecraft.world.GameType;
import net.minecraft.world.IWorld;
import net.minecraft.world.World;
import net.minecraft.world.biome.Biome;
import net.minecraft.world.biome.provider.BiomeProvider;
import net.minecraft.world.biome.provider.BiomeProviderType;
import net.minecraft.world.biome.provider.IBiomeProviderSettings;
import net.minecraft.world.gen.feature.IFeatureConfig;
import net.minecraft.world.gen.feature.structure.Structure;
import net.minecraft.world.storage.WorldInfo;
import net.minecraftforge.common.DimensionManager;
import net.minecraftforge.fml.common.ObfuscationReflectionHelper;
import static net.minecraftforge.fml.common.ObfuscationReflectionHelper.remapName;
import org.apache.logging.log4j.LogManager;

public class ReflectionUtils {
    // -----------------------------------------------------------------------------------
    // helper stuff
    // -----------------------------------------------------------------------------------

    public static Method getMethod(Class c, String name, Class... pars) {
        try {
            return ObfuscationReflectionHelper.findMethod(c, name, pars);
        } catch(Exception ex) {
            LogManager.getLogger().warn(name + " - " + ex);
        }
        return null;
    }

    public static Field getField(Class c, String field) {
        try {
            Field f = c.getDeclaredField(remapName(INameMappingService.Domain.FIELD, field));
            f.setAccessible(true);
            return f;
        } catch(Exception ex) {
            LogManager.getLogger().warn(field + " - " + ex);
        }
        return null;
    }

    private static <T> void setInt(T t, Field f, int i) {
        try {
            f.setInt(t, i);
        } catch(SecurityException | IllegalArgumentException | IllegalAccessException | NullPointerException ex) {
            LogManager.getLogger().warn(f + " - " + ex);
        }
    }

    private static <T> int getInt(T t, Field f, int error) {
        try {
            return f.getInt(t);
        } catch(SecurityException | IllegalArgumentException | IllegalAccessException | NullPointerException ex) {
            LogManager.getLogger().warn(f + " - " + ex);
            return error;
        }
    }

    private static <T> void setFloat(T t, Field f, float fl) {
        try {
            f.setFloat(t, fl);
        } catch(SecurityException | IllegalArgumentException | IllegalAccessException | NullPointerException ex) {
            LogManager.getLogger().warn(f + " - " + ex);
        }
    }

    private static <T> float getFloat(T t, Field f, float error) {
        try {
            return f.getFloat(t);
        } catch(SecurityException | IllegalArgumentException | IllegalAccessException | NullPointerException ex) {
            LogManager.getLogger().warn(f + " - " + ex);
            return error;
        }
    }

    private static <T> boolean getBoolean(T t, Field f, boolean error) {
        try {
            return f.getBoolean(t);
        } catch(SecurityException | IllegalArgumentException | IllegalAccessException | NullPointerException ex) {
            LogManager.getLogger().warn(f + " - " + ex);
            return error;
        }
    }

    public static <T> T getFieldValue(Class<T> cast, Object o, Field f) {
        try {
            return (T) f.get(o);
        } catch(SecurityException | IllegalAccessException | IllegalArgumentException ex) {
            LogManager.getLogger().warn(f + " - " + ex);
            return null;
        }
    }

    public static void setFieldValue(Object instance, Field f, Object value) {
        try {
            f.set(instance, value);
        } catch(SecurityException | IllegalAccessException | IllegalArgumentException ex) {
            LogManager.getLogger().warn(f + " - " + ex);
        }
    }

    public static <T> T getFieldValue(Class<T> cast, Class c, Object instance, String field) {
        Field f = getField(c, field);
        if(f == null) {
            return null;
        }
        return getFieldValue(cast, instance, f);
    }

    // -----------------------------------------------------------------------------------
    // food stats
    // -----------------------------------------------------------------------------------
    private final static Field FOOD_EXHAUSTION_LEVEL = getField(FoodStats.class, "field_75126_c"); // foodExhaustionLevel

    public static void setExhaustion(FoodStats stats, float f) {
        setFloat(stats, FOOD_EXHAUSTION_LEVEL, f);
    }

    public static float getExhaustion(FoodStats stats) {
        return getFloat(stats, FOOD_EXHAUSTION_LEVEL, 0);
    }

    private final static Field FOOD_SATURATION_LEVEL = getField(FoodStats.class, "field_75125_b"); // foodSaturationLevel

    public static void setSaturation(FoodStats stats, float f) {
        setFloat(stats, FOOD_SATURATION_LEVEL, f);
    }

    // -----------------------------------------------------------------------------------
    // player stats
    // -----------------------------------------------------------------------------------
    private final static Field FLY_SPEED = getField(PlayerAbilities.class, "field_75096_f"); // flySpeed

    public static void setFlySpeed(PlayerAbilities cap, float f) {
        setFloat(cap, FLY_SPEED, f);
    }

    private final static Field WALK_SPEED = getField(PlayerAbilities.class, "field_75097_g"); // walkSpeed

    public static void setWalkSpeed(PlayerAbilities cap, float f) {
        setFloat(cap, WALK_SPEED, f);
    }

    // -----------------------------------------------------------------------------------
    // PlayerList
    // -----------------------------------------------------------------------------------
    private final static Method SET_GAMETYPE = getMethod(PlayerList.class, "func_72381_a",
            ServerPlayerEntity.class, ServerPlayerEntity.class, IWorld.class); // setPlayerGameTypeBasedOnOther

    public static void setPlayerGameTypeBasedOnOther(PlayerList pl, ServerPlayerEntity target, ServerPlayerEntity source, IWorld worldIn) {
        try {
            SET_GAMETYPE.invoke(pl, target, source, worldIn);
        } catch(IllegalAccessException | IllegalArgumentException | InvocationTargetException ex) {
            LogManager.getLogger().warn("setPlayerGameTypeBasedOnOther - " + ex);
        }
    }

    // -----------------------------------------------------------------------------------
    // minecraft server 
    // -----------------------------------------------------------------------------------
    private final static Field COMMAND_MANAGER = getField(MinecraftServer.class, "field_195579_af"); // commandManager

    public static void setCommandManager(MinecraftServer server, Commands manager) {
        setFieldValue(server, COMMAND_MANAGER, manager);
    }

    // -----------------------------------------------------------------------------------
    // DimensionManager
    // -----------------------------------------------------------------------------------
    private final static Field SAVED_ENTRIES = getField(DimensionManager.class, "savedEntries"); // forge name

    public static void removeDimensionSaveEntry(ResourceLocation rl) {
        // the map seems to be always empty ...
        getFieldValue(Map.class, null, SAVED_ENTRIES).remove(rl);
    }

    // -----------------------------------------------------------------------------------
    // explosion stuff
    // -----------------------------------------------------------------------------------
    private final static Field MODE = getField(Explosion.class, "field_222260_b"); // mode
    private final static Field EXPLODER = getField(Explosion.class, "field_77283_e"); // exploder
    private final static Field SIZE = getField(Explosion.class, "field_77280_f"); // size

    public static void setNoBreakMode(Explosion ex) {
        Explosion.Mode mode = getFieldValue(Explosion.Mode.class, ex, MODE);
        if(mode == Explosion.Mode.DESTROY) {
            setFieldValue(ex, MODE, Explosion.Mode.BREAK);
        }
    }

    public static Entity getExploder(Explosion ex) {
        return getFieldValue(Entity.class, ex, EXPLODER);
    }

    public static float getSize(Explosion ex) {
        return getFloat(ex, SIZE, 1);
    }

    // -----------------------------------------------------------------------------------
    // world info
    // -----------------------------------------------------------------------------------
    private final static Field WORLD_INFO = getField(World.class, "field_72986_A"); // worldInfo

    public static void setWorldInfo(World w, WorldInfo info) {
        setFieldValue(w, WORLD_INFO, info);
    }

    // -----------------------------------------------------------------------------------
    // gamerule integer value
    // -----------------------------------------------------------------------------------
    private final static Field INTEGER_VALUE = getField(GameRules.IntegerValue.class, "field_223566_a"); // value

    public static void setIntegerValue(GameRules.IntegerValue rule, int value) {
        setInt(rule, INTEGER_VALUE, value);
    }

    public static <C extends IBiomeProviderSettings, T extends BiomeProvider> BiomeProviderType
            newBiomeProviderType(Function<C, T> factory, Function<WorldInfo, C> settingsFactory) {
        try {
            Constructor<BiomeProviderType> con = (Constructor<BiomeProviderType>) BiomeProviderType.class.getDeclaredConstructors()[0];
            con.setAccessible(true);
            return con.newInstance(factory, settingsFactory);
        } catch(Exception ex) {
            return null;
        }
    }

    // -----------------------------------------------------------------------------------
    // game type setter without update for dirty hack
    // -----------------------------------------------------------------------------------
    private final static Field GAME_TYPE = getField(PlayerInteractionManager.class, "field_73091_c");

    public static void setGameType(PlayerInteractionManager m, GameType type) {
        setFieldValue(m, GAME_TYPE, type);
    }
    
    // -----------------------------------------------------------------------------------
    // biome map
    // -----------------------------------------------------------------------------------
    private final static Field BIOME_STRUCTURE = getField(Biome.class, "field_201874_aj");

    public static Map<Structure<?>, IFeatureConfig> getBiomeStructures(Biome b) {
        return getFieldValue(Map.class, b, BIOME_STRUCTURE);
    }
    
    // -----------------------------------------------------------------------------------
    // for clearing ai
    // -----------------------------------------------------------------------------------
    private final static Field GOALS = getField(GoalSelector.class, "field_220892_d");

    public static Set<PrioritizedGoal> getGoals(GoalSelector gs) {
        return getFieldValue(Set.class, gs, GOALS);
    }
}
