using AppData;
using NKC_SDK;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;

namespace NKC_WF.WebUserControls
{
    public partial class cmp_batchDetailSplit : BaseUserControl
    {
        #region Protected Fields

        /// <summary>
        /// Master Place (hard-coded)
        /// </summary>
        protected string PlaceCod = "VIRTNE";

        #endregion Protected Fields

        #region Protected Properties

        protected bool BatchIsAncestor
        {
            get
            {
                return ComLib.BType(BatchId) == BatchType.Ancestor;
            }
        }

        /// <summary>
        /// Status corrente del batch
        /// </summary>
        protected BatchStatus CurrBatchStatus
        {
            get
            {
                return ComLib.BStatus(BatchId);
            }
        }

        protected double fullTime
        {
            get
            {
                double answ = 1;
                if (!string.IsNullOrEmpty(hfFullTime.Value))
                {
                    double.TryParse(hfFullTime.Value, out answ);
                }
                return answ;
            }
            set
            {
                hfFullTime.Value = $"{value}";
            }
        }

        protected int lastValRatio
        {
            get
            {
                int answ = 50;
                if (!string.IsNullOrEmpty(hfLastRatio.Value))
                {
                    int.TryParse(hfLastRatio.Value, out answ);
                }
                return answ;
            }
            set
            {
                hfLastRatio.Value = $"{value}";
            }
        }

        protected bool needSave
        {
            get
            {
                bool answ = true;
                bool.TryParse(hfNeedSave.Value, out answ);
                return answ;
            }
            set
            {
                hfNeedSave.Value = $"{value}";
            }
        }

        /// <summary>
        /// Tabella dei batch Descendant di quello corrente
        /// </summary>
        protected DS_App.BatchListDataTable TabBatchDesc
        {
            get
            {
                return ComLib.BatchDescendant(BatchId);
            }
        }

        protected DS_App.OrderListTreeDataTable TabOrders
        {
            get
            {
                // cerco in redis
                return ComLib.OrdersExtByBatch(BatchId);
            }
        }

        protected double totTime01
        {
            get
            {
                double answ = 0;
                DS_App.OrderListTreeDataTable tabNe01 = DLMan.taOLT.getByBatch(cmp_orderExtListNE01.BatchId);
                answ = tabNe01.Sum(x => x.EstProcTime);
                return answ;
            }
        }

        protected double totTime02
        {
            get
            {
                double answ = 0;
                DS_App.OrderListTreeDataTable tabNe02 = DLMan.taOLT.getByBatch(cmp_orderExtListNE02.BatchId);
                answ = tabNe02.Sum(x => x.EstProcTime);
                return answ;
            }
        }

        protected int valRatio
        {
            get
            {
                int answ = 50;
                if (!string.IsNullOrEmpty(txtRatio.Text))
                {
                    int.TryParse(txtRatio.Text, out answ);
                }
                return answ;
            }
            set
            {
                txtRatio.Text = $"{value}";
                // scrivo ANCHE i valori assoluti
                lblRatio01.Text = $"{valRatio} %";
                lblRatio02.Text = $"{100 - valRatio} %";
                lblTime01.Text = $"{fullTime * value / (100 * 60):N2} h";
                lblTime02.Text = $"{fullTime * (100 - value) / (100 * 60):N2} h";
            }
        }

        #endregion Protected Properties

        #region Public Properties

        public int BatchId
        {
            set
            {
                hfBatchId.Value = value.ToString();
                if (BatchId > 0)
                {
                    checkCreateDescendant();
                    doUpdate();
                }
                fixRatio();
            }
            get
            {
                int answ = 0;
                int.TryParse(hfBatchId.Value, out answ);
                return answ;
            }
        }

        #endregion Public Properties

        #region Private Methods

        private void checkCreateDescendant()
        {
            // se ho un batchId...
            if (BatchId != 0)
            {
                // verifico se ho descendant
                if (TabBatchDesc.Rows.Count == 0)
                {
                    // altrimenti creo...
                    DLMan.taBL.makeDescendantByKey(BatchId, PlaceCod);
                }
            }
        }

        /// <summary>
        /// verifico modalità display (editing solo se PRIMA di lanciare nesting...)
        /// </summary>
        private void checkDisplayMode()
        {
            txtRatio.Visible = (BatchIsAncestor && CurrBatchStatus <= BatchStatus.EstimationDone);
            // se non suddiviso --> indico rosso!
            double tmpTime = totTime01 + totTime02;
            string baseCssClass = "btn btn-success btn-block";
            if (tmpTime < 0.1)
            {
                // imposto colore btn ROSSO...
                baseCssClass = "btn btn-danger btn-block";
            }
            lbtBalance.CssClass = txtRatio.Visible ? baseCssClass : baseCssClass + " disabled";
            cmp_orderExtListNE01.Visible = txtRatio.Visible;
            cmp_orderExtListNE02.Visible = txtRatio.Visible;
            cmp_batchDetailSplitInfoNE01.Visible = !txtRatio.Visible;
            cmp_batchDetailSplitInfoNE02.Visible = !txtRatio.Visible;
        }

        private void Cmp_orderExtListNE01_eh_doRefresh(object sender, EventArgs e)
        {
            // sposto ordine in altro set
            DLMan.taOLT.updateBatch(cmp_orderExtListNE01.BatchId, cmp_orderExtListNE01.SelOrderId, cmp_orderExtListNE02.BatchId);
            fixChildBatch();
            fixRatio();
        }

        private void Cmp_orderExtListNE02_eh_doRefresh(object sender, EventArgs e)
        {
            // sposto ordine in altro set
            DLMan.taOLT.updateBatch(cmp_orderExtListNE02.BatchId, cmp_orderExtListNE02.SelOrderId, cmp_orderExtListNE01.BatchId);
            fixChildBatch();
            fixRatio();
        }

        private void fixChildBatch()
        {
            DS_App.BatchListDataTable TabBatch = TabBatchDesc;
            cmp_orderExtListNE01.BatchId = TabBatch.Rows.Count > 0 ? TabBatchDesc[0].BatchID : 0;
            cmp_orderExtListNE02.BatchId = TabBatch.Rows.Count > 0 ? TabBatchDesc[1].BatchID : 0;
            cmp_orderExtListNE01.doUpdate();
            cmp_orderExtListNE02.doUpdate();
            cmp_batchDetailSplitInfoNE01.BatchId = cmp_orderExtListNE01.BatchId;
            cmp_batchDetailSplitInfoNE02.BatchId = cmp_orderExtListNE02.BatchId;
        }

        private void fixRatio()
        {
            // calcolo tempi....
            double tmpTime = totTime01 + totTime02;
            fullTime = tmpTime > 0 ? tmpTime : 1;
            // sistemazione ratio calcolata...
            double newRatio = (totTime01 * 100 / fullTime);
            valRatio = (int)newRatio;
            lastValRatio = valRatio;
        }

        /// <summary>
        ///  effettua ribilanciamento ordini partendo da elenco completo e poi assegnando a impianti
        /// </summary>
        private void rebalanceOrder()
        {
            // solo se variato il ratio...
            if (valRatio != lastValRatio)
            {
                Stopwatch stopWatch = new Stopwatch();
                stopWatch.Start();
                // costruisco vettore durata ordini
                OrderSetSim FullSet = new OrderSetSim();
                OrderSetSim SetNe01 = new OrderSetSim();
                OrderSetSim SetNe02 = new OrderSetSim();
                // creo liste x i valori da processare...
                Dictionary<int, double> RawList = new Dictionary<int, double>();
                Dictionary<int, double> OrderedList = new Dictionary<int, double>();
                foreach (var item in TabOrders)
                {
                    RawList.Add(item.OrdID, item.EstProcTime);
                }
                // ordino la lista x processing successivo
                OrderedList = RawList.OrderBy(x => x.Value).ToDictionary(t => t.Key, t => t.Value);
                // imposto maxDepth ...
                int maxDepth = 5;
                // salvo i set
                FullSet.OrderSet = OrderedList;
                // lavoro sull'ottimizzare il MINORE
                double ValRatioP = (double)valRatio / 100;
                // se uno è zero
                if (valRatio == 0)
                {
                    // vuoto
                    SetNe01.OrderSet = new Dictionary<int, double>();
                    // tutto
                    SetNe02.OrderSet = OrderedList;
                }
                // ...oppure è 100%...
                else if (valRatio >= 99)
                {
                    // tutto
                    SetNe01.OrderSet = OrderedList;
                    // vuoto
                    SetNe02.OrderSet = new Dictionary<int, double>();
                }
                // altrimenti se è minore...
                else if (valRatio <= 50)
                {
                    SetNe01.TargetValue = FullSet.ActualValue * ValRatioP;
                    SetNe01.OrderSet = findLocalMin(OrderedList, SetNe01.TargetValue, maxDepth);
                    // l'altro è la differenza
                    SetNe02.OrderSet = OrderedList;
                    foreach (var item in SetNe01.OrderSet)
                    {
                        SetNe02.OrderSet.Remove(item.Key);
                    }
                }
                else
                {
                    SetNe02.TargetValue = FullSet.ActualValue * (1 - ValRatioP);
                    SetNe02.OrderSet = findLocalMin(OrderedList, SetNe02.TargetValue, maxDepth);
                    // l'altro è la differenza
                    SetNe01.OrderSet = OrderedList;
                    foreach (var item in SetNe02.OrderSet)
                    {
                        SetNe01.OrderSet.Remove(item.Key);
                    }
                }
                // riorganizzo tabOrders...
                foreach (var orderItem in TabOrders)
                {
                    if (SetNe01.OrderSet.ContainsKey(orderItem.OrdID))
                    {
                        if (orderItem.BatchID != cmp_orderExtListNE01.BatchId)
                        {
                            DLMan.taOLT.updateBatch(orderItem.BatchID, orderItem.OrdID, cmp_orderExtListNE01.BatchId);
                        }
                    }
                    else
                    {
                        if (orderItem.BatchID != cmp_orderExtListNE02.BatchId)
                        {
                            DLMan.taOLT.updateBatch(orderItem.BatchID, orderItem.OrdID, cmp_orderExtListNE02.BatchId);
                        }
                    }
                }
                // --> richiede salvataggio
                needSave = true;
                // salvo tempo calcolo
                stopWatch.Stop();
                Log.Instance.Info($"Rebalance executed | valRatio: {valRatio} | maxDepth: {maxDepth} | elapsed ms: {stopWatch.ElapsedMilliseconds}");
            }
            fixRatio();
        }

        #endregion Private Methods

        #region Protected Methods

        /// <summary>
        /// Cerca il set con lo score migliore calcolando x subset della lista ordinata
        /// </summary>
        /// <param name="OrderedList">Lista ordinata oggetti (INT) + valore</param>
        /// <param name="TargetVal">Valore desiderato (come SOMMA)</param>
        /// <param name="maxDepth">Massima profondità ricorsione accettata (x evitare loop infinito)</param>
        /// <returns></returns>
        protected Dictionary<int, double> findLocalMin(Dictionary<int, double> OrderedList, double TargetVal, int maxDepth)
        {
            Dictionary<int, double> answ = new Dictionary<int, double>();
            List<OrderSetSim> Candidates = new List<OrderSetSim>();
            OrderSetSim CurrSimSet = new OrderSetSim();
            // parte dal valore (singolo) più piccolo tra quelli maggiori del target... se c'è...
            var OrdSup = OrderedList.Where(x => x.Value > TargetVal).OrderBy(x => x.Value);
            if (OrdSup != null && OrdSup.Any())
            {
                var currOrder = OrdSup.FirstOrDefault();
                CurrSimSet = new OrderSetSim();
                CurrSimSet.TargetValue = TargetVal;
                CurrSimSet.OrderSet.Add(currOrder.Key, currOrder.Value);
                Candidates.Add(CurrSimSet);
            }
            // ora guardo gli elementi restanti.. se ci sono
            var OrdInf = OrderedList.Where(x => x.Value <= TargetVal).OrderByDescending(x => x.Value).ToDictionary(t => t.Key, t => t.Value);
            if (OrdInf != null && OrdInf.Any())
            {
                var currOrder = OrdInf.FirstOrDefault();
                CurrSimSet = new OrderSetSim();
                CurrSimSet.TargetValue = TargetVal;
                CurrSimSet.OrderSet.Add(currOrder.Key, currOrder.Value);
                Candidates.Add(CurrSimSet);

                // prendo i restanti tranne il primo
                OrdInf.Remove(currOrder.Key);

                // guardo i successivi che NON superano + corrente...
                Dictionary<int, double> TestOrderSetMinDelta = findStepMin(OrdInf, TargetVal - currOrder.Value);
                TestOrderSetMinDelta.Add(currOrder.Key, currOrder.Value);
                // verifico se migliorativo...
                if (TestOrderSetMinDelta.Count > 0)
                {
                    OrderSetSim CurrSet = new OrderSetSim();
                    CurrSet.TargetValue = TargetVal - currOrder.Value;
                    CurrSet.OrderSet = TestOrderSetMinDelta;
                    Candidates.Add(CurrSet);
                }

                // solo successivi che non superano
                Dictionary<int, double> TestOrderSetMin = findStepMin(OrdInf, TargetVal);
                // verifico se migliorativo...
                if (TestOrderSetMin.Count > 0)
                {
                    OrderSetSim CurrSet = new OrderSetSim();
                    CurrSet.TargetValue = TargetVal - currOrder.Value;
                    CurrSet.OrderSet = TestOrderSetMin;
                    Candidates.Add(CurrSet);
                }

                // se posso fare ricorsioni
                if (maxDepth > 0)
                {
                    maxDepth--;

                    // se rimane qualcosa...
                    if (OrdInf != null && OrdInf.Any())
                    {
                        // calcolo il minimo locale nei 2 casi, da soli
                        Dictionary<int, double> TestOrderSet01 = findLocalMin(OrdInf, TargetVal, maxDepth);
                        if (TestOrderSet01.Count > 0)
                        {
                            OrderSetSim CurrSet = new OrderSetSim();
                            CurrSet.TargetValue = TargetVal;
                            CurrSet.OrderSet = TestOrderSet01;
                            Candidates.Add(CurrSet);
                        }
                        // ...e con il primo valore...
                        Dictionary<int, double> TestOrderSet02 = findLocalMin(OrdInf, TargetVal - currOrder.Value, maxDepth);
                        TestOrderSet02.Add(currOrder.Key, currOrder.Value);
                        // verifico se migliorativo...
                        if (TestOrderSet02.Count > 0)
                        {
                            OrderSetSim CurrSet = new OrderSetSim();
                            CurrSet.TargetValue = TargetVal - currOrder.Value;
                            CurrSet.OrderSet = TestOrderSet02;
                            Candidates.Add(CurrSet);
                        }
                    }
                }
            }
            // cerco il minimo...
            if (Candidates != null && Candidates.Count > 0)
            {
                answ = Candidates.OrderBy(x => x.IndexScore).FirstOrDefault().OrderSet;
            }
            // calcolo il minimo e lo restituisco...
            return answ;
        }

        /// <summary>
        /// Cerca il set della lista ordinata <= target val
        /// </summary>
        /// <param name="OrderedList">Lista ordinata oggetti (INT) + valore</param>
        /// <param name="TargetVal">Valore desiderato (come SOMMA)</param>
        /// <returns></returns>
        protected Dictionary<int, double> findStepMin(Dictionary<int, double> OrderedList, double TargetVal)
        {
            double CurrVal = 0;
            Dictionary<int, double> answ = new Dictionary<int, double>();
            foreach (var item in OrderedList)
            {
                CurrVal += item.Value;
                if (CurrVal <= TargetVal)
                {
                    answ.Add(item.Key, item.Value);
                }
            }
            // restituisco set minore...
            return answ;
        }

        protected void lbtBalance_Click(object sender, EventArgs e)
        {
            valRatio = 51;
            lastValRatio = 0;
            doUpdate();
        }

        protected void Page_Load(object sender, EventArgs e)
        {
            if (!Page.IsPostBack)
            {
                // verifica visualizzazione
                fixChildBatch();
                fixRatio();
                checkDisplayMode();
            }
            cmp_orderExtListNE01.eh_doRefresh += Cmp_orderExtListNE01_eh_doRefresh;
            cmp_orderExtListNE02.eh_doRefresh += Cmp_orderExtListNE02_eh_doRefresh;
        }

        protected void txtRatio_TextChanged(object sender, EventArgs e)
        {
            doUpdate();
        }

        #endregion Protected Methods

        #region Public Methods

        public void doUpdate()
        {
            rebalanceOrder();
            fixChildBatch();
            checkDisplayMode();
        }

        #endregion Public Methods
    }
}