package net.minecraft.gametest.framework;

import net.minecraft.world.level.block.entity.StructureBlockEntity;
import it.unimi.dsi.fastutil.objects.ObjectIterator;
import it.unimi.dsi.fastutil.objects.Object2LongOpenHashMap;
import com.google.common.collect.Lists;
import javax.annotation.Nullable;
import com.google.common.base.Stopwatch;
import it.unimi.dsi.fastutil.objects.Object2LongMap;
import java.util.Collection;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.core.BlockPos;

public class GameTestInfo {
    private final TestFunction testFunction;
    private BlockPos testPos;
    private final ServerLevel level;
    private final Collection<GameTestListener> listeners;
    private final int timeoutTicks;
    private final Collection<GameTestSequence> sequences;
    private Object2LongMap<Runnable> runAtTickTimeMap;
    private long startTick;
    private long tickCount;
    private boolean started;
    private final Stopwatch timer;
    private boolean done;
    @Nullable
    private Throwable error;
    
    public GameTestInfo(final TestFunction jk, final ServerLevel xd) {
        this.listeners = Lists.newArrayList();
        this.sequences = Lists.newCopyOnWriteArrayList();
        this.runAtTickTimeMap = (Object2LongMap<Runnable>)new Object2LongOpenHashMap();
        this.started = false;
        this.timer = Stopwatch.createUnstarted();
        this.done = false;
        this.testFunction = jk;
        this.level = xd;
        this.timeoutTicks = jk.getMaxTicks();
    }
    
    public GameTestInfo(final TestFunction jk, final BlockPos fk, final ServerLevel xd) {
        this(jk, xd);
        this.assignPosition(fk);
    }
    
    void assignPosition(final BlockPos fk) {
        this.testPos = fk;
    }
    
    void startExecution() {
        this.startTick = this.level.getGameTime() + 1L + this.testFunction.getSetupTicks();
        this.timer.start();
    }
    
    public void tick() {
        if (this.isDone()) {
            return;
        }
        this.tickCount = this.level.getGameTime() - this.startTick;
        if (this.tickCount < 0L) {
            return;
        }
        if (this.tickCount == 0L) {
            this.startTest();
        }
        final ObjectIterator<Object2LongMap.Entry<Runnable>> objectIterator2 = (ObjectIterator<Object2LongMap.Entry<Runnable>>)this.runAtTickTimeMap.object2LongEntrySet().iterator();
        while (objectIterator2.hasNext()) {
            final Object2LongMap.Entry<Runnable> entry3 = (Object2LongMap.Entry<Runnable>)objectIterator2.next();
            if (entry3.getLongValue() <= this.tickCount) {
                try {
                    ((Runnable)entry3.getKey()).run();
                }
                catch (Exception exception4) {
                    this.fail(exception4);
                }
                objectIterator2.remove();
            }
        }
        if (this.tickCount > this.timeoutTicks) {
            if (this.sequences.isEmpty()) {
                this.fail(new GameTestTimeoutException("Didn't succeed or fail within " + this.testFunction.getMaxTicks() + " ticks"));
            }
            else {
                this.sequences.forEach(ja -> ja.tickAndFailIfNotComplete(this.tickCount));
                if (this.error == null) {
                    this.fail(new GameTestTimeoutException("No sequences finished"));
                }
            }
        }
        else {
            this.sequences.forEach(ja -> ja.tickAndContinue(this.tickCount));
        }
    }
    
    private void startTest() {
        if (this.started) {
            throw new IllegalStateException("Test already started");
        }
        this.started = true;
        try {
            this.testFunction.run(new GameTestHelper(this));
        }
        catch (Exception exception2) {
            this.fail(exception2);
        }
    }
    
    public String getTestName() {
        return this.testFunction.getTestName();
    }
    
    public BlockPos getTestPos() {
        return this.testPos;
    }
    
    @Nullable
    public BlockPos getStructureSize() {
        final StructureBlockEntity bxl2 = this.getStructureBlockEntity();
        if (bxl2 == null) {
            return null;
        }
        return bxl2.getStructureSize();
    }
    
    @Nullable
    private StructureBlockEntity getStructureBlockEntity() {
        return (StructureBlockEntity)this.level.getBlockEntity(this.testPos);
    }
    
    public ServerLevel getLevel() {
        return this.level;
    }
    
    public boolean hasSucceeded() {
        return this.done && this.error == null;
    }
    
    public boolean hasFailed() {
        return this.error != null;
    }
    
    public boolean hasStarted() {
        return this.started;
    }
    
    public boolean isDone() {
        return this.done;
    }
    
    private void finish() {
        if (!this.done) {
            this.done = true;
            this.timer.stop();
        }
    }
    
    public void fail(final Throwable throwable) {
        this.finish();
        this.error = throwable;
        this.listeners.forEach(ix -> ix.testFailed(this));
    }
    
    @Nullable
    public Throwable getError() {
        return this.error;
    }
    
    @Override
    public String toString() {
        return this.getTestName();
    }
    
    public void addListener(final GameTestListener ix) {
        this.listeners.add(ix);
    }
    
    public void spawnStructure(final int integer) {
        final StructureBlockEntity bxl3 = StructureUtils.spawnStructure(this.testFunction.getStructureName(), this.testPos, integer, this.level, false);
        bxl3.setStructureName(this.getTestName());
        StructureUtils.addCommandBlockAndButtonToStartTest(this.testPos.offset(1, 0, -1), this.level);
        this.listeners.forEach(ix -> ix.testStructureLoaded(this));
    }
    
    public boolean isRequired() {
        return this.testFunction.isRequired();
    }
    
    public boolean isOptional() {
        return !this.testFunction.isRequired();
    }
    
    public String getStructureName() {
        return this.testFunction.getStructureName();
    }
}
