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

package com.hypixel.hytale.server.core.inventory.container;

import com.hypixel.hytale.codec.codecs.array.ArrayCodec;
import com.hypixel.hytale.codec.Codec;
import com.hypixel.hytale.server.core.inventory.container.filter.TagFilter;
import com.hypixel.hytale.server.core.asset.type.item.config.ItemStackContainerConfig;
import com.hypixel.hytale.codec.ExtraInfo;
import com.hypixel.hytale.codec.EmptyExtraInfo;
import com.hypixel.hytale.server.core.inventory.transaction.ClearTransaction;
import java.util.function.Function;
import java.util.function.Supplier;
import javax.annotation.Nullable;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import com.hypixel.hytale.server.core.inventory.container.filter.FilterType;
import com.hypixel.hytale.server.core.inventory.container.filter.SlotFilter;
import com.hypixel.fastutil.ints.Int2ObjectConcurrentHashMap;
import com.hypixel.hytale.server.core.inventory.container.filter.FilterActionType;
import java.util.Map;
import java.util.concurrent.locks.ReadWriteLock;
import com.hypixel.hytale.server.core.inventory.ItemStack;
import javax.annotation.Nonnull;
import org.bson.BsonDocument;
import com.hypixel.hytale.codec.KeyedCodec;

public class ItemStackItemContainer extends ItemContainer
{
    @Nonnull
    public static KeyedCodec<BsonDocument> CONTAINER_CODEC;
    @Nonnull
    public static KeyedCodec<Short> CAPACITY_CODEC;
    @Nonnull
    public static KeyedCodec<ItemStack[]> ITEMS_CODEC;
    protected final ReadWriteLock lock;
    protected final ItemContainer parentContainer;
    protected final short itemStackSlot;
    protected final ItemStack originalItemStack;
    protected final short capacity;
    protected ItemStack[] items;
    private final Map<FilterActionType, Int2ObjectConcurrentHashMap<SlotFilter>> slotFilters;
    @Nonnull
    private FilterType globalFilter;
    
    private ItemStackItemContainer(final ItemContainer parentContainer, final short itemStackSlot, final ItemStack originalItemStack, final short capacity, final ItemStack[] items) {
        this.lock = new ReentrantReadWriteLock();
        this.slotFilters = new ConcurrentHashMap<FilterActionType, Int2ObjectConcurrentHashMap<SlotFilter>>();
        this.globalFilter = FilterType.ALLOW_ALL;
        this.parentContainer = parentContainer;
        this.itemStackSlot = itemStackSlot;
        this.originalItemStack = originalItemStack;
        this.capacity = capacity;
        this.items = items;
    }
    
    public ItemContainer getParentContainer() {
        return this.parentContainer;
    }
    
    public short getItemStackSlot() {
        return this.itemStackSlot;
    }
    
    public ItemStack getOriginalItemStack() {
        return this.originalItemStack;
    }
    
    public boolean isItemStackValid() {
        final ItemStack itemStack = this.parentContainer.getItemStack(this.itemStackSlot);
        return !ItemStack.isEmpty(itemStack) && ItemStack.isSameItemType(itemStack, this.originalItemStack);
    }
    
    @Override
    public short getCapacity() {
        return this.capacity;
    }
    
    @Override
    public void setGlobalFilter(@Nonnull final FilterType globalFilter) {
        this.globalFilter = globalFilter;
    }
    
    @Override
    public void setSlotFilter(final FilterActionType actionType, final short slot, @Nullable final SlotFilter filter) {
        ItemContainer.validateSlotIndex(slot, this.getCapacity());
        if (filter != null) {
            this.slotFilters.computeIfAbsent(actionType, k -> new Int2ObjectConcurrentHashMap()).put(slot, filter);
        }
        else {
            this.slotFilters.computeIfPresent(actionType, (k, map) -> {
                map.remove(slot);
                return map.isEmpty() ? null : map;
            });
        }
    }
    
    @Override
    public ItemContainer clone() {
        throw new UnsupportedOperationException("Item stack containers don't support clone");
    }
    
    @Override
    protected <V> V readAction(@Nonnull final Supplier<V> action) {
        this.lock.readLock().lock();
        try {
            return action.get();
        }
        finally {
            this.lock.readLock().unlock();
        }
    }
    
    @Override
    protected <X, V> V readAction(@Nonnull final Function<X, V> action, final X x) {
        this.lock.readLock().lock();
        try {
            return action.apply(x);
        }
        finally {
            this.lock.readLock().unlock();
        }
    }
    
    @Override
    protected <V> V writeAction(@Nonnull final Supplier<V> action) {
        this.lock.writeLock().lock();
        try {
            return action.get();
        }
        finally {
            this.lock.writeLock().unlock();
        }
    }
    
    @Override
    protected <X, V> V writeAction(@Nonnull final Function<X, V> action, final X x) {
        this.lock.writeLock().lock();
        try {
            return action.apply(x);
        }
        finally {
            this.lock.writeLock().unlock();
        }
    }
    
    @Override
    public boolean isEmpty() {
        this.lock.readLock().lock();
        try {
            if (this.items == null) {
                return true;
            }
            for (short i = 0; i < this.items.length; ++i) {
                if (!ItemStack.isEmpty(this.items[i])) {
                    return false;
                }
            }
            return false;
        }
        finally {
            this.lock.readLock().unlock();
        }
    }
    
    @Nonnull
    @Override
    protected ClearTransaction internal_clear() {
        if (this.items == null) {
            return new ClearTransaction(true, (short)0, ItemStack.EMPTY_ARRAY);
        }
        final ItemStack[] oldItems = this.items;
        this.items = new ItemStack[oldItems.length];
        writeToItemStack(this.parentContainer, this.itemStackSlot, this.originalItemStack, this.items);
        return new ClearTransaction(true, (short)0, oldItems);
    }
    
    @Nullable
    @Override
    protected ItemStack internal_getSlot(final short slot) {
        return (this.items != null) ? this.items[slot] : null;
    }
    
    @Nullable
    @Override
    protected ItemStack internal_setSlot(final short slot, final ItemStack itemStack) {
        if (this.items == null) {
            return null;
        }
        if (ItemStack.isEmpty(itemStack)) {
            return this.internal_removeSlot(slot);
        }
        final ItemStack old = this.items[slot];
        this.items[slot] = itemStack;
        writeToItemStack(this.parentContainer, this.itemStackSlot, this.originalItemStack, this.items);
        return old;
    }
    
    @Nullable
    @Override
    protected ItemStack internal_removeSlot(final short slot) {
        if (this.items == null) {
            return null;
        }
        final ItemStack old = this.items[slot];
        this.items[slot] = null;
        writeToItemStack(this.parentContainer, this.itemStackSlot, this.originalItemStack, this.items);
        return old;
    }
    
    @Override
    protected boolean cantAddToSlot(final short slot, final ItemStack itemStack, final ItemStack slotItemStack) {
        return !this.globalFilter.allowInput() || this.testFilter(FilterActionType.ADD, slot, itemStack);
    }
    
    @Override
    protected boolean cantRemoveFromSlot(final short slot) {
        return !this.globalFilter.allowOutput() || this.testFilter(FilterActionType.REMOVE, slot, null);
    }
    
    @Override
    protected boolean cantDropFromSlot(final short slot) {
        return this.testFilter(FilterActionType.DROP, slot, null);
    }
    
    @Override
    protected boolean cantMoveToSlot(final ItemContainer fromContainer, final short slotFrom) {
        return fromContainer == this.parentContainer && slotFrom == this.itemStackSlot;
    }
    
    private boolean testFilter(final FilterActionType actionType, final short slot, final ItemStack itemStack) {
        final Int2ObjectConcurrentHashMap<SlotFilter> map = this.slotFilters.get(actionType);
        if (map == null) {
            return false;
        }
        final SlotFilter filter = map.get(slot);
        return filter != null && !filter.test(actionType, this, slot, itemStack);
    }
    
    @Nullable
    @Override
    public ItemStack getItemStack(final short slot) {
        ItemContainer.validateSlotIndex(slot, this.getCapacity());
        this.lock.readLock().lock();
        try {
            return this.internal_getSlot(slot);
        }
        finally {
            this.lock.readLock().unlock();
        }
    }
    
    public static void writeToItemStack(@Nonnull final ItemContainer itemContainer, final short slot, final ItemStack originalItemStack, final ItemStack[] items) {
        if (ItemStack.isEmpty(originalItemStack)) {
            throw new IllegalStateException("Item stack container is empty");
        }
        final ItemStack itemStack = itemContainer.getItemStack(slot);
        if (!ItemStack.isSameItemType(itemStack, originalItemStack)) {
            throw new IllegalStateException("Item stack in parent container changed!");
        }
        final BsonDocument newMetadata = itemStack.getMetadata();
        final BsonDocument containerDocument = ItemStackItemContainer.CONTAINER_CODEC.getOrNull(newMetadata, EmptyExtraInfo.EMPTY);
        if (containerDocument == null) {
            throw new IllegalStateException("Item stack container is empty!");
        }
        ItemStackItemContainer.ITEMS_CODEC.put(containerDocument, items, EmptyExtraInfo.EMPTY);
        itemContainer.setItemStackForSlot(slot, itemStack.withMetadata(newMetadata));
    }
    
    @Nullable
    public static ItemStackItemContainer getContainer(@Nonnull final ItemContainer itemContainer, final short slot) {
        final ItemStack itemStack = itemContainer.getItemStack(slot);
        if (ItemStack.isEmpty(itemStack)) {
            return null;
        }
        final BsonDocument containerDocument = itemStack.getFromMetadataOrNull(ItemStackItemContainer.CONTAINER_CODEC);
        if (containerDocument == null) {
            return null;
        }
        final Short capacity = ItemStackItemContainer.CAPACITY_CODEC.getOrNull(containerDocument, EmptyExtraInfo.EMPTY);
        if (capacity == null || capacity <= 0) {
            return null;
        }
        ItemStack[] items = ItemStackItemContainer.ITEMS_CODEC.getOrNull(containerDocument, EmptyExtraInfo.EMPTY);
        if (items == null) {
            items = new ItemStack[(short)capacity];
        }
        return new ItemStackItemContainer(itemContainer, slot, itemStack, capacity, items);
    }
    
    @Nonnull
    public static ItemStackItemContainer makeContainerWithCapacity(@Nonnull final ItemContainer itemContainer, final short slot, final short capacity) {
        if (capacity <= 0) {
            throw new IllegalArgumentException("Capacity must be > 0");
        }
        final ItemStack itemStack = itemContainer.getItemStack(slot);
        if (ItemStack.isEmpty(itemStack)) {
            throw new IllegalArgumentException("Item stack is empty!");
        }
        final ItemStackItemContainer itemStackItemContainer = getContainer(itemContainer, slot);
        if (itemStackItemContainer != null && itemStackItemContainer.getCapacity() != 0) {
            throw new IllegalStateException("Item stack already has a container!");
        }
        BsonDocument newMetadata = itemStack.getMetadata();
        if (newMetadata == null) {
            newMetadata = new BsonDocument();
        }
        BsonDocument containerDocument = ItemStackItemContainer.CONTAINER_CODEC.getOrNull(newMetadata, EmptyExtraInfo.EMPTY);
        if (containerDocument == null) {
            containerDocument = new BsonDocument();
            ItemStackItemContainer.CONTAINER_CODEC.put(newMetadata, containerDocument, EmptyExtraInfo.EMPTY);
        }
        ItemStackItemContainer.CAPACITY_CODEC.put(containerDocument, capacity, EmptyExtraInfo.EMPTY);
        itemContainer.setItemStackForSlot(slot, itemStack.withMetadata(newMetadata));
        return new ItemStackItemContainer(itemContainer, slot, itemStack, capacity, new ItemStack[capacity]);
    }
    
    @Nullable
    public static ItemStackItemContainer ensureContainer(@Nonnull final ItemContainer itemContainer, final short slot, final short capacity) {
        if (capacity <= 0) {
            throw new IllegalArgumentException("Capacity must be > 0");
        }
        final ItemStack itemStack = itemContainer.getItemStack(slot);
        if (ItemStack.isEmpty(itemStack)) {
            return null;
        }
        final ItemStackItemContainer itemStackItemContainer = getContainer(itemContainer, slot);
        if (itemStackItemContainer != null && itemStackItemContainer.getCapacity() != 0) {
            return itemStackItemContainer;
        }
        BsonDocument newMetadata = itemStack.getMetadata();
        if (newMetadata == null) {
            newMetadata = new BsonDocument();
        }
        BsonDocument containerDocument = ItemStackItemContainer.CONTAINER_CODEC.getOrNull(newMetadata, EmptyExtraInfo.EMPTY);
        if (containerDocument == null) {
            containerDocument = new BsonDocument();
            ItemStackItemContainer.CONTAINER_CODEC.put(newMetadata, containerDocument, EmptyExtraInfo.EMPTY);
        }
        ItemStackItemContainer.CAPACITY_CODEC.put(containerDocument, capacity, EmptyExtraInfo.EMPTY);
        itemContainer.setItemStackForSlot(slot, itemStack.withMetadata(newMetadata));
        return new ItemStackItemContainer(itemContainer, slot, itemStack, capacity, new ItemStack[capacity]);
    }
    
    @Nullable
    public static ItemStackItemContainer ensureConfiguredContainer(@Nonnull final ItemContainer itemContainer, final short slot, @Nonnull final ItemStackContainerConfig config) {
        final ItemStackItemContainer itemStackItemContainer = ensureContainer(itemContainer, slot, config.getCapacity());
        if (itemStackItemContainer == null) {
            return null;
        }
        itemStackItemContainer.setGlobalFilter(config.getGlobalFilter());
        final int tagIndex = config.getTagIndex();
        if (tagIndex != Integer.MIN_VALUE) {
            for (short i = 0; i < itemStackItemContainer.getCapacity(); ++i) {
                itemStackItemContainer.setSlotFilter(FilterActionType.ADD, i, new TagFilter(tagIndex));
            }
        }
        return itemStackItemContainer;
    }
    
    static {
        ItemStackItemContainer.CONTAINER_CODEC = new KeyedCodec<BsonDocument>("Container", Codec.BSON_DOCUMENT);
        ItemStackItemContainer.CAPACITY_CODEC = new KeyedCodec<Short>("Capacity", Codec.SHORT);
        ItemStackItemContainer.ITEMS_CODEC = new KeyedCodec<ItemStack[]>("Items", new ArrayCodec<ItemStack>(ItemStack.CODEC, ItemStack[]::new));
    }
}
