using IOB_UT_NEXT;
using IOB_UT_NEXT.Config;
using MapoSDK;
using System;
using System.Collections.Generic;
using System.Linq;

namespace IOB_WIN_SIEMENS.IobSiemens
{
    public class SiemensTorri : Siemens
    {
        /* --------------------------------------------------------------------------------
         * Controlli SIEMENS TORRI
         * - basasto su SIEMENS
         * - S7 vers 1200
         *
         * mod: 2019.01.19: aggiunta gestione segnale test/accensione/spegnimento (DB700.B1.4) --> mod StateMachine!
         * mod: 2019.04.06: aggiunta indicazione (IOB--> PLC) di stato setup su DB701.B0.4
         * mod: 2024.12.05: gestione allarmi da file ext come base Siemens e opzione ENABLE_ALARM_LEGACY
         * -------------------------------------------------------------------------------- */

        #region Public Constructors

        /// <summary>
        /// estende l'init della classe base con i metodi x Siemens specifici x Torri
        /// </summary>
        /// <param name="caller">Form chiamante</param>
        /// <param name="IOBConf">Configurazione (legacy)</param>
        /// <param name="IobConfFull">Configurazione (v 4.x)</param>
        public SiemensTorri(AdapterFormNext caller, IobConfTree IobConfFull) : base(caller, IobConfFull)
        {
            // gestione restart OpcUa client...
            if (!string.IsNullOrEmpty(getOptPar("ENABLE_ALARM_LEGACY")))
            {
                bool.TryParse(getOptPar("ENABLE_ALARM_LEGACY"), out enableAlarmLegacy);
            }

            // provo ad aggiungere un piccolo delay lettura pzCount
            dtVetoReadPzCount = DateTime.Now.AddSeconds(10);

            lgInfo("NEW IOB SIEMENS versione TORRI avviato | vers 2024");
        }

        #endregion Public Constructors

        #region Public Methods

        /// <summary>
        /// Processo i task richiesti e li elimino dalla coda 1:1
        /// </summary>
        /// <param name="task2exe"></param>
        public override Dictionary<string, string> executeTasks(Dictionary<string, string> task2exe, string codTav)
        {
            // Verificare il protocollo: dovrebeb togliere SOLO i task eseguiti...
            Dictionary<string, string> taskDone = new Dictionary<string, string>();
            bool taskOk = false;
            string taskVal = "";
            // inizio con 1 byte VUOTO
            byte[] MemBlock = new byte[1];
            if (task2exe != null)
            {
                // cerco task specifici: se ho startSetup --> imposto bit DBB701.DBB0.4
                foreach (var item in task2exe)
                {
                    taskOk = false;
                    taskVal = "";
                    // converto richiesta in enum...
                    taskType tName = taskType.nihil;
                    Enum.TryParse(item.Key, out tName);
                    // controllo sulla KEY
                    switch (tName)
                    {
                        case taskType.nihil:
                        case taskType.forceResetPzCount:
                        case taskType.forceSetPzCount:
                        case taskType.setArt:
                        case taskType.setComm:
                        case taskType.setProg:
                        case taskType.sendWatchDogMes2Plc:
                            taskVal = $"taskReq: {tName} | key: {item.Key} | val: {item.Value} | SKIPPED | NO EXEC";
                            lgInfo($"Chiamata senza processing: taskOk: {taskOk} | taskVal: {taskVal}");
                            break;

                        case taskType.fixStopSetup:
                            MemBlock[0] = 0;
                            taskVal = "VALUE DB701.0.4 --> 0";
                            lgInfo($"Chiamata fixStopSetup: taskOk: {taskOk} | taskVal: {taskVal}");
                            break;

                        case taskType.startSetup:
                            MemBlock[0] += (1 << 4);
                            taskVal = "VALUE DB701.0.4 --> 1";
                            lgInfo($"Chiamata startSetup: taskOk: {taskOk} | taskVal: {taskVal}");
                            break;

                        case taskType.stopSetup:
                            MemBlock[0] = 0;
                            taskVal = "VALUE DB701.0.4 --> 0";
                            lgInfo($"Chiamata stopSetup: taskOk: {taskOk} | taskVal: {taskVal}");
                            break;

                        default:
                            taskVal = "SKIPPED | NO EXEC";
                            lgInfo($"Chiamata default senza processing: taskOk: {taskOk} | taskVal: {taskVal}");
                            break;
                    }
                    taskDone.Add(item.Key, taskVal);
                }
                // scrivo comunque!
                bool fatto = S7WriteBB(ref MemBlock);
            }
            return taskDone;
        }

        /// <summary>
        /// Recupera ULTIMO allarme...
        /// </summary>
        /// <returns></returns>
        public override Dictionary<string, string> getCncAlarms()
        {
            // parto da ultimo vettore allarmi rilevato
            Dictionary<string, string> outVal = lastReadAlarms;
            // se ho altro --> accodo
            try
            {
                string almMsg = string.Format("{0} | {1}", currPLC.LastErrorCode.ToString(), currPLC.LastErrorString);
                outVal.Add("CNC_ALARM", almMsg);
            }
            catch (Exception exc)
            {
                lgError(exc, "Errore in getCncAlarms");
            }
            // resetto vettore allarmi
            lastReadAlarms = new Dictionary<string, string>();
            return outVal;
        }

        /// <summary>
        /// Recupero dati dinamici...
        /// </summary>
        public override Dictionary<string, string> getDynData()
        {
            Dictionary<string, string> outVal = new Dictionary<string, string>();
#if false
      inizio = DateTime.Now;
      EgwProxy.MultiCncLib.Focas1.ODBDY2_1 answ = FANUC_ref.getAllDynData();
      if (utils.CRB("recTime")) TimingData.addResult(string.Format("PROC-DYN-DATA"), DateTime.Now.Subtract(inizio).Ticks);
      try
      {
        string actf = answ.actf.ToString();
        string acts = answ.acts.ToString();
        //string numAlarm = answ.alarm.ToString();
        // preparo i singoli valori dell'array...
        //outVal.Add("DYNDATA", string.Format("{0}#{1}#{2}", actf, acts, numAlarm));
        outVal.Add("DYNDATA", string.Format("FEED {0}#SPEED_RPM {1}", actf, acts));
        if (utils.CRB("SendFeedSpeed"))
        {
          outVal.Add("FEED", actf);
          outVal.Add("SPEED_RPM", acts);
          //outVal.Add("NUM_ALARM", numAlarm);
        }
        if (utils.CRB("SendAxPos"))
        {
          // salvo le posizioni...
          EgwProxy.MultiCncLib.Focas1.FAXIS posAx = answ.pos;
          int[] currPosAbs = posAx.absolute;
          int i = 0;
          foreach (var item in currPosAbs)
          {
            i++;
            outVal.Add(string.Format("POS_{0:00}", i), item.ToString());
          }
        }
      }
      catch (Exception exc)
      {
        lgError(exc, "Errore in getDynData");
      }
#endif
            return outVal;
        }

        /// <summary>
        /// Recupero dati override in formato dictionary
        /// </summary>
        /// <returns></returns>
        public override Dictionary<string, string> getOverrides()
        {
            Dictionary<string, string> outVal = new Dictionary<string, string>();
            uint valDW = 0;
            // !!!FARE!!! recuperare da conf memoria, ora HARD CODED
            outVal.Add("FEED_OVER", RawInput[19].ToString());
            outVal.Add("RAPID_OVER", RawInput[20].ToString());
            outVal.Add("CURR_MODE", decodeCurrMode(RawInput[21]));
            // recupero RPM pezzo/mola !!!FARE!!! cambio nome da config, qui sono 01:conduttrice e
            // 02:operatrice (3013), mentre sono pezzo/mola nella V100
            valDW = S7.Net.Types.DWord.FromByteArray(RawInput.Skip(24).Take(4).ToArray());
            outVal.Add("RPM_01", valDW.ToString());
            valDW = S7.Net.Types.DWord.FromByteArray(RawInput.Skip(28).Take(4).ToArray());
            outVal.Add("RPM_02", valDW.ToString());
            return outVal;
        }

        /// <summary>
        /// Recupero programma in lavorazione
        /// </summary>
        /// <returns></returns>
        public override string getPrgName()
        {
            string prgName = "";
#if false
      // recupero NUOVO prgName...
      try
      {
        // recupero nome programma MAIN
        prgName = utils.purgedChar2String(FANUC_ref.getPrgNameMain());
        // trimmo path del programma, ovvero "CNCMEMUSERPATH1"
        prgName = prgName.Replace(utils.CRS("basePrgMemPath"), "");
      }
      catch (Exception exc)
      {
        lgError(string.Format("Eccezione in recupero PRG NAME MAIN:{0}{1}", Environment.NewLine, exc));
      }
#endif
            return prgName;
        }

        /// <summary>
        /// Recupero programma in lavorazione come Dictionary FANUC...
        /// - SYSINFO: (prima KEY globale) TUTTI i valori separati da # (x fare check modifica)
        /// - altre stringhe: ogni singolo parametro / valore
        /// </summary>
        /// <returns></returns>
        public override Dictionary<string, string> getSysInfo()
        {
            Dictionary<string, string> outVal = new Dictionary<string, string>();
            try
            {
                outVal.Add("CPU", currPLC.CPU.ToString());
                outVal.Add("MAX_PDU", currPLC.MaxPDUSize.ToString());
                outVal.Add("RACK", currPLC.Rack.ToString());
                outVal.Add("SLOT", currPLC.Slot.ToString());
            }
            catch (Exception exc)
            {
                lgError(exc, "Errore in getSysInfo");
            }
            return outVal;
        }

        /// <summary>
        /// Esegue processing MODE (e nel contempo recupera altri dati dell'area G)
        /// </summary>
        public override void processMode()
        {
            if (utils.CRB("enableMode"))
            {
#if false
        try
        {
          inizio = DateTime.Now;
          // leggo tutto da 0 a 43...
          int memIndex = 0;
          FanucMemRW(R, FANUC.MemType.G, memIndex, ref MemBlockG);
          if (utils.CRB("recTime")) TimingData.addResult(string.Format("R{0}-G-AREA", MemBlockG.Length), DateTime.Now.Subtract(inizio).Ticks);
          // verifico modo con valore corrente, se cambia aggiorno...
          CNC_MODE newMode = decodeG43(MemBlockG[43]);
          if (newMode != currMode)
          {
            // aggiorno!
            currMode = newMode;
            // conversione NUM MODE in descrizione da ENUM
            string descrMode = Enum.GetName(typeof(CNC_MODE), currMode);
            // accodo x invio
            string sVal = string.Format("[CNC_MODE]{0}", descrMode);
            // chiamo accodamento...
            accodaFLog(sVal, qEncodeFLog("CNC_MODE", descrMode));
          }
        }
        catch (Exception exc)
        {
          lgError(exc, "Errore in process Mode G43");
        }
#endif
            }
        }

        /// <summary>
        /// Override salvataggio valori in memoria...
        /// </summary>
        /// <param name="tipo">tipo di DUMP</param>
        public override void saveMemDump(dumpType tipo)
        {
        }

        #endregion Public Methods

        #region Protected Fields

        protected Dictionary<string, string> lastReadAlarms = new Dictionary<string, string>();

        #endregion Protected Fields

        #region Protected Methods

        /// <summary>
        /// Decodifica il resto dell'area TORRI x i dati accessori (allarmi, ...)
        /// </summary>
        protected override void decodeOtherData()
        {
            if (verboseLog)
            {
            }
        }

        /// <summary>
        /// Effettua decodifica aree memoria alla bitmap usata x MAPO
        /// </summary>
        protected override void decodeToBaseBitmap()
        {
            // init a zero...
            B_input = 0;
            // TORRI: leggo i primi 8 bit hard coded...
            int byteSem = RawInput[0];

            // azzero powerOn...
            byteSem &= ~(1 << 0);
            // bit 0 (powerOn) imposto a 1 SE connected...
            if (currPLC.IsConnected)
            {
                byteSem += (1 << 0);
            }

            // azzero i bit NON gestiti (2-5-6-7)
            byteSem &= ~(1 << 2);
            byteSem &= ~(1 << 5);
            byteSem &= ~(1 << 6);
            byteSem &= ~(1 << 7);
            // leggo bit DB700.B1.4 e lo porto al bit 5 --> ciclo test/accensione/spegnimento
            if ((RawInput[1] & (1 << 4)) != 0) //se RawInput[1] & 16-- > 5° bit-- > TEST
            {
                byteSem += (1 << 5);
            }
            // salvo infine variabile bit x invio
            B_input = byteSem;

            // se ho il bit di allarme
            if ((RawInput[0] & (1 << 3)) != 0)
            {
                checkAlarms();
            }

            // procedo SOLO SE è enabled IOB
            if (IobOnline)
            {
                // 2026.04.24 controllo ODL solo se son passati ALMENO 10 sec
                int newIdx = CurrOdl();
                currODL = $"{currIdxODL}";
#if DEBUG
                lgInfo($"SiemensTorri | Verifica ODL | IdxODL {currIdxODL}");
#else
                lgDebug($"SiemensTorri | Verifica ODL | IdxODL {currIdxODL}");
#endif

            }
            else
            {
                // imposto currODL a vuoto!
                currODL = "";
                if (periodicLog)
                {
                    lgInfo($"SiemensTorri | Lettura ODL non effettuata: IobOnline: {IobOnline} | currODL: {currODL} | currIdxODL: {currIdxODL}");
                }
            }

            // verifico NON sia vietato gestione contapezzi
            if (!plcPzCountValid || !rawInputRead)
            {
                lgInfo($"Veto check contapezzi e ODL fino alle {dtVetoReadPzCount}");
            }
            else
            {
                if (!string.IsNullOrEmpty(currODL) && currIdxODL > 0)
                {
                    // controllo se è passato intervallo minimo tra 2 controlli/elaborazioni x
                    // distanziare invio e ridurre letture
                    if (DateTime.Now >= lastPzCountSend.AddMilliseconds(pzCountDelay))
                    {
                        // semplifico: se ho differenza invio in blocco
                        if (contapezziPLC > contapezziIOB)
                        {
                            if ((contapezziPLC - contapezziIOB) > minSendPzCountBlock)
                            {
                                trySendPzCountBlock();
                            }
                            else
                            {

                                // salvo nuovo contapezzi (incremento di 1...) + richiesta refresh conteggio
                                contapezziIOB++;
                                needRefreshPzCount = true;
                                // salvo in semaforo!
                                B_input += 1 << 2;
                            }
                            // registro contapezzi
                            lgInfo($"contapezziPLC SIEMENST-TORRI: {contapezziPLC} | contapezziIOB {contapezziIOB}");
                        }
                    }
                }
                else
                {
                    if (DateTime.Now >= lastPzCountSend.AddMilliseconds(pzCountDelay))
                    {
                        lgInfo($"Attenzione: mancanza ODL non procedo con gestione contapezzi. contapezziPLC SIEMENST-TORRI: {contapezziPLC} | contapezziIOB: {contapezziIOB}");
                        // resetto timer...
                        lastPzCountSend = DateTime.Now;
                    }
                }
                // metto cmq 1 sec a veto verifica contapezzi
                dtVetoReadPzCount = DateTime.Now.AddSeconds(1);
            }
            // log opzionale!
            if (verboseLog)
            {
                lgInfo($"Trasformazione B_input: {B_input}");
            }
        }

        #endregion Protected Methods

        #region Private Fields

        /// <summary>
        ///  gestione allarmi classica torri come eventi FluxLog
        /// </summary>
        private bool enableAlarmLegacy = false;

        #endregion Private Fields

        #region Private Methods

        private int checkAlarmBank(string memAddrAlarms)
        {
            int trovato = 0;
            uint valDW = 0;
            var MemBlockPZ = new byte[4];
            bool fatto = S7ReadBB(ref MemBlockPZ, memAddrAlarms, 4);
            if (fatto)
            {
                valDW = S7.Net.Types.DWord.FromByteArray(MemBlockPZ.ToArray());
                // se <> 0 --> log e accodo a dynData
                if (valDW != 0)
                {
                    string key = $"MTH_ALARM_{memAddrAlarms}_{valDW}";
                    var biteVal = baseUtils.binaryForm(valDW);
                    lgInfo($"Stato allarmi rilevati: {key} | {valDW} | {biteVal}");
                    // accodo a dictionary
                    string almMsg = $"{DateTime.Now} | val {valDW} | {biteVal}";
                    // se non ci fosse aggiungo
                    if (!lastReadAlarms.ContainsKey(key))
                    {
                        lastReadAlarms.Add(key, almMsg);
                    }
                    trovato++;
                }
            }
            return trovato;
        }

        private void checkAlarms()
        {
            int trovati = 0;
            // SE ho la gestione legacy allarmi sennò nulla...
            if (enableAlarmLegacy)
            {
                // test gestione da conf alarmMap...
                if (alarmMaps != null && alarmMaps.Count > 0)
                {
                    foreach (var item in alarmMaps)
                    {
                        // da rivedere procedura x usare metodi di gestione allarmi con check differenze + invio alarmLog...
                        trovati += checkAlarmBank(item.memAddr);
                    }
                }
                // legacy... tolto!
#if false
                else
                {
                    // leggo i banchi allarmi : cablato D700.DBDW2 --> D700 DBDW14, sono 4 banchi a 32 bit
                    // da verificare

                    // ciclo nei 4 banchi...
                    trovati += checkAlarmBank("DB700.DBDW2");
                    trovati += checkAlarmBank("DB700.DBDW6");
                    trovati += checkAlarmBank("DB700.DBDW10");
                    trovati += checkAlarmBank("DB700.DBDW14");
                }
#endif
            }
        }

        #endregion Private Methods
    }
}