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

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

import com.hypixel.hytale.math.vector.Vector3i;
import com.hypixel.hytale.math.util.MathUtil;
import com.hypixel.hytale.server.core.asset.type.fluid.Fluid;
import com.hypixel.hytale.server.core.asset.type.blockhitbox.BlockBoundingBoxes;
import com.hypixel.hytale.protocol.BlockMaterial;
import javax.annotation.Nonnull;
import com.hypixel.hytale.server.core.universe.world.World;
import com.hypixel.hytale.math.vector.Vector3d;
import javax.annotation.Nullable;
import com.hypixel.hytale.math.shape.Box;
import com.hypixel.hytale.math.iterator.BoxBlockIterator;

public class BlockCollisionProvider implements BoxBlockIterator.BoxIterationConsumer
{
    protected final BoxBlockIntersectionEvaluator boxBlockIntersectionEvaluator;
    protected final MovingBoxBoxCollisionEvaluator movingBoxBoxCollisionEvaluator;
    protected final BlockDataProvider blockData;
    protected final Box fluidBox;
    protected final CollisionTracker damageTracker;
    protected final CollisionTracker triggerTracker;
    protected final BlockTracker collisionTracker;
    protected int requestedCollisionMaterials;
    protected boolean reportOverlaps;
    @Nullable
    protected IBlockCollisionConsumer collisionConsumer;
    @Nullable
    protected IBlockTracker activeTriggers;
    @Nullable
    protected Vector3d motion;
    protected double relativeStopDistance;
    protected IBlockCollisionConsumer.Result collisionState;
    
    public BlockCollisionProvider() {
        this.boxBlockIntersectionEvaluator = new BoxBlockIntersectionEvaluator();
        this.movingBoxBoxCollisionEvaluator = new MovingBoxBoxCollisionEvaluator();
        this.blockData = new BlockDataProvider();
        this.fluidBox = new Box(Box.UNIT);
        this.damageTracker = new CollisionTracker();
        this.triggerTracker = new CollisionTracker();
        this.collisionTracker = new BlockTracker();
        this.requestedCollisionMaterials = 4;
    }
    
    public void setRequestedCollisionMaterials(final int requestedCollisionMaterials) {
        this.requestedCollisionMaterials = requestedCollisionMaterials;
    }
    
    public void setReportOverlaps(final boolean reportOverlaps) {
        this.reportOverlaps = reportOverlaps;
        this.movingBoxBoxCollisionEvaluator.setComputeOverlaps(reportOverlaps);
    }
    
    @Override
    public boolean next() {
        return this.onSliceFinished();
    }
    
    @Override
    public boolean accept(final long x, final long y, final long z) {
        return this.processBlockDynamic((int)x, (int)y, (int)z);
    }
    
    public void cast(@Nonnull final World world, @Nonnull final Box collider, @Nonnull final Vector3d pos, @Nonnull final Vector3d v, @Nonnull final IBlockCollisionConsumer collisionConsumer, @Nonnull final IBlockTracker activeTriggers, final double collisionStop) {
        if (CollisionModule.get().isDisabled()) {
            return;
        }
        this.collisionConsumer = collisionConsumer;
        this.activeTriggers = activeTriggers;
        this.motion = v;
        this.blockData.initialize(world);
        final boolean isFarDistance = !CollisionModule.isBelowMovementThreshold(v);
        if (isFarDistance) {
            this.castIterative(collider, pos, v, collisionStop);
        }
        else {
            this.castShortDistance(collider, pos, v);
        }
        collisionConsumer.onCollisionFinished();
        this.blockData.cleanup();
        this.triggerTracker.reset();
        this.damageTracker.reset();
        this.collisionConsumer = null;
        this.activeTriggers = null;
        this.motion = null;
    }
    
    protected void castShortDistance(@Nonnull final Box collider, @Nonnull final Vector3d pos, @Nonnull final Vector3d v) {
        this.boxBlockIntersectionEvaluator.setBox(collider, pos).offsetPosition(v);
        collider.forEachBlock(pos.x + v.x, pos.y + v.y, pos.z + v.z, 1.0E-5, this, (x, y, z, _this) -> _this.processBlockStatic(x, y, z));
        this.generateTriggerExit();
    }
    
    protected boolean processBlockStatic(final int x, final int y, final int z) {
        this.blockData.read(x, y, z);
        final BlockBoundingBoxes boundingBoxes = this.blockData.getBlockBoundingBoxes();
        final int blockX = this.blockData.originX(x);
        final int blockY = this.blockData.originY(y);
        final int blockZ = this.blockData.originZ(z);
        boolean trigger = this.blockData.isTrigger() && !this.triggerTracker.isTracked(blockX, blockY, blockZ);
        int damage = this.blockData.getBlockDamage();
        boolean canCollide = this.canCollide();
        final Box[] boxes = boundingBoxes.get(this.blockData.rotation).getDetailBoxes();
        this.boxBlockIntersectionEvaluator.setDamageAndSubmerged(damage, false);
        if (this.blockData.getBlockType().getMaterial() != BlockMaterial.Empty || (this.blockData.getBlockType().getMaterial() == BlockMaterial.Empty && this.blockData.getFluidId() == 0)) {
            if (damage != 0 && boundingBoxes.protrudesUnitBox()) {
                if (this.damageTracker.isTracked(blockX, blockY, blockZ)) {
                    damage = 0;
                }
                else {
                    this.damageTracker.trackNew(blockX, blockY, blockZ);
                }
            }
            if (canCollide && boundingBoxes.protrudesUnitBox()) {
                if (this.collisionTracker.isTracked(blockX, blockY, blockZ)) {
                    canCollide = false;
                }
                else {
                    this.collisionTracker.trackNew(blockX, blockY, blockZ);
                }
            }
            for (int i = 0; (canCollide || trigger || damage > 0) && i < boxes.length; ++i) {
                final Box box = boxes[i];
                if (!CollisionMath.isDisjoint(this.boxBlockIntersectionEvaluator.intersectBoxComputeTouch(box, blockX, blockY, blockZ))) {
                    if (canCollide || (this.boxBlockIntersectionEvaluator.isOverlapping() && this.reportOverlaps)) {
                        this.collisionConsumer.onCollision(blockX, blockY, blockZ, this.motion, this.boxBlockIntersectionEvaluator, this.blockData, box);
                        canCollide = false;
                    }
                    if (trigger) {
                        if (!this.activeTriggers.isTracked(blockX, blockY, blockZ)) {
                            this.activeTriggers.trackNew(blockX, blockY, blockZ);
                        }
                        this.triggerTracker.trackNew(blockX, blockY, blockZ);
                        trigger = false;
                    }
                    if (damage != 0) {
                        this.collisionConsumer.onCollisionDamage(blockX, blockY, blockZ, this.motion, this.boxBlockIntersectionEvaluator, this.blockData);
                        damage = 0;
                    }
                }
            }
            final Fluid fluid = this.blockData.getFluid();
            if (fluid != null && this.blockData.getFluidId() != 0) {
                this.processBlockStaticFluid(x, y, z, fluid, true);
            }
            return true;
        }
        if (trigger) {
            this.boxBlockIntersectionEvaluator.setDamageAndSubmerged(damage, false);
            for (final Box box2 : boxes) {
                if (!CollisionMath.isDisjoint(this.boxBlockIntersectionEvaluator.intersectBoxComputeTouch(box2, blockX, blockY, blockZ))) {
                    this.triggerTracker.trackNew(blockX, blockY, blockZ);
                    break;
                }
            }
        }
        this.processBlockStaticFluid(x, y, z, this.blockData.getFluid(), false);
        return true;
    }
    
    protected void processBlockStaticFluid(final int x, final int y, final int z, @Nonnull final Fluid fluid, final boolean submergeFluid) {
        final boolean processDamage = fluid.getDamageToEntities() != 0;
        final boolean processCollision = this.canCollide(2);
        if (processDamage || processCollision) {
            this.fluidBox.max.y = this.blockData.getFillHeight();
            if (!CollisionMath.isDisjoint(this.boxBlockIntersectionEvaluator.intersectBoxComputeTouch(this.fluidBox, x, y, z))) {
                this.boxBlockIntersectionEvaluator.setDamageAndSubmerged(fluid.getDamageToEntities(), submergeFluid);
                if (processCollision) {
                    this.collisionConsumer.onCollision(x, y, z, this.motion, this.boxBlockIntersectionEvaluator, this.blockData, this.fluidBox);
                }
                if (processDamage) {
                    this.collisionConsumer.onCollisionDamage(x, y, z, this.motion, this.boxBlockIntersectionEvaluator, this.blockData);
                }
            }
        }
    }
    
    protected boolean canCollide() {
        return this.canCollide(this.blockData.getCollisionMaterials());
    }
    
    protected boolean canCollide(final int collisionMaterials) {
        return (collisionMaterials & this.requestedCollisionMaterials) != 0x0;
    }
    
    protected void castIterative(@Nonnull final Box collider, @Nonnull final Vector3d pos, @Nonnull final Vector3d v, final double collisionStop) {
        this.relativeStopDistance = MathUtil.clamp(collisionStop, 0.0, 1.0);
        this.collisionState = IBlockCollisionConsumer.Result.CONTINUE;
        this.movingBoxBoxCollisionEvaluator.setCollider(collider).setMove(pos, v);
        int x = 0;
        int y = 0;
        int z = 0;
        collider.forEachBlock(pos, 1.0E-5, this, (x, y, z, _this) -> _this.processBlockDynamic(x, y, z));
        BoxBlockIterator.iterate(collider, pos, v, v.length(), this);
        for (int count = this.damageTracker.getCount(), i = 0; i < count; ++i) {
            final BlockContactData collision = this.damageTracker.getContactData(i);
            if (collision.getCollisionStart() <= this.relativeStopDistance) {
                final Vector3i position = this.damageTracker.getPosition(i);
                this.collisionConsumer.onCollisionDamage(position.x, position.y, position.z, this.motion, collision, this.damageTracker.getBlockData(i));
            }
        }
        this.generateTriggerExit();
        for (int count = this.triggerTracker.getCount(), i = 0; i < count; ++i) {
            final BlockContactData collision = this.triggerTracker.getContactData(i);
            if (collision.getCollisionStart() <= this.relativeStopDistance) {
                final Vector3i position = this.triggerTracker.getPosition(i);
                x = position.x;
                y = position.y;
                z = position.z;
                if (!this.activeTriggers.isTracked(x, y, z)) {
                    this.activeTriggers.trackNew(x, y, z);
                }
            }
        }
    }
    
    protected boolean onSliceFinished() {
        final IBlockCollisionConsumer.Result result = this.collisionConsumer.onCollisionSliceFinished();
        if (result != null && this.collisionState.ordinal() < result.ordinal()) {
            this.collisionState = result;
        }
        return this.collisionState == IBlockCollisionConsumer.Result.CONTINUE;
    }
    
    protected boolean processBlockDynamic(final int x, final int y, final int z) {
        this.blockData.read(x, y, z);
        final int blockX = this.blockData.originX(x);
        final int blockY = this.blockData.originY(y);
        final int blockZ = this.blockData.originZ(z);
        final BlockBoundingBoxes boundingBoxes = this.blockData.getBlockBoundingBoxes();
        final Box[] boxes = boundingBoxes.get(this.blockData.rotation).getDetailBoxes();
        final boolean canCollide = this.canCollide();
        int damage = this.blockData.getBlockDamage();
        boolean trigger = this.blockData.isTrigger();
        this.movingBoxBoxCollisionEvaluator.setDamageAndSubmerged(damage, false);
        BlockContactData triggerCollisionData = null;
        BlockContactData damageCollisionData = null;
        if (trigger) {
            triggerCollisionData = this.triggerTracker.getContactData(blockX, blockY, blockZ);
            if (triggerCollisionData != null) {
                trigger = false;
            }
        }
        if (damage != 0 && boundingBoxes.protrudesUnitBox()) {
            damageCollisionData = this.damageTracker.getContactData(blockX, blockY, blockZ);
            if (damageCollisionData != null) {
                damage = 0;
            }
        }
        if (this.blockData.getBlockType().getMaterial() != BlockMaterial.Empty || (this.blockData.getBlockType().getMaterial() == BlockMaterial.Empty && this.blockData.getFluidId() == 0)) {
            for (final Box box : boxes) {
                if (this.movingBoxBoxCollisionEvaluator.isBoundingBoxColliding(box, blockX, blockY, blockZ)) {
                    if (this.movingBoxBoxCollisionEvaluator.getCollisionStart() > this.relativeStopDistance) {
                        if (this.movingBoxBoxCollisionEvaluator.isOverlapping() && this.reportOverlaps) {
                            final IBlockCollisionConsumer.Result result = this.collisionConsumer.onCollision(blockX, blockY, blockZ, this.motion, this.movingBoxBoxCollisionEvaluator, this.blockData, box);
                            this.updateStopDistance(result);
                        }
                    }
                    else {
                        if (canCollide || (this.movingBoxBoxCollisionEvaluator.isOverlapping() && this.reportOverlaps)) {
                            final IBlockCollisionConsumer.Result result = this.collisionConsumer.onCollision(blockX, blockY, blockZ, this.motion, this.movingBoxBoxCollisionEvaluator, this.blockData, box);
                            this.updateStopDistance(result);
                        }
                        if (trigger) {
                            triggerCollisionData = this.processTriggerDynamic(blockX, blockY, blockZ, triggerCollisionData);
                        }
                        if (damage != 0) {
                            damageCollisionData = this.processDamageDynamic(blockX, blockY, blockZ, damageCollisionData);
                        }
                    }
                }
            }
            final Fluid fluid = this.blockData.getFluid();
            if (fluid != null && this.blockData.getFluidId() != 0) {
                this.processBlockDynamicFluid(x, y, z, fluid, damageCollisionData, true);
            }
            return this.collisionState != IBlockCollisionConsumer.Result.STOP_NOW;
        }
        if (trigger) {
            for (final Box box : boxes) {
                if (this.movingBoxBoxCollisionEvaluator.isBoundingBoxColliding(box, blockX, blockY, blockZ) && this.movingBoxBoxCollisionEvaluator.getCollisionStart() <= this.relativeStopDistance) {
                    triggerCollisionData = this.processTriggerDynamic(blockX, blockY, blockZ, triggerCollisionData);
                }
            }
        }
        this.processBlockDynamicFluid(x, y, z, this.blockData.getFluid(), damageCollisionData, false);
        return this.collisionState != IBlockCollisionConsumer.Result.STOP_NOW;
    }
    
    protected void processBlockDynamicFluid(final int x, final int y, final int z, @Nonnull final Fluid fluid, final BlockContactData damageCollisionData, final boolean isSubmergeFluid) {
        final boolean processDamage = fluid.getDamageToEntities() != 0;
        final boolean processCollision = this.canCollide(2);
        if (!processDamage && !processCollision) {
            return;
        }
        this.fluidBox.max.y = this.blockData.getFillHeight();
        if (this.movingBoxBoxCollisionEvaluator.isBoundingBoxColliding(this.fluidBox, x, y, z) && this.movingBoxBoxCollisionEvaluator.getCollisionStart() <= this.relativeStopDistance) {
            this.movingBoxBoxCollisionEvaluator.setDamageAndSubmerged(fluid.getDamageToEntities(), isSubmergeFluid);
            if (processCollision) {
                final IBlockCollisionConsumer.Result result = this.collisionConsumer.onCollision(x, y, z, this.motion, this.movingBoxBoxCollisionEvaluator, this.blockData, this.fluidBox);
                this.updateStopDistance(result);
            }
            if (processDamage) {
                this.processDamageDynamic(x, y, z, damageCollisionData);
            }
        }
    }
    
    @Nonnull
    protected BlockContactData processTriggerDynamic(final int blockX, final int blockY, final int blockZ, @Nullable final BlockContactData collisionData) {
        if (collisionData == null) {
            return this.triggerTracker.trackNew(blockX, blockY, blockZ, this.movingBoxBoxCollisionEvaluator, this.blockData);
        }
        final double collisionEnd = Math.max(collisionData.collisionEnd, this.movingBoxBoxCollisionEvaluator.getCollisionEnd());
        if (this.movingBoxBoxCollisionEvaluator.getCollisionStart() < collisionData.collisionStart) {
            collisionData.assign(this.movingBoxBoxCollisionEvaluator);
        }
        collisionData.collisionEnd = collisionEnd;
        return collisionData;
    }
    
    @Nonnull
    protected BlockContactData processDamageDynamic(final int blockX, final int blockY, final int blockZ, @Nullable final BlockContactData collisionData) {
        final IBlockCollisionConsumer.Result result = this.collisionConsumer.probeCollisionDamage(blockX, blockY, blockZ, this.motion, this.movingBoxBoxCollisionEvaluator, this.blockData);
        this.updateStopDistance(result);
        if (collisionData == null) {
            return this.damageTracker.trackNew(blockX, blockY, blockZ, this.movingBoxBoxCollisionEvaluator, this.blockData);
        }
        if (this.movingBoxBoxCollisionEvaluator.getCollisionStart() < collisionData.collisionStart) {
            collisionData.assign(this.movingBoxBoxCollisionEvaluator);
        }
        return collisionData;
    }
    
    protected void updateStopDistance(@Nullable final IBlockCollisionConsumer.Result result) {
        if (result == null || result == IBlockCollisionConsumer.Result.CONTINUE) {
            return;
        }
        if (this.movingBoxBoxCollisionEvaluator.collisionStart < this.relativeStopDistance) {
            this.relativeStopDistance = this.movingBoxBoxCollisionEvaluator.collisionStart;
        }
        if (result.ordinal() > this.collisionState.ordinal()) {
            this.collisionState = result;
        }
    }
    
    protected void generateTriggerExit() {
        for (int i = this.activeTriggers.getCount() - 1; i >= 0; --i) {
            final Vector3i p = this.activeTriggers.getPosition(i);
            if (!this.triggerTracker.isTracked(p.x, p.y, p.z)) {
                this.activeTriggers.untrack(p.x, p.y, p.z);
            }
        }
    }
}
