using System;
using UnityEngine;

namespace pdinklag
{
    [Serializable]
    public struct MinMax
    {
        public static readonly MinMax Infinity = new MinMax(float.NegativeInfinity, float.PositiveInfinity);

        public float Min;
        public float Max;

        /// <summary>
        /// The difference between min and max.
        /// </summary>
        public float Delta => Max - Min;

        /// <summary>
        /// Constructs a range.
        /// </summary>
        /// <param name="min">the minimum value</param>
        /// <param name="max">the maximum value</param>
        public MinMax(float min, float max)
        {
            Min = min;
            Max = max;
        }

        /// <summary>
        /// Clamps a value to the defined range.
        /// </summary>
        /// <param name="x">the value to clamp</param>
        /// <returns></returns>
        public float Clamp(float x) => Mathf.Clamp(x, Min, Max);

        /// <summary>
        /// Linearly interpolates within the range.
        /// </summary>
        /// <param name="t">the time point (0 -> min, 1 -> max)</param>
        /// <returns></returns>
        public float Lerp(float t) => Mathf.Lerp(Min, Max, t);

        /// <summary>
        /// Linearly interpolates within the range, reversed.
        /// </summary>
        /// <param name="t">the time point (0 -> max, 1 -> min)</param>
        /// <returns></returns>
        public float RLerp(float t) => Mathf.Lerp(Max, Min, t);

        /// <summary>
        /// Tests if a value is in the defined range.
        /// </summary>
        /// <param name="x">the value in question</param>
        /// <returns></returns>
        public bool Contains(float x) => x >= Min && x <= Max;

        /// <summary>
        /// The relative value of the given value within the range.
        /// </summary>
        /// <param name="x">the value in question</param>
        /// <returns></returns>
        public float Relative(float x) => Min != Max ? (x - Min) / Delta : 0;

        /// <summary>
        /// The relative value of the clamped given value within the range.
        /// </summary>
        /// <param name="x">the value in question</param>
        /// <returns></returns>
        public float RelativeClamped(float x) => (x <= Min) ? 0 : (x >= Max ? 1 : Relative(x));

        /// <summary>
        /// Expands the range in both directions, i.e. subtracts it from the minimum and adds it from the maximum.
        /// </summary>
        /// <param name="amount">the amount of the expansion</param>
        /// <returns>the expanded range</returns>
        public MinMax Expand(float amount) => new MinMax(Min - amount, Max + amount);

        /// <summary>
        /// Reduces the range in both directions, i.e. adds it to the minimum and removes it from the minimum.
        /// 
        /// Note that no checks are made ensuring <code>Max >= Min</code> after this operation.
        /// </summary>
        /// <param name="amount">the amount of the expansion</param>
        /// <returns>the expanded range</returns>
        public MinMax Reduce(float amount) => Expand(-amount);

        /// <summary>
        /// Gets a random value in the defined range.
        /// </summary>
        /// <returns></returns>
        public float Random() => UnityEngine.Random.Range(Min, Max);
    }
}
