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

package com.hypixel.hytale.server.core.modules.collision;

import com.hypixel.hytale.math.shape.Box;
import com.hypixel.hytale.math.vector.Vector3d;
import java.util.logging.Level;
import com.hypixel.hytale.server.core.entity.InteractionChain;
import com.hypixel.hytale.server.core.modules.interaction.interaction.config.Interaction;
import com.hypixel.hytale.server.core.entity.InteractionContext;
import com.hypixel.hytale.server.core.modules.interaction.interaction.config.RootInteraction;
import it.unimi.dsi.fastutil.longs.LongIterator;
import com.hypixel.hytale.server.core.asset.type.blocktype.config.BlockType;
import com.hypixel.hytale.server.core.universe.world.chunk.WorldChunk;
import com.hypixel.hytale.server.core.universe.world.World;
import com.hypixel.hytale.protocol.BlockPosition;
import com.hypixel.hytale.math.block.BlockUtil;
import com.hypixel.hytale.server.core.util.FillerBlockUtil;
import com.hypixel.hytale.protocol.InteractionType;
import com.hypixel.hytale.server.core.asset.type.fluid.Fluid;
import com.hypixel.hytale.math.util.ChunkUtil;
import com.hypixel.hytale.server.core.entity.LivingEntity;
import com.hypixel.hytale.component.ComponentAccessor;
import com.hypixel.hytale.server.core.universe.world.storage.EntityStore;
import com.hypixel.hytale.component.Ref;
import com.hypixel.hytale.server.core.entity.InteractionManager;
import javax.annotation.Nullable;
import java.util.function.Consumer;
import it.unimi.dsi.fastutil.objects.ObjectArrayList;
import it.unimi.dsi.fastutil.longs.LongOpenHashSet;
import it.unimi.dsi.fastutil.longs.LongSet;
import java.util.function.Predicate;
import com.hypixel.hytale.logger.HytaleLogger;
import com.hypixel.hytale.server.core.entity.Entity;
import java.util.List;
import javax.annotation.Nonnull;
import java.util.Comparator;
import com.hypixel.hytale.math.iterator.BoxBlockIterator;

public class CollisionResult implements BoxBlockIterator.BoxIterationConsumer
{
    public static final Comparator<BlockCollisionData> BLOCK_COLLISION_DATA_COMPARATOR;
    @Nonnull
    private final CollisionConfig collisionConfig;
    @Nonnull
    private final CollisionDataArray<BlockCollisionData> blockCollisions;
    @Nonnull
    private final CollisionDataArray<BlockCollisionData> blockSlides;
    @Nonnull
    private final CollisionDataArray<BlockCollisionData> blockTriggers;
    @Nonnull
    private final CollisionDataArray<CharacterCollisionData> characterCollisions;
    @Nonnull
    private final MovingBoxBoxCollisionEvaluator movingBoxBoxCollision;
    @Nonnull
    private final BoxBlockIntersectionEvaluator boxBlockIntersection;
    public List<Entity> collisionEntities;
    private boolean continueAfterCollision;
    private boolean haveNoCollision;
    private HytaleLogger logger;
    public double slideStart;
    public double slideEnd;
    public boolean isSliding;
    public int validate;
    private boolean checkForCharacterCollisions;
    private int walkableMaterialMask;
    public Predicate<CollisionConfig> isNonWalkable;
    private LongSet lastTriggers;
    private LongSet newTriggers;
    
    public CollisionResult() {
        this(true, false);
    }
    
    public CollisionResult(final boolean enableSlides, final boolean enableCharacters) {
        this.continueAfterCollision = true;
        this.haveNoCollision = true;
        this.lastTriggers = new LongOpenHashSet();
        this.newTriggers = new LongOpenHashSet();
        final ObjectArrayList<BlockCollisionData> blockCollisionDataFreePool = new ObjectArrayList<BlockCollisionData>();
        final ObjectArrayList<CharacterCollisionData> characterCollisionDataFreePool = new ObjectArrayList<CharacterCollisionData>();
        this.blockCollisions = new CollisionDataArray<BlockCollisionData>(BlockCollisionData::new, BlockCollisionData::clear, blockCollisionDataFreePool);
        this.blockSlides = new CollisionDataArray<BlockCollisionData>(BlockCollisionData::new, BlockCollisionData::clear, blockCollisionDataFreePool);
        this.blockTriggers = new CollisionDataArray<BlockCollisionData>(BlockCollisionData::new, BlockCollisionData::clear, blockCollisionDataFreePool);
        this.characterCollisions = new CollisionDataArray<CharacterCollisionData>(CharacterCollisionData::new, null, characterCollisionDataFreePool);
        (this.collisionConfig = new CollisionConfig()).setDefaultCollisionBehaviour();
        (this.movingBoxBoxCollision = new MovingBoxBoxCollisionEvaluator()).setCheckForOnGround(enableSlides);
        this.boxBlockIntersection = new BoxBlockIntersectionEvaluator();
        this.checkForCharacterCollisions = enableCharacters;
        this.setDefaultWalkableBehaviour();
    }
    
    @Nonnull
    public CollisionConfig getConfig() {
        return this.collisionConfig;
    }
    
    public List<Entity> getCollisionEntities() {
        return this.collisionEntities;
    }
    
    public void setCollisionEntities(final List<Entity> collisionEntities) {
        this.collisionEntities = collisionEntities;
    }
    
    @Nonnull
    public BoxBlockIntersectionEvaluator getBoxBlockIntersection() {
        return this.boxBlockIntersection;
    }
    
    @Nonnull
    public MovingBoxBoxCollisionEvaluator getMovingBoxBoxCollision() {
        return this.movingBoxBoxCollision;
    }
    
    public CharacterCollisionData allocCharacterCollision() {
        return this.characterCollisions.alloc();
    }
    
    public void addCollision(@Nonnull final IBlockCollisionEvaluator blockCollisionEvaluator, final int index) {
        if (blockCollisionEvaluator.getCollisionStart() > 1.0) {
            return;
        }
        blockCollisionEvaluator.setCollisionData(this.newCollision(), this.collisionConfig, index);
    }
    
    public BlockCollisionData newCollision() {
        return this.blockCollisions.alloc();
    }
    
    public void addSlide(@Nonnull final IBlockCollisionEvaluator blockCollisionEvaluator, final int index) {
        if (blockCollisionEvaluator.getCollisionStart() > 1.0) {
            return;
        }
        blockCollisionEvaluator.setCollisionData(this.newSlide(), this.collisionConfig, index);
    }
    
    public BlockCollisionData newSlide() {
        return this.blockSlides.alloc();
    }
    
    public void addTrigger(@Nonnull final IBlockCollisionEvaluator blockCollisionEvaluator, final int index) {
        if (blockCollisionEvaluator.getCollisionStart() > 1.0) {
            return;
        }
        blockCollisionEvaluator.setCollisionData(this.newTrigger(), this.collisionConfig, index);
    }
    
    public BlockCollisionData newTrigger() {
        return this.blockTriggers.alloc();
    }
    
    public void reset() {
        this.blockCollisions.reset();
        this.blockSlides.reset();
        this.blockTriggers.reset();
        this.characterCollisions.reset();
    }
    
    public void process() {
        this.blockCollisions.sort(BasicCollisionData.COLLISION_START_COMPARATOR);
        this.blockTriggers.sort(BasicCollisionData.COLLISION_START_COMPARATOR);
        this.characterCollisions.sort(BasicCollisionData.COLLISION_START_COMPARATOR);
        if (this.blockSlides.getCount() > 0) {
            this.blockSlides.sort(CollisionResult.BLOCK_COLLISION_DATA_COMPARATOR);
            BlockCollisionData slide = this.blockSlides.get(0);
            this.slideStart = slide.collisionStart;
            this.slideEnd = slide.collisionEnd;
            for (int i = 1; i < this.blockSlides.getCount(); ++i) {
                slide = this.blockSlides.get(i);
                if (slide.collisionStart <= this.slideEnd && slide.collisionEnd > this.slideEnd) {
                    this.slideEnd = slide.collisionEnd;
                }
            }
            this.isSliding = (this.slideStart <= 0.0);
            if (this.slideEnd > 1.0) {
                this.slideEnd = 1.0;
            }
        }
        else {
            this.isSliding = false;
        }
    }
    
    public int getBlockCollisionCount() {
        return this.blockCollisions.getCount();
    }
    
    public BlockCollisionData getBlockCollision(final int i) {
        return this.blockCollisions.get(i);
    }
    
    @Nullable
    public BlockCollisionData getFirstBlockCollision() {
        return this.blockCollisions.getFirst();
    }
    
    @Nullable
    public BlockCollisionData forgetFirstBlockCollision() {
        return this.blockCollisions.forgetFirst();
    }
    
    public int getCharacterCollisionCount() {
        return this.characterCollisions.getCount();
    }
    
    @Nullable
    public CharacterCollisionData getFirstCharacterCollision() {
        return this.characterCollisions.getFirst();
    }
    
    @Nullable
    public CharacterCollisionData forgetFirstCharacterCollision() {
        return this.characterCollisions.forgetFirst();
    }
    
    public void pruneTriggerBlocks(final double distance) {
        for (int l = this.blockTriggers.size() - 1; l >= 0; --l) {
            final BlockCollisionData blockCollisionData = this.blockTriggers.get(l);
            if (blockCollisionData.collisionStart <= distance) {
                break;
            }
            this.blockTriggers.remove(l);
        }
    }
    
    @Nonnull
    public CollisionDataArray<BlockCollisionData> getTriggerBlocks() {
        return this.blockTriggers;
    }
    
    public int defaultTriggerBlocksProcessing(@Nonnull final InteractionManager manager, @Nonnull final Entity entity, @Nonnull final Ref<EntityStore> ref, final boolean executeTriggers, @Nonnull final ComponentAccessor<EntityStore> componentAccessor) {
        final World world = componentAccessor.getExternalData().getWorld();
        final LongSet temp = this.lastTriggers;
        this.lastTriggers = this.newTriggers;
        (this.newTriggers = temp).clear();
        int damageToEntity = 0;
        final CollisionDataArray<BlockCollisionData> triggerBlocks = this.getTriggerBlocks();
        for (int i = 0, size = triggerBlocks.size(); i < size; ++i) {
            final BlockCollisionData triggerCollision = triggerBlocks.get(i);
            if (triggerCollision.blockType != null) {
                final int damageToEntities = Math.max(triggerCollision.blockType.getDamageToEntities(), triggerCollision.fluid.getDamageToEntities());
                if (damageToEntities > damageToEntity) {
                    damageToEntity = damageToEntities;
                }
            }
            if (executeTriggers && entity instanceof LivingEntity) {
                final WorldChunk chunk = world.getChunkIfInMemory(ChunkUtil.indexChunkFromBlock(triggerCollision.x, triggerCollision.z));
                if (chunk != null) {
                    final BlockType blockType = chunk.getBlockType(triggerCollision.x, triggerCollision.y, triggerCollision.z);
                    final Fluid fluidType = Fluid.getAssetMap().getAsset(chunk.getFluidId(triggerCollision.x, triggerCollision.y, triggerCollision.z));
                    String interactionsEnter = blockType.getInteractions().get(InteractionType.CollisionEnter);
                    if (interactionsEnter == null) {
                        interactionsEnter = fluidType.getInteractions().get(InteractionType.CollisionEnter);
                    }
                    String interactions = blockType.getInteractions().get(InteractionType.Collision);
                    if (interactions == null) {
                        interactions = fluidType.getInteractions().get(InteractionType.Collision);
                    }
                    if (interactionsEnter != null || interactions != null) {
                        final int filler = chunk.getFiller(triggerCollision.x, triggerCollision.y, triggerCollision.z);
                        int x = triggerCollision.x;
                        int y = triggerCollision.y;
                        int z = triggerCollision.z;
                        if (filler != 0) {
                            x -= FillerBlockUtil.unpackX(filler);
                            y -= FillerBlockUtil.unpackY(filler);
                            z -= FillerBlockUtil.unpackZ(filler);
                        }
                        final long index = BlockUtil.packUnchecked(x, y, z);
                        if (this.newTriggers.add(index)) {
                            final BlockPosition pos = new BlockPosition(x, y, z);
                            if (!this.lastTriggers.remove(index) && interactionsEnter != null) {
                                this.doCollisionInteraction(manager, InteractionType.CollisionEnter, ref, interactionsEnter, pos, componentAccessor);
                            }
                            if (interactions != null) {
                                this.doCollisionInteraction(manager, InteractionType.Collision, ref, interactions, pos, componentAccessor);
                            }
                        }
                    }
                }
            }
        }
        if (executeTriggers && entity instanceof LivingEntity && !this.lastTriggers.isEmpty()) {
            for (final Long old : this.lastTriggers) {
                final int x2 = BlockUtil.unpackX(old);
                final int y2 = BlockUtil.unpackY(old);
                final int z2 = BlockUtil.unpackZ(old);
                final WorldChunk chunk2 = world.getChunkIfInMemory(ChunkUtil.indexChunkFromBlock(x2, z2));
                if (chunk2 != null) {
                    final BlockType blockType2 = chunk2.getBlockType(x2, y2, z2);
                    final Fluid fluidType2 = Fluid.getAssetMap().getAsset(chunk2.getFluidId(x2, y2, z2));
                    String interactions2 = blockType2.getInteractions().get(InteractionType.CollisionLeave);
                    if (interactions2 == null) {
                        interactions2 = fluidType2.getInteractions().get(InteractionType.CollisionLeave);
                    }
                    if (interactions2 == null) {
                        continue;
                    }
                    this.doCollisionInteraction(manager, InteractionType.CollisionLeave, ref, interactions2, new BlockPosition(x2, y2, z2), componentAccessor);
                }
            }
        }
        return damageToEntity;
    }
    
    private void doCollisionInteraction(@Nonnull final InteractionManager manager, @Nonnull final InteractionType type, @Nonnull final Ref<EntityStore> ref, @Nonnull final String interactions, @Nonnull final BlockPosition pos, @Nonnull final ComponentAccessor<EntityStore> componentAccessor) {
        final RootInteraction root = RootInteraction.getRootInteractionOrUnknown(interactions);
        final World world = componentAccessor.getExternalData().getWorld();
        final InteractionContext context = InteractionContext.forInteraction(manager, ref, type, componentAccessor);
        context.getMetaStore().putMetaObject(Interaction.TARGET_BLOCK_RAW, pos);
        context.getMetaStore().putMetaObject(Interaction.TARGET_BLOCK, world.getBaseBlock(pos));
        final InteractionChain chain = manager.initChain(type, context, root, -1, pos, false);
        manager.queueExecuteChain(chain);
    }
    
    @Override
    public boolean next() {
        return this.continueAfterCollision || this.haveNoCollision;
    }
    
    @Override
    public boolean accept(long x, long y, long z) {
        if (this.collisionConfig.canCollide((int)x, (int)y, (int)z)) {
            x += this.collisionConfig.getBoundingBoxOffsetX();
            y += this.collisionConfig.getBoundingBoxOffsetY();
            z += this.collisionConfig.getBoundingBoxOffsetZ();
            final int numDetails = this.collisionConfig.getDetailCount();
            boolean haveCollision = this.movingBoxBoxCollision.isBoundingBoxColliding(this.collisionConfig.getBoundingBox(), (double)x, (double)y, (double)z);
            if (this.logger != null) {
                final Object arg7 = (this.collisionConfig.blockType != null) ? this.collisionConfig.blockType.getId() : "null";
                this.logger.at(Level.INFO).log("?? Block Test at %s/%s/%s numDet=%d haveColl=%s overlap=%s blockType=%s", x, y, z, numDetails, haveCollision, this.movingBoxBoxCollision.isOverlapping(), arg7);
            }
            if (numDetails <= 1) {
                this.processCollisionResult(haveCollision, 0);
            }
            else if (haveCollision || this.movingBoxBoxCollision.isOverlapping() || this.movingBoxBoxCollision.isTouching()) {
                for (int i = 0; i < numDetails; ++i) {
                    haveCollision = this.movingBoxBoxCollision.isBoundingBoxColliding(this.collisionConfig.getBoundingBox(i), (double)x, (double)y, (double)z);
                    this.processCollisionResult(haveCollision, i);
                }
            }
        }
        else if (this.logger != null) {
            final Object arg8 = (this.collisionConfig.blockType != null) ? this.collisionConfig.blockType.getId() : "null";
            this.logger.at(Level.INFO).log("-- Ignoring block at %s/%s/%s blockType=%s", x, y, z, arg8);
        }
        return true;
    }
    
    private void processCollisionResult(boolean haveCollision, final int hitboxIndex) {
        if (this.logger != null) {
            this.logger.at(Level.INFO).log("?? Further testing block haveCol=%s hitBoxIndex=%s onGround=%s touching=%s canCollide=%s canTrigger=%s", haveCollision, hitboxIndex, this.movingBoxBoxCollision.isOnGround(), this.movingBoxBoxCollision.isTouching(), this.collisionConfig.blockCanCollide, this.collisionConfig.blockCanTrigger);
        }
        if (this.collisionConfig.blockCanCollide) {
            boolean isNoSlideCollision = true;
            if (this.movingBoxBoxCollision.onGround) {
                haveCollision = (this.collisionConfig.blockType == null || this.isNonWalkable.test(this.collisionConfig));
                if (!haveCollision) {
                    this.addSlide(this.movingBoxBoxCollision, hitboxIndex);
                    if (this.collisionConfig.blockCanTrigger) {
                        this.addTrigger(this.movingBoxBoxCollision, hitboxIndex);
                    }
                    if (this.logger != null) {
                        this.logger.at(Level.INFO).log("++ Sliding block start=%s end=%s normal=%s", this.movingBoxBoxCollision.getCollisionStart(), this.movingBoxBoxCollision.getCollisionEnd(), Vector3d.formatShortString(this.movingBoxBoxCollision.getCollisionNormal()));
                    }
                    return;
                }
                isNoSlideCollision = false;
                if (this.logger != null) {
                    this.logger.at(Level.INFO).log("?? Sliding block is unwalkable start=%s end=%s normal=%s", this.movingBoxBoxCollision.getCollisionStart(), this.movingBoxBoxCollision.getCollisionEnd(), Vector3d.formatShortString(this.movingBoxBoxCollision.getCollisionNormal()));
                }
            }
            if (haveCollision) {
                this.addCollision(this.movingBoxBoxCollision, hitboxIndex);
                if (isNoSlideCollision) {
                    this.haveNoCollision = false;
                }
                if (this.logger != null) {
                    this.logger.at(Level.INFO).log("++ Collision with block start=%s end=%s normal=%s", this.movingBoxBoxCollision.collisionStart, this.movingBoxBoxCollision.collisionEnd, Vector3d.formatShortString(this.movingBoxBoxCollision.collisionNormal));
                }
            }
        }
        if (this.collisionConfig.blockCanTrigger && (haveCollision || this.movingBoxBoxCollision.isTouching())) {
            if (this.logger != null) {
                this.logger.at(Level.INFO).log("++ Trigger block start=%s end=%s normal=%s", this.movingBoxBoxCollision.getCollisionStart(), this.movingBoxBoxCollision.getCollisionEnd(), Vector3d.formatShortString(this.movingBoxBoxCollision.getCollisionNormal()));
            }
            this.addTrigger(this.movingBoxBoxCollision, hitboxIndex);
        }
    }
    
    public void iterateBlocks(@Nonnull final Box collider, @Nonnull final Vector3d pos, @Nonnull final Vector3d direction, final double length, final boolean stopOnCollisionFound) {
        this.continueAfterCollision = !stopOnCollisionFound;
        BoxBlockIterator.iterate(collider, pos, direction, length, this);
    }
    
    public void acquireCollisionModule() {
        this.haveNoCollision = true;
    }
    
    public void disableSlides() {
        this.movingBoxBoxCollision.setCheckForOnGround(false);
    }
    
    public void enableSlides() {
        this.movingBoxBoxCollision.setCheckForOnGround(true);
    }
    
    public void disableCharacterCollisions() {
        this.checkForCharacterCollisions = false;
    }
    
    public void enableCharacterCollsions() {
        this.checkForCharacterCollisions = true;
    }
    
    public boolean isCheckingForCharacterCollisions() {
        return this.checkForCharacterCollisions;
    }
    
    public void enableTriggerBlocks() {
        this.collisionConfig.setCheckTriggerBlocks(true);
    }
    
    public void disableTriggerBlocks() {
        this.collisionConfig.setCheckTriggerBlocks(false);
    }
    
    public boolean isCheckingTriggerBlocks() {
        return this.collisionConfig.isCheckTriggerBlocks();
    }
    
    public void enableDamageBlocks() {
        this.collisionConfig.setCheckDamageBlocks(true);
    }
    
    public void disableDamageBlocks() {
        this.collisionConfig.setCheckDamageBlocks(false);
    }
    
    public boolean isCheckingDamageBlocks() {
        return this.collisionConfig.isCheckDamageBlocks();
    }
    
    public boolean setDamageBlocking(final boolean blocking) {
        final boolean oldState = this.collisionConfig.setCollideWithDamageBlocks(blocking);
        this.updateDamageWalkableFlag();
        return oldState;
    }
    
    public boolean isDamageBlocking() {
        return this.collisionConfig.isCollidingWithDamageBlocks();
    }
    
    public void setCollisionByMaterial(final int collidingMaterials) {
        this.collisionConfig.setCollisionByMaterial(collidingMaterials);
    }
    
    public void setCollisionByMaterial(final int collidingMaterials, final int walkableMaterials) {
        this.collisionConfig.setCollisionByMaterial(collidingMaterials);
        this.setWalkableByMaterial(walkableMaterials);
    }
    
    public int getCollisionByMaterial() {
        return this.collisionConfig.getCollisionByMaterial();
    }
    
    public void setDefaultCollisionBehaviour() {
        this.collisionConfig.setDefaultCollisionBehaviour();
    }
    
    public void setDefaultBlockCollisionPredicate() {
        this.collisionConfig.setDefaultBlockCollisionPredicate();
    }
    
    public void setDefaultNonWalkablePredicate() {
        this.isNonWalkable = (collisionConfig -> {
            final int matches = collisionConfig.blockMaterialMask & this.walkableMaterialMask;
            return matches == 0 || (matches & 0x10) != 0x0;
        });
    }
    
    public void setNonWalkablePredicate(final Predicate<CollisionConfig> classifier) {
        this.isNonWalkable = classifier;
    }
    
    public void setWalkableByMaterial(final int walkableMaterial) {
        this.walkableMaterialMask = (0xF & walkableMaterial);
        this.updateDamageWalkableFlag();
    }
    
    protected void updateDamageWalkableFlag() {
        if (this.collisionConfig.isCollidingWithDamageBlocks()) {
            this.walkableMaterialMask |= 0x10;
        }
        else {
            this.walkableMaterialMask &= 0xFFFFFFEF;
        }
    }
    
    public void setDefaultWalkableBehaviour() {
        this.setDefaultNonWalkablePredicate();
        this.setWalkableByMaterial(5);
    }
    
    public void setDefaultPlayerSettings() {
        this.enableSlides();
        this.disableCharacterCollisions();
        this.setDefaultNonWalkablePredicate();
        this.setDefaultBlockCollisionPredicate();
        this.setCollisionByMaterial(4);
        this.setWalkableByMaterial(15);
    }
    
    public boolean isComputeOverlaps() {
        return this.movingBoxBoxCollision.isComputeOverlaps();
    }
    
    public void setComputeOverlaps(final boolean computeOverlaps) {
        this.movingBoxBoxCollision.setComputeOverlaps(computeOverlaps);
    }
    
    public HytaleLogger getLogger() {
        return this.logger;
    }
    
    public boolean shouldLog() {
        return this.logger != null;
    }
    
    public void setLogger(final HytaleLogger logger) {
        this.logger = logger;
    }
    
    static {
        BLOCK_COLLISION_DATA_COMPARATOR = Comparator.comparingDouble(a -> a.collisionStart).thenComparingDouble(a -> a.collisionEnd);
    }
}
