package net.minecraft.world.phys;

import net.minecraft.world.phys.Vec3;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Direction;
import net.minecraft.world.entity.Entity;

import org.bukkit.block.BlockFace;
import com.bergerkiller.bukkit.common.bases.IntVector3;

import com.bergerkiller.generated.net.minecraft.world.phys.HitResultHandle;
import com.bergerkiller.generated.net.minecraft.world.phys.BlockHitResultHandle;
import com.bergerkiller.generated.net.minecraft.world.phys.EntityHitResultHandle;

class HitResult {
#if version >= 1.14
    // Note: has remappings in hitresult_remappings.txt
    public (org.bukkit.util.Vector) Vec3 getLocation();
#else
    public (org.bukkit.util.Vector) Vec3 getLocation() {
        return instance.pos;
    }
#endif

    // Properly converts HitResult into Block/Entity hit result, if it is of that type
    public static HitResultHandle createHandle((Object) HitResult hitResult) {
#if version >= 1.14
        if (hitResult instanceof net.minecraft.world.phys.EntityHitResult && hitResult.getType() == net.minecraft.world.phys.HitResult$Type.ENTITY) {
            return (EntityHitResultHandle) EntityHitResultHandle.T.createHandle(hitResult);
        } else if (hitResult instanceof net.minecraft.world.phys.BlockHitResult) {
            return (BlockHitResultHandle) BlockHitResultHandle.T.createHandle(hitResult);
        }
#else
        if (hitResult.type == net.minecraft.world.phys.HitResult$Type.ENTITY) {
            return (EntityHitResultHandle) EntityHitResultHandle.T.createHandle(hitResult);
        } else {
            return (BlockHitResultHandle) BlockHitResultHandle.T.createHandle(hitResult);
        }
#endif

        // Probably never hit, but maybe someone creates a custom HitResult type not covered here.
        return (HitResultHandle) HitResultHandle.T.createHandle(hitResult);
    }
}

class BlockHitResult extends HitResult {
    // Note: has remappings in hitresult_remappings.txt
    public (IntVector3) BlockPos getBlockPos();

    // Convenience methods to get the delta x/y/z
    // This is what the < 1.9 player interaction packet used
    public org.bukkit.util.Vector getRelativePosition() {
        BlockPos blockPos = instance.getBlockPos();
#if version >= 1.14
        Vec3 pos = instance.getLocation();
#else
        Vec3 pos = instance.pos;
#endif
        return new org.bukkit.util.Vector(pos.x - blockPos.getX(), pos.y - blockPos.getY(), pos.z - blockPos.getZ());
    }

#if version >= 1.14
    public (BlockFace) Direction getDirection();
#else
    public (BlockFace) Direction getDirection() {
        return instance.direction;
    }
#endif

#if version >= 1.18
    public boolean isInside();
#elseif version >= 1.14
    public boolean isInside:d();
#else
    public boolean isInside() { return false; }
#endif

#if version >= 1.21.2
    public boolean isWorldBorderHit();
#else
    public boolean isWorldBorderHit() { return false; }
#endif

    public boolean isMiss() {
#if version >= 1.14
        return instance.getType() == net.minecraft.world.phys.HitResult$Type.MISS;
#else
        return instance.type == net.minecraft.world.phys.HitResult$Type.MISS;
#endif
    }

#if version >= 1.18
    public static (BlockHitResultHandle) BlockHitResult createNewMiss:miss((org.bukkit.util.Vector) Vec3 location, (BlockFace) Direction direction, (IntVector3) BlockPos pos);
#elseif version >= 1.14
    public static (BlockHitResultHandle) BlockHitResult createNewMiss:a((org.bukkit.util.Vector) Vec3 location, (BlockFace) Direction direction, (IntVector3) BlockPos pos);
#else
    public static (BlockHitResultHandle) BlockHitResult createNewMiss((org.bukkit.util.Vector) Vec3 location, (BlockFace) Direction direction, (IntVector3) BlockPos pos) {
        return new BlockHitResult(net.minecraft.world.phys.HitResult$Type.MISS, location, direction, pos);
    }
#endif

    public static (BlockHitResultHandle) BlockHitResult createNew((org.bukkit.util.Vector) Vec3 location, (BlockFace) Direction direction, (IntVector3) BlockPos pos, boolean inside) {
#if version >= 1.14
        return new BlockHitResult(location, direction, pos, inside);
#else
        return new BlockHitResult(location, direction, pos);
#endif
    }

    <code>
    public BlockHitResultHandle withLocation(org.bukkit.util.Vector location) {
        return createNew(location, getDirection(), getBlockPos(), isInside());
    }

    public BlockHitResultHandle withRelativePosition(org.bukkit.util.Vector relativeLocation) {
        IntVector3 blockPos = getBlockPos();
        return withLocation(new org.bukkit.util.Vector(
            relativeLocation.getX() + blockPos.x,
            relativeLocation.getY() + blockPos.y,
            relativeLocation.getZ() + blockPos.z
        ));
    }
    </code>
}

class EntityHitResult extends HitResult {
#if version >= 1.14
    public (org.bukkit.entity.Entity) Entity getEntity();
#else
    public (org.bukkit.entity.Entity) Entity getEntity() {
        return instance.entity;
    }
#endif

    public static (EntityHitResultHandle) EntityHitResult createNew((org.bukkit.entity.Entity) Entity entity) {
        return new EntityHitResult(entity);
    }

    public static (EntityHitResultHandle) EntityHitResult createNewAt((org.bukkit.entity.Entity) Entity entity, (org.bukkit.util.Vector) Vec3 location) {
        return new EntityHitResult(entity, location);
    }

    <code>
    public EntityHitResultHandle withLocation(org.bukkit.util.Vector location) {
        return createNewAt(getEntity(), location);
    }
    </code>
}
