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

namespace U.SUE
{
    public class OdPair
    {
        private string oriLabel;
        private string desLabel;
        private int oriIndex;
        private int desIndex;
        private double demand;
        ChoiceModel choiceModel;
        private int[] pathIndices;


        private PathCost.Type costType;
        private double[] V;
        //private double[] P;
        
        


        // CONSTRUCTORS
        public OdPair(string oriLabel, string desLabel, double demand, ChoiceModel choiceModel)
        {
            this.oriLabel = oriLabel;
            this.desLabel = desLabel;
            this.demand = demand;
            this.choiceModel = choiceModel;
            this.costType = PathCost.getType(choiceModel);
        }
        public OdPair(List<string> args)
        {
            this.oriLabel = args[0];
            this.desLabel = args[1];
            this.oriIndex = Str.toInt(args[2]);
            this.desIndex = Str.toInt(args[3]);
            this.demand = Str.toDouble(args[4]);
            this.choiceModel = ChoiceModel.fromString(args[5]);
            this.pathIndices = GraphString.getArrInt(args[6], Str.Delimiter.Semicolon);
        }

        // to delete
        public OdPair(string strOdpair)
        {
            StrFunc sf = new StrFunc(strOdpair);
            List<string> l = Str.split(sf.getFuncName(), Str.Delimiter.Dash);
            if (l.Count != 2) { throw new InvalidOdException(strOdpair); }
            if (sf.getNbArgs() != 2) { throw new InvalidOdException(strOdpair); }
            if (!Str.isNumeric(sf.getArg(0))) { throw new InvalidOdException(strOdpair); }
            this.oriLabel = l[0];
            this.desLabel = l[1];
            this.demand = sf.getArgDouble(0);
            this.choiceModel = ChoiceModel.fromString(sf.getArg(1));
            this.costType = PathCost.getType(this.choiceModel);
        }



        // SETTERS
        internal void setOriIndex(int oriIndex) { this.oriIndex = oriIndex; }
        internal void setDesIndex(int desIndex) { this.desIndex = desIndex; }
        internal void setPathIndices(int[] pathIndices) { this.pathIndices = pathIndices; this.V = new double[pathIndices.Length]; /*this.P = new double[pathIndices.Length];//*/ }
        public void setDemand(double demand) { this.demand = demand; }
        public void setChoiceModel(ChoiceModel choiceModel) { this.choiceModel = choiceModel; this.costType = PathCost.getType(this.choiceModel); }
        


        // GETTERS
        public string getOriLabel() { return this.oriLabel; }
        public string getDesLabel() { return this.desLabel; }
        public int getOriIndex() { return this.oriIndex; }
        public int getDesIndex() { return this.desIndex; }
        public double getDemand() { return this.demand; }
        public ChoiceModel getChoiceModel() { return this.choiceModel; }
        public int[] getPathIndices() { return this.pathIndices; }
        



        // CALCULATORS
        public void calcPathCosts(Path[] paths, Link[] links)
        {
            this.V = new double[this.pathIndices.Length];
            switch (costType)
            {
                case PathCost.Type.ADD:
                    for (int i = 0; i < pathIndices.Length; i++)
                    {
                        int p = pathIndices[i];
                        double sum = 0.0;
                        foreach (int a in paths[p].getArcIndices()) { sum += links[a].getCostAdd(); }
                        paths[p].setCost(sum);
                        V[i] = -sum;
                    }
                    break;
                case PathCost.Type.MUL:
                    for (int i = 0; i < pathIndices.Length; i++)
                    {
                        int p = pathIndices[i];
                        double sum = 1.0;
                        foreach (int a in paths[p].getArcIndices()) { sum *= links[a].getCostMul(); }
                        paths[p].setCost(sum);
                        V[i] = sum;
                    }
                    break;
            }
        }
        public void calcProbsFlows(Path[] paths)
        {
            double[] P = this.choiceModel.calcProbabilities(V);
            for (int i = 0; i < pathIndices.Length; i++)
            {
                int p = pathIndices[i];
                paths[p].setProb(P[i]);
                paths[p].setFlow(P[i] * demand);
            }
            if (this.choiceModel.getType() == ChoiceModel.Type.MDM) { MDM mdm = (MDM)this.choiceModel; MSA.mdmLambda = mdm.getLambda(); }
        }
        public double calcSpFFTT(Graph graph)
        {
            double c = double.MaxValue;
            foreach (int p in this.pathIndices)
            {
                Path path = graph.getPath(p);
                c = Math.Min(c, path.calcFFTT(graph));
            }
            return c;
        }
        public double[] calcLengths(Graph graph)
        {
            double[] lengths = new double[pathIndices.Length];
            for (int p = 0; p < pathIndices.Length; p++)
            {
                Path path = graph.getPath(pathIndices[p]);
                foreach (int a in path.getArcIndices()) { lengths[p] += graph.getLink(a).getLength(); }
            }
            return lengths;
        }
        public double[] calcCommonalityFactors(Graph graph, double beta0, double gamma)
        {
            double[] c = new double[pathIndices.Length];
            double[] lengths = calcLengths(graph);
            for (int p1 = 0; p1 < pathIndices.Length; p1++)
            {
                Path path1 = graph.getPath(pathIndices[p1]);
                double sum = 0.0;
                List<int> arcs1 = UList.toList(path1.getArcIndices());
                for (int p2 = 0; p2 < pathIndices.Length; p2++)
                {
                    Path path2 = graph.getPath(pathIndices[p2]);
                    List<int> arcs2 = UList.toList(path2.getArcIndices());
                    List<int> sharedArcs = UList.intersection(arcs1, arcs2);
                    double sharedLen = 0.0;
                    foreach (int a in sharedArcs) { sharedLen += graph.getLink(a).getLength(); }
                    sum += Math.Pow(sharedLen / Math.Sqrt(lengths[p1] * lengths[p2]), gamma);
                }
                c[p1] = beta0 * Math.Log(sum);
            }
            Array.Clear(lengths, 0, lengths.Length); lengths = null;
            return c;
        }
        public double[] calcPathSize(Graph graph)
        {
            double[] c = new double[pathIndices.Length];
            int[] nbUsed = new int[graph.getLinks().Length];
            double[] lengths = calcLengths(graph);
            foreach (int p in pathIndices)
            {
                Path path = graph.getPath(p);
                foreach (int a in path.getArcIndices()) { nbUsed[a] += 1; }
            }
            for (int p = 0; p < pathIndices.Length; p++)
            {
                Path path = graph.getPath(pathIndices[p]);
                double sum = 0.0;
                foreach (int a in path.getArcIndices())
                {
                    sum += graph.getLink(a).getLength() / nbUsed[a];
                }
                c[p] = sum / lengths[p];
            }
            Array.Clear(nbUsed, 0, nbUsed.Length); nbUsed = null;
            Array.Clear(lengths, 0, lengths.Length); lengths = null;
            return c;
        }



        //COMMON
        public string getLabel() { return Str.combine(this.getOriLabel(), this.getDesLabel(), Str.Delimiter.Dash); }
        public override string ToString()
        {
            List<string> args = new List<string>() { this.demand.ToString(), "null" };
            if (this.choiceModel != null) { args[1] = this.choiceModel.ToString(); }
            return StrFunc.getFuncString(this.getLabel(), args);
        }
        public bool Equals(OdPair odPair) { return this.oriLabel == odPair.getOriLabel() && this.desLabel == odPair.getDesLabel(); }
        public OdPair clone() { return null; }//TODO


        // EXCEPTIONS
        public class InvalidOdException : Exception { public InvalidOdException(string message) : base("Invalid OD pair: " + message + ".") { } }


        // Private Constructor
        private void construct(string oriLabel, string desLabel, StrFunc sf)
        {
            this.oriLabel = oriLabel;
            this.desLabel = desLabel;
            this.demand = sf.getArgDouble(0);
            if (sf.getNbArgs() == 1) { return; }
        }
        private void construct(string oriLabel, string desLabel, double demand, ChoiceModel choiceModel)
        {
            this.oriLabel = oriLabel;
            this.desLabel = desLabel;
            this.demand = demand;
            
        }
    }
}
