import { Quaternion } from "./quaternion";
import { lerp } from "./math";
/**
 * @remarks A class to extend Minecraft's Vector3 with additional functions for vector math.
 */
export class Vector3 {
    /**
     * @remarks Create a new Vector3.
     * @param value The value to crate a new Vector3. Either a single value used for x, y and z. Or an object with an x, y and z value.
     * @example
     * Number Example
     * ```typescript
     * const vector = new Vector3(0); // Creates a vector with value {x: 0, y: 0, z: 0};
     * ```
     * @example
     * Vector Example
     * ```typescript
     * const vector = new Vector3({x: 0, y: 0, z: 0});
     * ```
     */
    constructor(value) {
        if (typeof value === 'object' && value !== null) {
            this.x = value.x;
            this.y = value.y;
            this.z = value.z;
        }
        else {
            this.x = value;
            this.y = value;
            this.z = value;
        }
    }
    /**
     * @remarks Adds another Vector3 to this Vector3.
     * @param value The Vector3 to be added to this.
     * @returns A new Vector3.
     * @example
     * ```typescript
     * vector = vector.add({x: 0, y: 10, z: 0}); // Creates a vector where y is 10 higher.
     * ```
     */
    add(value) {
        if (typeof value === 'object' && value !== null) {
            return new Vector3({ x: this.x + value.x, y: this.y + value.y, z: this.z + value.z });
        }
        else {
            return new Vector3({ x: this.x + value, y: this.y + value, z: this.z + value });
        }
    }
    /**
     * @remarks Subtracts another Vector3 from this Vector3.
     * @param value The Vector3 to be subtracted from this.
     * @returns A new Vector3.
     * @example
     * ```typescript
     * vector = vector.subtract({x: 0, y: 10, z: 0}); // Creates a vector where y is 10 lower.
     * ```
     */
    subtract(value) {
        if (typeof value === 'object' && value !== null) {
            return new Vector3({ x: this.x - value.x, y: this.y - value.y, z: this.z - value.z });
        }
        else {
            return new Vector3({ x: this.x - value, y: this.y - value, z: this.z - value });
        }
    }
    /**
     * @remarks Multiplies another Vector3 by this Vector3.
     * @param value The Vector3 to be multiplied to this.
     * @returns A new Vector3.
     * @example
     * ```typescript
     * new Vector3(1).multiply(10); // Creates the vector {x: 10, y: 10, z: 10}
     * ```
     */
    multiply(value) {
        if (typeof value === 'object' && value !== null) {
            return new Vector3({ x: this.x * value.x, y: this.y * value.y, z: this.z * value.z });
        }
        else {
            return new Vector3({ x: this.x * value, y: this.y * value, z: this.z * value });
        }
    }
    /**
     * @remarks Divides another Vector3 by this Vector3.
     * @param value The Vector3 to be divided by this.
     * @returns A new Vector3.
     * ```typescript
     * new Vector3(10).divide(10); // Creates the vector {x: 1, y: 1, z: 1}
     * ```
     */
    divide(value) {
        if (typeof value === 'object' && value !== null) {
            return new Vector3({ x: this.x / value.x, y: this.y / value.y, z: this.z / value.z });
        }
        else {
            return new Vector3({ x: this.x / value, y: this.y / value, z: this.z / value });
        }
    }
    /**
     * @remarks Normalizes this vector.
     * @returns This vector normalized.
     * @example
     * ```typescript
     * vector = vector.normalize();
     * ```
     */
    normalize() {
        return this.divide(this.length() || 1);
    }
    /**
     * @remarks Calculates the dot product of this vector against another.
     * @param vector The vector to calculate the dot product with.
     * @returns A number representing the dot product.
     */
    dot(vector) {
        return this.x * vector.x + this.y * vector.y + this.z * vector.z;
    }
    /**
     * @remarks Calculates the cross vector of this vector against another.
     * @param vector The vector to calculate the cross vector with.
     * @returns The cross of the two vectors.
     */
    cross(vector) {
        const ax = this.x, ay = this.y, az = this.z;
        const bx = vector.x, by = vector.y, bz = vector.z;
        const x = ay * bz - az * by;
        const y = az * bx - ax * bz;
        const z = ax * by - ay * bx;
        return new Vector3({ x, y, z });
    }
    /**
     * @remarks Linearly interpolates between two vectors.
     * @param vector The destination vector.
     * @param alpha The alpha of interpolation between the two, as 0-1.
     * @returns The Vector at the point of the alpha.
     */
    lerp(vector, alpha) {
        const x = lerp(this.x, vector.x, alpha);
        const y = lerp(this.y, vector.y, alpha);
        const z = lerp(this.z, vector.z, alpha);
        return new Vector3({ x, y, z });
    }
    /**
     * @remarks Gets the distance between this and a target vector.
     * @param vector The vector to check the distance against.
     * @returns The distance between this and the target vector.
     * @example
     * ```typescript
     * function getDistance(source: SafetyCheckedEntity, target: SafetyCheckedEntity): number {
     * 	return source.location.distance(target.location);
     * }
     * ```
     */
    distance(vector) {
        const x = (this.x - vector.x) ** 2;
        const y = (this.y - vector.y) ** 2;
        const z = (this.z - vector.z) ** 2;
        return Math.sqrt(x + y + z);
    }
    /**
     * @remarks Gets the length of this Vector, i.e. the distance from 0 0 0.
     * @returns This Vector's length.
     */
    length() {
        return Vector3.ZERO.distance(this);
    }
    /**
     * @remarks Applies a quaternion to the vector.
     * @param quaternion The quaternion to apply.
     * @returns The vector modified by a quaternion.
     */
    applyQuaternion(quaternion) {
        const vx = this.x;
        const vy = this.y;
        const vz = this.z;
        const qx = quaternion.X;
        const qy = quaternion.Y;
        const qz = quaternion.Z;
        const qw = quaternion.W;
        const tx = 2 * (qy * vz - qz * vy);
        const ty = 2 * (qz * vx - qx * vz);
        const tz = 2 * (qx * vy - qy * vx);
        const x = vx + qw * tx + qy * tz - qz * ty;
        const y = vy + qw * ty + qz * tx - qx * tz;
        const z = vz + qw * tz + qx * ty - qy * tx;
        return new Vector3({ x, y, z });
    }
    /**
     * @deprecated This function has been deprecated in favor of {@link rotateYaw} to better differentiate the direction of rotation.
     * @remarks Rotates this vector by a provided angle.
     * @param angle The angle by which to rotate the vector.
     * @returns The rotated vector.
     */
    rotateByAngle(angle) {
        return this.rotateYaw(angle);
    }
    /**
     * @remarks Rotates this vector on the yaw (y) axis by a provided angle.
     * @param angle The angle by which to rotate the vector.
     * @returns The rotated vector.
     * @example
     * ```typescript
     * for (let angle = look_angle; angle < look_angle + 360; angle += 45) {
     * 	const direction = needle_hog.getViewDirection().rotateByAngleYaw(angle);
     * 	const origin = needle_hog.getHeadLocation().add(direction.multiply(0.5));
     * 	const projectile = new utils.SafetyCheckedEntity(event.sourceEntity.dimension.spawnEntity(projectile_ID, origin));
     * 	projectile.applyImpulse(direction.multiply(2));
     * 	projectile.setRotation({x: 0, y: angle});
     * }
     * ```
     */
    rotateYaw(angle, crossAxis = Vector3.UP) {
        const quaternion = Quaternion.fromAxisAngle(crossAxis, angle * (Math.PI / 180));
        return this.applyQuaternion(quaternion);
    }
    /**
     * @remarks Rotates this vector on the pitch (x) axis by a provided angle.
     * @param angle The angle by which to rotate the vector.
     * @returns The rotated vector.
     * @example
     * ```typescript
     * for (let angle = look_angle; angle < look_angle + 360; angle += 45) {
     * 	const direction = needle_hog.getViewDirection().rotateByAnglePitch(angle);
     * 	const origin = needle_hog.getHeadLocation().add(direction.multiply(0.5));
     * 	const projectile = new utils.SafetyCheckedEntity(event.sourceEntity.dimension.spawnEntity(projectile_ID, origin));
     * 	projectile.applyImpulse(direction.multiply(2));
     * 	projectile.setRotation({x: 0, y: angle});
     * }
     * ```
     */
    rotatePitch(angle) {
        const qAxis = Vector3.UP.cross(this).normalize();
        const qAngle = Math.acos(Vector3.UP.dot(this)) + (angle * (Math.PI / 180));
        const quaternion = Quaternion.fromAxisAngle(qAxis, qAngle);
        return Vector3.UP.applyQuaternion(quaternion);
    }
    /**
     * @remarks Rounds a Vector3 to whole numbers.
     * @returns This Vector3 rounded to the nearest whole number.
     * @example
     * ```typescript
     * const vector = new Vector3({x: 0.6, y: 10.2, z: 14.9}).round(); // Returns a vector with the value {x: 1, y: 10, z: 15}
     * ```
     */
    round() {
        const x = Math.round(this.x);
        const y = Math.round(this.y);
        const z = Math.round(this.z);
        return new Vector3({ x, y, z });
    }
    /**
     * @remarks Floors a Vector3 to whole numbers.
     * @returns This Vector3 floored to the nearest whole number.
     * @example
     * ```typescript
     * const vector = new Vector3({x: 0.6, y: 10.2, z: 14.9}).floor(); // Returns a vector with the value {x: 0, y: 10, z: 14}
     * ```
     */
    floor() {
        const x = Math.floor(this.x);
        const y = Math.floor(this.y);
        const z = Math.floor(this.z);
        return new Vector3({ x, y, z });
    }
    /**
     * @remarks Ceils a Vector3 to whole numbers.
     * @returns This Vector3 ceiled to the nearest whole number.
     * @example
     * ```typescript
     * const vector = new Vector3({x: 0.6, y: 10.2, z: 14.9}).ceil(); // Returns a vector with the value {x: 1, y: 11, z: 15}
     * ```
     */
    ceil() {
        const x = Math.ceil(this.x);
        const y = Math.ceil(this.y);
        const z = Math.ceil(this.z);
        return new Vector3({ x, y, z });
    }
    /**
     * @remarks Returns a block center, useful for spawning mobs at the center of a block.
     * @returns This Vector3 centered.
     * @example
     * ```typescript
     * const vector = new Vector3({x: 0.6, y: 10.2, z: 14.9}).center(); // Returns a vector with the value {x: 0.5, y: 10, z: 14.5}
     * ```
     */
    center() {
        return this.floor().add({ x: 0.5, y: 0, z: 0.5 });
    }
    /**
     * @remarks Returns a Vector3 some blocks up of this Vector3.
     * @param [amount=1] The number of blocks up from this Vector3 to return, defaults to 1.
     * @returns The location above this Vector.
     */
    up(amount = 1) {
        return this.add(Vector3.UP.multiply(amount));
    }
    /**
     * @remarks Returns a Vector3 one block down of this Vector3.
     * @param [amount=1] The number of blocks down from this Vector3 to return, defaults to 1.
     * @returns The location below this Vector.
     */
    down(amount = 1) {
        return this.add(Vector3.DOWN.multiply(amount));
    }
    /**
     * @remarks Returns a Vector3 none block north of this Vector3.
     * @param [amount=1] The number of blocks north from this Vector3 to return, defaults to 1.
     * @returns The location north of this Vector.
     */
    north(amount = 1) {
        return this.add(Vector3.NORTH.multiply(amount));
    }
    /**
     * @remarks Returns a Vector3 sone block south of this Vector3.
     * @param [amount=1] The number of blocks south from this Vector3 to return, defaults to 1.
     * @returns The location south of this Vector.
     */
    south(amount = 1) {
        return this.add(Vector3.SOUTH.multiply(amount));
    }
    /**
     * @remarks Returns a Vector3 one block east of this Vector3.
     * @param [amount=1] The number of blocks east from this Vector3 to return, defaults to 1.
     * @returns The location east of this Vector.
     */
    east(amount = 1) {
        return this.add(Vector3.EAST.multiply(amount));
    }
    /**
     * @remarks Returns a Vector3 one block west of this Vector3.
     * @param [amount=1] The number of blocks west from this Vector3 to return, defaults to 1.
     * @returns The location west of this Vector.
     */
    west(amount = 1) {
        return this.add(Vector3.WEST.multiply(amount));
    }
    /**
     * @remarks Checks if two vectors are approximately equal.
     * @param vector The vector to compare
     * @returns True if these vectors are very close to eachother. False otherwise.
     * @example
     * ```typescript
     * const locationA = new Vector3(1.1);
     * const locationB = new Vector3(1.2);
     * const locationC = new Vector3(4.0);
     * locationA.approximatelyEqual(locationB); // Returns true.
     * locationA.approximatelyEqual(locationC); // Returns false.
     * ```
     */
    approximatelyEqual(vector) {
        const originX = Math.floor(this.x);
        const originY = Math.floor(this.y);
        const originZ = Math.floor(this.z);
        const targetX = Math.floor(vector.x);
        const targetY = Math.floor(vector.y);
        const targetZ = Math.floor(vector.z);
        return (originX === targetX) && (originY === targetY) && (originZ === targetZ);
    }
    /**
     * @remarks Checks if this vector is within the provided bounds volume.
     * @param minLocation The negative-most coordinate of the volume (inclusive).
     * @param maxLocation The positive-most coordinate of the volume (inclusive).
     * @returns True if this vector is within the bounds, false otherwise.
     * @example
     * ```typescript
     * const myLocation = new Vector3(1);
     * myLocation.inBounds(new Vector3(0), new Vector3(2)); // returns true
     * ```
     */
    inBounds(minLocation, maxLocation) {
        return ((this.x >= minLocation.x && this.y >= minLocation.y && this.z >= minLocation.z) &&
            (this.x <= maxLocation.x && this.y <= maxLocation.y && this.z <= maxLocation.z));
    }
    /**
     * @remarks Represents a Vector3 as spaced out numbers, can be used in commands.
     * @returns A string representation of the Vector3.
     * @example
     * ```typescript
     * const player = getAllPlayersSafe()[0];
     * player.runCommand(`setblock ${location.toString()} stone`); // toString can be called manually.
     * player.runCommand(`setblock ${location} stone`); // toString is also automatically called when using string conversion.
     * ```
     */
    toString() {
        return `${this.x} ${this.y} ${this.z}`;
    }
    /**
     * @deprecated Result moved to toString to allow default string conversion in the command format
     * Converts a vector to a string used for command strings.
     * @returns A string formated for command inputs.
     */
    toCommandString() {
        return `${this.x} ${this.y} ${this.z}`;
    }
}
Vector3.ZERO = new Vector3({ x: 0, y: 0, z: 0 });
Vector3.UP = new Vector3({ x: 0, y: 1, z: 0 });
Vector3.DOWN = new Vector3({ x: 0, y: -1, z: 0 });
Vector3.NORTH = new Vector3({ x: 0, y: 0, z: -1 });
Vector3.SOUTH = new Vector3({ x: 0, y: 0, z: 1 });
Vector3.EAST = new Vector3({ x: 1, y: 0, z: 0 });
Vector3.WEST = new Vector3({ x: -1, y: 0, z: 0 });
