package net.minecraft.core;

import org.apache.logging.log4j.LogManager;
import java.util.List;
import java.util.function.Consumer;
import com.google.common.collect.ImmutableMap;
import java.util.function.Function;
import net.minecraft.resources.RegistryDataPackCodec;
import com.google.common.collect.ImmutableList;
import com.mojang.datafixers.util.Pair;
import net.minecraft.util.Codecs;
import com.mojang.serialization.Codec;
import com.mojang.serialization.MapCodec;
import java.util.Collection;
import net.minecraft.Util;
import java.util.Random;
import java.util.Map;
import java.util.Collections;
import java.util.Iterator;
import java.util.Optional;
import javax.annotation.Nullable;
import org.apache.commons.lang3.Validate;
import com.google.common.collect.Sets;
import com.google.common.collect.HashBiMap;
import com.mojang.serialization.Lifecycle;
import java.util.Set;
import net.minecraft.resources.ResourceKey;
import net.minecraft.resources.ResourceLocation;
import com.google.common.collect.BiMap;
import net.minecraft.util.CrudeIncrementalIntIdentityHashBiMap;
import org.apache.logging.log4j.Logger;

public class MappedRegistry<T> extends WritableRegistry<T> {
    protected static final Logger LOGGER;
    protected final CrudeIncrementalIntIdentityHashBiMap<T> map;
    protected final BiMap<ResourceLocation, T> storage;
    private final BiMap<ResourceKey<T>, T> keyStorage;
    private final Set<ResourceKey<T>> persistent;
    protected Object[] randomCache;
    private int nextId;
    
    public MappedRegistry(final ResourceKey<Registry<T>> ug, final Lifecycle lifecycle) {
        super(ug, lifecycle);
        this.map = new CrudeIncrementalIntIdentityHashBiMap<T>(256);
        this.storage = HashBiMap.create();
        this.keyStorage = HashBiMap.create();
        this.persistent = Sets.<ResourceKey<T>>newIdentityHashSet();
    }
    
    @Override
    public <V extends T> V registerMapping(final int integer, final ResourceKey<T> ug, final V object) {
        this.map.addMapping(object, integer);
        Validate.notNull(ug);
        Validate.notNull(object);
        this.randomCache = null;
        if (this.keyStorage.containsKey(ug)) {
            MappedRegistry.LOGGER.debug("Adding duplicate key '{}' to registry", ug);
        }
        this.storage.put(ug.location(), object);
        this.keyStorage.put(ug, object);
        if (this.nextId <= integer) {
            this.nextId = integer + 1;
        }
        return object;
    }
    
    @Override
    public <V extends T> V register(final ResourceKey<T> ug, final V object) {
        return this.<V>registerMapping(this.nextId, ug, object);
    }
    
    @Nullable
    @Override
    public ResourceLocation getKey(final T object) {
        return this.storage.inverse().get(object);
    }
    
    @Override
    public Optional<ResourceKey<T>> getResourceKey(final T object) {
        return Optional.<ResourceKey<T>>ofNullable(this.keyStorage.inverse().get(object));
    }
    
    @Override
    public int getId(@Nullable final T object) {
        return this.map.getId(object);
    }
    
    @Nullable
    @Override
    public T get(@Nullable final ResourceKey<T> ug) {
        return this.keyStorage.get(ug);
    }
    
    @Nullable
    public T byId(final int integer) {
        return this.map.byId(integer);
    }
    
    public Iterator<T> iterator() {
        return this.map.iterator();
    }
    
    @Nullable
    @Override
    public T get(@Nullable final ResourceLocation uh) {
        return this.storage.get(uh);
    }
    
    @Override
    public Optional<T> getOptional(@Nullable final ResourceLocation uh) {
        return Optional.<T>ofNullable(this.storage.get(uh));
    }
    
    @Override
    public Set<ResourceLocation> keySet() {
        return Collections.<ResourceLocation>unmodifiableSet(this.storage.keySet());
    }
    
    public Set<Map.Entry<ResourceKey<T>, T>> entrySet() {
        return Collections.<ResourceKey<T>, T>unmodifiableMap(this.keyStorage).entrySet();
    }
    
    @Nullable
    public T getRandom(final Random random) {
        if (this.randomCache == null) {
            final Collection<?> collection3 = this.storage.values();
            if (collection3.isEmpty()) {
                return null;
            }
            this.randomCache = collection3.toArray(new Object[collection3.size()]);
        }
        return Util.<T>getRandom(this.randomCache, random);
    }
    
    @Override
    public boolean containsKey(final ResourceLocation uh) {
        return this.storage.containsKey(uh);
    }
    
    @Override
    public boolean containsId(final int integer) {
        return this.map.contains(integer);
    }
    
    @Override
    public boolean persistent(final ResourceKey<T> ug) {
        return this.persistent.contains(ug);
    }
    
    @Override
    public void setPersistent(final ResourceKey<T> ug) {
        this.persistent.add(ug);
    }
    
    public static <T> Codec<MappedRegistry<T>> networkCodec(final ResourceKey<Registry<T>> ug, final Lifecycle lifecycle, final MapCodec<T> mapCodec) {
        final MappedRegistry<T> gh2;
        final Iterator<Pair> iterator;
        Pair<ResourceKey<T>, T> pair6;
        final ImmutableList.Builder<Pair<ResourceKey<T>, T>> builder2;
        final Iterator<T> iterator2;
        T object4;
        return (Codec<MappedRegistry<T>>)Codecs.<T>withName(ug, mapCodec).codec().listOf().xmap(list -> {
            gh2 = (MappedRegistry<T>)new MappedRegistry<>((ResourceKey<Registry<T>>)ug, lifecycle);
            list.iterator();
            while (iterator.hasNext()) {
                pair6 = (Pair<ResourceKey<T>, T>)iterator.next();
                gh2.register((ResourceKey<T>)pair6.getFirst(), pair6.getSecond());
            }
            return gh2;
        }, gh -> {
            builder2 = ImmutableList.<Pair<ResourceKey<T>, T>>builder();
            gh.map.iterator();
            while (iterator2.hasNext()) {
                object4 = iterator2.next();
                builder2.add((Pair<ResourceKey<T>, T>)Pair.of(gh.getResourceKey(object4).get(), object4));
            }
            return builder2.build();
        });
    }
    
    public static <T> Codec<MappedRegistry<T>> dataPackCodec(final ResourceKey<Registry<T>> ug, final Lifecycle lifecycle, final MapCodec<T> mapCodec) {
        return RegistryDataPackCodec.create(ug, lifecycle, mapCodec);
    }
    
    public static <T> Codec<MappedRegistry<T>> directCodec(final ResourceKey<Registry<T>> ug, final Lifecycle lifecycle, final MapCodec<T> mapCodec) {
        final MappedRegistry<T> gh2;
        final MappedRegistry<Object> mappedRegistry;
        final ImmutableMap.Builder<ResourceKey<T>, T> builder2;
        return (Codec<MappedRegistry<T>>)Codec.unboundedMap(ResourceLocation.CODEC.xmap((Function)ResourceKey.<T>elementKey(ug), (Function)ResourceKey::location), mapCodec.codec()).xmap(map -> {
            gh2 = (MappedRegistry<T>)new MappedRegistry<>((ResourceKey<Registry<T>>)ug, lifecycle);
            map.forEach((ug, object) -> {
                mappedRegistry.registerMapping(mappedRegistry.nextId, ug, object);
                mappedRegistry.setPersistent(ug);
                return;
            });
            return gh2;
        }, gh -> {
            builder2 = ImmutableMap.<ResourceKey<T>, T>builder();
            gh.keyStorage.entrySet().stream().filter(entry -> gh.persistent(entry.getKey())).forEach(builder2::put);
            return builder2.build();
        });
    }
    
    static {
        LOGGER = LogManager.getLogger();
    }
}
