// 
// Decompiled by Procyon v0.6.0
// 

package com.hypixel.hytale.component;

import com.hypixel.hytale.component.event.WorldEventType;
import com.hypixel.hytale.component.event.EntityEventType;
import com.hypixel.hytale.component.system.EcsEvent;
import java.util.ArrayDeque;
import javax.annotation.Nullable;
import java.util.function.Consumer;
import java.util.Deque;
import javax.annotation.Nonnull;

public class CommandBuffer<ECS_TYPE> implements ComponentAccessor<ECS_TYPE>
{
    @Nonnull
    private final Store<ECS_TYPE> store;
    @Nonnull
    private final Deque<Consumer<Store<ECS_TYPE>>> queue;
    @Nullable
    private Ref<ECS_TYPE> trackedRef;
    private boolean trackedRefRemoved;
    @Nullable
    private CommandBuffer<ECS_TYPE> parentBuffer;
    @Nullable
    private Thread thread;
    
    protected CommandBuffer(@Nonnull final Store<ECS_TYPE> store) {
        this.queue = new ArrayDeque<Consumer<Store<ECS_TYPE>>>();
        this.store = store;
        assert this.setThread();
    }
    
    @Nonnull
    public Store<ECS_TYPE> getStore() {
        return this.store;
    }
    
    public void run(@Nonnull final Consumer<Store<ECS_TYPE>> consumer) {
        assert Thread.currentThread() == this.thread;
        this.queue.add(consumer);
    }
    
    @Override
    public <T extends Component<ECS_TYPE>> T getComponent(@Nonnull final Ref<ECS_TYPE> ref, @Nonnull final ComponentType<ECS_TYPE, T> componentType) {
        assert Thread.currentThread() == this.thread;
        return this.store.__internal_getComponent(ref, componentType);
    }
    
    @Nonnull
    @Override
    public Archetype<ECS_TYPE> getArchetype(@Nonnull final Ref<ECS_TYPE> ref) {
        assert Thread.currentThread() == this.thread;
        return this.store.__internal_getArchetype(ref);
    }
    
    @Nonnull
    @Override
    public <T extends Resource<ECS_TYPE>> T getResource(@Nonnull final ResourceType<ECS_TYPE, T> resourceType) {
        assert Thread.currentThread() == this.thread;
        return this.store.__internal_getResource(resourceType);
    }
    
    @Nonnull
    @Override
    public ECS_TYPE getExternalData() {
        return this.store.getExternalData();
    }
    
    @Nonnull
    @Override
    public Ref<ECS_TYPE>[] addEntities(@Nonnull final Holder<ECS_TYPE>[] holders, @Nonnull final AddReason reason) {
        assert Thread.currentThread() == this.thread;
        final Ref<ECS_TYPE>[] refs = new Ref[holders.length];
        for (int i = 0; i < holders.length; ++i) {
            refs[i] = new Ref<ECS_TYPE>(this.store);
        }
        this.queue.add(chunk -> chunk.addEntities(holders, refs, reason));
        return refs;
    }
    
    @Nonnull
    @Override
    public Ref<ECS_TYPE> addEntity(@Nonnull final Holder<ECS_TYPE> holder, @Nonnull final AddReason reason) {
        assert Thread.currentThread() == this.thread;
        final Ref<ECS_TYPE> ref = new Ref<ECS_TYPE>(this.store);
        this.queue.add(chunk -> chunk.addEntity(holder, ref, reason));
        return ref;
    }
    
    public void addEntities(@Nonnull final Holder<ECS_TYPE>[] holders, final int holderStart, @Nonnull final Ref<ECS_TYPE>[] refs, final int refStart, final int length, @Nonnull final AddReason reason) {
        assert Thread.currentThread() == this.thread;
        for (int i = refStart; i < refStart + length; ++i) {
            refs[i] = new Ref<ECS_TYPE>(this.store);
        }
        this.queue.add(chunk -> chunk.addEntities(holders, holderStart, refs, refStart, length, reason));
    }
    
    @Nonnull
    public Ref<ECS_TYPE> addEntity(@Nonnull final Holder<ECS_TYPE> holder, @Nonnull final Ref<ECS_TYPE> ref, @Nonnull final AddReason reason) {
        if (ref.isValid()) {
            throw new IllegalArgumentException("EntityReference is already in use!");
        }
        if (ref.getStore() != this.store) {
            throw new IllegalArgumentException("EntityReference is not for the correct store!");
        }
        assert Thread.currentThread() == this.thread;
        this.queue.add(chunk -> chunk.addEntity(holder, ref, reason));
        return ref;
    }
    
    @Nonnull
    public Holder<ECS_TYPE> copyEntity(@Nonnull final Ref<ECS_TYPE> ref, @Nonnull final Holder<ECS_TYPE> target) {
        assert Thread.currentThread() == this.thread;
        this.queue.add(chunk -> chunk.copyEntity(ref, target));
        return target;
    }
    
    public void tryRemoveEntity(@Nonnull final Ref<ECS_TYPE> ref, @Nonnull final RemoveReason reason) {
        assert Thread.currentThread() == this.thread;
        final Throwable source = new Throwable();
        this.queue.add(chunk -> {
            if (!ref.isValid()) {
                return;
            }
            else {
                chunk.removeEntity(ref, chunk.getRegistry().newHolder(), reason, source);
                return;
            }
        });
        if (ref.equals(this.trackedRef)) {
            this.trackedRefRemoved = true;
        }
        if (this.parentBuffer != null) {
            this.parentBuffer.testRemovedTracked(ref);
        }
    }
    
    public void removeEntity(@Nonnull final Ref<ECS_TYPE> ref, @Nonnull final RemoveReason reason) {
        assert Thread.currentThread() == this.thread;
        final Throwable source = new Throwable();
        this.queue.add(chunk -> chunk.removeEntity(ref, chunk.getRegistry().newHolder(), reason, source));
        if (ref.equals(this.trackedRef)) {
            this.trackedRefRemoved = true;
        }
        if (this.parentBuffer != null) {
            this.parentBuffer.testRemovedTracked(ref);
        }
    }
    
    @Nonnull
    @Override
    public Holder<ECS_TYPE> removeEntity(@Nonnull final Ref<ECS_TYPE> ref, @Nonnull final Holder<ECS_TYPE> target, @Nonnull final RemoveReason reason) {
        assert Thread.currentThread() == this.thread;
        final Throwable source = new Throwable();
        this.queue.add(chunk -> chunk.removeEntity(ref, target, reason, source));
        if (ref.equals(this.trackedRef)) {
            this.trackedRefRemoved = true;
        }
        if (this.parentBuffer != null) {
            this.parentBuffer.testRemovedTracked(ref);
        }
        return target;
    }
    
    public <T extends Component<ECS_TYPE>> void ensureComponent(@Nonnull final Ref<ECS_TYPE> ref, @Nonnull final ComponentType<ECS_TYPE, T> componentType) {
        assert Thread.currentThread() == this.thread;
        this.queue.add(chunk -> {
            if (!(!ref.isValid())) {
                chunk.ensureComponent(ref, componentType);
            }
        });
    }
    
    @Nonnull
    @Override
    public <T extends Component<ECS_TYPE>> T ensureAndGetComponent(@Nonnull final Ref<ECS_TYPE> ref, @Nonnull final ComponentType<ECS_TYPE, T> componentType) {
        assert Thread.currentThread() == this.thread;
        final T component = this.store.__internal_getComponent(ref, componentType);
        if (component != null) {
            return component;
        }
        final T newComponent = this.store.getRegistry()._internal_getData().createComponent(componentType);
        this.queue.add(chunk -> {
            if (!ref.isValid()) {
                return;
            }
            else {
                chunk.addComponent(ref, componentType, newComponent);
                return;
            }
        });
        return newComponent;
    }
    
    @Nonnull
    @Override
    public <T extends Component<ECS_TYPE>> T addComponent(@Nonnull final Ref<ECS_TYPE> ref, @Nonnull final ComponentType<ECS_TYPE, T> componentType) {
        assert Thread.currentThread() == this.thread;
        final T component = this.store.getRegistry()._internal_getData().createComponent(componentType);
        this.queue.add(chunk -> {
            if (!ref.isValid()) {
                return;
            }
            else {
                chunk.addComponent(ref, componentType, component);
                return;
            }
        });
        return component;
    }
    
    @Override
    public <T extends Component<ECS_TYPE>> void addComponent(@Nonnull final Ref<ECS_TYPE> ref, @Nonnull final ComponentType<ECS_TYPE, T> componentType, @Nonnull final T component) {
        assert Thread.currentThread() == this.thread;
        this.queue.add(chunk -> {
            if (!(!ref.isValid())) {
                chunk.addComponent(ref, componentType, component);
            }
        });
    }
    
    public <T extends Component<ECS_TYPE>> void replaceComponent(@Nonnull final Ref<ECS_TYPE> ref, @Nonnull final ComponentType<ECS_TYPE, T> componentType, @Nonnull final T component) {
        assert Thread.currentThread() == this.thread;
        this.queue.add(chunk -> {
            if (!(!ref.isValid())) {
                chunk.replaceComponent(ref, componentType, component);
            }
        });
    }
    
    @Override
    public <T extends Component<ECS_TYPE>> void removeComponent(@Nonnull final Ref<ECS_TYPE> ref, @Nonnull final ComponentType<ECS_TYPE, T> componentType) {
        assert Thread.currentThread() == this.thread;
        this.queue.add(chunk -> {
            if (!(!ref.isValid())) {
                chunk.removeComponent(ref, componentType);
            }
        });
    }
    
    @Override
    public <T extends Component<ECS_TYPE>> void tryRemoveComponent(@Nonnull final Ref<ECS_TYPE> ref, @Nonnull final ComponentType<ECS_TYPE, T> componentType) {
        assert Thread.currentThread() == this.thread;
        this.queue.add(chunk -> {
            if (!(!ref.isValid())) {
                chunk.tryRemoveComponent(ref, componentType);
            }
        });
    }
    
    @Override
    public <T extends Component<ECS_TYPE>> void putComponent(@Nonnull final Ref<ECS_TYPE> ref, @Nonnull final ComponentType<ECS_TYPE, T> componentType, @Nonnull final T component) {
        assert Thread.currentThread() == this.thread;
        this.queue.add(chunk -> {
            if (!(!ref.isValid())) {
                chunk.putComponent(ref, componentType, component);
            }
        });
    }
    
    @Override
    public <Event extends EcsEvent> void invoke(@Nonnull final Ref<ECS_TYPE> ref, @Nonnull final Event param) {
        assert Thread.currentThread() == this.thread;
        this.store.internal_invoke(this, ref, param);
    }
    
    @Override
    public <Event extends EcsEvent> void invoke(@Nonnull final EntityEventType<ECS_TYPE, Event> systemType, @Nonnull final Ref<ECS_TYPE> ref, @Nonnull final Event param) {
        assert Thread.currentThread() == this.thread;
        this.store.internal_invoke(this, systemType, ref, param);
    }
    
    @Override
    public <Event extends EcsEvent> void invoke(@Nonnull final Event param) {
        assert Thread.currentThread() == this.thread;
        this.store.internal_invoke(this, param);
    }
    
    @Override
    public <Event extends EcsEvent> void invoke(@Nonnull final WorldEventType<ECS_TYPE, Event> systemType, @Nonnull final Event param) {
        assert Thread.currentThread() == this.thread;
        this.store.internal_invoke(this, systemType, param);
    }
    
    void track(@Nonnull final Ref<ECS_TYPE> ref) {
        this.trackedRef = ref;
    }
    
    private void testRemovedTracked(@Nonnull final Ref<ECS_TYPE> ref) {
        if (ref.equals(this.trackedRef)) {
            this.trackedRefRemoved = true;
        }
        if (this.parentBuffer != null) {
            this.parentBuffer.testRemovedTracked(ref);
        }
    }
    
    boolean consumeWasTrackedRefRemoved() {
        if (this.trackedRef == null) {
            throw new IllegalStateException("Not tracking any ref!");
        }
        final boolean wasRemoved = this.trackedRefRemoved;
        this.trackedRefRemoved = false;
        return wasRemoved;
    }
    
    void consume() {
        this.trackedRef = null;
        this.trackedRefRemoved = false;
        assert Thread.currentThread() == this.thread;
        while (!this.queue.isEmpty()) {
            this.queue.pop().accept(this.store);
        }
        this.store.storeCommandBuffer(this);
    }
    
    @Nonnull
    public CommandBuffer<ECS_TYPE> fork() {
        final CommandBuffer<ECS_TYPE> forkedBuffer = this.store.takeCommandBuffer();
        forkedBuffer.parentBuffer = this;
        return forkedBuffer;
    }
    
    public void mergeParallel(@Nonnull final CommandBuffer<ECS_TYPE> commandBuffer) {
        this.trackedRef = null;
        this.trackedRefRemoved = false;
        this.parentBuffer = null;
        while (!this.queue.isEmpty()) {
            commandBuffer.queue.add(this.queue.pop());
        }
        this.store.storeCommandBuffer(this);
    }
    
    public boolean setThread() {
        boolean areAssertionsEnabled = false;
        assert areAssertionsEnabled = true;
        if (!areAssertionsEnabled) {
            throw new AssertionError((Object)"setThread should only be called when assertions are enabled!");
        }
        this.thread = Thread.currentThread();
        return true;
    }
    
    public void validateEmpty() {
        if (!this.queue.isEmpty()) {
            throw new AssertionError((Object)"CommandBuffer must be empty when returned to store!");
        }
    }
}
