// 
// Decompiled by Procyon v0.6.0
// 

package com.hypixel.hytale.server.core.entity;

import com.hypixel.hytale.server.core.entity.effect.ActiveEntityEffect;
import com.hypixel.hytale.server.core.asset.type.item.config.Item;
import java.util.function.Function;
import javax.annotation.Nullable;
import com.hypixel.hytale.server.core.asset.type.item.config.ItemArmor;
import com.hypixel.hytale.server.core.inventory.container.ItemContainer;
import it.unimi.dsi.fastutil.objects.ObjectIterator;
import com.hypixel.hytale.server.core.modules.entitystats.modifier.Modifier;
import java.util.Iterator;
import it.unimi.dsi.fastutil.objects.Object2FloatOpenHashMap;
import com.hypixel.hytale.server.core.asset.type.entityeffect.config.EntityEffect;
import com.hypixel.hytale.server.core.entity.effect.EffectControllerComponent;
import com.hypixel.hytale.server.core.inventory.ItemStack;
import com.hypixel.hytale.server.core.asset.type.gameplay.BrokenPenalties;
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
import com.hypixel.hytale.server.core.inventory.Inventory;
import com.hypixel.hytale.server.core.universe.world.World;
import it.unimi.dsi.fastutil.ints.IntIterator;
import com.hypixel.hytale.server.core.modules.entitystats.modifier.StaticModifier;
import it.unimi.dsi.fastutil.objects.Object2FloatMap;
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
import com.hypixel.hytale.component.ComponentAccessor;
import com.hypixel.hytale.server.core.modules.entitystats.EntityStatMap;
import com.hypixel.hytale.server.core.universe.world.storage.EntityStore;
import com.hypixel.hytale.component.Ref;
import it.unimi.dsi.fastutil.ints.IntOpenHashSet;
import it.unimi.dsi.fastutil.ints.IntSet;
import javax.annotation.Nonnull;
import java.util.concurrent.atomic.AtomicBoolean;

public class StatModifiersManager
{
    @Nonnull
    private final AtomicBoolean recalculate;
    @Nonnull
    private final IntSet statsToClear;
    
    public StatModifiersManager() {
        this.recalculate = new AtomicBoolean();
        this.statsToClear = new IntOpenHashSet();
    }
    
    public void setRecalculate(final boolean value) {
        this.recalculate.set(value);
    }
    
    public void queueEntityStatsToClear(@Nonnull final int[] entityStatsToClear) {
        for (int i = 0; i < entityStatsToClear.length; ++i) {
            this.statsToClear.add(entityStatsToClear[i]);
        }
    }
    
    public void recalculateEntityStatModifiers(@Nonnull final Ref<EntityStore> ref, @Nonnull final EntityStatMap statMap, @Nonnull final ComponentAccessor<EntityStore> componentAccessor) {
        if (!this.recalculate.getAndSet(false)) {
            return;
        }
        if (!this.statsToClear.isEmpty()) {
            final IntIterator iterator = this.statsToClear.iterator();
            while (iterator.hasNext()) {
                statMap.minimizeStatValue(EntityStatMap.Predictable.SELF, iterator.nextInt());
            }
            this.statsToClear.clear();
        }
        final World world = componentAccessor.getExternalData().getWorld();
        final Entity entity = EntityUtils.getEntity(ref, componentAccessor);
        if (entity instanceof final LivingEntity livingEntity) {
            final Inventory inventory = livingEntity.getInventory();
            final Int2ObjectOpenHashMap<Object2FloatMap<StaticModifier.CalculationType>> effectModifiers = calculateEffectStatModifiers(ref, componentAccessor);
            applyEffectModifiers(statMap, effectModifiers);
            final BrokenPenalties brokenPenalties = world.getGameplayConfig().getItemDurabilityConfig().getBrokenPenalties();
            final Int2ObjectMap<Object2FloatMap<StaticModifier.CalculationType>> statModifiers = computeStatModifiers(brokenPenalties, inventory);
            applyStatModifiers(statMap, statModifiers);
            final ItemStack itemInHand = inventory.getItemInHand();
            addItemStatModifiers(itemInHand, statMap, "*Weapon_", v -> (v.getWeapon() != null) ? v.getWeapon().getStatModifiers() : null);
            if (itemInHand == null || itemInHand.getItem().getUtility().isCompatible()) {
                addItemStatModifiers(inventory.getUtilityItem(), statMap, "*Utility_", v -> v.getUtility().getStatModifiers());
            }
        }
    }
    
    @Nonnull
    private static Int2ObjectOpenHashMap<Object2FloatMap<StaticModifier.CalculationType>> calculateEffectStatModifiers(@Nonnull final Ref<EntityStore> ref, @Nonnull final ComponentAccessor<EntityStore> componentAccessor) {
        final Int2ObjectOpenHashMap<Object2FloatMap<StaticModifier.CalculationType>> statModifiers = new Int2ObjectOpenHashMap<Object2FloatMap<StaticModifier.CalculationType>>();
        final EffectControllerComponent effectControllerComponent = componentAccessor.getComponent(ref, EffectControllerComponent.getComponentType());
        if (effectControllerComponent == null) {
            return statModifiers;
        }
        effectControllerComponent.getActiveEffects().forEach((k, v) -> {
            if (!v.isInfinite() && v.getRemainingDuration() <= 0.0f) {
                return;
            }
            else {
                final int index = v.getEntityEffectIndex();
                final EntityEffect effect = EntityEffect.getAssetMap().getAsset(index);
                if (effect == null || effect.getStatModifiers() == null) {
                    return;
                }
                else {
                    for (final Int2ObjectMap.Entry<StaticModifier[]> entry : effect.getStatModifiers().int2ObjectEntrySet()) {
                        final int entityStatType = entry.getIntKey();
                        for (final StaticModifier modifier : entry.getValue()) {
                            final float value = modifier.getAmount();
                            final Object2FloatMap<StaticModifier.CalculationType> statModifierToApply = statModifiers.computeIfAbsent(entityStatType, x -> new Object2FloatOpenHashMap());
                            statModifierToApply.mergeFloat(modifier.getCalculationType(), value, Float::sum);
                        }
                    }
                    return;
                }
            }
        });
        return statModifiers;
    }
    
    private static void applyEffectModifiers(@Nonnull final EntityStatMap statMap, @Nonnull final Int2ObjectMap<Object2FloatMap<StaticModifier.CalculationType>> statModifiers) {
        for (int i = 0; i < statMap.size(); ++i) {
            final Object2FloatMap<StaticModifier.CalculationType> statModifiersForEntityStat = statModifiers.get(i);
            if (statModifiersForEntityStat == null) {
                for (final StaticModifier.CalculationType calculationType : StaticModifier.CalculationType.values()) {
                    statMap.removeModifier(i, calculationType.createKey("Effect"));
                }
            }
            else {
                for (final StaticModifier.CalculationType calculationType : StaticModifier.CalculationType.values()) {
                    if (!statModifiersForEntityStat.containsKey(calculationType)) {
                        statMap.removeModifier(i, calculationType.createKey("Effect"));
                    }
                }
                for (final Object2FloatMap.Entry<StaticModifier.CalculationType> entry : statModifiersForEntityStat.object2FloatEntrySet()) {
                    final StaticModifier.CalculationType calculationType2 = entry.getKey();
                    final StaticModifier modifier = new StaticModifier(Modifier.ModifierTarget.MAX, calculationType2, entry.getFloatValue());
                    statMap.putModifier(i, calculationType2.createKey("Effect"), modifier);
                }
            }
        }
    }
    
    private static void computeStatModifiers(final double brokenPenalty, @Nonnull final Int2ObjectMap<Object2FloatMap<StaticModifier.CalculationType>> statModifiers, @Nonnull final ItemStack itemInHand, @Nonnull final Int2ObjectMap<StaticModifier[]> itemStatModifiers) {
        final boolean broken = itemInHand.isBroken();
        for (final Int2ObjectMap.Entry<StaticModifier[]> entry : itemStatModifiers.int2ObjectEntrySet()) {
            final int entityStatType = entry.getIntKey();
            for (final StaticModifier modifier : entry.getValue()) {
                float value = modifier.getAmount();
                if (broken) {
                    value *= (float)brokenPenalty;
                }
                final Object2FloatMap<StaticModifier.CalculationType> statModifierToApply = statModifiers.computeIfAbsent(entityStatType, x -> new Object2FloatOpenHashMap());
                statModifierToApply.mergeFloat(modifier.getCalculationType(), value, Float::sum);
            }
        }
    }
    
    @Nonnull
    private static Int2ObjectMap<Object2FloatMap<StaticModifier.CalculationType>> computeStatModifiers(@Nonnull final BrokenPenalties brokenPenalties, @Nonnull final Inventory inventory) {
        final Int2ObjectOpenHashMap<Object2FloatMap<StaticModifier.CalculationType>> statModifiers = new Int2ObjectOpenHashMap<Object2FloatMap<StaticModifier.CalculationType>>();
        final double armorBrokenPenalty = brokenPenalties.getArmor(0.0);
        final ItemContainer armorContainer = inventory.getArmor();
        for (short i = 0; i < armorContainer.getCapacity(); ++i) {
            final ItemStack armorItemStack = armorContainer.getItemStack(i);
            if (armorItemStack != null) {
                addArmorStatModifiers(armorItemStack, armorBrokenPenalty, statModifiers);
            }
        }
        return statModifiers;
    }
    
    private static void addArmorStatModifiers(@Nonnull final ItemStack itemStack, final double brokenPenalties, @Nonnull final Int2ObjectOpenHashMap<Object2FloatMap<StaticModifier.CalculationType>> statModifiers) {
        if (ItemStack.isEmpty(itemStack)) {
            return;
        }
        final ItemArmor armorItem = itemStack.getItem().getArmor();
        if (armorItem == null) {
            return;
        }
        final Int2ObjectMap<StaticModifier[]> itemStatModifiers = armorItem.getStatModifiers();
        if (itemStatModifiers == null) {
            return;
        }
        computeStatModifiers(brokenPenalties, statModifiers, itemStack, itemStatModifiers);
    }
    
    private static void addItemStatModifiers(@Nullable final ItemStack itemStack, @Nonnull final EntityStatMap entityStatMap, @Nonnull final String prefix, @Nonnull final Function<Item, Int2ObjectMap<StaticModifier[]>> toStatModifiers) {
        if (ItemStack.isEmpty(itemStack)) {
            clearAllStatModifiers(EntityStatMap.Predictable.SELF, entityStatMap, prefix, null);
            return;
        }
        final Int2ObjectMap<StaticModifier[]> itemStatModifiers = toStatModifiers.apply(itemStack.getItem());
        if (itemStatModifiers == null) {
            clearAllStatModifiers(EntityStatMap.Predictable.SELF, entityStatMap, prefix, null);
            return;
        }
        for (Int2ObjectMap.Entry<StaticModifier[]> entry : itemStatModifiers.int2ObjectEntrySet()) {
            int offset = 0;
            final int statIndex = entry.getIntKey();
            final StaticModifier[] array = entry.getValue();
            for (int length = array.length, i = 0; i < length; ++i) {
                final StaticModifier modifier = array[i];
                final String key = prefix + offset;
                ++offset;
                final Modifier existing = entityStatMap.getModifier(statIndex, key);
                if (existing instanceof final StaticModifier existingStatic) {
                    if (existingStatic.equals(modifier)) {
                        continue;
                    }
                }
                entityStatMap.putModifier(EntityStatMap.Predictable.SELF, statIndex, key, modifier);
            }
            clearStatModifiers(EntityStatMap.Predictable.SELF, entityStatMap, statIndex, prefix, offset);
        }
        clearAllStatModifiers(EntityStatMap.Predictable.SELF, entityStatMap, prefix, itemStatModifiers);
    }
    
    private static void clearAllStatModifiers(@Nonnull final EntityStatMap.Predictable predictable, @Nonnull final EntityStatMap entityStatMap, @Nonnull final String prefix, @Nullable final Int2ObjectMap<StaticModifier[]> excluding) {
        for (int i = 0; i < entityStatMap.size(); ++i) {
            if (excluding == null || !excluding.containsKey(i)) {
                clearStatModifiers(predictable, entityStatMap, i, prefix, 0);
            }
        }
    }
    
    private static void clearStatModifiers(@Nonnull final EntityStatMap.Predictable predictable, @Nonnull final EntityStatMap entityStatMap, final int statIndex, @Nonnull final String prefix, int offset) {
        String key;
        do {
            key = prefix + offset;
            ++offset;
        } while (entityStatMap.removeModifier(predictable, statIndex, key) != null);
    }
    
    private static void applyStatModifiers(@Nonnull final EntityStatMap statMap, @Nonnull final Int2ObjectMap<Object2FloatMap<StaticModifier.CalculationType>> statModifiers) {
        for (int i = 0; i < statMap.size(); ++i) {
            final Object2FloatMap<StaticModifier.CalculationType> statModifiersForEntityStat = statModifiers.get(i);
            if (statModifiersForEntityStat == null) {
                for (final StaticModifier.CalculationType calculationType : StaticModifier.CalculationType.values()) {
                    statMap.removeModifier(i, calculationType.createKey("Armor"));
                }
            }
            else {
                for (final StaticModifier.CalculationType calculationType : StaticModifier.CalculationType.values()) {
                    if (!statModifiersForEntityStat.containsKey(calculationType)) {
                        statMap.removeModifier(i, calculationType.createKey("Armor"));
                    }
                }
                for (final Object2FloatMap.Entry<StaticModifier.CalculationType> entry : statModifiersForEntityStat.object2FloatEntrySet()) {
                    final StaticModifier.CalculationType calculationType2 = entry.getKey();
                    final StaticModifier modifier = new StaticModifier(Modifier.ModifierTarget.MAX, calculationType2, entry.getFloatValue());
                    statMap.putModifier(i, calculationType2.createKey("Armor"), modifier);
                }
            }
        }
    }
}
