using IOB_UT;
using MapoSDK;
using System;
using System.Collections.Generic;

namespace IOB_WIN
{
    public class IobSimula : IobGeneric
    {
        #region Protected Fields

        /// <summary>
        /// Parametri simulazione oscillazione bit 2
        /// </summary>
        protected simPar bit2;

        /// <summary>
        /// Parametri simulazione oscillazione bit 3
        /// </summary>
        protected simPar bit3;

        /// <summary>
        /// Parametri simulazione oscillazione bit 4
        /// </summary>
        protected simPar bit4;

        /// <summary>
        /// Parametri simulazione oscillazione bit 5
        /// </summary>
        protected simPar bit5;

        /// <summary>
        /// Parametri simulazione oscillazione bit 6
        /// </summary>
        protected simPar bit6;

        /// <summary>
        /// Parametri simulazione oscillazione bit 7
        /// </summary>
        protected simPar bit7;

        /// <summary>
        /// pallet corrente
        /// </summary>
        protected int cP = 1;

        /// <summary>
        /// ultimo controllo decremento eventi
        /// </summary>
        protected DateTime lastEvCheck;

        /// <summary>
        /// Ultimo istante in cui sono stati generati dati di simulazione
        /// </summary>
        protected DateTime lastSimData;

        /// <summary>
        /// Durata minima ODL x reset quando pezzi iob > pezzi macchina...
        /// </summary>
        protected int minDurataODL = 480;

        /// <summary>
        /// pallet successivo (next)
        /// </summary>
        protected int nP = 1;

        /// <summary>
        /// periodo base del simulatore (in millisecondi)
        /// </summary>
        protected int periodoMSec = 1000;

        /// <summary>
        /// variabile di appoggio x stato segnale contapezzo
        /// </summary>
        protected bool sigPzCount = false;

        /// <summary>
        /// BOOL: indica se simulare powerOn/Off (bit 0 e 1) compresi WarmUp e CoolDown
        /// </summary>
        protected bool simPowerOnOff;

        /// <summary>
        /// Tempo di MINIMO attesa x simulazione parametri
        /// </summary>
        protected int waitSimPar = utils.CRI("waitSimPar");

        #endregion Protected Fields

        #region Public Fields

        /// <summary>
        /// Ora spegniemnto (standard)
        /// </summary>
        public int tOff = 22;

        /// <summary>
        /// Ora dia ccensione (standard)
        /// </summary>
        public int tOn = 6;

        #endregion Public Fields

        #region Public Constructors

        /// <summary>
        /// estende l'init della classe base...
        /// </summary>
        /// <param name="caller"></param>
        /// <param name="adpConf"></param>
        public IobSimula(AdapterForm caller, IobConfiguration IOBConf) : base(caller, IOBConf)
        {
            // gestione invio ritardato contapezzi
            
            DateTime adesso = DateTime.Now;
            lastPzCountSend = adesso;
            lastWarnODL = adesso;
            lastEvCheck = adesso;
            lastSimData = adesso;
            // sistemo parametri x simulazione...
            if (cIobConf.optPar.Count > 0)
            {
                if (!string.IsNullOrEmpty(getOptPar("PER_BASE")))
                {
                    int.TryParse(getOptPar("PER_BASE"), out periodoMSec);
                    // aggiungo NOISE... +/- 20%
                    Random rnd = new Random();
                    int noise = rnd.Next(1, periodoMSec / 5);
                    periodoMSec += noise - (periodoMSec / 10);
                }
                simPowerOnOff = false;
                bool.TryParse(getOptPar("SIM_POW_ON_OFF"), out simPowerOnOff);
                int.TryParse(getOptPar("T_ON"), out tOn);
                int.TryParse(getOptPar("T_OFF"), out tOff);
                bit2 = setupSimPar("SIM_PZCNT");
                bit3 = setupSimPar("SIM_ALARM");
                bit4 = setupSimPar("SIM_MANU");
                bit5 = setupSimPar("SIM_SLOW");
                bit6 = setupSimPar("SIM_WUCD");
                bit7 = setupSimPar("SIM_EMRG");
                int.TryParse(getOptPar("MIN_DURATA_ODL"), out minDurataODL);
            }
            setParamPlc();
            // ricarico da server i dati dei pezzi fatti...
            lgInfo("Init contapezzi SIMULA: pzCntReload(true)");
            if (!isMulti)
            {
                pzCntReload(true);
            }
            // imposto pezzi CNC ai pezzi contati da server...
            contapezziPLC = contapezziIOB;
            lgInfo($"Impostazione iniziale contatori: contapezzi macchina contapezziPLC: {contapezziPLC} | contapezziIOB: {contapezziIOB}");
        }

        #endregion Public Constructors

        #region Private Methods

        /// <summary>
        /// Decodifica il resto dell'area x i dati accessori (allarmi, ...)
        /// </summary>
        private void decodeOtherData()
        {
        }

        /// <summary>
        /// Effettua decodifica aree memoria alla bitmap usata x MAPO
        /// </summary>
        private void decodeToBaseBitmap()
        {
            // init a zero...
            B_input = 0;
            bool sendContapezzi = false;
            /* -----------------------------------------------------
             * bitmap MAPO
             * B0: POWER_ON
             * B1: RUN
             * B2: pzCount
             * B3: allarme
             * B4: manuale
             * B5: allarme TCiclo (SLOW)
             * B6: WarmUp_CoolDown
             * B7: EmergArmed (1 = NON emergenza, 0 = emergenza)
             * B8: pallet 1 (SE doppio pallet)
             * B9: pallet 2 (SE doppio pallet)
            ----------------------------------------------------- */

            // di base macchina in RUN
            B_input = 3;

            /*----------------------------------------
             * Simulazione segnali con priorità:
             * - Power ON / OFF (bit0/1)
             * - ALLARMI
             * - MANUALE
             * - SLOW
             * - contapezzi
             * - emergenza
             *
             *----------------------------------------*/

            // se simulo PowerOn/Off --> spegnimento con CoolDown e accensione con WarmUp..
            if (simPowerOnOff)
            {
                DateTime adesso = DateTime.Now;
                // se l'orario è dopo le tOff (tipicamente 22) --> NO RUN...
                if (adesso.Hour >= tOff || adesso.Hour <= tOn)
                {
                    // se prima/ultima mezz'ora è ancora accesa NON in run...
                    if (adesso.AddMinutes(-30).Hour < tOff || adesso.AddMinutes(30).Hour > tOn)
                    {
                        B_input = 1;
                    }
                    else
                    {
                        B_input = 0;
                    }
                    // aggiungo NON emergenza...
                    B_input += (1 << 7);
                }
            }
            // in primis verifico SE posso inviare in blocco i pezzi...... SE MP online e SE NON E' MULTI
            if (MPOnline && !isMulti)
            {
                // SE IOB online...
                if (IobOnline)
                {
                    // se il contapezzi è OLTRE il valore inviato....
                    if (contapezziPLC > contapezziIOB)
                    {
                        // invio SOLO SE sono OLTRE i numSim pz e li invio TUTTI in blocco
                        if ((contapezziPLC - contapezziIOB) > minSendPzCountBlock)
                        {
                            trySendPzCountBlock();
                            sigPzCount = false;
                        }
                        // altrimenti invio 1 segnale
                        else
                        {
                            // se NON STAVA inviando di già...
                            if (!sigPzCount)
                            {
                                // segnalo BIT (1 pz)
                                B_input += (1 << 2);
                                sigPzCount = true;
                                // salvo nuovo contapezzi (incremento di 1...) + richiesta refresh conteggio
                                contapezziIOB++;
                                needRefreshPzCount = true;
                                // invio conferma contapezzi..
                                string retVal = utils.callUrl($"{urlSetPzCount}{contapezziIOB}");
                                // verifica salvataggio
                                if (retVal != contapezziIOB.ToString())
                                {
                                    // errore salvataggio contapezzi
                                    lgInfo($"Errore salvataggio Contapezzi SIMULA 01: contapezziIOB {contapezziIOB} | risposta: {retVal}");
                                    // rileggo il counter pezzi da server
                                    pzCntReload(true);
                                }
                            }
                            else
                            {
                                string retVal = utils.callUrl($"{urlSetPzCount}{contapezziIOB}");
                                if (retVal != contapezziIOB.ToString())
                                {
                                    // errore salvataggio contapezzi
                                    lgInfo($"Errore salvataggio Contapezzi SIMULA 02: contapezziIOB {contapezziIOB} | risposta: {retVal}");
                                    // rileggo il counter pezzi da server
                                    pzCntReload(true);
                                }
                                sigPzCount = false;
                            }
                        }
                        lgInfo($"S01: Valori contatori: contapezzi macchina contapezziPLC: {contapezziPLC} | contapezziIOB: {contapezziIOB} | contapezziPLC > contapezziIOB");
                    }
                }
                else
                {
                    // imposto currODL a vuoto!
                    currODL = "";
                    if (periodicLog)
                    {
                        lgInfo($"SIMULA | Lettura ODL non effettuata: IobOnline: {IobOnline} | currODL impostato a vuoto");
                    }
                }
            }

            // questa parte la processo SOLO SE sono in run --> B_input == 3
            if (B_input == 3)
            {
                // di base NON emergenza
                B_input += (1 << 7);
                // ora controllo il resto...
                if (bit3.wait <= 0)
                {
                    // segnalo BIT
                    B_input += (1 << 3);
                    // decremento duration
                    bit3.duration--;
                    // controllo se sia scaduta la duration... in quel caso reset...
                    if (bit3.duration <= 0)
                    {
                        bit3 = setupSimPar("SIM_ALARM");
                    }
                }
                else if (bit6.wait <= 0)
                {
                    // segnalo BIT
                    B_input += (1 << 6);
                    // decremento duration
                    bit6.duration--;
                    // controllo se sia scaduta la duration... in quel caso reset...
                    if (bit6.duration <= 0)
                    {
                        bit6 = setupSimPar("SIM_WUCD");
                    }
                    // in warm up: provo split ODL
                    trySplitOdl();
                }
                else if (bit7.wait <= 0)
                {
                    // segnalo BIT NEGATO (emergenza)
                    B_input -= (1 << 7);
                    // decremento duration
                    bit7.duration--;
                    // controllo se sia scaduta la duration... in quel caso reset...
                    if (bit7.duration <= 0)
                    {
                        bit7 = setupSimPar("SIM_EMRG");
                    }
                    // in emergenza: provo split ODL
                    trySplitOdl();
                }
                else if (bit4.wait <= 0)
                {
                    // segnalo BIT
                    B_input += (1 << 4);
                    // decremento duration
                    bit4.duration--;
                    // controllo se sia scaduta la duration... in quel caso reset...
                    if (bit4.duration <= 0)
                    {
                        bit4 = setupSimPar("SIM_MANU");
                    }
                    // in manuale: provo split ODL
                    trySplitOdl();
                }
                else if (bit2.wait <= 0)
                {
                    // salvo nuovo contapezziPLC
                    var rand = new Random();
                    // se online vero delta (0..3) altrimenti 1
                    int delta = IobOnline ? rand.Next(1, 4) : 1;
                    if (!isMulti)
                    {
                        // solo se MP online...
                        if (MPOnline)
                        {
                            contapezziPLC += delta;
                            lgInfo($"S01: Valori contatori: contapezzi macchina contapezziPLC: {contapezziPLC} | contapezziIOB: {contapezziIOB} | aggiunto delta {delta}");
                        }
                    }
                    // SOLO SE sono online...
                    if (IobOnline)
                    {
                        // se multipallet --> cP a zero!
                        if (isMulti)
                        {
                            cP = 0;
                        }
                        // se NON Multi fa contapezzi...
                        else
                        {
                            // SE NON SONO GIA' OLTRE il contapezzi
                            if (contapezziIOB < contapezziPLC)
                            {
                                // segnalo BIT (1 pz)
                                B_input += (1 << 2);
                            }
                        }
                        // decremento duration
                        bit2.duration--;
                        // controllo se sia scaduta la duration... in quel caso reset...
                        if (bit2.duration <= 0)
                        {
                            bit2 = setupSimPar("SIM_PZCNT");
                            // salvo nuovo contapezzi (incremento di 1...) + richiesta refresh conteggio
                            contapezziIOB++;
                            needRefreshPzCount = true;
                            lgInfo($"S01: Valori contatori: contapezzi macchina contapezziPLC: {contapezziPLC} | contapezziIOB: {contapezziIOB} - incremento contapezzi per bit2.duration <= 0");
                            // invio SOLO SE il contapezzi PLC è <= contapezzi IOB...
                            if (contapezziPLC <= contapezziIOB)
                            {
                                sendContapezzi = true;
                            }
                        }
                        if (sendContapezzi)
                        {
                            // controllo se ALMENO sia pingabile il server
                            if (checkServerAlive)
                            {
                                // invio a server contapezzi (aggiornato)
                                string retVal = utils.callUrl(urlSetPzCount + contapezziIOB.ToString());
                                // verifica se tutto OK
                                if (retVal != contapezziIOB.ToString())
                                {
                                    // errore salvataggio contapezzi
                                    lgInfo($"Errore salvataggio Contapezzi SIMULA 03: contapezziIOB {contapezziIOB} | risposta: {retVal}");
                                    // se non sono multi...
                                    if (!isMulti)
                                    {
                                        // rileggo il counter pezzi da server
                                        pzCntReload(true);
                                    }
                                }
                                // resetto timer...
                                lastPzCountSend = DateTime.Now;
                            }
                        }
                        // provo a fare split ODL SE NON E' multi....
                        trySplitOdl();
                    }
                    else
                    {
                        // decremento duration
                        bit2.duration--;
                        if (bit2.duration <= 0)
                        {
                            bit2 = setupSimPar("SIM_PZCNT");
                        }
                    }
                }
                else if (bit5.wait <= 0 || tcMan.alarmDelayTC)
                {
                    // segnalo BIT
                    B_input += (1 << 5);
                    // decremento duration
                    bit5.duration--;
                    // controllo se sia scaduta la duration... in quel caso reset...
                    if (bit5.duration <= 0)
                    {
                        bit5 = setupSimPar("SIM_SLOW");
                    }
                }
                // se multi gestisco il bit delle tavole...
                if (isMulti)
                {
                    // se sono in fase di fronte d'uscita (invio contapezzi) INVERTO nP...
                    if (sendContapezzi)
                    {
                        nP = nP == 1 ? 2 : 1;
                        // assegno a cP il valore nP...
                        cP = nP;
                        contapezziPLC++;
                    }
                    // se cP > 0 --> segnalo bit tavola...
                    if (cP == 1)
                    {
                        B_input += (1 << 8);
                    }
                    else if (cP == 2)
                    {
                        B_input += (1 << 9);
                    }
                }

                // init obj display
                newDisplayData currDispData = new newDisplayData();
                currDispData.counter = contapezziIOB;
                currDispData.semOut = Semaforo.SV;
                raiseRefresh(currDispData);
            }
        }

        /// <summary>
        /// Setup aprametri di simulazione per BIT indicato
        /// </summary>
        /// <param name="keyName"></param>
        private simPar setupSimPar(string keyName)
        {
            simPar answ = new simPar();
            if (cIobConf.optPar.Count > 0)
            {
                if (cIobConf.optPar.ContainsKey(keyName))
                {
                    string fullVal = getOptPar(keyName);
                    if (!string.IsNullOrEmpty(fullVal) && fullVal.IndexOf("|") > 0)
                    {
                        string[] param = fullVal.Split('|');
                        int.TryParse(param[0], out answ.wait);
                        int.TryParse(param[1], out answ.duration);
                        // aggiongo noise, +/- 40%...
                        Random rnd = new Random();
                        int noise = rnd.Next(1, answ.wait * 40 / 100);
                        answ.wait += noise - (answ.wait * 20 / 100);
                    }
                }
            }
            return answ;
        }

        /// <summary>
        ///  provo a chiamare split ODL
        /// </summary>
        private void trySplitOdl()
        {
            if (!isMulti)
            {
                // solo se ODL è in lavorazione da ALMENO minDurataODL minuti...
                DateTime inizioOdl = DateTime.Now.AddDays(-1);
                string rawDataInizio = callUrl(urlInizioOdlIob, false);
                DateTime.TryParse(rawDataInizio, out inizioOdl);
                if (DateTime.Now.Subtract(inizioOdl).TotalMinutes > minDurataODL)
                {
                    // invio reset ODL...
                    forceSplitOdl();
                }
            }
        }

        #endregion Private Methods

        #region Protected Methods

        /// <summary>
        /// Salvo valori PLC
        /// </summary>
        /// <param name="updatedPar"></param>
        protected override void plcWriteParams(List<objItem> updatedPar)
        {
        }

        #endregion Protected Methods

        #region Public Methods

        /// <summary>
        /// Processo i task richiesti e li elimino dalla coda 1:1 (in realtà SOLO forceSetPzCount x ora)
        /// </summary>
        /// <param name="task2exe"></param>
        public override Dictionary<string, string> executeTasks(Dictionary<string, string> task2exe)
        {
            // Verificare il protocollo: dovrebeb togliere SOLO i task eseguiti...
            Dictionary<string, string> taskDone = new Dictionary<string, string>();
            string taskVal = "";
            // verifico non sia null
            if (task2exe != null)
            {
                // cerco task specifici
                foreach (var item in task2exe)
                {
                    taskVal = "";
                    // converto richiesta in enum...
                    taskType tName = taskType.nihil;
                    Enum.TryParse(item.Key, out tName);
                    // controllo sulla KEY
                    switch (tName)
                    {
                        case taskType.setArt:
                        case taskType.setComm:
                        case taskType.setProg:
                        case taskType.setPzComm:
                            memMap.mMapWrite[item.Key].value = item.Value;
                            taskVal = $"taskReq: {tName} | key: {item.Key} | val: {item.Value} | UPDATED memMap.mMapWrite";
                            break;

                        case taskType.setParameter:
                            // richiedo da URL i parametri WRITE da popolare
                            lgInfo("Chiamata processMemWriteRequests");
                            taskVal = processMemWriteRequests();
                            // se restituiscce "" faccio altra prova...
                            if (string.IsNullOrEmpty(taskVal))
                            {
                                // i parametri me li aspetto come stringa composta paramName|paramvalue
                                if (item.Value.Contains("|"))
                                {
                                    string[] paramsJob = item.Value.Split('|');
                                    taskVal = $"REQUEST SET PARAMETERS: {paramsJob[0]} --> {paramsJob[1]}";
                                }
                                else
                                {
                                    taskVal = $"WRONG REQUEST FOR SET PARAMETERS: {item.Value} doesnt contain pipe for splitting key/value";
                                }
                            }
                            break;

                        case taskType.nihil:
                        case taskType.fixStopSetup:
                        case taskType.forceResetPzCount:
                        case taskType.sendWatchDogMes2Plc:
                        case taskType.startSetup:
                        case taskType.stopSetup:
                            taskVal = $"taskReq: {tName} | key: {item.Key} | val: {item.Value} | SKIPPED | NO EXEC";
                            break;

                        case taskType.forceSetPzCount:
                            // forzo sul SIM il valore pzCount dell'IOB...
                            int newPzCount = contapezziPLC;
                            bool fatto = int.TryParse(item.Value, out newPzCount);
                            if (fatto)
                            {
                                // verifico SE sia ammesso il cambio ...
                                int deltaPzCount = newPzCount - contapezziPLC;
                                double maxDelta = DateTime.Now.Subtract(plcLastPzRead).TotalMinutes / (plcAvgTc / 60);
                                // se incremento superiore del doppio atteso --> segnalo errore e NON accetto
                                if (deltaPzCount > (maxDelta * maxPzDeltaPerc) / 100)
                                {
                                    lgError($"[DELTA CHECK]: intremento contapezziPLC troppo elevato: lettura {newPzCount} | contapezzi attuale: {contapezziPLC} | ultima lettura PLC: {plcLastPzRead} | TCiclo medio: {plcAvgTc}s | incremento accettato ");
                                }
                                else
                                {
                                    contapezziPLC = newPzCount;
                                    taskVal = $"Set new contapezziPLC: {contapezziPLC}";
                                }
                            }
                            break;

                        default:
                            taskVal = "SKIPPED | NO EXEC";
                            break;
                    }
                    // aggiungo task!
                    taskDone.Add(item.Key, taskVal);
                }
            }

            return taskDone;
        }

        /// <summary>
        /// Recupera e processa allarmi CNC...
        /// </summary>
        public override Dictionary<string, string> getCncAlarms()
        {
            Dictionary<string, string> outVal = new Dictionary<string, string>();
            return outVal;
        }

        /// <summary>
        /// Recupero dati dinamici...
        /// </summary>
        public override Dictionary<string, string> getDynData()
        {
            // valore non presente in vers default... se gestito fare override
            Dictionary<string, string> outVal = new Dictionary<string, string>();
            // verificare periodo SIM parametri... se passato li invio altrimenti NO... FIX a 20 sec
            if (lastSimData.AddSeconds(waitSimPar) < DateTime.Now)
            {
                Random rnd = new Random();
                // controllo conf memorie json (se ci sono...)
                try
                {
                    if (memMap.mMapWrite.Count > 0)
                    {
                        foreach (var item in memMap.mMapWrite)
                        {
                            outVal.Add(item.Key, item.Value.value);
                        }
                    }
                    if (memMap.mMapRead.Count > 0)
                    {
                        foreach (var item in memMap.mMapRead)
                        {
                            // uso factor come valore MAX ammesso
                            int randVal = rnd.Next((int)item.Value.minVal, (int)item.Value.maxVal);
                            outVal.Add(item.Key, randVal.ToString());
                        }
                    }
                }
                catch
                { }
                lastSimData = DateTime.Now;
            }
            return outVal;
        }

        /// <summary>
        /// Recupero programma in lavorazione
        /// </summary>
        /// <returns></returns>
        public override string getPrgName()
        {
            // valore non presente in vers default... se gestito fare override
            string prgName = string.Format("DEMO_{0:00}", DateTime.Now.Minute);
            return prgName;
        }

        /// <summary>
        /// Recupero info sistema generiche
        /// <returns></returns>
        public override Dictionary<string, string> getSysInfo()
        {
            // valore non presente in vers default... se gestito fare override
            Dictionary<string, string> outVal = new Dictionary<string, string>();
            outVal.Add("MACHINE", "IOB_SIM");
            return outVal;
        }

        /// <summary>
        /// Effettua vero processing contapezzi
        /// </summary>
        public override void processContapezzi()
        {
        }

        /// <summary>
        /// Processo contatori eventi...
        /// </summary>
        public override void processVHF()
        {
            if (lastEvCheck.AddMilliseconds(periodoMSec) < DateTime.Now)
            {
                // decremento contatore ultimo evento
                bit2.wait--;
                bit3.wait--;
                bit4.wait--;
                bit5.wait--;
                lastEvCheck = DateTime.Now;
            }
        }

        /// <summary>
        /// Effettua lettura semafori principale
        /// <paramref name="currDispData">Parametri da aggiornare x display in form</paramref>
        /// </summary>
        public override void readSemafori(ref newDisplayData currDispData)
        {
            base.readSemafori(ref currDispData);
            // decodifica e gestione
            decodeToBaseBitmap();
            decodeOtherData();
            reportRawInput(ref currDispData);
        }

        /// <summary>
        /// Effettua reset del contapezzi
        /// </summary>
        /// <returns></returns>
        public bool resetContapezziPLC()
        {
            bool answ = false;
            // ...SE abilitato da conf IOB
            if (cIobConf.optPar.Count > 0 && getOptPar("ENABLE_PZ_RESET") == "TRUE")
            {
                // fingo di aver fatto...
                answ = true;
            }
            return answ;
        }

        public override void tryConnect()
        {
            base.tryConnect();
            connectionOk = true;
        }

        public override void tryDisconnect()
        {
            base.tryDisconnect();
            connectionOk = false;
        }

        #endregion Public Methods
    }

    /// <summary>
    /// Configuraizone eventi da simulare
    /// </summary>
    public class simPar
    {
        #region Public Fields

        /// <summary>
        /// Durata dell'evento
        /// </summary>
        public int duration = 1;

        /// <summary>
        /// DateTime ultimo evento
        /// </summary>
        public DateTime lastEv = DateTime.Now;

        /// <summary>
        /// Attesa per evento
        /// </summary>
        public int wait = 10;

        #endregion Public Fields
    }
}