using StatisticalDistribution.SpecialFunctions;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using U.MathematicalOperations.Functions;
using U.StringOperations;

namespace StatisticalDistribution
{
    public class StandardNormalDist : Distribution
    {
        public enum ErfFunction { Erf1, Erf2, Erf3 };
        public static string abbr = "StaN";
        private ErfFunction erfFunction;
        private static double sqrt2 = Math.Sqrt(2.0);
        private static double oneOverSqrt2 = 1.0 / Math.Sqrt(2.0);

        #region CONSTRUCTORS
        public StandardNormalDist()
            : base(Distribution.Type.StandardNormal)
        {
            this.erfFunction = ErfFunction.Erf1;
        }
        public StandardNormalDist(ErfFunction erfFunction)
            : base(Type.StandardNormal)
        {
            this.erfFunction = erfFunction;
        }
        #endregion

        #region OVERRIDE
        public override double pdf(double x) { return Constants.InvSqrt2Pi * Math.Exp(x * x / -2.0); }
        public override double cdf(double x)
        {
            switch (this.erfFunction)
            {
                case ErfFunction.Erf1: return 0.5 * (1.0 + Functions.Erf1(x * Constants.Sqrt1_2));
                case ErfFunction.Erf2: return 0.5 * (1.0 + Functions.Erf2(x * Constants.Sqrt1_2));
                case ErfFunction.Erf3: return 0.5 * (1.0 + Functions.Erf3(x * Constants.Sqrt1_2));
            }
            throw new NotImplementedException();
        }
        public override double cdfInv(double p)
        {
            if (p == 1.0) { return double.PositiveInfinity; }
            if (p == 0.0) { return double.NegativeInfinity; }
            double erfInv = -1.0;
            switch (this.erfFunction)
            {
                case ErfFunction.Erf1: erfInv = Functions.Erf1Inverse((2.0 * p) - 1.0); break;
                case ErfFunction.Erf2: erfInv = Functions.Erf2Inverse((2.0 * p) - 1.0); break;
                case ErfFunction.Erf3: erfInv = Functions.Erf3Inverse((2.0 * p) - 1.0); break;
            }
            if (double.IsPositiveInfinity(erfInv)) { return double.PositiveInfinity; }
            if (double.IsNegativeInfinity(erfInv)) { return double.NegativeInfinity; }
            return sqrt2 * erfInv;
        }
        public override double variance() { return 1.0; }
        public override double mean() { return 0.0; }
        public override double std() { return 1.0; }
        public override double skewness() { return 0.0; }
        public override double kurtosis() { return 3.0; }
        public override double mode() { return 0.0; }
        public override double median() { return 0.0; }
        public override double Q1() { return -0.67448; }
        public override double Q3() { return 0.67448; }
        #endregion

        #region TOSTRING and CLONE
        public override string ToString() { return abbr; }
        public override Distribution Clone() { return new StandardNormalDist(); } 
        #endregion

        #region STATIC
        public static double Cdf(double x)
        {
            return 0.5 * (1.0 + Functions.Erf1(x * Constants.Sqrt1_2));
            //return 0.5 * (1.0 + Erf.calc(x * oneOverSqrt2));
        }
        public static double CdfInv(double p)
        {
            return Constants.Sqrt1_2 * Functions.Erf1Inverse((2.0 * p) - 1.0);
            /*
            double erfInv = Erf.calcInverse((2.0 * p) - 1.0);
            if (double.IsPositiveInfinity(erfInv)) { return double.PositiveInfinity; }
            if (double.IsNegativeInfinity(erfInv)) { return double.NegativeInfinity; }
            return sqrt2 * Erf.calcInverse((2.0 * p) - 1.0);
            //*/
        } 
        #endregion

    }
}