package codechicken.lib.raytracer;

import java.util.List;

import codechicken.lib.math.MathHelper;
import codechicken.lib.vec.BlockCoord;
import codechicken.lib.vec.Cuboid6;
import codechicken.lib.vec.Vector3;
import cpw.mods.fml.relauncher.Side;
import cpw.mods.fml.relauncher.SideOnly;
import net.minecraft.block.Block;
import net.minecraft.client.Minecraft;
import net.minecraft.entity.Entity;
import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.entity.player.EntityPlayerMP;
import net.minecraft.util.EnumMovingObjectType;
import net.minecraft.util.MovingObjectPosition;
import net.minecraft.util.Vec3;
import net.minecraft.world.World;

public class RayTracer
{
    private Vector3 vec = new Vector3();
    private Vector3 vec2 = new Vector3();

    private Vector3 s_vec = new Vector3();
    private double s_dist;
    private int s_side;
    private IndexedCuboid6 c_cuboid;
    
    public static RayTracer instance = new RayTracer();
    
    private void traceSide(int side, Vector3 start, Vector3 end, Cuboid6 cuboid)
    {
        vec.set(start);
        Vector3 hit = null;
        switch(side)
        {
            case 0:
                hit = vec.XZintercept(end, cuboid.min.y);
                break;
            case 1:
                hit = vec.XZintercept(end, cuboid.max.y);
                break;
            case 2:
                hit = vec.XYintercept(end, cuboid.min.z);
                break;
            case 3:
                hit = vec.XYintercept(end, cuboid.max.z);
                break;
            case 4:
                hit = vec.YZintercept(end, cuboid.min.x);
                break;
            case 5:
                hit = vec.YZintercept(end, cuboid.max.x);
                break;
        }
        if(hit == null)
            return;
        
        switch(side)
        {
            case 0:
            case 1:
                if(!MathHelper.between(cuboid.min.x, hit.x, cuboid.max.x) || !MathHelper.between(cuboid.min.z, hit.z, cuboid.max.z)) return;
                break;
            case 2:
            case 3:
                if(!MathHelper.between(cuboid.min.x, hit.x, cuboid.max.x) || !MathHelper.between(cuboid.min.y, hit.y, cuboid.max.y)) return;
                break;
            case 4:
            case 5:
                if(!MathHelper.between(cuboid.min.y, hit.y, cuboid.max.y) || !MathHelper.between(cuboid.min.z, hit.z, cuboid.max.z)) return;
                break;
        }
        
        double dist = vec2.set(hit).subtract(start).magSquared();
        if(dist < s_dist)
        {
            s_side = side;
            s_dist = dist;
            s_vec.set(vec);
        }
    }
    
    public MovingObjectPosition rayTraceCuboid(Vector3 start, Vector3 end, Cuboid6 cuboid)
    {
        s_dist = Double.MAX_VALUE;
        s_side = -1;
        
        for(int i = 0; i < 6; i++)
            traceSide(i, start, end, cuboid);
        
        if(s_side < 0)
            return null;
        
        MovingObjectPosition mop = new MovingObjectPosition(0, 0, 0, s_side, s_vec.toVec3D());
        mop.typeOfHit = null;
        return mop;
    }

    public MovingObjectPosition rayTraceCuboid(Vector3 start, Vector3 end, Cuboid6 cuboid, BlockCoord pos)
    {
        MovingObjectPosition mop = rayTraceCuboid(start, end, cuboid);
        if(mop != null)
        {
            mop.typeOfHit = EnumMovingObjectType.TILE;
            mop.blockX = pos.x;
            mop.blockY = pos.y;
            mop.blockZ = pos.z;
        }
        return mop;
    }

    public MovingObjectPosition rayTraceCuboid(Vector3 start, Vector3 end, Cuboid6 cuboid, Entity e)
    {
        MovingObjectPosition mop = rayTraceCuboid(start, end, cuboid);
        if(mop != null)
        {
            mop.typeOfHit = EnumMovingObjectType.ENTITY;
            mop.entityHit = e;
        }
        return mop;
    }
    
    public MovingObjectPosition rayTraceCuboids(Vector3 start, Vector3 end, List<IndexedCuboid6> cuboids)
    {
        double c_dist = Double.MAX_VALUE;
        MovingObjectPosition c_hit = null;
        
        for(IndexedCuboid6 cuboid : cuboids)
        {
            MovingObjectPosition mop = rayTraceCuboid(start, end, cuboid);
            if(mop != null && s_dist < c_dist)
            {
                mop = new ExtendedMOP(mop, cuboid.data);
                c_dist = s_dist;
                c_hit = mop;
                c_cuboid = cuboid;
            }
        }
        
        return c_hit;
    }

    public MovingObjectPosition rayTraceCuboids(Vector3 start, Vector3 end, List<IndexedCuboid6> cuboids, BlockCoord pos, Block block)
    {
        MovingObjectPosition mop = rayTraceCuboids(start, end, cuboids);
        if(mop != null)
        {
            mop.typeOfHit = EnumMovingObjectType.TILE;
            mop.blockX = pos.x;
            mop.blockY = pos.y;
            mop.blockZ = pos.z;
            if(block != null)
                c_cuboid.add(new Vector3(-pos.x, -pos.y, -pos.z)).setBlockBounds(block);
        }
        return mop;
    }

    public static MovingObjectPosition retraceBlock(World world, EntityPlayer player, int x, int y, int z)
    {
        Vec3 headVec = Vec3.createVectorHelper(player.posX, (player.posY + 1.62) - player.yOffset, player.posZ);
        Vec3 lookVec = player.getLook(1.0F);
        double reach = world.isRemote ? getBlockReachDistance_client() : getBlockReachDistance_server((EntityPlayerMP) player);
        Vec3 endVec = headVec.addVector(lookVec.xCoord * reach, lookVec.yCoord * reach, lookVec.zCoord * reach);
        return Block.blocksList[world.getBlockId(x, y, z)].collisionRayTrace(world, x, y, z, headVec, endVec);
    }

    private static double getBlockReachDistance_server(EntityPlayerMP player)
    {
        return player.theItemInWorldManager.getBlockReachDistance();
    }

    @SideOnly(Side.CLIENT)
    private static double getBlockReachDistance_client()
    {
        return Minecraft.getMinecraft().playerController.getBlockReachDistance();
    }
    
    public static MovingObjectPosition reTrace(World world, EntityPlayer player)
    {
        return reTrace(world, player, world.isRemote ? getBlockReachDistance_client() : getBlockReachDistance_server((EntityPlayerMP) player));
    }

    public static MovingObjectPosition reTrace(World world, EntityPlayer player, double reach)
    {
        Vec3 headVec = Vec3.createVectorHelper(player.posX, (player.posY + 1.62) - player.yOffset, player.posZ);
        Vec3 lookVec = player.getLook(1);
        Vec3 endVec = headVec.addVector(lookVec.xCoord * reach, lookVec.yCoord * reach, lookVec.zCoord * reach);
        return world.rayTraceBlocks_do_do(headVec, endVec, true, false);
    }
}

@@ -5147,32 +5147,159 @@
 y, int z)%0A    %7B%0A
+        Block block = Block.blocksList%5Bworld.getBlockId(x, y, z)%5D;%0A        if(block == null)%0A            return null;%0A        %0A
         Vec3 hea
@@ -5700,51 +5700,13 @@
 urn 
-Block.blocksList%5Bworld.getBlockId(x, y, z)%5D
+block
 .col

Fix NPE in RayTracer when tracing a null block.