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 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 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 zd, final Predicate<T> predicate, final Function<T, ResourceLocation> function, 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 = function;
        this.level = zd;
        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 zb3 = this.level.getChunkSource();
        final Iterator<TickNextTickData<T>> iterator4 = this.tickNextTickList.iterator();
        this.level.getProfiler().push("cleaning");
        while (integer2 > 0 && iterator4.hasNext()) {
            final TickNextTickData<T> bqs5 = iterator4.next();
            if (bqs5.triggerTick > this.level.getGameTime()) {
                break;
            }
            if (!zb3.isTickingChunk(bqs5.pos)) {
                continue;
            }
            iterator4.remove();
            this.tickNextTickSet.remove(bqs5);
            this.currentlyTicking.add(bqs5);
            --integer2;
        }
        this.level.getProfiler().popPush("ticking");
        TickNextTickData<T> bqs5;
        while ((bqs5 = this.currentlyTicking.poll()) != null) {
            if (zb3.isTickingChunk(bqs5.pos)) {
                try {
                    this.alreadyTicked.add(bqs5);
                    this.ticker.accept(bqs5);
                    continue;
                }
                catch (Throwable throwable6) {
                    final CrashReport j7 = CrashReport.forThrowable(throwable6, "Exception while ticking");
                    final CrashReportCategory k8 = j7.addCategory("Block being ticked");
                    CrashReportCategory.populateBlockDetails(k8, bqs5.pos, null);
                    throw new ReportedException(j7);
                }
            }
            this.scheduleTick(bqs5.pos, bqs5.getType(), 0);
        }
        this.level.getProfiler().pop();
        this.alreadyTicked.clear();
        this.currentlyTicking.clear();
    }
    
    @Override
    public boolean willTickThisTick(final BlockPos fu, final T object) {
        return this.currentlyTicking.contains(new TickNextTickData(fu, object));
    }
    
    public List<TickNextTickData<T>> fetchTicksInChunk(final ChunkPos bph, final boolean boolean2, final boolean boolean3) {
        final int integer5 = (bph.x << 4) - 2;
        final int integer6 = integer5 + 16 + 2;
        final int integer7 = (bph.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 ctd, final boolean boolean2, final boolean boolean3) {
        List<TickNextTickData<T>> list5 = this.fetchTicksInArea(null, this.tickNextTickList, ctd, boolean2);
        if (boolean2 && list5 != null) {
            this.tickNextTickSet.removeAll(list5);
        }
        list5 = this.fetchTicksInArea(list5, this.currentlyTicking, ctd, boolean2);
        if (!boolean3) {
            list5 = this.fetchTicksInArea(list5, this.alreadyTicked, ctd, 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 ctd, final boolean boolean4) {
        final Iterator<TickNextTickData<T>> iterator6 = collection.iterator();
        while (iterator6.hasNext()) {
            final TickNextTickData<T> bqs7 = iterator6.next();
            final BlockPos fu8 = bqs7.pos;
            if (fu8.getX() >= ctd.x0 && fu8.getX() < ctd.x1 && fu8.getZ() >= ctd.z0 && fu8.getZ() < ctd.z1) {
                if (boolean4) {
                    iterator6.remove();
                }
                if (list == null) {
                    list = Lists.newArrayList();
                }
                list.add(bqs7);
            }
        }
        return list;
    }
    
    public void copy(final BoundingBox ctd, final BlockPos fu) {
        final List<TickNextTickData<T>> list4 = this.fetchTicksInArea(ctd, false, false);
        for (final TickNextTickData<T> bqs6 : list4) {
            if (ctd.isInside(bqs6.pos)) {
                final BlockPos fu2 = bqs6.pos.offset(fu);
                final T object8 = bqs6.getType();
                this.addTickData(new TickNextTickData<T>(fu2, object8, bqs6.triggerTick, bqs6.priority));
            }
        }
    }
    
    public ListTag save(final ChunkPos bph) {
        final List<TickNextTickData<T>> list3 = this.fetchTicksInChunk(bph, false, true);
        return ServerTickList.<T>saveTickList(this.toId, list3, this.level.getGameTime());
    }
    
    private static <T> ListTag saveTickList(final Function<T, ResourceLocation> function, final Iterable<TickNextTickData<T>> iterable, final long long3) {
        final ListTag lk5 = new ListTag();
        for (final TickNextTickData<T> bqs7 : iterable) {
            final CompoundTag le8 = new CompoundTag();
            le8.putString("i", function.apply(bqs7.getType()).toString());
            le8.putInt("x", bqs7.pos.getX());
            le8.putInt("y", bqs7.pos.getY());
            le8.putInt("z", bqs7.pos.getZ());
            le8.putInt("t", (int)(bqs7.triggerTick - long3));
            le8.putInt("p", bqs7.priority.getValue());
            ((AbstractList<CompoundTag>)lk5).add(le8);
        }
        return lk5;
    }
    
    @Override
    public boolean hasScheduledTick(final BlockPos fu, final T object) {
        return this.tickNextTickSet.contains(new TickNextTickData(fu, object));
    }
    
    @Override
    public void scheduleTick(final BlockPos fu, final T object, final int integer, final TickPriority bqt) {
        if (!this.ignore.test(object)) {
            this.addTickData(new TickNextTickData<T>(fu, object, integer + this.level.getGameTime(), bqt));
        }
    }
    
    private void addTickData(final TickNextTickData<T> bqs) {
        if (!this.tickNextTickSet.contains(bqs)) {
            this.tickNextTickSet.add(bqs);
            this.tickNextTickList.add(bqs);
        }
    }
    
    public int size() {
        return this.tickNextTickSet.size();
    }
}
