package atomicstryker.minions.common.util;

/**
 * Represents a 3d vector.
 */
public class Vector3 implements Comparable<Vector3> {
	/**
	 * Vector with all elements set to 0. (0, 0, 0)
	 */
	public final static Vector3 ZERO = new Vector3(0, 0, 0);

	/**
	 * Unit Vector in the X direction. (1, 0, 0)
	 */
	public final static Vector3 UNIT_X = new Vector3(1, 0, 0);

	/**
	 * Unit Vector facing Forward. (1, 0, 0)
	 */
	public final static Vector3 Forward = UNIT_X;

	/**
	 * Unit Vector in the Y direction. (0, 1, 0)
	 */
	public final static Vector3 UNIT_Y = new Vector3(0, 1, 0);

	/**
	 * Unit Vector pointing Up. (0, 1, 0)
	 */
	public final static Vector3 Up = UNIT_Y;

	/**
	 * Unit Vector in the Z direction. (0, 0, 1)
	 */
	public final static Vector3 UNIT_Z = new Vector3(0, 0, 1);

	/**
	 * Unit Vector pointing Right. (0, 0, 1)
	 */
	public final static Vector3 Right = UNIT_Z;

	/**
	 * Unit Vector with all elements set to 1. (1, 1, 1)
	 */
	public final static Vector3 ONE = new Vector3(1, 1, 1);

	protected float x, y, z;

	/**
	 * Constructs a new Vector3 with the given x, y, z
	 *
	 * @param x
	 * @param y
	 * @param z
	 */
	public Vector3(float x, float y, float z) {
		this.x = x;
		this.y = y;
		this.z = z;
	}
	
	/**
	 * Constructs a new Vector3 with the given x, y, z
	 *
	 * @param x
	 * @param y
	 * @param z
	 */
	public Vector3(Double x, Double y, Double z) {
		this(x.floatValue(), y.floatValue(), z.floatValue());
	}

	/**
	 * Constructs a new Vector3 with all elements set to 0
	 */
	public Vector3() {
		this(0, 0, 0);
	}

	/**
	 * Constructs a new Vector3 that is a clone of the given vector3
	 *
	 * @param clone
	 */
	public Vector3(Vector3 clone) {
		this(clone.getX(), clone.getY(), clone.getZ());
	}

	/**
	 * Constructs a new Vector3 from the given Vector2 and z
	 *
	 * @param vector
	 * @param z
	 */
	public Vector3(Vector2 vector, float z) {
		this(vector.getX(), vector.getY(), z);
	}

	/**
	 * Constructs a new Vector3 from the given Vector2 and z set to 0
	 *
	 * @param vector
	 */
	public Vector3(Vector2 vector) {
		this(vector, 0);
	}

	public float getX() {
		return x;
	}

	public float getY() {
		return y;
	}

	public float getZ() {
		return z;
	}

	/**
	 * Adds two vectors
	 *
	 * @param that
	 * @return
	 */
	public Vector3 add(Vector3 that) {
		return Vector3.add(this, that);
	}

	/**
	 * Subtracts two vectors
	 *
	 * @param that
	 * @return
	 */
	public Vector3 subtract(Vector3 that) {
		return Vector3.subtract(this, that);
	}

	/**
	 * Scales by the scalar value
	 *
	 * @param scale
	 * @return
	 */
	public Vector3 scale(float scale) {
		return Vector3.scale(this, scale);
	}

	/**
	 * Takes the dot product of two vectors
	 *
	 * @param that
	 * @return
	 */
	public float dot(Vector3 that) {
		return Vector3.dot(this, that);
	}

	/**
	 * Takes the cross product of two vectors
	 *
	 * @param that
	 * @return
	 */
	public Vector3 cross(Vector3 that) {
		return Vector3.cross(this, that);
	}
	
	/**
	 * Returns a Vector2 object using the X and Z values of
	 * this Vector3. The x of this Vector3 becomes the x 
	 * of the Vector2, and the z of this Vector3 becomes the 
	 * y of the Vector2.
	 * 
	 * @return 
	 */
	public Vector2 toVector2() {
		return Vector3.toVector2(this);
	}
	
	/**
	 * Returns a Vector2m object using the X and Z values of
	 * this Vector3. The x of this Vector3 becomes the x 
	 * of the Vector2, and the z of this Vector3 becomes the 
	 * y of the Vector2m.
	 * 
	 * @return 
	 */
	public Vector2m toVector2m() {
		return Vector3.toVector2m(this);
	}
	
	/**
	 * Rounds the X, Y, and Z values of this Vector3 up to 
	 * the nearest integer value. 
	 * 
	 * @return 
	 */
	public Vector3 ceil() {
		return new Vector3(Math.ceil(x), Math.ceil(y), Math.ceil(z));
	}
	
	/**
	 * Rounds the X, Y, and Z values of this Vector3 down to 
	 * the nearest integer value. 
	 * 
	 * @return 
	 */
	public Vector3 floor() {
		return new Vector3(Math.floor(x), Math.floor(y), Math.floor(z));
	}
	
	/**
	 * Rounds the X, Y, and Z values of this Vector3 to 
	 * the nearest integer value. 
	 * 
	 * @return 
	 */
	public Vector3 round() {
		return new Vector3(Math.round(x), Math.round(y), Math.round(z));
	}
	
	/**
	 * Sets the X, Y, and Z values of this Vector3 to their
	 * absolute value.
	 * 
	 * @return 
	 */
	public Vector3 abs() {
		return new Vector3(Math.abs(x), Math.abs(y), Math.abs(z));
	}
	
	/**
	 * Gets the distance between this Vector3 and a given Vector3.
	 * 
	 * @param a
	 * @return 
	 */
	public double distance(Vector3 a) {
		return Vector3.distance(a, this);
	}
	
	/**
	 * Raises the X, Y, and Z values of this Vector3 to the given power.
	 * 
	 * @param power
	 * @return 
	 */
	public Vector3 pow(double power) {
		return Vector3.pow(this, power);
	}

	/**
	 * returns the squared length of the vector
	 *
	 * @return
	 */
	public float lengthSquared() {
		return Vector3.lengthSquared(this);
	}

	/**
	 * returns the length of this vector. Note: makes use of Math.sqrt and is
	 * not cached.
	 *
	 * @return
	 */
	public float length() {
		return Vector3.length(this);
	}

	/**
	 * Returns a fast approximation of this vector's length.
	 * 
	 * @return 
	 */
	public float fastLength() {
		return Vector3.fastLength(this);
	}
	
	/**
	 * returns the vector with a length of 1
	 *
	 * @return
	 */
	public Vector3 normalize() {
		return Vector3.normalize(this);
	}

	/**
	 * returns the vector as [x,y,z]
	 *
	 * @return
	 */
	public float[] toArray() {
		return Vector3.toArray(this);
	}

	/**
	 * Compares two Vector3s
	 */
	public int compareTo(Vector3 o) {
		return Vector3.compareTo(this, o);
	}

	/**
	 * Checks if two Vector3s are equal
	 */
	public boolean equals(Object o) {
		return Vector3.equals(this, o);
	}

	/**
	 * toString Override
	 */
	public String toString() {
		return String.format("{ %f, %f, %f }", x, y, z);
	}

	/**
	 * Returns the length of the given vector. 
	 * 
	 * Note: Makes use of Math.sqrt and
	 * is not cached, so can be slow
	 *
	 * Also known as norm. ||a||
	 * 
	 * @param a
	 * @return
	 */
	public static float length(Vector3 a) {
		return (float) Math.sqrt(lengthSquared(a));
	}

	/**
	 * Returns an approximate length of the given vector.
	 * 
	 * @param a
	 * @return 
	 */
	public static float fastLength(Vector3 a) {
		return (float) Math.sqrt(lengthSquared(a));
	}
	
	/**
	 * returns the length squared to the given vector
	 *
	 * @param a
	 * @return
	 */
	public static float lengthSquared(Vector3 a) {
		return Vector3.dot(a, a);
	}

	/**
	 * Returns a new vector that is the given vector but length 1
	 *
	 * @param a
	 * @return
	 */
	public static Vector3 normalize(Vector3 a) {
		return Vector3.scale(a, (1.f / a.length()));
	}

	/**
	 * Creates a new vector that is A - B
	 *
	 * @param a
	 * @param b
	 * @return
	 */
	public static Vector3 subtract(Vector3 a, Vector3 b) {
		return new Vector3(a.getX() - b.getX(), a.getY() - b.getY(), a.getZ() - b.getZ());
	}

	/**
	 * Creates a new Vector that is A + B
	 *
	 * @param a
	 * @param b
	 * @return
	 */
	public static Vector3 add(Vector3 a, Vector3 b) {
		return new Vector3(a.getX() + b.getX(), a.getY() + b.getY(), a.getZ() + b.getZ());
	}

	/**
	 * Creates a new vector that is A multiplied by the uniform scalar B
	 *
	 * @param a
	 * @param b
	 * @return
	 */
	public static Vector3 scale(Vector3 a, float b) {
		return new Vector3(a.getX() * b, a.getY() * b, a.getZ() * b);
	}

	/**
	 * Returns the dot product of A and B
	 *
	 * @param a
	 * @param b
	 * @return
	 */
	public static float dot(Vector3 a, Vector3 b) {
		return a.getX() * b.getX() + a.getY() * b.getY() + a.getZ() * b.getZ();
	}

	/**
	 * Creates a new Vector that is the A x B The Cross Product is the vector
	 * orthogonal to both A and B
	 *
	 * @param a
	 * @param b
	 * @return
	 */
	public static Vector3 cross(Vector3 a, Vector3 b) {
		return new Vector3(a.getY() * b.getZ() - a.getZ() * b.getY(), a.getZ() * b.getX() - a.getX() * b.getZ(), a.getX() * b.getY() - a.getY() * b.getX());
	}
	
	/**
	 * Rounds the X, Y, and Z values of the given Vector3 up to 
	 * the nearest integer value. 
	 * 
	 * @param o Vector3 to use
	 * @return 
	 */
	public static Vector3 ceil(Vector3 o) {
		return new Vector3(Math.ceil(o.x), Math.ceil(o.y), Math.ceil(o.z));
	}
	
	/**
	 * Rounds the X, Y, and Z values of the given Vector3 down to 
	 * the nearest integer value. 
	 * 
	 * @param o Vector3 to use
	 * @return 
	 */
	public static Vector3 floor(Vector3 o) {
		return new Vector3(Math.floor(o.x), Math.floor(o.y), Math.floor(o.z));
	}
	
	/**
	 * Rounds the X, Y, and Z values of the given Vector3 to 
	 * the nearest integer value. 
	 * 
	 * @param o Vector3 to use
	 * @return 
	 */
	public static Vector3 round(Vector3 o) {
		return new Vector3(Math.round(o.x), Math.round(o.y), Math.round(o.z));
	}
	
	/**
	 * Sets the X, Y, and Z values of the given Vector3 to their
	 * absolute value.
	 * 
	 * @param o Vector3 to use
	 * @return 
	 */
	public static Vector3 abs(Vector3 o) {
		return new Vector3(Math.abs(o.x), Math.abs(o.y), Math.abs(o.z));
	}
	
	/**
	 * Returns a Vector3 containing the smallest X, Y, and Z values.
	 * 
	 * @param o1
	 * @param o2
	 * @return 
	 */
	public static Vector3 min(Vector3 o1, Vector3 o2) {
		return new Vector3(Math.min(o1.x, o2.x), Math.min(o1.y, o2.y), Math.min(o1.z, o2.z));
	}
	
	/**
	 * Returns a Vector3 containing the largest X, Y, and Z values.
	 * 
	 * @param o1
	 * @param o2
	 * @return 
	 */
	public static Vector3 max(Vector3 o1, Vector3 o2) {
		return new Vector3(Math.max(o1.x, o2.x), Math.max(o1.y, o2.y), Math.max(o1.z, o2.z));
	}
	
	/**
	 * Returns a Vector3 with random X, Y, and Z values (between 0 and 1)
	 * 
	 * @return 
	 */
	public static Vector3 rand() {
		return new Vector3(Math.random(), Math.random(), Math.random());
	}
	
	/**
	 * Gets the distance between two Vector3. 
	 * 
	 * @param a
	 * @param b
	 * @return 
	 */
	public static double distance(Vector3 a, Vector3 b) {
		double xzDist = Vector2.distance(a.toVector2(), b.toVector2());
		return Math.sqrt(Math.pow(xzDist, 2) + Math.pow(Math.abs(Vector3.subtract(a, b).getY()), 2));
	}
	
	/**
	 * Raises the X, Y, and Z values of a Vector3 to the given power.
	 * 
	 * @param o
	 * @param power
	 * @return 
	 */
	public static Vector3 pow(Vector3 o, double power) {
		return new Vector3(Math.pow(o.x, power), Math.pow(o.y, power), Math.pow(o.z, power));
	}
	
	/**
	 * Returns a Vector2 object using the X and Z values of
	 * the given Vector3. The x of the Vector3 becomes the x 
	 * of the Vector2, and the z of this Vector3 becomes the 
	 * y of the Vector2m.
	 * 
	 * @param o Vector3 object to use
	 * @return 
	 */
	public static Vector2 toVector2(Vector3 o) {
		return new Vector2(o.x, o.z);
	}
	
	/**
	 * Returns a Vector2m object using the X and Z values of
	 * the given Vector3. The x of the Vector3 becomes the x 
	 * of the Vector2m, and the z of this Vector3 becomes the 
	 * y of the Vector2m.
	 * 
	 * @param o Vector3 object to use
	 * @return 
	 */
	public static Vector2m toVector2m(Vector3 o) {
		return new Vector2m(o.x, o.z);
	}

	/**
	 * Returns a new float array that is {x, y, z}
	 *
	 * @param a
	 * @return
	 */
	public static float[] toArray(Vector3 a) {
		return new float[]{a.getX(), a.getY(), a.getZ()};
	}

	/**
	 * Compares two Vector3s
	 */
	public static int compareTo(Vector3 a, Vector3 b) {
		return (int) a.lengthSquared() - (int) b.lengthSquared();
	}

	/**
	 * Checks if two Vector3s are equal
	 */
	public static boolean equals(Object a, Object b) {
		if (!(a instanceof Vector3) || !(b instanceof Vector3)) {
			return false;
		}
		if (a == b) {
			return true;
		}
		Vector3 x = (Vector3)a;
		Vector3 y = (Vector3)b;
		if(x.getX() == y.getX() && x.getY() == y.getY() && x.getZ() == y.getZ()) return true;
		return false;
	}
	
}
