    using Algorithms;
using DiscreteChoiceModel;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using U.ListOperations;
using U.MathematicalOperations;
using U.StringOperations;
using U.SUE.Algorithms.MSAClasses;
using U.SUE.Helpers;

namespace U.SUE.Algorithms
{
    public class MSA : Algorithm
    {
        public enum CostType { ADD, MUL, NONE };

        private StepSize stepSize;
        private long maxNbIterations;
        private double rmseThreshold;
        private long nbIterations;
        private long solutionMiliseconds;
        private List<double[]> F;
        private List<double> rmse;
        internal static double mdmLambda;

        private bool addCostUsed;
        private bool mulCostUsed;


        #region CONSTRUCTOR
        public MSA(StepSize stepSize, long maxNbIterations, double rmseThreshold) { this.stepSize = stepSize; this.maxNbIterations = maxNbIterations; this.rmseThreshold = rmseThreshold; this.solutionMiliseconds = -1; this.F = new List<double[]>(); this.rmse = new List<double>(); }
        public MSA(string strMsa)
        {
            List<string> lst = Str.split(strMsa, Str.Delimiter.NewLine);
            lst.RemoveAt(0);
            if (lst.Count < 1) { throw new NotImplementedException(); }
            List<string> lst2 = Str.split(lst[0], Str.Delimiter.Tab);
            this.stepSize = StepSize.fromString(lst2[0]);
            this.maxNbIterations = Convert.ToInt64(lst2[1]);
            this.rmseThreshold = Convert.ToDouble(lst2[2]);
            this.nbIterations = Convert.ToInt64(lst2[3]);
            this.solutionMiliseconds = Convert.ToInt64(lst2[4]);
            this.F = new List<double[]>();
            this.rmse = new List<double>();
            if (lst.Count == 1) { return; }
            List<string> lst3 = Str.split(lst[1], Str.Delimiter.Semicolon);
            foreach (string str3 in lst3) { this.F.Add(UArray.toArray(Str.toDouble(Str.split(str3, Str.Delimiter.Comma)))); }
            List<string> lst4 = Str.split(lst[2], Str.Delimiter.Semicolon);
            foreach (string str4 in lst4) { this.rmse.Add(Str.toDouble(str4)); }
        } 
        #endregion

        #region ALGORITHM
        public void run(Graph graph, bool keepTime)
        {
            if (graph == null) { return; }

            // Initialization
            this.nbIterations = maxNbIterations;
            stepSize.reset();
            F = new List<double[]>();
            rmse = new List<double>();
            graph.initializeForMsa();
            if (addCostUsed) { graph.calcLinkCostsAdd(); }
            if (mulCostUsed) { graph.calcLinkCostsMul(); }

            // First step
            if (keepTime) { base.startTimer(); }
            graph.calcPathCosts();
            graph.performTrafficAssignment();
            F.Add(graph.getLinkFlowArray());
            rmse.Add(-1.0);

            // Iteration
            for (int k = 1; k < maxNbIterations + 1.0; k++)
            {
                // Update
                if (addCostUsed) { graph.calcLinkCostsAdd(); }
                if (mulCostUsed) { graph.calcLinkCostsMul(); }
                graph.calcPathCosts();

                // Direction
                graph.performTrafficAssignment();
                double[] G = graph.getLinkFlowArray();
                double alpha = stepSize.getAlpha(k);
                double[] newF = Linear.linCombi(alpha, G, F[F.Count - 1]);

                // Move
                graph.setLinkFlows(newF);
                F.Add(newF);
                rmse.Add(getRmse(k));
                if (k > 10) { if (rmse[k - 1] < rmseThreshold) { this.nbIterations = k; k = Convert.ToInt32(maxNbIterations) + 1; } }
            }
            if (keepTime) { base.stopTimer(); this.solutionMiliseconds = Convert.ToInt64(base.getMiliseconds()); }

            // Final Update
            if (addCostUsed) { graph.calcLinkCostsAdd(); }
            if (mulCostUsed) { graph.calcLinkCostsMul(); }
            calcEquilibriumCosts(graph);
        }
        #endregion

        #region SETTER
        public void setMaxNbIterations(long maxNbIterations) { this.maxNbIterations = maxNbIterations; } 
        #endregion

        #region GETTERS
        public StepSize getStepSize() { return this.stepSize; }
        public long getMaxNbIterations() { return this.maxNbIterations; }
        public double getRmseThreshold() { return this.rmseThreshold; }
        public long getSolutionMiliseconds() { return this.solutionMiliseconds; }
        public long getNbIterations() { return this.nbIterations; }
        public List<double> getRmse() { return this.rmse; }
        public List<double[]> getLinkFlows() { return this.F; }
        public double[] getFinalLinkFlows() { return this.F[this.F.Count - 1]; }
        public string getLabel() { return StrFunc.getFuncString("MSA", new List<string>() { this.stepSize.ToString(), maxNbIterations.ToString(), rmseThreshold.ToString() }); }
        public string toString() { return GraphString.msaToString(this); }
        public double getMdmLambda() { return mdmLambda; }
        public bool areAllChoiceModelsSet(Graph graph)
        {
            addCostUsed = false;
            mulCostUsed = false;
            for (int w = 0; w < graph.getOdPairs().Length; w++)
            {
                OdPair od = graph.getOdPair(w);
                if (od == null) { return false; }
                ChoiceModel cm = od.getChoiceModel();
                if (cm == null) { return false; }
                switch (cm.getType())
                {
                    case ChoiceModel.Type.Deterministic: addCostUsed = true; break;
                    case ChoiceModel.Type.Logit: addCostUsed = true; break;
                    case ChoiceModel.Type.MDM:
                        MDM mdm = (MDM)cm;
                        if (mdm.getMarginalDistributions() == null) { return false; }
                        if (mdm.getMarginalDistributions().Length != od.getPathIndices().Length) { return false; }
                        for (int k = 0; k < od.getPathIndices().Length; k++) { if (mdm.getMarginalDistributions()[k] == null) { return false; } }
                        if (mdm.getUtiityFunctionType() == MDM.UtilityFunctionType.Additive) { addCostUsed = true; break; }
                        mulCostUsed = true; break;
                    case ChoiceModel.Type.Weibit: mulCostUsed = true; break;
                }
            }
            return true;
        }
        // to delete
        public override string ToString()
        {

            double miliseconds = -1; try { miliseconds = base.getMiliseconds(); }
            catch (Exception) { }
            string strArr = String.Empty;
            if (this.F != null)
            {
                if (this.F.Count > 1)
                {
                    foreach (double[] iter in this.F)
                    {
                        strArr = strArr + Environment.NewLine + Str.inParenthesis(Str.combine(iter, Str.Delimiter.Semicolon), Str.ParenthesisTypes.Square);
                    }
                }
            }
            return StrFunc.getFuncString("MSA", new List<string>() { stepSize.ToString(), maxNbIterations.ToString(), rmseThreshold.ToString(), miliseconds.ToString(), nbIterations.ToString() }) + strArr;
        } 
        #endregion

        #region PRIVATE
        private void calcEquilibriumCosts(Graph graph)
        {
            foreach (Link link in graph.getLinks()) { link.calcCostAdd(); link.calcCostMul(); }
            graph.calcPathCosts();
            if (addCostUsed) { foreach (Link link in graph.getLinks()) { link.setCost(link.getCostAdd()); } }
            else { foreach (Link link in graph.getLinks()) { link.setCost(link.getCostMul()); } }
        }
        private double getRmse(int k)
        {
            double sum = 0.0;
            for (int a = 0; a < F[k].Length; a++) { sum += Math.Pow(F[k][a] - F[k - 1][a], 2.0); }
            return Math.Sqrt(sum / F[k].Length);
        } 
        #endregion

        #region PUBLIC
        public MSA Clone()
        {
            MSA msa = new MSA(this.stepSize, this.maxNbIterations, this.rmseThreshold);
            msa.nbIterations = this.nbIterations;
            msa.solutionMiliseconds = this.solutionMiliseconds;
            msa.addCostUsed = this.addCostUsed;
            msa.mulCostUsed = this.mulCostUsed;
            msa.F = new List<double[]>();
            foreach (double[] arr in this.F) { msa.F.Add(UArray.clone(arr)); }
            msa.rmse = UList.clone(this.rmse);
            return msa;
        } 
        #endregion

    }
}
