
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.
     */
    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;
        }
    }
	
    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 });
        }
    }
    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 });
        }
    }
    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 });
        }
    }
	
	equals(value) {
		return (value !== undefined && this.x == value.x && this.y == value.y && this.z == value.z);
	}
	equals_xz(value) {
		return (this.x == value.x && this.z == value.z);
	}
	
	toCommand() {
		return `${this.x.toFixed(2)} ${this.y.toFixed(2)} ${this.z.toFixed(2)}`;
	}
	toLocation() {
		return {x: this.x, y: this.y, z: this.z};
	}
	
}

export function distance(a, b) {
    return Math.sqrt(distanceSquared(a,b));
}

// Prefer using distance squared over distance to avoid calculating square root
// Consider using 'distanceSquared(a,b) < targetDistance * targetDistance' when making 'in range' checks
export function distanceSquared(a, b) {
    const difference = (new Vector3(a)).subtract(b);
    return difference.x * difference.x + difference.y * difference.y + difference.z * difference.z;
}

export function cross(a, b) {
    // a2 * b3 - a3 * b2, a3 * b1 - a1 * b3, a1 * b2 - a2 * b1
    const c = new Vector3({
        x: a.y * b.z - a.z * b.y,
        y: a.z * b.x - a.x * b.z,
        z: a.x * b.y - a.y * b.x
    });
    return c;
}

/**
 * Calculates the direction (unit) vector from point A to point B.
 * @param {Vector3} pointA - The start point.
 * @param {Vector3} pointB - The end point.
 * @returns {number} - The result direction vector.
 */
export function getDirection(pointA, pointB) {
    return unit(getVector(pointA, pointB));
}

/**
 * Calculates the vector from point A to point B.
 * @param {Vector3} pointA - The start point.
 * @param {Vector3} pointB - The end point.
 * @returns {number} - The result vector.
 */
export function getVector(pointA, pointB) {
    return {
        x: pointB.x - pointA.x,
        y: pointB.y - pointA.y,
        z: pointB.z - pointA.z
    };
}

/**
 * Converts vector to unit vector (vector with length 1).
 * This is usualy used when representing direction.
 * @param {Vector3} originalVector - The original vector.
 * @returns {Vector3} The unit vector.
 */
export function unit(originalVector) {
    const originalVectorLength = length(originalVector);
    return {
        x: originalVector.x / originalVectorLength,
        y: originalVector.y / originalVectorLength,
        z: originalVector.z / originalVectorLength
    };
}

/**
 * Calcualtes the length of vector.
 * @param {Vector3} vector - The vector.
 * @returns {number} - The length of vector.
 */
export function length(vector) {
    return Math.sqrt(vector.x ** 2 + vector.y ** 2 + vector.z ** 2);
}

export function copy(value)
{
	return new Vector3(value)
}
export function copyFloor(value)
{
	return new Vector3({x: Math.floor(value.x), y: Math.floor(value.y), z: Math.floor(value.z)});
}
export function copyCenter(value)
{
	return new Vector3({x: Math.floor(value.x)+0.5, y: Math.floor(value.y), z: Math.floor(value.z)+0.5});
}

export function fromRotation(rotation)
{
	let rotationH = rotation.y * -1;
	let z0 = Math.cos(rotationH *Math.PI/180);
	let x0 = Math.sin(rotationH *Math.PI/180);
	
	let rotationV = rotation.x * -1;
	let h = Math.cos(rotationV *Math.PI/180);
	let v = Math.sin(rotationV*Math.PI/180);
	
	return new Vector3({x: h*x0, y:v, z:h*z0});
}
export function rotationToFacing(rotation)
{
	let rot = rotation.y;
	if (rot < 0)
		rot += 360;
	
	if (rot < 45) return "south";
	if (rot < 135) return "west";
	if (rot < 225) return "north"; 
	if (rot < 315) return "east";
	return "south";
	// 0 south 90 west 180 north 270 east
}

// dim - what dimensions to check - x, xz, xyz, etc
export function locationIsInArea(location, corner1, corner2, dim)
{
	if (dim.includes("x") && !numIsInRange(location.x, corner1.x, corner2.x))
		return false;
	if (dim.includes("y") && !numIsInRange(location.y, corner1.y, corner2.y))
		return false;
	if (dim.includes("z") && !numIsInRange(location.z, corner1.z, corner2.z))
		return false;
	return true;
}

export function numIsInRange(num, edge1, edge2)
{
	if (edge1 <= num && num <= edge2) 
		return true;
	if (edge2 <= num && num <= edge1)
		return true;
	return false;
}

export const North = new Vector3({ x: 0, y: 0, z: -1 });
export const South = new Vector3({ x: 0, y: 0, z: 1 });
export const West = new Vector3({ x: -1, y: 0, z: 0 });
export const East = new Vector3({ x: 1, y: 0, z: 0 });
export const Up = new Vector3({ x: 0, y: 1, z: 0 });
export const Up2 = new Vector3({ x: 0, y: 2, z: 0 });
export const Down = new Vector3({ x: 0, y: -1, z: 0 });

