/*
 * +================================================================================+
 *      __  ___  ______________  __  __  ______________   ____
 *     / / / / |/ /  _/_  __/\ \/ / / / / /_  __/  _/ /  / __/
 *    / /_/ /    // /  / /    \  / / /_/ / / / _/ // /___\ \
 *    \____/_/|_/___/ /_/     /_/  \____/ /_/ /___/____/___/
 *
 * +================================================================================+
 *
 *    "THE BEER-WARE LICENSE"
 *    -----------------------
 *
 *    As long as you retain this notice you can do whatever you want with this
 *    stuff. However it is expressly forbidden to sell or commercially distribute
 *    Unity Utils or parts of it apart from your games.
 *    If you meet any of the authors some day, and you think this stuff is
 *    worth it, you can buy them a beer in return.
 *
 *    THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 *    IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 *    FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 *    AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 *    LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 *    OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 *    THE SOFTWARE.
 *
 * +================================================================================+
 *
 *    Authors:
 *    David Krummenacher (dave@timbookedtwo.com)
 *
 */

using UnityEngine;

namespace Utils.Helpers {

	/// <summary>
	/// Helper methods for Vectors.
	/// </summary>
	public abstract class VectorHelper {

		#region Randomization

		/// <summary>
		/// Get a random Vector2. Ranges from [0, 0] to [1, 1].
		/// </summary>
		/// <returns>A random Vector2.</returns>
		public static Vector2 RandomVector2() {
			return new Vector2(Random.value, Random.value);
		}
		
		/// <summary>
		/// Get a random Vector2 within a certain range.
		/// </summary>
		/// <returns>A random Vector2.</returns>
		/// <param name="v1">Range point 1</param>
		/// <param name="v2">Range point 2</param>
		public static Vector2 RandomRangeVector2(Vector2 v1, Vector2 v2) {
			return new Vector2(Random.Range(v1.x, v2.x), Random.Range(v1.y, v2.y));
		}
		/// <summary>
		/// Get a random Vector2 within a certain range.
		/// </summary>
		/// <returns>A random Vector2.</returns>
		/// <param name="b">Bounds (only x and y are regarded)</param>
		public static Vector2 RandomRangeVector2(Bounds b) {
			return new Vector2(Random.Range(b.min.x, b.max.x), Random.Range(b.min.y, b.max.y));
		}
		
		/// <summary>
		/// Get a random Vector3. Ranges from [0, 0, 0] to [1, 1, 1].
		/// </summary>
		/// <returns>A random Vector3.</returns>
		public static Vector3 RandomVector3() {
			return new Vector3(Random.value, Random.value, Random.value);
		}
		
		/// <summary>
		/// Get a random Vector3 within a certain range.
		/// </summary>
		/// <returns>A random Vector3.</returns>
		/// <param name="v1">Range point 1</param>
		/// <param name="v2">Range point 2</param>
		public static Vector3 RandomRangeVector3(Vector3 v1, Vector3 v2) {
			return new Vector3(Random.Range(v1.x, v2.x), Random.Range(v1.y, v2.y), Random.Range(v1.z, v2.z));
		}
		/// <summary>
		/// Get a random Vector3 within a certain range.
		/// </summary>
		/// <returns>A random Vector3.</returns>
		/// <param name="b">Bounds</param>
		public static Vector3 RandomRangeVector3(Bounds b) {
			return new Vector3(Random.Range(b.min.x, b.max.x), Random.Range(b.min.y, b.max.y), Random.Range(b.min.z, b.max.z));
		}

		#endregion

		#region Conversion
		
		/// <summary>
		/// Converts spherical coordinates to cartesian coordinates.
		/// </summary>
		/// <param name="radius">Radius.</param>
		/// <param name="polar">Polar.</param>
		/// <param name="elevation">Elevation.</param>
		/// <param name="cartesian">Cartesian.</param>
		public static void SphericalToCartesian(float radius, float polar, float elevation, out Vector3 cartesian){
			float a = radius * Mathf.Cos(elevation);
			cartesian.x = a * Mathf.Cos(polar);
			cartesian.y = radius * Mathf.Sin(elevation);
			cartesian.z = a * Mathf.Sin(polar);
		}
		/// <summary>
		/// Converts spherical coordinates to cartesian coordinates.
		/// </summary>
		/// <returns>Cartesian coordinates.</returns>
		/// <param name="radius">Radius.</param>
		/// <param name="polar">Polar.</param>
		/// <param name="elevation">Elevation.</param>
		public static Vector3 SphericalToCartesian(float radius, float polar, float elevation){
			float a = radius * Mathf.Cos(elevation);
			Vector3 cartesian = Vector3.zero;
			cartesian.x = a * Mathf.Cos(polar);
			cartesian.y = radius * Mathf.Sin(elevation);
			cartesian.z = a * Mathf.Sin(polar);
			return cartesian;
		}
		
		/// <summary>
		/// Converts Cartesian coordinates to spherical coordinates.
		/// </summary>
		/// <param name="cartesian">Cartesian.</param>
		/// <param name="radius">Radius.</param>
		/// <param name="polar">Polar.</param>
		/// <param name="elevation">Elevation.</param>
		public static void CartesianToSpherical(Vector3 cartesian, out float radius, out float polar, out float elevation){
			if (cartesian.x == 0) cartesian.x = Mathf.Epsilon;
			radius = Mathf.Sqrt((cartesian.x * cartesian.x) + (cartesian.y * cartesian.y) + (cartesian.z * cartesian.z));
			polar = Mathf.Atan(cartesian.z / cartesian.x);
			if (cartesian.x < 0) polar += Mathf.PI;
			elevation = Mathf.Asin(cartesian.y / radius);
		}
		
		/// <summary>
		/// Converts polar coordinates to cartesian coordinates.
		/// </summary>
		/// <param name="radius">Radius.</param>
		/// <param name="angle">Angle.</param>
		/// <param name="cartesian">Cartesian.</param>
		public static void PolarToCartesian(float radius, float angle, out Vector2 cartesian) {
			cartesian.x = radius * Mathf.Cos(angle);
			cartesian.y = radius * Mathf.Sin(angle);
		}
		/// <summary>
		/// Converts polar coordinates to cartesian coordinates.
		/// </summary>
		/// <returns>Cartesian coordinates.</returns>
		/// <param name="radius">Radius.</param>
		/// <param name="angle">Angle.</param>
		public static Vector2 PolarToCartesian(float radius, float angle) {
			Vector2 cartesian = Vector2.zero;
			cartesian.x = radius * Mathf.Cos(angle);
			cartesian.y = radius * Mathf.Sin(angle);
			return cartesian;
		}
		
		/// <summary>
		/// Converts Cartesian coordinates to polar coordinates.
		/// </summary>
		/// <param name="cartesian">Cartesian.</param>
		/// <param name="radius">Radius.</param>
		/// <param name="angle">Angle.</param>
		public static void CartesianToPolar(Vector2 cartesian, out float radius, out float angle) {
			radius = Mathf.Sqrt((cartesian.x * cartesian.y) + (cartesian.y + cartesian.y));
			angle = Mathf.Atan(cartesian.y / cartesian.x);
			if (cartesian.x < 0) angle += Mathf.PI;
			if (cartesian.y < 0) angle += Mathf.PI;
		}

		/// <summary>
		/// Converts isometric coordinates to planar coordinates.
		/// </summary>
		/// <returns>Planar coordinates.</returns>
		/// <param name="isometric">Isometric coordinates.</param>
		/// <param name="tileWidth">Tile width.</param>
		/// <param name="tileHeight">Tile height.</param>
		public static Vector2 IsometricToPlanar(Vector2 isometric, float tileWidth = 2f, float tileHeight = 1f) {
			Vector2 planar = Vector2.zero;
			planar.x = (isometric.y - isometric.x) * tileWidth / 2f;
			planar.y = (isometric.y + isometric.x) * tileHeight / 2f;
			return planar;
		}
		
		/// <summary>
		/// Converts planar coordinates to isometric coordinates.
		/// </summary>
		/// <returns>Isometric coordinates.</returns>
		/// <param name="planar">Planar coordinates.</param>
		/// <param name="tileWidth">Tile width.</param>
		/// <param name="tileHeight">Tile height.</param>
		public static Vector2 PlanarToIso(Vector2 planar, float tileWidth = 2f, float tileHeight = 1f) {
			Vector2 isometric = Vector2.zero;
			isometric.x = (2 * planar.y - planar.x) / tileWidth;
			isometric.y = (2 * planar.y + planar.x) / (tileHeight * 2);
			return isometric;
		}
		
		/// <summary>
		/// Returns the isometric depth.
		/// </summary>
		/// <returns>The isometric depth.</returns>
		/// <param name="y">The y coordinate.</param>
		public static float IsoDepth(float y) { return -y; }
		/// Returns the isometric depth.
		/// </summary>
		/// <returns>The isometric depth.</returns>
		/// <param name="planar">The planar coordinates.</param>
		public static float IsoDepth(Vector2 planar) { return IsoDepth(planar.y); }

		#endregion

		#region Interpolation

		/// <summary>
		/// Lerps a Vector2 with overshoot.
		/// </summary>
		/// <returns>The Vector2.</returns>
		/// <param name="from">From.</param>
		/// <param name="to">To.</param>
		/// <param name="t">T.</param>
		public static Vector2 LerpWithOvershoot(Vector2 from, Vector2 to, float t) {
			return from + (to - from).normalized * Vector2.Distance(from, to) * t;
		}

		/// <summary>
		/// Lerps a Vector3 with overshoot.
		/// </summary>
		/// <returns>The Vector3.</returns>
		/// <param name="from">From.</param>
		/// <param name="to">To.</param>
		/// <param name="t">T.</param>
		public static Vector3 LerpWithOvershoot(Vector3 from, Vector3 to, float t) {
			return from + (to - from).normalized * Vector3.Distance(from, to) * t;
		}

		#endregion

		#region Calculation

		/// <summary>
		/// Will return the nearest point on a line to a point. Useful for making an object follow a track. 
		/// </summary>
		/// <returns>Nearest point.</returns>
		/// <param name="lineStart">Line start.</param>
		/// <param name="lineEnd">Line end.</param>
		/// <param name="point">Point.</param>
		public static Vector3 NearestPoint(Vector3 lineStart, Vector3 lineEnd, Vector3 point) {
			Vector3 lineDirection = Vector3.Normalize(lineEnd - lineStart);
			float closestPoint = Vector3.Dot((point - lineStart), lineDirection) / Vector3.Dot(lineDirection, lineDirection);
			return lineStart + (closestPoint * lineDirection);
		}

		/// <summary>
		/// Will return the nearest point on a line to a point. Useful for making an object follow a track. 
		/// Works like NearestPoint except the end of the line is clamped. 
		/// </summary>
		/// <returns>Nearest point.</returns>
		/// <param name="lineStart">Line start.</param>
		/// <param name="lineEnd">Line end.</param>
		/// <param name="point">Point.</param>
		public static Vector3 NearestPointStrict(Vector3 lineStart, Vector3 lineEnd, Vector3 point) {
			Vector3 fullDirection = lineEnd - lineStart;
			Vector3 lineDirection = Vector3.Normalize(fullDirection);
			float closestPoint = Vector3.Dot((point - lineStart), lineDirection) / Vector3.Dot(lineDirection, lineDirection);
			return lineStart + (Mathf.Clamp(closestPoint, 0.0f, Vector3.Magnitude(fullDirection)) * lineDirection);
		}

		/// <summary>
		/// Gets an vector offset to a transform.
		/// </summary>
		/// <returns>The vector offset to a transform.</returns>
		/// <param name="transform">The transform.</param>
		/// <param name="offset">The offset.</param>
		public static Vector3 OffsetToTransform(Transform transform, Vector3 offset) {
			return transform.position + transform.right * offset.x + transform.up * offset.y + transform.forward * offset.z;
		}

		#endregion
	}
}
