/**
 * HitBlox.java - Class for getting blocks along line of sight
 * 
 * NOTES: This class is designed to handle the annoying parts of the seemingly
 * simple task of getting the coordinates of the block a player is currently
 * aimed at. This class abstracts the simpler tasks of finding the current
 * target block and the adjacent unoccupied block to their own methods, but it
 * also provides a public getNextBlock method for processing the entire
 * line-of-sight from the player for more specialized tasks. This method can be
 * used exactly as it is in getTargetBlock, for instance.
 * 
 * WARNING: Servers with map coordinate bugs may experience a one or more block
 * inaccuracy when in affected parts of the world. A good way to test areas for
 * the offset bug is to use Chrisinajar's Magic Carpet plugin.
 * 
 * Contact: For questions, contact Ho0ber@gmail.com or channel #hey0 on
 * irc.esper.net
 * 
 * @author Ho0ber
 */
public class HitBlox {

    private Location player_loc;
    private double   rot_x, rot_y, view_height;
    private double   length, h_length, step;
    private int      range;
    private double   x_offset, y_offset, z_offset;
    private int      last_x, last_y, last_z;
    private int      target_x, target_y, target_z, target_type;

    /**
     * Constructor requiring player, uses default values
     * 
     * @param in_player
     */
    public HitBlox(Player in_player) {
        init(in_player.getLocation(), 200, 0.2, 1.65); // Reasonable default
        // values
    }

    /**
     * Constructor requiring location, uses default values
     * 
     * @param in_location
     */
    public HitBlox(Location in_location) {
        init(in_location, 200, 0.2, 0);
    }

    /**
     * Constructor requiring player, max range, and a stepping value
     * 
     * @param in_player
     * @param in_range
     * @param in_step
     */
    public HitBlox(Player in_player, int in_range, double in_step) {
        init(in_player.getLocation(), in_range, in_step, 1.65);
    }

    /**
     * Constructor requiring location, max range, and a stepping value
     * 
     * @param in_location
     * @param in_range
     * @param in_step
     */
    public HitBlox(Location in_location, int in_range, double in_step) {
        init(in_location, in_range, in_step, 0);
    }

    /**
     * Initialization method
     * 
     * @param in_location
     * @param in_range
     * @param in_step
     * @param in_view_height
     */
    public void init(Location in_location, int in_range, double in_step, double in_view_height) {
        player_loc = in_location;
        view_height = in_view_height;
        range = in_range;
        step = in_step;
        length = 0;
        rot_x = (player_loc.rotX + 90) % 360;
        rot_y = player_loc.rotY * -1;

        target_x = etc.floor(player_loc.x);
        target_y = etc.floor(player_loc.y + view_height);
        target_z = etc.floor(player_loc.z);
        last_x = target_x;
        last_y = target_y;
        last_z = target_z;
    }

    /**
     * Returns the block at the cursor, or null if out of range
     * 
     * @return Block
     */
    public Block getTargetBlock() {
        while ((getNextBlock() != null) && (getCurBlock().getType() == 0))
            ;
        return getCurBlock();
    }

    /**
     * Sets the type of the block at the cursor
     * 
     * @param type
     */
    public void setTargetBlock(int type) {
        while ((getNextBlock() != null) && (getCurBlock().getType() == 0))
            ;
        if (getCurBlock() != null)
            etc.getServer().setBlockAt(type, target_x, target_y, target_z);
    }

    /**
     * Returns the block attached to the face at the cursor, or null if out of
     * range
     * 
     * @return Block
     */
    public Block getFaceBlock() {
        while ((getNextBlock() != null) && (getCurBlock().getType() == 0))
            ;
        if (getCurBlock() != null)
            return getLastBlock();
        else
            return null;
    }

    /**
     * Sets the type of the block attached to the face at the cursor
     * 
     * @param type
     */
    public void setFaceBlock(int type) {
        while ((getNextBlock() != null) && (getCurBlock().getType() == 0))
            ;
        if (getCurBlock() != null)
            etc.getServer().setBlockAt(type, last_x, last_y, last_z);
    }

    /**
     * Returns STEPS forward along line of vision and returns block
     * 
     * @return Block
     */
    public Block getNextBlock() {
        last_x = target_x;
        last_y = target_y;
        last_z = target_z;

        do {
            length += step;

            h_length = (length * Math.cos(Math.toRadians(rot_y)));
            y_offset = (length * Math.sin(Math.toRadians(rot_y)));
            x_offset = (h_length * Math.cos(Math.toRadians(rot_x)));
            z_offset = (h_length * Math.sin(Math.toRadians(rot_x)));

            target_x = etc.floor(x_offset + player_loc.x);
            target_y = etc.floor(y_offset + player_loc.y + view_height);
            target_z = etc.floor(z_offset + player_loc.z);

        } while ((length <= range) && ((target_x == last_x) && (target_y == last_y) && (target_z == last_z)));

        if (length > range)
            return null;

        return etc.getServer().getBlockAt(target_x, target_y, target_z);
    }

    /**
     * Returns the current block along the line of vision
     * 
     * @return Block
     */
    public Block getCurBlock() {
        if (length > range)
            return null;
        else
            return etc.getServer().getBlockAt(target_x, target_y, target_z);
    }

    /**
     * Sets current block type id
     * 
     * @param type
     */
    public void setCurBlock(int type) {
        if (getCurBlock() != null)
            etc.getServer().setBlockAt(type, target_x, target_y, target_z);
    }

    /**
     * Returns the previous block along the line of vision
     * 
     * @return Block
     */
    public Block getLastBlock() {
        return etc.getServer().getBlockAt(last_x, last_y, last_z);
    }

    /**
     * Sets previous block type id
     * 
     * @param type
     */
    public void setLastBlock(int type) {
        if (getLastBlock() != null)
            etc.getServer().setBlockAt(type, last_x, last_y, last_z);
    }
}
