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

package com.hypixel.hytale.server.npc.instructions;

import com.hypixel.hytale.server.npc.NPCPlugin;
import com.hypixel.hytale.function.consumer.QuadConsumer;
import java.util.function.BiConsumer;
import java.util.function.Consumer;
import com.hypixel.hytale.server.npc.sensorinfo.InfoProvider;
import com.hypixel.hytale.server.npc.role.support.DebugSupport;
import java.util.logging.Level;
import com.hypixel.hytale.server.core.entity.UUIDComponent;
import com.hypixel.hytale.component.Store;
import com.hypixel.hytale.server.npc.util.ComponentInfo;
import com.hypixel.hytale.server.core.universe.world.World;
import com.hypixel.hytale.component.ComponentAccessor;
import com.hypixel.hytale.server.npc.movement.controllers.MotionController;
import com.hypixel.hytale.server.npc.entities.NPCEntity;
import com.hypixel.hytale.server.core.universe.world.storage.EntityStore;
import com.hypixel.hytale.component.Ref;
import com.hypixel.hytale.server.npc.role.Role;
import com.hypixel.hytale.server.npc.instructions.builders.BuilderInstruction;
import com.hypixel.hytale.server.npc.asset.builder.BuilderSupport;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import com.hypixel.hytale.server.npc.util.IAnnotatedComponent;
import com.hypixel.hytale.logger.HytaleLogger;
import com.hypixel.hytale.server.npc.util.IAnnotatedComponentCollection;

public class Instruction implements RoleStateChange, IAnnotatedComponentCollection
{
    public static final Instruction[] EMPTY_ARRAY;
    public static final HytaleLogger LOGGER;
    protected IAnnotatedComponent parent;
    @Nullable
    protected final String name;
    @Nullable
    protected final String tag;
    protected final Sensor sensor;
    protected int index;
    protected final Instruction[] instructionList;
    @Nullable
    protected final BodyMotion bodyMotion;
    @Nullable
    protected final HeadMotion headMotion;
    @Nonnull
    protected final ActionList actions;
    protected final double weight;
    protected final boolean treeMode;
    protected final boolean invertTreeModeResult;
    protected boolean continueAfter;
    @Nullable
    protected Instruction parentTreeModeStep;
    
    private Instruction(final Instruction[] instructionList, @Nonnull final BuilderSupport support) {
        this.tag = null;
        this.name = "Root";
        this.sensor = Sensor.NULL;
        this.instructionList = instructionList;
        this.bodyMotion = null;
        this.headMotion = null;
        this.actions = ActionList.EMPTY_ACTION_LIST;
        this.continueAfter = false;
        this.treeMode = false;
        this.invertTreeModeResult = false;
        this.weight = 1.0;
        final int index = support.getInstructionSlot(this.name);
        support.putInstruction(index, this);
    }
    
    public Instruction(@Nonnull final BuilderInstruction builder, final Sensor sensor, @Nullable final Instruction[] instructionList, @Nonnull final BuilderSupport support) {
        this.tag = builder.getTag();
        this.name = builder.getName();
        this.sensor = sensor;
        if (instructionList != null) {
            this.instructionList = instructionList;
            this.bodyMotion = null;
            this.headMotion = null;
            this.actions = ActionList.EMPTY_ACTION_LIST;
        }
        else {
            this.instructionList = Instruction.EMPTY_ARRAY;
            this.bodyMotion = builder.getBodyMotion(support);
            this.headMotion = builder.getHeadMotion(support);
            this.actions = builder.getActionList(support);
        }
        this.continueAfter = builder.isContinueAfter();
        this.treeMode = builder.isTreeMode();
        this.invertTreeModeResult = builder.isInvertTreeModeResult(support);
        this.weight = builder.getChance(support);
        final int index = support.getInstructionSlot(this.name);
        support.putInstruction(index, this);
    }
    
    public Sensor getSensor() {
        return this.sensor;
    }
    
    @Nullable
    public String getDebugTag() {
        return this.tag;
    }
    
    public double getWeight() {
        return this.weight;
    }
    
    public boolean isContinueAfter() {
        return this.continueAfter;
    }
    
    @Nullable
    public BodyMotion getBodyMotion() {
        return this.bodyMotion;
    }
    
    @Nullable
    public HeadMotion getHeadMotion() {
        return this.headMotion;
    }
    
    @Override
    public void registerWithSupport(final Role role) {
        this.sensor.registerWithSupport(role);
        for (final Instruction instruction : this.instructionList) {
            instruction.registerWithSupport(role);
        }
        if (this.bodyMotion != null) {
            this.bodyMotion.registerWithSupport(role);
        }
        if (this.headMotion != null) {
            this.headMotion.registerWithSupport(role);
        }
        this.actions.registerWithSupport(role);
    }
    
    @Override
    public void motionControllerChanged(@Nullable final Ref<EntityStore> ref, @Nonnull final NPCEntity npcComponent, final MotionController motionController, @Nullable final ComponentAccessor<EntityStore> componentAccessor) {
        this.sensor.motionControllerChanged(ref, npcComponent, motionController, componentAccessor);
        this.forEachInstruction((instruction, motionController1) -> instruction.motionControllerChanged(ref, npcComponent, motionController1, componentAccessor), motionController);
        if (this.bodyMotion != null) {
            this.bodyMotion.motionControllerChanged(ref, npcComponent, motionController, componentAccessor);
        }
        if (this.headMotion != null) {
            this.headMotion.motionControllerChanged(ref, npcComponent, motionController, componentAccessor);
        }
        this.actions.motionControllerChanged(ref, npcComponent, motionController, componentAccessor);
    }
    
    @Override
    public void loaded(final Role role) {
        this.sensor.loaded(role);
        this.forEachInstruction(Instruction::loaded, role);
        if (this.bodyMotion != null) {
            this.bodyMotion.loaded(role);
        }
        if (this.headMotion != null) {
            this.headMotion.loaded(role);
        }
        this.actions.loaded(role);
    }
    
    @Override
    public void spawned(final Role role) {
        this.sensor.spawned(role);
        this.forEachInstruction(Instruction::spawned, role);
        if (this.bodyMotion != null) {
            this.bodyMotion.spawned(role);
        }
        if (this.headMotion != null) {
            this.headMotion.spawned(role);
        }
        this.actions.spawned(role);
    }
    
    @Override
    public void unloaded(final Role role) {
        this.sensor.unloaded(role);
        this.forEachInstruction(Instruction::unloaded, role);
        if (this.bodyMotion != null) {
            this.bodyMotion.unloaded(role);
        }
        if (this.headMotion != null) {
            this.headMotion.unloaded(role);
        }
        this.actions.unloaded(role);
    }
    
    @Override
    public void removed(final Role role) {
        this.sensor.removed(role);
        this.forEachInstruction(Instruction::removed, role);
        if (this.bodyMotion != null) {
            this.bodyMotion.removed(role);
        }
        if (this.headMotion != null) {
            this.headMotion.removed(role);
        }
        this.actions.removed(role);
    }
    
    @Override
    public void teleported(final Role role, final World from, final World to) {
        this.sensor.teleported(role, from, to);
        this.forEachInstruction(Instruction::teleported, role, from, to);
        if (this.bodyMotion != null) {
            this.bodyMotion.teleported(role, from, to);
        }
        if (this.headMotion != null) {
            this.headMotion.teleported(role, from, to);
        }
        this.actions.teleported(role, from, to);
    }
    
    @Override
    public int componentCount() {
        int count = 1;
        count += this.actions.actionCount();
        count += this.instructionList.length;
        if (this.bodyMotion != null) {
            ++count;
        }
        if (this.headMotion != null) {
            ++count;
        }
        return count;
    }
    
    @Override
    public IAnnotatedComponent getComponent(final int index) {
        if (index < 1) {
            return this.sensor;
        }
        if (index <= this.actions.actionCount()) {
            return this.actions.getComponent(index - 1);
        }
        if (index >= this.componentCount()) {
            throw new IndexOutOfBoundsException();
        }
        if (this.bodyMotion != null) {
            if (this.headMotion != null && index == this.componentCount() - 1) {
                return this.headMotion;
            }
            return this.bodyMotion;
        }
        else {
            if (this.headMotion != null) {
                return this.headMotion;
            }
            return this.instructionList[index - 1];
        }
    }
    
    @Override
    public void getInfo(final Role role, @Nonnull final ComponentInfo holder) {
        if (this.name != null && !this.name.isEmpty()) {
            holder.addField("Name: " + this.name);
        }
    }
    
    @Override
    public IAnnotatedComponent getParent() {
        return this.parent;
    }
    
    @Override
    public int getIndex() {
        return this.index;
    }
    
    @Nonnull
    @Override
    public String getLabel() {
        final String tag = this.getDebugTag();
        if (tag != null) {
            return tag;
        }
        return (this.index >= 0) ? String.format("[%s]%s", this.index, this.getClass().getSimpleName()) : this.getClass().getSimpleName();
    }
    
    @Override
    public void setContext(final IAnnotatedComponent parent, final int index) {
        this.parent = parent;
        this.index = index;
        this.sensor.setContext(this, -1);
        if (this.bodyMotion != null) {
            this.bodyMotion.setContext(this, -1);
        }
        if (this.headMotion != null) {
            this.headMotion.setContext(this, -1);
        }
        this.actions.setContext(this);
        for (int i = 0; i < this.instructionList.length; ++i) {
            this.instructionList[i].setContext(this, i);
        }
    }
    
    public boolean matches(@Nonnull final Ref<EntityStore> ref, @Nonnull final Role role, final double dt, @Nonnull final Store<EntityStore> store) {
        final DebugSupport debugSupport = role.getDebugSupport();
        final boolean traceSensorFails = debugSupport.isTraceSensorFails();
        if (traceSensorFails) {
            debugSupport.setLastFailingSensor(this.sensor);
        }
        if (this.sensor.matches(ref, role, dt, store)) {
            if (!this.treeMode && !this.continueAfter) {
                role.notifySensorMatch();
            }
            if (traceSensorFails) {
                debugSupport.setLastFailingSensor(null);
            }
            return true;
        }
        if (debugSupport.isTraceFail()) {
            final UUIDComponent uuidComponent = store.getComponent(ref, UUIDComponent.getComponentType());
            assert uuidComponent != null;
            Instruction.LOGGER.at(Level.INFO).log("Instruction Sensor FAIL uuid=%d, debug=%s", uuidComponent.getUuid(), this.getBreadCrumbs());
        }
        if (traceSensorFails) {
            Instruction.LOGGER.at(Level.INFO).log("Sensor FAIL, sensor=%s", debugSupport.getLastFailingSensor().getBreadCrumbs());
            debugSupport.setLastFailingSensor(null);
        }
        return false;
    }
    
    public void executeActions(@Nonnull final Ref<EntityStore> ref, @Nonnull final Role role, final InfoProvider sensorInfo, final double dt, @Nonnull final Store<EntityStore> store) {
        if (this.actions.canExecute(ref, role, sensorInfo, dt, store)) {
            this.actions.execute(ref, role, sensorInfo, dt, store);
        }
    }
    
    public void execute(@Nonnull final Ref<EntityStore> ref, @Nonnull final Role role, final double dt, @Nonnull final Store<EntityStore> store) {
        if (this.instructionList.length > 0) {
            for (final Instruction instruction : this.instructionList) {
                if (instruction.matches(ref, role, dt, store)) {
                    instruction.onMatched(role);
                    instruction.execute(ref, role, dt, store);
                    instruction.onCompleted(role);
                    if (!instruction.isContinueAfter()) {
                        break;
                    }
                }
            }
            this.sensor.setOnce();
            this.sensor.done();
            return;
        }
        final InfoProvider sensorInfo = this.sensor.getSensorInfo();
        if (this.headMotion != null && role.getEntitySupport().setNextHeadMotionStep(this)) {
            this.headMotion.preComputeSteering(ref, role, sensorInfo, store);
        }
        if (this.bodyMotion != null && role.getEntitySupport().setNextBodyMotionStep(this)) {
            this.bodyMotion.preComputeSteering(ref, role, sensorInfo, store);
        }
        this.executeActions(ref, role, sensorInfo, dt, store);
        if (this.headMotion == null && this.bodyMotion == null) {
            this.sensor.setOnce();
            this.sensor.done();
        }
        if (role.getDebugSupport().isTraceSuccess()) {
            final UUIDComponent uuidComponent = store.getComponent(ref, UUIDComponent.getComponentType());
            assert uuidComponent != null;
            Instruction.LOGGER.at(Level.INFO).log("Instruction SUCC uuid=%d, debug=%s", uuidComponent.getUuid(), this.getBreadCrumbs());
        }
    }
    
    public void clearOnce() {
        this.sensor.clearOnce();
        this.forEachInstruction(Instruction::clearOnce);
        this.actions.clearOnce();
    }
    
    public void onEndMotion() {
        this.actions.onEndMotion();
    }
    
    public void onMatched(@Nonnull final Role role) {
        if (!this.treeMode) {
            return;
        }
        this.parentTreeModeStep = role.swapTreeModeSteps(this);
        this.continueAfter = true;
    }
    
    public void onCompleted(@Nonnull final Role role) {
        if (!this.treeMode) {
            return;
        }
        role.swapTreeModeSteps(this.parentTreeModeStep);
        if (this.parentTreeModeStep != null) {
            if (this.continueAfter == this.invertTreeModeResult) {
                this.parentTreeModeStep.notifyChildSensorMatch();
            }
            this.parentTreeModeStep = null;
        }
    }
    
    public void notifyChildSensorMatch() {
        if (!this.treeMode) {
            return;
        }
        this.continueAfter = false;
    }
    
    public void reset() {
        this.clearOnce();
    }
    
    protected void forEachInstruction(@Nonnull final Consumer<Instruction> instructionConsumer) {
        for (final Instruction instruction : this.instructionList) {
            instructionConsumer.accept(instruction);
        }
    }
    
    protected <T> void forEachInstruction(@Nonnull final BiConsumer<Instruction, T> instructionConsumer, final T obj) {
        for (final Instruction instruction : this.instructionList) {
            instructionConsumer.accept(instruction, obj);
        }
    }
    
    protected <T, U, V> void forEachInstruction(@Nonnull final QuadConsumer<Instruction, T, U, V> instructionConsumer, final T t, final U u, final V v) {
        for (final Instruction instruction : this.instructionList) {
            instructionConsumer.accept(instruction, t, u, v);
        }
    }
    
    @Nonnull
    public static Instruction createRootInstruction(final Instruction[] instructions, @Nonnull final BuilderSupport support) {
        return new Instruction(instructions, support);
    }
    
    static {
        EMPTY_ARRAY = new Instruction[0];
        LOGGER = NPCPlugin.get().getLogger();
    }
}
