package me.km.utils;

import cpw.mods.modlauncher.api.INameMappingService;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Set;
import net.minecraft.block.Block;
import net.minecraft.block.Blocks;
import net.minecraft.block.FireBlock;
import net.minecraft.command.Commands;
import net.minecraft.entity.ai.goal.GoalSelector;
import net.minecraft.entity.ai.goal.PrioritizedGoal;
import net.minecraft.entity.player.PlayerEntity;
import net.minecraft.entity.player.ServerPlayerEntity;
import net.minecraft.inventory.IInventory;
import net.minecraft.inventory.container.Container;
import net.minecraft.inventory.container.Slot;
import net.minecraft.item.ItemStack;
import net.minecraft.resources.DataPackRegistries;
import net.minecraft.server.MinecraftServer;
import net.minecraft.server.management.PlayerInteractionManager;
import net.minecraft.server.management.PlayerList;
import net.minecraft.tileentity.SignTileEntity;
import net.minecraft.util.FoodStats;
import net.minecraft.util.IIntArray;
import net.minecraft.util.IntReferenceHolder;
import net.minecraft.util.text.ITextComponent;
import net.minecraft.world.Explosion;
import net.minecraft.world.GameRules;
import net.minecraft.world.GameType;
import net.minecraft.world.World;
import net.minecraft.world.server.ServerWorld;
import net.minecraft.world.storage.DerivedWorldInfo;
import net.minecraft.world.storage.IServerConfiguration;
import net.minecraft.world.storage.IServerWorldInfo;
import net.minecraftforge.fml.common.ObfuscationReflectionHelper;
import static net.minecraftforge.fml.common.ObfuscationReflectionHelper.remapName;
import org.apache.logging.log4j.LogManager;

public class ReflectionUtils {
    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(Exception ex) {
            LogManager.getLogger().warn(f + " - " + ex);
        }
    }

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

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

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

    public static void setFieldValue(Object instance, Field f, Object value) {
        try {
            f.set(instance, value);
        } catch(Exception 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);
    }

    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);
    }

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

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

    private final static Field COMMAND_MANAGER =
            getField(DataPackRegistries.class, "field_240953_c_"); // commands

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

    private final static Field MODE = getField(Explosion.class, "field_222260_b"); // mode

    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);
        }
    }

    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);
    }

    // -----------------------------------------------------------------------------------
    // 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);
    }

    // -----------------------------------------------------------------------------------
    // for clearing ai
    // -----------------------------------------------------------------------------------
    private final static Field GOALS = getField(GoalSelector.class, "field_220892_d");

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

    private final static Method SET_FIRE_INFO =
            getMethod(FireBlock.class, "func_180686_a", Block.class, int.class, int.class);

    public static void setFireInfo(Block b, int encouragement, int flammability) {
        FireBlock fireblock = (FireBlock) Blocks.FIRE;
        try {
            SET_FIRE_INFO.invoke(fireblock, b, encouragement, flammability);
        } catch(IllegalAccessException | IllegalArgumentException | InvocationTargetException ex) {
            LogManager.getLogger().warn("setFireInfo - " + ex);
        }
    }

    private final static Field SIGN_TEXT = getField(SignTileEntity.class, "field_145915_a");

    public static ITextComponent getSignText(SignTileEntity ent, int index) {
        return getFieldValue(ITextComponent[].class, ent, SIGN_TEXT)[index];
    }

    private final static Field WORLD_INFO = getField(World.class, "field_72986_A"); // worldInfo
    private final static Field SERVER_WORLD_INFO = getField(ServerWorld.class, "field_241103_E_"); // worldInfo

    public static void setWorldInfo(ServerWorld w, IServerWorldInfo info) {
        setFieldValue(w, WORLD_INFO, info);
        setFieldValue(w, SERVER_WORLD_INFO, info);
    }

    private final static Field DERIVED_WORLD_INFO_DELEGATE =
            getField(DerivedWorldInfo.class, "field_76115_a");

    public static IServerWorldInfo getDerivedWorldInfoDelegate(DerivedWorldInfo info) {
        return getFieldValue(IServerWorldInfo.class, info, DERIVED_WORLD_INFO_DELEGATE);
    }

    private final static Field DERIVED_WORLD_INFO_CONFIGURATION =
            getField(DerivedWorldInfo.class, "field_237244_a_");

    public static IServerConfiguration getDerivedWorldInfoConfiguration(DerivedWorldInfo info) {
        return getFieldValue(IServerConfiguration.class, info, DERIVED_WORLD_INFO_CONFIGURATION);
    }

    private final static Method ADD_SLOT = getMethod(Container.class, "func_75146_a", Slot.class); // addSlot
    private final static Method TRACK_INT =
            getMethod(Container.class, "func_216958_a", IntReferenceHolder.class); // trackInt
    private final static Method TRACK_INT_ARRAY =
            getMethod(Container.class, "func_216961_a", IIntArray.class); // trackIntArray
    private final static Method CLEAR_CONTAINER = getMethod(Container.class, "func_193327_a",
            PlayerEntity.class, World.class, IInventory.class); // clearContainer
    private final static Method MERGE_ITEM_STACK = getMethod(Container.class, "func_75135_a",
            ItemStack.class, int.class, int.class, boolean.class); // mergeItemStack
    private final static Method RESET_DRAG = getMethod(Container.class, "func_94533_d"); // resetDrag

    public static Slot addSlot(Container c, Slot slot) {
        try {
            return (Slot) ADD_SLOT.invoke(c, slot);
        } catch(Exception ex) {
            LogManager.getLogger().warn("addSlot - " + ex);
        }
        return null;
    }

    public static IntReferenceHolder trackInt(Container c, IntReferenceHolder i) {
        try {
            return (IntReferenceHolder) TRACK_INT.invoke(c, i);
        } catch(Exception ex) {
            LogManager.getLogger().warn("trackInt - " + ex);
        }
        return null;
    }

    public static void trackIntArray(Container c, IIntArray array) {
        try {
            TRACK_INT_ARRAY.invoke(c, array);
        } catch(Exception ex) {
            LogManager.getLogger().warn("trackIntArray - " + ex);
        }
    }

    public static void clearContainer(Container c, PlayerEntity p, World w, IInventory i) {
        try {
            CLEAR_CONTAINER.invoke(c, p, w, i);
        } catch(Exception ex) {
            LogManager.getLogger().warn("clearContainer - " + ex);
        }
    }

    public static boolean mergeItemStack(Container c, ItemStack stack, int startIndex, int endIndex,
            boolean reverseDirection) {
        try {
            return (boolean) MERGE_ITEM_STACK.invoke(c, stack, startIndex, endIndex,
                    reverseDirection);
        } catch(Exception ex) {
            LogManager.getLogger().warn("mergeItemStack - " + ex);
        }
        return false;
    }

    public static void resetDrag(Container c) {
        try {
            RESET_DRAG.invoke(c);
        } catch(Exception ex) {
            LogManager.getLogger().warn("resetDrag - " + ex);
        }
    }
}
