/////////////////////////////////////////////////////////////////////////////////////////////////
// GF - Math
// various math utilitary functions
/////////////////////////////////////////////////////////////////////////////////////////////////
using UnityEngine;
using System.Collections;

public enum GFCurve
{
    None = -1,
    Linear,				// x
	EasyIn,				// x^2
	EasyIn3,			// x^3
	EasyInSin,			// 1-cos(x*pi/2)
	EasyOut,			// 1-(1-x)^2 
	EasyOut3,			// 1-(1-x)^3
	EasyOutSin,			// sin(x*pi/2)
	EasyInOut,          // x^2 / (2 * (x^2 - x) + 1) more easy; similar to EasyInOutSin = -0.5*(cos(pi*x)-1)
    EasyInOutBezier,	// x^2 * (3 - 2 * x) less easy
	BellSin,			// sin(x * pi) // fat
	Bell02,				// (4 * x * (1-x)) ^ 0.2 // fat
	Bell05,				// (4 * x * (1-x)) ^ 0.5
	Bell,				// (4 * x * (1-x)) ^ 1	// normal
	Bell2,				// (4 * x * (1-x)) ^ 2
	Bell4,				// (4 * x * (1-x)) ^ 4
	Bell8,				// (4 * x * (1-x)) ^ 8	// thin
	WaveIn2,			// sin((1-x)*2*pi) * x
	WaveIn4,			// sin((1-x)*4*pi) * x
	WaveIn8,			// sin((1-x)*8*pi) * x
	WaveOut2,			// sin(x*2*pi) * (1-x)
	WaveOut4,			// sin(x*4*pi) * (1-x)
	WaveOut8,			// sin(x*8*pi) * (1-x)
}

public class GFMath
{

	public static bool AlmostEqual(Vector2 a, Vector2 b, float eps = 0.01f)
	{
		return (a-b).sqrMagnitude < eps;
	}
	
	public static float Mod(float a, float n)
	{
		return a - Mathf.Floor(a / n) * n;
	}

	// clamps in 0, 360
	public static float AngleClamp360(float a)
	{
		a = a % 360.0f;
		if(a < 0.0f)
			a = 360 + a;
		return a;
	}

	// clamp angle in -180, 180
	public static float AngleClamp180(float a)
	{
		a = a % 360.0f;
		if(a > 180.0f)
			a = a - 360.0f;
		else if(a < -180.0f)
			a = 360.0f + a;
		return a;
	}
	
	// positive if first number is higher in trygonometric (CCW) order, after the second one
	// CCW ex dif(0, 10)=-10; dif(350, 10)=-20
	public static float AngleDif(float a, float b)
	{
		float d = a - b;
		d = Mod(d + 180.0f, 360.0f) - 180.0f;
		return d;

		/*
		float d = Mathf.Abs(a - b) % 360.0f;
		Debug.Log("dif "+a+"-"+b+"="+d);
		float r = (d > 180.0f) ? (-360.0f + d) : d;
		return r;
		*/
	}

    public static float AngleLerp(float a, float b, float t)
    {
        float d = AngleDif(b, a);
        return AngleClamp360(a + d * t);
    }

	// tells if an angle is in given range
	public static bool AngleInRange(float a, float min, float max)
	{
		float range = AngleDif(max, min);
		float amin = AngleDif(a, min);
		return amin >= 0 && amin < range;
	}

	// compare 2 angles
	public static int AngleCompare(float a, float b)
	{
		float dif = GFMath.AngleDif(a, b);
		if(dif == 0.0f) 
			return 0;
		else
			return dif > 0.0f ? 1 : -1;
	}

	// computes angle of a 2d vector ( (1,0) => 0 deg)
	public static float GetAngleFromPos(float x, float y)
	{
		// safety
		if(Mathf.Abs(x) < 0.001f && Mathf.Abs(y) < 0.001f)
			return 0.0f;
			
		Vector3 dir = new Vector3(x, 0.0f, y);
		dir.Normalize();
		float dx = Vector3.Dot(dir, Vector3.right);
		float dy = Vector3.Dot(dir, Vector3.forward);
		float a = Mathf.Acos(dx) * Mathf.Rad2Deg;
		return (dy >= 0.0f) ? a : (360.0f - a);
	}

	public static Vector3 GetDirFromAngle(float angle)
	{
		return new Vector3(Mathf.Cos(angle * Mathf.Deg2Rad), 0.0f, Mathf.Sin(angle * Mathf.Deg2Rad));
	}

	public static float GetClampedFactorOfValueInRange(float value, float min, float max)
	{
		if(value < min)
			value = min;
		else if(value > max)
			value = max;
		float t = (value - min) / (max - min);
		return t;
	}

    public static float GetFlatDistance(Vector3 a, Vector3 b)
    {
        Vector3 dir = b - a;
        dir.y = 0.0f;
        return dir.magnitude;
    }

    public static Vector3 GetFlatDir(Vector3 a, Vector3 b)
    {
        Vector3 dir = b - a;
        dir.y = 0.0f;
        return dir.normalized;
    }

    public static float GetFactorBetween(float a, float b, float c)
    {
        return (c - a) / (b - a);
    }

    public static float GetFactorBetween(int a, int b, int c)
    {
        return (float)(c - a) / (float)(b - a);
    }

    public static float CurvedDistribution(float x, GFCurve type)
	{
		switch(type)
		{
			case GFCurve.Linear:		
				return x;
			case GFCurve.EasyIn:		
				return x * x;
			case GFCurve.EasyIn3:	
				return x * x * x;
			case GFCurve.EasyInSin:
				return 1.0f - Mathf.Cos(x * Mathf.PI / 2.0f);
			case GFCurve.EasyOut:	
				return 1.0f - (1.0f - x) * (1.0f - x);
			case GFCurve.EasyOut3:	
				return 1.0f - (1.0f - x) * (1.0f - x) * (1.0f - x);
			case GFCurve.EasyOutSin:	
				return Mathf.Sin(x * Mathf.PI / 2.0f);
			case GFCurve.EasyInOut:
				return x * x / (2.0f * (x * x - x) + 1.0f);
			case GFCurve.EasyInOutBezier:
				return x * x * (3.0f - 2.0f * x);
            case GFCurve.BellSin:
            	return Mathf.Sin(x * Mathf.PI);
			case GFCurve.Bell02:
				return Mathf.Pow(4.0f * x * (1.0f - x), 0.2f);
			case GFCurve.Bell05:
            	return Mathf.Sqrt(4.0f * x * (1.0f - x));
            case GFCurve.Bell:
            	return 4.0f * x * (1.0f - x);
            case GFCurve.Bell2:		
				return 16.0f * x * x * (1.0f - x) * (1.0f - x);
            case GFCurve.Bell4:
				return Mathf.Pow(4.0f * x * (1.0f - x), 4.0f);
            case GFCurve.Bell8:		
				return Mathf.Pow(4.0f * x * (1.0f - x), 8.0f);
            case GFCurve.WaveIn2:
				return Mathf.Cos(x * 2.0f * Mathf.PI) * x;
            case GFCurve.WaveIn4:
				return Mathf.Cos(x * 4.0f * Mathf.PI) * x;
            case GFCurve.WaveIn8:	
				return Mathf.Cos(x * 8.0f * Mathf.PI) * x;
            case GFCurve.WaveOut2:	
				return Mathf.Sin(x * 2.0f * Mathf.PI) * (1-x);
            case GFCurve.WaveOut4:	
				return Mathf.Sin(x * 4.0f * Mathf.PI) * (1-x);
            case GFCurve.WaveOut8:	
				return Mathf.Sin(x * 8.0f * Mathf.PI) * (1-x);
			default:
				return 0.0f;
		}
	}

	// normal distribuion (not in [0,1])
	public static float NormalDistribution(float x, float mu, float sigma)
	{
		return Mathf.Exp(-0.5f * (x - mu) * (x - mu) / (sigma * sigma)) / (sigma * Mathf.Sqrt(2.0f * Mathf.PI));
	}

	// bell distribution in [0,1] examples p = fat 0.2 0.5 1 1.5 2 4 8 thin
	public static float BellDistribution(float x, float p)
	{
		return Mathf.Pow(4.0f * x * (1.0f-x), p);
	}
}
