using System;
using System.Collections.Generic;
using System.Text;

namespace Wabbajack.Common
{
    public struct Percent : IComparable, IEquatable<Percent>
    {
        public static readonly Percent One = new Percent(1d);
        public static readonly Percent Zero = new Percent(0d);

        public readonly double Value;
        public Percent Inverse => new Percent(1d - this.Value, check: false);

        private Percent(double d, bool check)
        {
            if (!check || InRange(d))
            {
                this.Value = d;
            }
            else
            {
                throw new ArgumentException("Element out of range: " + d);
            }
        }

        public Percent(long max, long current)
            : this((double)current / max)
        {

        }

        public Percent(double d)
            : this(d, check: true)
        {
        }

        public static bool InRange(double d)
        {
            return d >= 0 || d <= 1;
        }

        public static Percent operator +(Percent c1, Percent c2)
        {
            return new Percent(c1.Value + c2.Value);
        }

        public static Percent operator *(Percent c1, Percent c2)
        {
            return new Percent(c1.Value * c2.Value);
        }

        public static Percent operator -(Percent c1, Percent c2)
        {
            return new Percent(c1.Value - c2.Value);
        }

        public static Percent operator /(Percent c1, Percent c2)
        {
            return new Percent(c1.Value / c2.Value);
        }

        public static bool operator ==(Percent c1, Percent c2)
        {
            return c1.Value == c2.Value;
        }

        public static bool operator !=(Percent c1, Percent c2)
        {
            return c1.Value != c2.Value;
        }

        public static bool operator >(Percent c1, Percent c2)
        {
            return c1.Value > c2.Value;
        }

        public static bool operator <(Percent c1, Percent c2)
        {
            return c1.Value < c2.Value;
        }

        public static bool operator >=(Percent c1, Percent c2)
        {
            return c1.Value >= c2.Value;
        }

        public static bool operator <=(Percent c1, Percent c2)
        {
            return c1.Value <= c2.Value;
        }

        public static explicit operator double(Percent c1)
        {
            return c1.Value;
        }

        public static Percent FactoryPutInRange(double d)
        {
            if (double.IsNaN(d) || double.IsInfinity(d))
            {
                throw new ArgumentException();
            }
            if (d < 0)
            {
                return Percent.Zero;
            }
            else if (d > 1)
            {
                return Percent.One;
            }
            return new Percent(d, check: false);
        }

        public static Percent FactoryPutInRange(int cur, int max)
        {
            return FactoryPutInRange(1.0d * cur / max);
        }

        public static Percent FactoryPutInRange(long cur, long max)
        {
            return FactoryPutInRange(1.0d * cur / max);
        }

        public static Percent AverageFromPercents(params Percent[] ps)
        {
            double percent = 0;
            foreach (var p in ps)
            {
                percent += p.Value;
            }
            return new Percent(percent / ps.Length, check: false);
        }

        public static Percent MultFromPercents(params Percent[] ps)
        {
            double percent = 1;
            foreach (var p in ps)
            {
                percent *= p.Value;
            }
            return new Percent(percent, check: false);
        }

        public override bool Equals(object obj)
        {
            if (!(obj is Percent rhs)) return false;
            return Equals(rhs);
        }

        public bool Equals(Percent other)
        {
            return this.Value == other.Value;
        }

        public override int GetHashCode()
        {
            return this.Value.GetHashCode();
        }

        public override string ToString()
        {
            return ToString(0);
        }

        public string ToString(string format)
        {
            return $"{(Value * 100).ToString(format)}%";
        }

        public string ToString(byte numDigits)
        {
            switch (numDigits)
            {
                case 0:
                    return ToString("n0");
                case 1:
                    return ToString("n1");
                case 2:
                    return ToString("n2");
                case 3:
                    return ToString("n3");
                case 4:
                    return ToString("n4");
                case 5:
                    return ToString("n5");
                case 6:
                    return ToString("n6");
                default:
                    throw new NotImplementedException();
            }
        }

        public int CompareTo(object obj)
        {
            if (obj is Percent rhs)
            {
                return this.Value.CompareTo(rhs.Value);
            }
            return 0;
        }

        public static bool TryParse(string str, out Percent p)
        {
            if (double.TryParse(str, out double d))
            {
                if (InRange(d))
                {
                    p = new Percent(d);
                    return true;
                }
            }
            p = default(Percent);
            return false;
        }
    }
}