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
{
    class CMStr
    {
        internal const Str.ParenthesisTypes PAR = Str.ParenthesisTypes.Regular;
        internal const Str.Delimiter DEL = Str.Delimiter.Comma;

        private static string errStr = "Invalid choice model string, ";

        private static List<ChoiceModel.Type> lstTypes = new List<ChoiceModel.Type>()
        {
            ChoiceModel.Type.Deterministic,
            ChoiceModel.Type.Logit,
            ChoiceModel.Type.Weibit,
            ChoiceModel.Type.MDM
        };
        private static List<string> lstTypeStrings = new List<string>()
        {
            Deterministic.abbr.ToUpper(),
            Logit.abbr.ToUpper(),
            Weibit.abbr.ToUpper(),
            MDM.abbr.ToUpper()
        };
        

        internal static ChoiceModel fromString(string str)
        {
            if (str.ToUpper() == "NULL") { return null; }
            StrFunc sf = new StrFunc(str, CMStr.PAR, CMStr.DEL);
            int index = lstTypeStrings.IndexOf(sf.getFuncName().ToUpper());
            if (index == -1) { throw new ArgumentException(errStr + "unknown identifier '" + str + "'."); }
            ChoiceModel.Type type = lstTypes[index];
            switch (type)
            {
                case ChoiceModel.Type.Deterministic:
                    checkNbOfArguments(sf.getNbArgs(), new List<int>() { 1 }, str);
                    string arg = sf.getArgs()[0];
                    if (arg.ToUpper() == "MAX") { return new Deterministic(true); }
                    if (arg.ToUpper() == "MIN") { return new Deterministic(false); }
                    throw new ArgumentException(errStr + "argument should be Max or Min '" + str + "'.");
                case ChoiceModel.Type.Logit:
                    checkNbOfArguments(sf.getNbArgs(), new List<int>() { 1, 2 }, str);
                    double theta = dblArg(sf.getArg(0), "theta", str);
                    if (sf.getNbArgs() == 1) { return new Logit(theta); }
                    return new Logit(theta, doubleArray(sf.getArg(1), "correction terms", str));
                case ChoiceModel.Type.Weibit:
                    checkNbOfArguments(sf.getNbArgs(), new List<int>() { 2, 3 }, str);
                    double xi = dblArg(sf.getArg(0), "xi", str);
                    double beta = dblArg(sf.getArg(1), "beta", str);
                    if (sf.getNbArgs() == 2) { return new Weibit(xi, beta); }
                    return new Weibit(xi, beta, doubleArray(sf.getArg(2), "correction terms", str));
                case ChoiceModel.Type.MDM:
                    checkNbOfArguments(sf.getNbArgs(), new List<int>() { 2, 3 }, str);
                    MDM.UtilityFunctionType utType = mdmArgUtility(sf.getArg(0), str);
                    double epsilon = dblArg(sf.getArg(1), "epsilon", str);
                    if (sf.getNbArgs() == 2) { return new MDM(null, utType, epsilon); }
                    //if (sf.getNbArgs() == 3) { return new MDM(mdmDists(sf.getArg(2), str), utType, epsilon); }
                    //if (sf.getNbArgs() == 4) { return new MDM(null, utType, epsilon, dblArg(sf.getArg(2), "minV", str), dblArg(sf.getArg(3), "maxV", str)); }
                    //return new MDM(mdmDists(sf.getArg(4), str), utType, epsilon, dblArg(sf.getArg(2), "minV", str), dblArg(sf.getArg(3), "maxV", str));
                    return new MDM(mdmDists(sf.getArg(2), str), utType, epsilon);
            }
            throw new NotImplementedException();
        }


        private static void checkNbOfArguments(int nbArgs, List<int> possNbArgs, string strChoiceModel) { if (possNbArgs.IndexOf(nbArgs) == -1) { throw new ArgumentException(errStr + "wrong number of arguments: '" + strChoiceModel + "'."); } }
        private static double dblArg(string str, string argName, string strChoiceModel)
        {
            if (!Str.isNumeric(str)) { throw new ArgumentException(errStr + argName + " should be numeric: '" + strChoiceModel + "'."); }
            return Str.toDouble(str);
        }
        private static double[] doubleArray(string str, string argName, string strChoiceModel)
        {
            StrArray strArray = new StrArray(str);
            if (!Str.isNumeric(strArray.getItems())) { throw new ArgumentException(errStr + argName +  " should be numeric: '" + strChoiceModel + "'."); }
            return UArray.toArray(strArray.getItemsDouble());
        }
        private static MDM.UtilityFunctionType mdmArgUtility(string str, string strChoiceModel)
        {
            if (str.ToUpper() == "ADD") { return MDM.UtilityFunctionType.Additive; }
            else if (str.ToUpper() == "MUL") { return MDM.UtilityFunctionType.Multiplicative; }
            throw new ArgumentException(errStr + "undefined argument, should be Add or Mul: '" + strChoiceModel + "'.");
        }
        private static Distribution[] mdmDists(string str, string strChoiceModel)
        {
            StrArray strArray = new StrArray(str);
            List<string> strLst = strArray.getItems();
            Distribution[] arr = new Distribution[strLst.Count];
            for (int i = 0; i < strLst.Count; i++) { arr[i] = Distribution.fromString(strLst[i]); }
            return arr;
        }

    }
}
