/** @ignore */
const { sin, cos, sqrt, atan2 } = Math
/** @ignore */
const hypot = Math.hypot || ((a, b) => sqrt(a * a + b * b))

/**
 * Simple 2D JavaScript Vector Class
 */
export default class Vector {
	/**
	 * Creates an instance of Vector.
	 * @param {number} x     x value
	 * @param {number} y     y value
	 */
	constructor(x = 0, y = 0) {
		/**
		 * @type {number}
		 */
		this.x = x
		/**
		 * @type {number}
		 */
		this.y = y
	}

	/**
	 * Set vector value.
	 * @param {number} x     x value
	 * @param {number} y     y value
	 * @return {Vector}
	 */
	xy(x, y) {
		this.x = x
		this.y = y
		return this
	}

	/**
	 * Clones another current into current vector.
	 * @return {Vector}
	 */
	copy(vec) {
		this.x = vec.x
		this.y = vec.y
		return this
	}

	/**
	 * Returns a representation of the current vector as a string.
	 * @return {string} A representation of the current vector as a string.
	 */
	toString() {
		return `(${this.x}, ${this.y})`
	}

	/**
	 * Whether the vector is equal to another vector.
	 * @param  {Vector} v The vector to compare to.
	 * @return {boolean}  Whether the current vector is equal to the given vector.
	 */
	equalTo(v) {
		return this.x == v.x && this.y == v.y
	}

	/**
	 * Return the dot product of the current vector and another vector.
	 * @param  {Vector} v   The other vector we should calculate the dot product with.
	 * @return {Vector}	 The current vector. Useful for daisy-chaining calls.
	 */
	dot(v) {
		return this.x * v.x + this.y * v.y
	}

	/**
	 * Get the length of the current vector.
	 * @return {Number} The length of the current vector.
	 */
	mag() {
		return hypot(this.x, this.y)
	}

	/**
	 * Get the unit vector of the current vector - that is a vector poiting in the same direction with a length of 1.
	 * @return {Vector}     The current vector.
	 */
	unit() {
		return this.divide(this.mag())
	}

	/**
	 * Add another vector to this vector.
	 * @param {Vector} vec    The vector to add.
	 * @return {Vector}     The current vector.
	 */
	add(vec) {
		this.x += vec.x
		this.y += vec.y
		return this
	}

	/**
	 * Add another vector multiplayed by number to this vector.
	 * @param {Vector} vec      The vector to add.
	 * @param {number} scale    Value to multiply another vector.
	 * @return {Vector}     The current vector.
	 */
	addScaled(vec, scale) {
		this.x += vec.x * scale
		this.y += vec.y * scale
		return this
	}

	/**
	 * Add x, y values to this vector.
	 * @param {number} x      x value to add.
	 * @param {number} y      y value to add.
	 * @return {Vector}     The current vector.
	 */
	addXY(x, y) {
		this.x += x
		this.y += y
		return this
	}

	/**
	 * Swap x and y values
	 * @return {Vector}     The current vector.
	 */
	swapXY() {
		const { x, y } = this
		this.x = y
		this.y = x
		return this
	}

	/**
	 * Take another vector from this vector.
	 * @param  {Vector} vec The vector to subtrace from this one.
	 * @return {Vector} The current vector.
	 */
	subtract(vec) {
		this.x -= vec.x
		this.y -= vec.y
		return this
	}

	/**
	 * Multiply the current vector by a given value.
	 * @param  {Number} value The number to multiply the current vector by.
	 * @return {Vector}	   The current vector.
	 */
	multiply(value) {
		this.x *= value
		this.y *= value
		return this
	}

	/**
	 * Divide the current vector by a given value.
	 * @param  {Number} value The number to divide by.
	 * @return {Vector}	   The current vector.
	 */
	divide(value) {
		if (value === 0) return this
		this.x /= value
		this.y /= value
		return this
	}

	/**
	 * Rotate the current vector by a given angle.
	 * @param  {Number} angle The number to totate vector by.
	 * @return {Vector}	   The current vector.
	 */
	rotate(angle) {
		const x = this.x * cos(angle) - this.y * sin(angle)
		const y = this.x * sin(angle) + this.y * cos(angle)

		this.x = x
		this.y = y

		return this
	}

	/**
	 * Limit the length of the current vector to value without changing the
	 * direction in which the vector is pointing.
	 * @param  {Number} value The number to limit the current vector's length to.
	 * @return {Vector}	   The current vector. useful for daisy-chaining calls.
	 */
	limit(value) {
		const l = this.mag()
		if (l > value) {
			this.divide(l).multiply(value)
		}
		return this
	}

	/**
	 * Calculates current vector angle.
	 * @return {Number} angle in radians.
	 */
	angle() {
		return atan2(this.y, this.x)
	}

	/**
	 * Return array with x and y values of current vector
	 * @return {number[]} array with 2 values
	 */
	toArray() {
		return [this.x, this.y]
	}

	/**
	 * Check if current vector equals {x: 0, y: 0}
	 *
	 * @return {boolean} True if vector is zero
	 */
	isEmpty() {
		return this.x === 0 && this.y === 0
	}

	round() {
		this.x = Math.round(this.x)
		this.y = Math.round(this.y)
		return this
	}

	/**
	 * Returns a new vector based on an angle and a length.
	 * @param	{Number}	angle	The angle, in radians.
	 * @param	{Number}	length	The length.
	 * @return	{Vector}	A new vector that represents the (x, y) of the specified angle and length.
	 */
	static fromAngle(angle, length) {
		return new Vector(length * cos(angle), length * sin(angle))
	}

	/**
	 * Take one vector from another vector.
	 * @param  {Vector} v1 First vector.
	 * @param  {Vector} v2 Second vector.
	 * @return {Vector} New vector.
	 */
	static subtract(v1, v2) {
		return new Vector(v1.x - v2.x, v1.y - v2.y)
	}

	/**
	 * Add one vector to another vector.
	 * @param  {Vector} v1 First vector.
	 * @param  {Vector} v2 Second vector.
	 * @return {Vector} New vector.
	 */
	static add(v1, v2) {
		return new Vector(v1.x + v2.x, v1.y + v2.y)
	}

	/**
	 * Create new vector with given values.
	 *
	 * @param {number} x
	 * @param {number} y
	 *
	 * @return {Vector}
	 */
	static of(x, y) {
		return new Vector(x, y)
	}

	/**
	 * Clones the current vector.
	 * @return {Vector} A clone of the current vector. Very useful for passing around copies of a vector if you don't want the original to be altered.
	 */
	static copy(vec) {
		return Vector.of(vec.x, vec.y)
	}
}
