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

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

import com.hypixel.hytale.codec.Codec;
import com.hypixel.hytale.codec.KeyedCodec;
import com.hypixel.hytale.server.core.modules.entity.BlockMigrationExtraInfo;
import com.hypixel.hytale.codec.ExtraInfo;
import com.hypixel.hytale.server.core.inventory.container.ItemContainer;
import com.hypixel.hytale.server.core.asset.type.item.config.Item;
import com.hypixel.hytale.server.core.inventory.transaction.ItemStackSlotTransaction;
import javax.annotation.Nullable;
import com.hypixel.hytale.protocol.MovementStates;
import com.hypixel.hytale.server.core.entity.movement.MovementStatesComponent;
import com.hypixel.hytale.server.core.modules.entity.component.TransformComponent;
import java.util.Iterator;
import com.hypixel.hytale.server.core.inventory.transaction.ListTransaction;
import java.util.List;
import com.hypixel.hytale.server.core.inventory.transaction.ItemStackTransaction;
import com.hypixel.hytale.server.core.inventory.ItemStack;
import it.unimi.dsi.fastutil.objects.ObjectArrayList;
import com.hypixel.hytale.math.vector.Vector3d;
import com.hypixel.hytale.math.vector.Transform;
import com.hypixel.hytale.server.core.universe.world.storage.ChunkStore;
import com.hypixel.hytale.server.core.modules.collision.WorldUtil;
import com.hypixel.hytale.math.util.MathUtil;
import com.hypixel.hytale.math.util.ChunkUtil;
import com.hypixel.hytale.server.core.util.TargetUtil;
import com.hypixel.hytale.component.ComponentType;
import com.hypixel.hytale.server.core.modules.entity.component.Invulnerable;
import com.hypixel.hytale.component.ComponentAccessor;
import com.hypixel.hytale.protocol.BlockMaterial;
import com.hypixel.hytale.server.core.universe.world.storage.EntityStore;
import com.hypixel.hytale.component.Ref;
import com.hypixel.hytale.server.core.universe.world.World;
import com.hypixel.hytale.event.EventRegistration;
import com.hypixel.hytale.server.core.inventory.Inventory;
import javax.annotation.Nonnull;
import com.hypixel.hytale.codec.builder.BuilderCodec;

public abstract class LivingEntity extends Entity
{
    @Nonnull
    public static final BuilderCodec<LivingEntity> CODEC;
    public static final int DEFAULT_ITEM_THROW_SPEED = 6;
    @Nonnull
    private final StatModifiersManager statModifiersManager;
    private Inventory inventory;
    protected double currentFallDistance;
    private EventRegistration armorInventoryChangeEventRegistration;
    private boolean isEquipmentNetworkOutdated;
    
    public LivingEntity() {
        this.statModifiersManager = new StatModifiersManager();
        this.setInventory(this.createDefaultInventory());
    }
    
    public LivingEntity(@Nonnull final World world) {
        super(world);
        this.statModifiersManager = new StatModifiersManager();
        this.setInventory(this.createDefaultInventory());
    }
    
    protected abstract Inventory createDefaultInventory();
    
    public boolean canBreathe(@Nonnull final Ref<EntityStore> ref, @Nonnull final BlockMaterial breathingMaterial, final int fluidId, @Nonnull final ComponentAccessor<EntityStore> componentAccessor) {
        final boolean invulnerable = componentAccessor.getArchetype(ref).contains(Invulnerable.getComponentType());
        return invulnerable || (breathingMaterial == BlockMaterial.Empty && fluidId == 0);
    }
    
    public static long getPackedMaterialAndFluidAtBreathingHeight(@Nonnull final Ref<EntityStore> ref, @Nonnull final ComponentAccessor<EntityStore> componentAccessor) {
        final World world = componentAccessor.getExternalData().getWorld();
        final Transform lookVec = TargetUtil.getLook(ref, componentAccessor);
        final Vector3d position = lookVec.getPosition();
        final ChunkStore chunkStore = world.getChunkStore();
        final long chunkIndex = ChunkUtil.indexChunkFromBlock(position.x, position.z);
        final Ref<ChunkStore> chunkRef = chunkStore.getChunkReference(chunkIndex);
        if (chunkRef == null || !chunkRef.isValid()) {
            return MathUtil.packLong(BlockMaterial.Empty.ordinal(), 0);
        }
        return WorldUtil.getPackedMaterialAndFluidAtPosition(chunkRef, chunkStore.getStore(), position.x, position.y, position.z);
    }
    
    public Inventory getInventory() {
        return this.inventory;
    }
    
    @Nonnull
    public Inventory setInventory(final Inventory inventory) {
        return this.setInventory(inventory, false);
    }
    
    @Nonnull
    public Inventory setInventory(Inventory inventory, final boolean ensureCapacity) {
        final List<ItemStack> remainder = ensureCapacity ? new ObjectArrayList<ItemStack>() : null;
        inventory = this.setInventory(inventory, ensureCapacity, remainder);
        if (remainder != null && !remainder.isEmpty()) {
            final ListTransaction<ItemStackTransaction> transactionList = inventory.getCombinedHotbarFirst().addItemStacks(remainder);
            for (ItemStackTransaction itemStackTransaction : transactionList.getList()) {}
        }
        return inventory;
    }
    
    @Nonnull
    public Inventory setInventory(Inventory inventory, final boolean ensureCapacity, final List<ItemStack> remainder) {
        if (this.inventory != null) {
            this.inventory.unregister();
        }
        if (this.armorInventoryChangeEventRegistration != null) {
            this.armorInventoryChangeEventRegistration.unregister();
        }
        if (ensureCapacity) {
            inventory = Inventory.ensureCapacity(inventory, remainder);
        }
        inventory.setEntity(this);
        this.armorInventoryChangeEventRegistration = inventory.getArmor().registerChangeEvent(event -> this.statModifiersManager.setRecalculate(true));
        return this.inventory = inventory;
    }
    
    @Override
    public void moveTo(@Nonnull final Ref<EntityStore> ref, final double locX, final double locY, final double locZ, @Nonnull final ComponentAccessor<EntityStore> componentAccessor) {
        final TransformComponent transformComponent = componentAccessor.getComponent(ref, TransformComponent.getComponentType());
        assert transformComponent != null;
        final MovementStatesComponent movementStatesComponent = componentAccessor.getComponent(ref, MovementStatesComponent.getComponentType());
        assert movementStatesComponent != null;
        final MovementStates movementStates = movementStatesComponent.getMovementStates();
        final boolean fallDamageActive = !movementStates.inFluid && !movementStates.climbing && !movementStates.flying && !movementStates.gliding;
        if (fallDamageActive) {
            final Vector3d position = transformComponent.getPosition();
            if (!movementStates.onGround) {
                if (position.getY() > locY) {
                    this.currentFallDistance += position.getY() - locY;
                }
            }
            else {
                this.currentFallDistance = 0.0;
            }
        }
        else {
            this.currentFallDistance = 0.0;
        }
        super.moveTo(ref, locX, locY, locZ, componentAccessor);
    }
    
    public boolean canDecreaseItemStackDurability(@Nonnull final Ref<EntityStore> ref, @Nonnull final ComponentAccessor<EntityStore> componentAccessor) {
        return false;
    }
    
    public boolean canApplyItemStackPenalties(final Ref<EntityStore> ref, final ComponentAccessor<EntityStore> componentAccessor) {
        return true;
    }
    
    @Nullable
    public ItemStackSlotTransaction decreaseItemStackDurability(@Nonnull final Ref<EntityStore> ref, @Nullable final ItemStack itemStack, final int inventoryId, final int slotId, @Nonnull final ComponentAccessor<EntityStore> componentAccessor) {
        if (!this.canDecreaseItemStackDurability(ref, componentAccessor)) {
            return null;
        }
        if (itemStack == null || itemStack.isEmpty() || itemStack.getItem() == null) {
            return null;
        }
        if (itemStack.isBroken()) {
            return null;
        }
        final Item item = itemStack.getItem();
        final ItemContainer section = this.inventory.getSectionById(inventoryId);
        if (section == null) {
            return null;
        }
        if (item.getArmor() != null) {
            final ItemStackSlotTransaction transaction = this.updateItemStackDurability(ref, itemStack, section, slotId, -item.getDurabilityLossOnHit(), componentAccessor);
            if (transaction.getSlotAfter().isBroken()) {
                this.statModifiersManager.setRecalculate(true);
            }
            return transaction;
        }
        if (item.getWeapon() != null) {
            return this.updateItemStackDurability(ref, itemStack, section, slotId, -item.getDurabilityLossOnHit(), componentAccessor);
        }
        return null;
    }
    
    @Nullable
    public ItemStackSlotTransaction updateItemStackDurability(@Nonnull final Ref<EntityStore> ref, @Nonnull final ItemStack itemStack, final ItemContainer container, final int slotId, final double durabilityChange, @Nonnull final ComponentAccessor<EntityStore> componentAccessor) {
        final ItemStack updatedItemStack = itemStack.withIncreasedDurability(durabilityChange);
        return container.replaceItemStackInSlot((short)slotId, itemStack, updatedItemStack);
    }
    
    public void invalidateEquipmentNetwork() {
        this.isEquipmentNetworkOutdated = true;
    }
    
    public boolean consumeEquipmentNetworkOutdated() {
        final boolean temp = this.isEquipmentNetworkOutdated;
        this.isEquipmentNetworkOutdated = false;
        return temp;
    }
    
    @Nonnull
    public StatModifiersManager getStatModifiersManager() {
        return this.statModifiersManager;
    }
    
    public double getCurrentFallDistance() {
        return this.currentFallDistance;
    }
    
    public void setCurrentFallDistance(final double currentFallDistance) {
        this.currentFallDistance = currentFallDistance;
    }
    
    @Nonnull
    @Override
    public String toString() {
        return "LivingEntity{, " + super.toString();
    }
    
    static {
        CODEC = BuilderCodec.abstractBuilder(LivingEntity.class, Entity.CODEC).append(new KeyedCodec<Inventory>("Inventory", Inventory.CODEC), (livingEntity, inventory, extraInfo) -> {
            livingEntity.setInventory(inventory);
            if (extraInfo instanceof final BlockMigrationExtraInfo blockMigrationExtraInfo) {
                livingEntity.inventory.doMigration(blockMigrationExtraInfo.getBlockMigration());
            }
        }, (livingEntity, extraInfo) -> livingEntity.inventory).add().afterDecode(livingEntity -> {
            if (livingEntity.inventory == null) {
                livingEntity.setInventory(livingEntity.createDefaultInventory());
            }
        }).build();
    }
}
