using System;
using UnityEngine;

namespace Cryville.Common.Unity {
	public class PropertyTweener<T> {
		readonly Func<T> _getter;
		readonly Action<T> _setter;
		readonly Tweener<T> _tweener;
		public PropertyTweener(Func<T> getter, Action<T> setter, Tweener<T> tweener) {
			_getter = getter;
			_setter = setter;
			_tweener = tweener;
		}
		public PropertyTweener<T> Start(T endValue, float duration) {
			_tweener.Start(_getter(), endValue, duration);
			return this;
		}
		public void Advance(float deltaTime) {
			_setter(_tweener.Advance(deltaTime));
		}
	}
	public class Tweener<T> {
		readonly Func<T, T, T> _addition;
		readonly Func<float, T, T> _multiplication;
		public Tweener(Func<T, T, T> addition, Func<float, T, T> multiplication) {
			_addition = addition;
			_multiplication = multiplication;
		}
		public Func<float, float> EasingFunction { get; set; } = EasingFunctions.Linear;
		public Tweener<T> SetEasingFunction(Func<float, float> easing) {
			EasingFunction = easing;
			return this;
		}

		T _startValue = default;
		T _endValue = default;
		float _duration = float.PositiveInfinity;
		float _time;
		public Tweener<T> Start(T startValue, T endValue, float duration) {
			_startValue = startValue;
			_endValue = endValue;
			_duration = duration;
			_time = 0;
			return this;
		}
		public T Advance(float deltaTime) {
			_time += deltaTime;
			var ratio = EasingFunction(Math.Clamp(_time / _duration, 0, 1));
			return _addition(_multiplication(1 - ratio, _startValue), _multiplication(ratio, _endValue));
		}
	}
	public static class Tweeners {
		public static Tweener<int> Int32 => new((a, b) => a + b, (k, v) => (int)(k * v));
		public static Tweener<float> Single => new((a, b) => a + b, (k, v) => k * v);
		public static Tweener<double> Double => new((a, b) => a + b, (k, v) => k * v);
		public static Tweener<Vector2> Vector2 => new((a, b) => a + b, (k, v) => k * v);
		public static Tweener<Vector3> Vector3 => new((a, b) => a + b, (k, v) => k * v);
		public static Tweener<Quaternion> Quaternion => new((a, b) => a * b, (k, v) => UnityEngine.Quaternion.Slerp(UnityEngine.Quaternion.identity, v, k));
	}
	public static class EasingFunctions {
		public static float Linear(float x) => x;
		public static float InQuad(float x) => x * x;
		public static float InCubic(float x) => x * x * x;
		public static float InSine(float x) => 1 - OutSine(1 - x);
		public static float OutQuad(float x) => 1 - InQuad(1 - x);
		public static float OutCubic(float x) => 1 - InCubic(1 - x);
		public static float OutSine(float x) => MathF.Sin(x * MathF.PI / 2);
	}
}
