package net.minecraft.world.level;

import java.util.AbstractList;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.nbt.ListTag;
import net.minecraft.core.Vec3i;
import javax.annotation.Nullable;
import java.util.Collections;
import java.util.Collection;
import net.minecraft.world.level.levelgen.structure.BoundingBox;
import java.util.stream.Stream;
import net.minecraft.core.BlockPos;
import java.util.Iterator;
import net.minecraft.server.level.ServerChunkCache;
import net.minecraft.ReportedException;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.CrashReportCategory;
import net.minecraft.CrashReport;
import com.google.common.collect.Lists;
import com.google.common.collect.Queues;
import java.util.Comparator;
import com.google.common.collect.Sets;
import java.util.function.Consumer;
import java.util.List;
import java.util.Queue;
import net.minecraft.server.level.ServerLevel;
import java.util.TreeSet;
import java.util.Set;
import net.minecraft.resources.ResourceLocation;
import java.util.function.Function;
import java.util.function.Predicate;

public class ServerTickList<T> implements TickList<T> {
    protected final Predicate<T> ignore;
    private final Function<T, ResourceLocation> toId;
    private final Function<ResourceLocation, T> fromId;
    private final Set<TickNextTickData<T>> tickNextTickSet;
    private final TreeSet<TickNextTickData<T>> tickNextTickList;
    private final ServerLevel level;
    private final Queue<TickNextTickData<T>> currentlyTicking;
    private final List<TickNextTickData<T>> alreadyTicked;
    private final Consumer<TickNextTickData<T>> ticker;
    
    public ServerTickList(final ServerLevel xd, final Predicate<T> predicate, final Function<T, ResourceLocation> function3, final Function<ResourceLocation, T> function4, final Consumer<TickNextTickData<T>> consumer) {
        this.tickNextTickSet = Sets.newHashSet();
        this.tickNextTickList = Sets.<TickNextTickData<T>>newTreeSet(TickNextTickData.createTimeComparator());
        this.currentlyTicking = Queues.newArrayDeque();
        this.alreadyTicked = Lists.newArrayList();
        this.ignore = predicate;
        this.toId = function3;
        this.fromId = function4;
        this.level = xd;
        this.ticker = consumer;
    }
    
    public void tick() {
        int integer2 = this.tickNextTickList.size();
        if (integer2 != this.tickNextTickSet.size()) {
            throw new IllegalStateException("TickNextTick list out of synch");
        }
        if (integer2 > 65536) {
            integer2 = 65536;
        }
        final ServerChunkCache xb3 = this.level.getChunkSource();
        final Iterator<TickNextTickData<T>> iterator4 = this.tickNextTickList.iterator();
        this.level.getProfiler().push("cleaning");
        while (integer2 > 0 && iterator4.hasNext()) {
            final TickNextTickData<T> bkm5 = iterator4.next();
            if (bkm5.delay > this.level.getGameTime()) {
                break;
            }
            if (!xb3.isTickingChunk(bkm5.pos)) {
                continue;
            }
            iterator4.remove();
            this.tickNextTickSet.remove(bkm5);
            this.currentlyTicking.add(bkm5);
            --integer2;
        }
        this.level.getProfiler().popPush("ticking");
        TickNextTickData<T> bkm5;
        while ((bkm5 = this.currentlyTicking.poll()) != null) {
            if (xb3.isTickingChunk(bkm5.pos)) {
                try {
                    this.alreadyTicked.add(bkm5);
                    this.ticker.accept(bkm5);
                    continue;
                }
                catch (Throwable throwable6) {
                    final CrashReport h7 = CrashReport.forThrowable(throwable6, "Exception while ticking");
                    final CrashReportCategory i8 = h7.addCategory("Block being ticked");
                    CrashReportCategory.populateBlockDetails(i8, bkm5.pos, null);
                    throw new ReportedException(h7);
                }
            }
            this.scheduleTick(bkm5.pos, bkm5.getType(), 0);
        }
        this.level.getProfiler().pop();
        this.alreadyTicked.clear();
        this.currentlyTicking.clear();
    }
    
    @Override
    public boolean willTickThisTick(final BlockPos fk, final T object) {
        return this.currentlyTicking.contains(new TickNextTickData(fk, object));
    }
    
    @Override
    public void addAll(final Stream<TickNextTickData<T>> stream) {
        stream.forEach(this::addTickData);
    }
    
    public List<TickNextTickData<T>> fetchTicksInChunk(final ChunkPos bjh, final boolean boolean2, final boolean boolean3) {
        final int integer5 = (bjh.x << 4) - 2;
        final int integer6 = integer5 + 16 + 2;
        final int integer7 = (bjh.z << 4) - 2;
        final int integer8 = integer7 + 16 + 2;
        return this.fetchTicksInArea(new BoundingBox(integer5, 0, integer7, integer6, 256, integer8), boolean2, boolean3);
    }
    
    public List<TickNextTickData<T>> fetchTicksInArea(final BoundingBox clb, final boolean boolean2, final boolean boolean3) {
        List<TickNextTickData<T>> list5 = this.fetchTicksInArea(null, this.tickNextTickList, clb, boolean2);
        if (boolean2 && list5 != null) {
            this.tickNextTickSet.removeAll(list5);
        }
        list5 = this.fetchTicksInArea(list5, this.currentlyTicking, clb, boolean2);
        if (!boolean3) {
            list5 = this.fetchTicksInArea(list5, this.alreadyTicked, clb, boolean2);
        }
        return (list5 == null) ? Collections.<TickNextTickData<T>>emptyList() : list5;
    }
    
    @Nullable
    private List<TickNextTickData<T>> fetchTicksInArea(@Nullable List<TickNextTickData<T>> list, final Collection<TickNextTickData<T>> collection, final BoundingBox clb, final boolean boolean4) {
        final Iterator<TickNextTickData<T>> iterator6 = collection.iterator();
        while (iterator6.hasNext()) {
            final TickNextTickData<T> bkm7 = iterator6.next();
            final BlockPos fk8 = bkm7.pos;
            if (fk8.getX() >= clb.x0 && fk8.getX() < clb.x1 && fk8.getZ() >= clb.z0 && fk8.getZ() < clb.z1) {
                if (boolean4) {
                    iterator6.remove();
                }
                if (list == null) {
                    list = Lists.newArrayList();
                }
                list.add(bkm7);
            }
        }
        return list;
    }
    
    public void copy(final BoundingBox clb, final BlockPos fk) {
        final List<TickNextTickData<T>> list4 = this.fetchTicksInArea(clb, false, false);
        for (final TickNextTickData<T> bkm6 : list4) {
            if (clb.isInside(bkm6.pos)) {
                final BlockPos fk2 = bkm6.pos.offset(fk);
                final T object8 = bkm6.getType();
                this.addTickData(new TickNextTickData<T>(fk2, object8, bkm6.delay, bkm6.priority));
            }
        }
    }
    
    public ListTag save(final ChunkPos bjh) {
        final List<TickNextTickData<T>> list3 = this.fetchTicksInChunk(bjh, false, true);
        return ServerTickList.<T>saveTickList(this.toId, list3, this.level.getGameTime());
    }
    
    public static <T> ListTag saveTickList(final Function<T, ResourceLocation> function, final Iterable<TickNextTickData<T>> iterable, final long long3) {
        final ListTag jz5 = new ListTag();
        for (final TickNextTickData<T> bkm7 : iterable) {
            final CompoundTag jt8 = new CompoundTag();
            jt8.putString("i", function.apply(bkm7.getType()).toString());
            jt8.putInt("x", bkm7.pos.getX());
            jt8.putInt("y", bkm7.pos.getY());
            jt8.putInt("z", bkm7.pos.getZ());
            jt8.putInt("t", (int)(bkm7.delay - long3));
            jt8.putInt("p", bkm7.priority.getValue());
            ((AbstractList<CompoundTag>)jz5).add(jt8);
        }
        return jz5;
    }
    
    @Override
    public boolean hasScheduledTick(final BlockPos fk, final T object) {
        return this.tickNextTickSet.contains(new TickNextTickData(fk, object));
    }
    
    @Override
    public void scheduleTick(final BlockPos fk, final T object, final int integer, final TickPriority bkn) {
        if (!this.ignore.test(object)) {
            this.addTickData(new TickNextTickData<T>(fk, object, integer + this.level.getGameTime(), bkn));
        }
    }
    
    private void addTickData(final TickNextTickData<T> bkm) {
        if (!this.tickNextTickSet.contains(bkm)) {
            this.tickNextTickSet.add(bkm);
            this.tickNextTickList.add(bkm);
        }
    }
    
    public int size() {
        return this.tickNextTickSet.size();
    }
}
