using DiscreteChoiceModel.MDMHelpers;
using StatisticalDistribution;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using U.ListOperations;
using U.StringOperations;

namespace DiscreteChoiceModel
{
    public class MDM : ChoiceModel
    {
        // Additive: U(k) = V(k) + e(k)       Multiplicative: U(k) = V(k) * e(k)

        internal static double lambda;
        public static string abbr = "MDM";
        public enum UtilityFunctionType { Additive, Multiplicative };
        public enum LambdaAlgorithm { AddSta, MulSta }//, AddDyn, MulDyn }

        

        private Distribution[] mds;
        private UtilityFunctionType utilityFunctionType;
        private double epsilon;

        private double[] cdfInv0;
        private double[] cdfInv1;
        //private double minV;
        //private double maxV;
        private double[] lambdaRange;
        private LambdaAlgorithm lambdaAlgorithm;


        // CONSTRUCTORS
        public MDM(Distribution[] marginalDistributions, UtilityFunctionType utilityFunctionType, double epsilon): base(ChoiceModel.Type.MDM)
        {
            this.utilityFunctionType = utilityFunctionType;
            this.mds = marginalDistributions;
            this.epsilon = epsilon;
            setDistBounds();
            setLambdaAlgorithm();
        }
        public MDM(Distribution[] marginalDistributions, UtilityFunctionType utilityFunctionType, double epsilon, double minV, double maxV) : base(ChoiceModel.Type.MDM)
        {
            this.utilityFunctionType = utilityFunctionType;
            this.mds = marginalDistributions;
            this.epsilon = epsilon;
            this.epsilon = epsilon;
            setDistBounds();
            setLambdaAlgorithm();
            //this.minV = minV;
            //this.maxV = maxV;
            //if (marginalDistributions != null) { setLambdaRange(null); }
        }


        // OVERRIDE
        public override double[] calcProbabilities(double[] V) { return Prob.get(this, V); }


        // SETTER
        public void setMinMaxV(double minV, double maxV)
        {
            setLambdaRange(minV, maxV);
        }
        public void setMarginalDistributions(Distribution[] marginalDistributions)
        {
            this.mds = marginalDistributions;
            setDistBounds();
            //if (this.lambdaAlgorithm == LambdaAlgorithm.AddSta || this.lambdaAlgorithm == LambdaAlgorithm.MulSta) { setLambdaRange(null); }
        }
        public void setIdenticalMarginalDistributions(Distribution identicalDistribution, int nbAlternatives)
        {
            this.mds = new Distribution[nbAlternatives];
            if (identicalDistribution == null) { for (int k = 0; k < nbAlternatives; k++) { this.mds[k] = null; } return; }
            for (int k = 0; k < nbAlternatives; k++) { this.mds[k] = identicalDistribution.Clone(); }
            setDistBounds();
        }
        public void setMarginalDistribution(Distribution distribution, int marginalDistributionIndex) { this.mds[marginalDistributionIndex] = distribution; setDistBounds(); }
        internal void setLambdaRange(double minV, double maxV)
        {
            switch (lambdaAlgorithm)
            {
                case LambdaAlgorithm.AddSta: lambdaRange = LambdaBounds.getAddSta(minV, maxV, cdfInv0, cdfInv1); break;
                case LambdaAlgorithm.MulSta: lambdaRange = LambdaBounds.getMulSta(minV, maxV, cdfInv0, cdfInv1); break;
                /*case LambdaAlgorithm.AddDyn: lambdaRange = LambdaBounds.getAddDyn(V, cdfInv0, cdfInv1); break;
                case LambdaAlgorithm.MulDyn: lambdaRange = LambdaBounds.getMulDyn(V, cdfInv0, cdfInv1); break;*/
            }
        }



        // GETTER
        public Distribution[] getMarginalDistributions() { return this.mds; }
        public UtilityFunctionType getUtiityFunctionType() { return this.utilityFunctionType; }
        public double getEpsilon() { return this.epsilon; }
        public double[] getCdfInv0() { return this.cdfInv0; }
        public double[] getCdfInv1() { return this.cdfInv1; }
        //public double getMinV() { return this.minV; }
        //public double getMaxV() { return this.maxV; }
        public double getLambdaL() { return lambdaRange[0]; }
        public double getLambdaH() { return lambdaRange[1]; }
        public LambdaAlgorithm getLambdaAlgorithm() { return this.lambdaAlgorithm; }
        public double getLambda() { return lambda; }




        // TOSTRING
        public override string ToString()
        {
            List<string> args = new List<string>();
            switch (this.lambdaAlgorithm)
            {
                case LambdaAlgorithm.AddSta: args.Add("Add"); args.Add(epsilon.ToString()); /*args.Add(minV.ToString()); args.Add(maxV.ToString());*/ break;
                case LambdaAlgorithm.MulSta: args.Add("Mul"); args.Add(epsilon.ToString()); /*args.Add(minV.ToString()); args.Add(maxV.ToString());*/ break;
                /*case LambdaAlgorithm.AddDyn: args.Add("Add"); args.Add(epsilon.ToString()); break;
                case LambdaAlgorithm.MulDyn: args.Add("Mul"); args.Add(epsilon.ToString()); break;*/
            }
            if (this.mds != null)
            {
                List<string> strMds = new List<string>();
                foreach (Distribution md in mds)
                {
                    if (md == null) { strMds.Add("null"); }
                    else { strMds.Add(md.ToString()); }
                }
                args.Add(Str.inParenthesis(Str.combine(strMds, Str.Delimiter.Semicolon), Str.ParenthesisTypes.Square));
            }

            return StrFunc.getFuncString(abbr, args, CMStr.PAR, CMStr.DEL);
        }

        // CLONE
        public override ChoiceModel Clone()
        {
            Distribution[] arr = new Distribution[this.mds.Length];
            for (int i = 0; i < this.mds.Length; i++) { arr[i] = this.mds[i].Clone(); }
            return new MDM(arr, this.utilityFunctionType, epsilon);
        }





        // Private - Constructor
        private void setLambdaAlgorithm(/*bool useDynamic*/)
        {
            if (true/*!useDynamic*/)
            {
                switch (this.utilityFunctionType)
                {
                    case UtilityFunctionType.Additive: this.lambdaAlgorithm = LambdaAlgorithm.AddSta; break;
                    case UtilityFunctionType.Multiplicative: this.lambdaAlgorithm = LambdaAlgorithm.MulSta; break;
                }
            }
            /*else
            {
                switch (this.utilityFunctionType)
                {
                    case UtilityFunctionType.Additive: return LambdaAlgorithm.AddDyn;
                    case UtilityFunctionType.Multiplicative: return LambdaAlgorithm.MulDyn;
                }
            }*/
            //throw new NotImplementedException();
        }
        private void setDistBounds()
        {
            if (mds != null)
            {
                this.cdfInv0 = DistBounds.cdfInv(mds, epsilon);
                this.cdfInv1 = DistBounds.cdfInv(mds, 1.0 - epsilon);
            }
        }
    }
}
