package net.minecraft.world.entity.ai.village.poi;

import org.apache.logging.log4j.LogManager;
import java.util.function.BiConsumer;
import java.util.function.Consumer;
import com.google.common.collect.ImmutableMap;
import com.mojang.datafixers.types.DynamicOps;
import java.util.Optional;
import net.minecraft.Util;
import com.google.common.collect.Sets;
import net.minecraft.core.SectionPos;
import org.apache.logging.log4j.util.Supplier;
import net.minecraft.core.BlockPos;
import java.util.stream.Stream;
import java.util.function.Predicate;
import com.mojang.datafixers.Dynamic;
import com.google.common.collect.Maps;
import it.unimi.dsi.fastutil.shorts.Short2ObjectOpenHashMap;
import java.util.Set;
import java.util.Map;
import it.unimi.dsi.fastutil.shorts.Short2ObjectMap;
import org.apache.logging.log4j.Logger;
import net.minecraft.util.Serializable;

public class PoiSection implements Serializable {
    private static final Logger LOGGER;
    private final Short2ObjectMap<PoiRecord> records;
    private final Map<PoiType, Set<PoiRecord>> byType;
    private final Runnable setDirty;
    private boolean isValid;
    
    public PoiSection(final Runnable runnable) {
        this.records = (Short2ObjectMap<PoiRecord>)new Short2ObjectOpenHashMap();
        this.byType = Maps.newHashMap();
        this.setDirty = runnable;
        this.isValid = true;
    }
    
    public <T> PoiSection(final Runnable runnable, final Dynamic<T> dynamic) {
        this.records = (Short2ObjectMap<PoiRecord>)new Short2ObjectOpenHashMap();
        this.byType = Maps.newHashMap();
        this.setDirty = runnable;
        try {
            this.isValid = dynamic.get("Valid").asBoolean(false);
            dynamic.get("Records").asStream().forEach(dynamic -> this.add(new PoiRecord(dynamic, runnable)));
        }
        catch (Exception exception4) {
            PoiSection.LOGGER.error("Failed to load POI chunk", (Throwable)exception4);
            this.clear();
            this.isValid = false;
        }
    }
    
    public Stream<PoiRecord> getRecords(final Predicate<PoiType> predicate, final PoiManager.Occupancy b) {
        return this.byType.entrySet().stream().filter(entry -> predicate.test(entry.getKey())).<PoiRecord>flatMap(entry -> entry.getValue().stream()).filter(b.getTest());
    }
    
    public void add(final BlockPos fk, final PoiType asu) {
        if (this.add(new PoiRecord(fk, asu, this.setDirty))) {
            PoiSection.LOGGER.debug("Added POI of type {} @ {}", new Supplier[] { () -> asu, () -> fk });
            this.setDirty.run();
        }
    }
    
    private boolean add(final PoiRecord ass) {
        final BlockPos fk3 = ass.getPos();
        final PoiType asu4 = ass.getPoiType();
        final short short5 = SectionPos.sectionRelativePos(fk3);
        final PoiRecord ass2 = (PoiRecord)this.records.get(short5);
        if (ass2 == null) {
            this.records.put(short5, ass);
            this.byType.computeIfAbsent(asu4, asu -> Sets.newHashSet()).add(ass);
            return true;
        }
        if (asu4.equals(ass2.getPoiType())) {
            return false;
        }
        throw Util.<IllegalStateException>pauseInIde(new IllegalStateException("POI data mismatch: already registered at " + fk3));
    }
    
    public void remove(final BlockPos fk) {
        final PoiRecord ass3 = (PoiRecord)this.records.remove(SectionPos.sectionRelativePos(fk));
        if (ass3 == null) {
            PoiSection.LOGGER.error("POI data mismatch: never registered at " + fk);
            return;
        }
        this.byType.get(ass3.getPoiType()).remove(ass3);
        PoiSection.LOGGER.debug("Removed POI of type {} @ {}", new Supplier[] { ass3::getPoiType, ass3::getPos });
        this.setDirty.run();
    }
    
    public boolean release(final BlockPos fk) {
        final PoiRecord ass3 = (PoiRecord)this.records.get(SectionPos.sectionRelativePos(fk));
        if (ass3 == null) {
            throw Util.<IllegalStateException>pauseInIde(new IllegalStateException("POI never registered at " + fk));
        }
        final boolean boolean4 = ass3.releaseTicket();
        this.setDirty.run();
        return boolean4;
    }
    
    public boolean exists(final BlockPos fk, final Predicate<PoiType> predicate) {
        final short short4 = SectionPos.sectionRelativePos(fk);
        final PoiRecord ass5 = (PoiRecord)this.records.get(short4);
        return ass5 != null && predicate.test(ass5.getPoiType());
    }
    
    public Optional<PoiType> getType(final BlockPos fk) {
        final short short3 = SectionPos.sectionRelativePos(fk);
        final PoiRecord ass4 = (PoiRecord)this.records.get(short3);
        return (ass4 != null) ? Optional.<PoiType>of(ass4.getPoiType()) : Optional.<PoiType>empty();
    }
    
    @Override
    public <T> T serialize(final DynamicOps<T> dynamicOps) {
        final T object3 = (T)dynamicOps.createList((Stream)this.records.values().stream().map(ass -> ass.<T>serialize(dynamicOps)));
        return (T)dynamicOps.createMap((Map)ImmutableMap.of(dynamicOps.createString("Records"), object3, dynamicOps.createString("Valid"), dynamicOps.createBoolean(this.isValid)));
    }
    
    public void refresh(final Consumer<BiConsumer<BlockPos, PoiType>> consumer) {
        if (!this.isValid) {
            final Short2ObjectMap<PoiRecord> short2ObjectMap3 = (Short2ObjectMap<PoiRecord>)new Short2ObjectOpenHashMap((Short2ObjectMap)this.records);
            this.clear();
            final short short5;
            final Short2ObjectMap short2ObjectMap4;
            final PoiRecord ass6;
            consumer.accept((fk, asu) -> {
                short5 = SectionPos.sectionRelativePos(fk);
                ass6 = (PoiRecord)short2ObjectMap4.computeIfAbsent(short5, integer -> new PoiRecord(fk, asu, this.setDirty));
                this.add(ass6);
                return;
            });
            this.isValid = true;
            this.setDirty.run();
        }
    }
    
    private void clear() {
        this.records.clear();
        this.byType.clear();
    }
    
    boolean isValid() {
        return this.isValid;
    }
    
    static {
        LOGGER = LogManager.getLogger();
    }
}
