package net.minecraft.world.level.storage.loot;

import org.apache.commons.lang3.ArrayUtils;
import com.google.gson.JsonSerializationContext;
import com.google.gson.JsonParseException;
import com.google.gson.JsonObject;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.util.GsonHelper;
import com.google.gson.JsonDeserializationContext;
import java.lang.reflect.Type;
import com.google.gson.JsonElement;
import com.google.gson.JsonSerializer;
import com.google.gson.JsonDeserializer;
import net.minecraft.world.level.storage.loot.functions.FunctionUserBuilder;
import net.minecraft.world.level.storage.loot.parameters.LootContextParamSets;
import org.apache.logging.log4j.LogManager;
import java.util.Collections;
import java.util.Collection;
import net.minecraft.util.Mth;
import java.util.Iterator;
import java.util.Random;
import net.minecraft.world.Container;
import com.google.common.collect.Lists;
import java.util.List;
import java.util.function.Consumer;
import net.minecraft.world.level.storage.loot.functions.LootItemFunctions;
import net.minecraft.world.item.ItemStack;
import java.util.function.BiFunction;
import net.minecraft.world.level.storage.loot.functions.LootItemFunction;
import net.minecraft.world.level.storage.loot.parameters.LootContextParamSet;
import org.apache.logging.log4j.Logger;

public class LootTable {
    private static final Logger LOGGER;
    public static final LootTable EMPTY;
    public static final LootContextParamSet DEFAULT_PARAM_SET;
    private final LootContextParamSet paramSet;
    private final LootPool[] pools;
    private final LootItemFunction[] functions;
    private final BiFunction<ItemStack, LootContext, ItemStack> compositeFunction;
    
    private LootTable(final LootContextParamSet dcy, final LootPool[] arr, final LootItemFunction[] arr) {
        this.paramSet = dcy;
        this.pools = arr;
        this.functions = arr;
        this.compositeFunction = LootItemFunctions.compose(arr);
    }
    
    public static Consumer<ItemStack> createStackSplitter(final Consumer<ItemStack> consumer) {
        int integer3;
        ItemStack bki2;
        return bki -> {
            if (bki.getCount() < bki.getMaxStackSize()) {
                consumer.accept(bki);
            }
            else {
                integer3 = bki.getCount();
                while (integer3 > 0) {
                    bki2 = bki.copy();
                    bki2.setCount(Math.min(bki.getMaxStackSize(), integer3));
                    integer3 -= bki2.getCount();
                    consumer.accept(bki2);
                }
            }
        };
    }
    
    public void getRandomItemsRaw(final LootContext dat, final Consumer<ItemStack> consumer) {
        if (dat.addVisitedTable(this)) {
            final Consumer<ItemStack> consumer2 = LootItemFunction.decorate(this.compositeFunction, consumer, dat);
            for (final LootPool dav8 : this.pools) {
                dav8.addRandomItems(consumer2, dat);
            }
            dat.removeVisitedTable(this);
        }
        else {
            LootTable.LOGGER.warn("Detected infinite loop in loot tables");
        }
    }
    
    public void getRandomItems(final LootContext dat, final Consumer<ItemStack> consumer) {
        this.getRandomItemsRaw(dat, createStackSplitter(consumer));
    }
    
    public List<ItemStack> getRandomItems(final LootContext dat) {
        final List<ItemStack> list3 = Lists.newArrayList();
        this.getRandomItems(dat, list3::add);
        return list3;
    }
    
    public LootContextParamSet getParamSet() {
        return this.paramSet;
    }
    
    public void validate(final ValidationContext dbe) {
        for (int integer3 = 0; integer3 < this.pools.length; ++integer3) {
            this.pools[integer3].validate(dbe.forChild(".pools[" + integer3 + "]"));
        }
        for (int integer3 = 0; integer3 < this.functions.length; ++integer3) {
            this.functions[integer3].validate(dbe.forChild(".functions[" + integer3 + "]"));
        }
    }
    
    public void fill(final Container amz, final LootContext dat) {
        final List<ItemStack> list4 = this.getRandomItems(dat);
        final Random random5 = dat.getRandom();
        final List<Integer> list5 = this.getAvailableSlots(amz, random5);
        this.shuffleAndSplitItems(list4, list5.size(), random5);
        for (final ItemStack bki8 : list4) {
            if (list5.isEmpty()) {
                LootTable.LOGGER.warn("Tried to over-fill a container");
                return;
            }
            if (bki8.isEmpty()) {
                amz.setItem(list5.remove(list5.size() - 1), ItemStack.EMPTY);
            }
            else {
                amz.setItem(list5.remove(list5.size() - 1), bki8);
            }
        }
    }
    
    private void shuffleAndSplitItems(final List<ItemStack> list, final int integer, final Random random) {
        final List<ItemStack> list2 = Lists.newArrayList();
        final Iterator<ItemStack> iterator6 = list.iterator();
        while (iterator6.hasNext()) {
            final ItemStack bki7 = iterator6.next();
            if (bki7.isEmpty()) {
                iterator6.remove();
            }
            else {
                if (bki7.getCount() <= 1) {
                    continue;
                }
                list2.add(bki7);
                iterator6.remove();
            }
        }
        while (integer - list.size() - list2.size() > 0 && !list2.isEmpty()) {
            final ItemStack bki8 = list2.remove(Mth.nextInt(random, 0, list2.size() - 1));
            final int integer2 = Mth.nextInt(random, 1, bki8.getCount() / 2);
            final ItemStack bki9 = bki8.split(integer2);
            if (bki8.getCount() > 1 && random.nextBoolean()) {
                list2.add(bki8);
            }
            else {
                list.add(bki8);
            }
            if (bki9.getCount() > 1 && random.nextBoolean()) {
                list2.add(bki9);
            }
            else {
                list.add(bki9);
            }
        }
        list.addAll(list2);
        Collections.shuffle(list, random);
    }
    
    private List<Integer> getAvailableSlots(final Container amz, final Random random) {
        final List<Integer> list4 = Lists.newArrayList();
        for (int integer5 = 0; integer5 < amz.getContainerSize(); ++integer5) {
            if (amz.getItem(integer5).isEmpty()) {
                list4.add(integer5);
            }
        }
        Collections.shuffle(list4, random);
        return list4;
    }
    
    public static Builder lootTable() {
        return new Builder();
    }
    
    static {
        LOGGER = LogManager.getLogger();
        EMPTY = new LootTable(LootContextParamSets.EMPTY, new LootPool[0], new LootItemFunction[0]);
        DEFAULT_PARAM_SET = LootContextParamSets.ALL_PARAMS;
    }
    
    public static class Builder implements FunctionUserBuilder<Builder> {
        private final List<LootPool> pools;
        private final List<LootItemFunction> functions;
        private LootContextParamSet paramSet;
        
        public Builder() {
            this.pools = Lists.newArrayList();
            this.functions = Lists.newArrayList();
            this.paramSet = LootTable.DEFAULT_PARAM_SET;
        }
        
        public Builder withPool(final LootPool.Builder a) {
            this.pools.add(a.build());
            return this;
        }
        
        public Builder setParamSet(final LootContextParamSet dcy) {
            this.paramSet = dcy;
            return this;
        }
        
        @Override
        public Builder apply(final LootItemFunction.Builder a) {
            this.functions.add(a.build());
            return this;
        }
        
        @Override
        public Builder unwrap() {
            return this;
        }
        
        public LootTable build() {
            return new LootTable(this.paramSet, this.pools.<LootPool>toArray(new LootPool[0]), this.functions.<LootItemFunction>toArray(new LootItemFunction[0]), null);
        }
    }
    
    public static class Serializer implements JsonDeserializer<LootTable>, JsonSerializer<LootTable> {
        @Override
        public LootTable deserialize(final JsonElement jsonElement, final Type type, final JsonDeserializationContext jsonDeserializationContext) throws JsonParseException {
            final JsonObject jsonObject5 = GsonHelper.convertToJsonObject(jsonElement, "loot table");
            final LootPool[] arr6 = GsonHelper.<LootPool[]>getAsObject(jsonObject5, "pools", new LootPool[0], jsonDeserializationContext, LootPool[].class);
            LootContextParamSet dcy7 = null;
            if (jsonObject5.has("type")) {
                final String string8 = GsonHelper.getAsString(jsonObject5, "type");
                dcy7 = LootContextParamSets.get(new ResourceLocation(string8));
            }
            final LootItemFunction[] arr7 = GsonHelper.<LootItemFunction[]>getAsObject(jsonObject5, "functions", new LootItemFunction[0], jsonDeserializationContext, LootItemFunction[].class);
            return new LootTable((dcy7 != null) ? dcy7 : LootContextParamSets.ALL_PARAMS, arr6, arr7, null);
        }
        
        @Override
        public JsonElement serialize(final LootTable daw, final Type type, final JsonSerializationContext jsonSerializationContext) {
            final JsonObject jsonObject5 = new JsonObject();
            if (daw.paramSet != LootTable.DEFAULT_PARAM_SET) {
                final ResourceLocation uh6 = LootContextParamSets.getKey(daw.paramSet);
                if (uh6 != null) {
                    jsonObject5.addProperty("type", uh6.toString());
                }
                else {
                    LootTable.LOGGER.warn("Failed to find id for param set " + daw.paramSet);
                }
            }
            if (daw.pools.length > 0) {
                jsonObject5.add("pools", jsonSerializationContext.serialize(daw.pools));
            }
            if (!ArrayUtils.isEmpty((Object[])daw.functions)) {
                jsonObject5.add("functions", jsonSerializationContext.serialize(daw.functions));
            }
            return jsonObject5;
        }
    }
}
