"use strict";

/******************************************************
 ************** Simple ES6 Vector Class  **************
 ******************************************************
 * Author: Starbeamrainbowlabs
 * Twitter: @SBRLabs
 * Email: feedback at starbeamrainbowlabs dot com
 * 
 * From https://gist.github.com/sbrl/69a8fa588865cacef9c0
 ******************************************************
 * Originally written for my 2D Graphics ACW at Hull
 * University.
 ******************************************************
 * Changelog
 ******************************************************
 * 19th December 2015:
	* Added this changelog.
 * 28th December 2015:
	* Rewrite tests with klud.js + Node.js
 * 30th January 2016:
	* Tweak angleFrom function to make it work properly.
 * 31st January 2016:
	* Add the moveTowards function.
	* Add the minComponent getter.
	* Add the maxComponent getter.
	* Add the equalTo function.
	* Tests still need to be written for all of the above.
 * 19th September 2016:
	* Added Vector support to the multiply method.
 * 10th June 2017:
	* Fixed a grammatical mistake in a comment.
	* Added Vector.fromBearing static method.
 * 21st October 2017:
 	* Converted to ES6 module.
 	* Added Vector.zero and Vector.one constants. Remember to clone them!
 * 4th August 2018: (#LOWREZJAM!)
 	* Optimised equalTo()
 * 6th August 2018: (#LOWREZJAM again!)
 	* Added round(), floor(), and ceil()
 * 7th August 2018: (moar #LOWREZJAM :D)
 	* Added area() and snapTo(grid_size)
 * 10th August 2018: (even more #LOWREZJAM!)
 	* Added Vector support to divide()
 */

class Vector {
	// Constructor
	constructor(inX, inY) {
		if(typeof inX != "number")
			throw new Error("Invalid x value.");
		if(typeof inY != "number")
			throw new Error("Invalid y value.");
		
		// Store the (x, y) coordinates
		this.x = inX;
		this.y = inY;
	}

	/**
	 * Add another vector to this vector.
	 * @param	{Vector}	v	The vector to add.
	 * @return	{Vector}	The current vector. useful for daisy-chaining calls.
	 */
	add(v) {
		this.x += v.x;
		this.y += v.y;

		return this;
	}

	/**
	 * Take another vector from this vector.
	 * @param  {Vector} v The vector to subtrace from this one.
	 * @return {Vector}   The current vector. useful for daisy-chaining calls.
	 */
	subtract(v) {
		this.x -= v.x;
		this.y -= v.y;

		return this;
	}

	/**
	 * Divide the current vector by a given value.
	 * @param  {Number|Vector} value The number (or Vector) to divide by.
	 * @return {Vector}	   The current vector. Useful for daisy-chaining calls.
	 */
	divide(value) {
		if(value instanceof Vector)
		{
			this.x /= value.x;
			this.y /= value.y;
		}
		else if(typeof value == "number")
		{
			this.x /= value;
			this.y /= value;
		}
		else
			throw new Error("Can't divide by non-number value.");
		
		return this;
	}

	/**
	 * Multiply the current vector by a given value.
	 * @param  {Number|Vector} value The number (or Vector) to multiply the current vector by.
	 * @return {Vector}	   The current vector. useful for daisy-chaining calls.
	 */
	multiply(value) {
		if(value instanceof Vector)
		{
			this.x *= value.x;
			this.y *= value.y;
		}
		else if(typeof value == "number")
		{
			this.x *= value;
			this.y *= value;
		}
		else
			throw new Error("Can't multiply by non-number value.");
		
		return this;
	}
	
	/**
	 * Move the vector towards the given vector by the given amount.
	 * @param  {Vector} v      The vector to move towards.
	 * @param  {Number} amount The distance to move towards the given vector.
	 */
	moveTowards(v, amount)
	{
		// From http://stackoverflow.com/a/2625107/1460422
		var dir = new Vector(
			v.x - this.x,
			v.y - this.y
		).limitTo(amount);
		this.x += dir.x;
		this.y += dir.y;
		
		return this;
	}
	
	/**
	 * Rounds the x and y components of this vector down to the next integer.
	 * @return	{Vector}	This vector - useful for diasy-chaining.
	 */
	floor() {
		this.x = Math.floor(this.x);
		this.y = Math.floor(this.y);
		
		return this;
	}
	/**
	 * Rounds the x and y components of this vector up to the next integer.
	 * @return	{Vector}	This vector - useful for diasy-chaining.
	 */
	ceil() {
		this.x = Math.ceil(this.x);
		this.y = Math.ceil(this.y);
		
		return this;
	}
	/**
	 * Rounds the x and y components of this vector to the nearest integer.
	 * @return	{Vector}	This vector - useful for diasy-chaining.
	 */
	round() {
		this.x = Math.round(this.x);
		this.y = Math.round(this.y);
		
		return this;
	}
	
	/**
	 * Calculates the 'area' of this vector and returns the result.
	 * In other words, returns x * y. Useful if you're using a Vector to store 
	 * a size.
	 * @return {Number} The 'area' of this vector.
	 */
	area() {
		return this.x * this.y;
	}
	
	/**
	 * Snaps this vector to an imaginary square grid with the specified sized 
	 * squares.
	 * @param	{Number}	grid_size	The size of the squares on the imaginary  grid to which to snap.
	 * @return	{Vector}	The current vector - useful for daisy-chaining.
	 */
	snapTo(grid_size) {
		this.x = Math.floor(this.x / grid_size) * grid_size;
		this.y = Math.floor(this.y / grid_size) * grid_size;
		
		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.
	 */
	limitTo(value) {
		if(typeof value != "number")
			throw new Error("Can't limit to non-number value.");
		
		this.divide(this.length);
		this.multiply(value);

		return this;
	}

	/**
	 * 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.
	 */
	dotProduct(v) {
		return (this.x * v.x) + (this.y * v.y);
	}

	/**
	 * Calculate the angle, in radians, from north to another vector.
	 * @param  {Vector} v The other vector to which to calculate the angle.
	 * @return {Vector}	 The current vector. useful for daisy-chaining calls.
	 */
	angleFrom(v) {
		// From http://stackoverflow.com/a/16340752/1460422
		var angle = Math.atan2(v.y - this.y, v.x - this.x) - (Math.PI / 2);
		angle += Math.PI/2;
		if(angle < 0) angle += Math.PI * 2;
		return angle;
	}

	/**
	 * 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.
	 */
	clone() {
		return new Vector(this.x, this.y);
	}

	/*
	 * Returns a representation of the current vector as a string.
	 * @returns {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;
	}

	/**
	 * Get the unit vector of the current vector - that is a vector poiting in the same direction with a length of 1. Note that this does *not* alter the original vector.
	 * @return {Vector} The current vector's unit form.
	 */
	get unitVector() {
		var length = this.length;
		return new Vector(
		this.x / length,
		this.y / length);
	}

	/**
	 * Get the length of the current vector.
	 * @return {Number} The length of the current vector.
	 */
	get length() {
		return Math.sqrt((this.x * this.x) + (this.y * this.y));
	}
	
	/**
	 * Get the value of the minimum component of the vector.
	 * @return {Number} The minimum component of the vector.
	 */
	get minComponent() {
		return Math.min(this.x, this.y);
	}
	
	/**
	 * Get the value of the maximum component of the vector.
	 * @return {Number} The maximum component of the vector.
	 */
	get maxComponent() {
		return Math.min(this.x, this.y);
	}
}

/**
 * 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.
 */
Vector.fromBearing = function(angle, length) {
	return new Vector(
		length * Math.cos(angle),
		length * Math.sin(angle)
	);
}

Vector.zero = new Vector(0, 0);
Vector.one = new Vector(1, 1);

export default Vector;