package ch.epfl.maze.util;

/**
 * Immutable 2-dimensional vector (<i>x</i>, <i>y</i>).
 *
 * @author EPFL
 * @author Pacien TRAN-GIRARD
 */
public final class Vector2D {

    /* shift constant to compute the hash */
    private static final int SHIFT = 1000;

    private static final int ZERO = 0;

    /* 2-dimension coordinates */
    private final int mX, mY;

    /**
     * Constructs a 2-dimensional vector.
     *
     * @param x Horizontal coordinate
     * @param y Vertical coordinate
     */
    public Vector2D(int x, int y) {
        mX = x;
        mY = y;
    }

    /**
     * Constructs a 2-dimensional zero vector.
     */
    public Vector2D() {
        this(Vector2D.ZERO, Vector2D.ZERO);
    }

    /**
     * Adds two coordinates to the vector.
     *
     * @param x Horizontal coordinate to add
     * @param y Vertical coordinate to add
     * @return The result of an addition with two coordinates
     */
    public Vector2D add(int x, int y) {
        return new Vector2D(mX + x, mY + y);
    }

    /**
     * Adds a vector to the vector.
     *
     * @param v Vector to add
     * @return The result of the addition with the vector
     */
    public Vector2D add(Vector2D v) {
        return add(v.mX, v.mY);
    }

    /**
     * Subtracts two coordinates to the vector.
     *
     * @param x Horizontal coordinate to subtract
     * @param y Vertical coordinate to subtract
     * @return The result of the subtraction with the vector
     */
    public Vector2D sub(int x, int y) {
        return new Vector2D(mX - x, mY - y);
    }

    /**
     * Subtracts a vector to the vector.
     *
     * @param v Vector to subtract
     * @return The result of the subtraction with the vector
     */
    public Vector2D sub(Vector2D v) {
        return sub(v.mX, v.mY);
    }

    /**
     * Negates the vector.
     *
     * @return The negated version of the vector
     */
    public Vector2D negate() {
        return new Vector2D(-mX, -mY);
    }

    /**
     * Multiplies the coordinates of the vector by a scalar.
     *
     * @param scalar Number to multiply the coordinates with
     * @return The result of the multiplication with a scalar
     */
    public Vector2D mul(int scalar) {
        return new Vector2D(scalar * mX, scalar * mY);
    }

    /**
     * Divides the coordinates of the vector by a scalar.
     *
     * @param scalar Number to divide the coordinates with
     * @return The result of the division with a scalar
     */
    public Vector2D div(int scalar) {
        return new Vector2D(scalar / mX, scalar / mY);
    }

    /**
     * Normalizes the vector.
     *
     * @return The normalized version of the vector
     */
    public Vector2D normalize() {
        double dist = dist();
        return new Vector2D((int) (mX / dist), (int) (mY / dist));
    }

    /**
     * The Euclidean distance of the vector.
     *
     * @return The length of the vector
     */
    public double dist() {
        return Math.sqrt(mX * mX + mY * mY);
    }

    /**
     * Adds a direction to the vector
     *
     * @param d Direction to add
     * @return The result of the addition with the direction
     */
    public Vector2D addDirectionTo(Direction d) {
        switch (d) {
            case UP:
                return new Vector2D(mX, mY - 1);

            case DOWN:
                return new Vector2D(mX, mY + 1);

            case LEFT:
                return new Vector2D(mX - 1, mY);

            case RIGHT:
                return new Vector2D(mX + 1, mY);

            case NONE:
            default:
                return new Vector2D(mX, mY);
        }
    }

    /**
     * Converts the vector to the closest corresponding direction.
     *
     * @return The closest direction corresponding to the vector
     */
    public Direction toDirection() {
        Vector2D normal = this.normalize();

        if (normal.mX == 0 && normal.mY == 1) {
            return Direction.DOWN;
        } else if (normal.mX == 0 && normal.mY == -1) {
            return Direction.UP;
        } else if (normal.mX == 1 && normal.mY == 0) {
            return Direction.RIGHT;
        } else if (normal.mX == -1 && normal.mY == 0) {
            return Direction.LEFT;
        } else {
            return Direction.NONE;
        }
    }

    /**
     * Returns the horizontal coordinate of the vector.
     *
     * @return x-coordinate of the vector
     */
    public int getX() {
        return mX;
    }

    /**
     * Returns the vertical coordinate of the vector.
     *
     * @return y-coordinate of the vector
     */
    public int getY() {
        return mY;
    }

    @Override
    public String toString() {
        return "(" + mX + ", " + mY + ")";
    }

    @Override
    public int hashCode() {
        return mX * SHIFT + mY;
    }

    @Override
    public boolean equals(Object o) {
        if (o == null) {
            return false;
        }
        if (o.getClass() != this.getClass()) {
            return false;
        }

        return o.hashCode() == this.hashCode();
    }

}