using System;

namespace UVC.Extension
{
    /// <summary>
    /// 숫자 관련 확장 메서드를 제공하는 클래스입니다.
    /// </summary>
    public static class NumberEx
    {
        /// <summary>
        ///  바이트 단위의 숫자를 사람이 읽기 쉬운 KB, MB, GB 등의 형태로 변환합니다.
        ///  1024.ToSizeSuffix() => 1KB
        ///  https://stackoverflow.com/questions/14488796/does-net-provide-an-easy-way-convert-bytes-to-kb-mb-gb-etc
        /// </summary>
        /// <param name="value">변환할 바이트 크기</param>
        /// <param name="decimalPlaces">소수점 자릿수 (기본값: 1)</param>
        /// <returns>단위가 포함된 크기 문자열 (예: "1.5 KB")</returns>
        /// <exception cref="ArgumentOutOfRangeException">decimalPlaces가 0보다 작은 경우 발생</exception>
        /// <example>
        /// <code>
        /// // 바이트 값을 크기 단위와 함께 표시
        /// long fileSize = 1536;
        /// string readableSize = fileSize.ToSizeSuffix(); // "1.5 KB" 반환
        /// 
        /// // 소수점 2자리까지 표시
        /// string preciseSize = fileSize.ToSizeSuffix(2); // "1.50 KB" 반환
        /// 
        /// // 더 큰 값 변환
        /// long bigFileSize = 1073741824; // 1GB
        /// string bigSize = bigFileSize.ToSizeSuffix(); // "1.0 GB" 반환
        /// </code>
        /// </example>
        public static string ToSizeSuffix(this long value, int decimalPlaces = 1)
        {
            if (decimalPlaces < 0) { throw new ArgumentOutOfRangeException("decimalPlaces"); }
            if (value < 0) { return "-" + ToSizeSuffix(-value, decimalPlaces); }
            if (value == 0) { return string.Format("{0:n" + decimalPlaces + "} bytes", 0); }

            // mag는 단위를 나타냄: 0=바이트, 1=KB, 2=MB 등
            int mag = (int)Math.Log(value, 1024);

            // 1L << (mag * 10) == 2 ^ (10 * mag)
            // [즉, mag에 해당하는 단위의 바이트 수]
            decimal adjustedSize = (decimal)value / (1L << (mag * 10));

            // 반올림했을 때 1000 이상이 되는 경우 단위를 한 단계 올림
            if (Math.Round(adjustedSize, decimalPlaces) >= 1000)
            {
                mag += 1;
                adjustedSize /= 1024;
            }

            string[] SizeSuffixes = { "bytes", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB" };

            return string.Format("{0:n" + decimalPlaces + "} {1}",
                adjustedSize,
                SizeSuffixes[mag]);
        }

        /// <summary>
        /// 숫자가 지정된 범위 내에 있는지 확인합니다.
        /// </summary>
        /// <typeparam name="T">비교 가능한 숫자 타입</typeparam>
        /// <param name="value">검사할 값</param>
        /// <param name="min">최소값 (포함)</param>
        /// <param name="max">최대값 (포함)</param>
        /// <returns>값이 지정된 범위 내에 있으면 true, 그렇지 않으면 false</returns>
        /// <example>
        /// <code>
        /// int score = 85;
        /// bool isInRange = score.IsBetween(0, 100); // true 반환
        /// 
        /// float temperature = -5.5f;
        /// bool isValidTemp = temperature.IsBetween(-10f, 40f); // true 반환
        /// </code>
        /// </example>
        public static bool IsBetween<T>(this T value, T min, T max) where T : IComparable<T>
        {
            return value.CompareTo(min) >= 0 && value.CompareTo(max) <= 0;
        }

        /// <summary>
        /// 값을 지정된 최소값과 최대값 사이로 제한합니다.
        /// </summary>
        /// <typeparam name="T">비교 가능한 숫자 타입</typeparam>
        /// <param name="value">제한할 값</param>
        /// <param name="min">최소값</param>
        /// <param name="max">최대값</param>
        /// <returns>최소값과 최대값 사이로 제한된 값</returns>
        /// <example>
        /// <code>
        /// int health = -10;
        /// int clampedHealth = health.Clamp(0, 100); // 0 반환
        /// 
        /// float speed = 120.5f;
        /// float clampedSpeed = speed.Clamp(0f, 100f); // 100f 반환
        /// </code>
        /// </example>
        public static T Clamp<T>(this T value, T min, T max) where T : IComparable<T>
        {
            if (value.CompareTo(min) < 0) return min;
            if (value.CompareTo(max) > 0) return max;
            return value;
        }

        /// <summary>
        /// 숫자를 백분율로 변환합니다.
        /// </summary>
        /// <param name="value">변환할 값 (0.0~1.0)</param>
        /// <param name="decimalPlaces">소수점 자릿수</param>
        /// <returns>백분율 문자열 (예: "85%")</returns>
        /// <example>
        /// <code>
        /// float progress = 0.85f;
        /// string percent = progress.ToPercentString(); // "85.0%" 반환
        /// 
        /// double ratio = 0.7525;
        /// string precisePercent = ratio.ToPercentString(2); // "75.25%" 반환
        /// </code>
        /// </example>
        public static string ToPercentString(this float value, int decimalPlaces = 1)
        {
            return string.Format("{0:n" + decimalPlaces + "}%", value * 100);
        }

        public static string ToPercentString(this double value, int decimalPlaces = 1)
        {
            return string.Format("{0:n" + decimalPlaces + "}%", value * 100);
        }

        /// <summary>
        /// 초 단위 시간을 시:분:초 형식의 문자열로 변환합니다.
        /// </summary>
        /// <param name="seconds">초 단위 시간</param>
        /// <returns>시:분:초 형식의 문자열</returns>
        /// <example>
        /// <code>
        /// int gameTime = 3725; // 1시간 2분 5초
        /// string formattedTime = gameTime.ToTimeString(); // "01:02:05" 반환
        /// 
        /// float recordTime = 135.5f; // 2분 15.5초
        /// string shortTime = recordTime.ToTimeString(); // "00:02:16" 반환 (반올림됨)
        /// </code>
        /// </example>
        public static string ToTimeString(this int seconds)
        {
            TimeSpan time = TimeSpan.FromSeconds(seconds);
            return time.ToString(@"hh\:mm\:ss");
        }

        public static string ToTimeString(this float seconds)
        {
            TimeSpan time = TimeSpan.FromSeconds((double)seconds);
            return time.ToString(@"hh\:mm\:ss");
        }

        /// <summary>
        /// 숫자를 서수형 문자열로 변환합니다 (1st, 2nd, 3rd 등).
        /// </summary>
        /// <param name="number">변환할 숫자</param>
        /// <returns>서수형 문자열</returns>
        /// <example>
        /// <code>
        /// int rank = 1;
        /// string rankString = rank.ToOrdinal(); // "1st" 반환
        /// 
        /// for (int i = 1; i <= 5; i++)
        /// {
        ///     Console.WriteLine(i.ToOrdinal()); // "1st", "2nd", "3rd", "4th", "5th" 출력
        /// }
        /// </code>
        /// </example>
        public static string ToOrdinal(this int number)
        {
            string suffix = "번째";
            return number + suffix;
        }

        /// <summary>
        /// 숫자의 각 자리 합을 계산합니다.
        /// </summary>
        /// <param name="number">계산할 숫자</param>
        /// <returns>각 자리 숫자의 합</returns>
        /// <example>
        /// <code>
        /// int number = 12345;
        /// int digitSum = number.SumOfDigits(); // 1+2+3+4+5 = 15 반환
        /// </code>
        /// </example>
        public static int SumOfDigits(this int number)
        {
            int sum = 0;
            number = Math.Abs(number);

            while (number > 0)
            {
                sum += number % 10;
                number /= 10;
            }

            return sum;
        }

        /// <summary>
        /// 두 숫자 사이의 선형 보간된 값을 계산합니다.
        /// </summary>
        /// <param name="start">시작값</param>
        /// <param name="end">종료값</param>
        /// <param name="t">보간 비율 (0.0 ~ 1.0)</param>
        /// <returns>보간된 값</returns>
        /// <example>
        /// <code>
        /// float min = 0f;
        /// float max = 100f;
        /// float half = min.Lerp(max, 0.5f); // 50f 반환
        /// 
        /// // 부드러운 이동 애니메이션 예제
        /// float startPosition = 0f;
        /// float endPosition = 10f;
        /// float t = 0.3f; // 30% 이동
        /// float currentPosition = startPosition.Lerp(endPosition, t); // 3f 반환
        /// </code>
        /// </example>
        public static float Lerp(this float start, float end, float t)
        {
            t = Math.Clamp(t, 0f, 1f);
            return start + (end - start) * t;
        }

        /// <summary>
        /// 숫자를 가장 가까운 배수로 반올림합니다.
        /// </summary>
        /// <param name="value">반올림할 값</param>
        /// <param name="factor">배수 단위</param>
        /// <returns>가장 가까운 배수로 반올림된 값</returns>
        /// <example>
        /// <code>
        /// int price = 1242;
        /// int roundedPrice = price.RoundToNearest(100); // 1200 반환
        /// 
        /// float value = 3.7f;
        /// float rounded = value.RoundToNearest(0.5f); // 3.5f 반환
        /// </code>
        /// </example>
        public static int RoundToNearest(this int value, int factor)
        {
            return (int)Math.Round((double)value / factor) * factor;
        }

        public static float RoundToNearest(this float value, float factor)
        {
            return (float)Math.Round(value / factor) * factor;
        }

    }
}
