using SeSue.Forms.Common;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using U.ListOperations;
using U.StringOperations;
using U.SUE;
using U.SUE.Algorithms;

namespace SeSue.Forms.Sensitivity
{
    public partial class SensitivityAnalysis : Form
    {
        private string sensitivityType;//demand, fftt
        private double[] originalValues;
        private List<double> epsilons;
        private MSA originalMsa;


        private List<List<double>> linkFlow;
        private List<List<double>> linkCostAdd;
        private List<List<double>> linkCostMul;
        private List<List<double>> pathFlow;
        private List<List<double>> pathCost;
        private List<List<double>> pathProb;
        private DataTable dt;
        private int cbIncrOrMultIndex;
        private bool rbAllChecked;
        private int singleIndex;

        private List<string> exportLines;

        
        #region CONSTRUCTOR
        public SensitivityAnalysis(string sensitivityType, int width, int height, FormWindowState parentWindowState)
        {
            InitializeComponent();
            this.originalMsa = SueForm.sue.msa;
            this.sensitivityType = sensitivityType;
            this.Width = width;
            this.Height = height;
            this.WindowState = parentWindowState;
            this.Text = "Sensitivity Analysis - OD Demand Perturbation";
            if (sensitivityType == "fftt")
            {
                this.Text = "Sensitivity Analysis - Link FFTT Perturbation";
                this.rbAll.Text = "Perturb all links";
                this.rbSingle.Text = "Select link to be perturbed";
                this.cbIncrOrMult.Items[0] = "Increase FFTT by epsilon";
                this.cbIncrOrMult.Items[1] = "Multiply FFTT by epsilon";
            }
            this.Icon = SeSue.Properties.Resources.logo;
            for (int i = 0; i < 5; i++) { this.dgvEpsilon.Rows.Add(); }
            dgvEpsilon.Columns[0].DefaultCellStyle.Alignment = DataGridViewContentAlignment.MiddleRight;
            dgvEpsilon.Columns[0].HeaderCell.Style.Alignment = DataGridViewContentAlignment.MiddleRight;
            dgvEpsilon.Columns[0].DefaultCellStyle.Padding = new Padding(10, 0, 10, 0);
            dgvEpsilon.Columns[0].HeaderCell.Style.Padding = new Padding(10, 0, 10, 0);
            this.dgvEpsilon.Rows[0].Cells[0].ReadOnly = true;
            this.dgvEpsilon.Rows[0].Cells[0].Style.BackColor = SueForm.labelColor;
            this.dgvEpsilon.Rows[0].Cells[0].Style.ForeColor = Color.White;
            this.cbIncrOrMult.SelectedIndex = 0;
            this.cbSingle.Items.Clear(); this.cbOdView.Items.Clear();
            for (int w = 0; w < SueForm.sue.graph.getNbOdPairs(); w++)
            {
                OdPair od = SueForm.sue.graph.getOdPair(w);
                cbOdView.Items.Add(od.getLabel());
            }
            if (sensitivityType == "demand")
            {
                this.originalValues = new double[SueForm.sue.graph.getNbOdPairs()];
                for (int w = 0; w < originalValues.Length; w++)
                {
                    OdPair od = SueForm.sue.graph.getOdPair(w);
                    cbSingle.Items.Add(od.getLabel());
                    originalValues[w] = od.getDemand();
                }
            }
            else
            {
                this.originalValues = new double[SueForm.sue.graph.getNbArcs()];
                for (int a = 0; a < originalValues.Length; a++)
                {
                    Link link = SueForm.sue.graph.getLink(a);
                    cbSingle.Items.Add(link.getLabel());
                    originalValues[a] = link.getLinkCost().getFreeFlowTravelTime();
                }
            }
        } 
        #endregion

        #region SIMPLE EVENTS
        private void dgvEpsilon_CellValidating(object sender, DataGridViewCellValidatingEventArgs e)
        {
            string val = e.FormattedValue.ToString();
            if (val == String.Empty) { return; }
            if (!Str.isNumeric(val))
            {
                Msg.show("Validation Error.", "Perturbation values should be numeric.", this);
                e.Cancel = true;
            }
        }
        private void cbIncrOrMult_SelectedIndexChanged(object sender, EventArgs e)
        {
            if (cbIncrOrMult.SelectedIndex == 0) { this.dgvEpsilon.Rows[0].Cells[0].Value = "0"; }
            else { this.dgvEpsilon.Rows[0].Cells[0].Value = "1"; }
            this.cbIncrOrMultIndex = cbIncrOrMult.SelectedIndex;
        }
        private void SensitivityAnalysis_FormClosing(object sender, FormClosingEventArgs e)
        {
            if (sensitivityType == "demand") { for (int w = 0; w < originalValues.Length; w++) { OdPair od = SueForm.sue.graph.getOdPair(w); od.setDemand(originalValues[w]); } }
            else { for (int a = 0; a < originalValues.Length; a++) { Link link = SueForm.sue.graph.getLink(a); link.setFreeFlowTravelTime(originalValues[a]); } }
            SueForm.sue.msa = originalMsa;
        }
        private void rbAll_CheckedChanged_1(object sender, EventArgs e) { cbSingle.Visible = !rbAll.Checked; rbAllChecked = rbAll.Checked; }
        private void btnMsaSettings_Click(object sender, EventArgs e)
        {
            MsaForm frm = new MsaForm(SueForm.sue.msa, this.Width, this.Height);
            frm.ShowDialog(this);
            if (MsaForm.newMsaCreated) { SueForm.sue.msa = MsaForm.newMsa; }
        }
        #endregion

        #region VIEW CHANGES
        private void cbView_SelectedIndexChanged(object sender, EventArgs e)
        {
            cbOdView.Visible = cbView.SelectedIndex >= 3;
            if (dt == null) { this.dgvReport.DataSource = null; return; }
            if (cbView.SelectedIndex >= 3) { if (cbOdView.SelectedIndex == -1) { cbOdView.SelectedIndex = 0; } cbOdView_SelectedIndexChanged(sender, e); return; }
            Graph graph = SueForm.sue.graph;
            dt.Rows.Clear();
            for (int a = 0; a < graph.getNbArcs(); a++)
            {
                Link link = graph.getLink(a);
                DataRow dr = dt.NewRow();
                dr[0] = a.ToString();
                dr[1] = link.getLabel();
                if (cbView.SelectedIndex == 0) { for (int eps = 0; eps < epsilons.Count; eps++) { dr[2 + eps] = linkFlow[a][eps].ToString("#,###,###,##0.00"); } }
                if (cbView.SelectedIndex == 1) { for (int eps = 0; eps < epsilons.Count; eps++) { dr[2 + eps] = linkCostAdd[a][eps].ToString("#,###,###,##0.00"); } }
                if (cbView.SelectedIndex == 2) { for (int eps = 0; eps < epsilons.Count; eps++) { dr[2 + eps] = linkCostMul[a][eps].ToString("#,###,###,##0.00"); } }
                if (cbView.SelectedIndex == 0) { for (int eps = 1; eps < epsilons.Count; eps++) { dr[1 + epsilons.Count + eps] = ((linkFlow[a][eps] - linkFlow[a][0]) / linkFlow[a][0]).ToString("#,###,###,##0.0000"); } }
                if (cbView.SelectedIndex == 1) { for (int eps = 1; eps < epsilons.Count; eps++) { dr[1 + epsilons.Count + eps] = ((linkCostAdd[a][eps] - linkCostAdd[a][0]) / linkCostAdd[a][0]).ToString("#,###,###,##0.0000"); } }
                if (cbView.SelectedIndex == 2) { for (int eps = 1; eps < epsilons.Count; eps++) { dr[1 + epsilons.Count + eps] = ((linkCostMul[a][eps] - linkCostMul[a][0]) / linkCostMul[a][0]).ToString("#,###,###,##0.0000"); } }
                dt.Rows.Add(dr);
            }
            this.dgvReport.DataSource = this.dt;
            formatDgvResult();
        }
        private void cbOdView_SelectedIndexChanged(object sender, EventArgs e)
        {
            if (dt == null) { this.dgvReport.DataSource = null; return; }
            if (cbOdView.SelectedIndex == -1) { return; }
            dt.Rows.Clear();
            int w = cbOdView.SelectedIndex;
            Graph graph = SueForm.sue.graph;
            int itr = -1;
            for (int i = 0; i < graph.getNbOdPairs(); i++)
            {
                OdPair od = graph.getOdPair(i);
                if (i == w)
                {
                    for (int j = 0; j < od.getPathIndices().Length; j++)
                    {
                        itr += 1;
                        int k = od.getPathIndices()[j];
                        Path path = graph.getPath(k);
                        DataRow dr = dt.NewRow();
                        dr[0] = j.ToString();
                        dr[1] = path.getLabel();
                        if (cbView.SelectedIndex == 3) { for (int eps = 0; eps < epsilons.Count; eps++) { dr[2 + eps] = pathFlow[itr][eps].ToString("#,###,###,##0.00"); } }
                        if (cbView.SelectedIndex == 4) { for (int eps = 0; eps < epsilons.Count; eps++) { dr[2 + eps] = pathCost[itr][eps].ToString("#,###,###,##0.00"); } }
                        if (cbView.SelectedIndex == 5) { for (int eps = 0; eps < epsilons.Count; eps++) { dr[2 + eps] = pathProb[itr][eps].ToString("#,###,###,##0.0000"); } }
                        if (cbView.SelectedIndex == 3) { for (int eps = 1; eps < epsilons.Count; eps++) { dr[1 + epsilons.Count + eps] = ((pathFlow[itr][eps] - pathFlow[itr][0]) / pathFlow[itr][0]).ToString("#,###,###,##0.0000"); } }
                        if (cbView.SelectedIndex == 4) { for (int eps = 1; eps < epsilons.Count; eps++) { dr[1 + epsilons.Count + eps] = ((pathCost[itr][eps] - pathCost[itr][0]) / pathCost[itr][0]).ToString("#,###,###,##0.0000"); } }
                        if (cbView.SelectedIndex == 5) { for (int eps = 1; eps < epsilons.Count; eps++) { dr[1 + epsilons.Count + eps] = ((pathProb[itr][eps] - pathProb[itr][0]) / pathProb[itr][0]).ToString("#,###,###,##0.0000"); } }
                        dt.Rows.Add(dr);
                    }
                    return;
                }
                else { itr = itr + od.getPathIndices().Length; }
            }
            this.dgvReport.DataSource = this.dt;
            formatDgvResult();
        } 
        #endregion

        #region FORMATTING
        private void formatDgvResult()
        {
            for (int i = 0; i < 2; i++)
            {
                dgvReport.Columns[i].DefaultCellStyle.Alignment = DataGridViewContentAlignment.MiddleLeft;
                dgvReport.Columns[i].HeaderCell.Style.Alignment = DataGridViewContentAlignment.MiddleLeft;
                dgvReport.Columns[i].DefaultCellStyle.Padding = new System.Windows.Forms.Padding(5, 0, 0, 0);
                dgvReport.Columns[i].HeaderCell.Style.Padding = new System.Windows.Forms.Padding(2, 0, 0, 0);
            }
            for (int i = 2; i < dgvReport.Columns.Count; i++)
            {
                dgvReport.Columns[i].DefaultCellStyle.Alignment = DataGridViewContentAlignment.MiddleRight;
                dgvReport.Columns[i].HeaderCell.Style.Alignment = DataGridViewContentAlignment.MiddleRight;
                dgvReport.Columns[i].DefaultCellStyle.Padding = new System.Windows.Forms.Padding(0, 0, 5, 0);
                dgvReport.Columns[i].HeaderCell.Style.Padding = new System.Windows.Forms.Padding(0, 0, 2, 0);
            }
            dgvReport.Columns[0].FillWeight = 100;
            dgvReport.Columns[1].FillWeight = 300;
            for (int i = 2; i < dgvReport.Columns.Count; i++)
            {
                dgvReport.Columns[i].FillWeight = 200;
            }
        } 
        #endregion
        
        #region PERTURB
        private void perturb(double epsilon)
        {
            U.SUE.Graph graph = SueForm.sue.graph;
            if (singleIndex == -1)
            {
                string msg = "Selected OD pair does not exist in the network.";
                if (sensitivityType == "fftt") { msg = "Selected link does not exist in the network."; }
                Msg.show("Validation Error.", msg, this); return;
            }
            if (sensitivityType == "demand")
            {
                if (rbAllChecked)
                {
                    if (cbIncrOrMultIndex == 0)
                    {
                        for (int w = 0; w < originalValues.Length; w++)
                        {
                            OdPair od = graph.getOdPair(w);
                            od.setDemand(originalValues[w] + epsilon);
                        }
                    }
                    else if (cbIncrOrMultIndex == 1)
                    {
                        for (int w = 0; w < originalValues.Length; w++)
                        {
                            OdPair od = graph.getOdPair(w);
                            od.setDemand(originalValues[w] * epsilon);
                        }
                    }
                }
                else if (!rbAllChecked)
                {
                    if (cbIncrOrMultIndex == 0)
                    {
                        OdPair od = graph.getOdPair(singleIndex);
                        od.setDemand(originalValues[singleIndex] + epsilon);
                    }
                    else if (cbIncrOrMultIndex == 1)
                    {
                        OdPair od = graph.getOdPair(singleIndex);
                        od.setDemand(originalValues[singleIndex] * epsilon);
                    }
                }
            }
            else if (sensitivityType == "fftt")
            {
                if (rbAllChecked)
                {
                    if (cbIncrOrMultIndex == 0)
                    {
                        for (int a = 0; a < graph.getNbArcs(); a++)
                        {
                            Link link = graph.getLink(a);
                            link.setFreeFlowTravelTime(originalValues[a] + epsilon);
                        }
                    }
                    else if (cbIncrOrMultIndex == 1)
                    {
                        for (int a = 0; a < graph.getNbArcs(); a++)
                        {
                            Link link = graph.getLink(a);
                            link.setFreeFlowTravelTime(originalValues[a] * epsilon);
                        }
                    }
                }
                else if (!rbAllChecked)
                {
                    if (cbIncrOrMultIndex == 0)
                    {
                        Link link = graph.getLink(singleIndex);
                        link.setFreeFlowTravelTime(originalValues[singleIndex] + epsilon);
                    }
                    else if (cbIncrOrMultIndex == 1)
                    {
                        Link link = graph.getLink(singleIndex);
                        link.setFreeFlowTravelTime(originalValues[singleIndex] * epsilon);
                    }
                }
            }
        }
        #endregion

        #region RUN CONTROLS
        private void btnRun_Click(object sender, EventArgs e)
        {
            setSingleIndex();
            prepareEpsilonIndices();
            if (singleIndex == -1)
            {
                string msg = "Selected OD pair does not exist in the network.";
                if (sensitivityType == "fftt") { msg = "Selected link does not exist in the network."; }
                Msg.show("Warning.", msg, this); return;
            }
            if (this.epsilons.Count == 1) { Msg.show("Warning.", "You should enter at least one value for epsilon.", this); return; }
            cbView.SelectedIndex = -1;
            initializeDtAndLists();

            Algorithms.Timer timer = new Algorithms.Timer(); timer.start();
            bgwRun.RunWorkerAsync(); Progress.setText("Initializing."); Progress.show(this);
            timer.stop();
            cbView.SelectedIndex = 0;
            Msg.show("Process completed.", "MSA runs are completed in " + timer.getMiliseconds().ToString() + " miliseconds.", this);
        }
        private void bgwRun_DoWork(object sender, DoWorkEventArgs e)
        {
            for (int i = 1; i < epsilons.Count; i++)
            {
                bgwRun.ReportProgress(0, "Running perturbation " + (i).ToString() + " / " + epsilons.Count.ToString() + ".");
                perturb(epsilons[i]);
                SueForm.sue.msa.run(SueForm.sue.graph, false);
                writeToLists(i);
            }
            bgwRun.ReportProgress(0, "Running perturbation " + epsilons.Count.ToString() + " / " + epsilons.Count.ToString() + ".");
            perturb(epsilons[0]);
            SueForm.sue.msa.run(SueForm.sue.graph, false);
            writeToLists(0);
        }
        private void bgwRun_ProgressChanged(object sender, ProgressChangedEventArgs e) { Progress.setText((string)e.UserState); }
        private void bgwRun_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e) { Progress.hide(); }
        #endregion

        #region RUN HELPERS
        private void setSingleIndex()
        {
            if (!rbSingle.Checked) { singleIndex = 0; return; }
            if (cbSingle.Text == null) { singleIndex = -1; return; }
            if (cbSingle.Text == String.Empty) { singleIndex = -1; return; }
            string label = cbSingle.Text;
            if (sensitivityType == "demand") { for (int w = 0; w < SueForm.sue.graph.getNbOdPairs(); w++) { if (SueForm.sue.graph.getOdPair(w).getLabel() == label) { singleIndex = w; return; } } }
            else if (sensitivityType == "fftt") { for (int a = 0; a < SueForm.sue.graph.getNbArcs(); a++) { if (SueForm.sue.graph.getLink(a).getLabel() == label) { singleIndex = a; return; } } }
            singleIndex = -1;
        }
        private void prepareEpsilonIndices()
        {
            this.epsilons = new List<double>();
            for (int i = 0; i < this.dgvEpsilon.Rows.Count; i++) { if (this.dgvEpsilon.Rows[i].Cells[0].Value != null) { if (this.dgvEpsilon.Rows[i].Cells[0].Value.ToString() != String.Empty) { this.epsilons.Add(Str.toDouble(this.dgvEpsilon.Rows[i].Cells[0].Value.ToString())); } } }
        }
        private void initializeDtAndLists()
        {
            dt = new DataTable();
            linkFlow = new List<List<double>>();
            linkCostMul = new List<List<double>>();
            linkCostAdd = new List<List<double>>();
            pathFlow = new List<List<double>>();
            pathCost = new List<List<double>>();
            pathProb = new List<List<double>>();
            dt.Columns.Add("Index");
            dt.Columns.Add("Label");
            for (int i = 0; i < epsilons.Count; i++) { dt.Columns.Add(epsilons[i].ToString()); }
            for (int i = 1; i < epsilons.Count; i++) { dt.Columns.Add("Δ " + epsilons[i].ToString()); }
            Graph graph = SueForm.sue.graph;
            for (int a = 0; a < graph.getNbArcs(); a++)
            {
                linkFlow.Add(UList.sameValues<double>(0, epsilons.Count));
                linkCostAdd.Add(UList.sameValues<double>(0, epsilons.Count));
                linkCostMul.Add(UList.sameValues<double>(0, epsilons.Count));
            }
            for (int w = 0; w < graph.getNbOdPairs(); w++)
            {
                OdPair od = graph.getOdPair(w);
                for (int k = 0; k < od.getPathIndices().Length; k++)
                {
                    pathFlow.Add(UList.sameValues<double>(0, epsilons.Count));
                    pathCost.Add(UList.sameValues<double>(0, epsilons.Count));
                    pathProb.Add(UList.sameValues<double>(0, epsilons.Count));
                }
            }
        }
        private void writeToLists(int epsilonIndex)
        {
            Graph graph = SueForm.sue.graph;
            for (int a = 0; a < graph.getNbArcs(); a++)
            {
                Link link = graph.getLink(a);
                linkFlow[a][epsilonIndex] = link.getFlow();
                linkCostAdd[a][epsilonIndex] = link.getCostAdd();
                linkCostMul[a][epsilonIndex] = link.getCostMul();
            }
            int itr = -1;
            for (int w = 0; w < graph.getNbOdPairs(); w++)
            {
                OdPair od = graph.getOdPair(w);
                for (int k = 0; k < od.getPathIndices().Length; k++)
                {
                    itr += 1;
                    Path path = graph.getPath(od.getPathIndices()[k]);
                    pathFlow[itr][epsilonIndex] = path.getFlow();
                    pathCost[itr][epsilonIndex] = path.getCost();
                    pathProb[itr][epsilonIndex] = path.getProb();
                }
            }
        } 
        #endregion
        
        #region EXPORT
        private void btnExport_Click(object sender, EventArgs e)
        {
            if (linkFlow == null) { Msg.show("Error.", "There is no sensitivity analysis to export.", this); return; }
            if (linkFlow.Count == 0) { Msg.show("Error.", "There is no sensitivity analysis to export.", this); return; }
            List<List<double>> lstVals;
            if (cbView.SelectedIndex == 0) { lstVals = linkFlow; }
            else if (cbView.SelectedIndex == 1) { lstVals = linkCostAdd; }
            else if (cbView.SelectedIndex == 2) { lstVals = linkCostMul; }
            else if (cbView.SelectedIndex == 3) { lstVals = pathFlow; }
            else if (cbView.SelectedIndex == 4) { lstVals = pathCost; }
            else { lstVals = pathProb; }
            List<object> args = new List<object>() { lstVals, cbView.SelectedIndex };
            bgwExport.RunWorkerAsync(args); Progress.setText("Preparing file."); Progress.show(this);
            string path = U.IO.OTextFile.createAndGetFilename(this.exportLines);
            if (path != String.Empty) { Msg.show("Export.", path + " created.", this); this.exportLines = null; }
        }
        private void prepareExportList(List<List<double>> lstVals, int viewIndex)
        {
            Graph graph = SueForm.sue.graph;
            exportLines = new List<string>();
            string line = "a" + '\t' + "Label";
            if (viewIndex >= 3) { line = "w" + '\t' + "k" + '\t' + "Label"; }
            for (int i = 0; i < epsilons.Count; i++) { line = line + '\t' + epsilons[i].ToString(); }
            for (int i = 1; i < epsilons.Count; i++) { line = line + '\t' + "Delta " + epsilons[i].ToString(); }
            exportLines.Add(line);
            if (viewIndex < 3)
            {
                for (int a = 0; a < linkFlow.Count; a++)
                {
                    Link link = graph.getLink(a);
                    line = a.ToString() + '\t' + link.getLabel();
                    for (int i = 0; i < epsilons.Count; i++) { line = line + '\t' + lstVals[a][i].ToString(); }
                    for (int i = 1; i < epsilons.Count; i++) { line = line + '\t' + ((lstVals[a][i] - lstVals[a][0]) / lstVals[a][0]).ToString(); }
                    exportLines.Add(line);
                }
            }
            else
            {
                int itr = -1;
                for (int w = 0; w < graph.getNbOdPairs(); w++)
                {
                    OdPair od = graph.getOdPair(w);
                    for (int k = 0; k < od.getPathIndices().Length; k++)
                    {
                        Path path = graph.getPath(od.getPathIndices()[k]);
                        itr += 1;
                        line = w.ToString() + '\t' + k.ToString() + '\t' + path.getLabel();
                        for (int i = 0; i < epsilons.Count; i++) { line = line + '\t' + lstVals[itr][i].ToString(); }
                        for (int i = 1; i < epsilons.Count; i++) { line = line + '\t' + ((lstVals[itr][i] - lstVals[itr][0]) / lstVals[itr][0]).ToString(); }
                        exportLines.Add(line);
                    }
                }
            }
        }
        private void bgwExport_DoWork(object sender, DoWorkEventArgs e)
        {
            List<object> args = (List<object>)e.Argument;
            List<List<double>> lstVals = (List<List<double>>)args[0];
            int viewIndex = (int)args[1];
            prepareExportList(lstVals, viewIndex);
        }
        private void bgwExport_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e) { Progress.hide(); }
        #endregion

        

        
        






    }
}
